mirror of
https://we.phorge.it/source/arcanist.git
synced 2025-02-04 10:58:25 +01:00
2ae0cb797d
Summary: This method is used in three cases: # For unit tests, to set the range to 'HEAD^' or '.^' in an agnostic way. # For "amend", to set the range to the commit to be amended (also 'HEAD^' or '.^'). # For "patch" and "upgrade" so we don't fail just because there's an invalid "base" rule somewhere in the config when doing clean-working-copy tests. For cases (1) and (2), introduce an "arc:this" rule to mean "the current commit". For case (3), remove the call; it is no longer necessary to check the commit range in order to do tests for the working copy state after D4095. Test Plan: Ran unit tests, "arc upgrade", "arc patch", "arc amend". Reviewers: vrana Reviewed By: vrana CC: aran Differential Revision: https://secure.phabricator.com/D4096
176 lines
4.5 KiB
PHP
176 lines
4.5 KiB
PHP
<?php
|
|
|
|
final class ArcanistBaseCommitParser {
|
|
|
|
private $api;
|
|
private $try;
|
|
private $verbose = false;
|
|
|
|
public function __construct(ArcanistRepositoryAPI $api) {
|
|
$this->api = $api;
|
|
return $this;
|
|
}
|
|
|
|
private function tokenizeBaseCommitSpecification($raw_spec) {
|
|
if (!$raw_spec) {
|
|
return array();
|
|
}
|
|
|
|
$spec = preg_split('/\s*,\s*/', $raw_spec);
|
|
$spec = array_filter($spec);
|
|
|
|
foreach ($spec as $rule) {
|
|
if (strpos($rule, ':') === false) {
|
|
throw new ArcanistUsageException(
|
|
"Rule '{$rule}' is invalid, it must have a type and name like ".
|
|
"'arc:upstream'.");
|
|
}
|
|
}
|
|
|
|
return $spec;
|
|
}
|
|
|
|
private function log($message) {
|
|
if ($this->verbose) {
|
|
fwrite(STDERR, $message."\n");
|
|
}
|
|
}
|
|
|
|
public function resolveBaseCommit(array $specs) {
|
|
$specs += array(
|
|
'args' => '',
|
|
'local' => '',
|
|
'project' => '',
|
|
'global' => '',
|
|
'system' => '',
|
|
);
|
|
|
|
foreach ($specs as $source => $spec) {
|
|
$specs[$source] = self::tokenizeBaseCommitSpecification($spec);
|
|
}
|
|
|
|
$this->try = array(
|
|
'args',
|
|
'local',
|
|
'project',
|
|
'global',
|
|
'system',
|
|
);
|
|
|
|
while ($this->try) {
|
|
$source = head($this->try);
|
|
|
|
if (!idx($specs, $source)) {
|
|
$this->log("No rules left from source '{$source}'.");
|
|
array_shift($this->try);
|
|
continue;
|
|
}
|
|
|
|
$this->log("Trying rules from source '{$source}'.");
|
|
|
|
$rules = &$specs[$source];
|
|
while ($rule = array_shift($rules)) {
|
|
$this->log("Trying rule '{$rule}'.");
|
|
|
|
$commit = $this->resolveRule($rule, $source);
|
|
|
|
if ($commit === false) {
|
|
// If a rule returns false, it means to go to the next ruleset.
|
|
break;
|
|
} else if ($commit !== null) {
|
|
$this->log("Resolved commit '{$commit}' from rule '{$rule}'.");
|
|
return $commit;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handle resolving individual rules.
|
|
*/
|
|
private function resolveRule($rule, $source) {
|
|
|
|
// NOTE: Returning `null` from this method means "no match".
|
|
// Returning `false` from this method means "stop current ruleset".
|
|
|
|
list($type, $name) = explode(':', $rule, 2);
|
|
switch ($type) {
|
|
case 'literal':
|
|
return $name;
|
|
case 'git':
|
|
case 'hg':
|
|
return $this->api->resolveBaseCommitRule($rule, $source);
|
|
case 'arc':
|
|
return $this->resolveArcRule($rule, $name, $source);
|
|
default:
|
|
throw new ArcanistUsageException(
|
|
"Base commit rule '{$rule}' (from source '{$source}') ".
|
|
"is not a recognized rule.");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle resolving "arc:*" rules.
|
|
*/
|
|
private function resolveArcRule($rule, $name, $source) {
|
|
switch ($name) {
|
|
case 'verbose':
|
|
$this->verbose = true;
|
|
$this->log("Enabled verbose mode.");
|
|
break;
|
|
case 'prompt':
|
|
$reason = "it is what you typed when prompted.";
|
|
$this->api->setBaseCommitExplanation($reason);
|
|
return phutil_console_prompt('Against which commit?');
|
|
case 'local':
|
|
case 'global':
|
|
case 'project':
|
|
case 'args':
|
|
case 'system':
|
|
// Push the other source on top of the list.
|
|
array_unshift($this->try, $name);
|
|
$this->log("Switching to source '{$name}'.");
|
|
return false;
|
|
case 'yield':
|
|
// Cycle this source to the end of the list.
|
|
$this->try[] = array_shift($this->try);
|
|
$this->log("Yielding processing of rules from '{$source}'.");
|
|
return false;
|
|
case 'halt':
|
|
// Dump the whole stack.
|
|
$this->try = array();
|
|
$this->log("Halting all rule processing.");
|
|
return false;
|
|
case 'skip':
|
|
return null;
|
|
case 'empty':
|
|
case 'upstream':
|
|
case 'outgoing':
|
|
case 'bookmark':
|
|
case 'amended':
|
|
case 'this':
|
|
return $this->api->resolveBaseCommitRule($rule, $source);
|
|
default:
|
|
$matches = null;
|
|
if (preg_match('/^exec\((.*)\)$/', $name, $matches)) {
|
|
$root = $this->api->getWorkingCopyIdentity()->getProjectRoot();
|
|
$future = new ExecFuture($matches[1]);
|
|
$future->setCWD($root);
|
|
list($err, $stdout) = $future->resolve();
|
|
if (!$err) {
|
|
return trim($stdout);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
throw new ArcanistUsageException(
|
|
"Base commit rule '{$rule}' (from source '{$source}') ".
|
|
"is not a recognized rule.");
|
|
}
|
|
}
|
|
|
|
}
|