Changeset View
Changeset View
Standalone View
Standalone View
src/land/engine/ArcanistMercurialLandEngine.php
| <?php | <?php | ||||
| final class ArcanistMercurialLandEngine | final class ArcanistMercurialLandEngine | ||||
| extends ArcanistLandEngine { | extends ArcanistLandEngine { | ||||
| protected function getDefaultSymbols() { | protected function getDefaultSymbols() { | ||||
| $api = $this->getRepositoryAPI(); | $api = $this->getRepositoryAPI(); | ||||
| $log = $this->getLogEngine(); | $log = $this->getLogEngine(); | ||||
| // TODO: In Mercurial, you normally can not create a branch and a bookmark | |||||
Lint: TODO Comment: This comment has a TODO. | |||||
| // with the same name. However, you can fetch a branch or bookmark from | |||||
| // a remote that has the same name as a local branch or bookmark of the | |||||
| // other type, and end up with a local branch and bookmark with the same | |||||
| // name. We should detect this and treat it as an error. | |||||
| // TODO: In Mercurial, you can create local bookmarks named | |||||
Lint: TODO Comment This comment has a TODO. Lint: TODO Comment: This comment has a TODO. | |||||
| // "default@default" and similar which do not surive a round trip through | |||||
| // a remote. Possibly, we should disallow interacting with these bookmarks. | |||||
| $markers = $api->newMarkerRefQuery() | $markers = $api->newMarkerRefQuery() | ||||
| ->withIsActive(true) | ->withIsActive(true) | ||||
| ->execute(); | ->execute(); | ||||
| $bookmark = null; | $bookmark = null; | ||||
| foreach ($markers as $marker) { | foreach ($markers as $marker) { | ||||
| if ($marker->isBookmark()) { | if ($marker->isBookmark()) { | ||||
| $bookmark = $marker->getName(); | $bookmark = $marker->getName(); | ||||
| ▲ Show 20 Lines • Show All 413 Lines • ▼ Show 20 Lines | protected function selectIntoCommit() { | ||||
| return null; | return null; | ||||
| } | } | ||||
| private function fetchTarget(ArcanistLandTarget $target) { | private function fetchTarget(ArcanistLandTarget $target) { | ||||
| $api = $this->getRepositoryAPI(); | $api = $this->getRepositoryAPI(); | ||||
| $log = $this->getLogEngine(); | $log = $this->getLogEngine(); | ||||
| // TODO: Support bookmarks. | // See T9948. If the user specified "--into X", we don't know if it's a | ||||
| // TODO: Deal with bookmark save/restore behavior. | // branch, a bookmark, or a symbol which doesn't exist yet. | ||||
| // TODO: Raise a good error message when the ref does not exist. | |||||
| // In native Mercurial it is difficult to figure this out, so we use | |||||
| // an extension to provide a command which works like "git ls-remote". | |||||
| // NOTE: We're using passthru on this because it's a remote command and | |||||
| // may prompt the user for credentials. | |||||
| // TODO: This is fairly silly/confusing to show to users in the common | |||||
Lint: TODO Comment This comment has a TODO. Lint: TODO Comment: This comment has a TODO. | |||||
| // case where it does not require credentials, particularly because the | |||||
| // actual command line is full of nonsense. | |||||
| $tmpfile = new TempFile(); | |||||
| Filesystem::remove($tmpfile); | |||||
| $err = $this->newPassthru( | |||||
| '%Ls arc-ls-remote --output %s -- %s', | |||||
| $api->getMercurialExtensionArguments(), | |||||
| phutil_string_cast($tmpfile), | |||||
| $target->getRemote()); | |||||
| if ($err) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Call to "hg arc-ls-remote" failed with error "%s".', | |||||
| $err)); | |||||
| } | |||||
| $raw_data = Filesystem::readFile($tmpfile); | |||||
| unset($tmpfile); | |||||
| $markers = phutil_json_decode($raw_data); | |||||
| $target_name = $target->getRef(); | |||||
| $bookmarks = array(); | |||||
| $branches = array(); | |||||
| foreach ($markers as $marker) { | |||||
| if ($marker['name'] !== $target_name) { | |||||
| continue; | |||||
| } | |||||
| if ($marker['type'] === 'bookmark') { | |||||
| $bookmarks[] = $marker; | |||||
| } else { | |||||
| $branches[] = $marker; | |||||
| } | |||||
| } | |||||
| if (!$bookmarks && !$branches) { | |||||
| throw new PhutilArgumentUsageException( | |||||
| pht( | |||||
| 'Remote "%s" has no bookmark or branch named "%s".', | |||||
| $target->getRemote(), | |||||
| $target->getRef())); | |||||
| } | |||||
| if ($bookmarks && $branches) { | |||||
| echo tsprintf( | |||||
| "\n%!\n%W\n\n", | |||||
| pht('AMBIGUOUS MARKER'), | |||||
| pht( | |||||
| 'In remote "%s", the name "%s" identifies one or more branch '. | |||||
| 'heads and one or more bookmarks. Close, rename, or delete all '. | |||||
| 'but one of these markers, or pull the state you want to merge '. | |||||
| 'into and use "--into-local --into <hash>" to disambiguate the '. | |||||
| 'desired merge target.', | |||||
| $target->getRemote(), | |||||
| $target->getRef())); | |||||
| throw new PhutilArgumentUsageException( | |||||
| pht('Merge target is ambiguous.')); | |||||
| } | |||||
| $is_bookmark = false; | |||||
| $is_branch = false; | |||||
| if ($bookmarks) { | |||||
| if (count($bookmarks) > 1) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Remote "%s" has multiple bookmarks with name "%s". This '. | |||||
| 'is unexpected.', | |||||
| $target->getRemote(), | |||||
| $target->getRef())); | |||||
| } | |||||
| $bookmark = head($bookmarks); | |||||
| $target_hash = $bookmark['node']; | |||||
| $is_bookmark = true; | |||||
| } | |||||
| if ($branches) { | |||||
| if (count($branches) > 1) { | |||||
| echo tsprintf( | |||||
| "\n%!\n%W\n\n", | |||||
| pht('MULTIPLE BRANCH HEADS'), | |||||
| pht( | |||||
| 'Remote "%s" has multiple branch heads named "%s". Close all '. | |||||
| 'but one, or pull the head you want and use "--into-local '. | |||||
| '--into <hash>" to specify an explicit merge target.', | |||||
| $target->getRemote(), | |||||
| $target->getRef())); | |||||
| throw new PhutilArgumentUsageException( | |||||
| pht( | |||||
| 'Remote branch has multiple heads.')); | |||||
| } | |||||
| $branch = head($branches); | |||||
| $target_hash = $branch['node']; | |||||
| $is_branch = true; | |||||
| } | |||||
| if ($is_branch) { | |||||
| $err = $this->newPassthru( | $err = $this->newPassthru( | ||||
| 'pull -b %s -- %s', | 'pull -b %s -- %s', | ||||
| $target->getRef(), | $target->getRef(), | ||||
| $target->getRemote()); | $target->getRemote()); | ||||
| } else { | |||||
| // TODO: Deal with errors. | // NOTE: This may have side effects: | ||||
| // TODO: Deal with multiple branch heads. | // | ||||
| // - It can create a "bookmark@remote" bookmark if there is a local | |||||
| // bookmark with the same name that is not an ancestor. | |||||
| // - It can create an arbitrary number of other bookmarks. | |||||
| // | |||||
| // Since these seem to generally be intentional behaviors in Mercurial, | |||||
| // and should theoretically be familiar to Mercurial users, just accept | |||||
| // them as the cost of doing business. | |||||
| list($stdout) = $api->execxLocal( | $err = $this->newPassthru( | ||||
| 'log --rev %s --template %s --', | 'pull -B %s -- %s', | ||||
| hgsprintf( | |||||
| 'last(ancestors(%s) and !outgoing(%s))', | |||||
| $target->getRef(), | $target->getRef(), | ||||
| $target->getRemote()), | $target->getRemote()); | ||||
| '{node}'); | } | ||||
| // NOTE: It's possible that between the time we ran "ls-remote" and the | |||||
| // time we ran "pull" that the remote changed. | |||||
| // It may even have been rewound or rewritten, in which case we did not | |||||
| // actually fetch the ref we are about to return as a target. For now, | |||||
| // assume this didn't happen: it's so unlikely that it's probably not | |||||
| // worth spending 100ms to check. | |||||
| // TODO: If the Mercurial command server is revived, this check becomes | |||||
Lint: TODO Comment This comment has a TODO. Lint: TODO Comment: This comment has a TODO. | |||||
| // more reasonable if it's cheap. | |||||
| return trim($stdout); | return $target_hash; | ||||
| } | } | ||||
| protected function selectCommits($into_commit, array $symbols) { | protected function selectCommits($into_commit, array $symbols) { | ||||
| assert_instances_of($symbols, 'ArcanistLandSymbol'); | assert_instances_of($symbols, 'ArcanistLandSymbol'); | ||||
| $api = $this->getRepositoryAPI(); | $api = $this->getRepositoryAPI(); | ||||
| $commit_map = array(); | $commit_map = array(); | ||||
| foreach ($symbols as $symbol) { | foreach ($symbols as $symbol) { | ||||
| ▲ Show 20 Lines • Show All 266 Lines • Show Last 20 Lines | |||||
This comment has a TODO.