[PATCH] ui: support declaring repositories in [remotes] section
Gregory Szorc
gregory.szorc at gmail.com
Fri Nov 13 06:13:26 UTC 2015
# HG changeset patch
# User Gregory Szorc <gregory.szorc at gmail.com>
# Date 1447395178 28800
# Thu Nov 12 22:12:58 2015 -0800
# Node ID eeed46507676c60df3febc358b5d301ed4470855
# Parent 3309714ded262f1bb3e32dd552b01d6926de06d5
ui: support declaring repositories in [remotes] section
Power users often want to apply per-path configuration options. For
example, they may want to declare an alternate URL for push operations
or declare a revset of revisions to push when `hg push` is used
(as opposed to attempting to push all revisions by default).
This patch establishes the [remotes] section (bikeshedding invited).
Unlike the [paths] sections, the [remotes] section reserves options
with "." in them for declaring per-path/remote attributes. (The
[paths] section interprets options with "." as a valid alias and
attempting to shoehorn "." attribute interpretation into [paths]
runs the risk of ambiguous behavior when a user defined an alias
with "." that happens to conflict with an attribute defining
special behavior. So, a dedicated section with different parsing
semantics is necessary to avoid ambiguity.)
Initially, we only support the "pushurl" per-path attribute. However,
additional attributes can be added relatively easily.
This patch requires that location values are URLs and that the URLs
don't contain #fragment parts. The URL requirement might be
controversial. I'm implementing it to see what people say. If people
don't like it, I can remove it with little objection. However, I would
encourage people to read the location interpretation logic in
ui.path.__init__ and commands.push before rushing to judgement. The
code and behavior is a bit complicated and requiring URLs does reduce
ambiguity over how values should be interpretted. The banning of
#fragment from locations should hopefully be less controversial. As
a refresher, these declare the default branch to push to. Since
branches aren't as popular as they once were, I'm guessing few will
miss this feature. Plus, I intend to introduce a "pushrev" or
"defaultpushrev" per-path attribute that declares a revset to be used
to select what will be pushed when no -r argument is given to
`hg push`. This feature will restore the capabilities of #fragment
in the URL but will be more powerful and more useful.
In the spirit of full disclosure, I believe mpm had signed off on
[urls] for the new section name. However, there has since been some
discussion on introducing "remote" "refs" into core. I'm not sure
what the user-facing nomenclature for that is going to be. But if it
is "remotes", then IMO the section for declaring repos should be
[remotes], as implemented in this patch.
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -1149,8 +1149,9 @@ used from the command line. Example::
To push to the path defined in ``my_path`` run the command::
hg push my_path
+(See ``[remotes]`` for a more advanced method of declaring repositories.)
``phases``
----------
@@ -1283,8 +1284,30 @@ have a definite end point.
``assume-tty``
If true, ALWAYS show a progress bar, unless disable is given.
+``remotes``
+-----------
+
+Declare symbolic names to remote repositories along with additional attributes
+defining behavior.
+
+Options without "." declare the symbolic name of a repository and its location.
+Options with "." declare attributes for a repository. For example::
+
+ [remotes]
+ my_remote = https://example.com/repo
+ my_remote.pushurl = ssh://example.com/repo
+
+The followeing per-entry attributes are recognized:
+
+``pushurl``
+ The URL to use for push operations. If not defined, the location defined
+ by the attribute-less config option is used.
+
+(See ``[paths]`` for a simpler but less powerful method to declare symbolic
+names to repositories.)
+
``revsetalias``
---------------
Alias definitions for revsets. See :hg:`help revsets` for details.
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1012,8 +1012,10 @@ class paths(dict):
"""
def __init__(self, ui):
dict.__init__(self)
+ # Traditionally, paths are defined in the [paths] section, one path per
+ # entry (with the exception of "default-push" which is special).
for name, loc in ui.configitems('paths'):
# No location is the same as not existing.
if not loc:
continue
@@ -1032,8 +1034,50 @@ class paths(dict):
if 'default' not in self:
self['default'] = path('default', rawloc=defaultpush)
self['default']._pushloc = defaultpush
+ # The [remotes] section defines paths in a more advanced manner. Options
+ # of the form "X" are just like regular paths. "X.a" define per-path
+ # attributes which can be used to define custom behavior.
+ for k, v in ui.configitems('remotes'):
+ # Looks like an attribute.
+ if '.' in k:
+ continue
+
+ # Unlike [paths], we perform some stricter checking on [remotes]
+ # entries.
+ if not v:
+ ui.warn(_('(remotes.%s is empty; ignoring)\n') % k)
+ continue
+
+ # Require a URL.
+ u = util.url(v)
+ if not u.scheme:
+ ui.warn(_('(remotes.%s not a URL; ignoring)\n') % k)
+ continue
+
+ if u.fragment:
+ ui.warn(_('("#fragment" in remotes.%s not allowed; '
+ 'ignoring)\n') % k)
+ continue
+
+ pushurl = ui.config('remotes', '%s.pushurl' % k)
+ if pushurl:
+ u = util.url(pushurl)
+ # Actually require a URL.
+ if not u.scheme:
+ ui.warn(_('(remotes.%s.pushurl not a URL; ignoring)\n') % k)
+ pushurl = None
+
+ # Don't support the #foo syntax in the push URL to declare
+ # branch to push.
+ if u.fragment:
+ ui.warn(_('("#fragment" in remotes.%s.pushurl not '
+ 'supported; ignoring)\n') % k)
+ pushurl = None
+
+ self[k] = path(k, rawloc=v, pushloc=pushurl)
+
def getpath(self, name, default=None):
"""Return a ``path`` from a string, falling back to a default.
``name`` can be a named path or locations. Locations are filesystem
diff --git a/tests/test-default-push.t b/tests/test-default-push.t
--- a/tests/test-default-push.t
+++ b/tests/test-default-push.t
@@ -68,4 +68,27 @@ Pushing to a path that isn't defined sho
$ hg --cwd b push doesnotexist
abort: repository doesnotexist does not exist!
[255]
+
+[remotes] .pushurl is used when defined
+
+ $ hg -q clone a pushurlsource
+ $ hg -q clone a pushurldest
+ $ cd pushurlsource
+ $ cat > .hg/hgrc << EOF
+ > [remotes]
+ > default = https://example.com/not/relevant
+ > default.pushurl = file://`pwd`/../pushurldest
+ > EOF
+
+ $ touch pushurl
+ $ hg -q commit -A -m 'add pushurl'
+ $ hg push
+ pushing to file:/*/$TESTTMP/pushurlsource/../pushurldest (glob)
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+
+ $ cd ..
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -1193,10 +1193,11 @@ Test section lookup
-------
Assigns symbolic names to repositories. The left side is the symbolic
name, and the right gives the directory or URL that is the location of the
- repository. Default paths can be declared by setting the following
- entries.
+ repository.
+
+ Default paths can be declared by setting the following entries.
"default"
Directory or URL to use when pulling if no source is specified.
(default: repository from which the current repository was cloned)
@@ -1214,8 +1215,10 @@ Test section lookup
To push to the path defined in "my_path" run the command:
hg push my_path
+ (See "[remotes]" for a more advanced method of declaring repositories.)
+
$ hg help glossary.mcguffin
abort: help section not found
[255]
diff --git a/tests/test-paths.t b/tests/test-paths.t
--- a/tests/test-paths.t
+++ b/tests/test-paths.t
@@ -43,8 +43,71 @@
$ hg paths -q unknown
[1]
$ cd ..
+[remotes] is parsed
+
+ $ hg init remotes
+ $ cd remotes
+
+ $ cat > .hg/hgrc << EOF
+ > [remotes]
+ > path0 = https://example.com/path0
+ > path1 = https://example.com/path1
+ > EOF
+
+ $ hg paths
+ path0 = https://example.com/path0
+ path1 = https://example.com/path1
+
+[remotes] overwrites [paths]
+
+ $ cat > .hg/hgrc << EOF
+ > [paths]
+ > old = https://example.com/old
+ > legacy = https://example.com/legacy
+ > [remotes]
+ > legacy = https://example.com/legacy-new
+ > EOF
+
+ $ hg paths
+ legacy = https://example.com/legacy-new
+ old = https://example.com/old
+
+Warnings on non-URLs
+
+ $ cat > .hg/hgrc << EOF
+ > [remotes]
+ > nourl = /path/to/nothing
+ > empty =
+ > relative = ../relative
+ > proper = https://example.com/repo
+ > proper.pushurl = /not/a/url
+ > EOF
+
+ $ hg paths
+ (remotes.nourl not a URL; ignoring)
+ (remotes.empty is empty; ignoring)
+ (remotes.relative not a URL; ignoring)
+ (remotes.proper.pushurl not a URL; ignoring)
+ proper = https://example.com/repo
+
+#fragment is not allowed
+
+ $ cat > .hg/hgrc << EOF
+ > [remotes]
+ > fragment = https://example.com/repo#branch
+ > valid = https://example.com/repo
+ > valid.pushurl = https://example.com/repo#branch
+ > EOF
+
+ $ hg paths
+ ("#fragment" in remotes.fragment not allowed; ignoring)
+ ("#fragment" in remotes.valid.pushurl not supported; ignoring)
+ valid = https://example.com/repo
+
+ $ cd ..
+
'file:' disables [paths] entries for clone destination
$ cat >> $HGRCPATH <<EOF
> [paths]
More information about the Mercurial-devel
mailing list