Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/ui.py @ 27266:4dccc37b87bd
ui: support declaring path push urls as sub-options
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 use of sub-options (config options with
":" in the name) to declare additional behavior for paths.
New sub-options are declared by using the new ``@ui.pathsuboption``
decorator. This decorator serves multiple purposes:
* Declaring which sub-options are registered
* Declaring how a sub-option maps to an attribute on ``path``
instances (this is needed to `hg paths` can render sub-options
and values properly)
* Validation and normalization of config options to attribute
values
* Allows extensions to declare new sub-options without monkeypatching
* Allows extensions to overwrite built-in behavior for sub-option
handling
As convenient as the new option registration decorator is, extensions
(and even core functionality) may still need an additional hook point
to perform finalization of path instances. For example, they may wish
to validate that multiple options/attributes aren't conflicting with
each other. This hook point could be added later, if needed.
To prove this new functionality works, we implement the "pushurl"
path sub-option. This option declares the URL that `hg push` should
use by default.
We require that "pushurl" is an actual URL. This requirement might be
controversial and could be dropped if there is opposition. However,
objectors should read the complicated code in ui.path.__init__ and
commands.push for resolving non-URL values before making a judgement.
We also don't allow #fragment in the URLs. I intend to introduce a
":pushrev" (or similar) option to define a revset to control which
revisions are pushed when "-r <rev>" isn't passed into `hg push`.
This is much more powerful than #fragment and I don't think #fragment
is useful enough to continue supporting.
The [paths] section of the "config" help page has been updated
significantly. `hg paths` has been taught to display path sub-options.
The docs mention that "default-push" is now deprecated. However, there
are several references to it that need to be cleaned up. A large part
of this is converting more consumers to the new paths API. This will
happen naturally as more path sub-options are added and more and more
components need to access them.
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Sat, 05 Dec 2015 21:11:04 -0800 |
parents | 47539ea08bdb |
children | 82910fdc216f |
comparison
equal
deleted
inserted
replaced
27265:47539ea08bdb | 27266:4dccc37b87bd |
---|---|
1061 loaded. | 1061 loaded. |
1062 """ | 1062 """ |
1063 def __init__(self, ui): | 1063 def __init__(self, ui): |
1064 dict.__init__(self) | 1064 dict.__init__(self) |
1065 | 1065 |
1066 for name, loc in ui.configitems('paths'): | 1066 for name, loc in ui.configitems('paths', ignoresub=True): |
1067 # No location is the same as not existing. | 1067 # No location is the same as not existing. |
1068 if not loc: | 1068 if not loc: |
1069 continue | 1069 continue |
1070 | 1070 |
1071 # TODO ignore default-push once all consumers stop referencing it | 1071 # TODO ignore default-push once all consumers stop referencing it |
1072 # since it is handled specifically below. | 1072 # since it is handled specifically below. |
1073 | 1073 |
1074 self[name] = path(ui, name, rawloc=loc) | 1074 loc, sub = ui.configsuboptions('paths', name) |
1075 self[name] = path(ui, name, rawloc=loc, suboptions=sub) | |
1075 | 1076 |
1076 # Handle default-push, which is a one-off that defines the push URL for | 1077 # Handle default-push, which is a one-off that defines the push URL for |
1077 # the "default" path. | 1078 # the "default" path. |
1078 defaultpush = ui.config('paths', 'default-push') | 1079 defaultpush = ui.config('paths', 'default-push') |
1079 if defaultpush: | 1080 if defaultpush: |
1118 raise error.RepoError(_('repository %s does not exist') % | 1119 raise error.RepoError(_('repository %s does not exist') % |
1119 name) | 1120 name) |
1120 | 1121 |
1121 assert False | 1122 assert False |
1122 | 1123 |
1124 _pathsuboptions = {} | |
1125 | |
1126 def pathsuboption(option, attr): | |
1127 """Decorator used to declare a path sub-option. | |
1128 | |
1129 Arguments are the sub-option name and the attribute it should set on | |
1130 ``path`` instances. | |
1131 | |
1132 The decorated function will receive as arguments a ``ui`` instance, | |
1133 ``path`` instance, and the string value of this option from the config. | |
1134 The function should return the value that will be set on the ``path`` | |
1135 instance. | |
1136 | |
1137 This decorator can be used to perform additional verification of | |
1138 sub-options and to change the type of sub-options. | |
1139 """ | |
1140 def register(func): | |
1141 _pathsuboptions[option] = (attr, func) | |
1142 return func | |
1143 return register | |
1144 | |
1145 @pathsuboption('pushurl', 'pushloc') | |
1146 def pushurlpathoption(ui, path, value): | |
1147 u = util.url(value) | |
1148 # Actually require a URL. | |
1149 if not u.scheme: | |
1150 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name) | |
1151 return None | |
1152 | |
1153 # Don't support the #foo syntax in the push URL to declare branch to | |
1154 # push. | |
1155 if u.fragment: | |
1156 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; ' | |
1157 'ignoring)\n') % path.name) | |
1158 u.fragment = None | |
1159 | |
1160 return str(u) | |
1161 | |
1123 class path(object): | 1162 class path(object): |
1124 """Represents an individual path and its configuration.""" | 1163 """Represents an individual path and its configuration.""" |
1125 | 1164 |
1126 def __init__(self, ui, name, rawloc=None, pushloc=None): | 1165 def __init__(self, ui, name, rawloc=None, suboptions=None): |
1127 """Construct a path from its config options. | 1166 """Construct a path from its config options. |
1128 | 1167 |
1129 ``ui`` is the ``ui`` instance the path is coming from. | 1168 ``ui`` is the ``ui`` instance the path is coming from. |
1130 ``name`` is the symbolic name of the path. | 1169 ``name`` is the symbolic name of the path. |
1131 ``rawloc`` is the raw location, as defined in the config. | 1170 ``rawloc`` is the raw location, as defined in the config. |
1149 self.branch = branch | 1188 self.branch = branch |
1150 | 1189 |
1151 self.name = name | 1190 self.name = name |
1152 self.rawloc = rawloc | 1191 self.rawloc = rawloc |
1153 self.loc = str(u) | 1192 self.loc = str(u) |
1154 self.pushloc = pushloc | |
1155 | 1193 |
1156 # When given a raw location but not a symbolic name, validate the | 1194 # When given a raw location but not a symbolic name, validate the |
1157 # location is valid. | 1195 # location is valid. |
1158 if not name and not u.scheme and not self._isvalidlocalpath(self.loc): | 1196 if not name and not u.scheme and not self._isvalidlocalpath(self.loc): |
1159 raise ValueError('location is not a URL or path to a local ' | 1197 raise ValueError('location is not a URL or path to a local ' |
1160 'repo: %s' % rawloc) | 1198 'repo: %s' % rawloc) |
1199 | |
1200 suboptions = suboptions or {} | |
1201 | |
1202 # Now process the sub-options. If a sub-option is registered, its | |
1203 # attribute will always be present. The value will be None if there | |
1204 # was no valid sub-option. | |
1205 for suboption, (attr, func) in _pathsuboptions.iteritems(): | |
1206 if suboption not in suboptions: | |
1207 setattr(self, attr, None) | |
1208 continue | |
1209 | |
1210 value = func(ui, self, suboptions[suboption]) | |
1211 setattr(self, attr, value) | |
1161 | 1212 |
1162 def _isvalidlocalpath(self, path): | 1213 def _isvalidlocalpath(self, path): |
1163 """Returns True if the given path is a potentially valid repository. | 1214 """Returns True if the given path is a potentially valid repository. |
1164 This is its own function so that extensions can change the definition of | 1215 This is its own function so that extensions can change the definition of |
1165 'valid' in this case (like when pulling from a git repo into a hg | 1216 'valid' in this case (like when pulling from a git repo into a hg |
1166 one).""" | 1217 one).""" |
1167 return os.path.isdir(os.path.join(path, '.hg')) | 1218 return os.path.isdir(os.path.join(path, '.hg')) |
1219 | |
1220 @property | |
1221 def suboptions(self): | |
1222 """Return sub-options and their values for this path. | |
1223 | |
1224 This is intended to be used for presentation purposes. | |
1225 """ | |
1226 d = {} | |
1227 for subopt, (attr, _func) in _pathsuboptions.iteritems(): | |
1228 value = getattr(self, attr) | |
1229 if value is not None: | |
1230 d[subopt] = value | |
1231 return d | |
1168 | 1232 |
1169 # we instantiate one globally shared progress bar to avoid | 1233 # we instantiate one globally shared progress bar to avoid |
1170 # competing progress bars when multiple UI objects get created | 1234 # competing progress bars when multiple UI objects get created |
1171 _progresssingleton = None | 1235 _progresssingleton = None |
1172 | 1236 |