Correct Aphlict websocket URI construction after PHP8 compatibility changes
[phabricator.git] / src / applications / diffusion / herald / HeraldCommitAdapter.php
blobaf7c757cd9bb05f4beb694124d9571afaec43ef8
1 <?php
3 final class HeraldCommitAdapter
4 extends HeraldAdapter
5 implements HarbormasterBuildableAdapterInterface {
7 protected $diff;
8 protected $revision;
10 protected $commit;
11 private $commitDiff;
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() {
33 return pht(
34 'Test rules which run after a commit is discovered and imported.');
37 public function newTestAdapter(PhabricatorUser $viewer, $object) {
38 return id(clone $this)
39 ->setObject($object);
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())
51 ->setViewer($viewer)
52 ->withPHIDs(array($commit_phid))
53 ->needCommitData(true)
54 ->needIdentities(true)
55 ->needAuditRequests(true)
56 ->executeOne();
57 if (!$commit) {
58 throw new Exception(
59 pht(
60 'Failed to reload commit ("%s") to fetch commit data.',
61 $commit_phid));
64 $this->commit = $commit;
66 return $this;
69 public function getObject() {
70 return $this->commit;
73 public function getAdapterContentType() {
74 return 'commit';
77 public function getAdapterContentName() {
78 return pht('Commits');
81 public function getAdapterContentDescription() {
82 return pht(
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) {
89 switch ($rule_type) {
90 case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
91 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
92 case HeraldRuleTypeConfig::RULE_TYPE_OBJECT:
93 return true;
94 default:
95 return false;
99 public function canTriggerOnObject($object) {
100 if ($object instanceof PhabricatorRepository) {
101 return true;
103 if ($object instanceof PhabricatorProject) {
104 return true;
106 return false;
109 public function getTriggerObjectPHIDs() {
110 $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
112 $repository_phid = $this->getRepository()->getPHID();
113 $commit_phid = $this->getObject()->getPHID();
115 $phids = array();
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(
122 $repository_phid,
123 $project_type);
124 foreach ($repository_project_phids as $phid) {
125 $phids[] = $phid;
128 $phids = array_unique($phids);
129 $phids = array_values($phids);
131 return $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(),
148 $this->commit,
149 $viewer);
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) {
168 $status_arr = array(
169 PhabricatorAuditRequestStatus::AUDIT_REQUIRED,
170 PhabricatorAuditRequestStatus::CONCERNED,
172 $requests = id(new PhabricatorRepositoryAuditRequest())
173 ->loadAllWhere(
174 'commitPHID = %s AND auditStatus IN (%Ls)',
175 $this->commit->getPHID(),
176 $status_arr);
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(
205 $viewer,
206 $this->getObject());
207 if ($revision) {
208 $this->affectedRevision = $revision;
209 } else {
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',
233 array(
234 'commit' => $this->commit->getCommitIdentifier(),
235 'timeout' => $time_limit,
236 'byteLimit' => $byte_limit,
237 'linesOfContext' => 0,
240 if ($diff_info['tooHuge']) {
241 throw new Exception(
242 pht(
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']) {
249 throw new Exception(
250 pht(
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())
258 ->setViewer($viewer)
259 ->withPHIDs(array($file_phid))
260 ->executeOne();
261 if (!$diff_file) {
262 throw new Exception(
263 pht(
264 'Failed to load diff ("%s") for this change.',
265 $file_phid));
268 $raw = $diff_file->loadFileData();
270 // See T13667. This happens when a commit is empty and affects no files.
271 if (!strlen($raw)) {
272 return false;
275 $parser = new ArcanistDiffParser();
276 $changes = $parser->parseDiff($raw);
278 $diff = DifferentialDiff::newEphemeralFromRawChanges(
279 $changes);
280 return $diff;
283 public function isDiffEnormous() {
284 $this->loadDiffContent('*');
285 return ($this->commitDiff instanceof Exception);
288 public function loadDiffContent($type) {
289 if ($this->commitDiff === null) {
290 try {
291 $this->commitDiff = $this->loadCommitDiff();
292 } catch (Exception $ex) {
293 $this->commitDiff = $ex;
294 phlog($ex);
298 if ($this->commitDiff === false) {
299 return array();
302 if ($this->commitDiff instanceof Exception) {
303 $ex = $this->commitDiff;
304 $ex_class = get_class($ex);
305 $ex_message = pht('Failed to load changes: %s', $ex->getMessage());
307 return array(
308 '<'.$ex_class.'>' => $ex_message,
312 $changes = $this->commitDiff->getChangesets();
314 $result = array();
315 foreach ($changes as $change) {
316 $lines = array();
317 foreach ($change->getHunks() as $hunk) {
318 switch ($type) {
319 case '-':
320 $lines[] = $hunk->makeOldFile();
321 break;
322 case '+':
323 $lines[] = $hunk->makeNewFile();
324 break;
325 case '*':
326 $lines[] = $hunk->makeChanges();
327 break;
328 default:
329 throw new Exception(pht("Unknown content selection '%s'!", $type));
332 $result[$change->getFilename()] = implode("\n", $lines);
335 return $result;
338 public function loadIsMergeCommit() {
339 $parents = $this->callConduit(
340 'diffusion.commitparentsquery',
341 array(
342 'commit' => $this->getObject()->getCommitIdentifier(),
345 return (count($parents) > 1);
348 private function callConduit($method, array $params) {
349 $viewer = $this->getViewer();
351 $drequest = DiffusionRequest::newFromDictionary(
352 array(
353 'user' => $viewer,
354 'repository' => $this->getRepository(),
355 'commit' => $this->commit->getCommitIdentifier(),
358 return DiffusionQuery::callConduitWithDiffusionRequest(
359 $viewer,
360 $drequest,
361 $method,
362 $params);
365 private function getRepository() {
366 return $this->getObject()->getRepository();
369 public function getAuthorPHID() {
370 return $this->getObject()->getEffectiveAuthorPHID();
373 public function getCommitterPHID() {
374 $commit = $this->getObject();
376 if ($commit->hasCommitterIdentity()) {
377 $identity = $commit->getCommitterIdentity();
378 return $identity->getCurrentEffectiveUserPHID();
381 return null;
385 /* -( HarbormasterBuildableAdapterInterface )------------------------------ */
388 public function getHarbormasterBuildablePHID() {
389 return $this->getObject()->getPHID();
392 public function getHarbormasterContainerPHID() {
393 return $this->getObject()->getRepository()->getPHID();
396 public function getQueuedHarbormasterBuildRequests() {
397 return $this->buildRequests;
400 public function queueHarbormasterBuildRequest(
401 HarbormasterBuildRequest $request) {
402 $this->buildRequests[] = $request;