Mercurial > public > mercurial-scm > hg
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 |