diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -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 @@ $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 @@ 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 @@ case PhabricatorAuditActionConstants::ACTION: case PhabricatorAuditActionConstants::INLINE: case PhabricatorAuditActionConstants::ADD_AUDITORS: + case PhabricatorAuditTransaction::TYPE_COMMIT: return $xaction->getNewValue(); } @@ -79,6 +114,7 @@ case PhabricatorAuditActionConstants::ACTION: case PhabricatorAuditActionConstants::INLINE: case PhabricatorAuditActionConstants::ADD_AUDITORS: + case PhabricatorAuditTransaction::TYPE_COMMIT: return; } @@ -95,6 +131,7 @@ 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 @@ ->setCommitPHID($object->getPHID()) ->setAuditorPHID($phid) ->setAuditStatus($audit_requested) - ->setAuditReasons( - array( - 'Added by '.$actor->getUsername(), - )) + ->setAuditReasons($this->getAuditReasons($phid)) ->save(); } @@ -165,8 +199,12 @@ $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 @@ $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 @@ $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 @@ $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 @@ $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 @@ $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 @@ $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 @@ 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 @@ 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; diff --git a/src/applications/audit/storage/PhabricatorAuditTransaction.php b/src/applications/audit/storage/PhabricatorAuditTransaction.php --- a/src/applications/audit/storage/PhabricatorAuditTransaction.php +++ b/src/applications/audit/storage/PhabricatorAuditTransaction.php @@ -3,6 +3,8 @@ final class PhabricatorAuditTransaction extends PhabricatorApplicationTransaction { + const TYPE_COMMIT = 'audit:commit'; + public function getApplicationName() { return 'audit'; } @@ -15,12 +17,35 @@ 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 @@ break; case PhabricatorAuditActionConstants::ADD_AUDITORS: return pht('Added Auditors'); + case self::TYPE_COMMIT: + return pht('Committed'); } return parent::getActionName(); @@ -104,10 +131,47 @@ } 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 @@ } 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 @@ 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 @@ switch ($this->getTransactionType()) { case PhabricatorAuditActionConstants::INLINE: return null; + case self::TYPE_COMMIT: + $data = $this->getNewValue(); + return $data['description']; } return parent::getBodyForMail(); diff --git a/src/applications/feed/story/PhabricatorFeedStory.php b/src/applications/feed/story/PhabricatorFeedStory.php --- a/src/applications/feed/story/PhabricatorFeedStory.php +++ b/src/applications/feed/story/PhabricatorFeedStory.php @@ -387,7 +387,7 @@ } } - final protected function renderSummary($text, $len = 128) { + final public function renderSummary($text, $len = 128) { if ($len) { $text = id(new PhutilUTF8StringTruncator()) ->setMaximumGlyphs($len) diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php --- a/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php @@ -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 @@ 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 @@ 'or no longer exists.')); } - $adapter = id(new HeraldCommitAdapter()) - ->setCommit($commit); - - $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). + $commit->attachRepository($repository); - $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'); - - $publisher = new PhabricatorFeedStoryPublisher(); - $publisher->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_COMMIT); - $publisher->setStoryData( - array( - 'commitPHID' => $commit->getPHID(), + $author_phid = $data->getCommitDetail('authorPHID'); + $acting_as_phid = nonempty( + $committer_phid, + $author_phid, + id(new PhabricatorDiffusionApplication())->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(); - } - - 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); - } + $raw_patch = pht('Unable to generate patch: %s', $ex->getMessage()); } - - if ($result) { - $result = "PATCH\n\n{$result}\n"; - } - - return $result; + $editor->setRawPatch($raw_patch); + return $editor->applyTransactions($commit, $xactions); } private function loadRawPatchText(