[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