Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14385748
D21343.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
D21343.diff
View Options
diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,6 @@
# Generated shell completion rulesets.
/support/shell/rules/
+
+# Python extension compiled files.
+/support/hg/arc-hg.pyc
diff --git a/src/land/engine/ArcanistMercurialLandEngine.php b/src/land/engine/ArcanistMercurialLandEngine.php
--- a/src/land/engine/ArcanistMercurialLandEngine.php
+++ b/src/land/engine/ArcanistMercurialLandEngine.php
@@ -7,6 +7,16 @@
$api = $this->getRepositoryAPI();
$log = $this->getLogEngine();
+ // TODO: In Mercurial, you normally can not create a branch and a bookmark
+ // 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
+ // "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()
->withIsActive(true)
->execute();
@@ -436,27 +446,156 @@
$api = $this->getRepositoryAPI();
$log = $this->getLogEngine();
- // TODO: Support bookmarks.
- // TODO: Deal with bookmark save/restore behavior.
- // TODO: Raise a good error message when the ref does not exist.
+ // See T9948. If the user specified "--into X", we don't know if it's a
+ // branch, a bookmark, or a symbol which doesn't exist yet.
+
+ // 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
+ // 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(
- 'pull -b %s -- %s',
- $target->getRef(),
+ '%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()));
- // TODO: Deal with errors.
- // TODO: Deal with multiple branch heads.
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Remote branch has multiple heads.'));
+ }
+
+ $branch = head($branches);
- list($stdout) = $api->execxLocal(
- 'log --rev %s --template %s --',
- hgsprintf(
- 'last(ancestors(%s) and !outgoing(%s))',
+ $target_hash = $branch['node'];
+ $is_branch = true;
+ }
+
+ if ($is_branch) {
+ $err = $this->newPassthru(
+ 'pull -b %s -- %s',
$target->getRef(),
- $target->getRemote()),
- '{node}');
+ $target->getRemote());
+ } else {
+
+ // NOTE: This may have side effects:
+ //
+ // - 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.
+
+ $err = $this->newPassthru(
+ 'pull -B %s -- %s',
+ $target->getRef(),
+ $target->getRemote());
+ }
+
+ // 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
+ // more reasonable if it's cheap.
- return trim($stdout);
+ return $target_hash;
}
protected function selectCommits($into_commit, array $symbols) {
diff --git a/src/repository/api/ArcanistMercurialAPI.php b/src/repository/api/ArcanistMercurialAPI.php
--- a/src/repository/api/ArcanistMercurialAPI.php
+++ b/src/repository/api/ArcanistMercurialAPI.php
@@ -1012,4 +1012,16 @@
return new ArcanistMercurialRepositoryRemoteQuery();
}
+
+ public function getMercurialExtensionArguments() {
+ $path = phutil_get_library_root('arcanist');
+ $path = dirname($path);
+ $path = $path.'/support/hg/arc-hg.py';
+
+ return array(
+ '--config',
+ 'extensions.arc-hg='.$path,
+ );
+ }
+
}
diff --git a/support/hg/arc-hg.py b/support/hg/arc-hg.py
new file mode 100644
--- /dev/null
+++ b/support/hg/arc-hg.py
@@ -0,0 +1,90 @@
+from __future__ import absolute_import
+
+import os
+import json
+
+from mercurial import (
+ cmdutil,
+ bookmarks,
+ bundlerepo,
+ error,
+ hg,
+ i18n,
+ node,
+ registrar,
+)
+
+_ = i18n._
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+@command(
+ "arc-ls-remote",
+ [('', 'output', '',
+ _('file to output refs to'), _('FILE')),
+ ] + cmdutil.remoteopts,
+ _('[--output FILENAME] [SOURCE]'))
+def lsremote(ui, repo, source="default", **opts):
+ """list markers in a remote
+
+ Show the current branch heads and bookmarks in a specified path/URL or the
+ default pull location.
+
+ Markers are printed to stdout in JSON.
+
+ (This is an Arcanist extension to Mercurial.)
+
+ Returns 0 if listing the markers succeeds, 1 otherwise.
+ """
+
+ # Disable status output from fetching a remote.
+ ui.quiet = True
+
+ source, branches = hg.parseurl(ui.expandpath(source))
+ remote = hg.peer(repo, opts, source)
+
+ markers = []
+
+ bundle, remotebranches, cleanup = bundlerepo.getremotechanges(
+ ui,
+ repo,
+ remote)
+
+ try:
+ for n in remotebranches:
+ ctx = bundle[n]
+ markers.append({
+ 'type': 'branch',
+ 'name': ctx.branch(),
+ 'node': node.hex(ctx.node()),
+ })
+ finally:
+ cleanup()
+
+ with remote.commandexecutor() as e:
+ remotemarks = bookmarks.unhexlifybookmarks(e.callcommand('listkeys', {
+ 'namespace': 'bookmarks',
+ }).result())
+
+ for mark in remotemarks:
+ markers.append({
+ 'type': 'bookmark',
+ 'name': mark,
+ 'node': node.hex(remotemarks[mark]),
+ })
+
+ json_opts = {
+ 'indent': 2,
+ 'sort_keys': True,
+ }
+
+ output_file = opts.get('output')
+ if output_file:
+ if os.path.exists(output_file):
+ raise error.Abort(_('File "%s" already exists.' % output_file))
+ with open(output_file, 'w+') as f:
+ json.dump(markers, f, **json_opts)
+ else:
+ print json.dumps(markers, output_file, **json_opts)
+
+ return 0
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Dec 22, 12:32 PM (18 h, 43 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6918261
Default Alt Text
D21343.diff (9 KB)
Attached To
Mode
D21343: Disambiguate various types of Mercurial remote markers with "hg arc-ls-remote"
Attached
Detach File
Event Timeline
Log In to Comment