3 final class PhabricatorRepositoryCommit
4 extends PhabricatorRepositoryDAO
6 PhabricatorPolicyInterface
,
7 PhabricatorFlaggableInterface
,
8 PhabricatorProjectInterface
,
9 PhabricatorTokenReceiverInterface
,
10 PhabricatorSubscribableInterface
,
11 PhabricatorMentionableInterface
,
12 HarbormasterBuildableInterface
,
13 HarbormasterCircleCIBuildableInterface
,
14 HarbormasterBuildkiteBuildableInterface
,
15 PhabricatorCustomFieldInterface
,
16 PhabricatorApplicationTransactionInterface
,
17 PhabricatorTimelineInterface
,
18 PhabricatorFulltextInterface
,
19 PhabricatorFerretInterface
,
20 PhabricatorConduitResultInterface
,
21 PhabricatorDraftInterface
{
23 protected $repositoryID;
25 protected $authorIdentityPHID;
26 protected $committerIdentityPHID;
27 protected $commitIdentifier;
29 protected $authorPHID;
30 protected $auditStatus = DiffusionCommitAuditStatus
::NONE
;
31 protected $summary = '';
32 protected $importStatus = 0;
34 const IMPORTED_MESSAGE
= 1;
35 const IMPORTED_CHANGE
= 2;
36 const IMPORTED_PUBLISH
= 8;
37 const IMPORTED_ALL
= 11;
39 const IMPORTED_PERMANENT
= 1024;
40 const IMPORTED_UNREACHABLE
= 2048;
42 private $commitData = self
::ATTACHABLE
;
43 private $audits = self
::ATTACHABLE
;
44 private $repository = self
::ATTACHABLE
;
45 private $customFields = self
::ATTACHABLE
;
46 private $authorIdentity = self
::ATTACHABLE
;
47 private $committerIdentity = self
::ATTACHABLE
;
49 private $drafts = array();
50 private $auditAuthorityPHIDs = array();
52 public function attachRepository(PhabricatorRepository
$repository) {
53 $this->repository
= $repository;
57 public function getRepository($assert_attached = true) {
58 if ($assert_attached) {
59 return $this->assertAttached($this->repository
);
61 return $this->repository
;
64 public function isPartiallyImported($mask) {
65 return (($mask & $this->getImportStatus()) == $mask);
68 public function isImported() {
69 return $this->isPartiallyImported(self
::IMPORTED_ALL
);
72 public function isUnreachable() {
73 return $this->isPartiallyImported(self
::IMPORTED_UNREACHABLE
);
76 public function writeImportStatusFlag($flag) {
77 return $this->adjustImportStatusFlag($flag, true);
80 public function clearImportStatusFlag($flag) {
81 return $this->adjustImportStatusFlag($flag, false);
84 private function adjustImportStatusFlag($flag, $set) {
85 $conn_w = $this->establishConnection('w');
86 $table_name = $this->getTableName();
92 'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d',
97 $this->setImportStatus($this->getImportStatus() |
$flag);
101 'UPDATE %T SET importStatus = (importStatus & ~%d) WHERE id = %d',
106 $this->setImportStatus($this->getImportStatus() & ~
$flag);
112 protected function getConfiguration() {
114 self
::CONFIG_AUX_PHID
=> true,
115 self
::CONFIG_TIMESTAMPS
=> false,
116 self
::CONFIG_COLUMN_SCHEMA
=> array(
117 'commitIdentifier' => 'text40',
118 'authorPHID' => 'phid?',
119 'authorIdentityPHID' => 'phid?',
120 'committerIdentityPHID' => 'phid?',
121 'auditStatus' => 'text32',
122 'summary' => 'text255',
123 'importStatus' => 'uint32',
125 self
::CONFIG_KEY_SCHEMA
=> array(
128 'columns' => array('phid'),
131 'repositoryID' => array(
132 'columns' => array('repositoryID', 'importStatus'),
134 'authorPHID' => array(
135 'columns' => array('authorPHID', 'auditStatus', 'epoch'),
137 'repositoryID_2' => array(
138 'columns' => array('repositoryID', 'epoch'),
140 'key_commit_identity' => array(
141 'columns' => array('commitIdentifier', 'repositoryID'),
144 'key_epoch' => array(
145 'columns' => array('epoch'),
147 'key_author' => array(
148 'columns' => array('authorPHID', 'epoch'),
151 self
::CONFIG_NO_MUTATE
=> array(
154 ) + parent
::getConfiguration();
157 public function generatePHID() {
158 return PhabricatorPHID
::generateNewPHID(
159 PhabricatorRepositoryCommitPHIDType
::TYPECONST
);
162 public function loadCommitData() {
163 if (!$this->getID()) {
166 return id(new PhabricatorRepositoryCommitData())->loadOneWhere(
171 public function attachCommitData(
172 PhabricatorRepositoryCommitData
$data = null) {
173 $this->commitData
= $data;
177 public function hasCommitData() {
178 return ($this->commitData
!== self
::ATTACHABLE
) &&
179 ($this->commitData
!== null);
182 public function getCommitData() {
183 return $this->assertAttached($this->commitData
);
186 public function attachAudits(array $audits) {
187 assert_instances_of($audits, 'PhabricatorRepositoryAuditRequest');
188 $this->audits
= $audits;
192 public function getAudits() {
193 return $this->assertAttached($this->audits
);
196 public function hasAttachedAudits() {
197 return ($this->audits
!== self
::ATTACHABLE
);
200 public function attachIdentities(
201 PhabricatorRepositoryIdentity
$author = null,
202 PhabricatorRepositoryIdentity
$committer = null) {
204 $this->authorIdentity
= $author;
205 $this->committerIdentity
= $committer;
210 public function getAuthorIdentity() {
211 return $this->assertAttached($this->authorIdentity
);
214 public function getCommitterIdentity() {
215 return $this->assertAttached($this->committerIdentity
);
218 public function attachAuditAuthority(
219 PhabricatorUser
$user,
222 $user_phid = $user->getPHID();
223 if (!$user->getPHID()) {
225 pht('You can not attach audit authority for a user with no PHID.'));
228 $this->auditAuthorityPHIDs
[$user_phid] = $authority;
233 public function hasAuditAuthority(
234 PhabricatorUser
$user,
235 PhabricatorRepositoryAuditRequest
$audit) {
237 $user_phid = $user->getPHID();
242 $map = $this->assertAttachedKey($this->auditAuthorityPHIDs
, $user_phid);
244 return isset($map[$audit->getAuditorPHID()]);
247 public function writeOwnersEdges(array $package_phids) {
248 $src_phid = $this->getPHID();
249 $edge_type = DiffusionCommitHasPackageEdgeType
::EDGECONST
;
251 $editor = new PhabricatorEdgeEditor();
253 $dst_phids = PhabricatorEdgeQuery
::loadDestinationPHIDs(
257 foreach ($dst_phids as $dst_phid) {
258 $editor->removeEdge($src_phid, $edge_type, $dst_phid);
261 foreach ($package_phids as $package_phid) {
262 $editor->addEdge($src_phid, $edge_type, $package_phid);
270 public function getAuditorPHIDsForEdit() {
271 $audits = $this->getAudits();
272 return mpull($audits, 'getAuditorPHID');
275 public function delete() {
276 $data = $this->loadCommitData();
277 $audits = id(new PhabricatorRepositoryAuditRequest())
278 ->loadAllWhere('commitPHID = %s', $this->getPHID());
279 $this->openTransaction();
284 foreach ($audits as $audit) {
287 $result = parent
::delete();
289 $this->saveTransaction();
293 public function getDateCreated() {
294 // This is primarily to make analysis of commits with the Fact engine work.
295 return $this->getEpoch();
298 public function getURI() {
299 return '/'.$this->getMonogram();
303 * Synchronize a commit's overall audit status with the individual audit
306 public function updateAuditStatus(array $requests) {
307 assert_instances_of($requests, 'PhabricatorRepositoryAuditRequest');
309 $any_concern = false;
313 foreach ($requests as $request) {
314 switch ($request->getAuditStatus()) {
315 case PhabricatorAuditRequestStatus
::AUDIT_REQUIRED
:
316 case PhabricatorAuditRequestStatus
::AUDIT_REQUESTED
:
319 case PhabricatorAuditRequestStatus
::ACCEPTED
:
322 case PhabricatorAuditRequestStatus
::CONCERNED
:
329 if ($this->isAuditStatusNeedsVerification()) {
330 // If the change is in "Needs Verification", we keep it there as
331 // long as any auditors still have concerns.
332 $status = DiffusionCommitAuditStatus
::NEEDS_VERIFICATION
;
334 $status = DiffusionCommitAuditStatus
::CONCERN_RAISED
;
336 } else if ($any_accept) {
338 $status = DiffusionCommitAuditStatus
::PARTIALLY_AUDITED
;
340 $status = DiffusionCommitAuditStatus
::AUDITED
;
342 } else if ($any_need) {
343 $status = DiffusionCommitAuditStatus
::NEEDS_AUDIT
;
345 $status = DiffusionCommitAuditStatus
::NONE
;
348 return $this->setAuditStatus($status);
351 public function getMonogram() {
352 $repository = $this->getRepository();
353 $callsign = $repository->getCallsign();
354 $identifier = $this->getCommitIdentifier();
355 if ($callsign !== null) {
356 return "r{$callsign}{$identifier}";
358 $id = $repository->getID();
359 return "R{$id}:{$identifier}";
363 public function getDisplayName() {
364 $repository = $this->getRepository();
365 $identifier = $this->getCommitIdentifier();
366 return $repository->formatCommitName($identifier);
370 * Return a local display name for use in the context of the containing
373 * In Git and Mercurial, this returns only a short hash, like "abcdef012345".
374 * See @{method:getDisplayName} for a short name that always includes
375 * repository context.
377 * @return string Short human-readable name for use inside a repository.
379 public function getLocalName() {
380 $repository = $this->getRepository();
381 $identifier = $this->getCommitIdentifier();
382 return $repository->formatCommitName($identifier, $local = true);
385 public function loadIdentities(PhabricatorUser
$viewer) {
386 if ($this->authorIdentity
!== self
::ATTACHABLE
) {
390 $commit = id(new DiffusionCommitQuery())
392 ->withIDs(array($this->getID()))
393 ->needIdentities(true)
396 $author_identity = $commit->getAuthorIdentity();
397 $committer_identity = $commit->getCommitterIdentity();
399 return $this->attachIdentities($author_identity, $committer_identity);
402 public function hasCommitterIdentity() {
403 return ($this->getCommitterIdentity() !== null);
406 public function hasAuthorIdentity() {
407 return ($this->getAuthorIdentity() !== null);
410 public function getCommitterDisplayPHID() {
411 if ($this->hasCommitterIdentity()) {
412 return $this->getCommitterIdentity()->getIdentityDisplayPHID();
415 $data = $this->getCommitData();
416 return $data->getCommitDetail('committerPHID');
419 public function getAuthorDisplayPHID() {
420 if ($this->hasAuthorIdentity()) {
421 return $this->getAuthorIdentity()->getIdentityDisplayPHID();
424 $data = $this->getCommitData();
425 return $data->getCommitDetail('authorPHID');
428 public function getEffectiveAuthorPHID() {
429 if ($this->hasAuthorIdentity()) {
430 $identity = $this->getAuthorIdentity();
431 if ($identity->hasEffectiveUser()) {
432 return $identity->getCurrentEffectiveUserPHID();
436 $data = $this->getCommitData();
437 return $data->getCommitDetail('authorPHID');
440 public function getAuditStatusObject() {
441 $status = $this->getAuditStatus();
442 return DiffusionCommitAuditStatus
::newForStatus($status);
445 public function isAuditStatusNoAudit() {
446 return $this->getAuditStatusObject()->isNoAudit();
449 public function isAuditStatusNeedsAudit() {
450 return $this->getAuditStatusObject()->isNeedsAudit();
453 public function isAuditStatusConcernRaised() {
454 return $this->getAuditStatusObject()->isConcernRaised();
457 public function isAuditStatusNeedsVerification() {
458 return $this->getAuditStatusObject()->isNeedsVerification();
461 public function isAuditStatusPartiallyAudited() {
462 return $this->getAuditStatusObject()->isPartiallyAudited();
465 public function isAuditStatusAudited() {
466 return $this->getAuditStatusObject()->isAudited();
469 public function isPermanentCommit() {
470 return (bool)$this->isPartiallyImported(self
::IMPORTED_PERMANENT
);
473 public function newCommitAuthorView(PhabricatorUser
$viewer) {
474 $author_phid = $this->getAuthorDisplayPHID();
476 $handles = $viewer->loadHandles(array($author_phid));
477 return $handles[$author_phid]->renderLink();
480 $author = $this->getRawAuthorStringForDisplay();
481 if ($author !== null && strlen($author)) {
482 return DiffusionView
::renderName($author);
488 public function newCommitCommitterView(PhabricatorUser
$viewer) {
489 $committer_phid = $this->getCommitterDisplayPHID();
490 if ($committer_phid) {
491 $handles = $viewer->loadHandles(array($committer_phid));
492 return $handles[$committer_phid]->renderLink();
495 $committer = $this->getRawCommitterStringForDisplay();
496 if ($committer !== null && strlen($committer)) {
497 return DiffusionView
::renderName($committer);
503 public function isAuthorSameAsCommitter() {
504 $author_phid = $this->getAuthorDisplayPHID();
505 $committer_phid = $this->getCommitterDisplayPHID();
507 if ($author_phid && $committer_phid) {
508 return ($author_phid === $committer_phid);
511 if ($author_phid ||
$committer_phid) {
515 $author = $this->getRawAuthorStringForDisplay();
516 $committer = $this->getRawCommitterStringForDisplay();
518 return ($author === $committer);
521 private function getRawAuthorStringForDisplay() {
522 $data = $this->getCommitData();
523 return $data->getAuthorString();
526 private function getRawCommitterStringForDisplay() {
527 $data = $this->getCommitData();
528 return $data->getCommitterString();
531 public function getCommitMessageForDisplay() {
532 $data = $this->getCommitData();
533 $message = $data->getCommitMessage();
537 public function newCommitRef(PhabricatorUser
$viewer) {
538 $repository = $this->getRepository();
540 $future = $repository->newConduitFuture(
542 'internal.commit.search',
544 'constraints' => array(
545 'repositoryPHIDs' => array($repository->getPHID()),
546 'phids' => array($this->getPHID()),
549 $result = $future->resolve();
551 $commit_display = $this->getMonogram();
553 if (empty($result['data'])) {
556 'Unable to retrieve details for commit "%s"!',
560 if (count($result['data']) !== 1) {
563 'Got too many results (%s) for commit "%s", expected %s.',
564 phutil_count($result['data']),
569 $record = head($result['data']);
570 $ref_record = idxv($record, array('fields', 'ref'));
575 'Unable to retrieve CommitRef record for commit "%s".',
579 return DiffusionCommitRef
::newFromDictionary($ref_record);
582 /* -( PhabricatorPolicyInterface )----------------------------------------- */
584 public function getCapabilities() {
586 PhabricatorPolicyCapability
::CAN_VIEW
,
587 PhabricatorPolicyCapability
::CAN_EDIT
,
591 public function getPolicy($capability) {
592 switch ($capability) {
593 case PhabricatorPolicyCapability
::CAN_VIEW
:
594 return $this->getRepository()->getPolicy($capability);
595 case PhabricatorPolicyCapability
::CAN_EDIT
:
596 return PhabricatorPolicies
::POLICY_USER
;
600 public function hasAutomaticCapability($capability, PhabricatorUser
$viewer) {
601 return $this->getRepository()->hasAutomaticCapability($capability, $viewer);
604 public function describeAutomaticCapability($capability) {
606 'Commits inherit the policies of the repository they belong to.');
610 /* -( PhabricatorTokenReceiverInterface )---------------------------------- */
612 public function getUsersToNotifyOfTokenGiven() {
614 $this->getAuthorPHID(),
618 /* -( Stuff for serialization )---------------------------------------------- */
621 * NOTE: this is not a complete serialization; only the 'protected' fields are
622 * involved. This is due to ease of (ab)using the Lisk abstraction to get this
623 * done, as well as complexity of the other fields.
625 public function toDictionary() {
627 'repositoryID' => $this->getRepositoryID(),
628 'phid' => $this->getPHID(),
629 'commitIdentifier' => $this->getCommitIdentifier(),
630 'epoch' => $this->getEpoch(),
631 'authorPHID' => $this->getAuthorPHID(),
632 'auditStatus' => $this->getAuditStatus(),
633 'summary' => $this->getSummary(),
634 'importStatus' => $this->getImportStatus(),
638 public static function newFromDictionary(array $dict) {
639 return id(new PhabricatorRepositoryCommit())
640 ->loadFromArray($dict);
644 /* -( HarbormasterBuildableInterface )------------------------------------- */
647 public function getHarbormasterBuildableDisplayPHID() {
648 return $this->getHarbormasterBuildablePHID();
651 public function getHarbormasterBuildablePHID() {
652 return $this->getPHID();
655 public function getHarbormasterContainerPHID() {
656 return $this->getRepository()->getPHID();
659 public function getBuildVariables() {
662 $results['buildable.commit'] = $this->getCommitIdentifier();
663 $repo = $this->getRepository();
665 $results['repository.callsign'] = $repo->getCallsign();
666 $results['repository.phid'] = $repo->getPHID();
667 $results['repository.vcs'] = $repo->getVersionControlSystem();
668 $results['repository.uri'] = $repo->getPublicCloneURI();
673 public function getAvailableBuildVariables() {
675 'buildable.commit' => pht('The commit identifier, if applicable.'),
676 'repository.callsign' =>
677 pht('The callsign of the repository.'),
679 pht('The PHID of the repository.'),
681 pht('The version control system, either "svn", "hg" or "git".'),
683 pht('The URI to clone or checkout the repository from.'),
687 public function newBuildableEngine() {
688 return new DiffusionBuildableEngine();
692 /* -( HarbormasterCircleCIBuildableInterface )----------------------------- */
695 public function getCircleCIGitHubRepositoryURI() {
696 $repository = $this->getRepository();
698 $commit_phid = $this->getPHID();
699 $repository_phid = $repository->getPHID();
701 if ($repository->isHosted()) {
704 'This commit ("%s") is associated with a hosted repository '.
705 '("%s"). Repositories must be imported from GitHub to be built '.
711 $remote_uri = $repository->getRemoteURI();
712 $path = HarbormasterCircleCIBuildStepImplementation
::getGitHubPath(
717 'This commit ("%s") is associated with a repository ("%s") which '.
718 'has a remote URI ("%s") that does not appear to be hosted on '.
719 'GitHub. Repositories must be hosted on GitHub to be built with '.
729 public function getCircleCIBuildIdentifierType() {
733 public function getCircleCIBuildIdentifier() {
734 return $this->getCommitIdentifier();
738 /* -( HarbormasterBuildkiteBuildableInterface )---------------------------- */
741 public function getBuildkiteBranch() {
742 $viewer = PhabricatorUser
::getOmnipotentUser();
743 $repository = $this->getRepository();
745 $branches = DiffusionQuery
::callConduitWithDiffusionRequest(
747 DiffusionRequest
::newFromDictionary(
749 'repository' => $repository,
752 'diffusion.branchquery',
754 'contains' => $this->getCommitIdentifier(),
755 'repository' => $repository->getPHID(),
761 'Commit "%s" is not an ancestor of any branch head, so it can not '.
762 'be built with Buildkite.',
763 $this->getCommitIdentifier()));
766 $branch = head($branches);
768 return 'refs/heads/'.$branch['shortName'];
771 public function getBuildkiteCommit() {
772 return $this->getCommitIdentifier();
776 /* -( PhabricatorCustomFieldInterface )------------------------------------ */
779 public function getCustomFieldSpecificationForRole($role) {
780 return PhabricatorEnv
::getEnvConfig('diffusion.fields');
783 public function getCustomFieldBaseClass() {
784 return 'PhabricatorCommitCustomField';
787 public function getCustomFields() {
788 return $this->assertAttached($this->customFields
);
791 public function attachCustomFields(PhabricatorCustomFieldAttachment
$fields) {
792 $this->customFields
= $fields;
797 /* -( PhabricatorSubscribableInterface )----------------------------------- */
800 public function isAutomaticallySubscribed($phid) {
802 // TODO: This should also list auditors, but handling that is a bit messy
803 // right now because we are not guaranteed to have the data. (It should not
804 // include resigned auditors.)
806 return ($phid == $this->getAuthorPHID());
810 /* -( PhabricatorApplicationTransactionInterface )------------------------- */
813 public function getApplicationTransactionEditor() {
814 return new PhabricatorAuditEditor();
817 public function getApplicationTransactionTemplate() {
818 return new PhabricatorAuditTransaction();
821 /* -( PhabricatorFulltextInterface )--------------------------------------- */
824 public function newFulltextEngine() {
825 return new DiffusionCommitFulltextEngine();
829 /* -( PhabricatorFerretInterface )----------------------------------------- */
832 public function newFerretEngine() {
833 return new DiffusionCommitFerretEngine();
837 /* -( PhabricatorConduitResultInterface )---------------------------------- */
839 public function getFieldSpecificationsForConduit() {
841 id(new PhabricatorConduitSearchFieldSpecification())
842 ->setKey('identifier')
844 ->setDescription(pht('The commit identifier.')),
845 id(new PhabricatorConduitSearchFieldSpecification())
846 ->setKey('repositoryPHID')
848 ->setDescription(pht('The repository this commit belongs to.')),
849 id(new PhabricatorConduitSearchFieldSpecification())
851 ->setType('map<string, wild>')
852 ->setDescription(pht('Information about the commit author.')),
853 id(new PhabricatorConduitSearchFieldSpecification())
854 ->setKey('committer')
855 ->setType('map<string, wild>')
856 ->setDescription(pht('Information about the committer.')),
857 id(new PhabricatorConduitSearchFieldSpecification())
858 ->setKey('isImported')
860 ->setDescription(pht('True if the commit is fully imported.')),
861 id(new PhabricatorConduitSearchFieldSpecification())
862 ->setKey('isUnreachable')
866 'True if the commit is not the ancestor of any tag, branch, or '.
868 id(new PhabricatorConduitSearchFieldSpecification())
869 ->setKey('auditStatus')
870 ->setType('map<string, wild>')
871 ->setDescription(pht('Information about the current audit status.')),
872 id(new PhabricatorConduitSearchFieldSpecification())
875 ->setDescription(pht('The commit message.')),
879 public function getFieldValuesForConduit() {
880 $data = $this->getCommitData();
882 $author_identity = $this->getAuthorIdentity();
883 if ($author_identity) {
884 $author_name = $author_identity->getIdentityDisplayName();
885 $author_email = $author_identity->getIdentityEmailAddress();
886 $author_raw = $author_identity->getIdentityName();
887 $author_identity_phid = $author_identity->getPHID();
888 $author_user_phid = $author_identity->getCurrentEffectiveUserPHID();
891 $author_email = null;
893 $author_identity_phid = null;
894 $author_user_phid = null;
897 $committer_identity = $this->getCommitterIdentity();
898 if ($committer_identity) {
899 $committer_name = $committer_identity->getIdentityDisplayName();
900 $committer_email = $committer_identity->getIdentityEmailAddress();
901 $committer_raw = $committer_identity->getIdentityName();
902 $committer_identity_phid = $committer_identity->getPHID();
903 $committer_user_phid = $committer_identity->getCurrentEffectiveUserPHID();
905 $committer_name = null;
906 $committer_email = null;
907 $committer_raw = null;
908 $committer_identity_phid = null;
909 $committer_user_phid = null;
912 $author_epoch = $data->getAuthorEpoch();
914 $audit_status = $this->getAuditStatusObject();
917 'identifier' => $this->getCommitIdentifier(),
918 'repositoryPHID' => $this->getRepository()->getPHID(),
920 'name' => $author_name,
921 'email' => $author_email,
922 'raw' => $author_raw,
923 'epoch' => $author_epoch,
924 'identityPHID' => $author_identity_phid,
925 'userPHID' => $author_user_phid,
927 'committer' => array(
928 'name' => $committer_name,
929 'email' => $committer_email,
930 'raw' => $committer_raw,
931 'epoch' => (int)$this->getEpoch(),
932 'identityPHID' => $committer_identity_phid,
933 'userPHID' => $committer_user_phid,
935 'isUnreachable' => (bool)$this->isUnreachable(),
936 'isImported' => (bool)$this->isImported(),
937 'auditStatus' => array(
938 'value' => $audit_status->getKey(),
939 'name' => $audit_status->getName(),
940 'closed' => (bool)$audit_status->getIsClosed(),
941 'color.ansi' => $audit_status->getAnsiColor(),
943 'message' => $data->getCommitMessage(),
947 public function getConduitSearchAttachments() {
949 id(new DiffusionAuditorsSearchEngineAttachment())
950 ->setAttachmentKey('auditors'),
955 /* -( PhabricatorDraftInterface )------------------------------------------ */
957 public function newDraftEngine() {
958 return new DiffusionCommitDraftEngine();
961 public function getHasDraft(PhabricatorUser
$viewer) {
962 return $this->assertAttachedKey($this->drafts
, $viewer->getCacheFragment());
965 public function attachHasDraft(PhabricatorUser
$viewer, $has_draft) {
966 $this->drafts
[$viewer->getCacheFragment()] = $has_draft;
971 /* -( PhabricatorTimelineInterface )--------------------------------------- */
974 public function newTimelineEngine() {
975 return new DiffusionCommitTimelineEngine();