Page MenuHomePhabricator

D9369.id22575.diff
No OneTemporary

D9369.id22575.diff

diff --git a/src/repository/api/ArcanistGitAPI.php b/src/repository/api/ArcanistGitAPI.php
--- a/src/repository/api/ArcanistGitAPI.php
+++ b/src/repository/api/ArcanistGitAPI.php
@@ -16,6 +16,9 @@
*/
const GIT_MAGIC_ROOT_COMMIT = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
+ private $symbolicHeadCommit = 'HEAD';
+ private $resolvedHeadCommit;
+
public static function newHookAPI($root) {
return new ArcanistGitAPI($root);
}
@@ -75,6 +78,20 @@
return !$this->repositoryHasNoCommits;
}
+ /**
+ * Tests if a child commit is descendant of a parent commit.
+ * @param child commit SHA.
+ * @param parent commit SHA.
+ * @return bool
+ */
+ public function isDescendant($child, $parent) {
+ list($common_ancestor) =
+ $this->execxLocal('merge-base %s %s', $child, $parent);
+ $common_ancestor = trim($common_ancestor, " \n\2");
+
+ return $common_ancestor == $parent && $common_ancestor != $child;
+ }
+
public function getLocalCommitInformation() {
if ($this->repositoryHasNoCommits) {
// Zero commits.
@@ -106,7 +123,16 @@
// 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.
- $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 Exception(
+ "base commit ${head_commit} is not a child of head commit ".
+ "${base_commit}");
+ }
+
+ $against = csprintf('%s --not %s',
+ $this->getHeadCommit(), $this->getBaseCommit());
}
// NOTE: Windows escaping of "%" symbols apparently is inherently broken;
@@ -161,8 +187,9 @@
}
list($err, $merge_base) = $this->execManualLocal(
- 'merge-base %s HEAD',
- $symbolic_commit);
+ 'merge-base %s %s',
+ $symbolic_commit,
+ $this->getHeadCommit());
if ($err) {
throw new ArcanistUsageException(
"Unable to find any git commit named '{$symbolic_commit}' in ".
@@ -170,8 +197,8 @@
}
$this->setBaseCommitExplanation(
- "it is the merge-base of '{$symbolic_commit}' and HEAD, as you ".
- "explicitly specified.");
+ "it is the merge-base of '{$symbolic_commit}' and ".
+ "{$this->symbolicHeadCommit}, as you explicitly specified.");
return trim($merge_base);
}
@@ -301,6 +328,44 @@
return trim($merge_base);
}
+ public function getHeadCommit() {
+ if (!$this->supportsCommitRanges()) {
+ throw new ArcanistCapabilityNotSupportedException($this);
+ }
+
+ 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) {
$options = array(
self::getDiffBaseOptions(),
@@ -335,8 +400,9 @@
public function getFullGitDiff() {
$options = $this->getDiffFullOptions();
list($stdout) = $this->execxLocal(
- "diff {$options} %s --",
- $this->getBaseCommit());
+ "diff {$options} %s..%s --",
+ $this->getBaseCommit(),
+ $this->getHeadCommit());
return $stdout;
}
@@ -401,8 +467,9 @@
} else {
// 2..N commits.
list($stdout) = $this->execxLocal(
- 'log --first-parent --format=medium %s..HEAD',
- $this->getBaseCommit());
+ 'log --first-parent --format=medium %s..%s',
+ $this->getBaseCommit(),
+ $this->getHeadCommit());
}
return $stdout;
}
diff --git a/src/repository/api/ArcanistRepositoryAPI.php b/src/repository/api/ArcanistRepositoryAPI.php
--- a/src/repository/api/ArcanistRepositoryAPI.php
+++ b/src/repository/api/ArcanistRepositoryAPI.php
@@ -575,6 +575,10 @@
return $this;
}
+ public function setHeadCommit($symbolic_commit) {
+ throw new ArcanistCapabilityNotSupportedException($this);
+ }
+
final public function getBaseCommit() {
if (!$this->supportsCommitRanges()) {
throw new ArcanistCapabilityNotSupportedException($this);
@@ -588,6 +592,10 @@
return $this->resolvedBaseCommit;
}
+ public function getHeadCommit() {
+ throw new ArcanistCapabilityNotSupportedException($this);
+ }
+
final public function reloadCommitRange() {
$this->resolvedBaseCommit = null;
$this->baseCommitExplanation = null;
diff --git a/src/workflow/ArcanistDiffWorkflow.php b/src/workflow/ArcanistDiffWorkflow.php
--- a/src/workflow/ArcanistDiffWorkflow.php
+++ b/src/workflow/ArcanistDiffWorkflow.php
@@ -395,6 +395,18 @@
),
),
'*' => '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()) {
@@ -433,8 +445,19 @@
array_unshift($argv, '--ansi');
}
- if ($this->getRepositoryAPI()->supportsCommitRanges()) {
- $this->getRepositoryAPI()->getBaseCommit();
+ $repo = $this->getRepositoryAPI();
+ $head_commit = $this->getArgument('head', null);
+ $range_supported = $repo->supportsCommitRanges();
+ if ($head_commit) {
+ if (!$range_supported) {
+ throw new Exception('ranged are not supported');
+ }
+
+ $repo->setHeadCommit($head_commit);
+ }
+
+ if ($range_supported) {
+ $repo->getBaseCommit();
}
$script = phutil_get_library_root('arcanist').'/../scripts/arcanist.php';
@@ -661,7 +684,9 @@
if ($repository_api instanceof ArcanistSubversionAPI) {
$repository_api->limitStatusToPaths($this->getArgument('paths'));
}
- $this->requireCleanWorkingCopy();
+ if (!$this->getArgument('head')) {
+ $this->requireCleanWorkingCopy();
+ }
} catch (ArcanistUncommittedChangesException $ex) {
if ($repository_api instanceof ArcanistMercurialAPI) {
$use_dirty_changes = false;
@@ -1216,7 +1241,8 @@
private function runLint() {
if ($this->getArgument('nolint') ||
$this->getArgument('only') ||
- $this->isRawDiffSource()) {
+ $this->isRawDiffSource() ||
+ $this->getArgument('head')) {
return ArcanistLintWorkflow::RESULT_SKIP;
}
@@ -1297,7 +1323,8 @@
private function runUnit() {
if ($this->getArgument('nounit') ||
$this->getArgument('only') ||
- $this->isRawDiffSource()) {
+ $this->isRawDiffSource() ||
+ $this->getArgument('head')) {
return ArcanistUnitWorkflow::RESULT_SKIP;
}
diff --git a/src/workflow/ArcanistWhichWorkflow.php b/src/workflow/ArcanistWhichWorkflow.php
--- a/src/workflow/ArcanistWhichWorkflow.php
+++ b/src/workflow/ArcanistWhichWorkflow.php
@@ -61,6 +61,10 @@
),
'supports' => array('git', 'hg'),
),
+ 'head' => array(
+ 'param' => 'commit',
+ 'help' => 'specify the head commit.'
+ ),
'*' => 'commit',
);
}
@@ -83,7 +87,19 @@
$repository_api->setBaseCommitArgumentRules(
$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();
if ($this->getArgument('show-base')) {

File Metadata

Mime Type
text/plain
Expires
Thu, May 23, 1:39 AM (3 w, 3 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6299427
Default Alt Text
D9369.id22575.diff (8 KB)

Event Timeline