@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
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move audit to application transactions

Summary:
Fixes T4896, T6293.

Do most of the work in the editor, but pull the raw patch in the daemon and set that on the editor. This is somewhat of a pre-optimization but it was easy enough to do and makes sense to me.

Test Plan:
made a commit and saw it get parsed.
made a commit with "Auditors: foo" field and saw audit made for foo
turned on inline patch and attach patch and saw the patches

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin, epriestley

Maniphest Tasks: T6293, T4896

Differential Revision: https://secure.phabricator.com/D10705

+421 -418
+278 -6
src/applications/audit/editor/PhabricatorAuditEditor.php
··· 3 3 final class PhabricatorAuditEditor 4 4 extends PhabricatorApplicationTransactionEditor { 5 5 6 + const MAX_FILES_SHOWN_IN_EMAIL = 1000; 7 + 8 + private $auditReasonMap = array(); 9 + private $heraldEmailPHIDs = array(); 10 + private $affectedFiles; 11 + private $rawPatch; 12 + 13 + public function addAuditReason($phid, $reason) { 14 + if (!isset($this->auditReasonMap[$phid])) { 15 + $this->auditReasonMap[$phid] = array(); 16 + } 17 + $this->auditReasonMap[$phid][] = $reason; 18 + return $this; 19 + } 20 + 21 + private function getAuditReasons($phid) { 22 + if (isset($this->auditReasonMap[$phid])) { 23 + return $this->auditReasonMap[$phid]; 24 + } 25 + return array('Added by '.$this->getActor()->getUsername().'.'); 26 + } 27 + 28 + public function setRawPatch($patch) { 29 + $this->rawPatch = $patch; 30 + return $this; 31 + } 32 + 33 + public function getRawPatch() { 34 + return $this->rawPatch; 35 + } 36 + 6 37 public function getEditorApplicationClass() { 7 38 return 'PhabricatorAuditApplication'; 8 39 } ··· 17 48 $types[] = PhabricatorTransactions::TYPE_COMMENT; 18 49 $types[] = PhabricatorTransactions::TYPE_EDGE; 19 50 51 + $types[] = PhabricatorAuditTransaction::TYPE_COMMIT; 52 + 20 53 // TODO: These will get modernized eventually, but that can happen one 21 54 // at a time later on. 22 55 $types[] = PhabricatorAuditActionConstants::ACTION; ··· 44 77 switch ($xaction->getTransactionType()) { 45 78 case PhabricatorAuditActionConstants::ACTION: 46 79 case PhabricatorAuditActionConstants::INLINE: 80 + case PhabricatorAuditTransaction::TYPE_COMMIT: 47 81 return null; 48 82 case PhabricatorAuditActionConstants::ADD_AUDITORS: 49 83 // TODO: For now, just record the added PHIDs. Eventually, turn these ··· 62 96 case PhabricatorAuditActionConstants::ACTION: 63 97 case PhabricatorAuditActionConstants::INLINE: 64 98 case PhabricatorAuditActionConstants::ADD_AUDITORS: 99 + case PhabricatorAuditTransaction::TYPE_COMMIT: 65 100 return $xaction->getNewValue(); 66 101 } 67 102 ··· 79 114 case PhabricatorAuditActionConstants::ACTION: 80 115 case PhabricatorAuditActionConstants::INLINE: 81 116 case PhabricatorAuditActionConstants::ADD_AUDITORS: 117 + case PhabricatorAuditTransaction::TYPE_COMMIT: 82 118 return; 83 119 } 84 120 ··· 95 131 case PhabricatorTransactions::TYPE_EDGE: 96 132 case PhabricatorAuditActionConstants::ACTION: 97 133 case PhabricatorAuditActionConstants::INLINE: 134 + case PhabricatorAuditTransaction::TYPE_COMMIT: 98 135 return; 99 136 case PhabricatorAuditActionConstants::ADD_AUDITORS: 100 137 $new = $xaction->getNewValue(); ··· 123 160 ->setCommitPHID($object->getPHID()) 124 161 ->setAuditorPHID($phid) 125 162 ->setAuditStatus($audit_requested) 126 - ->setAuditReasons( 127 - array( 128 - 'Added by '.$actor->getUsername(), 129 - )) 163 + ->setAuditReasons($this->getAuditReasons($phid)) 130 164 ->save(); 131 165 } 132 166 ··· 165 199 $actor_is_author = ($object->getAuthorPHID()) && 166 200 ($actor_phid == $object->getAuthorPHID()); 167 201 202 + $import_status_flag = null; 168 203 foreach ($xactions as $xaction) { 169 204 switch ($xaction->getTransactionType()) { 205 + case PhabricatorAuditTransaction::TYPE_COMMIT: 206 + $import_status_flag = PhabricatorRepositoryCommit::IMPORTED_HERALD; 207 + break; 170 208 case PhabricatorAuditActionConstants::ACTION: 171 209 $new = $xaction->getNewValue(); 172 210 switch ($new) { ··· 265 303 $object->updateAuditStatus($requests); 266 304 $object->save(); 267 305 306 + if ($import_status_flag) { 307 + $object->writeImportStatusFlag($import_status_flag); 308 + } 309 + 268 310 return $xactions; 269 311 } 270 312 313 + protected function expandTransaction( 314 + PhabricatorLiskDAO $object, 315 + PhabricatorApplicationTransaction $xaction) { 316 + 317 + $xactions = parent::expandTransaction($object, $xaction); 318 + switch ($xaction->getTransactionType()) { 319 + case PhabricatorAuditTransaction::TYPE_COMMIT: 320 + $request = $this->createAuditRequestTransactionFromCommitMessage( 321 + $object); 322 + if ($request) { 323 + $xactions[] = $request; 324 + } 325 + break; 326 + default: 327 + break; 328 + } 329 + return $xactions; 330 + } 331 + 332 + private function createAuditRequestTransactionFromCommitMessage( 333 + PhabricatorRepositoryCommit $commit) { 334 + 335 + $data = $commit->getCommitData(); 336 + $message = $data->getCommitMessage(); 337 + 338 + $matches = null; 339 + if (!preg_match('/^Auditors:\s*(.*)$/im', $message, $matches)) { 340 + return array(); 341 + } 342 + 343 + $phids = id(new PhabricatorObjectListQuery()) 344 + ->setViewer($this->getActor()) 345 + ->setAllowPartialResults(true) 346 + ->setAllowedTypes( 347 + array( 348 + PhabricatorPeopleUserPHIDType::TYPECONST, 349 + PhabricatorProjectProjectPHIDType::TYPECONST, 350 + )) 351 + ->setObjectList($matches[1]) 352 + ->execute(); 353 + 354 + if (!$phids) { 355 + return array(); 356 + } 357 + 358 + foreach ($phids as $phid) { 359 + $this->addAuditReason($phid, 'Requested by Author'); 360 + } 361 + return id(new PhabricatorAuditTransaction()) 362 + ->setTransactionType(PhabricatorAuditActionConstants::ADD_AUDITORS) 363 + ->setNewValue(array_fuse($phids)); 364 + } 365 + 271 366 protected function sortTransactions(array $xactions) { 272 367 $xactions = parent::sortTransactions($xactions); 273 368 ··· 386 481 $subject = "{$name}: {$summary}"; 387 482 $thread_topic = "Commit {$monogram}{$identifier}"; 388 483 389 - return id(new PhabricatorMetaMTAMail()) 484 + $template = id(new PhabricatorMetaMTAMail()) 390 485 ->setSubject($subject) 391 486 ->addHeader('Thread-Topic', $thread_topic); 487 + 488 + $this->attachPatch( 489 + $template, 490 + $object); 491 + 492 + return $template; 392 493 } 393 494 394 495 protected function getMailTo(PhabricatorLiskDAO $object) { 395 496 $phids = array(); 497 + if ($this->heraldEmailPHIDs) { 498 + $phids = $this->heraldEmailPHIDs; 499 + } 500 + 396 501 if ($object->getAuthorPHID()) { 397 502 $phids[] = $object->getAuthorPHID(); 398 503 } ··· 414 519 $body = parent::buildMailBody($object, $xactions); 415 520 416 521 $type_inline = PhabricatorAuditActionConstants::INLINE; 522 + $type_push = PhabricatorAuditTransaction::TYPE_COMMIT; 417 523 524 + $is_commit = false; 418 525 $inlines = array(); 419 526 foreach ($xactions as $xaction) { 420 527 if ($xaction->getTransactionType() == $type_inline) { 421 528 $inlines[] = $xaction; 529 + } 530 + if ($xaction->getTransactionType() == $type_push) { 531 + $is_commit = true; 422 532 } 423 533 } 424 534 ··· 428 538 $this->renderInlineCommentsForMail($object, $inlines)); 429 539 } 430 540 541 + if ($is_commit) { 542 + $data = $object->getCommitData(); 543 + $body->addTextSection(pht('AFFECTED FILES'), $this->affectedFiles); 544 + $this->inlinePatch( 545 + $body, 546 + $object); 547 + } 548 + 431 549 // Reload the commit to pull commit data. 432 550 $commit = id(new DiffusionCommitQuery()) 433 551 ->setViewer($this->requireActor()) ··· 440 558 441 559 $author_phid = $commit->getAuthorPHID(); 442 560 if ($author_phid) { 443 - $user_phids[$commit->getAuthorPHID()][] = pht('Author'); 561 + $user_phids[$author_phid][] = pht('Author'); 444 562 } 445 563 446 564 $committer_phid = $data->getCommitDetail('committerPHID'); 447 565 if ($committer_phid && ($committer_phid != $author_phid)) { 448 566 $user_phids[$committer_phid][] = pht('Committer'); 567 + } 568 + 569 + // we loaded this in applyFinalEffects 570 + $audit_requests = $object->getAudits(); 571 + $auditor_phids = mpull($audit_requests, 'getAuditorPHID'); 572 + foreach ($auditor_phids as $auditor_phid) { 573 + $user_phids[$auditor_phid][] = pht('Auditor'); 449 574 } 450 575 451 576 // TODO: It would be nice to show pusher here too, but that information ··· 481 606 return $body; 482 607 } 483 608 609 + private function attachPatch( 610 + PhabricatorMetaMTAMail $template, 611 + PhabricatorRepositoryCommit $commit) { 612 + 613 + if (!$this->getRawPatch()) { 614 + return; 615 + } 616 + 617 + $attach_key = 'metamta.diffusion.attach-patches'; 618 + $attach_patches = PhabricatorEnv::getEnvConfig($attach_key); 619 + if (!$attach_patches) { 620 + return; 621 + } 622 + 623 + $repository = $commit->getRepository(); 624 + $encoding = $repository->getDetail('encoding', 'UTF-8'); 625 + 626 + $raw_patch = $this->getRawPatch(); 627 + $commit_name = $repository->formatCommitName( 628 + $commit->getCommitIdentifier()); 629 + 630 + $template->addAttachment( 631 + new PhabricatorMetaMTAAttachment( 632 + $raw_patch, 633 + $commit_name.'.patch', 634 + 'text/x-patch; charset='.$encoding)); 635 + } 636 + 637 + private function inlinePatch( 638 + PhabricatorMetaMTAMailBody $body, 639 + PhabricatorRepositoryCommit $commit) { 640 + 641 + if (!$this->getRawPatch()) { 642 + return; 643 + } 644 + 645 + $inline_key = 'metamta.diffusion.inline-patches'; 646 + $inline_patches = PhabricatorEnv::getEnvConfig($inline_key); 647 + if (!$inline_patches) { 648 + return; 649 + } 650 + 651 + $repository = $commit->getRepository(); 652 + $raw_patch = $this->getRawPatch(); 653 + $result = null; 654 + $len = substr_count($raw_patch, "\n"); 655 + if ($len <= $inline_patches) { 656 + // We send email as utf8, so we need to convert the text to utf8 if 657 + // we can. 658 + $encoding = $repository->getDetail('encoding', 'UTF-8'); 659 + if ($encoding) { 660 + $raw_patch = phutil_utf8_convert($raw_patch, 'UTF-8', $encoding); 661 + } 662 + $result = phutil_utf8ize($raw_patch); 663 + } 664 + 665 + if ($result) { 666 + $result = "PATCH\n\n{$result}\n"; 667 + } 668 + $body->addRawSection($result); 669 + } 670 + 484 671 private function renderInlineCommentsForMail( 485 672 PhabricatorLiskDAO $object, 486 673 array $inline_xactions) { ··· 519 706 PhabricatorLiskDAO $object, 520 707 array $xactions) { 521 708 return $this->isCommitMostlyImported($object); 709 + } 710 + 711 + protected function shouldApplyHeraldRules( 712 + PhabricatorLiskDAO $object, 713 + array $xactions) { 714 + 715 + foreach ($xactions as $xaction) { 716 + switch ($xaction->getTransactionType()) { 717 + case PhabricatorAuditTransaction::TYPE_COMMIT: 718 + $repository = $object->getRepository(); 719 + if ($repository->isImporting()) { 720 + return false; 721 + } 722 + if ($repository->getDetail('herald-disabled')) { 723 + return false; 724 + } 725 + return true; 726 + default: 727 + break; 728 + } 729 + } 730 + return parent::shouldApplyHeraldRules($object, $xactions); 731 + } 732 + 733 + protected function buildHeraldAdapter( 734 + PhabricatorLiskDAO $object, 735 + array $xactions) { 736 + 737 + return id(new HeraldCommitAdapter()) 738 + ->setCommit($object); 739 + } 740 + 741 + protected function didApplyHeraldRules( 742 + PhabricatorLiskDAO $object, 743 + HeraldAdapter $adapter, 744 + HeraldTranscript $transcript) { 745 + 746 + $xactions = array(); 747 + 748 + $audit_phids = $adapter->getAuditMap(); 749 + foreach ($audit_phids as $phid => $rule_ids) { 750 + foreach ($rule_ids as $rule_id) { 751 + $this->addAuditReason( 752 + $phid, 753 + pht( 754 + '%s Triggered Audit', 755 + "H{$rule_id}")); 756 + } 757 + } 758 + if ($audit_phids) { 759 + $xactions[] = id(new PhabricatorAuditTransaction()) 760 + ->setTransactionType(PhabricatorAuditActionConstants::ADD_AUDITORS) 761 + ->setNewValue(array_fuse(array_keys($audit_phids))); 762 + } 763 + 764 + $cc_phids = $adapter->getAddCCMap(); 765 + $add_ccs = array('+' => array()); 766 + foreach ($cc_phids as $phid => $rule_ids) { 767 + $add_ccs['+'][$phid] = $phid; 768 + } 769 + $xactions[] = id(new PhabricatorAuditTransaction()) 770 + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) 771 + ->setNewValue($add_ccs); 772 + 773 + $this->heraldEmailPHIDs = $adapter->getEmailPHIDs(); 774 + 775 + HarbormasterBuildable::applyBuildPlans( 776 + $object->getPHID(), 777 + $object->getRepository()->getPHID(), 778 + $adapter->getBuildPlans()); 779 + 780 + $limit = self::MAX_FILES_SHOWN_IN_EMAIL; 781 + $files = $adapter->loadAffectedPaths(); 782 + sort($files); 783 + if (count($files) > $limit) { 784 + array_splice($files, $limit); 785 + $files[] = pht( 786 + '(This commit affected more than %d files. Only %d are shown here '. 787 + 'and additional ones are truncated.)', 788 + $limit, 789 + $limit); 790 + } 791 + $this->affectedFiles = implode("\n", $files); 792 + 793 + return $xactions; 522 794 } 523 795 524 796 private function isCommitMostlyImported(PhabricatorLiskDAO $object) {
+110
src/applications/audit/storage/PhabricatorAuditTransaction.php
··· 3 3 final class PhabricatorAuditTransaction 4 4 extends PhabricatorApplicationTransaction { 5 5 6 + const TYPE_COMMIT = 'audit:commit'; 7 + 6 8 public function getApplicationName() { 7 9 return 'audit'; 8 10 } ··· 15 17 return new PhabricatorAuditTransactionComment(); 16 18 } 17 19 20 + public function getRemarkupBlocks() { 21 + $blocks = parent::getRemarkupBlocks(); 22 + 23 + switch ($this->getTransactionType()) { 24 + case self::TYPE_COMMIT: 25 + $data = $this->getNewValue(); 26 + $blocks[] = $data['description']; 27 + break; 28 + } 29 + 30 + return $blocks; 31 + } 32 + 18 33 public function getRequiredHandlePHIDs() { 19 34 $phids = parent::getRequiredHandlePHIDs(); 20 35 21 36 $type = $this->getTransactionType(); 22 37 23 38 switch ($type) { 39 + case self::TYPE_COMMIT: 40 + $phids[] = $this->getObjectPHID(); 41 + $data = $this->getNewValue(); 42 + if ($data['authorPHID']) { 43 + $phids[] = $data['authorPHID']; 44 + } 45 + if ($data['committerPHID']) { 46 + $phids[] = $data['committerPHID']; 47 + } 48 + break; 24 49 case PhabricatorAuditActionConstants::ADD_CCS: 25 50 case PhabricatorAuditActionConstants::ADD_AUDITORS: 26 51 $old = $this->getOldValue(); ··· 59 84 break; 60 85 case PhabricatorAuditActionConstants::ADD_AUDITORS: 61 86 return pht('Added Auditors'); 87 + case self::TYPE_COMMIT: 88 + return pht('Committed'); 62 89 } 63 90 64 91 return parent::getActionName(); ··· 104 131 } 105 132 106 133 switch ($type) { 134 + case self::TYPE_COMMIT: 135 + $author = null; 136 + if ($new['authorPHID']) { 137 + $author = $this->renderHandleLink($new['authorPHID']); 138 + } else { 139 + $author = $new['authorName']; 140 + } 141 + 142 + $committer = null; 143 + if ($new['committerPHID']) { 144 + $committer = $this->renderHandleLink($new['committerPHID']); 145 + } else if ($new['committerName']) { 146 + $committer = $new['committerName']; 147 + } 148 + 149 + $commit = $this->renderHandleLink($this->getObjectPHID()); 150 + 151 + if (!$committer) { 152 + $committer = $author; 153 + $author = null; 154 + } 155 + 156 + if ($author) { 157 + $title = pht( 158 + '%s committed %s (authored by %s).', 159 + $committer, 160 + $commit, 161 + $author); 162 + } else { 163 + $title = pht( 164 + '%s committed %s.', 165 + $committer, 166 + $commit); 167 + } 168 + return $title; 169 + 107 170 case PhabricatorAuditActionConstants::INLINE: 108 171 return pht( 109 172 '%s added inline comments.', 110 173 $author_handle); 174 + 111 175 case PhabricatorAuditActionConstants::ADD_CCS: 112 176 if ($add && $rem) { 113 177 return pht( ··· 203 267 } 204 268 205 269 switch ($type) { 270 + case self::TYPE_COMMIT: 271 + $author = null; 272 + if ($new['authorPHID']) { 273 + $author = $this->renderHandleLink($new['authorPHID']); 274 + } else { 275 + $author = $new['authorName']; 276 + } 277 + 278 + $committer = null; 279 + if ($new['committerPHID']) { 280 + $committer = $this->renderHandleLink($new['committerPHID']); 281 + } else if ($new['committerName']) { 282 + $committer = $new['committerName']; 283 + } 284 + 285 + if (!$committer) { 286 + $committer = $author; 287 + $author = null; 288 + } 289 + 290 + if ($author) { 291 + $title = pht( 292 + '%s committed %s (authored by %s).', 293 + $committer, 294 + $object_handle, 295 + $author); 296 + } else { 297 + $title = pht( 298 + '%s committed %s.', 299 + $committer, 300 + $object_handle); 301 + } 302 + return $title; 303 + 206 304 case PhabricatorAuditActionConstants::INLINE: 207 305 return pht( 208 306 '%s added inline comments to %s.', ··· 265 363 return parent::getTitleForFeed($story); 266 364 } 267 365 366 + public function getBodyForFeed(PhabricatorFeedStory $story) { 367 + switch ($this->getTransactionType()) { 368 + case self::TYPE_COMMIT: 369 + $data = $this->getNewValue(); 370 + return $story->renderSummary($data['summary']); 371 + } 372 + return parent::getBodyForFeed($story); 373 + } 374 + 268 375 269 376 // TODO: These two mail methods can likely be abstracted by introducing a 270 377 // formal concept of "inline comment" transactions. ··· 288 395 switch ($this->getTransactionType()) { 289 396 case PhabricatorAuditActionConstants::INLINE: 290 397 return null; 398 + case self::TYPE_COMMIT: 399 + $data = $this->getNewValue(); 400 + return $data['description']; 291 401 } 292 402 293 403 return parent::getBodyForMail();
+1 -1
src/applications/feed/story/PhabricatorFeedStory.php
··· 387 387 } 388 388 } 389 389 390 - final protected function renderSummary($text, $len = 128) { 390 + final public function renderSummary($text, $len = 128) { 391 391 if ($len) { 392 392 $text = id(new PhutilUTF8StringTruncator()) 393 393 ->setMaximumGlyphs($len)
+32 -411
src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php
··· 3 3 final class PhabricatorRepositoryCommitHeraldWorker 4 4 extends PhabricatorRepositoryCommitParserWorker { 5 5 6 - const MAX_FILES_SHOWN_IN_EMAIL = 1000; 7 6 8 7 public function getRequiredLeaseTime() { 9 8 // Herald rules may take a long time to process. ··· 14 13 PhabricatorRepository $repository, 15 14 PhabricatorRepositoryCommit $commit) { 16 15 17 - $result = $this->applyHeraldRules($repository, $commit); 18 - 19 - $commit->writeImportStatusFlag( 20 - PhabricatorRepositoryCommit::IMPORTED_HERALD); 21 - 22 - return $result; 23 - } 24 - 25 - private function applyHeraldRules( 26 - PhabricatorRepository $repository, 27 - PhabricatorRepositoryCommit $commit) { 28 - 29 - $commit->attachRepository($repository); 30 - 31 - // Don't take any actions on an importing repository. Principally, this 32 - // avoids generating thousands of audits or emails when you import an 33 - // established repository on an existing install. 34 - if ($repository->isImporting()) { 35 - return; 36 - } 37 - 38 - if ($repository->getDetail('herald-disabled')) { 39 - return; 40 - } 41 - 42 - $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere( 43 - 'commitID = %d', 44 - $commit->getID()); 16 + // Reload the commit to pull commit data and audit requests. 17 + $commit = id(new DiffusionCommitQuery()) 18 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 19 + ->withIDs(array($commit->getID())) 20 + ->needCommitData(true) 21 + ->needAuditRequests(true) 22 + ->executeOne(); 23 + $data = $commit->getCommitData(); 45 24 46 25 if (!$data) { 47 26 throw new PhabricatorWorkerPermanentFailureException( ··· 50 29 'or no longer exists.')); 51 30 } 52 31 53 - $adapter = id(new HeraldCommitAdapter()) 54 - ->setCommit($commit); 55 - 56 - $rules = id(new HeraldRuleQuery()) 57 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 58 - ->withContentTypes(array($adapter->getAdapterContentType())) 59 - ->withDisabled(false) 60 - ->needConditionsAndActions(true) 61 - ->needAppliedToPHIDs(array($adapter->getPHID())) 62 - ->needValidateAuthors(true) 63 - ->execute(); 32 + $commit->attachRepository($repository); 64 33 65 - $engine = new HeraldEngine(); 34 + $content_source = PhabricatorContentSource::newForSource( 35 + PhabricatorContentSource::SOURCE_DAEMON, 36 + array()); 66 37 67 - $effects = $engine->applyRules($rules, $adapter); 68 - $engine->applyEffects($effects, $adapter, $rules); 69 - $xscript = $engine->getTranscript(); 70 - 71 - $audit_phids = $adapter->getAuditMap(); 72 - $cc_phids = $adapter->getAddCCMap(); 73 - if ($audit_phids || $cc_phids) { 74 - $this->createAudits($commit, $audit_phids, $cc_phids, $rules); 75 - } 76 - 77 - HarbormasterBuildable::applyBuildPlans( 78 - $commit->getPHID(), 79 - $repository->getPHID(), 80 - $adapter->getBuildPlans()); 81 - 82 - $explicit_auditors = $this->createAuditsFromCommitMessage($commit, $data); 83 - 84 - $this->publishFeedStory($repository, $commit, $data); 85 - 86 - $herald_targets = $adapter->getEmailPHIDs(); 87 - 88 - $email_phids = array_unique( 89 - array_merge( 90 - $explicit_auditors, 91 - array_keys($cc_phids), 92 - $herald_targets)); 93 - if (!$email_phids) { 94 - return; 95 - } 96 - 97 - $revision = $adapter->loadDifferentialRevision(); 98 - if ($revision) { 99 - $name = $revision->getTitle(); 100 - } else { 101 - $name = $data->getSummary(); 102 - } 103 - 38 + $committer_phid = $data->getCommitDetail('committerPHID'); 104 39 $author_phid = $data->getCommitDetail('authorPHID'); 105 - $reviewer_phid = $data->getCommitDetail('reviewerPHID'); 106 - 107 - $phids = array_filter( 108 - array( 109 - $author_phid, 110 - $reviewer_phid, 111 - $commit->getPHID(), 112 - )); 113 - 114 - $handles = id(new PhabricatorHandleQuery()) 115 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 116 - ->withPHIDs($phids) 117 - ->execute(); 118 - 119 - $commit_handle = $handles[$commit->getPHID()]; 120 - $commit_name = $commit_handle->getName(); 121 - 122 - if ($author_phid) { 123 - $author_name = $handles[$author_phid]->getName(); 124 - } else { 125 - $author_name = $data->getAuthorName(); 126 - } 127 - 128 - if ($reviewer_phid) { 129 - $reviewer_name = $handles[$reviewer_phid]->getName(); 130 - } else { 131 - $reviewer_name = null; 132 - } 133 - 134 - $who = implode(', ', array_filter(array($author_name, $reviewer_name))); 135 - 136 - $description = $data->getCommitMessage(); 137 - 138 - $commit_uri = PhabricatorEnv::getProductionURI($commit_handle->getURI()); 139 - $differential = $revision 140 - ? PhabricatorEnv::getProductionURI('/D'.$revision->getID()) 141 - : 'No revision.'; 142 - 143 - $limit = self::MAX_FILES_SHOWN_IN_EMAIL; 144 - $files = $adapter->loadAffectedPaths(); 145 - sort($files); 146 - if (count($files) > $limit) { 147 - array_splice($files, $limit); 148 - $files[] = '(This commit affected more than '.$limit.' files. '. 149 - 'Only '.$limit.' are shown here and additional ones are truncated.)'; 150 - } 151 - $files = implode("\n", $files); 152 - 153 - $xscript_id = $xscript->getID(); 154 - 155 - $why_uri = '/herald/transcript/'.$xscript_id.'/'; 156 - 157 - $reply_handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit( 158 - $commit); 159 - 160 - $template = new PhabricatorMetaMTAMail(); 161 - 162 - $inline_patch_text = $this->buildPatch($template, $repository, $commit); 40 + $acting_as_phid = nonempty( 41 + $committer_phid, 42 + $author_phid, 43 + id(new PhabricatorDiffusionApplication())->getPHID()); 163 44 164 - $body = new PhabricatorMetaMTAMailBody(); 165 - $body->addRawSection($description); 166 - $body->addTextSection(pht('DETAILS'), $commit_uri); 45 + $editor = id(new PhabricatorAuditEditor()) 46 + ->setActor(PhabricatorUser::getOmnipotentUser()) 47 + ->setActingAsPHID($acting_as_phid) 48 + ->setContentSource($content_source); 167 49 168 - // TODO: This should be integrated properly once we move to 169 - // ApplicationTransactions. 170 - $field_list = PhabricatorCustomField::getObjectFields( 171 - $commit, 172 - PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS); 173 - $field_list 174 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 175 - ->readFieldsFromStorage($commit); 176 - foreach ($field_list->getFields() as $field) { 177 - try { 178 - $field->buildApplicationTransactionMailBody( 179 - new DifferentialTransaction(), // Bogus object to satisfy typehint. 180 - $body); 181 - } catch (Exception $ex) { 182 - // Log the exception and continue. 183 - phlog($ex); 184 - } 185 - } 186 - 187 - $body->addTextSection(pht('DIFFERENTIAL REVISION'), $differential); 188 - $body->addTextSection(pht('AFFECTED FILES'), $files); 189 - $body->addReplySection($reply_handler->getReplyHandlerInstructions()); 190 - $body->addHeraldSection($why_uri); 191 - $body->addRawSection($inline_patch_text); 192 - $body = $body->render(); 193 - 194 - $prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix'); 195 - 196 - $threading = PhabricatorAuditCommentEditor::getMailThreading( 197 - $repository, 198 - $commit); 199 - list($thread_id, $thread_topic) = $threading; 200 - 201 - $template->setRelatedPHID($commit->getPHID()); 202 - $template->setSubject("{$commit_name}: {$name}"); 203 - $template->setSubjectPrefix($prefix); 204 - $template->setVarySubjectPrefix('[Commit]'); 205 - $template->setBody($body); 206 - $template->setThreadID($thread_id, $is_new = true); 207 - $template->addHeader('Thread-Topic', $thread_topic); 208 - $template->setIsBulk(true); 209 - 210 - $template->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader()); 211 - if ($author_phid) { 212 - $template->setFrom($author_phid); 213 - } 214 - 215 - // TODO: We should verify that each recipient can actually see the 216 - // commit before sending them email (T603). 217 - 218 - $mails = $reply_handler->multiplexMail( 219 - $template, 220 - id(new PhabricatorHandleQuery()) 221 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 222 - ->withPHIDs($email_phids) 223 - ->execute(), 224 - array()); 225 - 226 - foreach ($mails as $mail) { 227 - $mail->saveAndSend(); 228 - } 229 - } 230 - 231 - private function createAudits( 232 - PhabricatorRepositoryCommit $commit, 233 - array $map, 234 - array $ccmap, 235 - array $rules) { 236 - assert_instances_of($rules, 'HeraldRule'); 237 - 238 - $requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere( 239 - 'commitPHID = %s', 240 - $commit->getPHID()); 241 - $requests = mpull($requests, null, 'getAuditorPHID'); 242 - 243 - $rules = mpull($rules, null, 'getID'); 244 - 245 - $maps = array( 246 - PhabricatorAuditStatusConstants::AUDIT_REQUIRED => $map, 247 - ); 248 - 249 - foreach ($maps as $status => $map) { 250 - foreach ($map as $phid => $rule_ids) { 251 - $request = idx($requests, $phid); 252 - if ($request) { 253 - continue; 254 - } 255 - $reasons = array(); 256 - foreach ($rule_ids as $id) { 257 - $rule_name = '?'; 258 - if ($rules[$id]) { 259 - $rule_name = $rules[$id]->getName(); 260 - } 261 - if ($status == PhabricatorAuditStatusConstants::AUDIT_REQUIRED) { 262 - $reasons[] = pht( 263 - '%s Triggered Audit', 264 - "H{$id} {$rule_name}"); 265 - } else { 266 - $reasons[] = pht( 267 - '%s Triggered CC', 268 - "H{$id} {$rule_name}"); 269 - } 270 - } 271 - 272 - $request = new PhabricatorRepositoryAuditRequest(); 273 - $request->setCommitPHID($commit->getPHID()); 274 - $request->setAuditorPHID($phid); 275 - $request->setAuditStatus($status); 276 - $request->setAuditReasons($reasons); 277 - $request->save(); 278 - } 279 - } 280 - 281 - $commit->updateAuditStatus($requests); 282 - $commit->save(); 283 - 284 - if ($ccmap) { 285 - id(new PhabricatorSubscriptionsEditor()) 286 - ->setActor(PhabricatorUser::getOmnipotentUser()) 287 - ->setObject($commit) 288 - ->subscribeExplicit(array_keys($ccmap)) 289 - ->save(); 290 - } 291 - } 292 - 293 - 294 - /** 295 - * Find audit requests in the "Auditors" field if it is present and trigger 296 - * explicit audit requests. 297 - */ 298 - private function createAuditsFromCommitMessage( 299 - PhabricatorRepositoryCommit $commit, 300 - PhabricatorRepositoryCommitData $data) { 301 - 302 - $message = $data->getCommitMessage(); 303 - 304 - $matches = null; 305 - if (!preg_match('/^Auditors:\s*(.*)$/im', $message, $matches)) { 306 - return array(); 307 - } 308 - 309 - $phids = id(new PhabricatorObjectListQuery()) 310 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 311 - ->setAllowPartialResults(true) 312 - ->setAllowedTypes( 313 - array( 314 - PhabricatorPeopleUserPHIDType::TYPECONST, 315 - PhabricatorProjectProjectPHIDType::TYPECONST, 316 - )) 317 - ->setObjectList($matches[1]) 318 - ->execute(); 319 - 320 - if (!$phids) { 321 - return array(); 322 - } 323 - 324 - $requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere( 325 - 'commitPHID = %s', 326 - $commit->getPHID()); 327 - $requests = mpull($requests, null, 'getAuditorPHID'); 328 - 329 - foreach ($phids as $phid) { 330 - if (isset($requests[$phid])) { 331 - continue; 332 - } 333 - 334 - $request = new PhabricatorRepositoryAuditRequest(); 335 - $request->setCommitPHID($commit->getPHID()); 336 - $request->setAuditorPHID($phid); 337 - $request->setAuditStatus( 338 - PhabricatorAuditStatusConstants::AUDIT_REQUESTED); 339 - $request->setAuditReasons( 340 - array( 341 - 'Requested by Author', 342 - )); 343 - $request->save(); 344 - 345 - $requests[$phid] = $request; 346 - } 347 - 348 - $commit->updateAuditStatus($requests); 349 - $commit->save(); 350 - 351 - return $phids; 352 - } 353 - 354 - private function publishFeedStory( 355 - PhabricatorRepository $repository, 356 - PhabricatorRepositoryCommit $commit, 357 - PhabricatorRepositoryCommitData $data) { 358 - 359 - if (time() > $commit->getEpoch() + (24 * 60 * 60)) { 360 - // Don't publish stories that are more than 24 hours old, to avoid 361 - // ridiculous levels of feed spam if a repository is imported without 362 - // disabling feed publishing. 363 - return; 364 - } 365 - 366 - $author_phid = $commit->getAuthorPHID(); 367 - $committer_phid = $data->getCommitDetail('committerPHID'); 368 - 369 - $publisher = new PhabricatorFeedStoryPublisher(); 370 - $publisher->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_COMMIT); 371 - $publisher->setStoryData( 372 - array( 373 - 'commitPHID' => $commit->getPHID(), 50 + $xactions = array(); 51 + $xactions[] = id(new PhabricatorAuditTransaction()) 52 + ->setTransactionType(PhabricatorAuditTransaction::TYPE_COMMIT) 53 + ->setDateCreated($commit->getEpoch()) 54 + ->setNewValue(array( 55 + 'description' => $data->getCommitMessage(), 374 56 'summary' => $data->getSummary(), 375 57 'authorName' => $data->getAuthorName(), 376 - 'authorPHID' => $author_phid, 58 + 'authorPHID' => $commit->getAuthorPHID(), 377 59 'committerName' => $data->getCommitDetail('committer'), 378 - 'committerPHID' => $committer_phid, 60 + 'committerPHID' => $data->getCommitDetail('committerPHID'), 379 61 )); 380 - $publisher->setStoryTime($commit->getEpoch()); 381 - $publisher->setRelatedPHIDs( 382 - array_filter( 383 - array( 384 - $author_phid, 385 - $committer_phid, 386 - ))); 387 - if ($author_phid) { 388 - $publisher->setStoryAuthorPHID($author_phid); 389 - } 390 - $publisher->publish(); 391 - } 392 - 393 - private function buildPatch( 394 - PhabricatorMetaMTAMail $template, 395 - PhabricatorRepository $repository, 396 - PhabricatorRepositoryCommit $commit) { 397 - 398 - $attach_key = 'metamta.diffusion.attach-patches'; 399 - $inline_key = 'metamta.diffusion.inline-patches'; 400 - 401 - $attach_patches = PhabricatorEnv::getEnvConfig($attach_key); 402 - $inline_patches = PhabricatorEnv::getEnvConfig($inline_key); 403 - 404 - if (!$attach_patches && !$inline_patches) { 405 - return; 406 - } 407 - 408 - $encoding = $repository->getDetail('encoding', 'UTF-8'); 409 - 410 - $result = null; 411 - $patch_error = null; 412 - 413 62 try { 414 63 $raw_patch = $this->loadRawPatchText($repository, $commit); 415 - if ($attach_patches) { 416 - $commit_name = $repository->formatCommitName( 417 - $commit->getCommitIdentifier()); 418 - 419 - $template->addAttachment( 420 - new PhabricatorMetaMTAAttachment( 421 - $raw_patch, 422 - $commit_name.'.patch', 423 - 'text/x-patch; charset='.$encoding)); 424 - } 425 64 } catch (Exception $ex) { 426 65 phlog($ex); 427 - $patch_error = 'Unable to generate: '.$ex->getMessage(); 66 + $raw_patch = pht('Unable to generate patch: %s', $ex->getMessage()); 428 67 } 429 - 430 - if ($patch_error) { 431 - $result = $patch_error; 432 - } else if ($inline_patches) { 433 - $len = substr_count($raw_patch, "\n"); 434 - if ($len <= $inline_patches) { 435 - // We send email as utf8, so we need to convert the text to utf8 if 436 - // we can. 437 - if ($encoding) { 438 - $raw_patch = phutil_utf8_convert($raw_patch, 'UTF-8', $encoding); 439 - } 440 - $result = phutil_utf8ize($raw_patch); 441 - } 442 - } 443 - 444 - if ($result) { 445 - $result = "PATCH\n\n{$result}\n"; 446 - } 447 - 448 - return $result; 68 + $editor->setRawPatch($raw_patch); 69 + return $editor->applyTransactions($commit, $xactions); 449 70 } 450 71 451 72 private function loadRawPatchText(