mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-19 05:12:41 +01:00
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
This commit is contained in:
parent
87679d4295
commit
3dd92a78ac
4 changed files with 421 additions and 418 deletions
|
@ -3,6 +3,37 @@
|
|||
final class PhabricatorAuditEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
const MAX_FILES_SHOWN_IN_EMAIL = 1000;
|
||||
|
||||
private $auditReasonMap = array();
|
||||
private $heraldEmailPHIDs = array();
|
||||
private $affectedFiles;
|
||||
private $rawPatch;
|
||||
|
||||
public function addAuditReason($phid, $reason) {
|
||||
if (!isset($this->auditReasonMap[$phid])) {
|
||||
$this->auditReasonMap[$phid] = array();
|
||||
}
|
||||
$this->auditReasonMap[$phid][] = $reason;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getAuditReasons($phid) {
|
||||
if (isset($this->auditReasonMap[$phid])) {
|
||||
return $this->auditReasonMap[$phid];
|
||||
}
|
||||
return array('Added by '.$this->getActor()->getUsername().'.');
|
||||
}
|
||||
|
||||
public function setRawPatch($patch) {
|
||||
$this->rawPatch = $patch;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRawPatch() {
|
||||
return $this->rawPatch;
|
||||
}
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorAuditApplication';
|
||||
}
|
||||
|
@ -17,6 +48,8 @@ final class PhabricatorAuditEditor
|
|||
$types[] = PhabricatorTransactions::TYPE_COMMENT;
|
||||
$types[] = PhabricatorTransactions::TYPE_EDGE;
|
||||
|
||||
$types[] = PhabricatorAuditTransaction::TYPE_COMMIT;
|
||||
|
||||
// TODO: These will get modernized eventually, but that can happen one
|
||||
// at a time later on.
|
||||
$types[] = PhabricatorAuditActionConstants::ACTION;
|
||||
|
@ -44,6 +77,7 @@ final class PhabricatorAuditEditor
|
|||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorAuditActionConstants::ACTION:
|
||||
case PhabricatorAuditActionConstants::INLINE:
|
||||
case PhabricatorAuditTransaction::TYPE_COMMIT:
|
||||
return null;
|
||||
case PhabricatorAuditActionConstants::ADD_AUDITORS:
|
||||
// TODO: For now, just record the added PHIDs. Eventually, turn these
|
||||
|
@ -62,6 +96,7 @@ final class PhabricatorAuditEditor
|
|||
case PhabricatorAuditActionConstants::ACTION:
|
||||
case PhabricatorAuditActionConstants::INLINE:
|
||||
case PhabricatorAuditActionConstants::ADD_AUDITORS:
|
||||
case PhabricatorAuditTransaction::TYPE_COMMIT:
|
||||
return $xaction->getNewValue();
|
||||
}
|
||||
|
||||
|
@ -79,6 +114,7 @@ final class PhabricatorAuditEditor
|
|||
case PhabricatorAuditActionConstants::ACTION:
|
||||
case PhabricatorAuditActionConstants::INLINE:
|
||||
case PhabricatorAuditActionConstants::ADD_AUDITORS:
|
||||
case PhabricatorAuditTransaction::TYPE_COMMIT:
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -95,6 +131,7 @@ final class PhabricatorAuditEditor
|
|||
case PhabricatorTransactions::TYPE_EDGE:
|
||||
case PhabricatorAuditActionConstants::ACTION:
|
||||
case PhabricatorAuditActionConstants::INLINE:
|
||||
case PhabricatorAuditTransaction::TYPE_COMMIT:
|
||||
return;
|
||||
case PhabricatorAuditActionConstants::ADD_AUDITORS:
|
||||
$new = $xaction->getNewValue();
|
||||
|
@ -123,10 +160,7 @@ final class PhabricatorAuditEditor
|
|||
->setCommitPHID($object->getPHID())
|
||||
->setAuditorPHID($phid)
|
||||
->setAuditStatus($audit_requested)
|
||||
->setAuditReasons(
|
||||
array(
|
||||
'Added by '.$actor->getUsername(),
|
||||
))
|
||||
->setAuditReasons($this->getAuditReasons($phid))
|
||||
->save();
|
||||
}
|
||||
|
||||
|
@ -165,8 +199,12 @@ final class PhabricatorAuditEditor
|
|||
$actor_is_author = ($object->getAuthorPHID()) &&
|
||||
($actor_phid == $object->getAuthorPHID());
|
||||
|
||||
$import_status_flag = null;
|
||||
foreach ($xactions as $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorAuditTransaction::TYPE_COMMIT:
|
||||
$import_status_flag = PhabricatorRepositoryCommit::IMPORTED_HERALD;
|
||||
break;
|
||||
case PhabricatorAuditActionConstants::ACTION:
|
||||
$new = $xaction->getNewValue();
|
||||
switch ($new) {
|
||||
|
@ -265,9 +303,66 @@ final class PhabricatorAuditEditor
|
|||
$object->updateAuditStatus($requests);
|
||||
$object->save();
|
||||
|
||||
if ($import_status_flag) {
|
||||
$object->writeImportStatusFlag($import_status_flag);
|
||||
}
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
protected function expandTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
$xactions = parent::expandTransaction($object, $xaction);
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorAuditTransaction::TYPE_COMMIT:
|
||||
$request = $this->createAuditRequestTransactionFromCommitMessage(
|
||||
$object);
|
||||
if ($request) {
|
||||
$xactions[] = $request;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
private function createAuditRequestTransactionFromCommitMessage(
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
$data = $commit->getCommitData();
|
||||
$message = $data->getCommitMessage();
|
||||
|
||||
$matches = null;
|
||||
if (!preg_match('/^Auditors:\s*(.*)$/im', $message, $matches)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$phids = id(new PhabricatorObjectListQuery())
|
||||
->setViewer($this->getActor())
|
||||
->setAllowPartialResults(true)
|
||||
->setAllowedTypes(
|
||||
array(
|
||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||
PhabricatorProjectProjectPHIDType::TYPECONST,
|
||||
))
|
||||
->setObjectList($matches[1])
|
||||
->execute();
|
||||
|
||||
if (!$phids) {
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ($phids as $phid) {
|
||||
$this->addAuditReason($phid, 'Requested by Author');
|
||||
}
|
||||
return id(new PhabricatorAuditTransaction())
|
||||
->setTransactionType(PhabricatorAuditActionConstants::ADD_AUDITORS)
|
||||
->setNewValue(array_fuse($phids));
|
||||
}
|
||||
|
||||
protected function sortTransactions(array $xactions) {
|
||||
$xactions = parent::sortTransactions($xactions);
|
||||
|
||||
|
@ -386,13 +481,23 @@ final class PhabricatorAuditEditor
|
|||
$subject = "{$name}: {$summary}";
|
||||
$thread_topic = "Commit {$monogram}{$identifier}";
|
||||
|
||||
return id(new PhabricatorMetaMTAMail())
|
||||
$template = id(new PhabricatorMetaMTAMail())
|
||||
->setSubject($subject)
|
||||
->addHeader('Thread-Topic', $thread_topic);
|
||||
|
||||
$this->attachPatch(
|
||||
$template,
|
||||
$object);
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
protected function getMailTo(PhabricatorLiskDAO $object) {
|
||||
$phids = array();
|
||||
if ($this->heraldEmailPHIDs) {
|
||||
$phids = $this->heraldEmailPHIDs;
|
||||
}
|
||||
|
||||
if ($object->getAuthorPHID()) {
|
||||
$phids[] = $object->getAuthorPHID();
|
||||
}
|
||||
|
@ -414,12 +519,17 @@ final class PhabricatorAuditEditor
|
|||
$body = parent::buildMailBody($object, $xactions);
|
||||
|
||||
$type_inline = PhabricatorAuditActionConstants::INLINE;
|
||||
$type_push = PhabricatorAuditTransaction::TYPE_COMMIT;
|
||||
|
||||
$is_commit = false;
|
||||
$inlines = array();
|
||||
foreach ($xactions as $xaction) {
|
||||
if ($xaction->getTransactionType() == $type_inline) {
|
||||
$inlines[] = $xaction;
|
||||
}
|
||||
if ($xaction->getTransactionType() == $type_push) {
|
||||
$is_commit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($inlines) {
|
||||
|
@ -428,6 +538,14 @@ final class PhabricatorAuditEditor
|
|||
$this->renderInlineCommentsForMail($object, $inlines));
|
||||
}
|
||||
|
||||
if ($is_commit) {
|
||||
$data = $object->getCommitData();
|
||||
$body->addTextSection(pht('AFFECTED FILES'), $this->affectedFiles);
|
||||
$this->inlinePatch(
|
||||
$body,
|
||||
$object);
|
||||
}
|
||||
|
||||
// Reload the commit to pull commit data.
|
||||
$commit = id(new DiffusionCommitQuery())
|
||||
->setViewer($this->requireActor())
|
||||
|
@ -440,7 +558,7 @@ final class PhabricatorAuditEditor
|
|||
|
||||
$author_phid = $commit->getAuthorPHID();
|
||||
if ($author_phid) {
|
||||
$user_phids[$commit->getAuthorPHID()][] = pht('Author');
|
||||
$user_phids[$author_phid][] = pht('Author');
|
||||
}
|
||||
|
||||
$committer_phid = $data->getCommitDetail('committerPHID');
|
||||
|
@ -448,6 +566,13 @@ final class PhabricatorAuditEditor
|
|||
$user_phids[$committer_phid][] = pht('Committer');
|
||||
}
|
||||
|
||||
// we loaded this in applyFinalEffects
|
||||
$audit_requests = $object->getAudits();
|
||||
$auditor_phids = mpull($audit_requests, 'getAuditorPHID');
|
||||
foreach ($auditor_phids as $auditor_phid) {
|
||||
$user_phids[$auditor_phid][] = pht('Auditor');
|
||||
}
|
||||
|
||||
// TODO: It would be nice to show pusher here too, but that information
|
||||
// is a little tricky to get at right now.
|
||||
|
||||
|
@ -481,6 +606,68 @@ final class PhabricatorAuditEditor
|
|||
return $body;
|
||||
}
|
||||
|
||||
private function attachPatch(
|
||||
PhabricatorMetaMTAMail $template,
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
if (!$this->getRawPatch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attach_key = 'metamta.diffusion.attach-patches';
|
||||
$attach_patches = PhabricatorEnv::getEnvConfig($attach_key);
|
||||
if (!$attach_patches) {
|
||||
return;
|
||||
}
|
||||
|
||||
$repository = $commit->getRepository();
|
||||
$encoding = $repository->getDetail('encoding', 'UTF-8');
|
||||
|
||||
$raw_patch = $this->getRawPatch();
|
||||
$commit_name = $repository->formatCommitName(
|
||||
$commit->getCommitIdentifier());
|
||||
|
||||
$template->addAttachment(
|
||||
new PhabricatorMetaMTAAttachment(
|
||||
$raw_patch,
|
||||
$commit_name.'.patch',
|
||||
'text/x-patch; charset='.$encoding));
|
||||
}
|
||||
|
||||
private function inlinePatch(
|
||||
PhabricatorMetaMTAMailBody $body,
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
if (!$this->getRawPatch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$inline_key = 'metamta.diffusion.inline-patches';
|
||||
$inline_patches = PhabricatorEnv::getEnvConfig($inline_key);
|
||||
if (!$inline_patches) {
|
||||
return;
|
||||
}
|
||||
|
||||
$repository = $commit->getRepository();
|
||||
$raw_patch = $this->getRawPatch();
|
||||
$result = null;
|
||||
$len = substr_count($raw_patch, "\n");
|
||||
if ($len <= $inline_patches) {
|
||||
// We send email as utf8, so we need to convert the text to utf8 if
|
||||
// we can.
|
||||
$encoding = $repository->getDetail('encoding', 'UTF-8');
|
||||
if ($encoding) {
|
||||
$raw_patch = phutil_utf8_convert($raw_patch, 'UTF-8', $encoding);
|
||||
}
|
||||
$result = phutil_utf8ize($raw_patch);
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$result = "PATCH\n\n{$result}\n";
|
||||
}
|
||||
$body->addRawSection($result);
|
||||
}
|
||||
|
||||
private function renderInlineCommentsForMail(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $inline_xactions) {
|
||||
|
@ -521,6 +708,91 @@ final class PhabricatorAuditEditor
|
|||
return $this->isCommitMostlyImported($object);
|
||||
}
|
||||
|
||||
protected function shouldApplyHeraldRules(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorAuditTransaction::TYPE_COMMIT:
|
||||
$repository = $object->getRepository();
|
||||
if ($repository->isImporting()) {
|
||||
return false;
|
||||
}
|
||||
if ($repository->getDetail('herald-disabled')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return parent::shouldApplyHeraldRules($object, $xactions);
|
||||
}
|
||||
|
||||
protected function buildHeraldAdapter(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
||||
return id(new HeraldCommitAdapter())
|
||||
->setCommit($object);
|
||||
}
|
||||
|
||||
protected function didApplyHeraldRules(
|
||||
PhabricatorLiskDAO $object,
|
||||
HeraldAdapter $adapter,
|
||||
HeraldTranscript $transcript) {
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$audit_phids = $adapter->getAuditMap();
|
||||
foreach ($audit_phids as $phid => $rule_ids) {
|
||||
foreach ($rule_ids as $rule_id) {
|
||||
$this->addAuditReason(
|
||||
$phid,
|
||||
pht(
|
||||
'%s Triggered Audit',
|
||||
"H{$rule_id}"));
|
||||
}
|
||||
}
|
||||
if ($audit_phids) {
|
||||
$xactions[] = id(new PhabricatorAuditTransaction())
|
||||
->setTransactionType(PhabricatorAuditActionConstants::ADD_AUDITORS)
|
||||
->setNewValue(array_fuse(array_keys($audit_phids)));
|
||||
}
|
||||
|
||||
$cc_phids = $adapter->getAddCCMap();
|
||||
$add_ccs = array('+' => array());
|
||||
foreach ($cc_phids as $phid => $rule_ids) {
|
||||
$add_ccs['+'][$phid] = $phid;
|
||||
}
|
||||
$xactions[] = id(new PhabricatorAuditTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
|
||||
->setNewValue($add_ccs);
|
||||
|
||||
$this->heraldEmailPHIDs = $adapter->getEmailPHIDs();
|
||||
|
||||
HarbormasterBuildable::applyBuildPlans(
|
||||
$object->getPHID(),
|
||||
$object->getRepository()->getPHID(),
|
||||
$adapter->getBuildPlans());
|
||||
|
||||
$limit = self::MAX_FILES_SHOWN_IN_EMAIL;
|
||||
$files = $adapter->loadAffectedPaths();
|
||||
sort($files);
|
||||
if (count($files) > $limit) {
|
||||
array_splice($files, $limit);
|
||||
$files[] = pht(
|
||||
'(This commit affected more than %d files. Only %d are shown here '.
|
||||
'and additional ones are truncated.)',
|
||||
$limit,
|
||||
$limit);
|
||||
}
|
||||
$this->affectedFiles = implode("\n", $files);
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
private function isCommitMostlyImported(PhabricatorLiskDAO $object) {
|
||||
$has_message = PhabricatorRepositoryCommit::IMPORTED_MESSAGE;
|
||||
$has_changes = PhabricatorRepositoryCommit::IMPORTED_CHANGE;
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
final class PhabricatorAuditTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const TYPE_COMMIT = 'audit:commit';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'audit';
|
||||
}
|
||||
|
@ -15,12 +17,35 @@ final class PhabricatorAuditTransaction
|
|||
return new PhabricatorAuditTransactionComment();
|
||||
}
|
||||
|
||||
public function getRemarkupBlocks() {
|
||||
$blocks = parent::getRemarkupBlocks();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_COMMIT:
|
||||
$data = $this->getNewValue();
|
||||
$blocks[] = $data['description'];
|
||||
break;
|
||||
}
|
||||
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
public function getRequiredHandlePHIDs() {
|
||||
$phids = parent::getRequiredHandlePHIDs();
|
||||
|
||||
$type = $this->getTransactionType();
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_COMMIT:
|
||||
$phids[] = $this->getObjectPHID();
|
||||
$data = $this->getNewValue();
|
||||
if ($data['authorPHID']) {
|
||||
$phids[] = $data['authorPHID'];
|
||||
}
|
||||
if ($data['committerPHID']) {
|
||||
$phids[] = $data['committerPHID'];
|
||||
}
|
||||
break;
|
||||
case PhabricatorAuditActionConstants::ADD_CCS:
|
||||
case PhabricatorAuditActionConstants::ADD_AUDITORS:
|
||||
$old = $this->getOldValue();
|
||||
|
@ -59,6 +84,8 @@ final class PhabricatorAuditTransaction
|
|||
break;
|
||||
case PhabricatorAuditActionConstants::ADD_AUDITORS:
|
||||
return pht('Added Auditors');
|
||||
case self::TYPE_COMMIT:
|
||||
return pht('Committed');
|
||||
}
|
||||
|
||||
return parent::getActionName();
|
||||
|
@ -104,10 +131,47 @@ final class PhabricatorAuditTransaction
|
|||
}
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_COMMIT:
|
||||
$author = null;
|
||||
if ($new['authorPHID']) {
|
||||
$author = $this->renderHandleLink($new['authorPHID']);
|
||||
} else {
|
||||
$author = $new['authorName'];
|
||||
}
|
||||
|
||||
$committer = null;
|
||||
if ($new['committerPHID']) {
|
||||
$committer = $this->renderHandleLink($new['committerPHID']);
|
||||
} else if ($new['committerName']) {
|
||||
$committer = $new['committerName'];
|
||||
}
|
||||
|
||||
$commit = $this->renderHandleLink($this->getObjectPHID());
|
||||
|
||||
if (!$committer) {
|
||||
$committer = $author;
|
||||
$author = null;
|
||||
}
|
||||
|
||||
if ($author) {
|
||||
$title = pht(
|
||||
'%s committed %s (authored by %s).',
|
||||
$committer,
|
||||
$commit,
|
||||
$author);
|
||||
} else {
|
||||
$title = pht(
|
||||
'%s committed %s.',
|
||||
$committer,
|
||||
$commit);
|
||||
}
|
||||
return $title;
|
||||
|
||||
case PhabricatorAuditActionConstants::INLINE:
|
||||
return pht(
|
||||
'%s added inline comments.',
|
||||
$author_handle);
|
||||
|
||||
case PhabricatorAuditActionConstants::ADD_CCS:
|
||||
if ($add && $rem) {
|
||||
return pht(
|
||||
|
@ -203,6 +267,40 @@ final class PhabricatorAuditTransaction
|
|||
}
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_COMMIT:
|
||||
$author = null;
|
||||
if ($new['authorPHID']) {
|
||||
$author = $this->renderHandleLink($new['authorPHID']);
|
||||
} else {
|
||||
$author = $new['authorName'];
|
||||
}
|
||||
|
||||
$committer = null;
|
||||
if ($new['committerPHID']) {
|
||||
$committer = $this->renderHandleLink($new['committerPHID']);
|
||||
} else if ($new['committerName']) {
|
||||
$committer = $new['committerName'];
|
||||
}
|
||||
|
||||
if (!$committer) {
|
||||
$committer = $author;
|
||||
$author = null;
|
||||
}
|
||||
|
||||
if ($author) {
|
||||
$title = pht(
|
||||
'%s committed %s (authored by %s).',
|
||||
$committer,
|
||||
$object_handle,
|
||||
$author);
|
||||
} else {
|
||||
$title = pht(
|
||||
'%s committed %s.',
|
||||
$committer,
|
||||
$object_handle);
|
||||
}
|
||||
return $title;
|
||||
|
||||
case PhabricatorAuditActionConstants::INLINE:
|
||||
return pht(
|
||||
'%s added inline comments to %s.',
|
||||
|
@ -265,6 +363,15 @@ final class PhabricatorAuditTransaction
|
|||
return parent::getTitleForFeed($story);
|
||||
}
|
||||
|
||||
public function getBodyForFeed(PhabricatorFeedStory $story) {
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_COMMIT:
|
||||
$data = $this->getNewValue();
|
||||
return $story->renderSummary($data['summary']);
|
||||
}
|
||||
return parent::getBodyForFeed($story);
|
||||
}
|
||||
|
||||
|
||||
// TODO: These two mail methods can likely be abstracted by introducing a
|
||||
// formal concept of "inline comment" transactions.
|
||||
|
@ -288,6 +395,9 @@ final class PhabricatorAuditTransaction
|
|||
switch ($this->getTransactionType()) {
|
||||
case PhabricatorAuditActionConstants::INLINE:
|
||||
return null;
|
||||
case self::TYPE_COMMIT:
|
||||
$data = $this->getNewValue();
|
||||
return $data['description'];
|
||||
}
|
||||
|
||||
return parent::getBodyForMail();
|
||||
|
|
|
@ -387,7 +387,7 @@ abstract class PhabricatorFeedStory
|
|||
}
|
||||
}
|
||||
|
||||
final protected function renderSummary($text, $len = 128) {
|
||||
final public function renderSummary($text, $len = 128) {
|
||||
if ($len) {
|
||||
$text = id(new PhutilUTF8StringTruncator())
|
||||
->setMaximumGlyphs($len)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
final class PhabricatorRepositoryCommitHeraldWorker
|
||||
extends PhabricatorRepositoryCommitParserWorker {
|
||||
|
||||
const MAX_FILES_SHOWN_IN_EMAIL = 1000;
|
||||
|
||||
public function getRequiredLeaseTime() {
|
||||
// Herald rules may take a long time to process.
|
||||
|
@ -14,34 +13,14 @@ final class PhabricatorRepositoryCommitHeraldWorker
|
|||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
$result = $this->applyHeraldRules($repository, $commit);
|
||||
|
||||
$commit->writeImportStatusFlag(
|
||||
PhabricatorRepositoryCommit::IMPORTED_HERALD);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function applyHeraldRules(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
$commit->attachRepository($repository);
|
||||
|
||||
// Don't take any actions on an importing repository. Principally, this
|
||||
// avoids generating thousands of audits or emails when you import an
|
||||
// established repository on an existing install.
|
||||
if ($repository->isImporting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($repository->getDetail('herald-disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
|
||||
'commitID = %d',
|
||||
$commit->getID());
|
||||
// Reload the commit to pull commit data and audit requests.
|
||||
$commit = id(new DiffusionCommitQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withIDs(array($commit->getID()))
|
||||
->needCommitData(true)
|
||||
->needAuditRequests(true)
|
||||
->executeOne();
|
||||
$data = $commit->getCommitData();
|
||||
|
||||
if (!$data) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
|
@ -50,402 +29,44 @@ final class PhabricatorRepositoryCommitHeraldWorker
|
|||
'or no longer exists.'));
|
||||
}
|
||||
|
||||
$adapter = id(new HeraldCommitAdapter())
|
||||
->setCommit($commit);
|
||||
$commit->attachRepository($repository);
|
||||
|
||||
$rules = id(new HeraldRuleQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withContentTypes(array($adapter->getAdapterContentType()))
|
||||
->withDisabled(false)
|
||||
->needConditionsAndActions(true)
|
||||
->needAppliedToPHIDs(array($adapter->getPHID()))
|
||||
->needValidateAuthors(true)
|
||||
->execute();
|
||||
|
||||
$engine = new HeraldEngine();
|
||||
|
||||
$effects = $engine->applyRules($rules, $adapter);
|
||||
$engine->applyEffects($effects, $adapter, $rules);
|
||||
$xscript = $engine->getTranscript();
|
||||
|
||||
$audit_phids = $adapter->getAuditMap();
|
||||
$cc_phids = $adapter->getAddCCMap();
|
||||
if ($audit_phids || $cc_phids) {
|
||||
$this->createAudits($commit, $audit_phids, $cc_phids, $rules);
|
||||
}
|
||||
|
||||
HarbormasterBuildable::applyBuildPlans(
|
||||
$commit->getPHID(),
|
||||
$repository->getPHID(),
|
||||
$adapter->getBuildPlans());
|
||||
|
||||
$explicit_auditors = $this->createAuditsFromCommitMessage($commit, $data);
|
||||
|
||||
$this->publishFeedStory($repository, $commit, $data);
|
||||
|
||||
$herald_targets = $adapter->getEmailPHIDs();
|
||||
|
||||
$email_phids = array_unique(
|
||||
array_merge(
|
||||
$explicit_auditors,
|
||||
array_keys($cc_phids),
|
||||
$herald_targets));
|
||||
if (!$email_phids) {
|
||||
return;
|
||||
}
|
||||
|
||||
$revision = $adapter->loadDifferentialRevision();
|
||||
if ($revision) {
|
||||
$name = $revision->getTitle();
|
||||
} else {
|
||||
$name = $data->getSummary();
|
||||
}
|
||||
|
||||
$author_phid = $data->getCommitDetail('authorPHID');
|
||||
$reviewer_phid = $data->getCommitDetail('reviewerPHID');
|
||||
|
||||
$phids = array_filter(
|
||||
array(
|
||||
$author_phid,
|
||||
$reviewer_phid,
|
||||
$commit->getPHID(),
|
||||
));
|
||||
|
||||
$handles = id(new PhabricatorHandleQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withPHIDs($phids)
|
||||
->execute();
|
||||
|
||||
$commit_handle = $handles[$commit->getPHID()];
|
||||
$commit_name = $commit_handle->getName();
|
||||
|
||||
if ($author_phid) {
|
||||
$author_name = $handles[$author_phid]->getName();
|
||||
} else {
|
||||
$author_name = $data->getAuthorName();
|
||||
}
|
||||
|
||||
if ($reviewer_phid) {
|
||||
$reviewer_name = $handles[$reviewer_phid]->getName();
|
||||
} else {
|
||||
$reviewer_name = null;
|
||||
}
|
||||
|
||||
$who = implode(', ', array_filter(array($author_name, $reviewer_name)));
|
||||
|
||||
$description = $data->getCommitMessage();
|
||||
|
||||
$commit_uri = PhabricatorEnv::getProductionURI($commit_handle->getURI());
|
||||
$differential = $revision
|
||||
? PhabricatorEnv::getProductionURI('/D'.$revision->getID())
|
||||
: 'No revision.';
|
||||
|
||||
$limit = self::MAX_FILES_SHOWN_IN_EMAIL;
|
||||
$files = $adapter->loadAffectedPaths();
|
||||
sort($files);
|
||||
if (count($files) > $limit) {
|
||||
array_splice($files, $limit);
|
||||
$files[] = '(This commit affected more than '.$limit.' files. '.
|
||||
'Only '.$limit.' are shown here and additional ones are truncated.)';
|
||||
}
|
||||
$files = implode("\n", $files);
|
||||
|
||||
$xscript_id = $xscript->getID();
|
||||
|
||||
$why_uri = '/herald/transcript/'.$xscript_id.'/';
|
||||
|
||||
$reply_handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit(
|
||||
$commit);
|
||||
|
||||
$template = new PhabricatorMetaMTAMail();
|
||||
|
||||
$inline_patch_text = $this->buildPatch($template, $repository, $commit);
|
||||
|
||||
$body = new PhabricatorMetaMTAMailBody();
|
||||
$body->addRawSection($description);
|
||||
$body->addTextSection(pht('DETAILS'), $commit_uri);
|
||||
|
||||
// TODO: This should be integrated properly once we move to
|
||||
// ApplicationTransactions.
|
||||
$field_list = PhabricatorCustomField::getObjectFields(
|
||||
$commit,
|
||||
PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS);
|
||||
$field_list
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->readFieldsFromStorage($commit);
|
||||
foreach ($field_list->getFields() as $field) {
|
||||
try {
|
||||
$field->buildApplicationTransactionMailBody(
|
||||
new DifferentialTransaction(), // Bogus object to satisfy typehint.
|
||||
$body);
|
||||
} catch (Exception $ex) {
|
||||
// Log the exception and continue.
|
||||
phlog($ex);
|
||||
}
|
||||
}
|
||||
|
||||
$body->addTextSection(pht('DIFFERENTIAL REVISION'), $differential);
|
||||
$body->addTextSection(pht('AFFECTED FILES'), $files);
|
||||
$body->addReplySection($reply_handler->getReplyHandlerInstructions());
|
||||
$body->addHeraldSection($why_uri);
|
||||
$body->addRawSection($inline_patch_text);
|
||||
$body = $body->render();
|
||||
|
||||
$prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix');
|
||||
|
||||
$threading = PhabricatorAuditCommentEditor::getMailThreading(
|
||||
$repository,
|
||||
$commit);
|
||||
list($thread_id, $thread_topic) = $threading;
|
||||
|
||||
$template->setRelatedPHID($commit->getPHID());
|
||||
$template->setSubject("{$commit_name}: {$name}");
|
||||
$template->setSubjectPrefix($prefix);
|
||||
$template->setVarySubjectPrefix('[Commit]');
|
||||
$template->setBody($body);
|
||||
$template->setThreadID($thread_id, $is_new = true);
|
||||
$template->addHeader('Thread-Topic', $thread_topic);
|
||||
$template->setIsBulk(true);
|
||||
|
||||
$template->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader());
|
||||
if ($author_phid) {
|
||||
$template->setFrom($author_phid);
|
||||
}
|
||||
|
||||
// TODO: We should verify that each recipient can actually see the
|
||||
// commit before sending them email (T603).
|
||||
|
||||
$mails = $reply_handler->multiplexMail(
|
||||
$template,
|
||||
id(new PhabricatorHandleQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withPHIDs($email_phids)
|
||||
->execute(),
|
||||
$content_source = PhabricatorContentSource::newForSource(
|
||||
PhabricatorContentSource::SOURCE_DAEMON,
|
||||
array());
|
||||
|
||||
foreach ($mails as $mail) {
|
||||
$mail->saveAndSend();
|
||||
}
|
||||
}
|
||||
|
||||
private function createAudits(
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
array $map,
|
||||
array $ccmap,
|
||||
array $rules) {
|
||||
assert_instances_of($rules, 'HeraldRule');
|
||||
|
||||
$requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere(
|
||||
'commitPHID = %s',
|
||||
$commit->getPHID());
|
||||
$requests = mpull($requests, null, 'getAuditorPHID');
|
||||
|
||||
$rules = mpull($rules, null, 'getID');
|
||||
|
||||
$maps = array(
|
||||
PhabricatorAuditStatusConstants::AUDIT_REQUIRED => $map,
|
||||
);
|
||||
|
||||
foreach ($maps as $status => $map) {
|
||||
foreach ($map as $phid => $rule_ids) {
|
||||
$request = idx($requests, $phid);
|
||||
if ($request) {
|
||||
continue;
|
||||
}
|
||||
$reasons = array();
|
||||
foreach ($rule_ids as $id) {
|
||||
$rule_name = '?';
|
||||
if ($rules[$id]) {
|
||||
$rule_name = $rules[$id]->getName();
|
||||
}
|
||||
if ($status == PhabricatorAuditStatusConstants::AUDIT_REQUIRED) {
|
||||
$reasons[] = pht(
|
||||
'%s Triggered Audit',
|
||||
"H{$id} {$rule_name}");
|
||||
} else {
|
||||
$reasons[] = pht(
|
||||
'%s Triggered CC',
|
||||
"H{$id} {$rule_name}");
|
||||
}
|
||||
}
|
||||
|
||||
$request = new PhabricatorRepositoryAuditRequest();
|
||||
$request->setCommitPHID($commit->getPHID());
|
||||
$request->setAuditorPHID($phid);
|
||||
$request->setAuditStatus($status);
|
||||
$request->setAuditReasons($reasons);
|
||||
$request->save();
|
||||
}
|
||||
}
|
||||
|
||||
$commit->updateAuditStatus($requests);
|
||||
$commit->save();
|
||||
|
||||
if ($ccmap) {
|
||||
id(new PhabricatorSubscriptionsEditor())
|
||||
->setActor(PhabricatorUser::getOmnipotentUser())
|
||||
->setObject($commit)
|
||||
->subscribeExplicit(array_keys($ccmap))
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find audit requests in the "Auditors" field if it is present and trigger
|
||||
* explicit audit requests.
|
||||
*/
|
||||
private function createAuditsFromCommitMessage(
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
PhabricatorRepositoryCommitData $data) {
|
||||
|
||||
$message = $data->getCommitMessage();
|
||||
|
||||
$matches = null;
|
||||
if (!preg_match('/^Auditors:\s*(.*)$/im', $message, $matches)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$phids = id(new PhabricatorObjectListQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->setAllowPartialResults(true)
|
||||
->setAllowedTypes(
|
||||
array(
|
||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||
PhabricatorProjectProjectPHIDType::TYPECONST,
|
||||
))
|
||||
->setObjectList($matches[1])
|
||||
->execute();
|
||||
|
||||
if (!$phids) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere(
|
||||
'commitPHID = %s',
|
||||
$commit->getPHID());
|
||||
$requests = mpull($requests, null, 'getAuditorPHID');
|
||||
|
||||
foreach ($phids as $phid) {
|
||||
if (isset($requests[$phid])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$request = new PhabricatorRepositoryAuditRequest();
|
||||
$request->setCommitPHID($commit->getPHID());
|
||||
$request->setAuditorPHID($phid);
|
||||
$request->setAuditStatus(
|
||||
PhabricatorAuditStatusConstants::AUDIT_REQUESTED);
|
||||
$request->setAuditReasons(
|
||||
array(
|
||||
'Requested by Author',
|
||||
));
|
||||
$request->save();
|
||||
|
||||
$requests[$phid] = $request;
|
||||
}
|
||||
|
||||
$commit->updateAuditStatus($requests);
|
||||
$commit->save();
|
||||
|
||||
return $phids;
|
||||
}
|
||||
|
||||
private function publishFeedStory(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
PhabricatorRepositoryCommitData $data) {
|
||||
|
||||
if (time() > $commit->getEpoch() + (24 * 60 * 60)) {
|
||||
// Don't publish stories that are more than 24 hours old, to avoid
|
||||
// ridiculous levels of feed spam if a repository is imported without
|
||||
// disabling feed publishing.
|
||||
return;
|
||||
}
|
||||
|
||||
$author_phid = $commit->getAuthorPHID();
|
||||
$committer_phid = $data->getCommitDetail('committerPHID');
|
||||
$author_phid = $data->getCommitDetail('authorPHID');
|
||||
$acting_as_phid = nonempty(
|
||||
$committer_phid,
|
||||
$author_phid,
|
||||
id(new PhabricatorDiffusionApplication())->getPHID());
|
||||
|
||||
$publisher = new PhabricatorFeedStoryPublisher();
|
||||
$publisher->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_COMMIT);
|
||||
$publisher->setStoryData(
|
||||
array(
|
||||
'commitPHID' => $commit->getPHID(),
|
||||
$editor = id(new PhabricatorAuditEditor())
|
||||
->setActor(PhabricatorUser::getOmnipotentUser())
|
||||
->setActingAsPHID($acting_as_phid)
|
||||
->setContentSource($content_source);
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhabricatorAuditTransaction())
|
||||
->setTransactionType(PhabricatorAuditTransaction::TYPE_COMMIT)
|
||||
->setDateCreated($commit->getEpoch())
|
||||
->setNewValue(array(
|
||||
'description' => $data->getCommitMessage(),
|
||||
'summary' => $data->getSummary(),
|
||||
'authorName' => $data->getAuthorName(),
|
||||
'authorPHID' => $author_phid,
|
||||
'authorPHID' => $commit->getAuthorPHID(),
|
||||
'committerName' => $data->getCommitDetail('committer'),
|
||||
'committerPHID' => $committer_phid,
|
||||
'committerPHID' => $data->getCommitDetail('committerPHID'),
|
||||
));
|
||||
$publisher->setStoryTime($commit->getEpoch());
|
||||
$publisher->setRelatedPHIDs(
|
||||
array_filter(
|
||||
array(
|
||||
$author_phid,
|
||||
$committer_phid,
|
||||
)));
|
||||
if ($author_phid) {
|
||||
$publisher->setStoryAuthorPHID($author_phid);
|
||||
}
|
||||
$publisher->publish();
|
||||
}
|
||||
|
||||
private function buildPatch(
|
||||
PhabricatorMetaMTAMail $template,
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
$attach_key = 'metamta.diffusion.attach-patches';
|
||||
$inline_key = 'metamta.diffusion.inline-patches';
|
||||
|
||||
$attach_patches = PhabricatorEnv::getEnvConfig($attach_key);
|
||||
$inline_patches = PhabricatorEnv::getEnvConfig($inline_key);
|
||||
|
||||
if (!$attach_patches && !$inline_patches) {
|
||||
return;
|
||||
}
|
||||
|
||||
$encoding = $repository->getDetail('encoding', 'UTF-8');
|
||||
|
||||
$result = null;
|
||||
$patch_error = null;
|
||||
|
||||
try {
|
||||
$raw_patch = $this->loadRawPatchText($repository, $commit);
|
||||
if ($attach_patches) {
|
||||
$commit_name = $repository->formatCommitName(
|
||||
$commit->getCommitIdentifier());
|
||||
|
||||
$template->addAttachment(
|
||||
new PhabricatorMetaMTAAttachment(
|
||||
$raw_patch,
|
||||
$commit_name.'.patch',
|
||||
'text/x-patch; charset='.$encoding));
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
phlog($ex);
|
||||
$patch_error = 'Unable to generate: '.$ex->getMessage();
|
||||
$raw_patch = pht('Unable to generate patch: %s', $ex->getMessage());
|
||||
}
|
||||
|
||||
if ($patch_error) {
|
||||
$result = $patch_error;
|
||||
} else if ($inline_patches) {
|
||||
$len = substr_count($raw_patch, "\n");
|
||||
if ($len <= $inline_patches) {
|
||||
// We send email as utf8, so we need to convert the text to utf8 if
|
||||
// we can.
|
||||
if ($encoding) {
|
||||
$raw_patch = phutil_utf8_convert($raw_patch, 'UTF-8', $encoding);
|
||||
}
|
||||
$result = phutil_utf8ize($raw_patch);
|
||||
}
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$result = "PATCH\n\n{$result}\n";
|
||||
}
|
||||
|
||||
return $result;
|
||||
$editor->setRawPatch($raw_patch);
|
||||
return $editor->applyTransactions($commit, $xactions);
|
||||
}
|
||||
|
||||
private function loadRawPatchText(
|
||||
|
|
Loading…
Reference in a new issue