From 3ee01bacac390e16fe313719a86e6b1a050d0076 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 24 Jan 2012 08:07:38 -0800 Subject: [PATCH] Add 'arc which', and ArcanistRepositoryAPI->loadWorkingCopyDifferentialRevisions() Summary: - See T787. - @cpiro has an immediate use case for this, which is ##arc amend --revision `arc which --id` --show master## for "git merge --autosquash" or similar. - For T614, we need this to choose "--create" vs "--update". - Other workflows should also use this to improve how often we automatically get things right, particularly in Mercurial and SVN. Test Plan: Ran "arc which" in SVN, Git and HG working copies with various flags; results seemed reasonable. Reviewers: btrahan, cpiro, jungejason Reviewed By: btrahan CC: aran, epriestley Maniphest Tasks: T787 Differential Revision: https://secure.phabricator.com/D1478 --- src/__phutil_library_map__.php | 2 + .../api/base/ArcanistRepositoryAPI.php | 3 + src/repository/api/git/ArcanistGitAPI.php | 58 +++++++++ src/repository/api/git/__init__.php | 1 + .../api/mercurial/ArcanistMercurialAPI.php | 34 ++++- .../api/subversion/ArcanistSubversionAPI.php | 20 +++ src/workflow/which/ArcanistWhichWorkflow.php | 116 ++++++++++++++++++ src/workflow/which/__init__.php | 16 +++ 8 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 src/workflow/which/ArcanistWhichWorkflow.php create mode 100644 src/workflow/which/__init__.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 72890ccd..68f1dc54 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -95,6 +95,7 @@ phutil_register_library_map(array( 'ArcanistUploadWorkflow' => 'workflow/upload', 'ArcanistUsageException' => 'exception/usage', 'ArcanistUserAbortException' => 'exception/usage/userabort', + 'ArcanistWhichWorkflow' => 'workflow/which', 'ArcanistWorkingCopyIdentity' => 'workingcopyidentity', 'ArcanistXHPASTLintNamingHook' => 'lint/linter/xhpast/naminghook', 'ArcanistXHPASTLinter' => 'lint/linter/xhpast', @@ -166,6 +167,7 @@ phutil_register_library_map(array( 'ArcanistUnitWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistUploadWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistUserAbortException' => 'ArcanistUsageException', + 'ArcanistWhichWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistXHPASTLinter' => 'ArcanistLinter', 'ArcanistXHPASTLinterTestCase' => 'ArcanistLinterTestCase', 'ComprehensiveLintEngine' => 'ArcanistLintEngine', diff --git a/src/repository/api/base/ArcanistRepositoryAPI.php b/src/repository/api/base/ArcanistRepositoryAPI.php index 469e2a0e..8d80e0a9 100644 --- a/src/repository/api/base/ArcanistRepositoryAPI.php +++ b/src/repository/api/base/ArcanistRepositoryAPI.php @@ -154,6 +154,9 @@ abstract class ArcanistRepositoryAPI { abstract public function getSourceControlBaseRevision(); abstract public function supportsRelativeLocalCommits(); abstract public function getWorkingCopyRevision(); + abstract public function loadWorkingCopyDifferentialRevisions( + ConduitClient $conduit, + array $query); public function getCommitMessageForRevision($revision) { throw new ArcanistCapabilityNotSupportedException($this); diff --git a/src/repository/api/git/ArcanistGitAPI.php b/src/repository/api/git/ArcanistGitAPI.php index 32248f2e..c25f3d66 100644 --- a/src/repository/api/git/ArcanistGitAPI.php +++ b/src/repository/api/git/ArcanistGitAPI.php @@ -619,4 +619,62 @@ class ArcanistGitAPI extends ArcanistRepositoryAPI { return head($parser->parseDiff($message)); } + public function loadWorkingCopyDifferentialRevisions( + ConduitClient $conduit, + array $query) { + + $messages = $this->getGitCommitLog(); + if (!strlen($messages)) { + return array(); + } + + $parser = new ArcanistDiffParser(); + $messages = $parser->parseDiff($messages); + + // First, try to find revisions by explicit revision IDs in commit messages. + $revision_ids = array(); + foreach ($messages as $message) { + $object = ArcanistDifferentialCommitMessage::newFromRawCorpus( + $message->getMetadata('message')); + if ($object->getRevisionID()) { + $revision_ids[] = $object->getRevisionID(); + } + } + + if ($revision_ids) { + $results = $conduit->callMethodSynchronous( + 'differential.query', + $query + array( + 'ids' => $revision_ids, + )); + return $results; + } + + // If we didn't succeed, try to find revisions by hash. + $hashes = array(); + foreach ($this->getLocalCommitInformation() as $commit) { + $hashes[] = array('gtcm', $commit['commit']); + $hashes[] = array('gttr', $commit['tree']); + } + + $results = $conduit->callMethodSynchronous( + 'differential.query', + $query + array( + 'commitHashes' => $hashes, + )); + + if ($results) { + return $results; + } + + // If we still didn't succeed, try to find revisions by branch name. + $results = $conduit->callMethodSynchronous( + 'differential.query', + $query + array( + 'branches' => array($this->getBranchName()), + )); + + return $results; + } + } diff --git a/src/repository/api/git/__init__.php b/src/repository/api/git/__init__.php index a7fd205d..3bfab98b 100644 --- a/src/repository/api/git/__init__.php +++ b/src/repository/api/git/__init__.php @@ -6,6 +6,7 @@ +phutil_require_module('arcanist', 'differential/commitmessage'); phutil_require_module('arcanist', 'exception/usage'); phutil_require_module('arcanist', 'parser/diff'); phutil_require_module('arcanist', 'repository/api/base'); diff --git a/src/repository/api/mercurial/ArcanistMercurialAPI.php b/src/repository/api/mercurial/ArcanistMercurialAPI.php index 4968a56d..9aa736b9 100644 --- a/src/repository/api/mercurial/ArcanistMercurialAPI.php +++ b/src/repository/api/mercurial/ArcanistMercurialAPI.php @@ -45,11 +45,11 @@ class ArcanistMercurialAPI extends ArcanistRepositoryAPI { } public function getBranchName() { - // TODO: I have nearly no idea how hg local branches work. + // TODO: I have nearly no idea how hg branches work. list($stdout) = execx( '(cd %s && hg branch)', $this->getPath()); - return $stdout; + return trim($stdout); } public function setRelativeCommit($commit) { @@ -374,4 +374,34 @@ class ArcanistMercurialAPI extends ArcanistRepositoryAPI { "'hg push' or by printing and faxing it)."; } + public function loadWorkingCopyDifferentialRevisions( + ConduitClient $conduit, + array $query) { + + // Try to find revisions by hash. + $hashes = array(); + foreach ($this->getLocalCommitInformation() as $commit) { + $hashes[] = array('hgcm', $commit['rev']); + } + + $results = $conduit->callMethodSynchronous( + 'differential.query', + $query + array( + 'commitHashes' => $hashes, + )); + + if ($results) { + return $results; + } + + // If we still didn't succeed, try to find revisions by branch name. + $results = $conduit->callMethodSynchronous( + 'differential.query', + $query + array( + 'branches' => array($this->getBranchName()), + )); + + return $results; + } + } diff --git a/src/repository/api/subversion/ArcanistSubversionAPI.php b/src/repository/api/subversion/ArcanistSubversionAPI.php index 9656d17a..2070734e 100644 --- a/src/repository/api/subversion/ArcanistSubversionAPI.php +++ b/src/repository/api/subversion/ArcanistSubversionAPI.php @@ -506,4 +506,24 @@ EODIFF; return "Done."; } + public function loadWorkingCopyDifferentialRevisions( + ConduitClient $conduit, + array $query) { + + // We don't have much to go on in SVN, look for revisions that came from + // this directory. + + $results = $conduit->callMethodSynchronous( + 'differential.query', + $query); + + foreach ($results as $key => $result) { + if ($result['sourcePath'] != $this->getPath()) { + unset($results[$key]); + } + } + + return $results; + } + } diff --git a/src/workflow/which/ArcanistWhichWorkflow.php b/src/workflow/which/ArcanistWhichWorkflow.php new file mode 100644 index 00000000..0c73bb7e --- /dev/null +++ b/src/workflow/which/ArcanistWhichWorkflow.php @@ -0,0 +1,116 @@ + array( + 'help' => "Show revisions by any author, not just you.", + ), + 'any-status' => array( + 'help' => "Show committed and abandoned revisions.", + ), + 'id' => array( + 'help' => "If exactly one revision matches, print it to stdout. ". + "Otherwise, exit with an error. Intended for scripts.", + ), + '*' => 'commit', + ); + } + + public function run() { + + $repository_api = $this->getRepositoryAPI(); + + $commit = $this->getArgument('commit'); + if (count($commit)) { + if (!$repository_api->supportsRelativeLocalCommits()) { + throw new ArcanistUsageException( + "This version control system does not support relative commits."); + } else { + $repository_api->parseRelativeLocalCommit($commit); + } + } + + $any_author = $this->getArgument('any-author'); + $any_status = $this->getArgument('any-status'); + + $query = array( + 'authors' => $any_author + ? null + : array($this->getUserPHID()), + 'status' => $any_status + ? 'status-any' + : 'status-open', + ); + + $revisions = $repository_api->loadWorkingCopyDifferentialRevisions( + $this->getConduit(), + $query); + + if (empty($revisions)) { + $this->writeStatusMessage("No matching revisions.\n"); + return 1; + } + + if ($this->getArgument('id')) { + if (count($revisions) == 1) { + echo idx(head($revisions), 'id'); + return 0; + } else { + $this->writeStatusMessage("More than one matching revision.\n"); + return 1; + } + } + + foreach ($revisions as $revision) { + echo 'D'.$revision['id'].' '.$revision['title']."\n"; + } + + return 0; + } +} diff --git a/src/workflow/which/__init__.php b/src/workflow/which/__init__.php new file mode 100644 index 00000000..a92dfa89 --- /dev/null +++ b/src/workflow/which/__init__.php @@ -0,0 +1,16 @@ +