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