mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-22 14:52:40 +01:00
--range support for git
Summary: This adds support for passing range of commits for arc diff. This is useful when you want to submit code reviews for past commits without mucking around with the working copy. This will probably require changes :) Test Plan: Tested locally, but totally need to add tests for this Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D9369
This commit is contained in:
parent
08bad11bc8
commit
6b192f3178
6 changed files with 152 additions and 20 deletions
|
@ -84,7 +84,9 @@ final class ArcanistBundleTestCase extends ArcanistTestCase {
|
||||||
$configuration_manager);
|
$configuration_manager);
|
||||||
|
|
||||||
$repository_api->setBaseCommitArgumentRules('arc:this');
|
$repository_api->setBaseCommitArgumentRules('arc:this');
|
||||||
$diff = $repository_api->getFullGitDiff();
|
$diff = $repository_api->getFullGitDiff(
|
||||||
|
$repository_api->getBaseCommit(),
|
||||||
|
$repository_api->getHeadCommit());
|
||||||
|
|
||||||
$parser = new ArcanistDiffParser();
|
$parser = new ArcanistDiffParser();
|
||||||
$parser->setRepositoryAPI($repository_api);
|
$parser->setRepositoryAPI($repository_api);
|
||||||
|
|
|
@ -16,6 +16,9 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
||||||
*/
|
*/
|
||||||
const GIT_MAGIC_ROOT_COMMIT = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
const GIT_MAGIC_ROOT_COMMIT = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
||||||
|
|
||||||
|
private $symbolicHeadCommit = 'HEAD';
|
||||||
|
private $resolvedHeadCommit;
|
||||||
|
|
||||||
public static function newHookAPI($root) {
|
public static function newHookAPI($root) {
|
||||||
return new ArcanistGitAPI($root);
|
return new ArcanistGitAPI($root);
|
||||||
}
|
}
|
||||||
|
@ -75,6 +78,21 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
||||||
return !$this->repositoryHasNoCommits;
|
return !$this->repositoryHasNoCommits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if a child commit is descendant of a parent commit.
|
||||||
|
* If child and parent are the same, it returns false.
|
||||||
|
* @param child commit SHA.
|
||||||
|
* @param parent commit SHA.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function isDescendant($child, $parent) {
|
||||||
|
list($common_ancestor) =
|
||||||
|
$this->execxLocal('merge-base %s %s', $child, $parent);
|
||||||
|
$common_ancestor = trim($common_ancestor);
|
||||||
|
|
||||||
|
return $common_ancestor == $parent && $common_ancestor != $child;
|
||||||
|
}
|
||||||
|
|
||||||
public function getLocalCommitInformation() {
|
public function getLocalCommitInformation() {
|
||||||
if ($this->repositoryHasNoCommits) {
|
if ($this->repositoryHasNoCommits) {
|
||||||
// Zero commits.
|
// Zero commits.
|
||||||
|
@ -106,7 +124,16 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
||||||
// this as being the commits X and Y. If we log "B..Y", we only show
|
// this as being the commits X and Y. If we log "B..Y", we only show
|
||||||
// Y. With "Y --not B", we show X and Y.
|
// Y. With "Y --not B", we show X and Y.
|
||||||
|
|
||||||
$against = csprintf('%s --not %s', 'HEAD', $this->getBaseCommit());
|
$base_commit = $this->getBaseCommit();
|
||||||
|
$head_commit = $this->getHeadCommit();
|
||||||
|
if ($this->isDescendant($head_commit, $base_commit) === false) {
|
||||||
|
throw new ArcanistUsageException(
|
||||||
|
"base commit ${base_commit} is not a child of head commit ".
|
||||||
|
"${head_commit}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$against = csprintf('%s --not %s',
|
||||||
|
$this->getHeadCommit(), $this->getBaseCommit());
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Windows escaping of "%" symbols apparently is inherently broken;
|
// NOTE: Windows escaping of "%" symbols apparently is inherently broken;
|
||||||
|
@ -161,8 +188,9 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
list($err, $merge_base) = $this->execManualLocal(
|
list($err, $merge_base) = $this->execManualLocal(
|
||||||
'merge-base %s HEAD',
|
'merge-base %s %s',
|
||||||
$symbolic_commit);
|
$symbolic_commit,
|
||||||
|
$this->getHeadCommit());
|
||||||
if ($err) {
|
if ($err) {
|
||||||
throw new ArcanistUsageException(
|
throw new ArcanistUsageException(
|
||||||
"Unable to find any git commit named '{$symbolic_commit}' in ".
|
"Unable to find any git commit named '{$symbolic_commit}' in ".
|
||||||
|
@ -170,8 +198,8 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->setBaseCommitExplanation(
|
$this->setBaseCommitExplanation(
|
||||||
"it is the merge-base of '{$symbolic_commit}' and HEAD, as you ".
|
"it is the merge-base of '{$symbolic_commit}' and ".
|
||||||
"explicitly specified.");
|
"{$this->symbolicHeadCommit}, as you explicitly specified.");
|
||||||
return trim($merge_base);
|
return trim($merge_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,6 +329,40 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
||||||
return trim($merge_base);
|
return trim($merge_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getHeadCommit() {
|
||||||
|
if ($this->resolvedHeadCommit === null) {
|
||||||
|
$this->resolvedHeadCommit =
|
||||||
|
$this->resolveCommit($this->symbolicHeadCommit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resolvedHeadCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function setHeadCommit($symbolic_commit) {
|
||||||
|
$this->symbolicHeadCommit = $symbolic_commit;
|
||||||
|
$this->reloadCommitRange();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates a symbolic commit (like "HEAD^") to a commit identifier.
|
||||||
|
* @param string_symbol commit.
|
||||||
|
* @return string the commit SHA.
|
||||||
|
*/
|
||||||
|
private function resolveCommit($symbolic_commit) {
|
||||||
|
list($err, $commit_hash) = $this->execManualLocal(
|
||||||
|
'rev-parse %s',
|
||||||
|
$symbolic_commit);
|
||||||
|
|
||||||
|
if ($err) {
|
||||||
|
throw new ArcanistUsageException(
|
||||||
|
"Unable to find any git commit named '{$symbolic_commit}' in ".
|
||||||
|
"this repository.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim($commit_hash);
|
||||||
|
}
|
||||||
|
|
||||||
private function getDiffFullOptions($detect_moves_and_renames = true) {
|
private function getDiffFullOptions($detect_moves_and_renames = true) {
|
||||||
$options = array(
|
$options = array(
|
||||||
self::getDiffBaseOptions(),
|
self::getDiffBaseOptions(),
|
||||||
|
@ -332,11 +394,22 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
||||||
return implode(' ', $options);
|
return implode(' ', $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFullGitDiff() {
|
/**
|
||||||
|
* @param the base revision
|
||||||
|
* @param head revision. If this is null, the generated diff will include the
|
||||||
|
* working copy
|
||||||
|
*/
|
||||||
|
public function getFullGitDiff($base, $head=null) {
|
||||||
$options = $this->getDiffFullOptions();
|
$options = $this->getDiffFullOptions();
|
||||||
|
|
||||||
|
$diff_revision = $base;
|
||||||
|
if ($head) {
|
||||||
|
$diff_revision .= '..'.$head;
|
||||||
|
}
|
||||||
|
|
||||||
list($stdout) = $this->execxLocal(
|
list($stdout) = $this->execxLocal(
|
||||||
"diff {$options} %s --",
|
"diff {$options} %s --",
|
||||||
$this->getBaseCommit());
|
$diff_revision);
|
||||||
return $stdout;
|
return $stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,8 +474,9 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
||||||
} else {
|
} else {
|
||||||
// 2..N commits.
|
// 2..N commits.
|
||||||
list($stdout) = $this->execxLocal(
|
list($stdout) = $this->execxLocal(
|
||||||
'log --first-parent --format=medium %s..HEAD',
|
'log --first-parent --format=medium %s..%s',
|
||||||
$this->getBaseCommit());
|
$this->getBaseCommit(),
|
||||||
|
$this->getHeadCommit());
|
||||||
}
|
}
|
||||||
return $stdout;
|
return $stdout;
|
||||||
}
|
}
|
||||||
|
@ -821,7 +895,7 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllLocalChanges() {
|
public function getAllLocalChanges() {
|
||||||
$diff = $this->getFullGitDiff();
|
$diff = $this->getFullGitDiff($this->getBaseCommit());
|
||||||
if (!strlen(trim($diff))) {
|
if (!strlen(trim($diff))) {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
|
@ -575,6 +575,10 @@ abstract class ArcanistRepositoryAPI {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setHeadCommit($symbolic_commit) {
|
||||||
|
throw new ArcanistCapabilityNotSupportedException($this);
|
||||||
|
}
|
||||||
|
|
||||||
final public function getBaseCommit() {
|
final public function getBaseCommit() {
|
||||||
if (!$this->supportsCommitRanges()) {
|
if (!$this->supportsCommitRanges()) {
|
||||||
throw new ArcanistCapabilityNotSupportedException($this);
|
throw new ArcanistCapabilityNotSupportedException($this);
|
||||||
|
@ -588,6 +592,10 @@ abstract class ArcanistRepositoryAPI {
|
||||||
return $this->resolvedBaseCommit;
|
return $this->resolvedBaseCommit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getHeadCommit() {
|
||||||
|
throw new ArcanistCapabilityNotSupportedException($this);
|
||||||
|
}
|
||||||
|
|
||||||
final public function reloadCommitRange() {
|
final public function reloadCommitRange() {
|
||||||
$this->resolvedBaseCommit = null;
|
$this->resolvedBaseCommit = null;
|
||||||
$this->baseCommitExplanation = null;
|
$this->baseCommitExplanation = null;
|
||||||
|
|
|
@ -395,6 +395,18 @@ EOTEXT
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'*' => 'paths',
|
'*' => 'paths',
|
||||||
|
'head' => array(
|
||||||
|
'param' => 'commit',
|
||||||
|
'help' => "specify the head commit.\n".
|
||||||
|
"This disables many Arcanist/Phabricator features which depend on ".
|
||||||
|
"having access to the working copy.",
|
||||||
|
'supports' => array('git'),
|
||||||
|
'conflicts' => array(
|
||||||
|
'lintall' => '--head suppresses lint.',
|
||||||
|
'advice' => '--head suppresses lint.',
|
||||||
|
),
|
||||||
|
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (phutil_is_windows()) {
|
if (phutil_is_windows()) {
|
||||||
|
@ -433,8 +445,19 @@ EOTEXT
|
||||||
array_unshift($argv, '--ansi');
|
array_unshift($argv, '--ansi');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->getRepositoryAPI()->supportsCommitRanges()) {
|
$repo = $this->getRepositoryAPI();
|
||||||
$this->getRepositoryAPI()->getBaseCommit();
|
$head_commit = $this->getArgument('head');
|
||||||
|
$range_supported = $repo->supportsCommitRanges();
|
||||||
|
if ($head_commit) {
|
||||||
|
if (!$range_supported) {
|
||||||
|
throw new ArcanistUsageException('Ranged are not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
$repo->setHeadCommit($head_commit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($range_supported) {
|
||||||
|
$repo->getBaseCommit();
|
||||||
}
|
}
|
||||||
|
|
||||||
$script = phutil_get_library_root('arcanist').'/../scripts/arcanist.php';
|
$script = phutil_get_library_root('arcanist').'/../scripts/arcanist.php';
|
||||||
|
@ -661,7 +684,9 @@ EOTEXT
|
||||||
if ($repository_api instanceof ArcanistSubversionAPI) {
|
if ($repository_api instanceof ArcanistSubversionAPI) {
|
||||||
$repository_api->limitStatusToPaths($this->getArgument('paths'));
|
$repository_api->limitStatusToPaths($this->getArgument('paths'));
|
||||||
}
|
}
|
||||||
$this->requireCleanWorkingCopy();
|
if (!$this->getArgument('head')) {
|
||||||
|
$this->requireCleanWorkingCopy();
|
||||||
|
}
|
||||||
} catch (ArcanistUncommittedChangesException $ex) {
|
} catch (ArcanistUncommittedChangesException $ex) {
|
||||||
if ($repository_api instanceof ArcanistMercurialAPI) {
|
if ($repository_api instanceof ArcanistMercurialAPI) {
|
||||||
$use_dirty_changes = false;
|
$use_dirty_changes = false;
|
||||||
|
@ -937,7 +962,9 @@ EOTEXT
|
||||||
$repository_api,
|
$repository_api,
|
||||||
$paths);
|
$paths);
|
||||||
} else if ($repository_api instanceof ArcanistGitAPI) {
|
} else if ($repository_api instanceof ArcanistGitAPI) {
|
||||||
$diff = $repository_api->getFullGitDiff();
|
$diff = $repository_api->getFullGitDiff(
|
||||||
|
$repository_api->getBaseCommit(),
|
||||||
|
$repository_api->getHeadCommit());
|
||||||
if (!strlen($diff)) {
|
if (!strlen($diff)) {
|
||||||
throw new ArcanistUsageException(
|
throw new ArcanistUsageException(
|
||||||
'No changes found. (Did you specify the wrong commit range?)');
|
'No changes found. (Did you specify the wrong commit range?)');
|
||||||
|
@ -1216,7 +1243,8 @@ EOTEXT
|
||||||
private function runLint() {
|
private function runLint() {
|
||||||
if ($this->getArgument('nolint') ||
|
if ($this->getArgument('nolint') ||
|
||||||
$this->getArgument('only') ||
|
$this->getArgument('only') ||
|
||||||
$this->isRawDiffSource()) {
|
$this->isRawDiffSource() ||
|
||||||
|
$this->getArgument('head')) {
|
||||||
return ArcanistLintWorkflow::RESULT_SKIP;
|
return ArcanistLintWorkflow::RESULT_SKIP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1297,7 +1325,8 @@ EOTEXT
|
||||||
private function runUnit() {
|
private function runUnit() {
|
||||||
if ($this->getArgument('nounit') ||
|
if ($this->getArgument('nounit') ||
|
||||||
$this->getArgument('only') ||
|
$this->getArgument('only') ||
|
||||||
$this->isRawDiffSource()) {
|
$this->isRawDiffSource() ||
|
||||||
|
$this->getArgument('head')) {
|
||||||
return ArcanistUnitWorkflow::RESULT_SKIP;
|
return ArcanistUnitWorkflow::RESULT_SKIP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -178,7 +178,9 @@ EOTEXT
|
||||||
|
|
||||||
if ($repository_api instanceof ArcanistGitAPI) {
|
if ($repository_api instanceof ArcanistGitAPI) {
|
||||||
$this->parseBaseCommitArgument($this->getArgument('paths'));
|
$this->parseBaseCommitArgument($this->getArgument('paths'));
|
||||||
$diff = $repository_api->getFullGitDiff();
|
$diff = $repository_api->getFullGitDiff(
|
||||||
|
$repository_api->getBaseCommit(),
|
||||||
|
$repository_api->getHeadCommit());
|
||||||
$changes = $parser->parseDiff($diff);
|
$changes = $parser->parseDiff($diff);
|
||||||
$authors = $this->getConduit()->callMethodSynchronous(
|
$authors = $this->getConduit()->callMethodSynchronous(
|
||||||
'user.query',
|
'user.query',
|
||||||
|
|
|
@ -61,6 +61,10 @@ EOTEXT
|
||||||
),
|
),
|
||||||
'supports' => array('git', 'hg'),
|
'supports' => array('git', 'hg'),
|
||||||
),
|
),
|
||||||
|
'head' => array(
|
||||||
|
'param' => 'commit',
|
||||||
|
'help' => 'specify the head commit.'
|
||||||
|
),
|
||||||
'*' => 'commit',
|
'*' => 'commit',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +87,19 @@ EOTEXT
|
||||||
$repository_api->setBaseCommitArgumentRules(
|
$repository_api->setBaseCommitArgumentRules(
|
||||||
$this->getArgument('base', ''));
|
$this->getArgument('base', ''));
|
||||||
|
|
||||||
if ($repository_api->supportsCommitRanges()) {
|
$supports_ranges = $repository_api->supportsCommitRanges();
|
||||||
|
|
||||||
|
if ($this->getArgument('head')) {
|
||||||
|
if ($supports_ranges === false) {
|
||||||
|
throw new Exception('--head is not supported in this VCS');
|
||||||
|
}
|
||||||
|
|
||||||
|
$head_commit = $this->getArgument('head');
|
||||||
|
$arg .= " --head {$head_commit}";
|
||||||
|
$repository_api->setHeadCommit($head_commit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supports_ranges) {
|
||||||
$relative = $repository_api->getBaseCommit();
|
$relative = $repository_api->getBaseCommit();
|
||||||
|
|
||||||
if ($this->getArgument('show-base')) {
|
if ($this->getArgument('show-base')) {
|
||||||
|
@ -111,7 +127,8 @@ EOTEXT
|
||||||
$relative = substr($relative, 0, 16);
|
$relative = substr($relative, 0, 16);
|
||||||
|
|
||||||
if ($repository_api instanceof ArcanistGitAPI) {
|
if ($repository_api instanceof ArcanistGitAPI) {
|
||||||
$command = "git diff {$relative}..HEAD";
|
$head = $this->getArgument('head', 'HEAD');
|
||||||
|
$command = "git diff {$relative}..{$head}";
|
||||||
} else if ($repository_api instanceof ArcanistMercurialAPI) {
|
} else if ($repository_api instanceof ArcanistMercurialAPI) {
|
||||||
$command = "hg diff --rev {$relative}";
|
$command = "hg diff --rev {$relative}";
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue