diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -5364,6 +5364,8 @@ def paths(ui, repo, search=None): else: ui.write("%s = %s\n" % (name, util.hidepassword(path.loc))) + for subopt, value in sorted(path.suboptions.items()): + ui.write('%s:%s = %s\n' % (name, subopt, value)) @command('phase', [('p', 'public', False, _('set changeset phase to public')), diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt --- a/mercurial/help/config.txt +++ b/mercurial/help/config.txt @@ -1127,29 +1127,43 @@ command or with Mercurial Queues extensi ``paths`` --------- -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. +Assigns symbolic names and behavior to repositories. + +Options are symbolic names defining the URL or directory that is the +location of the repository. Example:: + + [paths] + my_server = https://example.com/my_repo + local_path = /home/me/repo + +These symbolic names can be used from the command line. To pull +from ``my_server``: :hg:`pull my_server`. To push to ``local_path``: +:hg:`push local_path`. + +Options containing colons (``:``) denote sub-options that can influence +behavior for that specific path. Example:: + + [paths] + my_server = https://example.com/my_path + my_server:pushurl = ssh://example.com/my_path + +The following sub-options can be defined: + +``pushurl`` + The URL to use for push operations. If not defined, the location + defined by the path's main entry is used. + +The following special named paths exist: ``default`` - Directory or URL to use when pulling if no source is specified. - (default: repository from which the current repository was cloned) + The URL or directory to use when no source or remote is specified. + + :hg:`clone` will automatically define this path to the location the + repository was cloned from. ``default-push`` - Optional. Directory or URL to use when pushing if no destination - is specified. - -Custom paths can be defined by assigning the path to a name that later can be -used from the command line. Example:: - - [paths] - my_path = http://example.com/path - -To push to the path defined in ``my_path`` run the command:: - - hg push my_path - + (deprecated) The URL or directory for the default :hg:`push` location. + ``default:pushurl`` should be used instead. ``phases`` ---------- diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -1063,7 +1063,7 @@ class paths(dict): def __init__(self, ui): dict.__init__(self) - for name, loc in ui.configitems('paths'): + for name, loc in ui.configitems('paths', ignoresub=True): # No location is the same as not existing. if not loc: continue @@ -1071,7 +1071,8 @@ class paths(dict): # TODO ignore default-push once all consumers stop referencing it # since it is handled specifically below. - self[name] = path(ui, name, rawloc=loc) + loc, sub = ui.configsuboptions('paths', name) + self[name] = path(ui, name, rawloc=loc, suboptions=sub) # Handle default-push, which is a one-off that defines the push URL for # the "default" path. @@ -1120,10 +1121,48 @@ class paths(dict): assert False +_pathsuboptions = {} + +def pathsuboption(option, attr): + """Decorator used to declare a path sub-option. + + Arguments are the sub-option name and the attribute it should set on + ``path`` instances. + + The decorated function will receive as arguments a ``ui`` instance, + ``path`` instance, and the string value of this option from the config. + The function should return the value that will be set on the ``path`` + instance. + + This decorator can be used to perform additional verification of + sub-options and to change the type of sub-options. + """ + def register(func): + _pathsuboptions[option] = (attr, func) + return func + return register + +@pathsuboption('pushurl', 'pushloc') +def pushurlpathoption(ui, path, value): + u = util.url(value) + # Actually require a URL. + if not u.scheme: + ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name) + return None + + # Don't support the #foo syntax in the push URL to declare branch to + # push. + if u.fragment: + ui.warn(_('("#fragment" in paths.%s:pushurl not supported; ' + 'ignoring)\n') % path.name) + u.fragment = None + + return str(u) + class path(object): """Represents an individual path and its configuration.""" - def __init__(self, ui, name, rawloc=None, pushloc=None): + def __init__(self, ui, name, rawloc=None, suboptions=None): """Construct a path from its config options. ``ui`` is the ``ui`` instance the path is coming from. @@ -1151,7 +1190,6 @@ class path(object): self.name = name self.rawloc = rawloc self.loc = str(u) - self.pushloc = pushloc # When given a raw location but not a symbolic name, validate the # location is valid. @@ -1159,6 +1197,19 @@ class path(object): raise ValueError('location is not a URL or path to a local ' 'repo: %s' % rawloc) + suboptions = suboptions or {} + + # Now process the sub-options. If a sub-option is registered, its + # attribute will always be present. The value will be None if there + # was no valid sub-option. + for suboption, (attr, func) in _pathsuboptions.iteritems(): + if suboption not in suboptions: + setattr(self, attr, None) + continue + + value = func(ui, self, suboptions[suboption]) + setattr(self, attr, value) + def _isvalidlocalpath(self, path): """Returns True if the given path is a potentially valid repository. This is its own function so that extensions can change the definition of @@ -1166,6 +1217,19 @@ class path(object): one).""" return os.path.isdir(os.path.join(path, '.hg')) + @property + def suboptions(self): + """Return sub-options and their values for this path. + + This is intended to be used for presentation purposes. + """ + d = {} + for subopt, (attr, _func) in _pathsuboptions.iteritems(): + value = getattr(self, attr) + if value is not None: + d[subopt] = value + return d + # we instantiate one globally shared progress bar to avoid # competing progress bars when multiple UI objects get created _progresssingleton = None 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 @@ -69,3 +69,26 @@ Pushing to a path that isn't defined sho $ hg --cwd b push doesnotexist abort: repository doesnotexist does not exist! [255] + +:pushurl is used when defined + + $ hg -q clone a pushurlsource + $ hg -q clone a pushurldest + $ cd pushurlsource + $ cat > .hg/hgrc << EOF + > [paths] + > 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 @@ -1207,28 +1207,43 @@ Test section lookup "paths" ------- - 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. + Assigns symbolic names and behavior to repositories. + + Options are symbolic names defining the URL or directory that is the + location of the repository. Example: + + [paths] + my_server = https://example.com/my_repo + local_path = /home/me/repo + + These symbolic names can be used from the command line. To pull from + "my_server": "hg pull my_server". To push to "local_path": "hg push + local_path". + + Options containing colons (":") denote sub-options that can influence + behavior for that specific path. Example: + + [paths] + my_server = https://example.com/my_path + my_server:pushurl = ssh://example.com/my_path + + The following sub-options can be defined: + + "pushurl" + The URL to use for push operations. If not defined, the location + defined by the path's main entry is used. + + The following special named paths exist: "default" - Directory or URL to use when pulling if no source is specified. - (default: repository from which the current repository was cloned) + The URL or directory to use when no source or remote is specified. + + "hg clone" will automatically define this path to the location the + repository was cloned from. "default-push" - Optional. Directory or URL to use when pushing if no destination is - specified. - - Custom paths can be defined by assigning the path to a name that later can - be used from the command line. Example: - - [paths] - my_path = http://example.com/path - - To push to the path defined in "my_path" run the command: - - hg push my_path + (deprecated) The URL or directory for the default "hg push" location. + "default:pushurl" should be used instead. $ hg help glossary.mcguffin abort: help section not found diff --git a/tests/test-paths.t b/tests/test-paths.t --- a/tests/test-paths.t +++ b/tests/test-paths.t @@ -44,6 +44,59 @@ [1] $ cd .. +sub-options for an undeclared path are ignored + + $ hg init suboptions + $ cd suboptions + + $ cat > .hg/hgrc << EOF + > [paths] + > path0 = https://example.com/path0 + > path1:pushurl = https://example.com/path1 + > EOF + $ hg paths + path0 = https://example.com/path0 + +unknown sub-options aren't displayed + + $ cat > .hg/hgrc << EOF + > [paths] + > path0 = https://example.com/path0 + > path0:foo = https://example.com/path1 + > EOF + + $ hg paths + path0 = https://example.com/path0 + +:pushurl must be a URL + + $ cat > .hg/hgrc << EOF + > [paths] + > default = /path/to/nothing + > default:pushurl = /not/a/url + > EOF + + $ hg paths + (paths.default:pushurl not a URL; ignoring) + default = /path/to/nothing + +#fragment is not allowed in :pushurl + + $ cat > .hg/hgrc << EOF + > [paths] + > default = https://example.com/repo + > invalid = https://example.com/repo + > invalid:pushurl = https://example.com/repo#branch + > EOF + + $ hg paths + ("#fragment" in paths.invalid:pushurl not supported; ignoring) + default = https://example.com/repo + invalid = https://example.com/repo + invalid:pushurl = https://example.com/repo + + $ cd .. + 'file:' disables [paths] entries for clone destination $ cat >> $HGRCPATH <