diff --git a/src/parser/ArcanistBaseCommitParser.php b/src/parser/ArcanistBaseCommitParser.php index b4a319d6..e226b334 100644 --- a/src/parser/ArcanistBaseCommitParser.php +++ b/src/parser/ArcanistBaseCommitParser.php @@ -166,6 +166,7 @@ final class ArcanistBaseCommitParser { case 'upstream': case 'outgoing': case 'bookmark': + case 'amended': return $this->api->resolveBaseCommitRule($rule, $source); default: $matches = null; diff --git a/src/repository/api/ArcanistGitAPI.php b/src/repository/api/ArcanistGitAPI.php index 4fb18334..48a2912f 100644 --- a/src/repository/api/ArcanistGitAPI.php +++ b/src/repository/api/ArcanistGitAPI.php @@ -752,12 +752,12 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI { "'git push', or 'git svn dcommit', or by printing and faxing it)."; } - public function getCommitMessageForRevision($rev) { + public function getCommitMessage($commit) { list($message) = $this->execxLocal( - 'log -n1 %s', - $rev); - $parser = new ArcanistDiffParser(); - return head($parser->parseDiff($message)); + 'log -n1 --format=%C %s --', + '%s%n%b', + $commit); + return $message; } public function loadWorkingCopyDifferentialRevisions( @@ -921,6 +921,18 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI { "you specified '{$rule}' in your {$source} 'base' ". "configuration."); return self::GIT_MAGIC_ROOT_COMMIT; + case 'amended': + $text = $this->getCommitMessage('HEAD'); + $message = ArcanistDifferentialCommitMessage::newFromRawCorpus( + $text); + if ($message->getRevisionID()) { + $this->setBaseCommitExplanation( + "HEAD has been amended with 'Differential Revision:', ". + "as specified by '{$rule}' in your {$source} 'base' ". + "configuration."); + return 'HEAD^'; + } + break; case 'upstream': list($err, $upstream) = $this->execManualLocal( "rev-parse --abbrev-ref --symbolic-full-name '@{upstream}'"); diff --git a/src/repository/api/ArcanistMercurialAPI.php b/src/repository/api/ArcanistMercurialAPI.php index 3d8196ca..e0f3aef3 100644 --- a/src/repository/api/ArcanistMercurialAPI.php +++ b/src/repository/api/ArcanistMercurialAPI.php @@ -26,6 +26,7 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { private $status; private $base; private $relativeCommit; + private $branch; private $workingCopyRevision; private $localCommitInfo; private $includeDirectoryStateInDiffs; @@ -78,9 +79,11 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { } public function getBranchName() { - // TODO: I have nearly no idea how hg branches work. - list($stdout) = $this->execxLocal('branch'); - return trim($stdout); + if (!$this->branch) { + list($stdout) = $this->execxLocal('branch'); + $this->branch = trim($stdout); + } + return $this->branch; } public function setRelativeCommit($commit) { @@ -92,7 +95,8 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { } $this->relativeCommit = $commit; - $this->dropCaches(); + $this->status = null; + $this->localCommitInfo = null; return $this; } @@ -114,7 +118,8 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { } list($err, $stdout) = $this->execManualLocal( - 'outgoing --branch `hg branch` --style default'); + 'outgoing --branch %s --style default', + $this->getBranchName()); if (!$err) { $logs = ArcanistMercurialParser::parseMercurialLog($stdout); @@ -187,11 +192,10 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { public function getLocalCommitInformation() { if ($this->localCommitInfo === null) { list($info) = $this->execxLocal( - "log --template '%C' --prune %s --rev %s --branch %s --", + "log --template '%C' --rev %s --branch %s --", "{node}\1{rev}\1{author}\1{date|rfc822date}\1". "{branch}\1{tag}\1{parents}\1{desc}\2", - $this->getRelativeCommit(), - "ancestors({$this->getWorkingCopyRevision()})", + '(ancestors(.) - ancestors('.$this->getRelativeCommit().'))', $this->getBranchName()); $logs = array_filter(explode("\2", $info)); @@ -356,26 +360,27 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { public function getRawDiffText($path) { $options = $this->getDiffOptions(); + // NOTE: In Mercurial, "--rev x" means "diff between x and the working + // copy state", while "--rev x..." means "diff between x and the working + // copy commit" (i.e., from 'x' to '.'). The latter excludes any dirty + // changes in the working copy. + + $range = $this->getRelativeCommit(); + if (!$this->includeDirectoryStateInDiffs) { + $range .= '...'; + } + list($stdout) = $this->execxLocal( - 'diff %C --rev %s --rev %s -- %s', + 'diff %C --rev %s -- %s', $options, - $this->getRelativeCommit(), - $this->getDiffToRevision(), + $range, $path); return $stdout; } public function getFullMercurialDiff() { - $options = $this->getDiffOptions(); - - list($stdout) = $this->execxLocal( - 'diff %C --rev %s --rev %s --', - $options, - $this->getRelativeCommit(), - $this->getDiffToRevision()); - - return $stdout; + return $this->getRawDiffText(''); } public function getOriginalFileData($path) { @@ -402,25 +407,7 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { } public function getWorkingCopyRevision() { - if ($this->workingCopyRevision === null) { - // In Mercurial, "tip" means the tip of the current branch, not what's in - // the working copy. The tip may be ahead of the working copy. We need to - // use "hg summary" to figure out what is actually in the working copy. - // For instance, "hg up 4 && arc diff" should not show commits 5 and - // above. - - // Without arguments, "hg id" shows the current working directory's - // commit, and "--debug" expands it to a 40-character hash. - list($stdout) = $this->execxLocal('--debug id --id'); - - // Even with "--id", "hg id" will print a trailing "+" after the hash - // if the working copy is dirty (has uncommitted changes). We'll - // explicitly detect this later by calling getWorkingCopyStatus(); ignore - // it for now. - $stdout = trim($stdout); - $this->workingCopyRevision = rtrim($stdout, '+'); - } - return $this->workingCopyRevision; + return '.'; } public function isHistoryDefaultImmutable() { @@ -449,6 +436,13 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { } } + public function getCommitMessage($commit) { + list($message) = $this->execxLocal( + 'log --template={desc} --rev %s', + $commit); + return $message; + } + public function parseRelativeLocalCommit(array $argv) { if (count($argv) == 0) { return; @@ -503,15 +497,15 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { public function getCommitMessageLog() { list($stdout) = $this->execxLocal( - "log --template '{node}\\1{desc}\\0' --prune %s --rev %s --", - $this->getRelativeCommit(), - "ancestors({$this->getWorkingCopyRevision()})"); + "log --template '{node}\\2{desc}\\1' --rev %s --branch %s --", + 'ancestors(.) - ancestors('.$this->getRelativeCommit().')', + $this->getBranchName()); $map = array(); - $logs = explode("\0", trim($stdout)); + $logs = explode("\1", trim($stdout)); foreach (array_filter($logs) as $log) { - list($node, $desc) = explode("\1", $log); + list($node, $desc) = explode("\2", $log); $map[$node] = $desc; } @@ -591,23 +585,6 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { return $this; } - private function getDiffToRevision() { - $this->dropCaches(); - - if ($this->includeDirectoryStateInDiffs) { - // This is a magic Mercurial revision name which means "current - // directory state"; see lookup() in localrepo.py. - return '.'; - } else { - return $this->getWorkingCopyRevision(); - } - } - - private function dropCaches() { - $this->status = null; - $this->localCommitInfo = null; - } - public function getCommitSummary($commit) { if ($commit == 'null') { return '(The Empty Void)'; @@ -670,6 +647,20 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { "'base' configuration."); return trim($outgoing_base); } + case 'amended': + $text = $this->getCommitMessage('.'); + $message = ArcanistDifferentialCommitMessage::newFromRawCorpus( + $text); + if ($message->getRevisionID()) { + $this->setBaseCommitExplanation( + "'.' has been amended with 'Differential Revision:', ". + "as specified by '{$rule}' in your {$source} 'base' ". + "configuration."); + // NOTE: This should be safe because Mercurial doesn't support + // amend until 2.2. + return '.^'; + } + break; case 'bookmark': $revset = 'limit('. diff --git a/src/repository/api/ArcanistRepositoryAPI.php b/src/repository/api/ArcanistRepositoryAPI.php index 25f74595..d96afd7c 100644 --- a/src/repository/api/ArcanistRepositoryAPI.php +++ b/src/repository/api/ArcanistRepositoryAPI.php @@ -195,7 +195,7 @@ abstract class ArcanistRepositoryAPI { throw new ArcanistCapabilityNotSupportedException($this); } - public function getCommitMessageForRevision($revision) { + public function getCommitMessage($commit) { throw new ArcanistCapabilityNotSupportedException($this); } @@ -221,17 +221,17 @@ abstract class ArcanistRepositoryAPI { throw new ArcanistCapabilityNotSupportedException($this); } - public function execxLocal($pattern /*, ... */) { + public function execxLocal($pattern /* , ... */) { $args = func_get_args(); return $this->buildLocalFuture($args)->resolvex(); } - public function execManualLocal($pattern /*, ... */) { + public function execManualLocal($pattern /* , ... */) { $args = func_get_args(); return $this->buildLocalFuture($args)->resolve(); } - public function execFutureLocal($pattern /*, ... */) { + public function execFutureLocal($pattern /* , ... */) { $args = func_get_args(); return $this->buildLocalFuture($args); } diff --git a/src/workflow/ArcanistDiffWorkflow.php b/src/workflow/ArcanistDiffWorkflow.php index 485d3089..cea9f099 100644 --- a/src/workflow/ArcanistDiffWorkflow.php +++ b/src/workflow/ArcanistDiffWorkflow.php @@ -1280,10 +1280,9 @@ EOTEXT /** * @task message */ - private function getCommitMessageFromCommit($rev) { - $change = $this->getRepositoryAPI()->getCommitMessageForRevision($rev); - $message = ArcanistDifferentialCommitMessage::newFromRawCorpus( - $change->getMetadata('message')); + private function getCommitMessageFromCommit($commit) { + $text = $this->getRepositoryAPI()->getCommitMessage($commit); + $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($text); $message->pullDataFromConduit($this->getConduit()); $this->validateCommitMessage($message); return $message;