Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F91211
D7782.diff
All Users
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
D7782.diff
View Options
diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php
--- a/src/__celerity_resource_map__.php
+++ b/src/__celerity_resource_map__.php
@@ -1211,7 +1211,7 @@
),
'herald-rule-editor' =>
array(
- 'uri' => '/res/928275b4/rsrc/js/application/herald/HeraldRuleEditor.js',
+ 'uri' => '/res/92c05b75/rsrc/js/application/herald/HeraldRuleEditor.js',
'type' => 'js',
'requires' =>
array(
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -756,6 +756,7 @@
'HeraldObjectTranscript' => 'applications/herald/storage/transcript/HeraldObjectTranscript.php',
'HeraldPHIDTypeRule' => 'applications/herald/phid/HeraldPHIDTypeRule.php',
'HeraldPholioMockAdapter' => 'applications/herald/adapter/HeraldPholioMockAdapter.php',
+ 'HeraldPreCommitRefAdapter' => 'applications/diffusion/herald/HeraldPreCommitRefAdapter.php',
'HeraldRecursiveConditionsException' => 'applications/herald/engine/exception/HeraldRecursiveConditionsException.php',
'HeraldRepetitionPolicyConfig' => 'applications/herald/config/HeraldRepetitionPolicyConfig.php',
'HeraldRule' => 'applications/herald/storage/HeraldRule.php',
@@ -3177,6 +3178,7 @@
'HeraldNewController' => 'HeraldController',
'HeraldPHIDTypeRule' => 'PhabricatorPHIDType',
'HeraldPholioMockAdapter' => 'HeraldAdapter',
+ 'HeraldPreCommitRefAdapter' => 'HeraldAdapter',
'HeraldRecursiveConditionsException' => 'Exception',
'HeraldRule' =>
array(
diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php
--- a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php
+++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php
@@ -26,6 +26,8 @@
private $transactionKey;
private $mercurialHook;
+ private $heraldViewerProjects;
+
/* -( Config )------------------------------------------------------------- */
@@ -130,7 +132,7 @@
throw $ex;
}
- // TODO: Fire ref herald rules.
+ $this->applyHeraldRefRules($ref_updates);
$content_updates = $this->findContentUpdates($ref_updates);
$all_updates = array_merge($all_updates, $content_updates);
@@ -221,6 +223,84 @@
}
+/* -( Herald )------------------------------------------------------------- */
+
+
+ private function applyHeraldRefRules(array $ref_updates) {
+ if (!$ref_updates) {
+ return;
+ }
+
+ $engine = new HeraldEngine();
+ $rules = null;
+ $blocking_effect = null;
+ foreach ($ref_updates as $ref_update) {
+ $adapter = id(new HeraldPreCommitRefAdapter())
+ ->setPushLog($ref_update)
+ ->setHookEngine($this);
+
+ if ($rules === null) {
+ $rules = $engine->loadRulesForAdapter($adapter);
+ }
+
+ $effects = $engine->applyRules($rules, $adapter);
+ $engine->applyEffects($effects, $adapter, $rules);
+ $xscript = $engine->getTranscript();
+
+ if ($blocking_effect === null) {
+ foreach ($effects as $effect) {
+ if ($effect->getAction() == HeraldAdapter::ACTION_BLOCK) {
+ $blocking_effect = $effect;
+ break;
+ }
+ }
+ }
+ }
+
+ if ($blocking_effect) {
+ foreach ($ref_updates as $ref_update) {
+ $ref_update->setRejectCode(PhabricatorRepositoryPushLog::REJECT_HERALD);
+ $ref_update->setRejectDetails($blocking_effect->getRulePHID());
+ }
+
+ $message = $blocking_effect->getTarget();
+ if (!strlen($message)) {
+ $message = pht('(None.)');
+ }
+
+ $rules = mpull($rules, null, 'getID');
+ $rule = idx($rules, $effect->getRuleID());
+ if ($rule && strlen($rule->getName())) {
+ $rule_name = $rule->getName();
+ } else {
+ $rule_name = pht('Unnamed Herald Rule');
+ }
+
+ throw new DiffusionCommitHookRejectException(
+ pht(
+ "This commit was rejected by Herald pre-commit rule %s.\n".
+ "Rule: %s\n".
+ "Reason: %s",
+ 'H'.$blocking_effect->getRuleID(),
+ $rule_name,
+ $message));
+ }
+ }
+
+ public function loadViewerProjectPHIDsForHerald() {
+ // This just caches the viewer's projects so we don't need to load them
+ // over and over again when applying Herald rules.
+ if ($this->heraldViewerProjects === null) {
+ $this->heraldViewerProjects = id(new PhabricatorProjectQuery())
+ ->setViewer($this->getViewer())
+ ->withMemberPHIDs(array($this->getViewer()->getPHID()))
+ ->execute();
+ }
+
+ return mpull($this->heraldViewerProjects, 'getPHID');
+ }
+
+
/* -( Git )---------------------------------------------------------------- */
@@ -246,8 +326,10 @@
if (preg_match('(^refs/heads/)', $ref_raw)) {
$ref_type = PhabricatorRepositoryPushLog::REFTYPE_BRANCH;
+ $ref_raw = substr($ref_raw, strlen('refs/heads/'));
} else if (preg_match('(^refs/tags/)', $ref_raw)) {
$ref_type = PhabricatorRepositoryPushLog::REFTYPE_TAG;
+ $ref_raw = substr($ref_raw, strlen('refs/tags/'));
} else {
throw new Exception(
pht(
@@ -764,7 +846,12 @@
// code. This indicates a broken hook, and covers the case where we
// encounter some unexpected exception and consequently reject the changes.
+ // NOTE: We generate PHIDs up front so the Herald transcripts can pick them
+ // up.
+ $phid = id(new PhabricatorRepositoryPushLog())->generatePHID();
+
return PhabricatorRepositoryPushLog::initializeNewLog($this->getViewer())
+ ->setPHID($phid)
->attachRepository($this->getRepository())
->setRepositoryPHID($this->getRepository()->getPHID())
->setEpoch(time())
diff --git a/src/applications/diffusion/herald/HeraldPreCommitRefAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitRefAdapter.php
new file mode 100644
--- /dev/null
+++ b/src/applications/diffusion/herald/HeraldPreCommitRefAdapter.php
@@ -0,0 +1,163 @@
+<?php
+
+final class HeraldPreCommitRefAdapter extends HeraldAdapter {
+
+ private $log;
+ private $hookEngine;
+
+ const FIELD_REF_TYPE = 'ref-type';
+ const FIELD_REF_NAME = 'ref-name';
+ const FIELD_REF_CHANGE = 'ref-change';
+
+ const VALUE_REF_TYPE = 'value-ref-type';
+ const VALUE_REF_CHANGE = 'value-ref-change';
+
+ public function setPushLog(PhabricatorRepositoryPushLog $log) {
+ $this->log = $log;
+ return $this;
+ }
+
+ public function setHookEngine(DiffusionCommitHookEngine $engine) {
+ $this->hookEngine = $engine;
+ return $this;
+ }
+
+ public function getAdapterApplicationClass() {
+ return 'PhabricatorApplicationDiffusion';
+ }
+
+ public function getObject() {
+ return $this->log;
+ }
+
+ public function getAdapterContentName() {
+ return pht('Commit Hook: Branches/Tags/Bookmarks');
+ }
+
+ public function getFieldNameMap() {
+ return array(
+ self::FIELD_REF_TYPE => pht('Ref type'),
+ self::FIELD_REF_NAME => pht('Ref name'),
+ self::FIELD_REF_CHANGE => pht('Ref change type'),
+ ) + parent::getFieldNameMap();
+ }
+
+ public function getFields() {
+ return array_merge(
+ array(
+ self::FIELD_REF_TYPE,
+ self::FIELD_REF_NAME,
+ self::FIELD_REF_CHANGE,
+ self::FIELD_REPOSITORY,
+ self::FIELD_PUSHER,
+ self::FIELD_PUSHER_PROJECTS,
+ self::FIELD_RULE,
+ ),
+ parent::getFields());
+ }
+
+ public function getConditionsForField($field) {
+ switch ($field) {
+ case self::FIELD_REF_NAME:
+ return array(
+ self::CONDITION_IS,
+ self::CONDITION_IS_NOT,
+ self::CONDITION_CONTAINS,
+ self::CONDITION_REGEXP,
+ );
+ case self::FIELD_REF_TYPE:
+ return array(
+ self::CONDITION_IS,
+ self::CONDITION_IS_NOT,
+ );
+ case self::FIELD_REF_CHANGE:
+ return array(
+ self::CONDITION_HAS_BIT,
+ self::CONDITION_NOT_BIT,
+ );
+ }
+ return parent::getConditionsForField($field);
+ }
+
+ public function getActions($rule_type) {
+ switch ($rule_type) {
+ case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
+ return array(
+ self::ACTION_BLOCK,
+ self::ACTION_NOTHING
+ );
+ case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
+ return array(
+ self::ACTION_NOTHING,
+ );
+ }
+ }
+
+ public function getValueTypeForFieldAndCondition($field, $condition) {
+ switch ($field) {
+ case self::FIELD_REF_TYPE:
+ return self::VALUE_REF_TYPE;
+ case self::FIELD_REF_CHANGE:
+ return self::VALUE_REF_CHANGE;
+ }
+
+ return parent::getValueTypeForFieldAndCondition($field, $condition);
+ }
+
+ public function getPHID() {
+ return $this->getObject()->getPHID();
+ }
+
+ public function getHeraldName() {
+ return pht('Push Log');
+ }
+
+ public function getHeraldField($field) {
+ $log = $this->getObject();
+ switch ($field) {
+ case self::FIELD_REF_TYPE:
+ return $log->getRefType();
+ case self::FIELD_REF_NAME:
+ return $log->getRefName();
+ case self::FIELD_REF_CHANGE:
+ return $log->getChangeFlags();
+ case self::FIELD_REPOSITORY:
+ return $this->hookEngine->getRepository()->getPHID();
+ case self::FIELD_PUSHER:
+ return $this->hookEngine->getViewer()->getPHID();
+ case self::FIELD_PUSHER_PROJECTS:
+ return $this->hookEngine->loadViewerProjectPHIDsForHerald();
+ }
+
+ 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('Did nothing.'));
+ break;
+ case self::ACTION_BLOCK:
+ $result[] = new HeraldApplyTranscript(
+ $effect,
+ true,
+ pht('Blocked push.'));
+ break;
+ default:
+ throw new Exception(pht('No rules to handle action "%s"!', $action));
+ }
+ }
+
+ return $result;
+ }
+
+}
diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php
--- a/src/applications/herald/adapter/HeraldAdapter.php
+++ b/src/applications/herald/adapter/HeraldAdapter.php
@@ -25,6 +25,8 @@
const FIELD_ALWAYS = 'always';
const FIELD_AUTHOR_PROJECTS = 'authorprojects';
const FIELD_PROJECTS = 'projects';
+ const FIELD_PUSHER = 'pusher';
+ const FIELD_PUSHER_PROJECTS = 'pusher-projects';
const CONDITION_CONTAINS = 'contains';
const CONDITION_NOT_CONTAINS = '!contains';
@@ -44,6 +46,8 @@
const CONDITION_NOT_EXISTS = '!exists';
const CONDITION_UNCONDITIONALLY = 'unconditionally';
const CONDITION_REGEXP_PAIR = 'regexp-pair';
+ const CONDITION_HAS_BIT = 'bit';
+ const CONDITION_NOT_BIT = '!bit';
const ACTION_ADD_CC = 'addcc';
const ACTION_REMOVE_CC = 'remcc';
@@ -56,6 +60,7 @@
const ACTION_ADD_REVIEWERS = 'addreviewers';
const ACTION_ADD_BLOCKING_REVIEWERS = 'addblockingreviewers';
const ACTION_APPLY_BUILD_PLANS = 'applybuildplans';
+ const ACTION_BLOCK = 'block';
const VALUE_TEXT = 'text';
const VALUE_NONE = 'none';
@@ -156,6 +161,8 @@
self::FIELD_ALWAYS => pht('Always'),
self::FIELD_AUTHOR_PROJECTS => pht("Author's projects"),
self::FIELD_PROJECTS => pht("Projects"),
+ self::FIELD_PUSHER => pht('Pusher'),
+ self::FIELD_PUSHER_PROJECTS => pht("Pusher's projects"),
);
}
@@ -183,6 +190,8 @@
self::CONDITION_NOT_EXISTS => pht('does not exist'),
self::CONDITION_UNCONDITIONALLY => '', // don't show anything!
self::CONDITION_REGEXP_PAIR => pht('matches regexp pair'),
+ self::CONDITION_HAS_BIT => pht('has bit'),
+ self::CONDITION_NOT_BIT => pht('lacks bit'),
);
}
@@ -200,6 +209,7 @@
case self::FIELD_AUTHOR:
case self::FIELD_COMMITTER:
case self::FIELD_REVIEWER:
+ case self::FIELD_PUSHER:
return array(
self::CONDITION_IS_ANY,
self::CONDITION_IS_NOT_ANY,
@@ -218,6 +228,7 @@
case self::FIELD_PROJECTS:
case self::FIELD_AFFECTED_PACKAGE:
case self::FIELD_AFFECTED_PACKAGE_OWNER:
+ case self::FIELD_PUSHER_PROJECTS:
return array(
self::CONDITION_INCLUDE_ALL,
self::CONDITION_INCLUDE_ANY,
@@ -392,6 +403,10 @@
$result = !$result;
}
return $result;
+ case self::CONDITION_HAS_BIT:
+ return (($condition_value & $field_value) === $condition_value);
+ case self::CONDITION_NOT_BIT:
+ return (($condition_value & $field_value) !== $condition_value);
default:
throw new HeraldInvalidConditionException(
"Unknown condition '{$condition_type}'.");
@@ -469,6 +484,8 @@
case self::CONDITION_EXISTS:
case self::CONDITION_NOT_EXISTS:
case self::CONDITION_UNCONDITIONALLY:
+ case self::CONDITION_HAS_BIT:
+ case self::CONDITION_NOT_BIT:
// No explicit validation for these types, although there probably
// should be in some cases.
break;
@@ -500,6 +517,7 @@
self::ACTION_ADD_REVIEWERS => pht('Add reviewers'),
self::ACTION_ADD_BLOCKING_REVIEWERS => pht('Add blocking reviewers'),
self::ACTION_APPLY_BUILD_PLANS => pht('Apply build plans'),
+ self::ACTION_BLOCK => pht('Block change with message'),
);
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
return array(
@@ -551,6 +569,7 @@
$target = PhabricatorFlagColor::COLOR_BLUE;
}
break;
+ case self::ACTION_BLOCK:
case self::ACTION_NOTHING:
break;
default:
@@ -607,6 +626,7 @@
case self::FIELD_AFFECTED_PACKAGE:
return self::VALUE_OWNERS_PACKAGE;
case self::FIELD_AUTHOR_PROJECTS:
+ case self::FIELD_PUSHER_PROJECTS:
case self::FIELD_PROJECTS:
return self::VALUE_PROJECT;
case self::FIELD_REVIEWERS:
@@ -670,6 +690,8 @@
return self::VALUE_USER_OR_PROJECT;
case self::ACTION_APPLY_BUILD_PLANS:
return self::VALUE_BUILD_PLAN;
+ case self::ACTION_BLOCK:
+ return self::VALUE_TEXT;
default:
throw new Exception("Unknown or invalid action '{$action}'.");
}
diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php
--- a/src/applications/herald/controller/HeraldRuleController.php
+++ b/src/applications/herald/controller/HeraldRuleController.php
@@ -349,6 +349,7 @@
switch ($action->getAction()) {
case HeraldAdapter::ACTION_FLAG:
+ case HeraldAdapter::ACTION_BLOCK:
$current_value = $action->getTarget();
break;
default:
@@ -414,12 +415,42 @@
'root' => 'herald-rule-edit-form',
'conditions' => (object)$serial_conditions,
'actions' => (object)$serial_actions,
+ 'select' => array(
+ HeraldAdapter::VALUE_CONTENT_SOURCE => array(
+ 'options' => PhabricatorContentSource::getSourceNameMap(),
+ 'default' => PhabricatorContentSource::SOURCE_WEB,
+ ),
+ HeraldAdapter::VALUE_FLAG_COLOR => array(
+ 'options' => PhabricatorFlagColor::getColorNameMap(),
+ 'default' => PhabricatorFlagColor::COLOR_BLUE,
+ ),
+ HeraldPreCommitRefAdapter::VALUE_REF_TYPE => array(
+ 'options' => array(
+ PhabricatorRepositoryPushLog::REFTYPE_BRANCH
+ => pht('branch (git/hg)'),
+ PhabricatorRepositoryPushLog::REFTYPE_TAG
+ => pht('tag (git)'),
+ PhabricatorRepositoryPushLog::REFTYPE_BOOKMARK
+ => pht('bookmark (hg)'),
+ ),
+ 'default' => PhabricatorRepositoryPushLog::REFTYPE_BRANCH,
+ ),
+ HeraldPreCommitRefAdapter::VALUE_REF_CHANGE => array(
+ 'options' => array(
+ PhabricatorRepositoryPushLog::CHANGEFLAG_ADD =>
+ pht('change creates ref'),
+ PhabricatorRepositoryPushLog::CHANGEFLAG_DELETE =>
+ pht('change deletes ref'),
+ PhabricatorRepositoryPushLog::CHANGEFLAG_REWRITE =>
+ pht('change rewrites ref'),
+ PhabricatorRepositoryPushLog::CHANGEFLAG_DANGEROUS =>
+ pht('dangerous change'),
+ ),
+ 'default' => PhabricatorRepositoryPushLog::CHANGEFLAG_ADD,
+ ),
+ ),
'template' => $this->buildTokenizerTemplates() + array(
'rules' => $all_rules,
- 'colors' => PhabricatorFlagColor::getColorNameMap(),
- 'defaultColor' => PhabricatorFlagColor::COLOR_BLUE,
- 'contentSources' => PhabricatorContentSource::getSourceNameMap(),
- 'defaultSource' => PhabricatorContentSource::SOURCE_WEB
),
'author' => array($rule->getAuthorPHID() =>
$handles[$rule->getAuthorPHID()]->getName()),
diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php
--- a/src/applications/herald/controller/HeraldTranscriptController.php
+++ b/src/applications/herald/controller/HeraldTranscriptController.php
@@ -293,6 +293,10 @@
case HeraldAdapter::ACTION_FLAG:
$target = PhabricatorFlagColor::getColorName($target);
break;
+ case HeraldAdapter::ACTION_BLOCK:
+ // Target is a text string.
+ $target = $target;
+ break;
default:
if ($target) {
foreach ($target as $k => $phid) {
diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php
--- a/src/applications/herald/engine/HeraldEngine.php
+++ b/src/applications/herald/engine/HeraldEngine.php
@@ -24,17 +24,21 @@
return idx($this->rules, $id);
}
- public static function loadAndApplyRules(HeraldAdapter $adapter) {
- $rules = id(new HeraldRuleQuery())
+ public function loadRulesForAdapter(HeraldAdapter $adapter) {
+ return id(new HeraldRuleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withDisabled(false)
->withContentTypes(array($adapter->getAdapterContentType()))
->needConditionsAndActions(true)
->needAppliedToPHIDs(array($adapter->getPHID()))
->needValidateAuthors(true)
->execute();
+ }
+ public static function loadAndApplyRules(HeraldAdapter $adapter) {
$engine = new HeraldEngine();
+
+ $rules = $engine->loadRulesForAdapter($adapter);
$effects = $engine->applyRules($rules, $adapter);
$engine->applyEffects($effects, $adapter, $rules);
diff --git a/src/applications/herald/storage/HeraldRule.php b/src/applications/herald/storage/HeraldRule.php
--- a/src/applications/herald/storage/HeraldRule.php
+++ b/src/applications/herald/storage/HeraldRule.php
@@ -16,9 +16,10 @@
protected $ruleType;
protected $isDisabled = 0;
- protected $configVersion = 15;
+ protected $configVersion = 16;
- private $ruleApplied = self::ATTACHABLE; // phids for which this rule has been applied
+ // phids for which this rule has been applied
+ private $ruleApplied = self::ATTACHABLE;
private $validAuthor = self::ATTACHABLE;
private $author = self::ATTACHABLE;
private $conditions;
diff --git a/src/applications/repository/query/PhabricatorRepositoryPushLogQuery.php b/src/applications/repository/query/PhabricatorRepositoryPushLogQuery.php
--- a/src/applications/repository/query/PhabricatorRepositoryPushLogQuery.php
+++ b/src/applications/repository/query/PhabricatorRepositoryPushLogQuery.php
@@ -4,6 +4,7 @@
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
+ private $phids;
private $repositoryPHIDs;
private $pusherPHIDs;
private $refTypes;
@@ -14,6 +15,11 @@
return $this;
}
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
public function withRepositoryPHIDs(array $repository_phids) {
$this->repositoryPHIDs = $repository_phids;
return $this;
@@ -84,6 +90,13 @@
$this->ids);
}
+ if ($this->phids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'phid IN (%Ls)',
+ $this->phids);
+ }
+
if ($this->repositoryPHIDs) {
$where[] = qsprintf(
$conn_r,
diff --git a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js
--- a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js
+++ b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js
@@ -233,16 +233,13 @@
set_fn = JX.bag;
break;
case 'contentsource':
- input = this._renderSelect(this._config.template.contentSources);
- get_fn = function() { return input.value; };
- set_fn = function(v) { input.value = v; };
- set_fn(this._config.template.defaultSource);
- break;
case 'flagcolor':
- input = this._renderSelect(this._config.template.colors);
+ case 'value-ref-type':
+ case 'value-ref-change':
+ input = this._renderSelect(this._config.select[type].options);
get_fn = function() { return input.value; };
set_fn = function(v) { input.value = v; };
- set_fn(this._config.template.defaultColor);
+ set_fn(this._config.select[type]['default']);
break;
default:
input = JX.$N('input', {type: 'text'});
File Metadata
Details
Attached
Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/v2/vb/ufmofco3wxsuw26u
Default Alt Text
D7782.diff (21 KB)
Attached To
Mode
D7782: Add Herald support for blocking ref changes
Attached
Detach File
Event Timeline
Log In to Comment