Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/herald/adapter/HeraldCommitAdapter.php b/src/applications/herald/adapter/HeraldCommitAdapter.php
index 639410d753..35aafd77ca 100644
--- a/src/applications/herald/adapter/HeraldCommitAdapter.php
+++ b/src/applications/herald/adapter/HeraldCommitAdapter.php
@@ -1,340 +1,341 @@
<?php
final class HeraldCommitAdapter extends HeraldAdapter {
const FIELD_NEED_AUDIT_FOR_PACKAGE = 'need-audit-for-package';
const FIELD_DIFFERENTIAL_REVISION = 'differential-revision';
const FIELD_DIFFERENTIAL_REVIEWERS = 'differential-reviewers';
const FIELD_DIFFERENTIAL_CCS = 'differential-ccs';
protected $diff;
protected $revision;
protected $repository;
protected $commit;
protected $commitData;
protected $emailPHIDs = array();
protected $auditMap = array();
protected $affectedPaths;
protected $affectedRevision;
protected $affectedPackages;
protected $auditNeededPackages;
public function isEnabled() {
$app = 'PhabricatorApplicationDiffusion';
return PhabricatorApplication::isClassInstalled($app);
}
public function getAdapterContentType() {
return 'commit';
}
public function getAdapterContentName() {
return pht('Commits');
}
public function getFieldNameMap() {
return array(
self::FIELD_NEED_AUDIT_FOR_PACKAGE =>
pht('Affected packages that need audit'),
self::FIELD_DIFFERENTIAL_REVISION => pht('Differential revision'),
self::FIELD_DIFFERENTIAL_REVIEWERS => pht('Differential reviewers'),
self::FIELD_DIFFERENTIAL_CCS => pht('Differential CCs'),
) + parent::getFieldNameMap();
}
public function getFields() {
return array(
self::FIELD_BODY,
self::FIELD_AUTHOR,
self::FIELD_REVIEWER,
self::FIELD_REPOSITORY,
self::FIELD_DIFF_FILE,
self::FIELD_DIFF_CONTENT,
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_REVIEWERS,
self::FIELD_DIFFERENTIAL_CCS,
);
}
public function getConditionsForField($field) {
switch ($field) {
case self::FIELD_DIFFERENTIAL_REVIEWERS:
case self::FIELD_DIFFERENTIAL_CCS:
return array(
self::CONDITION_INCLUDE_ALL,
self::CONDITION_INCLUDE_ANY,
self::CONDITION_INCLUDE_NONE,
);
case self::FIELD_DIFFERENTIAL_REVISION:
return array(
self::CONDITION_EXISTS,
self::CONDITION_NOT_EXISTS,
);
case self::FIELD_NEED_AUDIT_FOR_PACKAGE:
return array(
self::CONDITION_INCLUDE_ANY,
self::CONDITION_INCLUDE_NONE,
);
}
return parent::getConditionsForField($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_NOTHING,
);
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
return array(
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
+ self::ACTION_FLAG,
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();
$object->repository = $repository;
$object->commit = $commit;
$object->commitData = $commit_data;
return $object;
}
public function getPHID() {
return $this->commit->getPHID();
}
public function getEmailPHIDs() {
return array_keys($this->emailPHIDs);
}
public function getAuditMap() {
return $this->auditMap;
}
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) {
$revision = id(new DifferentialRevision())->load($revision_id);
if ($revision) {
$revision->loadRelationships();
$this->affectedRevision = $revision;
}
}
}
return $this->affectedRevision;
}
private function loadCommitDiff() {
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => PhabricatorUser::getOmnipotentUser(),
'repository' => $this->repository,
'commit' => $this->commit->getCommitIdentifier(),
));
$raw = DiffusionQuery::callConduitWithDiffusionRequest(
PhabricatorUser::getOmnipotentUser(),
$drequest,
'diffusion.rawdiffquery',
array(
'commit' => $this->commit->getCommitIdentifier(),
'timeout' => 60 * 60 * 15,
'linesOfContext' => 0));
$parser = new ArcanistDiffParser();
$changes = $parser->parseDiff($raw);
$diff = DifferentialDiff::newFromRawChanges($changes);
return $diff;
}
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_REVIEWER:
return $data->getCommitDetail('reviewerPHID');
case self::FIELD_DIFF_FILE:
return $this->loadAffectedPaths();
case self::FIELD_REPOSITORY:
return $this->repository->getPHID();
case self::FIELD_DIFF_CONTENT:
try {
$diff = $this->loadCommitDiff();
} catch (Exception $ex) {
return array(
'<<< Failed to load diff, this may mean the change was '.
'unimaginably enormous. >>>');
}
$dict = array();
$lines = array();
$changes = $diff->getChangesets();
foreach ($changes as $change) {
$lines = array();
foreach ($change->getHunks() as $hunk) {
$lines[] = $hunk->makeChanges();
}
$dict[$change->getFilename()] = implode("\n", $lines);
}
return $dict;
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_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();
}
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_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_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 f59271db0f..ce3287dd11 100644
--- a/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php
+++ b/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php
@@ -1,356 +1,357 @@
<?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 $repository;
protected $affectedPackages;
protected $changesets;
public function isEnabled() {
$app = 'PhabricatorApplicationDifferential';
return PhabricatorApplication::isClassInstalled($app);
}
public function getAdapterContentType() {
return 'differential';
}
public function getAdapterContentName() {
return pht('Differential Revisions');
}
public function getFields() {
return array(
self::FIELD_TITLE,
self::FIELD_BODY,
self::FIELD_AUTHOR,
self::FIELD_REVIEWERS,
self::FIELD_CC,
self::FIELD_REPOSITORY,
self::FIELD_DIFF_FILE,
self::FIELD_DIFF_CONTENT,
self::FIELD_RULE,
self::FIELD_AFFECTED_PACKAGE,
self::FIELD_AFFECTED_PACKAGE_OWNER,
);
}
public function getRepetitionOptions() {
return array(
HeraldRepetitionPolicyConfig::EVERY,
HeraldRepetitionPolicyConfig::FIRST,
);
}
public static function newLegacyAdapter(
DifferentialRevision $revision,
DifferentialDiff $diff) {
$object = new HeraldDifferentialRevisionAdapter();
$revision->loadRelationships();
$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 getPHID() {
return $this->revision->getPHID();
}
public function getHeraldName() {
return $this->revision->getTitle();
}
public function loadRepository() {
if ($this->repository === null) {
$diff = $this->diff;
$repository = false;
if ($diff->getRepositoryUUID()) {
$repository = id(new PhabricatorRepository())->loadOneWhere(
'uuid = %s',
$diff->getRepositoryUUID());
}
if (!$repository && $diff->getArcanistProjectPHID()) {
$project = id(new PhabricatorRepositoryArcanistProject())->loadOneWhere(
'phid = %s',
$diff->getArcanistProjectPHID());
if ($project && $project->getRepositoryID()) {
$repository = id(new PhabricatorRepository())->load(
$project->getRepositoryID());
}
}
$this->repository = $repository;
}
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;
}
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_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_DIFF_CONTENT:
return $this->loadContentDictionary();
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_NOTHING,
);
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
return array(
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
+ self::ACTION_FLAG,
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;
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 26d682624b..bccdf2769d 100644
--- a/src/applications/herald/controller/HeraldTestConsoleController.php
+++ b/src/applications/herald/controller/HeraldTestConsoleController.php
@@ -1,143 +1,147 @@
<?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) {
$matches = null;
$object = null;
if (preg_match('/^D(\d+)$/', $object_name, $matches)) {
$object = id(new DifferentialRevision())->load($matches[1]);
if (!$object) {
$e_name = pht('Invalid');
$errors[] = pht('No Differential Revision with that ID exists.');
}
} else if (preg_match('/^r([A-Z]+)(\w+)$/', $object_name, $matches)) {
$repo = id(new PhabricatorRepository())->loadOneWhere(
'callsign = %s',
$matches[1]);
if (!$repo) {
$e_name = pht('Invalid');
$errors[] = pht('There is no repository with the callsign: %s.',
$matches[1]);
}
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
'repositoryID = %d AND commitIdentifier = %s',
$repo->getID(),
$matches[2]);
if (!$commit) {
$e_name = pht('Invalid');
$errors[] = pht('There is no commit with that identifier.');
}
$object = $commit;
} else {
$e_name = pht('Invalid');
$errors[] = pht('This object name is not recognized.');
}
if (!$errors) {
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(
$repo,
$object,
$data);
} else {
throw new Exception("Can not build adapter for object!");
}
- $rules = HeraldRule::loadAllByContentTypeWithFullData(
- $adapter->getAdapterContentType(),
- $object->getPHID());
+ $rules = id(new HeraldRuleQuery())
+ ->setViewer($user)
+ ->withContentTypes(array($adapter->getAdapterContentType()))
+ ->needConditionsAndActions(true)
+ ->needAppliedToPHIDs(array($object->getPHID()))
+ ->needValidateAuthors(true)
+ ->execute();
$engine = new HeraldEngine();
$effects = $engine->applyRules($rules, $adapter);
$dry_run = new HeraldDryRunAdapter();
$engine->applyEffects($effects, $dry_run, $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(hsprintf(
'<p class="aphront-form-instructions">%s</p>', $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')));
$nav = $this->buildSideNavView();
$nav->selectFilter('test');
$nav->appendChild(
array(
$error_view,
$form,
));
$crumbs = id($this->buildApplicationCrumbs())
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Transcripts'))
->setHref($this->getApplicationURI('/transcript/')))
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Test Console')));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Test Console'),
'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php
index b92371ccff..895ef95cb9 100644
--- a/src/applications/herald/engine/HeraldEngine.php
+++ b/src/applications/herald/engine/HeraldEngine.php
@@ -1,340 +1,343 @@
<?php
final class HeraldEngine {
protected $rules = array();
protected $results = array();
protected $stack = array();
protected $activeRule = null;
protected $fieldCache = array();
protected $object = null;
public function getRule($id) {
return idx($this->rules, $id);
}
- public static function loadAndApplyRules(HeraldAdapter $object) {
- $content_type = $object->getAdapterContentType();
- $rules = HeraldRule::loadAllByContentTypeWithFullData(
- $content_type,
- $object->getPHID());
+ public static function loadAndApplyRules(HeraldAdapter $adapter) {
+ $rules = id(new HeraldRuleQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withContentTypes(array($adapter->getAdapterContentType()))
+ ->needConditionsAndActions(true)
+ ->needAppliedToPHIDs(array($adapter->getPHID()))
+ ->needValidateAuthors(true)
+ ->execute();
$engine = new HeraldEngine();
- $effects = $engine->applyRules($rules, $object);
- $engine->applyEffects($effects, $object, $rules);
+ $effects = $engine->applyRules($rules, $adapter);
+ $engine->applyEffects($effects, $adapter, $rules);
return $engine->getTranscript();
}
public function applyRules(array $rules, HeraldAdapter $object) {
assert_instances_of($rules, 'HeraldRule');
$t_start = microtime(true);
$rules = mpull($rules, null, 'getID');
$this->transcript = new HeraldTranscript();
$this->transcript->setObjectPHID((string)$object->getPHID());
$this->fieldCache = array();
$this->results = array();
$this->rules = $rules;
$this->object = $object;
$effects = array();
foreach ($rules as $id => $rule) {
$this->stack = array();
try {
if (($rule->getRepetitionPolicy() ==
HeraldRepetitionPolicyConfig::FIRST) &&
$rule->getRuleApplied($object->getPHID())) {
// This rule is only supposed to be applied a single time, and it's
// aleady been applied, so this is an automatic failure.
$xscript = id(new HeraldRuleTranscript())
->setRuleID($id)
->setResult(false)
->setRuleName($rule->getName())
->setRuleOwner($rule->getAuthorPHID())
->setReason(
"This rule is only supposed to be repeated a single time, ".
"and it has already been applied.");
$this->transcript->addRuleTranscript($xscript);
$rule_matches = false;
} else {
$rule_matches = $this->doesRuleMatch($rule, $object);
}
} catch (HeraldRecursiveConditionsException $ex) {
$names = array();
foreach ($this->stack as $rule_id => $ignored) {
$names[] = '"'.$rules[$rule_id]->getName().'"';
}
$names = implode(', ', $names);
foreach ($this->stack as $rule_id => $ignored) {
$xscript = new HeraldRuleTranscript();
$xscript->setRuleID($rule_id);
$xscript->setResult(false);
$xscript->setReason(
"Rules {$names} are recursively dependent upon one another! ".
"Don't do this! You have formed an unresolvable cycle in the ".
"dependency graph!");
$xscript->setRuleName($rules[$rule_id]->getName());
$xscript->setRuleOwner($rules[$rule_id]->getAuthorPHID());
$this->transcript->addRuleTranscript($xscript);
}
$rule_matches = false;
}
$this->results[$id] = $rule_matches;
if ($rule_matches) {
foreach ($this->getRuleEffects($rule, $object) as $effect) {
$effects[] = $effect;
}
}
}
$object_transcript = new HeraldObjectTranscript();
$object_transcript->setPHID($object->getPHID());
$object_transcript->setName($object->getHeraldName());
$object_transcript->setType($object->getAdapterContentType());
$object_transcript->setFields($this->fieldCache);
$this->transcript->setObjectTranscript($object_transcript);
$t_end = microtime(true);
$this->transcript->setDuration($t_end - $t_start);
return $effects;
}
public function applyEffects(
array $effects,
HeraldAdapter $object,
array $rules) {
assert_instances_of($effects, 'HeraldEffect');
assert_instances_of($rules, 'HeraldRule');
$this->transcript->setDryRun((int)($object instanceof HeraldDryRunAdapter));
$xscripts = $object->applyHeraldEffects($effects);
foreach ($xscripts as $apply_xscript) {
if (!($apply_xscript instanceof HeraldApplyTranscript)) {
throw new Exception(
"Heraldable must return HeraldApplyTranscripts from ".
"applyHeraldEffect().");
}
$this->transcript->addApplyTranscript($apply_xscript);
}
if (!$this->transcript->getDryRun()) {
$rules = mpull($rules, null, 'getID');
$applied_ids = array();
$first_policy = HeraldRepetitionPolicyConfig::toInt(
HeraldRepetitionPolicyConfig::FIRST);
// Mark all the rules that have had their effects applied as having been
// executed for the current object.
$rule_ids = mpull($xscripts, 'getRuleID');
foreach ($rule_ids as $rule_id) {
if (!$rule_id) {
// Some apply transcripts are purely informational and not associated
// with a rule, e.g. carryover emails from earlier revisions.
continue;
}
$rule = idx($rules, $rule_id);
if (!$rule) {
continue;
}
if ($rule->getRepetitionPolicy() == $first_policy) {
$applied_ids[] = $rule_id;
}
}
if ($applied_ids) {
$conn_w = id(new HeraldRule())->establishConnection('w');
$sql = array();
foreach ($applied_ids as $id) {
$sql[] = qsprintf(
$conn_w,
'(%s, %d)',
$object->getPHID(),
$id);
}
queryfx(
$conn_w,
'INSERT IGNORE INTO %T (phid, ruleID) VALUES %Q',
HeraldRule::TABLE_RULE_APPLIED,
implode(', ', $sql));
}
}
}
public function getTranscript() {
$this->transcript->save();
return $this->transcript;
}
public function doesRuleMatch(
HeraldRule $rule,
HeraldAdapter $object) {
$id = $rule->getID();
if (isset($this->results[$id])) {
// If we've already evaluated this rule because another rule depends
// on it, we don't need to reevaluate it.
return $this->results[$id];
}
if (isset($this->stack[$id])) {
// We've recursed, fail all of the rules on the stack. This happens when
// there's a dependency cycle with "Rule conditions match for rule ..."
// conditions.
foreach ($this->stack as $rule_id => $ignored) {
$this->results[$rule_id] = false;
}
throw new HeraldRecursiveConditionsException();
}
$this->stack[$id] = true;
$all = $rule->getMustMatchAll();
$conditions = $rule->getConditions();
$result = null;
$local_version = id(new HeraldRule())->getConfigVersion();
if ($rule->getConfigVersion() > $local_version) {
$reason = "Rule could not be processed, it was created with a newer ".
"version of Herald.";
$result = false;
} else if (!$conditions) {
$reason = "Rule failed automatically because it has no conditions.";
$result = false;
- } else if ($rule->hasInvalidOwner()) {
+ } else if (!$rule->hasValidAuthor()) {
$reason = "Rule failed automatically because its owner is invalid ".
"or disabled.";
$result = false;
} else {
foreach ($conditions as $condition) {
$match = $this->doesConditionMatch($rule, $condition, $object);
if (!$all && $match) {
$reason = "Any condition matched.";
$result = true;
break;
}
if ($all && !$match) {
$reason = "Not all conditions matched.";
$result = false;
break;
}
}
if ($result === null) {
if ($all) {
$reason = "All conditions matched.";
$result = true;
} else {
$reason = "No conditions matched.";
$result = false;
}
}
}
$rule_transcript = new HeraldRuleTranscript();
$rule_transcript->setRuleID($rule->getID());
$rule_transcript->setResult($result);
$rule_transcript->setReason($reason);
$rule_transcript->setRuleName($rule->getName());
$rule_transcript->setRuleOwner($rule->getAuthorPHID());
$this->transcript->addRuleTranscript($rule_transcript);
return $result;
}
protected function doesConditionMatch(
HeraldRule $rule,
HeraldCondition $condition,
HeraldAdapter $object) {
$object_value = $this->getConditionObjectValue($condition, $object);
$test_value = $condition->getValue();
$cond = $condition->getFieldCondition();
$transcript = new HeraldConditionTranscript();
$transcript->setRuleID($rule->getID());
$transcript->setConditionID($condition->getID());
$transcript->setFieldName($condition->getFieldName());
$transcript->setCondition($cond);
$transcript->setTestValue($test_value);
try {
$result = $object->doesConditionMatch(
$this,
$rule,
$condition,
$object_value);
} catch (HeraldInvalidConditionException $ex) {
$result = false;
$transcript->setNote($ex->getMessage());
}
$transcript->setResult($result);
$this->transcript->addConditionTranscript($transcript);
return $result;
}
protected function getConditionObjectValue(
HeraldCondition $condition,
HeraldAdapter $object) {
$field = $condition->getFieldName();
return $this->getObjectFieldValue($field);
}
public function getObjectFieldValue($field) {
if (isset($this->fieldCache[$field])) {
return $this->fieldCache[$field];
}
$result = $this->object->getHeraldField($field);
$this->fieldCache[$field] = $result;
return $result;
}
protected function getRuleEffects(
HeraldRule $rule,
HeraldAdapter $object) {
$effects = array();
foreach ($rule->getActions() as $action) {
$effect = new HeraldEffect();
$effect->setObjectPHID($object->getPHID());
$effect->setAction($action->getAction());
$effect->setTarget($action->getTarget());
$effect->setRuleID($rule->getID());
$name = $rule->getName();
$id = $rule->getID();
$effect->setReason(
'Conditions were met for Herald rule "'.$name.'" (#'.$id.').');
$effects[] = $effect;
}
return $effects;
}
}
diff --git a/src/applications/herald/query/HeraldRuleQuery.php b/src/applications/herald/query/HeraldRuleQuery.php
index df0e31d045..7b23231e6f 100644
--- a/src/applications/herald/query/HeraldRuleQuery.php
+++ b/src/applications/herald/query/HeraldRuleQuery.php
@@ -1,95 +1,207 @@
<?php
final class HeraldRuleQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $ruleTypes;
private $contentTypes;
+ private $needConditionsAndActions;
+ private $needAppliedToPHIDs;
+ private $needValidateAuthors;
+
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $author_phids) {
$this->authorPHIDs = $author_phids;
return $this;
}
public function withRuleTypes(array $types) {
$this->ruleTypes = $types;
return $this;
}
public function withContentTypes(array $types) {
$this->contentTypes = $types;
return $this;
}
+ public function withExecutableRules($executable) {
+ $this->executable = $executable;
+ return $this;
+ }
+
+ public function needConditionsAndActions($need) {
+ $this->needConditionsAndActions = $need;
+ return $this;
+ }
+
+ public function needAppliedToPHIDs(array $phids) {
+ $this->needAppliedToPHIDs = $phids;
+ return $this;
+ }
+
+ public function needValidateAuthors($need) {
+ $this->needValidateAuthors = $need;
+ return $this;
+ }
+
public function loadPage() {
$table = new HeraldRule();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT rule.* FROM %T rule %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
+ public function willFilterPage(array $rules) {
+ $rule_ids = mpull($rules, 'getID');
+
+ if ($this->needValidateAuthors) {
+ $this->validateRuleAuthors($rules);
+ }
+
+ if ($this->needConditionsAndActions) {
+ $conditions = id(new HeraldCondition())->loadAllWhere(
+ 'ruleID IN (%Ld)',
+ $rule_ids);
+ $conditions = mgroup($conditions, 'getRuleID');
+
+ $actions = id(new HeraldAction())->loadAllWhere(
+ 'ruleID IN (%Ld)',
+ $rule_ids);
+ $actions = mgroup($actions, 'getRuleID');
+
+ foreach ($rules as $rule) {
+ $rule->attachActions(idx($actions, $rule->getID(), array()));
+ $rule->attachConditions(idx($conditions, $rule->getID(), array()));
+ }
+ }
+
+ if ($this->needAppliedToPHIDs) {
+ $conn_r = id(new HeraldRule())->establishConnection('r');
+ $applied = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T WHERE ruleID IN (%Ld) AND phid IN (%Ls)',
+ HeraldRule::TABLE_RULE_APPLIED,
+ $rule_ids,
+ $this->needAppliedToPHIDs);
+
+ $map = array();
+ foreach ($applied as $row) {
+ $map[$row['ruleID']][$row['phid']] = true;
+ }
+
+ foreach ($rules as $rule) {
+ foreach ($this->needAppliedToPHIDs as $phid) {
+ $rule->setRuleApplied(
+ $phid,
+ isset($map[$rule->getID()][$phid]));
+ }
+ }
+ }
+
+ return $rules;
+ }
+
private function buildWhereClause($conn_r) {
$where = array();
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'rule.id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'rule.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs) {
$where[] = qsprintf(
$conn_r,
'rule.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->ruleTypes) {
$where[] = qsprintf(
$conn_r,
'rule.ruleType IN (%Ls)',
$this->ruleTypes);
}
if ($this->contentTypes) {
$where[] = qsprintf(
$conn_r,
'rule.contentType IN (%Ls)',
$this->contentTypes);
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
}
+ private function validateRuleAuthors(array $rules) {
+
+ // "Global" rules always have valid authors.
+ foreach ($rules as $key => $rule) {
+ if ($rule->isGlobalRule()) {
+ $rule->attachValidAuthor(true);
+ unset($rules[$key]);
+ continue;
+ }
+ }
+
+ if (!$rules) {
+ return;
+ }
+
+ // For personal rules, the author needs to exist and not be disabled.
+ $user_phids = mpull($rules, 'getAuthorPHID');
+ $users = id(new PhabricatorPeopleQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($user_phids)
+ ->execute();
+ $users = mpull($users, null, 'getPHID');
+
+ foreach ($rules as $key => $rule) {
+ $author_phid = $rule->getAuthorPHID();
+ if (empty($users[$author_phid])) {
+ $rule->attachValidAuthor(false);
+ continue;
+ }
+ if ($users[$author_phid]->getIsDisabled()) {
+ $rule->attachValidAuthor(false);
+ continue;
+ }
+
+ $rule->attachValidAuthor(true);
+ }
+ }
+
}
diff --git a/src/applications/herald/storage/HeraldRule.php b/src/applications/herald/storage/HeraldRule.php
index 8b326b20cc..b5dc2f62cd 100644
--- a/src/applications/herald/storage/HeraldRule.php
+++ b/src/applications/herald/storage/HeraldRule.php
@@ -1,271 +1,205 @@
<?php
final class HeraldRule extends HeraldDAO
implements PhabricatorPolicyInterface {
const TABLE_RULE_APPLIED = 'herald_ruleapplied';
protected $name;
protected $authorPHID;
protected $contentType;
protected $mustMatchAll;
protected $repetitionPolicy;
protected $ruleType;
protected $configVersion = 9;
private $ruleApplied = array(); // phids for which this rule has been applied
- private $invalidOwner = false;
+ private $validAuthor = self::ATTACHABLE;
private $conditions;
private $actions;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(HeraldPHIDTypeRule::TYPECONST);
}
- public static function loadAllByContentTypeWithFullData(
- $content_type,
- $object_phid) {
-
- $rules = id(new HeraldRule())->loadAllWhere(
- 'contentType = %s',
- $content_type);
-
- if (!$rules) {
- return array();
- }
-
- self::flagDisabledUserRules($rules);
-
- $rule_ids = mpull($rules, 'getID');
-
- $conditions = id(new HeraldCondition())->loadAllWhere(
- 'ruleID in (%Ld)',
- $rule_ids);
-
- $actions = id(new HeraldAction())->loadAllWhere(
- 'ruleID in (%Ld)',
- $rule_ids);
-
- $applied = queryfx_all(
- id(new HeraldRule())->establishConnection('r'),
- 'SELECT * FROM %T WHERE phid = %s',
- self::TABLE_RULE_APPLIED,
- $object_phid);
- $applied = ipull($applied, null, 'ruleID');
-
- $conditions = mgroup($conditions, 'getRuleID');
- $actions = mgroup($actions, 'getRuleID');
- $applied = igroup($applied, 'ruleID');
-
- foreach ($rules as $rule) {
- $rule->setRuleApplied($object_phid, isset($applied[$rule->getID()]));
-
- $rule->attachConditions(idx($conditions, $rule->getID(), array()));
- $rule->attachActions(idx($actions, $rule->getID(), array()));
- }
-
- return $rules;
- }
-
- private static function flagDisabledUserRules(array $rules) {
- assert_instances_of($rules, 'HeraldRule');
-
- $users = array();
- foreach ($rules as $rule) {
- if ($rule->getRuleType() != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
- continue;
- }
- $users[$rule->getAuthorPHID()] = true;
- }
-
- $handles = id(new PhabricatorObjectHandleData(array_keys($users)))
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->loadHandles();
-
- foreach ($rules as $key => $rule) {
- if ($rule->getRuleType() != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
- continue;
- }
- $handle = $handles[$rule->getAuthorPHID()];
- if (!$handle->isComplete() || $handle->isDisabled()) {
- $rule->invalidOwner = true;
- }
- }
- }
-
public function getRuleApplied($phid) {
if (idx($this->ruleApplied, $phid) === null) {
throw new Exception("Call setRuleApplied() before getRuleApplied()!");
}
return $this->ruleApplied[$phid];
}
public function setRuleApplied($phid, $applied) {
$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() {
// TODO:
// $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());
parent::delete();
// $this->saveTransaction();
}
- public function hasInvalidOwner() {
- return $this->invalidOwner;
+ public function hasValidAuthor() {
+ return $this->assertAttached($this->validAuthor);
+ }
+
+ public function attachValidAuthor($valid) {
+ $this->validAuthor = $valid;
+ return $this;
}
public function isGlobalRule() {
return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_GLOBAL);
}
public function isPersonalRule() {
return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
if ($this->isGlobalRule()) {
return PhabricatorPolicies::POLICY_USER;
} else {
return PhabricatorPolicies::POLICY_NOONE;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->isPersonalRule()) {
return ($viewer->getPHID() == $this->getAuthorPHID());
} else {
return false;
}
}
}
diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php
index 9e74c6a820..9ea559a369 100644
--- a/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php
+++ b/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php
@@ -1,398 +1,403 @@
<?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) {
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
if (!$data) {
// TODO: Permanent failure.
return;
}
- $rules = HeraldRule::loadAllByContentTypeWithFullData(
- id(new HeraldCommitAdapter())->getAdapterContentType(),
- $commit->getPHID());
-
$adapter = HeraldCommitAdapter::newLegacyAdapter(
$repository,
$commit,
$data);
+
+ $rules = id(new HeraldRuleQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withContentTypes(array($adapter->getAdapterContentType()))
+ ->needConditionsAndActions(true)
+ ->needAppliedToPHIDs(array($adapter->getPHID()))
+ ->needValidateAuthors(true)
+ ->execute();
+
$engine = new HeraldEngine();
$effects = $engine->applyRules($rules, $adapter);
$engine->applyEffects($effects, $adapter, $rules);
$audit_phids = $adapter->getAuditMap();
if ($audit_phids) {
$this->createAudits($commit, $audit_phids, $rules);
}
$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,
$herald_targets));
if (!$email_phids) {
return;
}
$xscript = $engine->getTranscript();
$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 PhabricatorObjectHandleData($phids))
->setViewer(PhabricatorUser::getOmnipotentUser())
->loadHandles();
$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();
$manage_uri = '/herald/view/commits/';
$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($manage_uri, $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 PhabricatorObjectHandleData($email_phids))
->setViewer(PhabricatorUser::getOmnipotentUser())
->loadHandles(),
array());
foreach ($mails as $mail) {
$mail->saveAndSend();
}
}
private function createAudits(
PhabricatorRepositoryCommit $commit,
array $map,
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');
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();
}
$reasons[] = 'Herald Rule #'.$id.' "'.$rule_name.'" Triggered Audit';
}
$request = new PhabricatorRepositoryAuditRequest();
$request->setCommitPHID($commit->getPHID());
$request->setAuditorPHID($phid);
$request->setAuditStatus(PhabricatorAuditStatusConstants::AUDIT_REQUIRED);
$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

Mime Type
text/x-diff
Expires
Tue, Mar 17, 1:53 AM (4 h, 33 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
964190
Default Alt Text
(62 KB)

Event Timeline