3 final class HeraldCommitAdapter
5 implements HarbormasterBuildableAdapterInterface
{
13 protected $affectedPaths;
14 protected $affectedRevision;
15 protected $affectedPackages;
16 protected $auditNeededPackages;
18 private $buildRequests = array();
20 public function getAdapterApplicationClass() {
21 return 'PhabricatorDiffusionApplication';
24 protected function newObject() {
25 return new PhabricatorRepositoryCommit();
28 public function isTestAdapterForObject($object) {
29 return ($object instanceof PhabricatorRepositoryCommit
);
32 public function getAdapterTestDescription() {
34 'Test rules which run after a commit is discovered and imported.');
37 public function newTestAdapter(PhabricatorUser
$viewer, $object) {
38 return id(clone $this)
42 protected function initializeNewAdapter() {
43 $this->commit
= $this->newObject();
46 public function setObject($object) {
47 $viewer = $this->getViewer();
48 $commit_phid = $object->getPHID();
50 $commit = id(new DiffusionCommitQuery())
52 ->withPHIDs(array($commit_phid))
53 ->needCommitData(true)
54 ->needIdentities(true)
55 ->needAuditRequests(true)
60 'Failed to reload commit ("%s") to fetch commit data.',
64 $this->commit
= $commit;
69 public function getObject() {
73 public function getAdapterContentType() {
77 public function getAdapterContentName() {
78 return pht('Commits');
81 public function getAdapterContentDescription() {
83 "React to new commits appearing in tracked repositories.\n".
84 "Commit rules can send email, flag commits, trigger audits, ".
85 "and run build plans.");
88 public function supportsRuleType($rule_type) {
90 case HeraldRuleTypeConfig
::RULE_TYPE_GLOBAL
:
91 case HeraldRuleTypeConfig
::RULE_TYPE_PERSONAL
:
92 case HeraldRuleTypeConfig
::RULE_TYPE_OBJECT
:
99 public function canTriggerOnObject($object) {
100 if ($object instanceof PhabricatorRepository
) {
103 if ($object instanceof PhabricatorProject
) {
109 public function getTriggerObjectPHIDs() {
110 $project_type = PhabricatorProjectObjectHasProjectEdgeType
::EDGECONST
;
112 $repository_phid = $this->getRepository()->getPHID();
113 $commit_phid = $this->getObject()->getPHID();
116 $phids[] = $commit_phid;
117 $phids[] = $repository_phid;
119 // NOTE: This is projects for the repository, not for the commit. When
120 // Herald evaluates, commits normally can not have any project tags yet.
121 $repository_project_phids = PhabricatorEdgeQuery
::loadDestinationPHIDs(
124 foreach ($repository_project_phids as $phid) {
128 $phids = array_unique($phids);
129 $phids = array_values($phids);
134 public function explainValidTriggerObjects() {
135 return pht('This rule can trigger for **repositories** and **projects**.');
138 public function getHeraldName() {
139 return $this->commit
->getMonogram();
142 public function loadAffectedPaths() {
143 $viewer = $this->getViewer();
145 if ($this->affectedPaths
=== null) {
146 $result = PhabricatorOwnerPathQuery
::loadAffectedPaths(
147 $this->getRepository(),
150 $this->affectedPaths
= $result;
153 return $this->affectedPaths
;
156 public function loadAffectedPackages() {
157 if ($this->affectedPackages
=== null) {
158 $packages = PhabricatorOwnersPackage
::loadAffectedPackages(
159 $this->getRepository(),
160 $this->loadAffectedPaths());
161 $this->affectedPackages
= $packages;
163 return $this->affectedPackages
;
166 public function loadAuditNeededPackages() {
167 if ($this->auditNeededPackages
=== null) {
169 PhabricatorAuditRequestStatus
::AUDIT_REQUIRED
,
170 PhabricatorAuditRequestStatus
::CONCERNED
,
172 $requests = id(new PhabricatorRepositoryAuditRequest())
174 'commitPHID = %s AND auditStatus IN (%Ls)',
175 $this->commit
->getPHID(),
177 $this->auditNeededPackages
= $requests;
179 return $this->auditNeededPackages
;
182 public function loadDifferentialRevision() {
183 if ($this->affectedRevision
=== null) {
184 $viewer = $this->getViewer();
186 // NOTE: The viewer here is omnipotent, which means that Herald discloses
187 // some information users do not normally have access to when rules load
188 // the revision related to a commit. See D20468.
190 // A user who wants to learn about "Dxyz" can write a Herald rule which
191 // uses all the "Related revision..." fields, then push a commit which
192 // contains "Differential Revision: Dxyz" in the message to make Herald
193 // evaluate the commit with "Dxyz" as the related revision.
195 // At time of writing, this commit will link to the revision and the
196 // transcript for the commit will disclose some information about the
197 // revision (like reviewers, subscribers, and build status) which the
198 // commit author could not otherwise see.
200 // For now, we just accept this. The disclosures are relatively
201 // uninteresting and you have to jump through a lot of hoops (and leave
202 // a lot of evidence) to get this information.
204 $revision = DiffusionCommitRevisionQuery
::loadRevisionForCommit(
208 $this->affectedRevision
= $revision;
210 $this->affectedRevision
= false;
214 return $this->affectedRevision
;
217 public static function getEnormousByteLimit() {
218 return 256 * 1024 * 1024; // 256MB. See T13142 and T13143.
221 public static function getEnormousTimeLimit() {
222 return 60 * 15; // 15 Minutes
225 private function loadCommitDiff() {
226 $viewer = $this->getViewer();
228 $byte_limit = self
::getEnormousByteLimit();
229 $time_limit = self
::getEnormousTimeLimit();
231 $diff_info = $this->callConduit(
232 'diffusion.rawdiffquery',
234 'commit' => $this->commit
->getCommitIdentifier(),
235 'timeout' => $time_limit,
236 'byteLimit' => $byte_limit,
237 'linesOfContext' => 0,
240 if ($diff_info['tooHuge']) {
243 'The raw text of this change is enormous (larger than %s byte(s)). '.
244 'Herald can not process it.',
245 new PhutilNumber($byte_limit)));
248 if ($diff_info['tooSlow']) {
251 'The raw text of this change took too long to process (longer '.
252 'than %s second(s)). Herald can not process it.',
253 new PhutilNumber($time_limit)));
256 $file_phid = $diff_info['filePHID'];
257 $diff_file = id(new PhabricatorFileQuery())
259 ->withPHIDs(array($file_phid))
264 'Failed to load diff ("%s") for this change.',
268 $raw = $diff_file->loadFileData();
270 $parser = new ArcanistDiffParser();
271 $changes = $parser->parseDiff($raw);
273 $diff = DifferentialDiff
::newEphemeralFromRawChanges(
278 public function isDiffEnormous() {
279 $this->loadDiffContent('*');
280 return ($this->commitDiff
instanceof Exception
);
283 public function loadDiffContent($type) {
284 if ($this->commitDiff
=== null) {
286 $this->commitDiff
= $this->loadCommitDiff();
287 } catch (Exception
$ex) {
288 $this->commitDiff
= $ex;
293 if ($this->commitDiff
instanceof Exception
) {
294 $ex = $this->commitDiff
;
295 $ex_class = get_class($ex);
296 $ex_message = pht('Failed to load changes: %s', $ex->getMessage());
299 '<'.$ex_class.'>' => $ex_message,
303 $changes = $this->commitDiff
->getChangesets();
306 foreach ($changes as $change) {
308 foreach ($change->getHunks() as $hunk) {
311 $lines[] = $hunk->makeOldFile();
314 $lines[] = $hunk->makeNewFile();
317 $lines[] = $hunk->makeChanges();
320 throw new Exception(pht("Unknown content selection '%s'!", $type));
323 $result[$change->getFilename()] = implode("\n", $lines);
329 public function loadIsMergeCommit() {
330 $parents = $this->callConduit(
331 'diffusion.commitparentsquery',
333 'commit' => $this->getObject()->getCommitIdentifier(),
336 return (count($parents) > 1);
339 private function callConduit($method, array $params) {
340 $viewer = $this->getViewer();
342 $drequest = DiffusionRequest
::newFromDictionary(
345 'repository' => $this->getRepository(),
346 'commit' => $this->commit
->getCommitIdentifier(),
349 return DiffusionQuery
::callConduitWithDiffusionRequest(
356 private function getRepository() {
357 return $this->getObject()->getRepository();
360 public function getAuthorPHID() {
361 return $this->getObject()->getEffectiveAuthorPHID();
364 public function getCommitterPHID() {
365 $commit = $this->getObject();
367 if ($commit->hasCommitterIdentity()) {
368 $identity = $commit->getCommitterIdentity();
369 return $identity->getCurrentEffectiveUserPHID();
376 /* -( HarbormasterBuildableAdapterInterface )------------------------------ */
379 public function getHarbormasterBuildablePHID() {
380 return $this->getObject()->getPHID();
383 public function getHarbormasterContainerPHID() {
384 return $this->getObject()->getRepository()->getPHID();
387 public function getQueuedHarbormasterBuildRequests() {
388 return $this->buildRequests
;
391 public function queueHarbormasterBuildRequest(
392 HarbormasterBuildRequest
$request) {
393 $this->buildRequests
[] = $request;