##// END OF EJS Templates
pytype: drop the last inline type comment...
marmoute -
r52182:7bd7fcc7 default
parent child Browse files
Show More
@@ -1,256 +1,261 b''
1 1 # config.py - configuration parsing for Mercurial
2 2 #
3 3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 import errno
10 10 import os
11 11
12 from typing import (
13 List,
14 Tuple,
15 )
16
12 17 from .i18n import _
13 18 from . import (
14 19 encoding,
15 20 error,
16 21 util,
17 22 )
18 23
19 24
20 25 class config:
21 26 def __init__(self, data=None):
22 27 self._current_source_level = 0
23 28 self._data = {}
24 29 self._unset = []
25 30 if data:
26 31 for k in data._data:
27 32 self._data[k] = data[k].copy()
28 33 self._current_source_level = data._current_source_level + 1
29 34
30 35 def new_source(self):
31 36 """increment the source counter
32 37
33 38 This is used to define source priority when reading"""
34 39 self._current_source_level += 1
35 40
36 41 def copy(self):
37 42 return config(self)
38 43
39 44 def __contains__(self, section):
40 45 return section in self._data
41 46
42 47 def hasitem(self, section, item):
43 48 return item in self._data.get(section, {})
44 49
45 50 def __getitem__(self, section):
46 51 return self._data.get(section, {})
47 52
48 53 def __iter__(self):
49 54 for d in self.sections():
50 55 yield d
51 56
52 57 def update(self, src):
53 58 current_level = self._current_source_level
54 59 current_level += 1
55 60 max_level = self._current_source_level
56 61 for s, n in src._unset:
57 62 ds = self._data.get(s, None)
58 63 if ds is not None and n in ds:
59 64 self._data[s] = ds.preparewrite()
60 65 del self._data[s][n]
61 66 for s in src:
62 67 ds = self._data.get(s, None)
63 68 if ds:
64 69 self._data[s] = ds.preparewrite()
65 70 else:
66 71 self._data[s] = util.cowsortdict()
67 72 for k, v in src._data[s].items():
68 73 value, source, level = v
69 74 level += current_level
70 75 max_level = max(level, current_level)
71 76 self._data[s][k] = (value, source, level)
72 77 self._current_source_level = max_level
73 78
74 79 def _get(self, section, item):
75 80 return self._data.get(section, {}).get(item)
76 81
77 82 def get(self, section, item, default=None):
78 83 result = self._get(section, item)
79 84 if result is None:
80 85 return default
81 86 return result[0]
82 87
83 88 def backup(self, section, key):
84 89 """return a tuple allowing restore to reinstall a previous value
85 90
86 91 The main reason we need it is because it handles the "no data" case.
87 92 """
88 93 try:
89 94 item = self._data[section][key]
90 95 except KeyError:
91 96 return (section, key)
92 97 else:
93 98 return (section, key) + item
94 99
95 100 def source(self, section, item):
96 101 result = self._get(section, item)
97 102 if result is None:
98 103 return b""
99 104 return result[1]
100 105
101 106 def level(self, section, item):
102 107 result = self._get(section, item)
103 108 if result is None:
104 109 return None
105 110 return result[2]
106 111
107 112 def sections(self):
108 113 return sorted(self._data.keys())
109 114
110 def items(self, section):
115 def items(self, section: bytes) -> List[Tuple[bytes, bytes]]:
111 116 items = self._data.get(section, {}).items()
112 117 return [(k, v[0]) for (k, v) in items]
113 118
114 119 def set(self, section, item, value, source=b""):
115 120 assert not isinstance(
116 121 section, str
117 122 ), b'config section may not be unicode strings on Python 3'
118 123 assert not isinstance(
119 124 item, str
120 125 ), b'config item may not be unicode strings on Python 3'
121 126 assert not isinstance(
122 127 value, str
123 128 ), b'config values may not be unicode strings on Python 3'
124 129 if section not in self:
125 130 self._data[section] = util.cowsortdict()
126 131 else:
127 132 self._data[section] = self._data[section].preparewrite()
128 133 self._data[section][item] = (value, source, self._current_source_level)
129 134
130 135 def alter(self, section, key, new_value):
131 136 """alter a value without altering its source or level
132 137
133 138 This method is meant to be used by `ui.fixconfig` only."""
134 139 item = self._data[section][key]
135 140 size = len(item)
136 141 new_item = (new_value,) + item[1:]
137 142 assert len(new_item) == size
138 143 self._data[section][key] = new_item
139 144
140 145 def restore(self, data):
141 146 """restore data returned by self.backup"""
142 147 if len(data) != 2:
143 148 # restore old data
144 149 section, key = data[:2]
145 150 item = data[2:]
146 151 self._data[section] = self._data[section].preparewrite()
147 152 self._data[section][key] = item
148 153 else:
149 154 # no data before, remove everything
150 155 section, item = data
151 156 if section in self._data:
152 157 self._data[section].pop(item, None)
153 158
154 159 def parse(self, src, data, sections=None, remap=None, include=None):
155 160 sectionre = util.re.compile(br'\[([^\[]+)\]')
156 161 itemre = util.re.compile(br'([^=\s][^=]*?)\s*=\s*(.*\S|)')
157 162 contre = util.re.compile(br'\s+(\S|\S.*\S)\s*$')
158 163 emptyre = util.re.compile(br'(;|#|\s*$)')
159 164 commentre = util.re.compile(br'(;|#)')
160 165 unsetre = util.re.compile(br'%unset\s+(\S+)')
161 166 includere = util.re.compile(br'%include\s+(\S|\S.*\S)\s*$')
162 167 section = b""
163 168 item = None
164 169 line = 0
165 170 cont = False
166 171
167 172 if remap:
168 173 section = remap.get(section, section)
169 174
170 175 for l in data.splitlines(True):
171 176 line += 1
172 177 if line == 1 and l.startswith(b'\xef\xbb\xbf'):
173 178 # Someone set us up the BOM
174 179 l = l[3:]
175 180 if cont:
176 181 if commentre.match(l):
177 182 continue
178 183 m = contre.match(l)
179 184 if m:
180 185 if sections and section not in sections:
181 186 continue
182 187 v = self.get(section, item) + b"\n" + m.group(1)
183 188 self.set(section, item, v, b"%s:%d" % (src, line))
184 189 continue
185 190 item = None
186 191 cont = False
187 192 m = includere.match(l)
188 193
189 194 if m and include:
190 195 expanded = util.expandpath(m.group(1))
191 196 try:
192 197 include(expanded, remap=remap, sections=sections)
193 198 except IOError as inst:
194 199 if inst.errno != errno.ENOENT:
195 200 raise error.ConfigError(
196 201 _(b"cannot include %s (%s)")
197 202 % (expanded, encoding.strtolocal(inst.strerror)),
198 203 b"%s:%d" % (src, line),
199 204 )
200 205 continue
201 206 if emptyre.match(l):
202 207 continue
203 208 m = sectionre.match(l)
204 209 if m:
205 210 section = m.group(1)
206 211 if remap:
207 212 section = remap.get(section, section)
208 213 if section not in self:
209 214 self._data[section] = util.cowsortdict()
210 215 continue
211 216 m = itemre.match(l)
212 217 if m:
213 218 item = m.group(1)
214 219 cont = True
215 220 if sections and section not in sections:
216 221 continue
217 222 self.set(section, item, m.group(2), b"%s:%d" % (src, line))
218 223 continue
219 224 m = unsetre.match(l)
220 225 if m:
221 226 name = m.group(1)
222 227 if sections and section not in sections:
223 228 continue
224 229 if self.get(section, name) is not None:
225 230 self._data[section] = self._data[section].preparewrite()
226 231 del self._data[section][name]
227 232 self._unset.append((section, name))
228 233 continue
229 234
230 235 message = l.rstrip()
231 236 if l.startswith(b' '):
232 237 message = b"unexpected leading whitespace: %s" % message
233 238 raise error.ConfigError(message, (b"%s:%d" % (src, line)))
234 239
235 240 def read(self, path, fp=None, sections=None, remap=None):
236 241 self.new_source()
237 242 if not fp:
238 243 fp = util.posixfile(path, b'rb')
239 244 assert (
240 245 getattr(fp, 'mode', 'rb') == 'rb'
241 246 ), b'config files must be opened in binary mode, got fp=%r mode=%r' % (
242 247 fp,
243 248 fp.mode,
244 249 )
245 250
246 251 dir = os.path.dirname(path)
247 252
248 253 def include(rel, remap, sections):
249 254 abs = os.path.normpath(os.path.join(dir, rel))
250 255 self.read(abs, remap=remap, sections=sections)
251 256 # anything after the include has a higher level
252 257 self.new_source()
253 258
254 259 self.parse(
255 260 path, fp.read(), sections=sections, remap=remap, include=include
256 261 )
@@ -1,533 +1,533 b''
1 1 # subrepoutil.py - sub-repository operations and substate handling
2 2 #
3 3 # Copyright 2009-2010 Olivia Mackall <olivia@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 import os
10 10 import posixpath
11 11 import re
12 12 import typing
13 13
14 14 from typing import (
15 15 Any,
16 16 Dict,
17 17 List,
18 18 Optional,
19 19 Set,
20 20 Tuple,
21 21 )
22 22
23 23 from .i18n import _
24 24 from . import (
25 25 config,
26 26 error,
27 27 filemerge,
28 28 pathutil,
29 29 phases,
30 30 util,
31 31 )
32 32 from .utils import (
33 33 stringutil,
34 34 urlutil,
35 35 )
36 36
37 37 # keeps pyflakes happy
38 38 assert [
39 39 Any,
40 40 Dict,
41 41 List,
42 42 Optional,
43 43 Set,
44 44 Tuple,
45 45 ]
46 46
47 47 nullstate = (b'', b'', b'empty')
48 48
49 49 if typing.TYPE_CHECKING:
50 50 from . import (
51 51 context,
52 52 localrepo,
53 53 match as matchmod,
54 54 scmutil,
55 55 subrepo,
56 56 ui as uimod,
57 57 )
58 58
59 59 # keeps pyflakes happy
60 60 assert [
61 61 context,
62 62 localrepo,
63 63 matchmod,
64 64 scmutil,
65 65 subrepo,
66 66 uimod,
67 67 ]
68 68
69 69 Substate = Dict[bytes, Tuple[bytes, bytes, bytes]]
70 70
71 71
72 72 def state(ctx: "context.changectx", ui: "uimod.ui") -> Substate:
73 73 """return a state dict, mapping subrepo paths configured in .hgsub
74 74 to tuple: (source from .hgsub, revision from .hgsubstate, kind
75 75 (key in types dict))
76 76 """
77 p = config.config()
77 p: config.config = config.config()
78 78 repo = ctx.repo()
79 79
80 80 def read(f, sections=None, remap=None):
81 81 if f in ctx:
82 82 try:
83 83 data = ctx[f].data()
84 84 except FileNotFoundError:
85 85 # handle missing subrepo spec files as removed
86 86 ui.warn(
87 87 _(b"warning: subrepo spec file \'%s\' not found\n")
88 88 % repo.pathto(f)
89 89 )
90 90 return
91 91 p.parse(f, data, sections, remap, read)
92 92 else:
93 93 raise error.Abort(
94 94 _(b"subrepo spec file \'%s\' not found") % repo.pathto(f)
95 95 )
96 96
97 97 if b'.hgsub' in ctx:
98 98 read(b'.hgsub')
99 99
100 100 for path, src in ui.configitems(b'subpaths'):
101 101 p.set(b'subpaths', path, src, ui.configsource(b'subpaths', path))
102 102
103 103 rev = {}
104 104 if b'.hgsubstate' in ctx:
105 105 try:
106 106 for i, l in enumerate(ctx[b'.hgsubstate'].data().splitlines()):
107 107 l = l.lstrip()
108 108 if not l:
109 109 continue
110 110 try:
111 111 revision, path = l.split(b" ", 1)
112 112 except ValueError:
113 113 raise error.Abort(
114 114 _(
115 115 b"invalid subrepository revision "
116 116 b"specifier in \'%s\' line %d"
117 117 )
118 118 % (repo.pathto(b'.hgsubstate'), (i + 1))
119 119 )
120 120 rev[path] = revision
121 121 except FileNotFoundError:
122 122 pass
123 123
124 124 def remap(src: bytes) -> bytes:
125 125 for pattern, repl in p.items(b'subpaths'):
126 126 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
127 127 # does a string decode.
128 128 repl = stringutil.escapestr(repl)
129 129 # However, we still want to allow back references to go
130 130 # through unharmed, so we turn r'\\1' into r'\1'. Again,
131 131 # extra escapes are needed because re.sub string decodes.
132 132 repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl)
133 133 try:
134 134 src = re.sub(pattern, repl, src, 1)
135 135 except re.error as e:
136 136 raise error.Abort(
137 137 _(b"bad subrepository pattern in %s: %s")
138 138 % (
139 139 p.source(b'subpaths', pattern),
140 140 stringutil.forcebytestr(e),
141 141 )
142 142 )
143 143 return src
144 144
145 145 state = {}
146 for path, src in p.items(b''): # type: bytes
146 for path, src in p.items(b''):
147 147 kind = b'hg'
148 148 if src.startswith(b'['):
149 149 if b']' not in src:
150 150 raise error.Abort(_(b'missing ] in subrepository source'))
151 151 kind, src = src.split(b']', 1)
152 152 kind = kind[1:]
153 153 src = src.lstrip() # strip any extra whitespace after ']'
154 154
155 155 if not urlutil.url(src).isabs():
156 156 parent = _abssource(repo, abort=False)
157 157 if parent:
158 158 parent = urlutil.url(parent)
159 159 parent.path = posixpath.join(parent.path or b'', src)
160 160 parent.path = posixpath.normpath(parent.path)
161 161 joined = bytes(parent)
162 162 # Remap the full joined path and use it if it changes,
163 163 # else remap the original source.
164 164 remapped = remap(joined)
165 165 if remapped == joined:
166 166 src = remap(src)
167 167 else:
168 168 src = remapped
169 169
170 170 src = remap(src)
171 171 state[util.pconvert(path)] = (src.strip(), rev.get(path, b''), kind)
172 172
173 173 return state
174 174
175 175
176 176 def writestate(repo: "localrepo.localrepository", state: Substate) -> None:
177 177 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
178 178 lines = [
179 179 b'%s %s\n' % (state[s][1], s)
180 180 for s in sorted(state)
181 181 if state[s][1] != nullstate[1]
182 182 ]
183 183 repo.wwrite(b'.hgsubstate', b''.join(lines), b'')
184 184
185 185
186 186 def submerge(
187 187 repo: "localrepo.localrepository",
188 188 wctx: "context.workingctx",
189 189 mctx: "context.changectx",
190 190 actx: "context.changectx",
191 191 overwrite: bool,
192 192 labels: Optional[Any] = None,
193 193 ) -> Substate:
194 194 # TODO: type the `labels` arg
195 195 """delegated from merge.applyupdates: merging of .hgsubstate file
196 196 in working context, merging context and ancestor context"""
197 197 if mctx == actx: # backwards?
198 198 actx = wctx.p1()
199 199 s1 = wctx.substate
200 200 s2 = mctx.substate
201 201 sa = actx.substate
202 202 sm = {}
203 203
204 204 repo.ui.debug(b"subrepo merge %s %s %s\n" % (wctx, mctx, actx))
205 205
206 206 def debug(s, msg, r=b""):
207 207 if r:
208 208 r = b"%s:%s:%s" % r
209 209 repo.ui.debug(b" subrepo %s: %s %s\n" % (s, msg, r))
210 210
211 211 promptssrc = filemerge.partextras(labels)
212 212 for s, l in sorted(s1.items()):
213 213 a = sa.get(s, nullstate)
214 214 ld = l # local state with possible dirty flag for compares
215 215 if wctx.sub(s).dirty():
216 216 ld = (l[0], l[1] + b"+")
217 217 if wctx == actx: # overwrite
218 218 a = ld
219 219
220 220 prompts = promptssrc.copy()
221 221 prompts[b's'] = s
222 222 if s in s2:
223 223 r = s2[s]
224 224 if ld == r or r == a: # no change or local is newer
225 225 sm[s] = l
226 226 continue
227 227 elif ld == a: # other side changed
228 228 debug(s, b"other changed, get", r)
229 229 wctx.sub(s).get(r, overwrite)
230 230 sm[s] = r
231 231 elif ld[0] != r[0]: # sources differ
232 232 prompts[b'lo'] = l[0]
233 233 prompts[b'ro'] = r[0]
234 234 if repo.ui.promptchoice(
235 235 _(
236 236 b' subrepository sources for %(s)s differ\n'
237 237 b'you can use (l)ocal%(l)s source (%(lo)s)'
238 238 b' or (r)emote%(o)s source (%(ro)s).\n'
239 239 b'what do you want to do?'
240 240 b'$$ &Local $$ &Remote'
241 241 )
242 242 % prompts,
243 243 0,
244 244 ):
245 245 debug(s, b"prompt changed, get", r)
246 246 wctx.sub(s).get(r, overwrite)
247 247 sm[s] = r
248 248 elif ld[1] == a[1]: # local side is unchanged
249 249 debug(s, b"other side changed, get", r)
250 250 wctx.sub(s).get(r, overwrite)
251 251 sm[s] = r
252 252 else:
253 253 debug(s, b"both sides changed")
254 254 srepo = wctx.sub(s)
255 255 prompts[b'sl'] = srepo.shortid(l[1])
256 256 prompts[b'sr'] = srepo.shortid(r[1])
257 257 option = repo.ui.promptchoice(
258 258 _(
259 259 b' subrepository %(s)s diverged (local revision: %(sl)s, '
260 260 b'remote revision: %(sr)s)\n'
261 261 b'you can (m)erge, keep (l)ocal%(l)s or keep '
262 262 b'(r)emote%(o)s.\n'
263 263 b'what do you want to do?'
264 264 b'$$ &Merge $$ &Local $$ &Remote'
265 265 )
266 266 % prompts,
267 267 0,
268 268 )
269 269 if option == 0:
270 270 wctx.sub(s).merge(r)
271 271 sm[s] = l
272 272 debug(s, b"merge with", r)
273 273 elif option == 1:
274 274 sm[s] = l
275 275 debug(s, b"keep local subrepo revision", l)
276 276 else:
277 277 wctx.sub(s).get(r, overwrite)
278 278 sm[s] = r
279 279 debug(s, b"get remote subrepo revision", r)
280 280 elif ld == a: # remote removed, local unchanged
281 281 debug(s, b"remote removed, remove")
282 282 wctx.sub(s).remove()
283 283 elif a == nullstate: # not present in remote or ancestor
284 284 debug(s, b"local added, keep")
285 285 sm[s] = l
286 286 continue
287 287 else:
288 288 if repo.ui.promptchoice(
289 289 _(
290 290 b' local%(l)s changed subrepository %(s)s'
291 291 b' which remote%(o)s removed\n'
292 292 b'use (c)hanged version or (d)elete?'
293 293 b'$$ &Changed $$ &Delete'
294 294 )
295 295 % prompts,
296 296 0,
297 297 ):
298 298 debug(s, b"prompt remove")
299 299 wctx.sub(s).remove()
300 300
301 301 for s, r in sorted(s2.items()):
302 302 if s in s1:
303 303 continue
304 304 elif s not in sa:
305 305 debug(s, b"remote added, get", r)
306 306 mctx.sub(s).get(r)
307 307 sm[s] = r
308 308 elif r != sa[s]:
309 309 prompts = promptssrc.copy()
310 310 prompts[b's'] = s
311 311 if (
312 312 repo.ui.promptchoice(
313 313 _(
314 314 b' remote%(o)s changed subrepository %(s)s'
315 315 b' which local%(l)s removed\n'
316 316 b'use (c)hanged version or (d)elete?'
317 317 b'$$ &Changed $$ &Delete'
318 318 )
319 319 % prompts,
320 320 0,
321 321 )
322 322 == 0
323 323 ):
324 324 debug(s, b"prompt recreate", r)
325 325 mctx.sub(s).get(r)
326 326 sm[s] = r
327 327
328 328 # record merged .hgsubstate
329 329 writestate(repo, sm)
330 330 return sm
331 331
332 332
333 333 def precommit(
334 334 ui: "uimod.ui",
335 335 wctx: "context.workingcommitctx",
336 336 status: "scmutil.status",
337 337 match: "matchmod.basematcher",
338 338 force: bool = False,
339 339 ) -> Tuple[List[bytes], Set[bytes], Substate]:
340 340 """Calculate .hgsubstate changes that should be applied before committing
341 341
342 342 Returns (subs, commitsubs, newstate) where
343 343 - subs: changed subrepos (including dirty ones)
344 344 - commitsubs: dirty subrepos which the caller needs to commit recursively
345 345 - newstate: new state dict which the caller must write to .hgsubstate
346 346
347 347 This also updates the given status argument.
348 348 """
349 349 subs = []
350 350 commitsubs = set()
351 351 newstate = wctx.substate.copy()
352 352
353 353 # only manage subrepos and .hgsubstate if .hgsub is present
354 354 if b'.hgsub' in wctx:
355 355 # we'll decide whether to track this ourselves, thanks
356 356 for c in status.modified, status.added, status.removed:
357 357 if b'.hgsubstate' in c:
358 358 c.remove(b'.hgsubstate')
359 359
360 360 # compare current state to last committed state
361 361 # build new substate based on last committed state
362 362 oldstate = wctx.p1().substate
363 363 for s in sorted(newstate.keys()):
364 364 if not match(s):
365 365 # ignore working copy, use old state if present
366 366 if s in oldstate:
367 367 newstate[s] = oldstate[s]
368 368 continue
369 369 if not force:
370 370 raise error.Abort(
371 371 _(b"commit with new subrepo %s excluded") % s
372 372 )
373 373 dirtyreason = wctx.sub(s).dirtyreason(True)
374 374 if dirtyreason:
375 375 if not ui.configbool(b'ui', b'commitsubrepos'):
376 376 raise error.Abort(
377 377 dirtyreason,
378 378 hint=_(b"use --subrepos for recursive commit"),
379 379 )
380 380 subs.append(s)
381 381 commitsubs.add(s)
382 382 else:
383 383 bs = wctx.sub(s).basestate()
384 384 newstate[s] = (newstate[s][0], bs, newstate[s][2])
385 385 if oldstate.get(s, (None, None, None))[1] != bs:
386 386 subs.append(s)
387 387
388 388 # check for removed subrepos
389 389 for p in wctx.parents():
390 390 r = [s for s in p.substate if s not in newstate]
391 391 subs += [s for s in r if match(s)]
392 392 if subs:
393 393 if not match(b'.hgsub') and b'.hgsub' in (
394 394 wctx.modified() + wctx.added()
395 395 ):
396 396 raise error.Abort(_(b"can't commit subrepos without .hgsub"))
397 397 status.modified.insert(0, b'.hgsubstate')
398 398
399 399 elif b'.hgsub' in status.removed:
400 400 # clean up .hgsubstate when .hgsub is removed
401 401 if b'.hgsubstate' in wctx and b'.hgsubstate' not in (
402 402 status.modified + status.added + status.removed
403 403 ):
404 404 status.removed.insert(0, b'.hgsubstate')
405 405
406 406 return subs, commitsubs, newstate
407 407
408 408
409 409 def repo_rel_or_abs_source(repo):
410 410 """return the source of this repo
411 411
412 412 Either absolute or relative the outermost repo"""
413 413 parent = repo
414 414 chunks = []
415 415 while hasattr(parent, '_subparent'):
416 416 source = urlutil.url(parent._subsource)
417 417 chunks.append(bytes(source))
418 418 if source.isabs():
419 419 break
420 420 parent = parent._subparent
421 421
422 422 chunks.reverse()
423 423 path = posixpath.join(*chunks)
424 424 return posixpath.normpath(path)
425 425
426 426
427 427 def reporelpath(repo: "localrepo.localrepository") -> bytes:
428 428 """return path to this (sub)repo as seen from outermost repo"""
429 429 parent = repo
430 430 while hasattr(parent, '_subparent'):
431 431 parent = parent._subparent
432 432 return repo.root[len(pathutil.normasprefix(parent.root)) :]
433 433
434 434
435 435 def subrelpath(sub: "subrepo.abstractsubrepo") -> bytes:
436 436 """return path to this subrepo as seen from outermost repo"""
437 437 return sub._relpath
438 438
439 439
440 440 def _abssource(
441 441 repo: "localrepo.localrepository",
442 442 push: bool = False,
443 443 abort: bool = True,
444 444 ) -> Optional[bytes]:
445 445 """return pull/push path of repo - either based on parent repo .hgsub info
446 446 or on the top repo config. Abort or return None if no source found."""
447 447 if hasattr(repo, '_subparent'):
448 448 source = urlutil.url(repo._subsource)
449 449 if source.isabs():
450 450 return bytes(source)
451 451 source.path = posixpath.normpath(source.path)
452 452 parent = _abssource(repo._subparent, push, abort=False)
453 453 if parent:
454 454 parent = urlutil.url(util.pconvert(parent))
455 455 parent.path = posixpath.join(parent.path or b'', source.path)
456 456 parent.path = posixpath.normpath(parent.path)
457 457 return bytes(parent)
458 458 else: # recursion reached top repo
459 459 path = None
460 460 if hasattr(repo, '_subtoppath'):
461 461 path = repo._subtoppath
462 462 elif push and repo.ui.config(b'paths', b'default-push'):
463 463 path = repo.ui.config(b'paths', b'default-push')
464 464 elif repo.ui.config(b'paths', b'default'):
465 465 path = repo.ui.config(b'paths', b'default')
466 466 elif repo.shared():
467 467 # chop off the .hg component to get the default path form. This has
468 468 # already run through vfsmod.vfs(..., realpath=True), so it doesn't
469 469 # have problems with 'C:'
470 470 return os.path.dirname(repo.sharedpath)
471 471 if path:
472 472 # issue5770: 'C:\' and 'C:' are not equivalent paths. The former is
473 473 # as expected: an absolute path to the root of the C: drive. The
474 474 # latter is a relative path, and works like so:
475 475 #
476 476 # C:\>cd C:\some\path
477 477 # C:\>D:
478 478 # D:\>python -c "import os; print os.path.abspath('C:')"
479 479 # C:\some\path
480 480 #
481 481 # D:\>python -c "import os; print os.path.abspath('C:relative')"
482 482 # C:\some\path\relative
483 483 if urlutil.hasdriveletter(path):
484 484 if len(path) == 2 or path[2:3] not in br'\/':
485 485 path = util.abspath(path)
486 486 return path
487 487
488 488 if abort:
489 489 raise error.Abort(_(b"default path for subrepository not found"))
490 490
491 491
492 492 def newcommitphase(ui: "uimod.ui", ctx: "context.changectx") -> int:
493 493 commitphase = phases.newcommitphase(ui)
494 494 substate = getattr(ctx, "substate", None)
495 495 if not substate:
496 496 return commitphase
497 497 check = ui.config(b'phases', b'checksubrepos')
498 498 if check not in (b'ignore', b'follow', b'abort'):
499 499 raise error.Abort(
500 500 _(b'invalid phases.checksubrepos configuration: %s') % check
501 501 )
502 502 if check == b'ignore':
503 503 return commitphase
504 504 maxphase = phases.public
505 505 maxsub = None
506 506 for s in sorted(substate):
507 507 sub = ctx.sub(s)
508 508 subphase = sub.phase(substate[s][1])
509 509 if maxphase < subphase:
510 510 maxphase = subphase
511 511 maxsub = s
512 512 if commitphase < maxphase:
513 513 if check == b'abort':
514 514 raise error.Abort(
515 515 _(
516 516 b"can't commit in %s phase"
517 517 b" conflicting %s from subrepository %s"
518 518 )
519 519 % (
520 520 phases.phasenames[commitphase],
521 521 phases.phasenames[maxphase],
522 522 maxsub,
523 523 )
524 524 )
525 525 ui.warn(
526 526 _(
527 527 b"warning: changes are committed in"
528 528 b" %s phase from subrepository %s\n"
529 529 )
530 530 % (phases.phasenames[maxphase], maxsub)
531 531 )
532 532 return maxphase
533 533 return commitphase
General Comments 0
You need to be logged in to leave comments. Login now