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