Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2891326
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
58 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/herald/adapter/HeraldCommitAdapter.php b/src/applications/herald/adapter/HeraldCommitAdapter.php
index 55bf6fc23e..34f1c51301 100644
--- a/src/applications/herald/adapter/HeraldCommitAdapter.php
+++ b/src/applications/herald/adapter/HeraldCommitAdapter.php
@@ -1,502 +1,539 @@
<?php
/**
* @group herald
*/
final class HeraldCommitAdapter extends HeraldAdapter {
const FIELD_NEED_AUDIT_FOR_PACKAGE = 'need-audit-for-package';
const FIELD_REPOSITORY_AUTOCLOSE_BRANCH = 'repository-autoclose-branch';
protected $diff;
protected $revision;
protected $repository;
protected $commit;
protected $commitData;
private $commitDiff;
protected $emailPHIDs = array();
protected $addCCPHIDs = array();
protected $auditMap = array();
protected $buildPlans = array();
protected $affectedPaths;
protected $affectedRevision;
protected $affectedPackages;
protected $auditNeededPackages;
public function getAdapterApplicationClass() {
return 'PhabricatorApplicationDiffusion';
}
public function getObject() {
return $this->commit;
}
public function getAdapterContentType() {
return 'commit';
}
public function getAdapterContentName() {
return pht('Commits');
}
public function getAdapterContentDescription() {
return pht(
"React to new commits appearing in tracked repositories.\n".
"Commit rules can send email, flag commits, trigger audits, ".
"and run build plans.");
}
public function supportsRuleType($rule_type) {
switch ($rule_type) {
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
case HeraldRuleTypeConfig::RULE_TYPE_OBJECT:
return true;
default:
return false;
}
}
public function canTriggerOnObject($object) {
if ($object instanceof PhabricatorRepository) {
return true;
}
+ if ($object instanceof PhabricatorProject) {
+ return true;
+ }
return false;
}
public function getTriggerObjectPHIDs() {
- return array(
- $this->repository->getPHID(),
- $this->getPHID(),
- );
+ return array_merge(
+ array(
+ $this->repository->getPHID(),
+ $this->getPHID(),
+ ),
+ $this->repository->getProjectPHIDs());
}
public function explainValidTriggerObjects() {
return pht(
- 'This rule can trigger for **repositories**.');
+ 'This rule can trigger for **repositories** and **projects**.');
}
public function getFieldNameMap() {
return array(
self::FIELD_NEED_AUDIT_FOR_PACKAGE =>
pht('Affected packages that need audit'),
self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH => pht('On autoclose branch'),
) + parent::getFieldNameMap();
}
public function getFields() {
return array_merge(
array(
self::FIELD_BODY,
self::FIELD_AUTHOR,
self::FIELD_COMMITTER,
self::FIELD_REVIEWER,
self::FIELD_REPOSITORY,
+ self::FIELD_REPOSITORY_PROJECTS,
self::FIELD_DIFF_FILE,
self::FIELD_DIFF_CONTENT,
self::FIELD_DIFF_ADDED_CONTENT,
self::FIELD_DIFF_REMOVED_CONTENT,
self::FIELD_DIFF_ENORMOUS,
self::FIELD_RULE,
self::FIELD_AFFECTED_PACKAGE,
self::FIELD_AFFECTED_PACKAGE_OWNER,
self::FIELD_NEED_AUDIT_FOR_PACKAGE,
self::FIELD_DIFFERENTIAL_REVISION,
self::FIELD_DIFFERENTIAL_ACCEPTED,
self::FIELD_DIFFERENTIAL_REVIEWERS,
self::FIELD_DIFFERENTIAL_CCS,
self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH,
),
parent::getFields());
}
public function getConditionsForField($field) {
switch ($field) {
case self::FIELD_NEED_AUDIT_FOR_PACKAGE:
return array(
self::CONDITION_INCLUDE_ANY,
self::CONDITION_INCLUDE_NONE,
);
case self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH:
return array(
self::CONDITION_UNCONDITIONALLY,
);
}
return parent::getConditionsForField($field);
}
public function getActions($rule_type) {
switch ($rule_type) {
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
case HeraldRuleTypeConfig::RULE_TYPE_OBJECT:
return array(
self::ACTION_ADD_CC,
self::ACTION_EMAIL,
self::ACTION_AUDIT,
self::ACTION_APPLY_BUILD_PLANS,
self::ACTION_NOTHING
);
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
return array(
self::ACTION_ADD_CC,
self::ACTION_EMAIL,
self::ACTION_FLAG,
self::ACTION_AUDIT,
self::ACTION_NOTHING,
);
}
}
public function getValueTypeForFieldAndCondition($field, $condition) {
switch ($field) {
case self::FIELD_DIFFERENTIAL_CCS:
return self::VALUE_EMAIL;
case self::FIELD_NEED_AUDIT_FOR_PACKAGE:
return self::VALUE_OWNERS_PACKAGE;
}
return parent::getValueTypeForFieldAndCondition($field, $condition);
}
public static function newLegacyAdapter(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $commit_data) {
$object = new HeraldCommitAdapter();
$commit->attachRepository($repository);
$object->repository = $repository;
$object->commit = $commit;
$object->commitData = $commit_data;
return $object;
}
+ public function setCommit(PhabricatorRepositoryCommit $commit) {
+ $viewer = PhabricatorUser::getOmnipotentUser();
+
+ $repository = id(new PhabricatorRepositoryQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($commit->getRepositoryID()))
+ ->needProjectPHIDs(true)
+ ->executeOne();
+ if (!$repository) {
+ throw new Exception(pht('Unable to load repository!'));
+ }
+
+ $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
+ 'commitID = %d',
+ $commit->getID());
+ if (!$data) {
+ throw new Exception(pht('Unable to load commit data!'));
+ }
+
+ $this->commit = clone $commit;
+ $this->commit->attachRepository($repository);
+ $this->commit->attachCommitData($data);
+
+ $this->repository = $repository;
+ $this->commitData = $data;
+
+ return $this;
+ }
+
public function getPHID() {
return $this->commit->getPHID();
}
public function getEmailPHIDs() {
return array_keys($this->emailPHIDs);
}
public function getAddCCMap() {
return $this->addCCPHIDs;
}
public function getAuditMap() {
return $this->auditMap;
}
public function getBuildPlans() {
return $this->buildPlans;
}
public function getHeraldName() {
return
'r'.
$this->repository->getCallsign().
$this->commit->getCommitIdentifier();
}
public function loadAffectedPaths() {
if ($this->affectedPaths === null) {
$result = PhabricatorOwnerPathQuery::loadAffectedPaths(
$this->repository,
$this->commit,
PhabricatorUser::getOmnipotentUser());
$this->affectedPaths = $result;
}
return $this->affectedPaths;
}
public function loadAffectedPackages() {
if ($this->affectedPackages === null) {
$packages = PhabricatorOwnersPackage::loadAffectedPackages(
$this->repository,
$this->loadAffectedPaths());
$this->affectedPackages = $packages;
}
return $this->affectedPackages;
}
public function loadAuditNeededPackage() {
if ($this->auditNeededPackages === null) {
$status_arr = array(
PhabricatorAuditStatusConstants::AUDIT_REQUIRED,
PhabricatorAuditStatusConstants::CONCERNED,
);
$requests = id(new PhabricatorRepositoryAuditRequest())
->loadAllWhere(
"commitPHID = %s AND auditStatus IN (%Ls)",
$this->commit->getPHID(),
$status_arr);
$packages = mpull($requests, 'getAuditorPHID');
$this->auditNeededPackages = $packages;
}
return $this->auditNeededPackages;
}
public function loadDifferentialRevision() {
if ($this->affectedRevision === null) {
$this->affectedRevision = false;
$data = $this->commitData;
$revision_id = $data->getCommitDetail('differential.revisionID');
if ($revision_id) {
// NOTE: The Herald rule owner might not actually have access to
// the revision, and can control which revision a commit is
// associated with by putting text in the commit message. However,
// the rules they can write against revisions don't actually expose
// anything interesting, so it seems reasonable to load unconditionally
// here.
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($revision_id))
->setViewer(PhabricatorUser::getOmnipotentUser())
->needRelationships(true)
->needReviewerStatus(true)
->executeOne();
if ($revision) {
$this->affectedRevision = $revision;
}
}
}
return $this->affectedRevision;
}
public static function getEnormousByteLimit() {
return 1024 * 1024 * 1024; // 1GB
}
public static function getEnormousTimeLimit() {
return 60 * 15; // 15 Minutes
}
private function loadCommitDiff() {
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => PhabricatorUser::getOmnipotentUser(),
'repository' => $this->repository,
'commit' => $this->commit->getCommitIdentifier(),
));
$byte_limit = self::getEnormousByteLimit();
$raw = DiffusionQuery::callConduitWithDiffusionRequest(
PhabricatorUser::getOmnipotentUser(),
$drequest,
'diffusion.rawdiffquery',
array(
'commit' => $this->commit->getCommitIdentifier(),
'timeout' => self::getEnormousTimeLimit(),
'byteLimit' => $byte_limit,
'linesOfContext' => 0,
));
if (strlen($raw) >= $byte_limit) {
throw new Exception(
pht(
'The raw text of this change is enormous (larger than %d bytes). '.
'Herald can not process it.',
$byte_limit));
}
$parser = new ArcanistDiffParser();
$changes = $parser->parseDiff($raw);
$diff = DifferentialDiff::newFromRawChanges($changes);
return $diff;
}
private function getDiffContent($type) {
if ($this->commitDiff === null) {
try {
$this->commitDiff = $this->loadCommitDiff();
} catch (Exception $ex) {
$this->commitDiff = $ex;
phlog($ex);
}
}
if ($this->commitDiff instanceof Exception) {
$ex = $this->commitDiff;
$ex_class = get_class($ex);
$ex_message = pht('Failed to load changes: %s', $ex->getMessage());
return array(
'<'.$ex_class.'>' => $ex_message,
);
}
$changes = $this->commitDiff->getChangesets();
$result = array();
foreach ($changes as $change) {
$lines = array();
foreach ($change->getHunks() as $hunk) {
switch ($type) {
case '-':
$lines[] = $hunk->makeOldFile();
break;
case '+':
$lines[] = $hunk->makeNewFile();
break;
case '*':
$lines[] = $hunk->makeChanges();
break;
default:
throw new Exception("Unknown content selection '{$type}'!");
}
}
$result[$change->getFilename()] = implode("\n", $lines);
}
return $result;
}
public function getHeraldField($field) {
$data = $this->commitData;
switch ($field) {
case self::FIELD_BODY:
return $data->getCommitMessage();
case self::FIELD_AUTHOR:
return $data->getCommitDetail('authorPHID');
case self::FIELD_COMMITTER:
return $data->getCommitDetail('committerPHID');
case self::FIELD_REVIEWER:
return $data->getCommitDetail('reviewerPHID');
case self::FIELD_DIFF_FILE:
return $this->loadAffectedPaths();
case self::FIELD_REPOSITORY:
return $this->repository->getPHID();
+ case self::FIELD_REPOSITORY_PROJECTS:
+ return $this->repository->getProjectPHIDs();
case self::FIELD_DIFF_CONTENT:
return $this->getDiffContent('*');
case self::FIELD_DIFF_ADDED_CONTENT:
return $this->getDiffContent('+');
case self::FIELD_DIFF_REMOVED_CONTENT:
return $this->getDiffContent('-');
case self::FIELD_DIFF_ENORMOUS:
$this->getDiffContent('*');
return ($this->commitDiff instanceof Exception);
case self::FIELD_AFFECTED_PACKAGE:
$packages = $this->loadAffectedPackages();
return mpull($packages, 'getPHID');
case self::FIELD_AFFECTED_PACKAGE_OWNER:
$packages = $this->loadAffectedPackages();
$owners = PhabricatorOwnersOwner::loadAllForPackages($packages);
return mpull($owners, 'getUserPHID');
case self::FIELD_NEED_AUDIT_FOR_PACKAGE:
return $this->loadAuditNeededPackage();
case self::FIELD_DIFFERENTIAL_REVISION:
$revision = $this->loadDifferentialRevision();
if (!$revision) {
return null;
}
return $revision->getID();
case self::FIELD_DIFFERENTIAL_ACCEPTED:
$revision = $this->loadDifferentialRevision();
if (!$revision) {
return null;
}
// after a revision is accepted, it can be closed (say via arc land)
// so use this function to figure out if it was accepted at one point
// *and* not later rejected... what a function!
$reviewed_by = $revision->loadReviewedBy();
if (!$reviewed_by) {
return null;
}
return $revision->getPHID();
case self::FIELD_DIFFERENTIAL_REVIEWERS:
$revision = $this->loadDifferentialRevision();
if (!$revision) {
return array();
}
return $revision->getReviewers();
case self::FIELD_DIFFERENTIAL_CCS:
$revision = $this->loadDifferentialRevision();
if (!$revision) {
return array();
}
return $revision->getCCPHIDs();
case self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH:
return $this->repository->shouldAutocloseCommit(
$this->commit,
$this->commitData);
}
return parent::getHeraldField($field);
}
public function applyHeraldEffects(array $effects) {
assert_instances_of($effects, 'HeraldEffect');
$result = array();
foreach ($effects as $effect) {
$action = $effect->getAction();
switch ($action) {
case self::ACTION_NOTHING:
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Great success at doing nothing.'));
break;
case self::ACTION_EMAIL:
foreach ($effect->getTarget() as $phid) {
$this->emailPHIDs[$phid] = true;
}
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Added address to email targets.'));
break;
case self::ACTION_ADD_CC:
foreach ($effect->getTarget() as $phid) {
if (empty($this->addCCPHIDs[$phid])) {
$this->addCCPHIDs[$phid] = array();
}
$this->addCCPHIDs[$phid][] = $effect->getRuleID();
}
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Added address to CC.'));
break;
case self::ACTION_AUDIT:
foreach ($effect->getTarget() as $phid) {
if (empty($this->auditMap[$phid])) {
$this->auditMap[$phid] = array();
}
$this->auditMap[$phid][] = $effect->getRuleID();
}
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Triggered an audit.'));
break;
case self::ACTION_APPLY_BUILD_PLANS:
foreach ($effect->getTarget() as $phid) {
$this->buildPlans[] = $phid;
}
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Applied build plans.'));
break;
case self::ACTION_FLAG:
$result[] = parent::applyFlagEffect(
$effect,
$this->commit->getPHID());
break;
default:
throw new Exception("No rules to handle action '{$action}'.");
}
}
return $result;
}
}
diff --git a/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php b/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php
index a17d6b2e6d..87c6357902 100644
--- a/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php
+++ b/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php
@@ -1,510 +1,521 @@
<?php
final class HeraldDifferentialRevisionAdapter extends HeraldAdapter {
protected $revision;
protected $diff;
protected $explicitCCs;
protected $explicitReviewers;
protected $forbiddenCCs;
protected $newCCs = array();
protected $remCCs = array();
protected $emailPHIDs = array();
protected $addReviewerPHIDs = array();
protected $blockingReviewerPHIDs = array();
protected $buildPlans = array();
protected $repository;
protected $affectedPackages;
protected $changesets;
public function getAdapterApplicationClass() {
return 'PhabricatorApplicationDifferential';
}
public function getObject() {
return $this->revision;
}
public function getAdapterContentType() {
return 'differential';
}
public function getAdapterContentName() {
return pht('Differential Revisions');
}
public function getAdapterContentDescription() {
return pht(
"React to revisions being created or updated.\n".
"Revision rules can send email, flag revisions, add reviewers, ".
"and run build plans.");
}
public function supportsRuleType($rule_type) {
switch ($rule_type) {
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
return true;
case HeraldRuleTypeConfig::RULE_TYPE_OBJECT:
default:
return false;
}
}
public function getFields() {
return array_merge(
array(
self::FIELD_TITLE,
self::FIELD_BODY,
self::FIELD_AUTHOR,
self::FIELD_AUTHOR_PROJECTS,
self::FIELD_REVIEWERS,
self::FIELD_CC,
self::FIELD_REPOSITORY,
+ self::FIELD_REPOSITORY_PROJECTS,
self::FIELD_DIFF_FILE,
self::FIELD_DIFF_CONTENT,
self::FIELD_DIFF_ADDED_CONTENT,
self::FIELD_DIFF_REMOVED_CONTENT,
self::FIELD_RULE,
self::FIELD_AFFECTED_PACKAGE,
self::FIELD_AFFECTED_PACKAGE_OWNER,
),
parent::getFields());
}
public function getRepetitionOptions() {
return array(
HeraldRepetitionPolicyConfig::EVERY,
HeraldRepetitionPolicyConfig::FIRST,
);
}
public static function newLegacyAdapter(
DifferentialRevision $revision,
DifferentialDiff $diff) {
$object = new HeraldDifferentialRevisionAdapter();
// Reload the revision to pick up relationship information.
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($revision->getID()))
->setViewer(PhabricatorUser::getOmnipotentUser())
->needRelationships(true)
->needReviewerStatus(true)
->executeOne();
$object->revision = $revision;
$object->diff = $diff;
return $object;
}
public function setExplicitCCs($explicit_ccs) {
$this->explicitCCs = $explicit_ccs;
return $this;
}
public function setExplicitReviewers($explicit_reviewers) {
$this->explicitReviewers = $explicit_reviewers;
return $this;
}
public function setForbiddenCCs($forbidden_ccs) {
$this->forbiddenCCs = $forbidden_ccs;
return $this;
}
public function getCCsAddedByHerald() {
return array_diff_key($this->newCCs, $this->remCCs);
}
public function getCCsRemovedByHerald() {
return $this->remCCs;
}
public function getEmailPHIDsAddedByHerald() {
return $this->emailPHIDs;
}
public function getReviewersAddedByHerald() {
return $this->addReviewerPHIDs;
}
public function getBlockingReviewersAddedByHerald() {
return $this->blockingReviewerPHIDs;
}
public function getBuildPlans() {
return $this->buildPlans;
}
public function getPHID() {
return $this->revision->getPHID();
}
public function getHeraldName() {
return $this->revision->getTitle();
}
public function loadRepository() {
if ($this->repository === null) {
$this->repository = false;
- // TODO: (T603) Implement policy stuff in Herald.
$viewer = PhabricatorUser::getOmnipotentUser();
+ $repository_phid = null;
$revision = $this->revision;
if ($revision->getRepositoryPHID()) {
- $repositories = id(new PhabricatorRepositoryQuery())
+ $repository_phid = $revision->getRepositoryPHID();
+ } else {
+ $repository = id(new DifferentialRepositoryLookup())
->setViewer($viewer)
- ->withPHIDs(array($revision->getRepositoryPHID()))
- ->execute();
- if ($repositories) {
- $this->repository = head($repositories);
- return $this->repository;
+ ->setDiff($this->diff)
+ ->lookupRepository();
+ if ($repository) {
+ // We want to get the projects for this repository too, so run a
+ // full query for it below.
+ $repository_phid = $repository->getPHID();
}
}
- $repository = id(new DifferentialRepositoryLookup())
- ->setViewer($viewer)
- ->setDiff($this->diff)
- ->lookupRepository();
- if ($repository) {
- $this->repository = $repository;
- return $this->repository;
+ if ($repository_phid) {
+ $repository = id(new PhabricatorRepositoryQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($repository_phid))
+ ->needProjectPHIDs(true)
+ ->executeOne();
+ if ($repository) {
+ $this->repository = $repository;
+ }
}
-
- $repository = false;
}
+
return $this->repository;
}
protected function loadChangesets() {
if ($this->changesets === null) {
$this->changesets = $this->diff->loadChangesets();
}
return $this->changesets;
}
protected function loadAffectedPaths() {
$changesets = $this->loadChangesets();
$paths = array();
foreach ($changesets as $changeset) {
$paths[] = $this->getAbsoluteRepositoryPathForChangeset($changeset);
}
return $paths;
}
protected function getAbsoluteRepositoryPathForChangeset(
DifferentialChangeset $changeset) {
$repository = $this->loadRepository();
if (!$repository) {
return '/'.ltrim($changeset->getFilename(), '/');
}
$diff = $this->diff;
return $changeset->getAbsoluteRepositoryPath($repository, $diff);
}
protected function loadContentDictionary() {
$changesets = $this->loadChangesets();
$hunks = array();
if ($changesets) {
$hunks = id(new DifferentialHunk())->loadAllWhere(
'changesetID in (%Ld)',
mpull($changesets, 'getID'));
}
$dict = array();
$hunks = mgroup($hunks, 'getChangesetID');
$changesets = mpull($changesets, null, 'getID');
foreach ($changesets as $id => $changeset) {
$path = $this->getAbsoluteRepositoryPathForChangeset($changeset);
$content = array();
foreach (idx($hunks, $id, array()) as $hunk) {
$content[] = $hunk->makeChanges();
}
$dict[$path] = implode("\n", $content);
}
return $dict;
}
protected function loadAddedContentDictionary() {
$changesets = $this->loadChangesets();
$hunks = array();
if ($changesets) {
$hunks = id(new DifferentialHunk())->loadAllWhere(
'changesetID in (%Ld)',
mpull($changesets, 'getID'));
}
$dict = array();
$hunks = mgroup($hunks, 'getChangesetID');
$changesets = mpull($changesets, null, 'getID');
foreach ($changesets as $id => $changeset) {
$path = $this->getAbsoluteRepositoryPathForChangeset($changeset);
$content = array();
foreach (idx($hunks, $id, array()) as $hunk) {
$content[] = implode('', $hunk->getAddedLines());
}
$dict[$path] = implode("\n", $content);
}
return $dict;
}
protected function loadRemovedContentDictionary() {
$changesets = $this->loadChangesets();
$hunks = array();
if ($changesets) {
$hunks = id(new DifferentialHunk())->loadAllWhere(
'changesetID in (%Ld)',
mpull($changesets, 'getID'));
}
$dict = array();
$hunks = mgroup($hunks, 'getChangesetID');
$changesets = mpull($changesets, null, 'getID');
foreach ($changesets as $id => $changeset) {
$path = $this->getAbsoluteRepositoryPathForChangeset($changeset);
$content = array();
foreach (idx($hunks, $id, array()) as $hunk) {
$content[] = implode('', $hunk->getRemovedLines());
}
$dict[$path] = implode("\n", $content);
}
return $dict;
}
public function loadAffectedPackages() {
if ($this->affectedPackages === null) {
$this->affectedPackages = array();
$repository = $this->loadRepository();
if ($repository) {
$packages = PhabricatorOwnersPackage::loadAffectedPackages(
$repository,
$this->loadAffectedPaths());
$this->affectedPackages = $packages;
}
}
return $this->affectedPackages;
}
public function getHeraldField($field) {
switch ($field) {
case self::FIELD_TITLE:
return $this->revision->getTitle();
break;
case self::FIELD_BODY:
return $this->revision->getSummary()."\n".
$this->revision->getTestPlan();
break;
case self::FIELD_AUTHOR:
return $this->revision->getAuthorPHID();
break;
case self::FIELD_AUTHOR_PROJECTS:
$author_phid = $this->revision->getAuthorPHID();
if (!$author_phid) {
return array();
}
$projects = id(new PhabricatorProjectQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withMemberPHIDs(array($author_phid))
->execute();
return mpull($projects, 'getPHID');
case self::FIELD_DIFF_FILE:
return $this->loadAffectedPaths();
case self::FIELD_CC:
if (isset($this->explicitCCs)) {
return array_keys($this->explicitCCs);
} else {
return $this->revision->getCCPHIDs();
}
case self::FIELD_REVIEWERS:
if (isset($this->explicitReviewers)) {
return array_keys($this->explicitReviewers);
} else {
return $this->revision->getReviewers();
}
case self::FIELD_REPOSITORY:
$repository = $this->loadRepository();
if (!$repository) {
return null;
}
return $repository->getPHID();
+ case self::FIELD_REPOSITORY_PROJECTS:
+ $repository = $this->loadRepository();
+ if (!$repository) {
+ return null;
+ }
+ return $repository->getProjectPHIDs();
case self::FIELD_DIFF_CONTENT:
return $this->loadContentDictionary();
case self::FIELD_DIFF_ADDED_CONTENT:
return $this->loadAddedContentDictionary();
case self::FIELD_DIFF_REMOVED_CONTENT:
return $this->loadRemovedContentDictionary();
case self::FIELD_AFFECTED_PACKAGE:
$packages = $this->loadAffectedPackages();
return mpull($packages, 'getPHID');
case self::FIELD_AFFECTED_PACKAGE_OWNER:
$packages = $this->loadAffectedPackages();
return PhabricatorOwnersOwner::loadAffiliatedUserPHIDs(
mpull($packages, 'getID'));
}
return parent::getHeraldField($field);
}
public function getActions($rule_type) {
switch ($rule_type) {
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
return array(
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
self::ACTION_ADD_REVIEWERS,
self::ACTION_ADD_BLOCKING_REVIEWERS,
self::ACTION_APPLY_BUILD_PLANS,
self::ACTION_NOTHING,
);
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
return array(
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
self::ACTION_FLAG,
self::ACTION_ADD_REVIEWERS,
self::ACTION_ADD_BLOCKING_REVIEWERS,
self::ACTION_NOTHING,
);
}
}
public function applyHeraldEffects(array $effects) {
assert_instances_of($effects, 'HeraldEffect');
$result = array();
if ($this->explicitCCs) {
$effect = new HeraldEffect();
$effect->setAction(self::ACTION_ADD_CC);
$effect->setTarget(array_keys($this->explicitCCs));
$effect->setReason(
pht('CCs provided explicitly by revision author or carried over '.
'from a previous version of the revision.'));
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Added addresses to CC list.'));
}
$forbidden_ccs = array_fill_keys(
nonempty($this->forbiddenCCs, array()),
true);
foreach ($effects as $effect) {
$action = $effect->getAction();
switch ($action) {
case self::ACTION_NOTHING:
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('OK, did nothing.'));
break;
case self::ACTION_FLAG:
$result[] = parent::applyFlagEffect(
$effect,
$this->revision->getPHID());
break;
case self::ACTION_EMAIL:
case self::ACTION_ADD_CC:
$op = ($action == self::ACTION_EMAIL) ? 'email' : 'CC';
$base_target = $effect->getTarget();
$forbidden = array();
foreach ($base_target as $key => $fbid) {
if (isset($forbidden_ccs[$fbid])) {
$forbidden[] = $fbid;
unset($base_target[$key]);
} else {
if ($action == self::ACTION_EMAIL) {
$this->emailPHIDs[$fbid] = true;
} else {
$this->newCCs[$fbid] = true;
}
}
}
if ($forbidden) {
$failed = clone $effect;
$failed->setTarget($forbidden);
if ($base_target) {
$effect->setTarget($base_target);
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Added these addresses to %s list. '.
'Others could not be added.', $op));
}
$result[] = new HeraldApplyTranscript(
$failed,
false,
pht('%s forbidden, these addresses have unsubscribed.', $op));
} else {
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Added addresses to %s list.', $op));
}
break;
case self::ACTION_REMOVE_CC:
foreach ($effect->getTarget() as $fbid) {
$this->remCCs[$fbid] = true;
}
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Removed addresses from CC list.'));
break;
case self::ACTION_ADD_REVIEWERS:
foreach ($effect->getTarget() as $phid) {
$this->addReviewerPHIDs[$phid] = true;
}
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Added reviewers.'));
break;
case self::ACTION_ADD_BLOCKING_REVIEWERS:
// This adds reviewers normally, it just also marks them blocking.
foreach ($effect->getTarget() as $phid) {
$this->addReviewerPHIDs[$phid] = true;
$this->blockingReviewerPHIDs[$phid] = true;
}
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Added blocking reviewers.'));
break;
case self::ACTION_APPLY_BUILD_PLANS:
foreach ($effect->getTarget() as $phid) {
$this->buildPlans[] = $phid;
}
$result[] = new HeraldApplyTranscript(
$effect,
true,
pht('Applied build plans.'));
break;
default:
throw new Exception("No rules to handle action '{$action}'.");
}
}
return $result;
}
}
diff --git a/src/applications/herald/controller/HeraldTestConsoleController.php b/src/applications/herald/controller/HeraldTestConsoleController.php
index 3cbfa47dc6..fb2f1cc996 100644
--- a/src/applications/herald/controller/HeraldTestConsoleController.php
+++ b/src/applications/herald/controller/HeraldTestConsoleController.php
@@ -1,128 +1,123 @@
<?php
final class HeraldTestConsoleController extends HeraldController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$request = $this->getRequest();
$object_name = trim($request->getStr('object_name'));
$e_name = true;
$errors = array();
if ($request->isFormPost()) {
if (!$object_name) {
$e_name = pht('Required');
$errors[] = pht('An object name is required.');
}
if (!$errors) {
$object = id(new PhabricatorObjectQuery())
->setViewer($user)
->withNames(array($object_name))
->executeOne();
if (!$object) {
$e_name = pht('Invalid');
$errors[] = pht('No object exists with that name.');
}
if (!$errors) {
// TODO: Let the adapters claim objects instead.
if ($object instanceof DifferentialRevision) {
$adapter = HeraldDifferentialRevisionAdapter::newLegacyAdapter(
$object,
$object->loadActiveDiff());
} else if ($object instanceof PhabricatorRepositoryCommit) {
- $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
- 'commitID = %d',
- $object->getID());
- $adapter = HeraldCommitAdapter::newLegacyAdapter(
- $object->getRepository(),
- $object,
- $data);
+ $adapter = id(new HeraldCommitAdapter())
+ ->setCommit($object);
} else if ($object instanceof ManiphestTask) {
$adapter = id(new HeraldManiphestTaskAdapter())
->setTask($object);
} else if ($object instanceof PholioMock) {
$adapter = id(new HeraldPholioMockAdapter())
->setMock($object);
} else {
throw new Exception("Can not build adapter for object!");
}
$rules = id(new HeraldRuleQuery())
->setViewer($user)
->withContentTypes(array($adapter->getAdapterContentType()))
->withDisabled(false)
->needConditionsAndActions(true)
->needAppliedToPHIDs(array($object->getPHID()))
->needValidateAuthors(true)
->execute();
$engine = id(new HeraldEngine())
->setDryRun(true);
$effects = $engine->applyRules($rules, $adapter);
$engine->applyEffects($effects, $adapter, $rules);
$xscript = $engine->getTranscript();
return id(new AphrontRedirectResponse())
->setURI('/herald/transcript/'.$xscript->getID().'/');
}
}
}
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('Form Errors'));
$error_view->setErrors($errors);
} else {
$error_view = null;
}
$text = pht(
'Enter an object to test rules for, like a Diffusion commit (e.g., '.
'rX123) or a Differential revision (e.g., D123). You will be shown '.
'the results of a dry run on the object.');
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
phutil_tag('p', array('class' => 'aphront-form-instructions'), $text))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Object Name'))
->setName('object_name')
->setError($e_name)
->setValue($object_name))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Test Rules')));
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Herald Test Console'))
->setFormError($error_view)
->setForm($form);
$crumbs = id($this->buildApplicationCrumbs())
->addTextCrumb(
pht('Transcripts'),
$this->getApplicationURI('/transcript/'))
->addTextCrumb(pht('Test Console'));
return $this->buildApplicationPage(
$box,
array(
'title' => pht('Test Console'),
'device' => true,
));
}
}
diff --git a/src/applications/herald/storage/HeraldRule.php b/src/applications/herald/storage/HeraldRule.php
index 85fd6e6c1b..cd6bfa4b9c 100644
--- a/src/applications/herald/storage/HeraldRule.php
+++ b/src/applications/herald/storage/HeraldRule.php
@@ -1,254 +1,254 @@
<?php
final class HeraldRule extends HeraldDAO
implements
PhabricatorFlaggableInterface,
PhabricatorPolicyInterface {
const TABLE_RULE_APPLIED = 'herald_ruleapplied';
protected $name;
protected $authorPHID;
protected $contentType;
protected $mustMatchAll;
protected $repetitionPolicy;
protected $ruleType;
protected $isDisabled = 0;
protected $triggerObjectPHID;
- protected $configVersion = 26;
+ protected $configVersion = 27;
// phids for which this rule has been applied
private $ruleApplied = self::ATTACHABLE;
private $validAuthor = self::ATTACHABLE;
private $author = self::ATTACHABLE;
private $conditions;
private $actions;
private $triggerObject = self::ATTACHABLE;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(HeraldPHIDTypeRule::TYPECONST);
}
public function getRuleApplied($phid) {
return $this->assertAttachedKey($this->ruleApplied, $phid);
}
public function setRuleApplied($phid, $applied) {
if ($this->ruleApplied === self::ATTACHABLE) {
$this->ruleApplied = array();
}
$this->ruleApplied[$phid] = $applied;
return $this;
}
public function loadConditions() {
if (!$this->getID()) {
return array();
}
return id(new HeraldCondition())->loadAllWhere(
'ruleID = %d',
$this->getID());
}
public function attachConditions(array $conditions) {
assert_instances_of($conditions, 'HeraldCondition');
$this->conditions = $conditions;
return $this;
}
public function getConditions() {
// TODO: validate conditions have been attached.
return $this->conditions;
}
public function loadActions() {
if (!$this->getID()) {
return array();
}
return id(new HeraldAction())->loadAllWhere(
'ruleID = %d',
$this->getID());
}
public function attachActions(array $actions) {
// TODO: validate actions have been attached.
assert_instances_of($actions, 'HeraldAction');
$this->actions = $actions;
return $this;
}
public function getActions() {
return $this->actions;
}
public function loadEdits() {
if (!$this->getID()) {
return array();
}
$edits = id(new HeraldRuleEdit())->loadAllWhere(
'ruleID = %d ORDER BY dateCreated DESC',
$this->getID());
return $edits;
}
public function logEdit($editor_phid, $action) {
id(new HeraldRuleEdit())
->setRuleID($this->getID())
->setRuleName($this->getName())
->setEditorPHID($editor_phid)
->setAction($action)
->save();
}
public function saveConditions(array $conditions) {
assert_instances_of($conditions, 'HeraldCondition');
return $this->saveChildren(
id(new HeraldCondition())->getTableName(),
$conditions);
}
public function saveActions(array $actions) {
assert_instances_of($actions, 'HeraldAction');
return $this->saveChildren(
id(new HeraldAction())->getTableName(),
$actions);
}
protected function saveChildren($table_name, array $children) {
assert_instances_of($children, 'HeraldDAO');
if (!$this->getID()) {
throw new Exception("Save rule before saving children.");
}
foreach ($children as $child) {
$child->setRuleID($this->getID());
}
// TODO:
// $this->openTransaction();
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE ruleID = %d',
$table_name,
$this->getID());
foreach ($children as $child) {
$child->save();
}
// $this->saveTransaction();
}
public function delete() {
$this->openTransaction();
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE ruleID = %d',
id(new HeraldCondition())->getTableName(),
$this->getID());
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE ruleID = %d',
id(new HeraldAction())->getTableName(),
$this->getID());
$result = parent::delete();
$this->saveTransaction();
return $result;
}
public function hasValidAuthor() {
return $this->assertAttached($this->validAuthor);
}
public function attachValidAuthor($valid) {
$this->validAuthor = $valid;
return $this;
}
public function getAuthor() {
return $this->assertAttached($this->author);
}
public function attachAuthor(PhabricatorUser $user) {
$this->author = $user;
return $this;
}
public function isGlobalRule() {
return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_GLOBAL);
}
public function isPersonalRule() {
return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
}
public function isObjectRule() {
return ($this->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_OBJECT);
}
public function attachTriggerObject($trigger_object) {
$this->triggerObject = $trigger_object;
return $this;
}
public function getTriggerObject() {
return $this->assertAttached($this->triggerObject);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
if ($this->isGlobalRule()) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return PhabricatorPolicies::POLICY_USER;
case PhabricatorPolicyCapability::CAN_EDIT:
$app = 'PhabricatorApplicationHerald';
$herald = PhabricatorApplication::getByClass($app);
$global = HeraldCapabilityManageGlobalRules::CAPABILITY;
return $herald->getPolicy($global);
}
} else if ($this->isObjectRule()) {
return $this->getTriggerObject()->getPolicy($capability);
} else {
return PhabricatorPolicies::POLICY_NOONE;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->isPersonalRule()) {
return ($viewer->getPHID() == $this->getAuthorPHID());
} else {
return false;
}
}
public function describeAutomaticCapability($capability) {
if ($this->isPersonalRule()) {
return pht("A personal rule's owner can always view and edit it.");
} else if ($this->isObjectRule()) {
return pht("Object rules inherit the policies of their objects.");
}
return null;
}
}
diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php
index 4d1d702e4f..8f4b3416d6 100644
--- a/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php
+++ b/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php
@@ -1,449 +1,447 @@
<?php
final class PhabricatorRepositoryCommitHeraldWorker
extends PhabricatorRepositoryCommitParserWorker {
public function getRequiredLeaseTime() {
// Herald rules may take a long time to process.
return 4 * 60 * 60;
}
public function parseCommit(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$result = $this->applyHeraldRules($repository, $commit);
$commit->writeImportStatusFlag(
PhabricatorRepositoryCommit::IMPORTED_HERALD);
return $result;
}
private function applyHeraldRules(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
// 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;
}
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
if (!$data) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Unable to load commit data. The data for this task is invalid '.
'or no longer exists.'));
}
- $adapter = HeraldCommitAdapter::newLegacyAdapter(
- $repository,
- $commit,
- $data);
+ $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);
if ($repository->getDetail('herald-disabled')) {
// This just means "disable email"; audits are (mostly) idempotent.
return;
}
$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.';
$files = $adapter->loadAffectedPaths();
sort($files);
$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);
$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(),
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,
PhabricatorAuditStatusConstants::CC => $ccmap,
);
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();
}
/**
* 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 = DifferentialFieldSpecification::parseCommitMessageObjectList(
$matches[1],
$include_mailables = false,
$allow_partial = true);
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(),
'summary' => $data->getSummary(),
'authorName' => $data->getAuthorName(),
'authorPHID' => $author_phid,
'committerName' => $data->getCommitDetail('committer'),
'committerPHID' => $committer_phid,
));
$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);
}
}
if ($result) {
$result = "PATCH\n\n{$result}\n";
}
return $result;
}
private function loadRawPatchText(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => PhabricatorUser::getOmnipotentUser(),
'initFromConduit' => false,
'repository' => $repository,
'commit' => $commit->getCommitIdentifier(),
));
$raw_query = DiffusionRawDiffQuery::newFromDiffusionRequest($drequest);
$raw_query->setLinesOfContext(3);
$time_key = 'metamta.diffusion.time-limit';
$byte_key = 'metamta.diffusion.byte-limit';
$time_limit = PhabricatorEnv::getEnvConfig($time_key);
$byte_limit = PhabricatorEnv::getEnvConfig($byte_key);
if ($time_limit) {
$raw_query->setTimeout($time_limit);
}
$raw_diff = $raw_query->loadRawDiff();
$size = strlen($raw_diff);
if ($byte_limit && $size > $byte_limit) {
$pretty_size = phabricator_format_bytes($size);
$pretty_limit = phabricator_format_bytes($byte_limit);
throw new Exception(
"Patch size of {$pretty_size} exceeds configured byte size limit of ".
"{$pretty_limit}.");
}
return $raw_diff;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 15:02 (3 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125762
Default Alt Text
(58 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment