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