3 final class DifferentialRevision
extends DifferentialDAO
5 PhabricatorTokenReceiverInterface
,
6 PhabricatorPolicyInterface
,
7 PhabricatorExtendedPolicyInterface
,
8 PhabricatorFlaggableInterface
,
9 PhrequentTrackableInterface
,
10 HarbormasterBuildableInterface
,
11 PhabricatorSubscribableInterface
,
12 PhabricatorCustomFieldInterface
,
13 PhabricatorApplicationTransactionInterface
,
14 PhabricatorTimelineInterface
,
15 PhabricatorMentionableInterface
,
16 PhabricatorDestructibleInterface
,
17 PhabricatorProjectInterface
,
18 PhabricatorFulltextInterface
,
19 PhabricatorFerretInterface
,
20 PhabricatorConduitResultInterface
,
21 PhabricatorDraftInterface
{
23 protected $title = '';
26 protected $summary = '';
27 protected $testPlan = '';
29 protected $authorPHID;
30 protected $lastReviewerPHID;
32 protected $lineCount = 0;
33 protected $attached = array();
36 protected $branchName;
37 protected $repositoryPHID;
38 protected $activeDiffPHID;
40 protected $viewPolicy = PhabricatorPolicies
::POLICY_USER
;
41 protected $editPolicy = PhabricatorPolicies
::POLICY_USER
;
42 protected $properties = array();
44 private $commitPHIDs = self
::ATTACHABLE
;
45 private $activeDiff = self
::ATTACHABLE
;
46 private $diffIDs = self
::ATTACHABLE
;
47 private $hashes = self
::ATTACHABLE
;
48 private $repository = self
::ATTACHABLE
;
50 private $reviewerStatus = self
::ATTACHABLE
;
51 private $customFields = self
::ATTACHABLE
;
52 private $drafts = array();
53 private $flags = array();
54 private $forceMap = array();
56 const RELATION_REVIEWER
= 'revw';
57 const RELATION_SUBSCRIBED
= 'subd';
59 const PROPERTY_CLOSED_FROM_ACCEPTED
= 'wasAcceptedBeforeClose';
60 const PROPERTY_DRAFT_HOLD
= 'draft.hold';
61 const PROPERTY_SHOULD_BROADCAST
= 'draft.broadcast';
62 const PROPERTY_LINES_ADDED
= 'lines.added';
63 const PROPERTY_LINES_REMOVED
= 'lines.removed';
64 const PROPERTY_BUILDABLES
= 'buildables';
65 const PROPERTY_WRONG_BUILDS
= 'wrong.builds';
67 public static function initializeNewRevision(PhabricatorUser
$actor) {
68 $app = id(new PhabricatorApplicationQuery())
70 ->withClasses(array('PhabricatorDifferentialApplication'))
73 $view_policy = $app->getPolicy(
74 DifferentialDefaultViewCapability
::CAPABILITY
);
76 $initial_state = DifferentialRevisionStatus
::DRAFT
;
77 $should_broadcast = false;
79 return id(new DifferentialRevision())
80 ->setViewPolicy($view_policy)
81 ->setAuthorPHID($actor->getPHID())
82 ->attachRepository(null)
83 ->attachActiveDiff(null)
84 ->attachReviewers(array())
85 ->setModernRevisionStatus($initial_state)
86 ->setShouldBroadcast($should_broadcast);
89 protected function getConfiguration() {
91 self
::CONFIG_AUX_PHID
=> true,
92 self
::CONFIG_SERIALIZATION
=> array(
93 'attached' => self
::SERIALIZATION_JSON
,
94 'unsubscribed' => self
::SERIALIZATION_JSON
,
95 'properties' => self
::SERIALIZATION_JSON
,
97 self
::CONFIG_COLUMN_SCHEMA
=> array(
101 'testPlan' => 'text',
102 'authorPHID' => 'phid?',
103 'lastReviewerPHID' => 'phid?',
104 'lineCount' => 'uint32?',
105 'mailKey' => 'bytes40',
106 'branchName' => 'text255?',
107 'repositoryPHID' => 'phid?',
109 self
::CONFIG_KEY_SCHEMA
=> array(
110 'authorPHID' => array(
111 'columns' => array('authorPHID', 'status'),
113 'repositoryPHID' => array(
114 'columns' => array('repositoryPHID'),
116 // If you (or a project you are a member of) is reviewing a significant
117 // fraction of the revisions on an install, the result set of open
118 // revisions may be smaller than the result set of revisions where you
119 // are a reviewer. In these cases, this key is better than keys on the
121 'key_status' => array(
122 'columns' => array('status', 'phid'),
124 'key_modified' => array(
125 'columns' => array('dateModified'),
128 ) + parent
::getConfiguration();
131 public function setProperty($key, $value) {
132 $this->properties
[$key] = $value;
136 public function getProperty($key, $default = null) {
137 return idx($this->properties
, $key, $default);
140 public function hasRevisionProperty($key) {
141 return array_key_exists($key, $this->properties
);
144 public function getMonogram() {
145 $id = $this->getID();
149 public function getURI() {
150 return '/'.$this->getMonogram();
153 public function getCommitPHIDs() {
154 return $this->assertAttached($this->commitPHIDs
);
157 public function getActiveDiff() {
158 // TODO: Because it's currently technically possible to create a revision
159 // without an associated diff, we allow an attached-but-null active diff.
160 // It would be good to get rid of this once we make diff-attaching
163 return $this->assertAttached($this->activeDiff
);
166 public function attachActiveDiff($diff) {
167 $this->activeDiff
= $diff;
171 public function getDiffIDs() {
172 return $this->assertAttached($this->diffIDs
);
175 public function attachDiffIDs(array $ids) {
177 $this->diffIDs
= array_values($ids);
181 public function attachCommitPHIDs(array $phids) {
182 $this->commitPHIDs
= $phids;
186 public function getAttachedPHIDs($type) {
187 return array_keys(idx($this->attached
, $type, array()));
190 public function setAttachedPHIDs($type, array $phids) {
191 $this->attached
[$type] = array_fill_keys($phids, array());
195 public function generatePHID() {
196 return PhabricatorPHID
::generateNewPHID(
197 DifferentialRevisionPHIDType
::TYPECONST
);
200 public function loadActiveDiff() {
201 return id(new DifferentialDiff())->loadOneWhere(
202 'revisionID = %d ORDER BY id DESC LIMIT 1',
206 public function save() {
207 if (!$this->getMailKey()) {
208 $this->mailKey
= Filesystem
::readRandomCharacters(40);
210 return parent
::save();
213 public function getHashes() {
214 return $this->assertAttached($this->hashes
);
217 public function attachHashes(array $hashes) {
218 $this->hashes
= $hashes;
222 public function canReviewerForceAccept(
223 PhabricatorUser
$viewer,
224 DifferentialReviewer
$reviewer) {
226 if (!$reviewer->isPackage()) {
230 $map = $this->getReviewerForceAcceptMap($viewer);
235 if (isset($map[$reviewer->getReviewerPHID()])) {
242 private function getReviewerForceAcceptMap(PhabricatorUser
$viewer) {
243 $fragment = $viewer->getCacheFragment();
245 if (!array_key_exists($fragment, $this->forceMap
)) {
246 $map = $this->newReviewerForceAcceptMap($viewer);
247 $this->forceMap
[$fragment] = $map;
250 return $this->forceMap
[$fragment];
253 private function newReviewerForceAcceptMap(PhabricatorUser
$viewer) {
254 $diff = $this->getActiveDiff();
259 $repository_phid = $diff->getRepositoryPHID();
260 if (!$repository_phid) {
267 $changesets = $diff->getChangesets();
268 } catch (Exception
$ex) {
269 $changesets = id(new DifferentialChangesetQuery())
271 ->withDiffs(array($diff))
275 foreach ($changesets as $changeset) {
276 $paths[] = $changeset->getOwnersFilename();
283 $reviewer_phids = array();
284 foreach ($this->getReviewers() as $reviewer) {
285 if (!$reviewer->isPackage()) {
289 $reviewer_phids[] = $reviewer->getReviewerPHID();
292 if (!$reviewer_phids) {
296 // Load all the reviewing packages which have control over some of the
297 // paths in the change. These are packages which the actor may be able
298 // to force-accept on behalf of.
299 $control_query = id(new PhabricatorOwnersPackageQuery())
301 ->withStatuses(array(PhabricatorOwnersPackage
::STATUS_ACTIVE
))
302 ->withPHIDs($reviewer_phids)
303 ->withControl($repository_phid, $paths);
304 $control_packages = $control_query->execute();
305 if (!$control_packages) {
309 // Load all the packages which have potential control over some of the
310 // paths in the change and are owned by the actor. These are packages
311 // which the actor may be able to use their authority over to gain the
312 // ability to force-accept for other packages. This query doesn't apply
313 // dominion rules yet, and we'll bypass those rules later on.
315 // See T13657. We ignore "watcher" packages which don't grant their owners
316 // permission to force accept anything.
318 $authority_query = id(new PhabricatorOwnersPackageQuery())
320 ->withStatuses(array(PhabricatorOwnersPackage
::STATUS_ACTIVE
))
321 ->withAuthorityModes(
323 PhabricatorOwnersPackage
::AUTHORITY_STRONG
,
325 ->withAuthorityPHIDs(array($viewer->getPHID()))
326 ->withControl($repository_phid, $paths);
327 $authority_packages = $authority_query->execute();
328 if (!$authority_packages) {
331 $authority_packages = mpull($authority_packages, null, 'getPHID');
333 // Build a map from each path in the revision to the reviewer packages
335 $control_map = array();
336 foreach ($paths as $path) {
337 $control_packages = $control_query->getControllingPackagesForPath(
341 // Remove packages which the viewer has authority over. We don't need
342 // to check these for force-accept because they can just accept them
344 $control_packages = mpull($control_packages, null, 'getPHID');
345 foreach ($control_packages as $phid => $control_package) {
346 if (isset($authority_packages[$phid])) {
347 unset($control_packages[$phid]);
351 if (!$control_packages) {
355 $control_map[$path] = $control_packages;
362 // From here on out, we only care about paths which we have at least one
363 // controlling package for.
364 $paths = array_keys($control_map);
366 // Now, build a map from each path to the packages which would control it
367 // if there were no dominion rules.
368 $authority_map = array();
369 foreach ($paths as $path) {
370 $authority_packages = $authority_query->getControllingPackagesForPath(
373 $ignore_dominion = true);
375 $authority_map[$path] = mpull($authority_packages, null, 'getPHID');
378 // For each path, find the most general package that the viewer has
379 // authority over. For example, we'll prefer a package that owns "/" to a
380 // package that owns "/src/".
381 $force_map = array();
382 foreach ($authority_map as $path => $package_map) {
383 $path_fragments = PhabricatorOwnersPackage
::splitPath($path);
384 $fragment_count = count($path_fragments);
386 // Find the package that we have authority over which has the most
387 // general match for this path.
389 $best_package = null;
390 foreach ($package_map as $package_phid => $package) {
391 $package_paths = $package->getPathsForRepository($repository_phid);
392 foreach ($package_paths as $package_path) {
394 // NOTE: A strength of 0 means "no match". A strength of 1 means
395 // that we matched "/", so we can not possibly find another stronger
398 $strength = $package_path->getPathMatchStrength(
405 if ($strength < $best_match ||
!$best_package) {
406 $best_match = $strength;
407 $best_package = $package;
408 if ($strength == 1) {
416 $force_map[$path] = array(
417 'strength' => $best_match,
418 'package' => $best_package,
423 // For each path which the viewer owns a package for, find other packages
424 // which that authority can be used to force-accept. Once we find a way to
425 // force-accept a package, we don't need to keep looking.
426 $has_control = array();
427 foreach ($force_map as $path => $spec) {
428 $path_fragments = PhabricatorOwnersPackage
::splitPath($path);
429 $fragment_count = count($path_fragments);
431 $authority_strength = $spec['strength'];
433 $control_packages = $control_map[$path];
434 foreach ($control_packages as $control_phid => $control_package) {
435 if (isset($has_control[$control_phid])) {
439 $control_paths = $control_package->getPathsForRepository(
441 foreach ($control_paths as $control_path) {
442 $strength = $control_path->getPathMatchStrength(
450 if ($strength > $authority_strength) {
451 $authority = $spec['package'];
452 $has_control[$control_phid] = array(
453 'authority' => $authority,
454 'phid' => $authority->getPHID(),
462 // Return a map from packages which may be force accepted to the packages
463 // which permit that forced acceptance.
464 return ipull($has_control, 'phid');
468 /* -( PhabricatorPolicyInterface )----------------------------------------- */
471 public function getCapabilities() {
473 PhabricatorPolicyCapability
::CAN_VIEW
,
474 PhabricatorPolicyCapability
::CAN_EDIT
,
478 public function getPolicy($capability) {
479 switch ($capability) {
480 case PhabricatorPolicyCapability
::CAN_VIEW
:
481 return $this->getViewPolicy();
482 case PhabricatorPolicyCapability
::CAN_EDIT
:
483 return $this->getEditPolicy();
487 public function hasAutomaticCapability($capability, PhabricatorUser
$user) {
488 // A revision's author (which effectively means "owner" after we added
489 // commandeering) can always view and edit it.
490 $author_phid = $this->getAuthorPHID();
492 if ($user->getPHID() == $author_phid) {
500 public function describeAutomaticCapability($capability) {
501 $description = array(
502 pht('The owner of a revision can always view and edit it.'),
505 switch ($capability) {
506 case PhabricatorPolicyCapability
::CAN_VIEW
:
507 $description[] = pht(
508 'If a revision belongs to a repository, other users must be able '.
509 'to view the repository in order to view the revision.');
517 /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
520 public function getExtendedPolicy($capability, PhabricatorUser
$viewer) {
523 switch ($capability) {
524 case PhabricatorPolicyCapability
::CAN_VIEW
:
525 $repository_phid = $this->getRepositoryPHID();
526 $repository = $this->getRepository();
528 // Try to use the object if we have it, since it will save us some
529 // data fetching later on. In some cases, we might not have it.
530 $repository_ref = nonempty($repository, $repository_phid);
531 if ($repository_ref) {
534 PhabricatorPolicyCapability
::CAN_VIEW
,
544 /* -( PhabricatorTokenReceiverInterface )---------------------------------- */
547 public function getUsersToNotifyOfTokenGiven() {
549 $this->getAuthorPHID(),
553 public function getReviewers() {
554 return $this->assertAttached($this->reviewerStatus
);
557 public function attachReviewers(array $reviewers) {
558 assert_instances_of($reviewers, 'DifferentialReviewer');
559 $reviewers = mpull($reviewers, null, 'getReviewerPHID');
560 $this->reviewerStatus
= $reviewers;
564 public function hasAttachedReviewers() {
565 return ($this->reviewerStatus
!== self
::ATTACHABLE
);
568 public function getReviewerPHIDs() {
569 $reviewers = $this->getReviewers();
570 return mpull($reviewers, 'getReviewerPHID');
573 public function getReviewerPHIDsForEdit() {
574 $reviewers = $this->getReviewers();
576 $status_blocking = DifferentialReviewerStatus
::STATUS_BLOCKING
;
579 foreach ($reviewers as $reviewer) {
580 $phid = $reviewer->getReviewerPHID();
581 if ($reviewer->getReviewerStatus() == $status_blocking) {
582 $value[] = 'blocking('.$phid.')';
591 public function getRepository() {
592 return $this->assertAttached($this->repository
);
595 public function attachRepository(PhabricatorRepository
$repository = null) {
596 $this->repository
= $repository;
600 public function setModernRevisionStatus($status) {
601 return $this->setStatus($status);
604 public function getModernRevisionStatus() {
605 return $this->getStatus();
608 public function getLegacyRevisionStatus() {
609 return $this->getStatusObject()->getLegacyKey();
612 public function isClosed() {
613 return $this->getStatusObject()->isClosedStatus();
616 public function isAbandoned() {
617 return $this->getStatusObject()->isAbandoned();
620 public function isAccepted() {
621 return $this->getStatusObject()->isAccepted();
624 public function isNeedsReview() {
625 return $this->getStatusObject()->isNeedsReview();
628 public function isNeedsRevision() {
629 return $this->getStatusObject()->isNeedsRevision();
632 public function isChangePlanned() {
633 return $this->getStatusObject()->isChangePlanned();
636 public function isPublished() {
637 return $this->getStatusObject()->isPublished();
640 public function isDraft() {
641 return $this->getStatusObject()->isDraft();
644 public function getStatusIcon() {
645 return $this->getStatusObject()->getIcon();
648 public function getStatusDisplayName() {
649 return $this->getStatusObject()->getDisplayName();
652 public function getStatusIconColor() {
653 return $this->getStatusObject()->getIconColor();
656 public function getStatusTagColor() {
657 return $this->getStatusObject()->getTagColor();
660 public function getStatusObject() {
661 $status = $this->getStatus();
662 return DifferentialRevisionStatus
::newForStatus($status);
665 public function getFlag(PhabricatorUser
$viewer) {
666 return $this->assertAttachedKey($this->flags
, $viewer->getPHID());
669 public function attachFlag(
670 PhabricatorUser
$viewer,
671 PhabricatorFlag
$flag = null) {
672 $this->flags
[$viewer->getPHID()] = $flag;
676 public function getHasDraft(PhabricatorUser
$viewer) {
677 return $this->assertAttachedKey($this->drafts
, $viewer->getCacheFragment());
680 public function attachHasDraft(PhabricatorUser
$viewer, $has_draft) {
681 $this->drafts
[$viewer->getCacheFragment()] = $has_draft;
685 public function getHoldAsDraft() {
686 return $this->getProperty(self
::PROPERTY_DRAFT_HOLD
, false);
689 public function setHoldAsDraft($hold) {
690 return $this->setProperty(self
::PROPERTY_DRAFT_HOLD
, $hold);
693 public function getShouldBroadcast() {
694 return $this->getProperty(self
::PROPERTY_SHOULD_BROADCAST
, true);
697 public function setShouldBroadcast($should_broadcast) {
698 return $this->setProperty(
699 self
::PROPERTY_SHOULD_BROADCAST
,
703 public function setAddedLineCount($count) {
704 return $this->setProperty(self
::PROPERTY_LINES_ADDED
, $count);
707 public function getAddedLineCount() {
708 return $this->getProperty(self
::PROPERTY_LINES_ADDED
);
711 public function setRemovedLineCount($count) {
712 return $this->setProperty(self
::PROPERTY_LINES_REMOVED
, $count);
715 public function getRemovedLineCount() {
716 return $this->getProperty(self
::PROPERTY_LINES_REMOVED
);
719 public function hasLineCounts() {
720 // This data was not populated on older revisions, so it may not be
721 // present on all revisions.
722 return isset($this->properties
[self
::PROPERTY_LINES_ADDED
]);
725 public function getRevisionScaleGlyphs() {
726 $add = $this->getAddedLineCount();
727 $rem = $this->getRemovedLineCount();
728 $all = ($add +
$rem);
744 foreach ($map as $size => $count) {
752 $add_n = (int)ceil(($add / $all) * $n);
753 $rem_n = (int)ceil(($rem / $all) * $n);
755 while ($add_n +
$rem_n > $n) {
764 str_repeat('+', $add_n).
765 str_repeat('-', $rem_n).
766 str_repeat(' ', (7 - $n));
769 public function getBuildableStatus($phid) {
770 $buildables = $this->getProperty(self
::PROPERTY_BUILDABLES
);
771 if (!is_array($buildables)) {
772 $buildables = array();
775 $buildable = idx($buildables, $phid);
776 if (!is_array($buildable)) {
777 $buildable = array();
780 return idx($buildable, 'status');
783 public function setBuildableStatus($phid, $status) {
784 $buildables = $this->getProperty(self
::PROPERTY_BUILDABLES
);
785 if (!is_array($buildables)) {
786 $buildables = array();
789 $buildable = idx($buildables, $phid);
790 if (!is_array($buildable)) {
791 $buildable = array();
794 $buildable['status'] = $status;
796 $buildables[$phid] = $buildable;
798 return $this->setProperty(self
::PROPERTY_BUILDABLES
, $buildables);
801 public function newBuildableStatus(PhabricatorUser
$viewer, $phid) {
802 // For Differential, we're ignoring autobuilds (local lint and unit)
803 // when computing build status. Differential only cares about remote
804 // builds when making publishing and undrafting decisions.
806 $builds = $this->loadImpactfulBuildsForBuildablePHIDs(
810 return $this->newBuildableStatusForBuilds($builds);
813 public function newBuildableStatusForBuilds(array $builds) {
814 // If we have nothing but passing builds, the buildable passes.
816 return HarbormasterBuildableStatus
::STATUS_PASSED
;
819 // If we have any completed, non-passing builds, the buildable fails.
820 foreach ($builds as $build) {
821 if ($build->isComplete()) {
822 return HarbormasterBuildableStatus
::STATUS_FAILED
;
826 // Otherwise, we're still waiting for the build to pass or fail.
830 public function loadImpactfulBuilds(PhabricatorUser
$viewer) {
831 $diff = $this->getActiveDiff();
833 // NOTE: We can't use `withContainerPHIDs()` here because the container
834 // update in Harbormaster is not synchronous.
835 $buildables = id(new HarbormasterBuildableQuery())
837 ->withBuildablePHIDs(array($diff->getPHID()))
838 ->withManualBuildables(false)
844 return $this->loadImpactfulBuildsForBuildablePHIDs(
846 mpull($buildables, 'getPHID'));
849 private function loadImpactfulBuildsForBuildablePHIDs(
850 PhabricatorUser
$viewer,
853 $builds = id(new HarbormasterBuildQuery())
855 ->withBuildablePHIDs($phids)
856 ->withAutobuilds(false)
859 HarbormasterBuildStatus
::STATUS_INACTIVE
,
860 HarbormasterBuildStatus
::STATUS_PENDING
,
861 HarbormasterBuildStatus
::STATUS_BUILDING
,
862 HarbormasterBuildStatus
::STATUS_FAILED
,
863 HarbormasterBuildStatus
::STATUS_ABORTED
,
864 HarbormasterBuildStatus
::STATUS_ERROR
,
865 HarbormasterBuildStatus
::STATUS_PAUSED
,
866 HarbormasterBuildStatus
::STATUS_DEADLOCKED
,
870 // Filter builds based on the "Hold Drafts" behavior of their associated
873 $hold_drafts = HarbormasterBuildPlanBehavior
::BEHAVIOR_DRAFTS
;
874 $behavior = HarbormasterBuildPlanBehavior
::getBehavior($hold_drafts);
876 $key_never = HarbormasterBuildPlanBehavior
::DRAFTS_NEVER
;
877 $key_building = HarbormasterBuildPlanBehavior
::DRAFTS_IF_BUILDING
;
879 foreach ($builds as $key => $build) {
880 $plan = $build->getBuildPlan();
882 // See T13526. If the viewer can't see the build plan, pretend it has
883 // generic options. This is often wrong, but "often wrong" is better than
886 $hold_key = $behavior->getPlanOption($plan)->getKey();
888 $hold_never = ($hold_key === $key_never);
889 $hold_building = ($hold_key === $key_building);
892 $hold_building = false;
895 // If the build "Never" holds drafts from promoting, we don't care what
898 unset($builds[$key]);
902 // If the build holds drafts from promoting "While Building", we only
903 // care about the status until it completes.
904 if ($hold_building) {
905 if ($build->isComplete()) {
906 unset($builds[$key]);
916 /* -( HarbormasterBuildableInterface )------------------------------------- */
919 public function getHarbormasterBuildableDisplayPHID() {
920 return $this->getHarbormasterContainerPHID();
923 public function getHarbormasterBuildablePHID() {
924 return $this->loadActiveDiff()->getPHID();
927 public function getHarbormasterContainerPHID() {
928 return $this->getPHID();
931 public function getBuildVariables() {
935 public function getAvailableBuildVariables() {
939 public function newBuildableEngine() {
940 return new DifferentialBuildableEngine();
944 /* -( PhabricatorSubscribableInterface )----------------------------------- */
947 public function isAutomaticallySubscribed($phid) {
948 if ($phid == $this->getAuthorPHID()) {
952 // TODO: This only happens when adding or removing CCs, and is safe from a
953 // policy perspective, but the subscription pathway should have some
954 // opportunity to load this data properly. For now, this is the only case
955 // where implicit subscription is not an intrinsic property of the object.
956 if ($this->reviewerStatus
== self
::ATTACHABLE
) {
957 $reviewers = id(new DifferentialRevisionQuery())
958 ->setViewer(PhabricatorUser
::getOmnipotentUser())
959 ->withPHIDs(array($this->getPHID()))
960 ->needReviewers(true)
964 $reviewers = $this->getReviewers();
967 foreach ($reviewers as $reviewer) {
968 if ($reviewer->getReviewerPHID() !== $phid) {
972 if ($reviewer->isResigned()) {
983 /* -( PhabricatorCustomFieldInterface )------------------------------------ */
986 public function getCustomFieldSpecificationForRole($role) {
987 return PhabricatorEnv
::getEnvConfig('differential.fields');
990 public function getCustomFieldBaseClass() {
991 return 'DifferentialCustomField';
994 public function getCustomFields() {
995 return $this->assertAttached($this->customFields
);
998 public function attachCustomFields(PhabricatorCustomFieldAttachment
$fields) {
999 $this->customFields
= $fields;
1004 /* -( PhabricatorApplicationTransactionInterface )------------------------- */
1007 public function getApplicationTransactionEditor() {
1008 return new DifferentialTransactionEditor();
1011 public function getApplicationTransactionTemplate() {
1012 return new DifferentialTransaction();
1016 /* -( PhabricatorDestructibleInterface )----------------------------------- */
1019 public function destroyObjectPermanently(
1020 PhabricatorDestructionEngine
$engine) {
1022 $viewer = $engine->getViewer();
1024 $this->openTransaction();
1025 $diffs = id(new DifferentialDiffQuery())
1026 ->setViewer($viewer)
1027 ->withRevisionIDs(array($this->getID()))
1029 foreach ($diffs as $diff) {
1030 $engine->destroyObject($diff);
1033 id(new DifferentialAffectedPathEngine())
1034 ->setRevision($this)
1035 ->destroyAffectedPaths();
1037 $viewstate_query = id(new DifferentialViewStateQuery())
1038 ->setViewer($viewer)
1039 ->withObjectPHIDs(array($this->getPHID()));
1040 $viewstates = new PhabricatorQueryIterator($viewstate_query);
1041 foreach ($viewstates as $viewstate) {
1042 $viewstate->delete();
1046 $this->saveTransaction();
1050 /* -( PhabricatorFulltextInterface )--------------------------------------- */
1053 public function newFulltextEngine() {
1054 return new DifferentialRevisionFulltextEngine();
1058 /* -( PhabricatorFerretInterface )----------------------------------------- */
1061 public function newFerretEngine() {
1062 return new DifferentialRevisionFerretEngine();
1066 /* -( PhabricatorConduitResultInterface )---------------------------------- */
1069 public function getFieldSpecificationsForConduit() {
1071 id(new PhabricatorConduitSearchFieldSpecification())
1074 ->setDescription(pht('The revision title.')),
1075 id(new PhabricatorConduitSearchFieldSpecification())
1078 ->setDescription(pht('View URI for the revision.')),
1079 id(new PhabricatorConduitSearchFieldSpecification())
1080 ->setKey('authorPHID')
1082 ->setDescription(pht('Revision author PHID.')),
1083 id(new PhabricatorConduitSearchFieldSpecification())
1085 ->setType('map<string, wild>')
1086 ->setDescription(pht('Information about revision status.')),
1087 id(new PhabricatorConduitSearchFieldSpecification())
1088 ->setKey('repositoryPHID')
1090 ->setDescription(pht('Revision repository PHID.')),
1091 id(new PhabricatorConduitSearchFieldSpecification())
1092 ->setKey('diffPHID')
1094 ->setDescription(pht('Active diff PHID.')),
1095 id(new PhabricatorConduitSearchFieldSpecification())
1098 ->setDescription(pht('Revision summary.')),
1099 id(new PhabricatorConduitSearchFieldSpecification())
1100 ->setKey('testPlan')
1102 ->setDescription(pht('Revision test plan.')),
1103 id(new PhabricatorConduitSearchFieldSpecification())
1108 'True if this revision is in any draft state, and thus not '.
1109 'notifying reviewers and subscribers about changes.')),
1110 id(new PhabricatorConduitSearchFieldSpecification())
1111 ->setKey('holdAsDraft')
1115 'True if this revision is being held as a draft. It will not be '.
1116 'automatically submitted for review even if tests pass.')),
1120 public function getFieldValuesForConduit() {
1121 $status = $this->getStatusObject();
1122 $status_info = array(
1123 'value' => $status->getKey(),
1124 'name' => $status->getDisplayName(),
1125 'closed' => $status->isClosedStatus(),
1126 'color.ansi' => $status->getANSIColor(),
1130 'title' => $this->getTitle(),
1131 'uri' => PhabricatorEnv
::getURI($this->getURI()),
1132 'authorPHID' => $this->getAuthorPHID(),
1133 'status' => $status_info,
1134 'repositoryPHID' => $this->getRepositoryPHID(),
1135 'diffPHID' => $this->getActiveDiffPHID(),
1136 'summary' => $this->getSummary(),
1137 'testPlan' => $this->getTestPlan(),
1138 'isDraft' => !$this->getShouldBroadcast(),
1139 'holdAsDraft' => (bool)$this->getHoldAsDraft(),
1143 public function getConduitSearchAttachments() {
1145 id(new DifferentialReviewersSearchEngineAttachment())
1146 ->setAttachmentKey('reviewers'),
1151 /* -( PhabricatorDraftInterface )------------------------------------------ */
1154 public function newDraftEngine() {
1155 return new DifferentialRevisionDraftEngine();
1159 /* -( PhabricatorTimelineInterface )--------------------------------------- */
1162 public function newTimelineEngine() {
1163 return new DifferentialRevisionTimelineEngine();