19 filemerge, |
19 filemerge, |
20 pathutil, |
20 pathutil, |
21 phases, |
21 phases, |
22 util, |
22 util, |
23 ) |
23 ) |
24 from .utils import ( |
24 from .utils import stringutil |
25 stringutil, |
|
26 ) |
|
27 |
25 |
28 nullstate = ('', '', 'empty') |
26 nullstate = ('', '', 'empty') |
|
27 |
29 |
28 |
30 def state(ctx, ui): |
29 def state(ctx, ui): |
31 """return a state dict, mapping subrepo paths configured in .hgsub |
30 """return a state dict, mapping subrepo paths configured in .hgsub |
32 to tuple: (source from .hgsub, revision from .hgsubstate, kind |
31 to tuple: (source from .hgsub, revision from .hgsubstate, kind |
33 (key in types dict)) |
32 (key in types dict)) |
34 """ |
33 """ |
35 p = config.config() |
34 p = config.config() |
36 repo = ctx.repo() |
35 repo = ctx.repo() |
|
36 |
37 def read(f, sections=None, remap=None): |
37 def read(f, sections=None, remap=None): |
38 if f in ctx: |
38 if f in ctx: |
39 try: |
39 try: |
40 data = ctx[f].data() |
40 data = ctx[f].data() |
41 except IOError as err: |
41 except IOError as err: |
42 if err.errno != errno.ENOENT: |
42 if err.errno != errno.ENOENT: |
43 raise |
43 raise |
44 # handle missing subrepo spec files as removed |
44 # handle missing subrepo spec files as removed |
45 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") % |
45 ui.warn( |
46 repo.pathto(f)) |
46 _("warning: subrepo spec file \'%s\' not found\n") |
|
47 % repo.pathto(f) |
|
48 ) |
47 return |
49 return |
48 p.parse(f, data, sections, remap, read) |
50 p.parse(f, data, sections, remap, read) |
49 else: |
51 else: |
50 raise error.Abort(_("subrepo spec file \'%s\' not found") % |
52 raise error.Abort( |
51 repo.pathto(f)) |
53 _("subrepo spec file \'%s\' not found") % repo.pathto(f) |
|
54 ) |
|
55 |
52 if '.hgsub' in ctx: |
56 if '.hgsub' in ctx: |
53 read('.hgsub') |
57 read('.hgsub') |
54 |
58 |
55 for path, src in ui.configitems('subpaths'): |
59 for path, src in ui.configitems('subpaths'): |
56 p.set('subpaths', path, src, ui.configsource('subpaths', path)) |
60 p.set('subpaths', path, src, ui.configsource('subpaths', path)) |
83 # extra escapes are needed because re.sub string decodes. |
91 # extra escapes are needed because re.sub string decodes. |
84 repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl) |
92 repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl) |
85 try: |
93 try: |
86 src = re.sub(pattern, repl, src, 1) |
94 src = re.sub(pattern, repl, src, 1) |
87 except re.error as e: |
95 except re.error as e: |
88 raise error.Abort(_("bad subrepository pattern in %s: %s") |
96 raise error.Abort( |
89 % (p.source('subpaths', pattern), |
97 _("bad subrepository pattern in %s: %s") |
90 stringutil.forcebytestr(e))) |
98 % ( |
|
99 p.source('subpaths', pattern), |
|
100 stringutil.forcebytestr(e), |
|
101 ) |
|
102 ) |
91 return src |
103 return src |
92 |
104 |
93 state = {} |
105 state = {} |
94 for path, src in p[''].items(): |
106 for path, src in p[''].items(): |
95 kind = 'hg' |
107 kind = 'hg' |
96 if src.startswith('['): |
108 if src.startswith('['): |
97 if ']' not in src: |
109 if ']' not in src: |
98 raise error.Abort(_('missing ] in subrepository source')) |
110 raise error.Abort(_('missing ] in subrepository source')) |
99 kind, src = src.split(']', 1) |
111 kind, src = src.split(']', 1) |
100 kind = kind[1:] |
112 kind = kind[1:] |
101 src = src.lstrip() # strip any extra whitespace after ']' |
113 src = src.lstrip() # strip any extra whitespace after ']' |
102 |
114 |
103 if not util.url(src).isabs(): |
115 if not util.url(src).isabs(): |
104 parent = _abssource(repo, abort=False) |
116 parent = _abssource(repo, abort=False) |
105 if parent: |
117 if parent: |
106 parent = util.url(parent) |
118 parent = util.url(parent) |
118 src = remap(src) |
130 src = remap(src) |
119 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind) |
131 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind) |
120 |
132 |
121 return state |
133 return state |
122 |
134 |
|
135 |
123 def writestate(repo, state): |
136 def writestate(repo, state): |
124 """rewrite .hgsubstate in (outer) repo with these subrepo states""" |
137 """rewrite .hgsubstate in (outer) repo with these subrepo states""" |
125 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state) |
138 lines = [ |
126 if state[s][1] != nullstate[1]] |
139 '%s %s\n' % (state[s][1], s) |
|
140 for s in sorted(state) |
|
141 if state[s][1] != nullstate[1] |
|
142 ] |
127 repo.wwrite('.hgsubstate', ''.join(lines), '') |
143 repo.wwrite('.hgsubstate', ''.join(lines), '') |
|
144 |
128 |
145 |
129 def submerge(repo, wctx, mctx, actx, overwrite, labels=None): |
146 def submerge(repo, wctx, mctx, actx, overwrite, labels=None): |
130 """delegated from merge.applyupdates: merging of .hgsubstate file |
147 """delegated from merge.applyupdates: merging of .hgsubstate file |
131 in working context, merging context and ancestor context""" |
148 in working context, merging context and ancestor context""" |
132 if mctx == actx: # backwards? |
149 if mctx == actx: # backwards? |
133 actx = wctx.p1() |
150 actx = wctx.p1() |
134 s1 = wctx.substate |
151 s1 = wctx.substate |
135 s2 = mctx.substate |
152 s2 = mctx.substate |
136 sa = actx.substate |
153 sa = actx.substate |
137 sm = {} |
154 sm = {} |
144 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r)) |
161 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r)) |
145 |
162 |
146 promptssrc = filemerge.partextras(labels) |
163 promptssrc = filemerge.partextras(labels) |
147 for s, l in sorted(s1.iteritems()): |
164 for s, l in sorted(s1.iteritems()): |
148 a = sa.get(s, nullstate) |
165 a = sa.get(s, nullstate) |
149 ld = l # local state with possible dirty flag for compares |
166 ld = l # local state with possible dirty flag for compares |
150 if wctx.sub(s).dirty(): |
167 if wctx.sub(s).dirty(): |
151 ld = (l[0], l[1] + "+") |
168 ld = (l[0], l[1] + "+") |
152 if wctx == actx: # overwrite |
169 if wctx == actx: # overwrite |
153 a = ld |
170 a = ld |
154 |
171 |
155 prompts = promptssrc.copy() |
172 prompts = promptssrc.copy() |
156 prompts['s'] = s |
173 prompts['s'] = s |
157 if s in s2: |
174 if s in s2: |
158 r = s2[s] |
175 r = s2[s] |
159 if ld == r or r == a: # no change or local is newer |
176 if ld == r or r == a: # no change or local is newer |
160 sm[s] = l |
177 sm[s] = l |
161 continue |
178 continue |
162 elif ld == a: # other side changed |
179 elif ld == a: # other side changed |
163 debug(s, "other changed, get", r) |
180 debug(s, "other changed, get", r) |
164 wctx.sub(s).get(r, overwrite) |
181 wctx.sub(s).get(r, overwrite) |
165 sm[s] = r |
182 sm[s] = r |
166 elif ld[0] != r[0]: # sources differ |
183 elif ld[0] != r[0]: # sources differ |
167 prompts['lo'] = l[0] |
184 prompts['lo'] = l[0] |
168 prompts['ro'] = r[0] |
185 prompts['ro'] = r[0] |
169 if repo.ui.promptchoice( |
186 if repo.ui.promptchoice( |
170 _(' subrepository sources for %(s)s differ\n' |
187 _( |
171 'you can use (l)ocal%(l)s source (%(lo)s)' |
188 ' subrepository sources for %(s)s differ\n' |
172 ' or (r)emote%(o)s source (%(ro)s).\n' |
189 'you can use (l)ocal%(l)s source (%(lo)s)' |
173 'what do you want to do?' |
190 ' or (r)emote%(o)s source (%(ro)s).\n' |
174 '$$ &Local $$ &Remote') % prompts, 0): |
191 'what do you want to do?' |
|
192 '$$ &Local $$ &Remote' |
|
193 ) |
|
194 % prompts, |
|
195 0, |
|
196 ): |
175 debug(s, "prompt changed, get", r) |
197 debug(s, "prompt changed, get", r) |
176 wctx.sub(s).get(r, overwrite) |
198 wctx.sub(s).get(r, overwrite) |
177 sm[s] = r |
199 sm[s] = r |
178 elif ld[1] == a[1]: # local side is unchanged |
200 elif ld[1] == a[1]: # local side is unchanged |
179 debug(s, "other side changed, get", r) |
201 debug(s, "other side changed, get", r) |
180 wctx.sub(s).get(r, overwrite) |
202 wctx.sub(s).get(r, overwrite) |
181 sm[s] = r |
203 sm[s] = r |
182 else: |
204 else: |
183 debug(s, "both sides changed") |
205 debug(s, "both sides changed") |
184 srepo = wctx.sub(s) |
206 srepo = wctx.sub(s) |
185 prompts['sl'] = srepo.shortid(l[1]) |
207 prompts['sl'] = srepo.shortid(l[1]) |
186 prompts['sr'] = srepo.shortid(r[1]) |
208 prompts['sr'] = srepo.shortid(r[1]) |
187 option = repo.ui.promptchoice( |
209 option = repo.ui.promptchoice( |
188 _(' subrepository %(s)s diverged (local revision: %(sl)s, ' |
210 _( |
189 'remote revision: %(sr)s)\n' |
211 ' subrepository %(s)s diverged (local revision: %(sl)s, ' |
190 'you can (m)erge, keep (l)ocal%(l)s or keep ' |
212 'remote revision: %(sr)s)\n' |
191 '(r)emote%(o)s.\n' |
213 'you can (m)erge, keep (l)ocal%(l)s or keep ' |
192 'what do you want to do?' |
214 '(r)emote%(o)s.\n' |
193 '$$ &Merge $$ &Local $$ &Remote') |
215 'what do you want to do?' |
194 % prompts, 0) |
216 '$$ &Merge $$ &Local $$ &Remote' |
|
217 ) |
|
218 % prompts, |
|
219 0, |
|
220 ) |
195 if option == 0: |
221 if option == 0: |
196 wctx.sub(s).merge(r) |
222 wctx.sub(s).merge(r) |
197 sm[s] = l |
223 sm[s] = l |
198 debug(s, "merge with", r) |
224 debug(s, "merge with", r) |
199 elif option == 1: |
225 elif option == 1: |
201 debug(s, "keep local subrepo revision", l) |
227 debug(s, "keep local subrepo revision", l) |
202 else: |
228 else: |
203 wctx.sub(s).get(r, overwrite) |
229 wctx.sub(s).get(r, overwrite) |
204 sm[s] = r |
230 sm[s] = r |
205 debug(s, "get remote subrepo revision", r) |
231 debug(s, "get remote subrepo revision", r) |
206 elif ld == a: # remote removed, local unchanged |
232 elif ld == a: # remote removed, local unchanged |
207 debug(s, "remote removed, remove") |
233 debug(s, "remote removed, remove") |
208 wctx.sub(s).remove() |
234 wctx.sub(s).remove() |
209 elif a == nullstate: # not present in remote or ancestor |
235 elif a == nullstate: # not present in remote or ancestor |
210 debug(s, "local added, keep") |
236 debug(s, "local added, keep") |
211 sm[s] = l |
237 sm[s] = l |
212 continue |
238 continue |
213 else: |
239 else: |
214 if repo.ui.promptchoice( |
240 if repo.ui.promptchoice( |
215 _(' local%(l)s changed subrepository %(s)s' |
241 _( |
216 ' which remote%(o)s removed\n' |
242 ' local%(l)s changed subrepository %(s)s' |
217 'use (c)hanged version or (d)elete?' |
243 ' which remote%(o)s removed\n' |
218 '$$ &Changed $$ &Delete') % prompts, 0): |
244 'use (c)hanged version or (d)elete?' |
|
245 '$$ &Changed $$ &Delete' |
|
246 ) |
|
247 % prompts, |
|
248 0, |
|
249 ): |
219 debug(s, "prompt remove") |
250 debug(s, "prompt remove") |
220 wctx.sub(s).remove() |
251 wctx.sub(s).remove() |
221 |
252 |
222 for s, r in sorted(s2.items()): |
253 for s, r in sorted(s2.items()): |
223 if s in s1: |
254 if s in s1: |
227 mctx.sub(s).get(r) |
258 mctx.sub(s).get(r) |
228 sm[s] = r |
259 sm[s] = r |
229 elif r != sa[s]: |
260 elif r != sa[s]: |
230 prompts = promptssrc.copy() |
261 prompts = promptssrc.copy() |
231 prompts['s'] = s |
262 prompts['s'] = s |
232 if repo.ui.promptchoice( |
263 if ( |
233 _(' remote%(o)s changed subrepository %(s)s' |
264 repo.ui.promptchoice( |
234 ' which local%(l)s removed\n' |
265 _( |
235 'use (c)hanged version or (d)elete?' |
266 ' remote%(o)s changed subrepository %(s)s' |
236 '$$ &Changed $$ &Delete') % prompts, 0) == 0: |
267 ' which local%(l)s removed\n' |
|
268 'use (c)hanged version or (d)elete?' |
|
269 '$$ &Changed $$ &Delete' |
|
270 ) |
|
271 % prompts, |
|
272 0, |
|
273 ) |
|
274 == 0 |
|
275 ): |
237 debug(s, "prompt recreate", r) |
276 debug(s, "prompt recreate", r) |
238 mctx.sub(s).get(r) |
277 mctx.sub(s).get(r) |
239 sm[s] = r |
278 sm[s] = r |
240 |
279 |
241 # record merged .hgsubstate |
280 # record merged .hgsubstate |
242 writestate(repo, sm) |
281 writestate(repo, sm) |
243 return sm |
282 return sm |
|
283 |
244 |
284 |
245 def precommit(ui, wctx, status, match, force=False): |
285 def precommit(ui, wctx, status, match, force=False): |
246 """Calculate .hgsubstate changes that should be applied before committing |
286 """Calculate .hgsubstate changes that should be applied before committing |
247 |
287 |
248 Returns (subs, commitsubs, newstate) where |
288 Returns (subs, commitsubs, newstate) where |
272 if s in oldstate: |
312 if s in oldstate: |
273 newstate[s] = oldstate[s] |
313 newstate[s] = oldstate[s] |
274 continue |
314 continue |
275 if not force: |
315 if not force: |
276 raise error.Abort( |
316 raise error.Abort( |
277 _("commit with new subrepo %s excluded") % s) |
317 _("commit with new subrepo %s excluded") % s |
|
318 ) |
278 dirtyreason = wctx.sub(s).dirtyreason(True) |
319 dirtyreason = wctx.sub(s).dirtyreason(True) |
279 if dirtyreason: |
320 if dirtyreason: |
280 if not ui.configbool('ui', 'commitsubrepos'): |
321 if not ui.configbool('ui', 'commitsubrepos'): |
281 raise error.Abort(dirtyreason, |
322 raise error.Abort( |
282 hint=_("use --subrepos for recursive commit")) |
323 dirtyreason, |
|
324 hint=_("use --subrepos for recursive commit"), |
|
325 ) |
283 subs.append(s) |
326 subs.append(s) |
284 commitsubs.add(s) |
327 commitsubs.add(s) |
285 else: |
328 else: |
286 bs = wctx.sub(s).basestate() |
329 bs = wctx.sub(s).basestate() |
287 newstate[s] = (newstate[s][0], bs, newstate[s][2]) |
330 newstate[s] = (newstate[s][0], bs, newstate[s][2]) |
291 # check for removed subrepos |
334 # check for removed subrepos |
292 for p in wctx.parents(): |
335 for p in wctx.parents(): |
293 r = [s for s in p.substate if s not in newstate] |
336 r = [s for s in p.substate if s not in newstate] |
294 subs += [s for s in r if match(s)] |
337 subs += [s for s in r if match(s)] |
295 if subs: |
338 if subs: |
296 if (not match('.hgsub') and |
339 if not match('.hgsub') and '.hgsub' in ( |
297 '.hgsub' in (wctx.modified() + wctx.added())): |
340 wctx.modified() + wctx.added() |
|
341 ): |
298 raise error.Abort(_("can't commit subrepos without .hgsub")) |
342 raise error.Abort(_("can't commit subrepos without .hgsub")) |
299 status.modified.insert(0, '.hgsubstate') |
343 status.modified.insert(0, '.hgsubstate') |
300 |
344 |
301 elif '.hgsub' in status.removed: |
345 elif '.hgsub' in status.removed: |
302 # clean up .hgsubstate when .hgsub is removed |
346 # clean up .hgsubstate when .hgsub is removed |
303 if ('.hgsubstate' in wctx and |
347 if '.hgsubstate' in wctx and '.hgsubstate' not in ( |
304 '.hgsubstate' not in (status.modified + status.added + |
348 status.modified + status.added + status.removed |
305 status.removed)): |
349 ): |
306 status.removed.insert(0, '.hgsubstate') |
350 status.removed.insert(0, '.hgsubstate') |
307 |
351 |
308 return subs, commitsubs, newstate |
352 return subs, commitsubs, newstate |
|
353 |
309 |
354 |
310 def reporelpath(repo): |
355 def reporelpath(repo): |
311 """return path to this (sub)repo as seen from outermost repo""" |
356 """return path to this (sub)repo as seen from outermost repo""" |
312 parent = repo |
357 parent = repo |
313 while util.safehasattr(parent, '_subparent'): |
358 while util.safehasattr(parent, '_subparent'): |
314 parent = parent._subparent |
359 parent = parent._subparent |
315 return repo.root[len(pathutil.normasprefix(parent.root)):] |
360 return repo.root[len(pathutil.normasprefix(parent.root)) :] |
|
361 |
316 |
362 |
317 def subrelpath(sub): |
363 def subrelpath(sub): |
318 """return path to this subrepo as seen from outermost repo""" |
364 """return path to this subrepo as seen from outermost repo""" |
319 return sub._relpath |
365 return sub._relpath |
|
366 |
320 |
367 |
321 def _abssource(repo, push=False, abort=True): |
368 def _abssource(repo, push=False, abort=True): |
322 """return pull/push path of repo - either based on parent repo .hgsub info |
369 """return pull/push path of repo - either based on parent repo .hgsub info |
323 or on the top repo config. Abort or return None if no source found.""" |
370 or on the top repo config. Abort or return None if no source found.""" |
324 if util.safehasattr(repo, '_subparent'): |
371 if util.safehasattr(repo, '_subparent'): |
330 if parent: |
377 if parent: |
331 parent = util.url(util.pconvert(parent)) |
378 parent = util.url(util.pconvert(parent)) |
332 parent.path = posixpath.join(parent.path or '', source.path) |
379 parent.path = posixpath.join(parent.path or '', source.path) |
333 parent.path = posixpath.normpath(parent.path) |
380 parent.path = posixpath.normpath(parent.path) |
334 return bytes(parent) |
381 return bytes(parent) |
335 else: # recursion reached top repo |
382 else: # recursion reached top repo |
336 path = None |
383 path = None |
337 if util.safehasattr(repo, '_subtoppath'): |
384 if util.safehasattr(repo, '_subtoppath'): |
338 path = repo._subtoppath |
385 path = repo._subtoppath |
339 elif push and repo.ui.config('paths', 'default-push'): |
386 elif push and repo.ui.config('paths', 'default-push'): |
340 path = repo.ui.config('paths', 'default-push') |
387 path = repo.ui.config('paths', 'default-push') |
363 return path |
410 return path |
364 |
411 |
365 if abort: |
412 if abort: |
366 raise error.Abort(_("default path for subrepository not found")) |
413 raise error.Abort(_("default path for subrepository not found")) |
367 |
414 |
|
415 |
368 def newcommitphase(ui, ctx): |
416 def newcommitphase(ui, ctx): |
369 commitphase = phases.newcommitphase(ui) |
417 commitphase = phases.newcommitphase(ui) |
370 substate = getattr(ctx, "substate", None) |
418 substate = getattr(ctx, "substate", None) |
371 if not substate: |
419 if not substate: |
372 return commitphase |
420 return commitphase |
373 check = ui.config('phases', 'checksubrepos') |
421 check = ui.config('phases', 'checksubrepos') |
374 if check not in ('ignore', 'follow', 'abort'): |
422 if check not in ('ignore', 'follow', 'abort'): |
375 raise error.Abort(_('invalid phases.checksubrepos configuration: %s') |
423 raise error.Abort( |
376 % (check)) |
424 _('invalid phases.checksubrepos configuration: %s') % check |
|
425 ) |
377 if check == 'ignore': |
426 if check == 'ignore': |
378 return commitphase |
427 return commitphase |
379 maxphase = phases.public |
428 maxphase = phases.public |
380 maxsub = None |
429 maxsub = None |
381 for s in sorted(substate): |
430 for s in sorted(substate): |
384 if maxphase < subphase: |
433 if maxphase < subphase: |
385 maxphase = subphase |
434 maxphase = subphase |
386 maxsub = s |
435 maxsub = s |
387 if commitphase < maxphase: |
436 if commitphase < maxphase: |
388 if check == 'abort': |
437 if check == 'abort': |
389 raise error.Abort(_("can't commit in %s phase" |
438 raise error.Abort( |
390 " conflicting %s from subrepository %s") % |
439 _( |
391 (phases.phasenames[commitphase], |
440 "can't commit in %s phase" |
392 phases.phasenames[maxphase], maxsub)) |
441 " conflicting %s from subrepository %s" |
393 ui.warn(_("warning: changes are committed in" |
442 ) |
394 " %s phase from subrepository %s\n") % |
443 % ( |
395 (phases.phasenames[maxphase], maxsub)) |
444 phases.phasenames[commitphase], |
|
445 phases.phasenames[maxphase], |
|
446 maxsub, |
|
447 ) |
|
448 ) |
|
449 ui.warn( |
|
450 _( |
|
451 "warning: changes are committed in" |
|
452 " %s phase from subrepository %s\n" |
|
453 ) |
|
454 % (phases.phasenames[maxphase], maxsub) |
|
455 ) |
396 return maxphase |
456 return maxphase |
397 return commitphase |
457 return commitphase |