2011-01-24 20:01:53 +01:00
|
|
|
<?php
|
|
|
|
|
2013-02-15 16:47:14 +01:00
|
|
|
final class DifferentialRevision extends DifferentialDAO
|
2013-03-30 17:32:29 +01:00
|
|
|
implements
|
|
|
|
PhabricatorTokenReceiverInterface,
|
|
|
|
PhabricatorPolicyInterface,
|
2015-05-11 23:23:35 +02:00
|
|
|
PhabricatorExtendedPolicyInterface,
|
2013-10-25 21:52:00 +02:00
|
|
|
PhabricatorFlaggableInterface,
|
2013-12-26 19:40:52 +01:00
|
|
|
PhrequentTrackableInterface,
|
2014-02-12 17:53:40 +01:00
|
|
|
HarbormasterBuildableInterface,
|
2014-02-19 01:32:55 +01:00
|
|
|
PhabricatorSubscribableInterface,
|
2014-04-18 01:03:24 +02:00
|
|
|
PhabricatorCustomFieldInterface,
|
2014-05-02 03:25:30 +02:00
|
|
|
PhabricatorApplicationTransactionInterface,
|
2014-09-09 23:21:13 +02:00
|
|
|
PhabricatorMentionableInterface,
|
2014-07-21 15:59:22 +02:00
|
|
|
PhabricatorDestructibleInterface,
|
2015-12-21 18:02:55 +01:00
|
|
|
PhabricatorProjectInterface,
|
2016-06-09 17:59:40 +02:00
|
|
|
PhabricatorFulltextInterface,
|
2017-09-06 01:25:28 +02:00
|
|
|
PhabricatorFerretInterface,
|
2017-01-13 15:36:25 +01:00
|
|
|
PhabricatorConduitResultInterface,
|
|
|
|
PhabricatorDraftInterface {
|
2011-01-24 20:01:53 +01:00
|
|
|
|
2013-10-09 22:58:00 +02:00
|
|
|
protected $title = '';
|
2011-01-24 20:01:53 +01:00
|
|
|
protected $status;
|
|
|
|
|
2013-10-09 22:58:00 +02:00
|
|
|
protected $summary = '';
|
|
|
|
protected $testPlan = '';
|
2011-01-24 20:01:53 +01:00
|
|
|
|
2011-01-30 19:37:36 +01:00
|
|
|
protected $authorPHID;
|
2012-03-05 19:51:47 +01:00
|
|
|
protected $lastReviewerPHID;
|
2011-01-24 20:01:53 +01:00
|
|
|
|
2013-10-09 22:58:00 +02:00
|
|
|
protected $lineCount = 0;
|
2011-02-17 23:32:01 +01:00
|
|
|
protected $attached = array();
|
2011-01-27 03:46:34 +01:00
|
|
|
|
2011-05-05 08:09:42 +02:00
|
|
|
protected $mailKey;
|
2012-04-10 21:51:34 +02:00
|
|
|
protected $branchName;
|
2013-09-26 21:36:30 +02:00
|
|
|
protected $repositoryPHID;
|
Denormalize Diff PHIDs onto Revisions
Summary:
Ref T12539. See PHI190. Currently, each Diff has a `revisionID`, but Revisions do not point at the current active diff. To find the active diff for a given revision, we need to issue a separate query.
Furthermore, this query is inefficient for bulk loads: if we have a lot of revisions, we end up querying for all diff IDs for all those revisions first, then selecting the largest ones and querying again to get the actual diff objects. This strategy could likely be optimized but the query is a mess in any case.
In several cases, it's useful to have the active diff PHID without needing to do a second query -- sometimes for convenience, and sometimes for performance.
T12539 is an example of such a case: it would be nice to refine the bucketing logic (which only depends on active diff PHIDs), but it feels bad to make the page heavier to do it.
For now, this is unused. I'll start using it to fix the bucketing issue, and then we can expand it gradually to address other performance/convenience issues.
Test Plan:
- Ran migrations, inspected database, saw sensible values.
- Created a new revision, saw a sensible database value.
- Updated an existing revision, saw database update properly.
Reviewers: amckinley
Reviewed By: amckinley
Maniphest Tasks: T12539
Differential Revision: https://secure.phabricator.com/D18756
2017-11-01 18:42:27 +01:00
|
|
|
protected $activeDiffPHID;
|
|
|
|
|
2013-09-26 21:36:30 +02:00
|
|
|
protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
|
|
|
|
protected $editPolicy = PhabricatorPolicies::POLICY_USER;
|
2016-06-27 22:29:47 +02:00
|
|
|
protected $properties = array();
|
2011-05-05 08:09:42 +02:00
|
|
|
|
2013-09-03 15:02:14 +02:00
|
|
|
private $commits = self::ATTACHABLE;
|
|
|
|
private $activeDiff = self::ATTACHABLE;
|
|
|
|
private $diffIDs = self::ATTACHABLE;
|
|
|
|
private $hashes = self::ATTACHABLE;
|
2013-09-26 21:36:45 +02:00
|
|
|
private $repository = self::ATTACHABLE;
|
2011-01-27 03:46:34 +01:00
|
|
|
|
2013-09-03 15:02:14 +02:00
|
|
|
private $reviewerStatus = self::ATTACHABLE;
|
2014-02-19 01:32:55 +01:00
|
|
|
private $customFields = self::ATTACHABLE;
|
2014-02-19 02:57:45 +01:00
|
|
|
private $drafts = array();
|
|
|
|
private $flags = array();
|
2017-03-28 18:30:58 +02:00
|
|
|
private $forceMap = array();
|
2012-04-10 21:51:34 +02:00
|
|
|
|
2011-04-08 06:59:42 +02:00
|
|
|
const TABLE_COMMIT = 'differential_commit';
|
2011-01-27 03:46:34 +01:00
|
|
|
|
|
|
|
const RELATION_REVIEWER = 'revw';
|
|
|
|
const RELATION_SUBSCRIBED = 'subd';
|
|
|
|
|
2016-06-27 22:29:47 +02:00
|
|
|
const PROPERTY_CLOSED_FROM_ACCEPTED = 'wasAcceptedBeforeClose';
|
2017-10-27 17:55:56 +02:00
|
|
|
const PROPERTY_DRAFT_HOLD = 'draft.hold';
|
2018-04-03 17:09:02 +02:00
|
|
|
const PROPERTY_SHOULD_BROADCAST = 'draft.broadcast';
|
2017-12-13 16:36:06 +01:00
|
|
|
const PROPERTY_LINES_ADDED = 'lines.added';
|
|
|
|
const PROPERTY_LINES_REMOVED = 'lines.removed';
|
2018-04-03 16:25:10 +02:00
|
|
|
const PROPERTY_BUILDABLES = 'buildables';
|
2016-06-27 22:29:47 +02:00
|
|
|
|
2013-10-09 22:58:00 +02:00
|
|
|
public static function initializeNewRevision(PhabricatorUser $actor) {
|
|
|
|
$app = id(new PhabricatorApplicationQuery())
|
|
|
|
->setViewer($actor)
|
2014-07-23 02:03:09 +02:00
|
|
|
->withClasses(array('PhabricatorDifferentialApplication'))
|
2013-10-09 22:58:00 +02:00
|
|
|
->executeOne();
|
|
|
|
|
|
|
|
$view_policy = $app->getPolicy(
|
2014-07-25 00:20:39 +02:00
|
|
|
DifferentialDefaultViewCapability::CAPABILITY);
|
2013-10-09 22:58:00 +02:00
|
|
|
|
2017-10-19 22:35:02 +02:00
|
|
|
if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) {
|
|
|
|
$initial_state = DifferentialRevisionStatus::DRAFT;
|
2018-04-03 17:15:05 +02:00
|
|
|
$should_broadcast = false;
|
2017-10-19 22:35:02 +02:00
|
|
|
} else {
|
|
|
|
$initial_state = DifferentialRevisionStatus::NEEDS_REVIEW;
|
2018-04-03 17:15:05 +02:00
|
|
|
$should_broadcast = true;
|
2017-10-19 22:35:02 +02:00
|
|
|
}
|
|
|
|
|
2013-10-09 22:58:00 +02:00
|
|
|
return id(new DifferentialRevision())
|
|
|
|
->setViewPolicy($view_policy)
|
|
|
|
->setAuthorPHID($actor->getPHID())
|
2015-06-08 19:07:05 +02:00
|
|
|
->attachRepository(null)
|
2016-12-15 19:11:58 +01:00
|
|
|
->attachActiveDiff(null)
|
2017-03-20 23:04:23 +01:00
|
|
|
->attachReviewers(array())
|
2018-04-03 17:15:05 +02:00
|
|
|
->setModernRevisionStatus($initial_state)
|
|
|
|
->setShouldBroadcast($should_broadcast);
|
2013-10-09 22:58:00 +02:00
|
|
|
}
|
|
|
|
|
2015-01-13 20:47:05 +01:00
|
|
|
protected function getConfiguration() {
|
2011-01-26 02:17:19 +01:00
|
|
|
return array(
|
|
|
|
self::CONFIG_AUX_PHID => true,
|
2011-02-17 23:32:01 +01:00
|
|
|
self::CONFIG_SERIALIZATION => array(
|
2011-02-19 23:36:13 +01:00
|
|
|
'attached' => self::SERIALIZATION_JSON,
|
|
|
|
'unsubscribed' => self::SERIALIZATION_JSON,
|
2016-06-27 22:29:47 +02:00
|
|
|
'properties' => self::SERIALIZATION_JSON,
|
2011-02-17 23:32:01 +01:00
|
|
|
),
|
2014-09-29 00:12:58 +02:00
|
|
|
self::CONFIG_COLUMN_SCHEMA => array(
|
|
|
|
'title' => 'text255',
|
|
|
|
'status' => 'text32',
|
|
|
|
'summary' => 'text',
|
|
|
|
'testPlan' => 'text',
|
|
|
|
'authorPHID' => 'phid?',
|
|
|
|
'lastReviewerPHID' => 'phid?',
|
|
|
|
'lineCount' => 'uint32?',
|
|
|
|
'mailKey' => 'bytes40',
|
2014-10-01 16:59:44 +02:00
|
|
|
'branchName' => 'text255?',
|
2014-09-29 00:12:58 +02:00
|
|
|
'repositoryPHID' => 'phid?',
|
|
|
|
),
|
|
|
|
self::CONFIG_KEY_SCHEMA => array(
|
|
|
|
'key_phid' => null,
|
|
|
|
'phid' => array(
|
|
|
|
'columns' => array('phid'),
|
|
|
|
'unique' => true,
|
|
|
|
),
|
|
|
|
'authorPHID' => array(
|
|
|
|
'columns' => array('authorPHID', 'status'),
|
|
|
|
),
|
|
|
|
'repositoryPHID' => array(
|
|
|
|
'columns' => array('repositoryPHID'),
|
|
|
|
),
|
2015-06-17 20:25:01 +02:00
|
|
|
// If you (or a project you are a member of) is reviewing a significant
|
|
|
|
// fraction of the revisions on an install, the result set of open
|
|
|
|
// revisions may be smaller than the result set of revisions where you
|
|
|
|
// are a reviewer. In these cases, this key is better than keys on the
|
|
|
|
// edge table.
|
|
|
|
'key_status' => array(
|
|
|
|
'columns' => array('status', 'phid'),
|
|
|
|
),
|
2014-09-29 00:12:58 +02:00
|
|
|
),
|
2011-01-26 02:17:19 +01:00
|
|
|
) + parent::getConfiguration();
|
|
|
|
}
|
|
|
|
|
2016-06-27 22:29:47 +02:00
|
|
|
public function setProperty($key, $value) {
|
|
|
|
$this->properties[$key] = $value;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getProperty($key, $default = null) {
|
|
|
|
return idx($this->properties, $key, $default);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function hasRevisionProperty($key) {
|
|
|
|
return array_key_exists($key, $this->properties);
|
|
|
|
}
|
|
|
|
|
2014-02-27 01:53:42 +01:00
|
|
|
public function getMonogram() {
|
|
|
|
$id = $this->getID();
|
|
|
|
return "D{$id}";
|
|
|
|
}
|
|
|
|
|
2016-07-01 02:20:29 +02:00
|
|
|
public function getURI() {
|
|
|
|
return '/'.$this->getMonogram();
|
|
|
|
}
|
|
|
|
|
2012-06-23 01:52:08 +02:00
|
|
|
public function loadIDsByCommitPHIDs($phids) {
|
|
|
|
if (!$phids) {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
$revision_ids = queryfx_all(
|
|
|
|
$this->establishConnection('r'),
|
|
|
|
'SELECT * FROM %T WHERE commitPHID IN (%Ls)',
|
|
|
|
self::TABLE_COMMIT,
|
|
|
|
$phids);
|
|
|
|
return ipull($revision_ids, 'revisionID', 'commitPHID');
|
|
|
|
}
|
|
|
|
|
2011-04-08 06:59:42 +02:00
|
|
|
public function loadCommitPHIDs() {
|
|
|
|
if (!$this->getID()) {
|
|
|
|
return ($this->commits = array());
|
|
|
|
}
|
|
|
|
|
|
|
|
$commits = queryfx_all(
|
|
|
|
$this->establishConnection('r'),
|
|
|
|
'SELECT commitPHID FROM %T WHERE revisionID = %d',
|
|
|
|
self::TABLE_COMMIT,
|
|
|
|
$this->getID());
|
|
|
|
$commits = ipull($commits, 'commitPHID');
|
|
|
|
|
|
|
|
return ($this->commits = $commits);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCommitPHIDs() {
|
2013-09-03 15:02:14 +02:00
|
|
|
return $this->assertAttached($this->commits);
|
2011-04-08 06:59:42 +02:00
|
|
|
}
|
|
|
|
|
2011-12-21 02:05:52 +01:00
|
|
|
public function getActiveDiff() {
|
|
|
|
// TODO: Because it's currently technically possible to create a revision
|
|
|
|
// without an associated diff, we allow an attached-but-null active diff.
|
|
|
|
// It would be good to get rid of this once we make diff-attaching
|
|
|
|
// transactional.
|
|
|
|
|
2013-09-03 15:02:14 +02:00
|
|
|
return $this->assertAttached($this->activeDiff);
|
2011-12-21 02:05:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function attachActiveDiff($diff) {
|
|
|
|
$this->activeDiff = $diff;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getDiffIDs() {
|
2013-09-03 15:02:14 +02:00
|
|
|
return $this->assertAttached($this->diffIDs);
|
2011-12-21 02:05:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function attachDiffIDs(array $ids) {
|
|
|
|
rsort($ids);
|
|
|
|
$this->diffIDs = array_values($ids);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function attachCommitPHIDs(array $phids) {
|
|
|
|
$this->commits = array_values($phids);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-02-17 23:32:01 +01:00
|
|
|
public function getAttachedPHIDs($type) {
|
|
|
|
return array_keys(idx($this->attached, $type, array()));
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setAttachedPHIDs($type, array $phids) {
|
|
|
|
$this->attached[$type] = array_fill_keys($phids, array());
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
public function generatePHID() {
|
2011-03-03 03:58:21 +01:00
|
|
|
return PhabricatorPHID::generateNewPHID(
|
2014-07-24 00:05:46 +02:00
|
|
|
DifferentialRevisionPHIDType::TYPECONST);
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
2011-01-27 03:46:34 +01:00
|
|
|
|
2011-01-30 22:20:56 +01:00
|
|
|
public function loadActiveDiff() {
|
|
|
|
return id(new DifferentialDiff())->loadOneWhere(
|
|
|
|
'revisionID = %d ORDER BY id DESC LIMIT 1',
|
|
|
|
$this->getID());
|
|
|
|
}
|
2011-05-05 08:09:42 +02:00
|
|
|
|
|
|
|
public function save() {
|
|
|
|
if (!$this->getMailKey()) {
|
Replace callsites to sha1() that use it to asciify entropy with
Filesystem::readRandomCharacters()
Summary: See T547. To improve auditability of use of crypto-sensitive hash
functions, use Filesystem::readRandomCharacters() in place of
sha1(Filesystem::readRandomBytes()) when we're just generating random ASCII
strings.
Test Plan:
- Generated a new PHID.
- Logged out and logged back in (to test sessions).
- Regenerated Conduit certificate.
- Created a new task, verified mail key generated sensibly.
- Created a new revision, verified mail key generated sensibly.
- Ran "arc list", got blocked, installed new certificate, ran "arc list"
again.
Reviewers: jungejason, nh, tuomaspelkonen, aran, benmathews
Reviewed By: jungejason
CC: aran, epriestley, jungejason
Differential Revision: 1000
2011-10-11 04:22:30 +02:00
|
|
|
$this->mailKey = Filesystem::readRandomCharacters(40);
|
2011-05-05 08:09:42 +02:00
|
|
|
}
|
|
|
|
return parent::save();
|
|
|
|
}
|
2011-01-30 22:20:56 +01:00
|
|
|
|
2012-06-26 18:07:52 +02:00
|
|
|
public function getHashes() {
|
2013-09-03 15:02:14 +02:00
|
|
|
return $this->assertAttached($this->hashes);
|
2012-06-26 18:07:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function attachHashes(array $hashes) {
|
|
|
|
$this->hashes = $hashes;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2017-03-28 18:30:58 +02:00
|
|
|
public function canReviewerForceAccept(
|
|
|
|
PhabricatorUser $viewer,
|
|
|
|
DifferentialReviewer $reviewer) {
|
|
|
|
|
|
|
|
if (!$reviewer->isPackage()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$map = $this->getReviewerForceAcceptMap($viewer);
|
|
|
|
if (!$map) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($map[$reviewer->getReviewerPHID()])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getReviewerForceAcceptMap(PhabricatorUser $viewer) {
|
|
|
|
$fragment = $viewer->getCacheFragment();
|
|
|
|
|
|
|
|
if (!array_key_exists($fragment, $this->forceMap)) {
|
|
|
|
$map = $this->newReviewerForceAcceptMap($viewer);
|
|
|
|
$this->forceMap[$fragment] = $map;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->forceMap[$fragment];
|
|
|
|
}
|
|
|
|
|
|
|
|
private function newReviewerForceAcceptMap(PhabricatorUser $viewer) {
|
|
|
|
$diff = $this->getActiveDiff();
|
|
|
|
if (!$diff) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$repository_phid = $diff->getRepositoryPHID();
|
|
|
|
if (!$repository_phid) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$paths = array();
|
|
|
|
|
|
|
|
try {
|
|
|
|
$changesets = $diff->getChangesets();
|
|
|
|
} catch (Exception $ex) {
|
|
|
|
$changesets = id(new DifferentialChangesetQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withDiffs(array($diff))
|
|
|
|
->execute();
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($changesets as $changeset) {
|
|
|
|
$paths[] = $changeset->getOwnersFilename();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$paths) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$reviewer_phids = array();
|
|
|
|
foreach ($this->getReviewers() as $reviewer) {
|
|
|
|
if (!$reviewer->isPackage()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$reviewer_phids[] = $reviewer->getReviewerPHID();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$reviewer_phids) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load all the reviewing packages which have control over some of the
|
|
|
|
// paths in the change. These are packages which the actor may be able
|
|
|
|
// to force-accept on behalf of.
|
|
|
|
$control_query = id(new PhabricatorOwnersPackageQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))
|
|
|
|
->withPHIDs($reviewer_phids)
|
|
|
|
->withControl($repository_phid, $paths);
|
|
|
|
$control_packages = $control_query->execute();
|
|
|
|
if (!$control_packages) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load all the packages which have potential control over some of the
|
|
|
|
// paths in the change and are owned by the actor. These are packages
|
|
|
|
// which the actor may be able to use their authority over to gain the
|
|
|
|
// ability to force-accept for other packages. This query doesn't apply
|
|
|
|
// dominion rules yet, and we'll bypass those rules later on.
|
|
|
|
$authority_query = id(new PhabricatorOwnersPackageQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))
|
|
|
|
->withAuthorityPHIDs(array($viewer->getPHID()))
|
|
|
|
->withControl($repository_phid, $paths);
|
|
|
|
$authority_packages = $authority_query->execute();
|
|
|
|
if (!$authority_packages) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$authority_packages = mpull($authority_packages, null, 'getPHID');
|
|
|
|
|
|
|
|
// Build a map from each path in the revision to the reviewer packages
|
|
|
|
// which control it.
|
|
|
|
$control_map = array();
|
|
|
|
foreach ($paths as $path) {
|
|
|
|
$control_packages = $control_query->getControllingPackagesForPath(
|
|
|
|
$repository_phid,
|
|
|
|
$path);
|
|
|
|
|
|
|
|
// Remove packages which the viewer has authority over. We don't need
|
|
|
|
// to check these for force-accept because they can just accept them
|
|
|
|
// normally.
|
|
|
|
$control_packages = mpull($control_packages, null, 'getPHID');
|
|
|
|
foreach ($control_packages as $phid => $control_package) {
|
|
|
|
if (isset($authority_packages[$phid])) {
|
|
|
|
unset($control_packages[$phid]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$control_packages) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$control_map[$path] = $control_packages;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$control_map) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// From here on out, we only care about paths which we have at least one
|
|
|
|
// controlling package for.
|
|
|
|
$paths = array_keys($control_map);
|
|
|
|
|
|
|
|
// Now, build a map from each path to the packages which would control it
|
|
|
|
// if there were no dominion rules.
|
|
|
|
$authority_map = array();
|
|
|
|
foreach ($paths as $path) {
|
|
|
|
$authority_packages = $authority_query->getControllingPackagesForPath(
|
|
|
|
$repository_phid,
|
|
|
|
$path,
|
|
|
|
$ignore_dominion = true);
|
|
|
|
|
|
|
|
$authority_map[$path] = mpull($authority_packages, null, 'getPHID');
|
|
|
|
}
|
|
|
|
|
|
|
|
// For each path, find the most general package that the viewer has
|
|
|
|
// authority over. For example, we'll prefer a package that owns "/" to a
|
|
|
|
// package that owns "/src/".
|
|
|
|
$force_map = array();
|
|
|
|
foreach ($authority_map as $path => $package_map) {
|
|
|
|
$path_fragments = PhabricatorOwnersPackage::splitPath($path);
|
|
|
|
$fragment_count = count($path_fragments);
|
|
|
|
|
|
|
|
// Find the package that we have authority over which has the most
|
|
|
|
// general match for this path.
|
|
|
|
$best_match = null;
|
|
|
|
$best_package = null;
|
|
|
|
foreach ($package_map as $package_phid => $package) {
|
|
|
|
$package_paths = $package->getPathsForRepository($repository_phid);
|
|
|
|
foreach ($package_paths as $package_path) {
|
|
|
|
|
|
|
|
// NOTE: A strength of 0 means "no match". A strength of 1 means
|
|
|
|
// that we matched "/", so we can not possibly find another stronger
|
|
|
|
// match.
|
|
|
|
|
|
|
|
$strength = $package_path->getPathMatchStrength(
|
|
|
|
$path_fragments,
|
|
|
|
$fragment_count);
|
|
|
|
if (!$strength) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($strength < $best_match || !$best_package) {
|
|
|
|
$best_match = $strength;
|
|
|
|
$best_package = $package;
|
|
|
|
if ($strength == 1) {
|
|
|
|
break 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($best_package) {
|
|
|
|
$force_map[$path] = array(
|
|
|
|
'strength' => $best_match,
|
|
|
|
'package' => $best_package,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// For each path which the viewer owns a package for, find other packages
|
|
|
|
// which that authority can be used to force-accept. Once we find a way to
|
2017-10-09 19:48:01 +02:00
|
|
|
// force-accept a package, we don't need to keep looking.
|
2017-03-28 18:30:58 +02:00
|
|
|
$has_control = array();
|
|
|
|
foreach ($force_map as $path => $spec) {
|
|
|
|
$path_fragments = PhabricatorOwnersPackage::splitPath($path);
|
|
|
|
$fragment_count = count($path_fragments);
|
|
|
|
|
|
|
|
$authority_strength = $spec['strength'];
|
|
|
|
|
|
|
|
$control_packages = $control_map[$path];
|
|
|
|
foreach ($control_packages as $control_phid => $control_package) {
|
|
|
|
if (isset($has_control[$control_phid])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$control_paths = $control_package->getPathsForRepository(
|
|
|
|
$repository_phid);
|
|
|
|
foreach ($control_paths as $control_path) {
|
|
|
|
$strength = $control_path->getPathMatchStrength(
|
|
|
|
$path_fragments,
|
|
|
|
$fragment_count);
|
|
|
|
|
|
|
|
if (!$strength) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($strength > $authority_strength) {
|
|
|
|
$authority = $spec['package'];
|
|
|
|
$has_control[$control_phid] = array(
|
|
|
|
'authority' => $authority,
|
|
|
|
'phid' => $authority->getPHID(),
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return a map from packages which may be force accepted to the packages
|
|
|
|
// which permit that forced acceptance.
|
|
|
|
return ipull($has_control, 'phid');
|
|
|
|
}
|
|
|
|
|
2015-05-11 23:23:35 +02:00
|
|
|
|
|
|
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
|
|
|
|
|
|
|
|
2013-02-15 16:47:14 +01:00
|
|
|
public function getCapabilities() {
|
|
|
|
return array(
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
|
|
PhabricatorPolicyCapability::CAN_EDIT,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getPolicy($capability) {
|
2013-09-26 21:36:45 +02:00
|
|
|
switch ($capability) {
|
|
|
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
|
|
return $this->getViewPolicy();
|
|
|
|
case PhabricatorPolicyCapability::CAN_EDIT:
|
|
|
|
return $this->getEditPolicy();
|
|
|
|
}
|
2013-02-15 16:47:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
|
2013-09-26 21:36:45 +02:00
|
|
|
// A revision's author (which effectively means "owner" after we added
|
|
|
|
// commandeering) can always view and edit it.
|
|
|
|
$author_phid = $this->getAuthorPHID();
|
|
|
|
if ($author_phid) {
|
|
|
|
if ($user->getPHID() == $author_phid) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-15 16:47:14 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-09-27 17:43:41 +02:00
|
|
|
public function describeAutomaticCapability($capability) {
|
|
|
|
$description = array(
|
|
|
|
pht('The owner of a revision can always view and edit it.'),
|
|
|
|
);
|
|
|
|
|
|
|
|
switch ($capability) {
|
|
|
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
Allow applications to define new policy capabilities
Summary:
Ref T603. I want to let applications define new capabilities (like "can manage global rules" in Herald) and get full support for them, including reasonable error strings in the UI.
Currently, this is difficult for a couple of reasons. Partly this is just a code organization issue, which is easy to fix. The bigger thing is that we have a bunch of strings which depend on both the policy and capability, like: "You must be an administrator to view this object." "Administrator" is the policy, and "view" is the capability.
That means every new capability has to add a string for each policy, and every new policy (should we introduce any) needs to add a string for each capability. And we can't do any piecemeal "You must be a {$role} to {$action} this object" becuase it's impossible to translate.
Instead, make all the strings depend on //only// the policy, //only// the capability, or //only// the object type. This makes the dialogs read a little more strangely, but I think it's still pretty easy to understand, and it makes adding new stuff way way easier.
Also provide more context, and more useful exception messages.
Test Plan:
- See screenshots.
- Also triggered a policy exception and verified it was dramatically more useful than it used to be.
Reviewers: btrahan, chad
Reviewed By: btrahan
CC: chad, aran
Maniphest Tasks: T603
Differential Revision: https://secure.phabricator.com/D7260
2013-10-07 22:28:58 +02:00
|
|
|
$description[] = pht(
|
|
|
|
'If a revision belongs to a repository, other users must be able '.
|
|
|
|
'to view the repository in order to view the revision.');
|
2013-09-27 17:43:41 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $description;
|
|
|
|
}
|
|
|
|
|
2015-05-11 23:23:35 +02:00
|
|
|
|
|
|
|
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
|
|
|
|
$extended = array();
|
|
|
|
|
|
|
|
switch ($capability) {
|
|
|
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
|
|
$repository_phid = $this->getRepositoryPHID();
|
|
|
|
$repository = $this->getRepository();
|
|
|
|
|
|
|
|
// Try to use the object if we have it, since it will save us some
|
|
|
|
// data fetching later on. In some cases, we might not have it.
|
|
|
|
$repository_ref = nonempty($repository, $repository_phid);
|
|
|
|
if ($repository_ref) {
|
|
|
|
$extended[] = array(
|
|
|
|
$repository_ref,
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $extended;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
|
|
|
|
|
|
|
|
|
2013-02-19 02:44:45 +01:00
|
|
|
public function getUsersToNotifyOfTokenGiven() {
|
|
|
|
return array(
|
|
|
|
$this->getAuthorPHID(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-03-20 23:04:23 +01:00
|
|
|
public function getReviewers() {
|
2013-09-03 15:02:14 +02:00
|
|
|
return $this->assertAttached($this->reviewerStatus);
|
2013-07-15 04:18:55 +02:00
|
|
|
}
|
|
|
|
|
2017-03-20 23:04:23 +01:00
|
|
|
public function attachReviewers(array $reviewers) {
|
2017-03-20 20:35:30 +01:00
|
|
|
assert_instances_of($reviewers, 'DifferentialReviewer');
|
2017-03-20 23:04:23 +01:00
|
|
|
$reviewers = mpull($reviewers, null, 'getReviewerPHID');
|
2013-07-15 04:18:55 +02:00
|
|
|
$this->reviewerStatus = $reviewers;
|
|
|
|
return $this;
|
|
|
|
}
|
2013-09-26 21:36:45 +02:00
|
|
|
|
2018-02-07 18:39:27 +01:00
|
|
|
public function hasAttachedReviewers() {
|
|
|
|
return ($this->reviewerStatus !== self::ATTACHABLE);
|
|
|
|
}
|
|
|
|
|
Remove obsolete "relationships" code from Differential
Summary:
Ref T10967. There have been two different ways to load reviewers for a while: `needReviewerStatus()` and `needRelationships()`.
The `needRelationships()` stuff was a false start along time ago that didn't really go anywhere. I believe the idea was that we might want to load several different types of edges (subscribers, reviewers, etc) on lots of different types of objects. However, all that stuff pretty much ended up modularizing so that main `Query` classes did not need to know about it, so `needRelationships()` never got generalized or went anywhere.
A handful of things still use it, but get rid of them: they should either `needReviewerStatus()` to get reviewer info, or the ~3 callsites that care about subscribers can just load them directly.
Test Plan:
- Grepped for removed methods (`needRelationships()`, `getReviewers()`, `getCCPHIDs()`, etc).
- Browsed Diffusion, Differential.
- Called `differential.query`.
It's possible I missed some stuff, but it should mostly show up as super obvious fatals ("call needReviewerStatus() before getReviewerStatus()!").
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T10967
Differential Revision: https://secure.phabricator.com/D17518
2017-03-20 22:02:08 +01:00
|
|
|
public function getReviewerPHIDs() {
|
2017-03-20 23:04:23 +01:00
|
|
|
$reviewers = $this->getReviewers();
|
Remove obsolete "relationships" code from Differential
Summary:
Ref T10967. There have been two different ways to load reviewers for a while: `needReviewerStatus()` and `needRelationships()`.
The `needRelationships()` stuff was a false start along time ago that didn't really go anywhere. I believe the idea was that we might want to load several different types of edges (subscribers, reviewers, etc) on lots of different types of objects. However, all that stuff pretty much ended up modularizing so that main `Query` classes did not need to know about it, so `needRelationships()` never got generalized or went anywhere.
A handful of things still use it, but get rid of them: they should either `needReviewerStatus()` to get reviewer info, or the ~3 callsites that care about subscribers can just load them directly.
Test Plan:
- Grepped for removed methods (`needRelationships()`, `getReviewers()`, `getCCPHIDs()`, etc).
- Browsed Diffusion, Differential.
- Called `differential.query`.
It's possible I missed some stuff, but it should mostly show up as super obvious fatals ("call needReviewerStatus() before getReviewerStatus()!").
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T10967
Differential Revision: https://secure.phabricator.com/D17518
2017-03-20 22:02:08 +01:00
|
|
|
return mpull($reviewers, 'getReviewerPHID');
|
|
|
|
}
|
|
|
|
|
2016-12-14 00:46:46 +01:00
|
|
|
public function getReviewerPHIDsForEdit() {
|
2017-03-20 23:04:23 +01:00
|
|
|
$reviewers = $this->getReviewers();
|
2016-12-14 00:46:46 +01:00
|
|
|
|
|
|
|
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
|
|
|
|
|
|
|
$value = array();
|
|
|
|
foreach ($reviewers as $reviewer) {
|
|
|
|
$phid = $reviewer->getReviewerPHID();
|
2017-03-20 23:04:23 +01:00
|
|
|
if ($reviewer->getReviewerStatus() == $status_blocking) {
|
2016-12-14 00:46:46 +01:00
|
|
|
$value[] = 'blocking('.$phid.')';
|
|
|
|
} else {
|
|
|
|
$value[] = $phid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
2013-09-26 21:36:45 +02:00
|
|
|
public function getRepository() {
|
|
|
|
return $this->assertAttached($this->repository);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function attachRepository(PhabricatorRepository $repository = null) {
|
|
|
|
$this->repository = $repository;
|
|
|
|
return $this;
|
|
|
|
}
|
2013-11-26 02:39:24 +01:00
|
|
|
|
2017-08-12 00:31:58 +02:00
|
|
|
public function setModernRevisionStatus($status) {
|
2017-08-12 01:27:38 +02:00
|
|
|
return $this->setStatus($status);
|
2017-08-12 00:31:58 +02:00
|
|
|
}
|
|
|
|
|
2017-08-12 00:44:51 +02:00
|
|
|
public function getModernRevisionStatus() {
|
2017-08-12 01:27:38 +02:00
|
|
|
return $this->getStatus();
|
2017-08-12 00:44:51 +02:00
|
|
|
}
|
|
|
|
|
2017-08-12 01:21:28 +02:00
|
|
|
public function getLegacyRevisionStatus() {
|
2017-08-12 01:27:38 +02:00
|
|
|
return $this->getStatusObject()->getLegacyKey();
|
2017-08-12 01:21:28 +02:00
|
|
|
}
|
|
|
|
|
2013-11-26 02:39:24 +01:00
|
|
|
public function isClosed() {
|
Continue reducing callsites to ArcanistDifferentialRevisionStatus
Summary:
Ref T2543. Further consolidates status management into DifferentialRevisionStatus.
One change I'm making here is internally renaming "CLOSED" to "PUBLISHED". The UI will continue to say "Closed", at least for now, but this should make the code more clear because we care about "is closed, exactly" vs "is any closed status (closed, abandoned, sometimes accepted)". This distinction is more obvious as `isClosed()` vs `isPublished()` than, e.g., `isClosedWithExactlyTheClosedStatus()` or something. I think "Published" is generally more clear, too, and more consistent with modern language (e.g., "pre-publish review" replacing "pre-commit review" to make it more clear what we mean in Git/Mercurial).
I've removed the IN_PREPARATION status since this was just earlier groundwork for "Draft" and not actually used, and under the newer plan I'm trying to just abandon `ArcanistDifferentialRevisionStatus` entirely (or, at least, substantially).
Test Plan:
- Viewed revisions.
- Viewed revision list.
- Viewed revisions linked to a task in Maniphest.
- Viewed revision graph of dependencies in Differential.
- Grepped for `COLOR_STATUS_...` constants.
- Grepped for removed method `getRevisionStatusIcon()` (no callsites).
- Grepped for removed method `renderFullDescription()` (one callsite, replaced with just building a `TagView` inline).
- Grepped for removed method `isClosedStatus()` (no callsites after other changes).
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T2543
Differential Revision: https://secure.phabricator.com/D18340
2017-08-04 17:08:18 +02:00
|
|
|
return $this->getStatusObject()->isClosedStatus();
|
2013-11-26 02:39:24 +01:00
|
|
|
}
|
|
|
|
|
2016-12-28 19:21:54 +01:00
|
|
|
public function isAbandoned() {
|
Continue reducing callsites to ArcanistDifferentialRevisionStatus
Summary:
Ref T2543. Further consolidates status management into DifferentialRevisionStatus.
One change I'm making here is internally renaming "CLOSED" to "PUBLISHED". The UI will continue to say "Closed", at least for now, but this should make the code more clear because we care about "is closed, exactly" vs "is any closed status (closed, abandoned, sometimes accepted)". This distinction is more obvious as `isClosed()` vs `isPublished()` than, e.g., `isClosedWithExactlyTheClosedStatus()` or something. I think "Published" is generally more clear, too, and more consistent with modern language (e.g., "pre-publish review" replacing "pre-commit review" to make it more clear what we mean in Git/Mercurial).
I've removed the IN_PREPARATION status since this was just earlier groundwork for "Draft" and not actually used, and under the newer plan I'm trying to just abandon `ArcanistDifferentialRevisionStatus` entirely (or, at least, substantially).
Test Plan:
- Viewed revisions.
- Viewed revision list.
- Viewed revisions linked to a task in Maniphest.
- Viewed revision graph of dependencies in Differential.
- Grepped for `COLOR_STATUS_...` constants.
- Grepped for removed method `getRevisionStatusIcon()` (no callsites).
- Grepped for removed method `renderFullDescription()` (one callsite, replaced with just building a `TagView` inline).
- Grepped for removed method `isClosedStatus()` (no callsites after other changes).
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T2543
Differential Revision: https://secure.phabricator.com/D18340
2017-08-04 17:08:18 +02:00
|
|
|
return $this->getStatusObject()->isAbandoned();
|
2016-12-28 19:21:54 +01:00
|
|
|
}
|
|
|
|
|
Order actions sensibly within Differential revision comment action groups
Summary:
Ref T11114. See D17114 for some discussion.
For review actions: accept, reject, resign.
For revision actions, order is basically least-severe to most-severe action pairs: plan changes, request review, close, reopen, abandon, reclaim, commandeer.
Test Plan: Viewed revisions as an author and a reviewer, saw sensible action order within action groups.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T11114
Differential Revision: https://secure.phabricator.com/D17115
2016-12-29 22:36:36 +01:00
|
|
|
public function isAccepted() {
|
Continue reducing callsites to ArcanistDifferentialRevisionStatus
Summary:
Ref T2543. Further consolidates status management into DifferentialRevisionStatus.
One change I'm making here is internally renaming "CLOSED" to "PUBLISHED". The UI will continue to say "Closed", at least for now, but this should make the code more clear because we care about "is closed, exactly" vs "is any closed status (closed, abandoned, sometimes accepted)". This distinction is more obvious as `isClosed()` vs `isPublished()` than, e.g., `isClosedWithExactlyTheClosedStatus()` or something. I think "Published" is generally more clear, too, and more consistent with modern language (e.g., "pre-publish review" replacing "pre-commit review" to make it more clear what we mean in Git/Mercurial).
I've removed the IN_PREPARATION status since this was just earlier groundwork for "Draft" and not actually used, and under the newer plan I'm trying to just abandon `ArcanistDifferentialRevisionStatus` entirely (or, at least, substantially).
Test Plan:
- Viewed revisions.
- Viewed revision list.
- Viewed revisions linked to a task in Maniphest.
- Viewed revision graph of dependencies in Differential.
- Grepped for `COLOR_STATUS_...` constants.
- Grepped for removed method `getRevisionStatusIcon()` (no callsites).
- Grepped for removed method `renderFullDescription()` (one callsite, replaced with just building a `TagView` inline).
- Grepped for removed method `isClosedStatus()` (no callsites after other changes).
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T2543
Differential Revision: https://secure.phabricator.com/D18340
2017-08-04 17:08:18 +02:00
|
|
|
return $this->getStatusObject()->isAccepted();
|
Order actions sensibly within Differential revision comment action groups
Summary:
Ref T11114. See D17114 for some discussion.
For review actions: accept, reject, resign.
For revision actions, order is basically least-severe to most-severe action pairs: plan changes, request review, close, reopen, abandon, reclaim, commandeer.
Test Plan: Viewed revisions as an author and a reviewer, saw sensible action order within action groups.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T11114
Differential Revision: https://secure.phabricator.com/D17115
2016-12-29 22:36:36 +01:00
|
|
|
}
|
|
|
|
|
2017-08-04 14:29:49 +02:00
|
|
|
public function isNeedsReview() {
|
Continue reducing callsites to ArcanistDifferentialRevisionStatus
Summary:
Ref T2543. Further consolidates status management into DifferentialRevisionStatus.
One change I'm making here is internally renaming "CLOSED" to "PUBLISHED". The UI will continue to say "Closed", at least for now, but this should make the code more clear because we care about "is closed, exactly" vs "is any closed status (closed, abandoned, sometimes accepted)". This distinction is more obvious as `isClosed()` vs `isPublished()` than, e.g., `isClosedWithExactlyTheClosedStatus()` or something. I think "Published" is generally more clear, too, and more consistent with modern language (e.g., "pre-publish review" replacing "pre-commit review" to make it more clear what we mean in Git/Mercurial).
I've removed the IN_PREPARATION status since this was just earlier groundwork for "Draft" and not actually used, and under the newer plan I'm trying to just abandon `ArcanistDifferentialRevisionStatus` entirely (or, at least, substantially).
Test Plan:
- Viewed revisions.
- Viewed revision list.
- Viewed revisions linked to a task in Maniphest.
- Viewed revision graph of dependencies in Differential.
- Grepped for `COLOR_STATUS_...` constants.
- Grepped for removed method `getRevisionStatusIcon()` (no callsites).
- Grepped for removed method `renderFullDescription()` (one callsite, replaced with just building a `TagView` inline).
- Grepped for removed method `isClosedStatus()` (no callsites after other changes).
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T2543
Differential Revision: https://secure.phabricator.com/D18340
2017-08-04 17:08:18 +02:00
|
|
|
return $this->getStatusObject()->isNeedsReview();
|
2017-08-04 14:29:49 +02:00
|
|
|
}
|
|
|
|
|
2017-08-12 00:31:58 +02:00
|
|
|
public function isNeedsRevision() {
|
|
|
|
return $this->getStatusObject()->isNeedsRevision();
|
|
|
|
}
|
|
|
|
|
2017-08-04 17:20:20 +02:00
|
|
|
public function isChangePlanned() {
|
|
|
|
return $this->getStatusObject()->isChangePlanned();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function isPublished() {
|
|
|
|
return $this->getStatusObject()->isPublished();
|
|
|
|
}
|
|
|
|
|
2017-09-18 21:54:14 +02:00
|
|
|
public function isDraft() {
|
|
|
|
return $this->getStatusObject()->isDraft();
|
|
|
|
}
|
|
|
|
|
2016-07-01 02:20:29 +02:00
|
|
|
public function getStatusIcon() {
|
Continue reducing callsites to ArcanistDifferentialRevisionStatus
Summary:
Ref T2543. Further consolidates status management into DifferentialRevisionStatus.
One change I'm making here is internally renaming "CLOSED" to "PUBLISHED". The UI will continue to say "Closed", at least for now, but this should make the code more clear because we care about "is closed, exactly" vs "is any closed status (closed, abandoned, sometimes accepted)". This distinction is more obvious as `isClosed()` vs `isPublished()` than, e.g., `isClosedWithExactlyTheClosedStatus()` or something. I think "Published" is generally more clear, too, and more consistent with modern language (e.g., "pre-publish review" replacing "pre-commit review" to make it more clear what we mean in Git/Mercurial).
I've removed the IN_PREPARATION status since this was just earlier groundwork for "Draft" and not actually used, and under the newer plan I'm trying to just abandon `ArcanistDifferentialRevisionStatus` entirely (or, at least, substantially).
Test Plan:
- Viewed revisions.
- Viewed revision list.
- Viewed revisions linked to a task in Maniphest.
- Viewed revision graph of dependencies in Differential.
- Grepped for `COLOR_STATUS_...` constants.
- Grepped for removed method `getRevisionStatusIcon()` (no callsites).
- Grepped for removed method `renderFullDescription()` (one callsite, replaced with just building a `TagView` inline).
- Grepped for removed method `isClosedStatus()` (no callsites after other changes).
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T2543
Differential Revision: https://secure.phabricator.com/D18340
2017-08-04 17:08:18 +02:00
|
|
|
return $this->getStatusObject()->getIcon();
|
2016-07-01 02:20:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getStatusDisplayName() {
|
Continue reducing callsites to ArcanistDifferentialRevisionStatus
Summary:
Ref T2543. Further consolidates status management into DifferentialRevisionStatus.
One change I'm making here is internally renaming "CLOSED" to "PUBLISHED". The UI will continue to say "Closed", at least for now, but this should make the code more clear because we care about "is closed, exactly" vs "is any closed status (closed, abandoned, sometimes accepted)". This distinction is more obvious as `isClosed()` vs `isPublished()` than, e.g., `isClosedWithExactlyTheClosedStatus()` or something. I think "Published" is generally more clear, too, and more consistent with modern language (e.g., "pre-publish review" replacing "pre-commit review" to make it more clear what we mean in Git/Mercurial).
I've removed the IN_PREPARATION status since this was just earlier groundwork for "Draft" and not actually used, and under the newer plan I'm trying to just abandon `ArcanistDifferentialRevisionStatus` entirely (or, at least, substantially).
Test Plan:
- Viewed revisions.
- Viewed revision list.
- Viewed revisions linked to a task in Maniphest.
- Viewed revision graph of dependencies in Differential.
- Grepped for `COLOR_STATUS_...` constants.
- Grepped for removed method `getRevisionStatusIcon()` (no callsites).
- Grepped for removed method `renderFullDescription()` (one callsite, replaced with just building a `TagView` inline).
- Grepped for removed method `isClosedStatus()` (no callsites after other changes).
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T2543
Differential Revision: https://secure.phabricator.com/D18340
2017-08-04 17:08:18 +02:00
|
|
|
return $this->getStatusObject()->getDisplayName();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getStatusIconColor() {
|
|
|
|
return $this->getStatusObject()->getIconColor();
|
|
|
|
}
|
|
|
|
|
2018-04-03 17:39:13 +02:00
|
|
|
public function getStatusTagColor() {
|
|
|
|
return $this->getStatusObject()->getTagColor();
|
|
|
|
}
|
|
|
|
|
Continue reducing callsites to ArcanistDifferentialRevisionStatus
Summary:
Ref T2543. Further consolidates status management into DifferentialRevisionStatus.
One change I'm making here is internally renaming "CLOSED" to "PUBLISHED". The UI will continue to say "Closed", at least for now, but this should make the code more clear because we care about "is closed, exactly" vs "is any closed status (closed, abandoned, sometimes accepted)". This distinction is more obvious as `isClosed()` vs `isPublished()` than, e.g., `isClosedWithExactlyTheClosedStatus()` or something. I think "Published" is generally more clear, too, and more consistent with modern language (e.g., "pre-publish review" replacing "pre-commit review" to make it more clear what we mean in Git/Mercurial).
I've removed the IN_PREPARATION status since this was just earlier groundwork for "Draft" and not actually used, and under the newer plan I'm trying to just abandon `ArcanistDifferentialRevisionStatus` entirely (or, at least, substantially).
Test Plan:
- Viewed revisions.
- Viewed revision list.
- Viewed revisions linked to a task in Maniphest.
- Viewed revision graph of dependencies in Differential.
- Grepped for `COLOR_STATUS_...` constants.
- Grepped for removed method `getRevisionStatusIcon()` (no callsites).
- Grepped for removed method `renderFullDescription()` (one callsite, replaced with just building a `TagView` inline).
- Grepped for removed method `isClosedStatus()` (no callsites after other changes).
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T2543
Differential Revision: https://secure.phabricator.com/D18340
2017-08-04 17:08:18 +02:00
|
|
|
public function getStatusObject() {
|
2016-07-01 02:20:29 +02:00
|
|
|
$status = $this->getStatus();
|
2017-08-12 01:27:38 +02:00
|
|
|
return DifferentialRevisionStatus::newForStatus($status);
|
2016-07-01 02:20:29 +02:00
|
|
|
}
|
|
|
|
|
2014-02-19 02:57:45 +01:00
|
|
|
public function getFlag(PhabricatorUser $viewer) {
|
|
|
|
return $this->assertAttachedKey($this->flags, $viewer->getPHID());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function attachFlag(
|
|
|
|
PhabricatorUser $viewer,
|
|
|
|
PhabricatorFlag $flag = null) {
|
|
|
|
$this->flags[$viewer->getPHID()] = $flag;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2017-01-13 15:36:25 +01:00
|
|
|
public function getHasDraft(PhabricatorUser $viewer) {
|
|
|
|
return $this->assertAttachedKey($this->drafts, $viewer->getCacheFragment());
|
2014-02-19 02:57:45 +01:00
|
|
|
}
|
|
|
|
|
2017-01-13 15:36:25 +01:00
|
|
|
public function attachHasDraft(PhabricatorUser $viewer, $has_draft) {
|
|
|
|
$this->drafts[$viewer->getCacheFragment()] = $has_draft;
|
2014-02-19 02:57:45 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2017-10-27 17:55:56 +02:00
|
|
|
public function getHoldAsDraft() {
|
|
|
|
return $this->getProperty(self::PROPERTY_DRAFT_HOLD, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setHoldAsDraft($hold) {
|
|
|
|
return $this->setProperty(self::PROPERTY_DRAFT_HOLD, $hold);
|
|
|
|
}
|
|
|
|
|
2018-04-03 17:09:02 +02:00
|
|
|
public function getShouldBroadcast() {
|
2018-04-03 17:39:13 +02:00
|
|
|
return $this->getProperty(self::PROPERTY_SHOULD_BROADCAST, true);
|
2017-11-28 19:42:16 +01:00
|
|
|
}
|
|
|
|
|
2018-04-03 17:09:02 +02:00
|
|
|
public function setShouldBroadcast($should_broadcast) {
|
|
|
|
return $this->setProperty(
|
|
|
|
self::PROPERTY_SHOULD_BROADCAST,
|
|
|
|
$should_broadcast);
|
2017-11-28 19:42:16 +01:00
|
|
|
}
|
|
|
|
|
2017-12-13 16:36:06 +01:00
|
|
|
public function setAddedLineCount($count) {
|
|
|
|
return $this->setProperty(self::PROPERTY_LINES_ADDED, $count);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getAddedLineCount() {
|
|
|
|
return $this->getProperty(self::PROPERTY_LINES_ADDED);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setRemovedLineCount($count) {
|
|
|
|
return $this->setProperty(self::PROPERTY_LINES_REMOVED, $count);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getRemovedLineCount() {
|
|
|
|
return $this->getProperty(self::PROPERTY_LINES_REMOVED);
|
|
|
|
}
|
2017-11-28 19:42:16 +01:00
|
|
|
|
2018-04-03 16:25:10 +02:00
|
|
|
public function getBuildableStatus($phid) {
|
|
|
|
$buildables = $this->getProperty(self::PROPERTY_BUILDABLES);
|
|
|
|
if (!is_array($buildables)) {
|
|
|
|
$buildables = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$buildable = idx($buildables, $phid);
|
|
|
|
if (!is_array($buildable)) {
|
|
|
|
$buildable = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
return idx($buildable, 'status');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setBuildableStatus($phid, $status) {
|
|
|
|
$buildables = $this->getProperty(self::PROPERTY_BUILDABLES);
|
|
|
|
if (!is_array($buildables)) {
|
|
|
|
$buildables = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$buildable = idx($buildables, $phid);
|
|
|
|
if (!is_array($buildable)) {
|
|
|
|
$buildable = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$buildable['status'] = $status;
|
|
|
|
|
|
|
|
$buildables[$phid] = $buildable;
|
|
|
|
|
|
|
|
return $this->setProperty(self::PROPERTY_BUILDABLES, $buildables);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function newBuildableStatus(PhabricatorUser $viewer, $phid) {
|
|
|
|
// For Differential, we're ignoring autobuilds (local lint and unit)
|
|
|
|
// when computing build status. Differential only cares about remote
|
|
|
|
// builds when making publishing and undrafting decisions.
|
|
|
|
|
2018-04-03 16:54:27 +02:00
|
|
|
$builds = $this->loadImpactfulBuildsForBuildablePHIDs(
|
|
|
|
$viewer,
|
|
|
|
array($phid));
|
|
|
|
|
|
|
|
return $this->newBuildableStatusForBuilds($builds);
|
|
|
|
}
|
2018-04-03 16:25:10 +02:00
|
|
|
|
2018-04-03 16:54:27 +02:00
|
|
|
public function newBuildableStatusForBuilds(array $builds) {
|
2018-04-03 16:25:10 +02:00
|
|
|
// If we have nothing but passing builds, the buildable passes.
|
|
|
|
if (!$builds) {
|
|
|
|
return HarbormasterBuildableStatus::STATUS_PASSED;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have any completed, non-passing builds, the buildable fails.
|
|
|
|
foreach ($builds as $build) {
|
2018-04-03 16:54:27 +02:00
|
|
|
if ($build->isComplete()) {
|
2018-04-03 16:25:10 +02:00
|
|
|
return HarbormasterBuildableStatus::STATUS_FAILED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we're still waiting for the build to pass or fail.
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-04-03 16:54:27 +02:00
|
|
|
public function loadImpactfulBuilds(PhabricatorUser $viewer) {
|
2017-10-19 23:09:25 +02:00
|
|
|
$diff = $this->getActiveDiff();
|
|
|
|
|
2018-02-12 21:03:56 +01:00
|
|
|
// NOTE: We can't use `withContainerPHIDs()` here because the container
|
|
|
|
// update in Harbormaster is not synchronous.
|
2017-10-19 23:09:25 +02:00
|
|
|
$buildables = id(new HarbormasterBuildableQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withBuildablePHIDs(array($diff->getPHID()))
|
|
|
|
->withManualBuildables(false)
|
|
|
|
->execute();
|
|
|
|
if (!$buildables) {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2018-04-03 16:54:27 +02:00
|
|
|
return $this->loadImpactfulBuildsForBuildablePHIDs(
|
|
|
|
$viewer,
|
|
|
|
mpull($buildables, 'getPHID'));
|
|
|
|
}
|
|
|
|
|
|
|
|
private function loadImpactfulBuildsForBuildablePHIDs(
|
|
|
|
PhabricatorUser $viewer,
|
|
|
|
array $phids) {
|
|
|
|
|
Simply how Differential drafts ignore Harbormaster autobuilds
Summary:
Ref T2543. When a revision is created, we check if any builds are waiting/failed, and submit it for review immediately if we aren't waiting for anything.
In doing this, we ignore builds with only autotargets, since these are client-side and failures from local `arc lint` / `arc unit` should not count (the user has already chosen to ignore/skip them).
The way we do this has some issues:
- Herald may have started builds, but they may still be PENDING and not have any targets yet. In this case, we'll see "no non-autotargets" and ignore the build, which is wrong.
- We have to load targets but don't really care about them, which is more work than we really need to do.
- And it's kind of complex, too.
Instead, just let `BuildQuery` filter out "autobuilds" (builds generated from autoplans) with a JOIN.
Test Plan: Ran `arc diff` with builds configured, got a clean "Draft" state instead of an incorrect promotion directly to "Needs Review".
Reviewers: amckinley
Reviewed By: amckinley
Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam
Maniphest Tasks: T2543
Differential Revision: https://secure.phabricator.com/D18721
2017-10-20 20:20:02 +02:00
|
|
|
return id(new HarbormasterBuildQuery())
|
2017-10-19 23:09:25 +02:00
|
|
|
->setViewer($viewer)
|
2018-04-03 16:54:27 +02:00
|
|
|
->withBuildablePHIDs($phids)
|
Simply how Differential drafts ignore Harbormaster autobuilds
Summary:
Ref T2543. When a revision is created, we check if any builds are waiting/failed, and submit it for review immediately if we aren't waiting for anything.
In doing this, we ignore builds with only autotargets, since these are client-side and failures from local `arc lint` / `arc unit` should not count (the user has already chosen to ignore/skip them).
The way we do this has some issues:
- Herald may have started builds, but they may still be PENDING and not have any targets yet. In this case, we'll see "no non-autotargets" and ignore the build, which is wrong.
- We have to load targets but don't really care about them, which is more work than we really need to do.
- And it's kind of complex, too.
Instead, just let `BuildQuery` filter out "autobuilds" (builds generated from autoplans) with a JOIN.
Test Plan: Ran `arc diff` with builds configured, got a clean "Draft" state instead of an incorrect promotion directly to "Needs Review".
Reviewers: amckinley
Reviewed By: amckinley
Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam
Maniphest Tasks: T2543
Differential Revision: https://secure.phabricator.com/D18721
2017-10-20 20:20:02 +02:00
|
|
|
->withAutobuilds(false)
|
2017-10-19 23:09:25 +02:00
|
|
|
->withBuildStatuses(
|
|
|
|
array(
|
|
|
|
HarbormasterBuildStatus::STATUS_INACTIVE,
|
|
|
|
HarbormasterBuildStatus::STATUS_PENDING,
|
|
|
|
HarbormasterBuildStatus::STATUS_BUILDING,
|
|
|
|
HarbormasterBuildStatus::STATUS_FAILED,
|
|
|
|
HarbormasterBuildStatus::STATUS_ABORTED,
|
|
|
|
HarbormasterBuildStatus::STATUS_ERROR,
|
|
|
|
HarbormasterBuildStatus::STATUS_PAUSED,
|
|
|
|
HarbormasterBuildStatus::STATUS_DEADLOCKED,
|
|
|
|
))
|
|
|
|
->execute();
|
|
|
|
}
|
|
|
|
|
2013-12-26 19:40:52 +01:00
|
|
|
|
|
|
|
/* -( HarbormasterBuildableInterface )------------------------------------- */
|
|
|
|
|
|
|
|
|
2016-02-26 21:20:47 +01:00
|
|
|
public function getHarbormasterBuildableDisplayPHID() {
|
|
|
|
return $this->getHarbormasterContainerPHID();
|
|
|
|
}
|
|
|
|
|
2013-12-26 19:40:52 +01:00
|
|
|
public function getHarbormasterBuildablePHID() {
|
|
|
|
return $this->loadActiveDiff()->getPHID();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getHarbormasterContainerPHID() {
|
|
|
|
return $this->getPHID();
|
|
|
|
}
|
|
|
|
|
2014-06-20 04:58:23 +02:00
|
|
|
public function getBuildVariables() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getAvailableBuildVariables() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2018-04-03 13:39:48 +02:00
|
|
|
public function newBuildableEngine() {
|
|
|
|
return new DifferentialBuildableEngine();
|
|
|
|
}
|
|
|
|
|
2014-02-12 17:53:40 +01:00
|
|
|
|
|
|
|
/* -( PhabricatorSubscribableInterface )----------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function isAutomaticallySubscribed($phid) {
|
2014-02-21 20:55:35 +01:00
|
|
|
if ($phid == $this->getAuthorPHID()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: This only happens when adding or removing CCs, and is safe from a
|
|
|
|
// policy perspective, but the subscription pathway should have some
|
|
|
|
// opportunity to load this data properly. For now, this is the only case
|
|
|
|
// where implicit subscription is not an intrinsic property of the object.
|
|
|
|
if ($this->reviewerStatus == self::ATTACHABLE) {
|
|
|
|
$reviewers = id(new DifferentialRevisionQuery())
|
|
|
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
|
|
|
->withPHIDs(array($this->getPHID()))
|
2017-03-20 22:37:24 +01:00
|
|
|
->needReviewers(true)
|
2014-02-21 20:55:35 +01:00
|
|
|
->executeOne()
|
2017-03-20 23:04:23 +01:00
|
|
|
->getReviewers();
|
2014-02-21 20:55:35 +01:00
|
|
|
} else {
|
2017-03-20 23:04:23 +01:00
|
|
|
$reviewers = $this->getReviewers();
|
2014-02-21 20:55:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($reviewers as $reviewer) {
|
When users resign from revisions, stop expanding projects/packages to include them
Summary:
Depends on D19019. Ref T13053. Fixes T12689. See PHI178.
Currently, if `@alice` resigns from a revision but `#alice-fan-club` is still a subscriber or reviewer, she'll continue to get mail. This is undesirable.
When users are associated with an object but have explicitly disengaged in an individal role (currently, only resign in audit/differential) mark them "unexpandable", so that they can no longer be included through implicit membership in a group (a project or package).
`@alice` can still get mail if she's a explicit recipient: as an author, owner, or if she adds herself back as a subscriber.
Test Plan:
- Added `@ducker` and `#users-named-ducker` as reviewers. Ducker got mail.
- Resigned as ducker, stopped getting future mail.
- Subscribed explicitly, got mail again.
- (Plus some `var_dump()` sanity checking in the internals.)
Reviewers: amckinley
Maniphest Tasks: T13053, T12689
Differential Revision: https://secure.phabricator.com/D19021
2018-02-07 16:08:36 +01:00
|
|
|
if ($reviewer->getReviewerPHID() !== $phid) {
|
|
|
|
continue;
|
2014-02-21 20:55:35 +01:00
|
|
|
}
|
When users resign from revisions, stop expanding projects/packages to include them
Summary:
Depends on D19019. Ref T13053. Fixes T12689. See PHI178.
Currently, if `@alice` resigns from a revision but `#alice-fan-club` is still a subscriber or reviewer, she'll continue to get mail. This is undesirable.
When users are associated with an object but have explicitly disengaged in an individal role (currently, only resign in audit/differential) mark them "unexpandable", so that they can no longer be included through implicit membership in a group (a project or package).
`@alice` can still get mail if she's a explicit recipient: as an author, owner, or if she adds herself back as a subscriber.
Test Plan:
- Added `@ducker` and `#users-named-ducker` as reviewers. Ducker got mail.
- Resigned as ducker, stopped getting future mail.
- Subscribed explicitly, got mail again.
- (Plus some `var_dump()` sanity checking in the internals.)
Reviewers: amckinley
Maniphest Tasks: T13053, T12689
Differential Revision: https://secure.phabricator.com/D19021
2018-02-07 16:08:36 +01:00
|
|
|
|
|
|
|
if ($reviewer->isResigned()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2014-02-21 20:55:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2014-02-12 17:53:40 +01:00
|
|
|
}
|
|
|
|
|
2014-02-19 01:32:55 +01:00
|
|
|
|
|
|
|
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
|
|
|
|
|
|
|
|
|
|
|
|
public function getCustomFieldSpecificationForRole($role) {
|
2014-03-08 18:13:51 +01:00
|
|
|
return PhabricatorEnv::getEnvConfig('differential.fields');
|
2014-02-19 01:32:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getCustomFieldBaseClass() {
|
|
|
|
return 'DifferentialCustomField';
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCustomFields() {
|
|
|
|
return $this->assertAttached($this->customFields);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
|
|
|
|
$this->customFields = $fields;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2014-04-18 01:03:24 +02:00
|
|
|
|
|
|
|
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getApplicationTransactionEditor() {
|
|
|
|
return new DifferentialTransactionEditor();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getApplicationTransactionObject() {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getApplicationTransactionTemplate() {
|
|
|
|
return new DifferentialTransaction();
|
|
|
|
}
|
|
|
|
|
2014-12-04 22:58:52 +01:00
|
|
|
public function willRenderTimeline(
|
|
|
|
PhabricatorApplicationTransactionView $timeline,
|
|
|
|
AphrontRequest $request) {
|
2015-04-22 00:32:23 +02:00
|
|
|
$viewer = $request->getViewer();
|
2014-12-04 22:58:52 +01:00
|
|
|
|
|
|
|
$render_data = $timeline->getRenderData();
|
|
|
|
$left = $request->getInt('left', idx($render_data, 'left'));
|
|
|
|
$right = $request->getInt('right', idx($render_data, 'right'));
|
|
|
|
|
|
|
|
$diffs = id(new DifferentialDiffQuery())
|
|
|
|
->setViewer($request->getUser())
|
|
|
|
->withIDs(array($left, $right))
|
|
|
|
->execute();
|
|
|
|
$diffs = mpull($diffs, null, 'getID');
|
|
|
|
$left_diff = $diffs[$left];
|
|
|
|
$right_diff = $diffs[$right];
|
|
|
|
|
2015-04-22 00:32:23 +02:00
|
|
|
$old_ids = $request->getStr('old', idx($render_data, 'old'));
|
|
|
|
$new_ids = $request->getStr('new', idx($render_data, 'new'));
|
2015-04-22 00:38:52 +02:00
|
|
|
$old_ids = array_filter(explode(',', $old_ids));
|
|
|
|
$new_ids = array_filter(explode(',', $new_ids));
|
2015-04-22 00:32:23 +02:00
|
|
|
|
2015-04-22 00:31:43 +02:00
|
|
|
$type_inline = DifferentialTransaction::TYPE_INLINE;
|
2015-04-22 00:32:23 +02:00
|
|
|
$changeset_ids = array_merge($old_ids, $new_ids);
|
|
|
|
$inlines = array();
|
2015-04-22 00:31:43 +02:00
|
|
|
foreach ($timeline->getTransactions() as $xaction) {
|
|
|
|
if ($xaction->getTransactionType() == $type_inline) {
|
2015-04-22 00:32:23 +02:00
|
|
|
$inlines[] = $xaction->getComment();
|
2015-04-22 00:31:43 +02:00
|
|
|
$changeset_ids[] = $xaction->getComment()->getChangesetID();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($changeset_ids) {
|
|
|
|
$changesets = id(new DifferentialChangesetQuery())
|
|
|
|
->setViewer($request->getUser())
|
|
|
|
->withIDs($changeset_ids)
|
|
|
|
->execute();
|
|
|
|
$changesets = mpull($changesets, null, 'getID');
|
|
|
|
} else {
|
|
|
|
$changesets = array();
|
|
|
|
}
|
2014-12-04 22:58:52 +01:00
|
|
|
|
2015-04-22 00:32:23 +02:00
|
|
|
foreach ($inlines as $key => $inline) {
|
|
|
|
$inlines[$key] = DifferentialInlineComment::newFromModernComment(
|
|
|
|
$inline);
|
|
|
|
}
|
|
|
|
|
|
|
|
$query = id(new DifferentialInlineCommentQuery())
|
Allow inline comments to be individually hidden
Summary:
Ref T7447. Implements per-viewer comment hiding. Once a comment is obsolete or uninteresting, you can hide it completely.
This is sticky per-user.
My hope is that this will strike a better balance between concerns than some of the other approaches (conservative porting, summarization, hide-all).
Specifically, this adds a new action here:
{F435621}
Clicking it completely collapses the comment into a small icon on the previous line, and saves the comment state as hidden for you:
{F435626}
You can click the icon to reveal all hidden comments below the line.
Test Plan:
- Hid comments.
- Showed comments.
- Created, edited, deleted and submitted comments.
- Used Diffusion comments (hiding is not implemented there yet, but I'd plan to bring it there eventually if it works out in Differential).
Reviewers: btrahan, chad
Reviewed By: btrahan
Subscribers: jparise, yelirekim, epriestley
Maniphest Tasks: T7447
Differential Revision: https://secure.phabricator.com/D13009
2015-05-27 19:28:38 +02:00
|
|
|
->needHidden(true)
|
2015-04-22 00:32:23 +02:00
|
|
|
->setViewer($viewer);
|
|
|
|
|
|
|
|
// NOTE: This is a bit sketchy: this method adjusts the inlines as a
|
|
|
|
// side effect, which means it will ultimately adjust the transaction
|
|
|
|
// comments and affect timeline rendering.
|
|
|
|
$query->adjustInlinesForChangesets(
|
|
|
|
$inlines,
|
|
|
|
array_select_keys($changesets, $old_ids),
|
2015-05-04 20:52:21 +02:00
|
|
|
array_select_keys($changesets, $new_ids),
|
|
|
|
$this);
|
2015-04-22 00:32:23 +02:00
|
|
|
|
2014-12-04 22:58:52 +01:00
|
|
|
return $timeline
|
|
|
|
->setChangesets($changesets)
|
|
|
|
->setRevision($this)
|
|
|
|
->setLeftDiff($left_diff)
|
|
|
|
->setRightDiff($right_diff);
|
|
|
|
}
|
|
|
|
|
2014-05-02 03:25:30 +02:00
|
|
|
|
2014-07-21 15:59:22 +02:00
|
|
|
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
2014-05-02 03:25:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
public function destroyObjectPermanently(
|
|
|
|
PhabricatorDestructionEngine $engine) {
|
|
|
|
|
|
|
|
$this->openTransaction();
|
|
|
|
$diffs = id(new DifferentialDiffQuery())
|
2015-05-15 23:07:17 +02:00
|
|
|
->setViewer($engine->getViewer())
|
2014-05-02 03:25:30 +02:00
|
|
|
->withRevisionIDs(array($this->getID()))
|
|
|
|
->execute();
|
|
|
|
foreach ($diffs as $diff) {
|
|
|
|
$engine->destroyObject($diff);
|
|
|
|
}
|
|
|
|
|
|
|
|
$conn_w = $this->establishConnection('w');
|
|
|
|
|
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
|
|
|
'DELETE FROM %T WHERE revisionID = %d',
|
|
|
|
self::TABLE_COMMIT,
|
|
|
|
$this->getID());
|
|
|
|
|
2017-10-09 19:48:01 +02:00
|
|
|
// we have to do paths a little differently as they do not have
|
2014-05-02 03:25:30 +02:00
|
|
|
// an id or phid column for delete() to act on
|
|
|
|
$dummy_path = new DifferentialAffectedPath();
|
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
|
|
|
'DELETE FROM %T WHERE revisionID = %d',
|
|
|
|
$dummy_path->getTableName(),
|
|
|
|
$this->getID());
|
|
|
|
|
|
|
|
$this->delete();
|
|
|
|
$this->saveTransaction();
|
|
|
|
}
|
|
|
|
|
2015-12-21 18:02:55 +01:00
|
|
|
|
|
|
|
/* -( PhabricatorFulltextInterface )--------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function newFulltextEngine() {
|
|
|
|
return new DifferentialRevisionFulltextEngine();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-06 01:25:28 +02:00
|
|
|
/* -( PhabricatorFerretInterface )----------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function newFerretEngine() {
|
|
|
|
return new DifferentialRevisionFerretEngine();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-06-09 17:59:40 +02:00
|
|
|
/* -( PhabricatorConduitResultInterface )---------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getFieldSpecificationsForConduit() {
|
|
|
|
return array(
|
|
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
|
|
->setKey('title')
|
|
|
|
->setType('string')
|
|
|
|
->setDescription(pht('The revision title.')),
|
|
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
|
|
->setKey('authorPHID')
|
|
|
|
->setType('phid')
|
|
|
|
->setDescription(pht('Revision author PHID.')),
|
2017-08-11 18:52:06 +02:00
|
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
|
|
->setKey('status')
|
|
|
|
->setType('map<string, wild>')
|
|
|
|
->setDescription(pht('Information about revision status.')),
|
2017-11-06 19:38:43 +01:00
|
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
|
|
->setKey('repositoryPHID')
|
|
|
|
->setType('phid?')
|
|
|
|
->setDescription(pht('Revision repository PHID.')),
|
|
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
|
|
->setKey('diffPHID')
|
|
|
|
->setType('phid')
|
|
|
|
->setDescription(pht('Active diff PHID.')),
|
|
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
|
|
->setKey('summary')
|
|
|
|
->setType('string')
|
|
|
|
->setDescription(pht('Revision summary.')),
|
2016-06-09 17:59:40 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getFieldValuesForConduit() {
|
2017-08-11 18:52:06 +02:00
|
|
|
$status = $this->getStatusObject();
|
|
|
|
$status_info = array(
|
|
|
|
'value' => $status->getKey(),
|
|
|
|
'name' => $status->getDisplayName(),
|
|
|
|
'closed' => $status->isClosedStatus(),
|
|
|
|
'color.ansi' => $status->getANSIColor(),
|
|
|
|
);
|
|
|
|
|
2016-06-09 17:59:40 +02:00
|
|
|
return array(
|
|
|
|
'title' => $this->getTitle(),
|
|
|
|
'authorPHID' => $this->getAuthorPHID(),
|
2017-08-11 18:52:06 +02:00
|
|
|
'status' => $status_info,
|
2017-11-06 19:38:43 +01:00
|
|
|
'repositoryPHID' => $this->getRepositoryPHID(),
|
|
|
|
'diffPHID' => $this->getActiveDiffPHID(),
|
|
|
|
'summary' => $this->getSummary(),
|
2016-06-09 17:59:40 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getConduitSearchAttachments() {
|
2017-04-06 23:07:09 +02:00
|
|
|
return array(
|
|
|
|
id(new DifferentialReviewersSearchEngineAttachment())
|
|
|
|
->setAttachmentKey('reviewers'),
|
|
|
|
);
|
2016-06-09 17:59:40 +02:00
|
|
|
}
|
|
|
|
|
2017-01-13 15:36:25 +01:00
|
|
|
|
|
|
|
/* -( PhabricatorDraftInterface )------------------------------------------ */
|
|
|
|
|
|
|
|
|
|
|
|
public function newDraftEngine() {
|
|
|
|
return new DifferentialRevisionDraftEngine();
|
|
|
|
}
|
|
|
|
|
2011-01-24 20:01:53 +01:00
|
|
|
}
|