@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class DifferentialDiff
4 extends DifferentialDAO
5 implements
6 PhabricatorPolicyInterface,
7 PhabricatorExtendedPolicyInterface,
8 HarbormasterBuildableInterface,
9 HarbormasterCircleCIBuildableInterface,
10 HarbormasterBuildkiteBuildableInterface,
11 PhabricatorApplicationTransactionInterface,
12 PhabricatorDestructibleInterface,
13 PhabricatorConduitResultInterface {
14
15 protected $revisionID;
16 protected $authorPHID;
17 protected $repositoryPHID;
18 protected $commitPHID;
19
20 protected $sourceMachine;
21 protected $sourcePath;
22
23 protected $sourceControlSystem;
24 protected $sourceControlBaseRevision;
25 protected $sourceControlPath;
26
27 protected $lintStatus;
28 protected $unitStatus;
29
30 protected $lineCount;
31
32 protected $branch;
33 protected $bookmark;
34
35 protected $creationMethod;
36 protected $repositoryUUID;
37
38 protected $description;
39
40 protected $viewPolicy;
41
42 private $unsavedChangesets = array();
43 private $changesets = self::ATTACHABLE;
44 private $revision = self::ATTACHABLE;
45 private $properties = self::ATTACHABLE;
46 private $buildable = self::ATTACHABLE;
47
48 private $unitMessages = self::ATTACHABLE;
49
50 protected function getConfiguration() {
51 return array(
52 self::CONFIG_AUX_PHID => true,
53 self::CONFIG_COLUMN_SCHEMA => array(
54 'revisionID' => 'id?',
55 'authorPHID' => 'phid?',
56 'repositoryPHID' => 'phid?',
57 'sourceMachine' => 'text255?',
58 'sourcePath' => 'text255?',
59 'sourceControlSystem' => 'text64?',
60 'sourceControlBaseRevision' => 'text255?',
61 'sourceControlPath' => 'text255?',
62 'lintStatus' => 'uint32',
63 'unitStatus' => 'uint32',
64 'lineCount' => 'uint32',
65 'branch' => 'text255?',
66 'bookmark' => 'text255?',
67 'repositoryUUID' => 'text64?',
68 'commitPHID' => 'phid?',
69
70 // T6203/NULLABILITY
71 // These should be non-null; all diffs should have a creation method
72 // and the description should just be empty.
73 'creationMethod' => 'text255?',
74 'description' => 'text255?',
75 ),
76 self::CONFIG_KEY_SCHEMA => array(
77 'revisionID' => array(
78 'columns' => array('revisionID'),
79 ),
80 'key_commit' => array(
81 'columns' => array('commitPHID'),
82 ),
83 ),
84 ) + parent::getConfiguration();
85 }
86
87 public function generatePHID() {
88 return PhabricatorPHID::generateNewPHID(
89 DifferentialDiffPHIDType::TYPECONST);
90 }
91
92 public function addUnsavedChangeset(DifferentialChangeset $changeset) {
93 if ($this->changesets === null) {
94 $this->changesets = array();
95 }
96 $this->unsavedChangesets[] = $changeset;
97 $this->changesets[] = $changeset;
98 return $this;
99 }
100
101 /**
102 * @param array<DifferentialChangeset> $changesets
103 */
104 public function attachChangesets(array $changesets) {
105 assert_instances_of($changesets, DifferentialChangeset::class);
106 $this->changesets = $changesets;
107 return $this;
108 }
109
110 public function getChangesets() {
111 return $this->assertAttached($this->changesets);
112 }
113
114 public function loadChangesets() {
115 if (!$this->getID()) {
116 return array();
117 }
118 $changesets = id(new DifferentialChangeset())->loadAllWhere(
119 'diffID = %d',
120 $this->getID());
121
122 foreach ($changesets as $changeset) {
123 $changeset->attachDiff($this);
124 }
125
126 return $changesets;
127 }
128
129 public function save() {
130 $this->openTransaction();
131 $ret = parent::save();
132 foreach ($this->unsavedChangesets as $changeset) {
133 $changeset->setDiffID($this->getID());
134 $changeset->save();
135 }
136 $this->saveTransaction();
137 return $ret;
138 }
139
140 public static function initializeNewDiff(PhabricatorUser $actor) {
141 $app = id(new PhabricatorApplicationQuery())
142 ->setViewer($actor)
143 ->withClasses(array(PhabricatorDifferentialApplication::class))
144 ->executeOne();
145 $view_policy = $app->getPolicy(
146 DifferentialDefaultViewCapability::CAPABILITY);
147
148 $diff = id(new DifferentialDiff())
149 ->setViewPolicy($view_policy);
150
151 return $diff;
152 }
153
154 /**
155 * @param PhabricatorUser $actor
156 * @param array<ArcanistDiffChange> $changes
157 */
158 public static function newFromRawChanges(
159 PhabricatorUser $actor,
160 array $changes) {
161
162 assert_instances_of($changes, ArcanistDiffChange::class);
163
164 $diff = self::initializeNewDiff($actor);
165 return self::buildChangesetsFromRawChanges($diff, $changes);
166 }
167
168 /**
169 * @param array<ArcanistDiffChange> $changes
170 */
171 public static function newEphemeralFromRawChanges(array $changes) {
172 assert_instances_of($changes, ArcanistDiffChange::class);
173
174 $diff = id(new DifferentialDiff())->makeEphemeral();
175 return self::buildChangesetsFromRawChanges($diff, $changes);
176 }
177
178 /**
179 * @param array<ArcanistDiffChange> $changes
180 */
181 private static function buildChangesetsFromRawChanges(
182 DifferentialDiff $diff,
183 array $changes) {
184
185 // There may not be any changes; initialize the changesets list so that
186 // we don't throw later when accessing it.
187 $diff->attachChangesets(array());
188
189 $lines = 0;
190 foreach ($changes as $change) {
191 if ($change->getType() == ArcanistDiffChangeType::TYPE_MESSAGE) {
192 // If a user pastes a diff into Differential which includes a commit
193 // message (e.g., they ran `git show` to generate it), discard that
194 // change when constructing a DifferentialDiff.
195 continue;
196 }
197
198 $changeset = new DifferentialChangeset();
199 $add_lines = 0;
200 $del_lines = 0;
201 $first_line = PHP_INT_MAX;
202 $hunks = $change->getHunks();
203 if ($hunks) {
204 foreach ($hunks as $hunk) {
205 $dhunk = new DifferentialHunk();
206 $dhunk->setOldOffset($hunk->getOldOffset());
207 $dhunk->setOldLen($hunk->getOldLength());
208 $dhunk->setNewOffset($hunk->getNewOffset());
209 $dhunk->setNewLen($hunk->getNewLength());
210 $dhunk->setChanges($hunk->getCorpus());
211 $changeset->addUnsavedHunk($dhunk);
212 $add_lines += $hunk->getAddLines();
213 $del_lines += $hunk->getDelLines();
214 $added_lines = $hunk->getChangedLines('new');
215 if ($added_lines) {
216 $first_line = min($first_line, head_key($added_lines));
217 }
218 }
219 $lines += $add_lines + $del_lines;
220 } else {
221 // This happens when you add empty files.
222 $changeset->attachHunks(array());
223 }
224
225 $metadata = $change->getAllMetadata();
226 if ($first_line != PHP_INT_MAX) {
227 $metadata['line:first'] = $first_line;
228 }
229
230 $changeset->setOldFile($change->getOldPath());
231 $changeset->setFilename($change->getCurrentPath());
232 $changeset->setChangeType($change->getType());
233
234 $changeset->setFileType($change->getFileType());
235 $changeset->setMetadata($metadata);
236 $changeset->setOldProperties($change->getOldProperties());
237 $changeset->setNewProperties($change->getNewProperties());
238 $changeset->setAwayPaths($change->getAwayPaths());
239 $changeset->setAddLines($add_lines);
240 $changeset->setDelLines($del_lines);
241
242 $diff->addUnsavedChangeset($changeset);
243 }
244 $diff->setLineCount($lines);
245
246 $changesets = $diff->getChangesets();
247
248 // TODO: This is "safe", but it would be better to propagate a real user
249 // down the stack.
250 $viewer = PhabricatorUser::getOmnipotentUser();
251
252 id(new DifferentialChangesetEngine())
253 ->setViewer($viewer)
254 ->rebuildChangesets($changesets);
255
256 return $diff;
257 }
258
259 public function getDiffDict() {
260 $dict = array(
261 'id' => $this->getID(),
262 'revisionID' => $this->getRevisionID(),
263 'dateCreated' => $this->getDateCreated(),
264 'dateModified' => $this->getDateModified(),
265 'sourceControlBaseRevision' => $this->getSourceControlBaseRevision(),
266 'sourceControlPath' => $this->getSourceControlPath(),
267 'sourceControlSystem' => $this->getSourceControlSystem(),
268 'branch' => $this->getBranch(),
269 'bookmark' => $this->getBookmark(),
270 'creationMethod' => $this->getCreationMethod(),
271 'description' => $this->getDescription(),
272 'unitStatus' => $this->getUnitStatus(),
273 'lintStatus' => $this->getLintStatus(),
274 'changes' => array(),
275 );
276
277 $dict['changes'] = $this->buildChangesList();
278
279 return $dict + $this->getDiffAuthorshipDict();
280 }
281
282 public function getDiffAuthorshipDict() {
283 $dict = array('properties' => array());
284
285 $properties = id(new DifferentialDiffProperty())->loadAllWhere(
286 'diffID = %d',
287 $this->getID());
288 foreach ($properties as $property) {
289 $dict['properties'][$property->getName()] = $property->getData();
290
291 if ($property->getName() == 'local:commits') {
292 foreach ($property->getData() as $commit) {
293 $dict['authorName'] = $commit['author'];
294 $dict['authorEmail'] = idx($commit, 'authorEmail');
295 break;
296 }
297 }
298 }
299
300 return $dict;
301 }
302
303 public function buildChangesList() {
304 $changes = array();
305 foreach ($this->getChangesets() as $changeset) {
306 $hunks = array();
307 foreach ($changeset->getHunks() as $hunk) {
308 $hunks[] = array(
309 'oldOffset' => $hunk->getOldOffset(),
310 'newOffset' => $hunk->getNewOffset(),
311 'oldLength' => $hunk->getOldLen(),
312 'newLength' => $hunk->getNewLen(),
313 'addLines' => null,
314 'delLines' => null,
315 'isMissingOldNewline' => null,
316 'isMissingNewNewline' => null,
317 'corpus' => $hunk->getChanges(),
318 );
319 }
320 $change = array(
321 'id' => $changeset->getID(),
322 'metadata' => $changeset->getMetadata(),
323 'oldPath' => $changeset->getOldFile(),
324 'currentPath' => $changeset->getFilename(),
325 'awayPaths' => $changeset->getAwayPaths(),
326 'oldProperties' => $changeset->getOldProperties(),
327 'newProperties' => $changeset->getNewProperties(),
328 'type' => $changeset->getChangeType(),
329 'fileType' => $changeset->getFileType(),
330 'commitHash' => null,
331 'addLines' => $changeset->getAddLines(),
332 'delLines' => $changeset->getDelLines(),
333 'hunks' => $hunks,
334 );
335 $changes[] = $change;
336 }
337 return $changes;
338 }
339
340 public function hasRevision() {
341 return $this->revision !== self::ATTACHABLE;
342 }
343
344 public function getRevision() {
345 return $this->assertAttached($this->revision);
346 }
347
348 public function attachRevision(?DifferentialRevision $revision = null) {
349 $this->revision = $revision;
350 return $this;
351 }
352
353 public function attachProperty($key, $value) {
354 if (!is_array($this->properties)) {
355 $this->properties = array();
356 }
357 $this->properties[$key] = $value;
358 return $this;
359 }
360
361 public function getProperty($key) {
362 return $this->assertAttachedKey($this->properties, $key);
363 }
364
365 public function hasDiffProperty($key) {
366 $properties = $this->getDiffProperties();
367 return array_key_exists($key, $properties);
368 }
369
370 public function attachDiffProperties(array $properties) {
371 $this->properties = $properties;
372 return $this;
373 }
374
375 public function getDiffProperties() {
376 return $this->assertAttached($this->properties);
377 }
378
379 public function attachBuildable(?HarbormasterBuildable $buildable = null) {
380 $this->buildable = $buildable;
381 return $this;
382 }
383
384 public function getBuildable() {
385 return $this->assertAttached($this->buildable);
386 }
387
388 public function getBuildTargetPHIDs() {
389 $buildable = $this->getBuildable();
390
391 if (!$buildable) {
392 return array();
393 }
394
395 $target_phids = array();
396 foreach ($buildable->getBuilds() as $build) {
397 foreach ($build->getBuildTargets() as $target) {
398 $target_phids[] = $target->getPHID();
399 }
400 }
401
402 return $target_phids;
403 }
404
405 public function loadCoverageMap(PhabricatorUser $viewer) {
406 $target_phids = $this->getBuildTargetPHIDs();
407 if (!$target_phids) {
408 return array();
409 }
410
411 $unit = id(new HarbormasterBuildUnitMessageQuery())
412 ->setViewer($viewer)
413 ->withBuildTargetPHIDs($target_phids)
414 ->execute();
415
416 $map = array();
417 foreach ($unit as $message) {
418 $coverage = $message->getProperty('coverage', array());
419 foreach ($coverage as $path => $coverage_data) {
420 $map[$path][] = $coverage_data;
421 }
422 }
423
424 foreach ($map as $path => $coverage_items) {
425 $map[$path] = ArcanistUnitTestResult::mergeCoverage($coverage_items);
426 }
427
428 return $map;
429 }
430
431 public function getURI() {
432 $id = $this->getID();
433 return "/differential/diff/{$id}/";
434 }
435
436
437 public function attachUnitMessages(array $unit_messages) {
438 $this->unitMessages = $unit_messages;
439 return $this;
440 }
441
442
443 public function getUnitMessages() {
444 return $this->assertAttached($this->unitMessages);
445 }
446
447
448/* -( PhabricatorPolicyInterface )----------------------------------------- */
449
450
451 public function getCapabilities() {
452 return array(
453 PhabricatorPolicyCapability::CAN_VIEW,
454 );
455 }
456
457 public function getPolicy($capability) {
458 if ($this->hasRevision()) {
459 return PhabricatorPolicies::getMostOpenPolicy();
460 }
461
462 return $this->viewPolicy;
463 }
464
465 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
466 if ($this->hasRevision()) {
467 return $this->getRevision()->hasAutomaticCapability($capability, $viewer);
468 }
469
470 return ($this->getAuthorPHID() == $viewer->getPHID());
471 }
472
473 public function describeAutomaticCapability($capability) {
474 if ($this->hasRevision()) {
475 return pht(
476 'This diff is attached to a revision, and inherits its policies.');
477 }
478
479 return pht('The author of a diff can see it.');
480 }
481
482
483/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
484
485
486 public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
487 $extended = array();
488
489 switch ($capability) {
490 case PhabricatorPolicyCapability::CAN_VIEW:
491 if ($this->hasRevision()) {
492 $extended[] = array(
493 $this->getRevision(),
494 PhabricatorPolicyCapability::CAN_VIEW,
495 );
496 } else if ($this->getRepositoryPHID()) {
497 $extended[] = array(
498 $this->getRepositoryPHID(),
499 PhabricatorPolicyCapability::CAN_VIEW,
500 );
501 }
502 break;
503 }
504
505 return $extended;
506 }
507
508
509/* -( HarbormasterBuildableInterface )------------------------------------- */
510
511
512 public function getHarbormasterBuildableDisplayPHID() {
513 $container_phid = $this->getHarbormasterContainerPHID();
514 if ($container_phid) {
515 return $container_phid;
516 }
517
518 return $this->getHarbormasterBuildablePHID();
519 }
520
521 public function getHarbormasterBuildablePHID() {
522 return $this->getPHID();
523 }
524
525 public function getHarbormasterContainerPHID() {
526 if ($this->getRevisionID()) {
527 $revision = id(new DifferentialRevision())->load($this->getRevisionID());
528 if ($revision) {
529 return $revision->getPHID();
530 }
531 }
532
533 return null;
534 }
535
536 public function getBuildVariables() {
537 $results = array();
538
539 $results['buildable.diff'] = $this->getID();
540 if ($this->revisionID) {
541 $revision = $this->getRevision();
542 $results['buildable.revision'] = $revision->getID();
543 $repo = $revision->getRepository();
544
545 if ($repo) {
546 $results['repository.callsign'] = $repo->getCallsign();
547 $results['repository.phid'] = $repo->getPHID();
548 $results['repository.vcs'] = $repo->getVersionControlSystem();
549 $results['repository.uri'] = $repo->getPublicCloneURI();
550
551 $results['repository.staging.uri'] = $repo->getStagingURI();
552 $results['repository.staging.ref'] = $this->getStagingRef();
553 }
554 }
555
556 return $results;
557 }
558
559 public function getAvailableBuildVariables() {
560 return array(
561 'buildable.diff' =>
562 pht('The differential diff ID, if applicable.'),
563 'buildable.revision' =>
564 pht('The differential revision ID, if applicable.'),
565 'repository.callsign' =>
566 pht('The callsign of the repository.'),
567 'repository.phid' =>
568 pht('The PHID of the repository.'),
569 'repository.vcs' =>
570 pht('The version control system, either "svn", "hg" or "git".'),
571 'repository.uri' =>
572 pht('The URI to clone or checkout the repository from.'),
573 'repository.staging.uri' =>
574 pht('The URI of the staging repository.'),
575 'repository.staging.ref' =>
576 pht('The ref name for this change in the staging repository.'),
577 );
578 }
579
580 public function newBuildableEngine() {
581 return new DifferentialBuildableEngine();
582 }
583
584
585/* -( HarbormasterCircleCIBuildableInterface )----------------------------- */
586
587
588 public function getCircleCIGitHubRepositoryURI() {
589 $diff_phid = $this->getPHID();
590 $repository_phid = $this->getRepositoryPHID();
591 if (!$repository_phid) {
592 throw new Exception(
593 pht(
594 'This diff ("%s") is not associated with a repository. A diff '.
595 'must belong to a tracked repository to be built by CircleCI.',
596 $diff_phid));
597 }
598
599 $repository = id(new PhabricatorRepositoryQuery())
600 ->setViewer(PhabricatorUser::getOmnipotentUser())
601 ->withPHIDs(array($repository_phid))
602 ->executeOne();
603 if (!$repository) {
604 throw new Exception(
605 pht(
606 'This diff ("%s") is associated with a repository ("%s") which '.
607 'could not be loaded.',
608 $diff_phid,
609 $repository_phid));
610 }
611
612 $staging_uri = $repository->getStagingURI();
613 if (!$staging_uri) {
614 throw new Exception(
615 pht(
616 'This diff ("%s") is associated with a repository ("%s") that '.
617 'does not have a Staging Area configured. You must configure a '.
618 'Staging Area to use CircleCI integration.',
619 $diff_phid,
620 $repository_phid));
621 }
622
623 $path = HarbormasterCircleCIBuildStepImplementation::getGitHubPath(
624 $staging_uri);
625 if (!$path) {
626 throw new Exception(
627 pht(
628 'This diff ("%s") is associated with a repository ("%s") that '.
629 'does not have a Staging Area ("%s") that is hosted on GitHub. '.
630 'CircleCI can only build from GitHub, so the Staging Area for '.
631 'the repository must be hosted there.',
632 $diff_phid,
633 $repository_phid,
634 $staging_uri));
635 }
636
637 return $staging_uri;
638 }
639
640 public function getCircleCIBuildIdentifierType() {
641 return 'tag';
642 }
643
644 public function getCircleCIBuildIdentifier() {
645 $ref = $this->getStagingRef();
646 $ref = preg_replace('(^refs/tags/)', '', $ref);
647 return $ref;
648 }
649
650
651/* -( HarbormasterBuildkiteBuildableInterface )---------------------------- */
652
653 public function getBuildkiteBranch() {
654 $ref = $this->getStagingRef();
655
656 // NOTE: Circa late January 2017, Buildkite fails with the error message
657 // "Tags have been disabled for this project" if we pass the "refs/tags/"
658 // prefix via the API and the project doesn't have GitHub tag builds
659 // enabled, even if GitHub builds are disabled. The tag builds fine
660 // without this prefix.
661 $ref = preg_replace('(^refs/tags/)', '', $ref);
662
663 return $ref;
664 }
665
666 public function getBuildkiteCommit() {
667 return 'HEAD';
668 }
669
670
671 public function getStagingRef() {
672 // TODO: We're just hoping to get lucky. Instead, `arc` should store
673 // where it sent changes and we should only provide staging details
674 // if we reasonably believe they are accurate.
675 return 'refs/tags/phabricator/diff/'.$this->getID();
676 }
677
678 public function loadTargetBranch() {
679 // TODO: This is sketchy, but just eat the query cost until this can get
680 // cleaned up.
681
682 // For now, we're only returning a target if there's exactly one and it's
683 // a branch, since we don't support landing to more esoteric targets like
684 // tags yet.
685
686 $property = id(new DifferentialDiffProperty())->loadOneWhere(
687 'diffID = %d AND name = %s',
688 $this->getID(),
689 'arc:onto');
690 if (!$property) {
691 return null;
692 }
693
694 $data = $property->getData();
695
696 if (!$data) {
697 return null;
698 }
699
700 if (!is_array($data)) {
701 return null;
702 }
703
704 if (count($data) != 1) {
705 return null;
706 }
707
708 $onto = head($data);
709 if (!is_array($onto)) {
710 return null;
711 }
712
713 $type = idx($onto, 'type');
714 if ($type != 'branch') {
715 return null;
716 }
717
718 return idx($onto, 'name');
719 }
720
721
722/* -( PhabricatorApplicationTransactionInterface )------------------------- */
723
724
725 public function getApplicationTransactionEditor() {
726 return new DifferentialDiffEditor();
727 }
728
729 public function getApplicationTransactionTemplate() {
730 return new DifferentialDiffTransaction();
731 }
732
733
734/* -( PhabricatorDestructibleInterface )----------------------------------- */
735
736
737 public function destroyObjectPermanently(
738 PhabricatorDestructionEngine $engine) {
739
740 $viewer = $engine->getViewer();
741
742 $this->openTransaction();
743 $this->delete();
744
745 foreach ($this->loadChangesets() as $changeset) {
746 $engine->destroyObject($changeset);
747 }
748
749 $properties = id(new DifferentialDiffProperty())->loadAllWhere(
750 'diffID = %d',
751 $this->getID());
752 foreach ($properties as $prop) {
753 $prop->delete();
754 }
755
756 $viewstate_query = id(new DifferentialViewStateQuery())
757 ->setViewer($viewer)
758 ->withObjectPHIDs(array($this->getPHID()));
759 $viewstates = new PhabricatorQueryIterator($viewstate_query);
760 foreach ($viewstates as $viewstate) {
761 $viewstate->delete();
762 }
763
764 $this->saveTransaction();
765 }
766
767
768/* -( PhabricatorConduitResultInterface )---------------------------------- */
769
770
771 public function getFieldSpecificationsForConduit() {
772 return array(
773 id(new PhabricatorConduitSearchFieldSpecification())
774 ->setKey('revisionPHID')
775 ->setType('phid')
776 ->setDescription(pht('Associated revision PHID.')),
777 id(new PhabricatorConduitSearchFieldSpecification())
778 ->setKey('authorPHID')
779 ->setType('phid')
780 ->setDescription(pht('Revision author PHID.')),
781 id(new PhabricatorConduitSearchFieldSpecification())
782 ->setKey('repositoryPHID')
783 ->setType('phid')
784 ->setDescription(pht('Associated repository PHID.')),
785 id(new PhabricatorConduitSearchFieldSpecification())
786 ->setKey('refs')
787 ->setType('map<string, wild>')
788 ->setDescription(pht('List of related VCS references.')),
789 );
790 }
791
792 public function getFieldValuesForConduit() {
793 $refs = array();
794
795 $branch = $this->getBranch();
796 if (phutil_nonempty_string($branch)) {
797 $refs[] = array(
798 'type' => 'branch',
799 'name' => $branch,
800 );
801 }
802
803 $onto = $this->loadTargetBranch();
804 if (phutil_nonempty_string($onto)) {
805 $refs[] = array(
806 'type' => 'onto',
807 'name' => $onto,
808 );
809 }
810
811 $base = $this->getSourceControlBaseRevision();
812 if ($base !== null && strlen($base)) {
813 $refs[] = array(
814 'type' => 'base',
815 'identifier' => $base,
816 );
817 }
818
819 $bookmark = $this->getBookmark();
820 if (phutil_nonempty_string($bookmark)) {
821 $refs[] = array(
822 'type' => 'bookmark',
823 'name' => $bookmark,
824 );
825 }
826
827 $revision_phid = null;
828 if ($this->getRevisionID()) {
829 $revision_phid = $this->getRevision()->getPHID();
830 }
831
832 return array(
833 'revisionPHID' => $revision_phid,
834 'authorPHID' => $this->getAuthorPHID(),
835 'repositoryPHID' => $this->getRepositoryPHID(),
836 'refs' => $refs,
837 );
838 }
839
840 public function getConduitSearchAttachments() {
841 return array(
842 id(new DifferentialCommitsSearchEngineAttachment())
843 ->setAttachmentKey('commits'),
844 );
845 }
846
847}