##// END OF EJS Templates
urlutil: extract `path` related code into a new module...
marmoute -
r47668:33524c46 default
parent child Browse files
Show More
@@ -0,0 +1,249 b''
1 # utils.urlutil - code related to [paths] management
2 #
3 # Copyright 2005-2021 Olivia Mackall <olivia@selenic.com> and others
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7 import os
8
9 from ..i18n import _
10 from ..pycompat import (
11 getattr,
12 setattr,
13 )
14 from .. import (
15 error,
16 pycompat,
17 util,
18 )
19
20
21 class paths(dict):
22 """Represents a collection of paths and their configs.
23
24 Data is initially derived from ui instances and the config files they have
25 loaded.
26 """
27
28 def __init__(self, ui):
29 dict.__init__(self)
30
31 for name, loc in ui.configitems(b'paths', ignoresub=True):
32 # No location is the same as not existing.
33 if not loc:
34 continue
35 loc, sub_opts = ui.configsuboptions(b'paths', name)
36 self[name] = path(ui, name, rawloc=loc, suboptions=sub_opts)
37
38 for name, p in sorted(self.items()):
39 p.chain_path(ui, self)
40
41 def getpath(self, ui, name, default=None):
42 """Return a ``path`` from a string, falling back to default.
43
44 ``name`` can be a named path or locations. Locations are filesystem
45 paths or URIs.
46
47 Returns None if ``name`` is not a registered path, a URI, or a local
48 path to a repo.
49 """
50 # Only fall back to default if no path was requested.
51 if name is None:
52 if not default:
53 default = ()
54 elif not isinstance(default, (tuple, list)):
55 default = (default,)
56 for k in default:
57 try:
58 return self[k]
59 except KeyError:
60 continue
61 return None
62
63 # Most likely empty string.
64 # This may need to raise in the future.
65 if not name:
66 return None
67
68 try:
69 return self[name]
70 except KeyError:
71 # Try to resolve as a local path or URI.
72 try:
73 # we pass the ui instance are warning might need to be issued
74 return path(ui, None, rawloc=name)
75 except ValueError:
76 raise error.RepoError(_(b'repository %s does not exist') % name)
77
78
79 _pathsuboptions = {}
80
81
82 def pathsuboption(option, attr):
83 """Decorator used to declare a path sub-option.
84
85 Arguments are the sub-option name and the attribute it should set on
86 ``path`` instances.
87
88 The decorated function will receive as arguments a ``ui`` instance,
89 ``path`` instance, and the string value of this option from the config.
90 The function should return the value that will be set on the ``path``
91 instance.
92
93 This decorator can be used to perform additional verification of
94 sub-options and to change the type of sub-options.
95 """
96
97 def register(func):
98 _pathsuboptions[option] = (attr, func)
99 return func
100
101 return register
102
103
104 @pathsuboption(b'pushurl', b'pushloc')
105 def pushurlpathoption(ui, path, value):
106 u = util.url(value)
107 # Actually require a URL.
108 if not u.scheme:
109 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
110 return None
111
112 # Don't support the #foo syntax in the push URL to declare branch to
113 # push.
114 if u.fragment:
115 ui.warn(
116 _(
117 b'("#fragment" in paths.%s:pushurl not supported; '
118 b'ignoring)\n'
119 )
120 % path.name
121 )
122 u.fragment = None
123
124 return bytes(u)
125
126
127 @pathsuboption(b'pushrev', b'pushrev')
128 def pushrevpathoption(ui, path, value):
129 return value
130
131
132 class path(object):
133 """Represents an individual path and its configuration."""
134
135 def __init__(self, ui, name, rawloc=None, suboptions=None):
136 """Construct a path from its config options.
137
138 ``ui`` is the ``ui`` instance the path is coming from.
139 ``name`` is the symbolic name of the path.
140 ``rawloc`` is the raw location, as defined in the config.
141 ``pushloc`` is the raw locations pushes should be made to.
142
143 If ``name`` is not defined, we require that the location be a) a local
144 filesystem path with a .hg directory or b) a URL. If not,
145 ``ValueError`` is raised.
146 """
147 if not rawloc:
148 raise ValueError(b'rawloc must be defined')
149
150 # Locations may define branches via syntax <base>#<branch>.
151 u = util.url(rawloc)
152 branch = None
153 if u.fragment:
154 branch = u.fragment
155 u.fragment = None
156
157 self.url = u
158 # the url from the config/command line before dealing with `path://`
159 self.raw_url = u.copy()
160 self.branch = branch
161
162 self.name = name
163 self.rawloc = rawloc
164 self.loc = b'%s' % u
165
166 self._validate_path()
167
168 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
169 self._own_sub_opts = {}
170 if suboptions is not None:
171 self._own_sub_opts = suboptions.copy()
172 sub_opts.update(suboptions)
173 self._all_sub_opts = sub_opts.copy()
174
175 self._apply_suboptions(ui, sub_opts)
176
177 def chain_path(self, ui, paths):
178 if self.url.scheme == b'path':
179 assert self.url.path is None
180 try:
181 subpath = paths[self.url.host]
182 except KeyError:
183 m = _('cannot use `%s`, "%s" is not a known path')
184 m %= (self.rawloc, self.url.host)
185 raise error.Abort(m)
186 if subpath.raw_url.scheme == b'path':
187 m = _('cannot use `%s`, "%s" is also define as a `path://`')
188 m %= (self.rawloc, self.url.host)
189 raise error.Abort(m)
190 self.url = subpath.url
191 self.rawloc = subpath.rawloc
192 self.loc = subpath.loc
193 if self.branch is None:
194 self.branch = subpath.branch
195 else:
196 base = self.rawloc.rsplit(b'#', 1)[0]
197 self.rawloc = b'%s#%s' % (base, self.branch)
198 suboptions = subpath._all_sub_opts.copy()
199 suboptions.update(self._own_sub_opts)
200 self._apply_suboptions(ui, suboptions)
201
202 def _validate_path(self):
203 # When given a raw location but not a symbolic name, validate the
204 # location is valid.
205 if (
206 not self.name
207 and not self.url.scheme
208 and not self._isvalidlocalpath(self.loc)
209 ):
210 raise ValueError(
211 b'location is not a URL or path to a local '
212 b'repo: %s' % self.rawloc
213 )
214
215 def _apply_suboptions(self, ui, sub_options):
216 # Now process the sub-options. If a sub-option is registered, its
217 # attribute will always be present. The value will be None if there
218 # was no valid sub-option.
219 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
220 if suboption not in sub_options:
221 setattr(self, attr, None)
222 continue
223
224 value = func(ui, self, sub_options[suboption])
225 setattr(self, attr, value)
226
227 def _isvalidlocalpath(self, path):
228 """Returns True if the given path is a potentially valid repository.
229 This is its own function so that extensions can change the definition of
230 'valid' in this case (like when pulling from a git repo into a hg
231 one)."""
232 try:
233 return os.path.isdir(os.path.join(path, b'.hg'))
234 # Python 2 may return TypeError. Python 3, ValueError.
235 except (TypeError, ValueError):
236 return False
237
238 @property
239 def suboptions(self):
240 """Return sub-options and their values for this path.
241
242 This is intended to be used for presentation purposes.
243 """
244 d = {}
245 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
246 value = getattr(self, attr)
247 if value is not None:
248 d[subopt] = value
249 return d
@@ -26,7 +26,6 b' 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 (
@@ -48,6 +47,7 b' from .utils import ('
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
@@ -1049,7 +1049,7 b' class ui(object):'
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
@@ -2180,237 +2180,6 b' class ui(object):'
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
General Comments 0
You need to be logged in to leave comments. Login now