[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class HeraldCommitAdapter extends HeraldAdapter { 4 5 const FIELD_NEED_AUDIT_FOR_PACKAGE = 'need-audit-for-package'; 6 const FIELD_REPOSITORY_AUTOCLOSE_BRANCH = 'repository-autoclose-branch'; 7 8 protected $diff; 9 protected $revision; 10 11 protected $repository; 12 protected $commit; 13 protected $commitData; 14 private $commitDiff; 15 16 protected $emailPHIDs = array(); 17 protected $addCCPHIDs = array(); 18 protected $auditMap = array(); 19 protected $buildPlans = array(); 20 21 protected $affectedPaths; 22 protected $affectedRevision; 23 protected $affectedPackages; 24 protected $auditNeededPackages; 25 26 public function getAdapterApplicationClass() { 27 return 'PhabricatorDiffusionApplication'; 28 } 29 30 public function getObject() { 31 return $this->commit; 32 } 33 34 public function getAdapterContentType() { 35 return 'commit'; 36 } 37 38 public function getAdapterContentName() { 39 return pht('Commits'); 40 } 41 42 public function getAdapterContentDescription() { 43 return pht( 44 "React to new commits appearing in tracked repositories.\n". 45 "Commit rules can send email, flag commits, trigger audits, ". 46 "and run build plans."); 47 } 48 49 public function supportsRuleType($rule_type) { 50 switch ($rule_type) { 51 case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: 52 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: 53 case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: 54 return true; 55 default: 56 return false; 57 } 58 } 59 60 public function canTriggerOnObject($object) { 61 if ($object instanceof PhabricatorRepository) { 62 return true; 63 } 64 if ($object instanceof PhabricatorProject) { 65 return true; 66 } 67 return false; 68 } 69 70 public function getTriggerObjectPHIDs() { 71 return array_merge( 72 array( 73 $this->repository->getPHID(), 74 $this->getPHID(), 75 ), 76 $this->repository->getProjectPHIDs()); 77 } 78 79 public function explainValidTriggerObjects() { 80 return pht('This rule can trigger for **repositories** and **projects**.'); 81 } 82 83 public function getFieldNameMap() { 84 return array( 85 self::FIELD_NEED_AUDIT_FOR_PACKAGE => 86 pht('Affected packages that need audit'), 87 self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH 88 => pht('Commit is on closing branch'), 89 ) + parent::getFieldNameMap(); 90 } 91 92 public function getFields() { 93 return array_merge( 94 array( 95 self::FIELD_BODY, 96 self::FIELD_AUTHOR, 97 self::FIELD_COMMITTER, 98 self::FIELD_REVIEWER, 99 self::FIELD_REPOSITORY, 100 self::FIELD_REPOSITORY_PROJECTS, 101 self::FIELD_DIFF_FILE, 102 self::FIELD_DIFF_CONTENT, 103 self::FIELD_DIFF_ADDED_CONTENT, 104 self::FIELD_DIFF_REMOVED_CONTENT, 105 self::FIELD_DIFF_ENORMOUS, 106 self::FIELD_AFFECTED_PACKAGE, 107 self::FIELD_AFFECTED_PACKAGE_OWNER, 108 self::FIELD_NEED_AUDIT_FOR_PACKAGE, 109 self::FIELD_DIFFERENTIAL_REVISION, 110 self::FIELD_DIFFERENTIAL_ACCEPTED, 111 self::FIELD_DIFFERENTIAL_REVIEWERS, 112 self::FIELD_DIFFERENTIAL_CCS, 113 self::FIELD_BRANCHES, 114 self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH, 115 ), 116 parent::getFields()); 117 } 118 119 public function getConditionsForField($field) { 120 switch ($field) { 121 case self::FIELD_NEED_AUDIT_FOR_PACKAGE: 122 return array( 123 self::CONDITION_INCLUDE_ANY, 124 self::CONDITION_INCLUDE_NONE, 125 ); 126 case self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH: 127 return array( 128 self::CONDITION_UNCONDITIONALLY, 129 ); 130 } 131 return parent::getConditionsForField($field); 132 } 133 134 public function getActions($rule_type) { 135 switch ($rule_type) { 136 case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: 137 case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: 138 return array_merge( 139 array( 140 self::ACTION_ADD_CC, 141 self::ACTION_EMAIL, 142 self::ACTION_AUDIT, 143 self::ACTION_APPLY_BUILD_PLANS, 144 self::ACTION_NOTHING, 145 ), 146 parent::getActions($rule_type)); 147 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: 148 return array_merge( 149 array( 150 self::ACTION_ADD_CC, 151 self::ACTION_EMAIL, 152 self::ACTION_FLAG, 153 self::ACTION_AUDIT, 154 self::ACTION_NOTHING, 155 ), 156 parent::getActions($rule_type)); 157 } 158 } 159 160 public function getValueTypeForFieldAndCondition($field, $condition) { 161 switch ($field) { 162 case self::FIELD_DIFFERENTIAL_CCS: 163 return self::VALUE_EMAIL; 164 case self::FIELD_NEED_AUDIT_FOR_PACKAGE: 165 return self::VALUE_OWNERS_PACKAGE; 166 } 167 168 return parent::getValueTypeForFieldAndCondition($field, $condition); 169 } 170 171 public static function newLegacyAdapter( 172 PhabricatorRepository $repository, 173 PhabricatorRepositoryCommit $commit, 174 PhabricatorRepositoryCommitData $commit_data) { 175 176 $object = new HeraldCommitAdapter(); 177 178 $commit->attachRepository($repository); 179 180 $object->repository = $repository; 181 $object->commit = $commit; 182 $object->commitData = $commit_data; 183 184 return $object; 185 } 186 187 public function setCommit(PhabricatorRepositoryCommit $commit) { 188 $viewer = PhabricatorUser::getOmnipotentUser(); 189 190 $repository = id(new PhabricatorRepositoryQuery()) 191 ->setViewer($viewer) 192 ->withIDs(array($commit->getRepositoryID())) 193 ->needProjectPHIDs(true) 194 ->executeOne(); 195 if (!$repository) { 196 throw new Exception(pht('Unable to load repository!')); 197 } 198 199 $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere( 200 'commitID = %d', 201 $commit->getID()); 202 if (!$data) { 203 throw new Exception(pht('Unable to load commit data!')); 204 } 205 206 $this->commit = clone $commit; 207 $this->commit->attachRepository($repository); 208 $this->commit->attachCommitData($data); 209 210 $this->repository = $repository; 211 $this->commitData = $data; 212 213 return $this; 214 } 215 216 public function getPHID() { 217 return $this->commit->getPHID(); 218 } 219 220 public function getEmailPHIDs() { 221 return array_keys($this->emailPHIDs); 222 } 223 224 public function getAddCCMap() { 225 return $this->addCCPHIDs; 226 } 227 228 public function getAuditMap() { 229 return $this->auditMap; 230 } 231 232 public function getBuildPlans() { 233 return $this->buildPlans; 234 } 235 236 public function getHeraldName() { 237 return 238 'r'. 239 $this->repository->getCallsign(). 240 $this->commit->getCommitIdentifier(); 241 } 242 243 public function loadAffectedPaths() { 244 if ($this->affectedPaths === null) { 245 $result = PhabricatorOwnerPathQuery::loadAffectedPaths( 246 $this->repository, 247 $this->commit, 248 PhabricatorUser::getOmnipotentUser()); 249 $this->affectedPaths = $result; 250 } 251 return $this->affectedPaths; 252 } 253 254 public function loadAffectedPackages() { 255 if ($this->affectedPackages === null) { 256 $packages = PhabricatorOwnersPackage::loadAffectedPackages( 257 $this->repository, 258 $this->loadAffectedPaths()); 259 $this->affectedPackages = $packages; 260 } 261 return $this->affectedPackages; 262 } 263 264 public function loadAuditNeededPackage() { 265 if ($this->auditNeededPackages === null) { 266 $status_arr = array( 267 PhabricatorAuditStatusConstants::AUDIT_REQUIRED, 268 PhabricatorAuditStatusConstants::CONCERNED, 269 ); 270 $requests = id(new PhabricatorRepositoryAuditRequest()) 271 ->loadAllWhere( 272 'commitPHID = %s AND auditStatus IN (%Ls)', 273 $this->commit->getPHID(), 274 $status_arr); 275 276 $packages = mpull($requests, 'getAuditorPHID'); 277 $this->auditNeededPackages = $packages; 278 } 279 return $this->auditNeededPackages; 280 } 281 282 public function loadDifferentialRevision() { 283 if ($this->affectedRevision === null) { 284 $this->affectedRevision = false; 285 $data = $this->commitData; 286 $revision_id = $data->getCommitDetail('differential.revisionID'); 287 if ($revision_id) { 288 // NOTE: The Herald rule owner might not actually have access to 289 // the revision, and can control which revision a commit is 290 // associated with by putting text in the commit message. However, 291 // the rules they can write against revisions don't actually expose 292 // anything interesting, so it seems reasonable to load unconditionally 293 // here. 294 295 $revision = id(new DifferentialRevisionQuery()) 296 ->withIDs(array($revision_id)) 297 ->setViewer(PhabricatorUser::getOmnipotentUser()) 298 ->needRelationships(true) 299 ->needReviewerStatus(true) 300 ->executeOne(); 301 if ($revision) { 302 $this->affectedRevision = $revision; 303 } 304 } 305 } 306 return $this->affectedRevision; 307 } 308 309 public static function getEnormousByteLimit() { 310 return 1024 * 1024 * 1024; // 1GB 311 } 312 313 public static function getEnormousTimeLimit() { 314 return 60 * 15; // 15 Minutes 315 } 316 317 private function loadCommitDiff() { 318 $drequest = DiffusionRequest::newFromDictionary( 319 array( 320 'user' => PhabricatorUser::getOmnipotentUser(), 321 'repository' => $this->repository, 322 'commit' => $this->commit->getCommitIdentifier(), 323 )); 324 325 $byte_limit = self::getEnormousByteLimit(); 326 327 $raw = DiffusionQuery::callConduitWithDiffusionRequest( 328 PhabricatorUser::getOmnipotentUser(), 329 $drequest, 330 'diffusion.rawdiffquery', 331 array( 332 'commit' => $this->commit->getCommitIdentifier(), 333 'timeout' => self::getEnormousTimeLimit(), 334 'byteLimit' => $byte_limit, 335 'linesOfContext' => 0, 336 )); 337 338 if (strlen($raw) >= $byte_limit) { 339 throw new Exception( 340 pht( 341 'The raw text of this change is enormous (larger than %d bytes). '. 342 'Herald can not process it.', 343 $byte_limit)); 344 } 345 346 $parser = new ArcanistDiffParser(); 347 $changes = $parser->parseDiff($raw); 348 349 $diff = DifferentialDiff::newFromRawChanges( 350 PhabricatorUser::getOmnipotentUser(), 351 $changes); 352 return $diff; 353 } 354 355 private function getDiffContent($type) { 356 if ($this->commitDiff === null) { 357 try { 358 $this->commitDiff = $this->loadCommitDiff(); 359 } catch (Exception $ex) { 360 $this->commitDiff = $ex; 361 phlog($ex); 362 } 363 } 364 365 if ($this->commitDiff instanceof Exception) { 366 $ex = $this->commitDiff; 367 $ex_class = get_class($ex); 368 $ex_message = pht('Failed to load changes: %s', $ex->getMessage()); 369 370 return array( 371 '<'.$ex_class.'>' => $ex_message, 372 ); 373 } 374 375 $changes = $this->commitDiff->getChangesets(); 376 377 $result = array(); 378 foreach ($changes as $change) { 379 $lines = array(); 380 foreach ($change->getHunks() as $hunk) { 381 switch ($type) { 382 case '-': 383 $lines[] = $hunk->makeOldFile(); 384 break; 385 case '+': 386 $lines[] = $hunk->makeNewFile(); 387 break; 388 case '*': 389 $lines[] = $hunk->makeChanges(); 390 break; 391 default: 392 throw new Exception("Unknown content selection '{$type}'!"); 393 } 394 } 395 $result[$change->getFilename()] = implode("\n", $lines); 396 } 397 398 return $result; 399 } 400 401 public function getHeraldField($field) { 402 $data = $this->commitData; 403 switch ($field) { 404 case self::FIELD_BODY: 405 return $data->getCommitMessage(); 406 case self::FIELD_AUTHOR: 407 return $data->getCommitDetail('authorPHID'); 408 case self::FIELD_COMMITTER: 409 return $data->getCommitDetail('committerPHID'); 410 case self::FIELD_REVIEWER: 411 return $data->getCommitDetail('reviewerPHID'); 412 case self::FIELD_DIFF_FILE: 413 return $this->loadAffectedPaths(); 414 case self::FIELD_REPOSITORY: 415 return $this->repository->getPHID(); 416 case self::FIELD_REPOSITORY_PROJECTS: 417 return $this->repository->getProjectPHIDs(); 418 case self::FIELD_DIFF_CONTENT: 419 return $this->getDiffContent('*'); 420 case self::FIELD_DIFF_ADDED_CONTENT: 421 return $this->getDiffContent('+'); 422 case self::FIELD_DIFF_REMOVED_CONTENT: 423 return $this->getDiffContent('-'); 424 case self::FIELD_DIFF_ENORMOUS: 425 $this->getDiffContent('*'); 426 return ($this->commitDiff instanceof Exception); 427 case self::FIELD_AFFECTED_PACKAGE: 428 $packages = $this->loadAffectedPackages(); 429 return mpull($packages, 'getPHID'); 430 case self::FIELD_AFFECTED_PACKAGE_OWNER: 431 $packages = $this->loadAffectedPackages(); 432 $owners = PhabricatorOwnersOwner::loadAllForPackages($packages); 433 return mpull($owners, 'getUserPHID'); 434 case self::FIELD_NEED_AUDIT_FOR_PACKAGE: 435 return $this->loadAuditNeededPackage(); 436 case self::FIELD_DIFFERENTIAL_REVISION: 437 $revision = $this->loadDifferentialRevision(); 438 if (!$revision) { 439 return null; 440 } 441 return $revision->getID(); 442 case self::FIELD_DIFFERENTIAL_ACCEPTED: 443 $revision = $this->loadDifferentialRevision(); 444 if (!$revision) { 445 return null; 446 } 447 448 $status = $data->getCommitDetail( 449 'precommitRevisionStatus', 450 $revision->getStatus()); 451 switch ($status) { 452 case ArcanistDifferentialRevisionStatus::ACCEPTED: 453 case ArcanistDifferentialRevisionStatus::CLOSED: 454 return $revision->getPHID(); 455 } 456 457 return null; 458 case self::FIELD_DIFFERENTIAL_REVIEWERS: 459 $revision = $this->loadDifferentialRevision(); 460 if (!$revision) { 461 return array(); 462 } 463 return $revision->getReviewers(); 464 case self::FIELD_DIFFERENTIAL_CCS: 465 $revision = $this->loadDifferentialRevision(); 466 if (!$revision) { 467 return array(); 468 } 469 return $revision->getCCPHIDs(); 470 case self::FIELD_BRANCHES: 471 $params = array( 472 'callsign' => $this->repository->getCallsign(), 473 'contains' => $this->commit->getCommitIdentifier(), 474 ); 475 476 $result = id(new ConduitCall('diffusion.branchquery', $params)) 477 ->setUser(PhabricatorUser::getOmnipotentUser()) 478 ->execute(); 479 480 $refs = DiffusionRepositoryRef::loadAllFromDictionaries($result); 481 return mpull($refs, 'getShortName'); 482 case self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH: 483 return $this->repository->shouldAutocloseCommit($this->commit); 484 } 485 486 return parent::getHeraldField($field); 487 } 488 489 public function applyHeraldEffects(array $effects) { 490 assert_instances_of($effects, 'HeraldEffect'); 491 492 $result = array(); 493 foreach ($effects as $effect) { 494 $action = $effect->getAction(); 495 switch ($action) { 496 case self::ACTION_NOTHING: 497 $result[] = new HeraldApplyTranscript( 498 $effect, 499 true, 500 pht('Great success at doing nothing.')); 501 break; 502 case self::ACTION_EMAIL: 503 foreach ($effect->getTarget() as $phid) { 504 $this->emailPHIDs[$phid] = true; 505 } 506 $result[] = new HeraldApplyTranscript( 507 $effect, 508 true, 509 pht('Added address to email targets.')); 510 break; 511 case self::ACTION_ADD_CC: 512 foreach ($effect->getTarget() as $phid) { 513 if (empty($this->addCCPHIDs[$phid])) { 514 $this->addCCPHIDs[$phid] = array(); 515 } 516 $this->addCCPHIDs[$phid][] = $effect->getRuleID(); 517 } 518 $result[] = new HeraldApplyTranscript( 519 $effect, 520 true, 521 pht('Added address to CC.')); 522 break; 523 case self::ACTION_AUDIT: 524 foreach ($effect->getTarget() as $phid) { 525 if (empty($this->auditMap[$phid])) { 526 $this->auditMap[$phid] = array(); 527 } 528 $this->auditMap[$phid][] = $effect->getRuleID(); 529 } 530 $result[] = new HeraldApplyTranscript( 531 $effect, 532 true, 533 pht('Triggered an audit.')); 534 break; 535 case self::ACTION_APPLY_BUILD_PLANS: 536 foreach ($effect->getTarget() as $phid) { 537 $this->buildPlans[] = $phid; 538 } 539 $result[] = new HeraldApplyTranscript( 540 $effect, 541 true, 542 pht('Applied build plans.')); 543 break; 544 case self::ACTION_FLAG: 545 $result[] = parent::applyFlagEffect( 546 $effect, 547 $this->commit->getPHID()); 548 break; 549 default: 550 $custom_result = parent::handleCustomHeraldEffect($effect); 551 if ($custom_result === null) { 552 throw new Exception(pht( 553 "No rules to handle action '%s'.", 554 $action)); 555 } 556 557 $result[] = $custom_result; 558 break; 559 } 560 } 561 return $result; 562 } 563 564 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |