comparison mercurial/ui.py @ 46906:33524c46a092

urlutil: extract `path` related code into a new module They are a lot of code related to url and path handling scattering into various large module. To consolidate the code before doing more change (for defining "multi-path"), we gather it together. Differential Revision: https://phab.mercurial-scm.org/D10373
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Sun, 11 Apr 2021 23:54:35 +0200
parents 395cf404e76a
children ffd3e823a7e5
comparison
equal deleted inserted replaced
46905:95a5ed7db9ca 46906:33524c46a092
24 from .i18n import _ 24 from .i18n import _
25 from .node import hex 25 from .node import hex
26 from .pycompat import ( 26 from .pycompat import (
27 getattr, 27 getattr,
28 open, 28 open,
29 setattr,
30 ) 29 )
31 30
32 from . import ( 31 from . import (
33 color, 32 color,
34 config, 33 config,
46 from .utils import ( 45 from .utils import (
47 dateutil, 46 dateutil,
48 procutil, 47 procutil,
49 resourceutil, 48 resourceutil,
50 stringutil, 49 stringutil,
50 urlutil,
51 ) 51 )
52 52
53 urlreq = util.urlreq 53 urlreq = util.urlreq
54 54
55 # for use with str.translate(None, _keepalnum), to keep just alphanumerics 55 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
1047 1047
1048 return loc 1048 return loc
1049 1049
1050 @util.propertycache 1050 @util.propertycache
1051 def paths(self): 1051 def paths(self):
1052 return paths(self) 1052 return urlutil.paths(self)
1053 1053
1054 def getpath(self, *args, **kwargs): 1054 def getpath(self, *args, **kwargs):
1055 """see paths.getpath for details 1055 """see paths.getpath for details
1056 1056
1057 This method exist as `getpath` need a ui for potential warning message. 1057 This method exist as `getpath` need a ui for potential warning message.
2178 _(b"ui.available-memory value is invalid ('%s')") % value 2178 _(b"ui.available-memory value is invalid ('%s')") % value
2179 ) 2179 )
2180 return util._estimatememory() 2180 return util._estimatememory()
2181 2181
2182 2182
2183 class paths(dict):
2184 """Represents a collection of paths and their configs.
2185
2186 Data is initially derived from ui instances and the config files they have
2187 loaded.
2188 """
2189
2190 def __init__(self, ui):
2191 dict.__init__(self)
2192
2193 for name, loc in ui.configitems(b'paths', ignoresub=True):
2194 # No location is the same as not existing.
2195 if not loc:
2196 continue
2197 loc, sub_opts = ui.configsuboptions(b'paths', name)
2198 self[name] = path(ui, name, rawloc=loc, suboptions=sub_opts)
2199
2200 for name, p in sorted(self.items()):
2201 p.chain_path(ui, self)
2202
2203 def getpath(self, ui, name, default=None):
2204 """Return a ``path`` from a string, falling back to default.
2205
2206 ``name`` can be a named path or locations. Locations are filesystem
2207 paths or URIs.
2208
2209 Returns None if ``name`` is not a registered path, a URI, or a local
2210 path to a repo.
2211 """
2212 # Only fall back to default if no path was requested.
2213 if name is None:
2214 if not default:
2215 default = ()
2216 elif not isinstance(default, (tuple, list)):
2217 default = (default,)
2218 for k in default:
2219 try:
2220 return self[k]
2221 except KeyError:
2222 continue
2223 return None
2224
2225 # Most likely empty string.
2226 # This may need to raise in the future.
2227 if not name:
2228 return None
2229
2230 try:
2231 return self[name]
2232 except KeyError:
2233 # Try to resolve as a local path or URI.
2234 try:
2235 # we pass the ui instance are warning might need to be issued
2236 return path(ui, None, rawloc=name)
2237 except ValueError:
2238 raise error.RepoError(_(b'repository %s does not exist') % name)
2239
2240
2241 _pathsuboptions = {}
2242
2243
2244 def pathsuboption(option, attr):
2245 """Decorator used to declare a path sub-option.
2246
2247 Arguments are the sub-option name and the attribute it should set on
2248 ``path`` instances.
2249
2250 The decorated function will receive as arguments a ``ui`` instance,
2251 ``path`` instance, and the string value of this option from the config.
2252 The function should return the value that will be set on the ``path``
2253 instance.
2254
2255 This decorator can be used to perform additional verification of
2256 sub-options and to change the type of sub-options.
2257 """
2258
2259 def register(func):
2260 _pathsuboptions[option] = (attr, func)
2261 return func
2262
2263 return register
2264
2265
2266 @pathsuboption(b'pushurl', b'pushloc')
2267 def pushurlpathoption(ui, path, value):
2268 u = util.url(value)
2269 # Actually require a URL.
2270 if not u.scheme:
2271 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
2272 return None
2273
2274 # Don't support the #foo syntax in the push URL to declare branch to
2275 # push.
2276 if u.fragment:
2277 ui.warn(
2278 _(
2279 b'("#fragment" in paths.%s:pushurl not supported; '
2280 b'ignoring)\n'
2281 )
2282 % path.name
2283 )
2284 u.fragment = None
2285
2286 return bytes(u)
2287
2288
2289 @pathsuboption(b'pushrev', b'pushrev')
2290 def pushrevpathoption(ui, path, value):
2291 return value
2292
2293
2294 class path(object):
2295 """Represents an individual path and its configuration."""
2296
2297 def __init__(self, ui, name, rawloc=None, suboptions=None):
2298 """Construct a path from its config options.
2299
2300 ``ui`` is the ``ui`` instance the path is coming from.
2301 ``name`` is the symbolic name of the path.
2302 ``rawloc`` is the raw location, as defined in the config.
2303 ``pushloc`` is the raw locations pushes should be made to.
2304
2305 If ``name`` is not defined, we require that the location be a) a local
2306 filesystem path with a .hg directory or b) a URL. If not,
2307 ``ValueError`` is raised.
2308 """
2309 if not rawloc:
2310 raise ValueError(b'rawloc must be defined')
2311
2312 # Locations may define branches via syntax <base>#<branch>.
2313 u = util.url(rawloc)
2314 branch = None
2315 if u.fragment:
2316 branch = u.fragment
2317 u.fragment = None
2318
2319 self.url = u
2320 # the url from the config/command line before dealing with `path://`
2321 self.raw_url = u.copy()
2322 self.branch = branch
2323
2324 self.name = name
2325 self.rawloc = rawloc
2326 self.loc = b'%s' % u
2327
2328 self._validate_path()
2329
2330 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
2331 self._own_sub_opts = {}
2332 if suboptions is not None:
2333 self._own_sub_opts = suboptions.copy()
2334 sub_opts.update(suboptions)
2335 self._all_sub_opts = sub_opts.copy()
2336
2337 self._apply_suboptions(ui, sub_opts)
2338
2339 def chain_path(self, ui, paths):
2340 if self.url.scheme == b'path':
2341 assert self.url.path is None
2342 try:
2343 subpath = paths[self.url.host]
2344 except KeyError:
2345 m = _('cannot use `%s`, "%s" is not a known path')
2346 m %= (self.rawloc, self.url.host)
2347 raise error.Abort(m)
2348 if subpath.raw_url.scheme == b'path':
2349 m = _('cannot use `%s`, "%s" is also define as a `path://`')
2350 m %= (self.rawloc, self.url.host)
2351 raise error.Abort(m)
2352 self.url = subpath.url
2353 self.rawloc = subpath.rawloc
2354 self.loc = subpath.loc
2355 if self.branch is None:
2356 self.branch = subpath.branch
2357 else:
2358 base = self.rawloc.rsplit(b'#', 1)[0]
2359 self.rawloc = b'%s#%s' % (base, self.branch)
2360 suboptions = subpath._all_sub_opts.copy()
2361 suboptions.update(self._own_sub_opts)
2362 self._apply_suboptions(ui, suboptions)
2363
2364 def _validate_path(self):
2365 # When given a raw location but not a symbolic name, validate the
2366 # location is valid.
2367 if (
2368 not self.name
2369 and not self.url.scheme
2370 and not self._isvalidlocalpath(self.loc)
2371 ):
2372 raise ValueError(
2373 b'location is not a URL or path to a local '
2374 b'repo: %s' % self.rawloc
2375 )
2376
2377 def _apply_suboptions(self, ui, sub_options):
2378 # Now process the sub-options. If a sub-option is registered, its
2379 # attribute will always be present. The value will be None if there
2380 # was no valid sub-option.
2381 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
2382 if suboption not in sub_options:
2383 setattr(self, attr, None)
2384 continue
2385
2386 value = func(ui, self, sub_options[suboption])
2387 setattr(self, attr, value)
2388
2389 def _isvalidlocalpath(self, path):
2390 """Returns True if the given path is a potentially valid repository.
2391 This is its own function so that extensions can change the definition of
2392 'valid' in this case (like when pulling from a git repo into a hg
2393 one)."""
2394 try:
2395 return os.path.isdir(os.path.join(path, b'.hg'))
2396 # Python 2 may return TypeError. Python 3, ValueError.
2397 except (TypeError, ValueError):
2398 return False
2399
2400 @property
2401 def suboptions(self):
2402 """Return sub-options and their values for this path.
2403
2404 This is intended to be used for presentation purposes.
2405 """
2406 d = {}
2407 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
2408 value = getattr(self, attr)
2409 if value is not None:
2410 d[subopt] = value
2411 return d
2412
2413
2414 # we instantiate one globally shared progress bar to avoid 2183 # we instantiate one globally shared progress bar to avoid
2415 # competing progress bars when multiple UI objects get created 2184 # competing progress bars when multiple UI objects get created
2416 _progresssingleton = None 2185 _progresssingleton = None
2417 2186
2418 2187