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,
|
|
|
|
PhabricatorConduitResultInterface {
|
2011-01-24 20:01:53 +01:00
|
|
|
|
2013-10-09 22:58:00 +02:00
|
|
|
protected $title = '';
|
2012-06-14 05:52:10 +02:00
|
|
|
protected $originalTitle;
|
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;
|
|
|
|
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 $relationships = self::ATTACHABLE;
|
|
|
|
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();
|
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';
|
|
|
|
|
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
|
|
|
|
|
|
|
return id(new DifferentialRevision())
|
|
|
|
->setViewPolicy($view_policy)
|
|
|
|
->setAuthorPHID($actor->getPHID())
|
|
|
|
->attachRelationships(array())
|
2015-06-08 19:07:05 +02:00
|
|
|
->attachRepository(null)
|
2013-10-09 22:58:00 +02:00
|
|
|
->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
|
|
|
|
}
|
|
|
|
|
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',
|
|
|
|
'originalTitle' => '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}";
|
|
|
|
}
|
|
|
|
|
2012-06-14 05:52:10 +02:00
|
|
|
public function setTitle($title) {
|
|
|
|
$this->title = $title;
|
|
|
|
if (!$this->getID()) {
|
|
|
|
$this->originalTitle = $title;
|
|
|
|
}
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
public function loadRelationships() {
|
2011-01-27 03:46:34 +01:00
|
|
|
if (!$this->getID()) {
|
|
|
|
$this->relationships = array();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-02-12 17:53:40 +01:00
|
|
|
$data = array();
|
|
|
|
|
|
|
|
$subscriber_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
|
|
|
$this->getPHID(),
|
2015-01-03 00:33:25 +01:00
|
|
|
PhabricatorObjectHasSubscriberEdgeType::EDGECONST);
|
2014-02-12 17:53:40 +01:00
|
|
|
$subscriber_phids = array_reverse($subscriber_phids);
|
|
|
|
foreach ($subscriber_phids as $phid) {
|
|
|
|
$data[] = array(
|
|
|
|
'relation' => self::RELATION_SUBSCRIBED,
|
|
|
|
'objectPHID' => $phid,
|
|
|
|
'reasonPHID' => null,
|
|
|
|
);
|
|
|
|
}
|
2013-10-05 22:48:45 +02:00
|
|
|
|
|
|
|
$reviewer_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
|
|
|
$this->getPHID(),
|
2015-01-01 04:43:26 +01:00
|
|
|
DifferentialRevisionHasReviewerEdgeType::EDGECONST);
|
2013-10-05 04:57:15 +02:00
|
|
|
$reviewer_phids = array_reverse($reviewer_phids);
|
2013-10-05 22:48:45 +02:00
|
|
|
foreach ($reviewer_phids as $phid) {
|
|
|
|
$data[] = array(
|
|
|
|
'relation' => self::RELATION_REVIEWER,
|
|
|
|
'objectPHID' => $phid,
|
2013-10-05 04:57:15 +02:00
|
|
|
'reasonPHID' => null,
|
2013-10-05 22:48:45 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2011-10-02 20:42:41 +02:00
|
|
|
return $this->attachRelationships($data);
|
|
|
|
}
|
2011-01-27 03:46:34 +01:00
|
|
|
|
2011-10-02 20:42:41 +02:00
|
|
|
public function attachRelationships(array $relationships) {
|
|
|
|
$this->relationships = igroup($relationships, 'relation');
|
2011-01-27 03:46:34 +01:00
|
|
|
return $this;
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
2011-01-27 03:46:34 +01:00
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
public function getReviewers() {
|
2011-01-27 03:46:34 +01:00
|
|
|
return $this->getRelatedPHIDs(self::RELATION_REVIEWER);
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
2011-01-27 03:46:34 +01:00
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
public function getCCPHIDs() {
|
2011-01-27 03:46:34 +01:00
|
|
|
return $this->getRelatedPHIDs(self::RELATION_SUBSCRIBED);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getRelatedPHIDs($relation) {
|
2013-09-03 15:02:14 +02:00
|
|
|
$this->assertAttached($this->relationships);
|
2011-02-03 04:38:43 +01:00
|
|
|
|
2011-02-03 02:38:03 +01:00
|
|
|
return ipull($this->getRawRelations($relation), 'objectPHID');
|
2011-01-27 03:46:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getRawRelations($relation) {
|
|
|
|
return idx($this->relationships, $relation, array());
|
|
|
|
}
|
2011-04-30 07:26:22 +02:00
|
|
|
|
2012-05-18 09:33:21 +02:00
|
|
|
public function getPrimaryReviewer() {
|
2012-08-08 22:27:52 +02:00
|
|
|
$reviewers = $this->getReviewers();
|
|
|
|
$last = $this->lastReviewerPHID;
|
|
|
|
if (!$last || !in_array($last, $reviewers)) {
|
2012-05-18 09:33:21 +02:00
|
|
|
return head($this->getReviewers());
|
|
|
|
}
|
2012-08-08 22:27:52 +02:00
|
|
|
return $last;
|
2012-05-18 09:33:21 +02: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;
|
|
|
|
}
|
|
|
|
|
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:
|
2015-05-22 09:27:56 +02:00
|
|
|
$description[] = pht("A revision's reviewers can always view it.");
|
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:
|
|
|
|
// NOTE: In Differential, an automatic capability on a revision (being
|
|
|
|
// an author) is sufficient to view it, even if you can not see the
|
|
|
|
// repository the revision belongs to. We can bail out early in this
|
|
|
|
// case.
|
|
|
|
if ($this->hasAutomaticCapability($capability, $viewer)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
$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(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-07-15 04:18:55 +02:00
|
|
|
public function getReviewerStatus() {
|
2013-09-03 15:02:14 +02:00
|
|
|
return $this->assertAttached($this->reviewerStatus);
|
2013-07-15 04:18:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function attachReviewerStatus(array $reviewers) {
|
|
|
|
assert_instances_of($reviewers, 'DifferentialReviewer');
|
|
|
|
|
|
|
|
$this->reviewerStatus = $reviewers;
|
|
|
|
return $this;
|
|
|
|
}
|
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
|
|
|
|
|
|
|
public function isClosed() {
|
|
|
|
return DifferentialRevisionStatus::isClosedStatus($this->getStatus());
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getDrafts(PhabricatorUser $viewer) {
|
|
|
|
return $this->assertAttachedKey($this->drafts, $viewer->getPHID());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function attachDrafts(PhabricatorUser $viewer, array $drafts) {
|
|
|
|
$this->drafts[$viewer->getPHID()] = $drafts;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
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()))
|
|
|
|
->needReviewerStatus(true)
|
|
|
|
->executeOne()
|
|
|
|
->getReviewerStatus();
|
|
|
|
} else {
|
|
|
|
$reviewers = $this->getReviewerStatus();
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($reviewers as $reviewer) {
|
|
|
|
if ($reviewer->getReviewerPHID() == $phid) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
|
|
|
// we have to do paths a little differentally as they do not have
|
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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.')),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getFieldValuesForConduit() {
|
|
|
|
return array(
|
|
|
|
'title' => $this->getTitle(),
|
|
|
|
'authorPHID' => $this->getAuthorPHID(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getConduitSearchAttachments() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2011-01-24 20:01:53 +01:00
|
|
|
}
|