##// END OF EJS Templates
subrepo: move prompts out of the if (issue5505)...
Simon Farnsworth -
r31516:2915cc1d stable
parent child Browse files
Show More
@@ -1,1960 +1,1965 b''
1 1 # subrepo.py - sub-repository handling for Mercurial
2 2 #
3 3 # Copyright 2009-2010 Matt Mackall <mpm@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 from __future__ import absolute_import
9 9
10 10 import copy
11 11 import errno
12 12 import hashlib
13 13 import os
14 14 import posixpath
15 15 import re
16 16 import stat
17 17 import subprocess
18 18 import sys
19 19 import tarfile
20 20 import xml.dom.minidom
21 21
22 22
23 23 from .i18n import _
24 24 from . import (
25 25 cmdutil,
26 26 config,
27 27 encoding,
28 28 error,
29 29 exchange,
30 30 filemerge,
31 31 match as matchmod,
32 32 node,
33 33 pathutil,
34 34 phases,
35 35 pycompat,
36 36 scmutil,
37 37 util,
38 38 )
39 39
40 40 hg = None
41 41 propertycache = util.propertycache
42 42
43 43 nullstate = ('', '', 'empty')
44 44
45 45 def _expandedabspath(path):
46 46 '''
47 47 get a path or url and if it is a path expand it and return an absolute path
48 48 '''
49 49 expandedpath = util.urllocalpath(util.expandpath(path))
50 50 u = util.url(expandedpath)
51 51 if not u.scheme:
52 52 path = util.normpath(os.path.abspath(u.path))
53 53 return path
54 54
55 55 def _getstorehashcachename(remotepath):
56 56 '''get a unique filename for the store hash cache of a remote repository'''
57 57 return hashlib.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
58 58
59 59 class SubrepoAbort(error.Abort):
60 60 """Exception class used to avoid handling a subrepo error more than once"""
61 61 def __init__(self, *args, **kw):
62 62 self.subrepo = kw.pop('subrepo', None)
63 63 self.cause = kw.pop('cause', None)
64 64 error.Abort.__init__(self, *args, **kw)
65 65
66 66 def annotatesubrepoerror(func):
67 67 def decoratedmethod(self, *args, **kargs):
68 68 try:
69 69 res = func(self, *args, **kargs)
70 70 except SubrepoAbort as ex:
71 71 # This exception has already been handled
72 72 raise ex
73 73 except error.Abort as ex:
74 74 subrepo = subrelpath(self)
75 75 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
76 76 # avoid handling this exception by raising a SubrepoAbort exception
77 77 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
78 78 cause=sys.exc_info())
79 79 return res
80 80 return decoratedmethod
81 81
82 82 def state(ctx, ui):
83 83 """return a state dict, mapping subrepo paths configured in .hgsub
84 84 to tuple: (source from .hgsub, revision from .hgsubstate, kind
85 85 (key in types dict))
86 86 """
87 87 p = config.config()
88 88 repo = ctx.repo()
89 89 def read(f, sections=None, remap=None):
90 90 if f in ctx:
91 91 try:
92 92 data = ctx[f].data()
93 93 except IOError as err:
94 94 if err.errno != errno.ENOENT:
95 95 raise
96 96 # handle missing subrepo spec files as removed
97 97 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
98 98 repo.pathto(f))
99 99 return
100 100 p.parse(f, data, sections, remap, read)
101 101 else:
102 102 raise error.Abort(_("subrepo spec file \'%s\' not found") %
103 103 repo.pathto(f))
104 104 if '.hgsub' in ctx:
105 105 read('.hgsub')
106 106
107 107 for path, src in ui.configitems('subpaths'):
108 108 p.set('subpaths', path, src, ui.configsource('subpaths', path))
109 109
110 110 rev = {}
111 111 if '.hgsubstate' in ctx:
112 112 try:
113 113 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
114 114 l = l.lstrip()
115 115 if not l:
116 116 continue
117 117 try:
118 118 revision, path = l.split(" ", 1)
119 119 except ValueError:
120 120 raise error.Abort(_("invalid subrepository revision "
121 121 "specifier in \'%s\' line %d")
122 122 % (repo.pathto('.hgsubstate'), (i + 1)))
123 123 rev[path] = revision
124 124 except IOError as err:
125 125 if err.errno != errno.ENOENT:
126 126 raise
127 127
128 128 def remap(src):
129 129 for pattern, repl in p.items('subpaths'):
130 130 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
131 131 # does a string decode.
132 132 repl = repl.encode('string-escape')
133 133 # However, we still want to allow back references to go
134 134 # through unharmed, so we turn r'\\1' into r'\1'. Again,
135 135 # extra escapes are needed because re.sub string decodes.
136 136 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
137 137 try:
138 138 src = re.sub(pattern, repl, src, 1)
139 139 except re.error as e:
140 140 raise error.Abort(_("bad subrepository pattern in %s: %s")
141 141 % (p.source('subpaths', pattern), e))
142 142 return src
143 143
144 144 state = {}
145 145 for path, src in p[''].items():
146 146 kind = 'hg'
147 147 if src.startswith('['):
148 148 if ']' not in src:
149 149 raise error.Abort(_('missing ] in subrepo source'))
150 150 kind, src = src.split(']', 1)
151 151 kind = kind[1:]
152 152 src = src.lstrip() # strip any extra whitespace after ']'
153 153
154 154 if not util.url(src).isabs():
155 155 parent = _abssource(repo, abort=False)
156 156 if parent:
157 157 parent = util.url(parent)
158 158 parent.path = posixpath.join(parent.path or '', src)
159 159 parent.path = posixpath.normpath(parent.path)
160 160 joined = str(parent)
161 161 # Remap the full joined path and use it if it changes,
162 162 # else remap the original source.
163 163 remapped = remap(joined)
164 164 if remapped == joined:
165 165 src = remap(src)
166 166 else:
167 167 src = remapped
168 168
169 169 src = remap(src)
170 170 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
171 171
172 172 return state
173 173
174 174 def writestate(repo, state):
175 175 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
176 176 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
177 177 if state[s][1] != nullstate[1]]
178 178 repo.wwrite('.hgsubstate', ''.join(lines), '')
179 179
180 180 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
181 181 """delegated from merge.applyupdates: merging of .hgsubstate file
182 182 in working context, merging context and ancestor context"""
183 183 if mctx == actx: # backwards?
184 184 actx = wctx.p1()
185 185 s1 = wctx.substate
186 186 s2 = mctx.substate
187 187 sa = actx.substate
188 188 sm = {}
189 189
190 190 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
191 191
192 192 def debug(s, msg, r=""):
193 193 if r:
194 194 r = "%s:%s:%s" % r
195 195 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
196 196
197 promptssrc = filemerge.partextras(labels)
197 198 for s, l in sorted(s1.iteritems()):
199 prompts = None
198 200 a = sa.get(s, nullstate)
199 201 ld = l # local state with possible dirty flag for compares
200 202 if wctx.sub(s).dirty():
201 203 ld = (l[0], l[1] + "+")
202 204 if wctx == actx: # overwrite
203 205 a = ld
204 206
207 prompts = promptssrc.copy()
208 prompts['s'] = s
205 209 if s in s2:
206 prompts = filemerge.partextras(labels)
207 prompts['s'] = s
208 210 r = s2[s]
209 211 if ld == r or r == a: # no change or local is newer
210 212 sm[s] = l
211 213 continue
212 214 elif ld == a: # other side changed
213 215 debug(s, "other changed, get", r)
214 216 wctx.sub(s).get(r, overwrite)
215 217 sm[s] = r
216 218 elif ld[0] != r[0]: # sources differ
217 219 prompts['lo'] = l[0]
218 220 prompts['ro'] = r[0]
219 221 if repo.ui.promptchoice(
220 222 _(' subrepository sources for %(s)s differ\n'
221 223 'use (l)ocal%(l)s source (%(lo)s)'
222 224 ' or (r)emote%(o)s source (%(ro)s)?'
223 225 '$$ &Local $$ &Remote') % prompts, 0):
224 226 debug(s, "prompt changed, get", r)
225 227 wctx.sub(s).get(r, overwrite)
226 228 sm[s] = r
227 229 elif ld[1] == a[1]: # local side is unchanged
228 230 debug(s, "other side changed, get", r)
229 231 wctx.sub(s).get(r, overwrite)
230 232 sm[s] = r
231 233 else:
232 234 debug(s, "both sides changed")
233 235 srepo = wctx.sub(s)
234 236 prompts['sl'] = srepo.shortid(l[1])
235 237 prompts['sr'] = srepo.shortid(r[1])
236 238 option = repo.ui.promptchoice(
237 239 _(' subrepository %(s)s diverged (local revision: %(sl)s, '
238 240 'remote revision: %(sr)s)\n'
239 241 '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
240 242 '$$ &Merge $$ &Local $$ &Remote')
241 243 % prompts, 0)
242 244 if option == 0:
243 245 wctx.sub(s).merge(r)
244 246 sm[s] = l
245 247 debug(s, "merge with", r)
246 248 elif option == 1:
247 249 sm[s] = l
248 250 debug(s, "keep local subrepo revision", l)
249 251 else:
250 252 wctx.sub(s).get(r, overwrite)
251 253 sm[s] = r
252 254 debug(s, "get remote subrepo revision", r)
253 255 elif ld == a: # remote removed, local unchanged
254 256 debug(s, "remote removed, remove")
255 257 wctx.sub(s).remove()
256 258 elif a == nullstate: # not present in remote or ancestor
257 259 debug(s, "local added, keep")
258 260 sm[s] = l
259 261 continue
260 262 else:
261 263 if repo.ui.promptchoice(
262 264 _(' local%(l)s changed subrepository %(s)s'
263 265 ' which remote%(o)s removed\n'
264 266 'use (c)hanged version or (d)elete?'
265 267 '$$ &Changed $$ &Delete') % prompts, 0):
266 268 debug(s, "prompt remove")
267 269 wctx.sub(s).remove()
268 270
269 271 for s, r in sorted(s2.items()):
272 prompts = None
270 273 if s in s1:
271 274 continue
272 275 elif s not in sa:
273 276 debug(s, "remote added, get", r)
274 277 mctx.sub(s).get(r)
275 278 sm[s] = r
276 279 elif r != sa[s]:
280 prompts = promptssrc.copy()
281 prompts['s'] = s
277 282 if repo.ui.promptchoice(
278 283 _(' remote%(o)s changed subrepository %(s)s'
279 284 ' which local%(l)s removed\n'
280 285 'use (c)hanged version or (d)elete?'
281 286 '$$ &Changed $$ &Delete') % prompts, 0) == 0:
282 287 debug(s, "prompt recreate", r)
283 288 mctx.sub(s).get(r)
284 289 sm[s] = r
285 290
286 291 # record merged .hgsubstate
287 292 writestate(repo, sm)
288 293 return sm
289 294
290 295 def _updateprompt(ui, sub, dirty, local, remote):
291 296 if dirty:
292 297 msg = (_(' subrepository sources for %s differ\n'
293 298 'use (l)ocal source (%s) or (r)emote source (%s)?'
294 299 '$$ &Local $$ &Remote')
295 300 % (subrelpath(sub), local, remote))
296 301 else:
297 302 msg = (_(' subrepository sources for %s differ (in checked out '
298 303 'version)\n'
299 304 'use (l)ocal source (%s) or (r)emote source (%s)?'
300 305 '$$ &Local $$ &Remote')
301 306 % (subrelpath(sub), local, remote))
302 307 return ui.promptchoice(msg, 0)
303 308
304 309 def reporelpath(repo):
305 310 """return path to this (sub)repo as seen from outermost repo"""
306 311 parent = repo
307 312 while util.safehasattr(parent, '_subparent'):
308 313 parent = parent._subparent
309 314 return repo.root[len(pathutil.normasprefix(parent.root)):]
310 315
311 316 def subrelpath(sub):
312 317 """return path to this subrepo as seen from outermost repo"""
313 318 return sub._relpath
314 319
315 320 def _abssource(repo, push=False, abort=True):
316 321 """return pull/push path of repo - either based on parent repo .hgsub info
317 322 or on the top repo config. Abort or return None if no source found."""
318 323 if util.safehasattr(repo, '_subparent'):
319 324 source = util.url(repo._subsource)
320 325 if source.isabs():
321 326 return str(source)
322 327 source.path = posixpath.normpath(source.path)
323 328 parent = _abssource(repo._subparent, push, abort=False)
324 329 if parent:
325 330 parent = util.url(util.pconvert(parent))
326 331 parent.path = posixpath.join(parent.path or '', source.path)
327 332 parent.path = posixpath.normpath(parent.path)
328 333 return str(parent)
329 334 else: # recursion reached top repo
330 335 if util.safehasattr(repo, '_subtoppath'):
331 336 return repo._subtoppath
332 337 if push and repo.ui.config('paths', 'default-push'):
333 338 return repo.ui.config('paths', 'default-push')
334 339 if repo.ui.config('paths', 'default'):
335 340 return repo.ui.config('paths', 'default')
336 341 if repo.shared():
337 342 # chop off the .hg component to get the default path form
338 343 return os.path.dirname(repo.sharedpath)
339 344 if abort:
340 345 raise error.Abort(_("default path for subrepository not found"))
341 346
342 347 def _sanitize(ui, vfs, ignore):
343 348 for dirname, dirs, names in vfs.walk():
344 349 for i, d in enumerate(dirs):
345 350 if d.lower() == ignore:
346 351 del dirs[i]
347 352 break
348 353 if vfs.basename(dirname).lower() != '.hg':
349 354 continue
350 355 for f in names:
351 356 if f.lower() == 'hgrc':
352 357 ui.warn(_("warning: removing potentially hostile 'hgrc' "
353 358 "in '%s'\n") % vfs.join(dirname))
354 359 vfs.unlink(vfs.reljoin(dirname, f))
355 360
356 361 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
357 362 """return instance of the right subrepo class for subrepo in path"""
358 363 # subrepo inherently violates our import layering rules
359 364 # because it wants to make repo objects from deep inside the stack
360 365 # so we manually delay the circular imports to not break
361 366 # scripts that don't use our demand-loading
362 367 global hg
363 368 from . import hg as h
364 369 hg = h
365 370
366 371 pathutil.pathauditor(ctx.repo().root)(path)
367 372 state = ctx.substate[path]
368 373 if state[2] not in types:
369 374 raise error.Abort(_('unknown subrepo type %s') % state[2])
370 375 if allowwdir:
371 376 state = (state[0], ctx.subrev(path), state[2])
372 377 return types[state[2]](ctx, path, state[:2], allowcreate)
373 378
374 379 def nullsubrepo(ctx, path, pctx):
375 380 """return an empty subrepo in pctx for the extant subrepo in ctx"""
376 381 # subrepo inherently violates our import layering rules
377 382 # because it wants to make repo objects from deep inside the stack
378 383 # so we manually delay the circular imports to not break
379 384 # scripts that don't use our demand-loading
380 385 global hg
381 386 from . import hg as h
382 387 hg = h
383 388
384 389 pathutil.pathauditor(ctx.repo().root)(path)
385 390 state = ctx.substate[path]
386 391 if state[2] not in types:
387 392 raise error.Abort(_('unknown subrepo type %s') % state[2])
388 393 subrev = ''
389 394 if state[2] == 'hg':
390 395 subrev = "0" * 40
391 396 return types[state[2]](pctx, path, (state[0], subrev), True)
392 397
393 398 def newcommitphase(ui, ctx):
394 399 commitphase = phases.newcommitphase(ui)
395 400 substate = getattr(ctx, "substate", None)
396 401 if not substate:
397 402 return commitphase
398 403 check = ui.config('phases', 'checksubrepos', 'follow')
399 404 if check not in ('ignore', 'follow', 'abort'):
400 405 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
401 406 % (check))
402 407 if check == 'ignore':
403 408 return commitphase
404 409 maxphase = phases.public
405 410 maxsub = None
406 411 for s in sorted(substate):
407 412 sub = ctx.sub(s)
408 413 subphase = sub.phase(substate[s][1])
409 414 if maxphase < subphase:
410 415 maxphase = subphase
411 416 maxsub = s
412 417 if commitphase < maxphase:
413 418 if check == 'abort':
414 419 raise error.Abort(_("can't commit in %s phase"
415 420 " conflicting %s from subrepository %s") %
416 421 (phases.phasenames[commitphase],
417 422 phases.phasenames[maxphase], maxsub))
418 423 ui.warn(_("warning: changes are committed in"
419 424 " %s phase from subrepository %s\n") %
420 425 (phases.phasenames[maxphase], maxsub))
421 426 return maxphase
422 427 return commitphase
423 428
424 429 # subrepo classes need to implement the following abstract class:
425 430
426 431 class abstractsubrepo(object):
427 432
428 433 def __init__(self, ctx, path):
429 434 """Initialize abstractsubrepo part
430 435
431 436 ``ctx`` is the context referring this subrepository in the
432 437 parent repository.
433 438
434 439 ``path`` is the path to this subrepository as seen from
435 440 innermost repository.
436 441 """
437 442 self.ui = ctx.repo().ui
438 443 self._ctx = ctx
439 444 self._path = path
440 445
441 446 def storeclean(self, path):
442 447 """
443 448 returns true if the repository has not changed since it was last
444 449 cloned from or pushed to a given repository.
445 450 """
446 451 return False
447 452
448 453 def dirty(self, ignoreupdate=False):
449 454 """returns true if the dirstate of the subrepo is dirty or does not
450 455 match current stored state. If ignoreupdate is true, only check
451 456 whether the subrepo has uncommitted changes in its dirstate.
452 457 """
453 458 raise NotImplementedError
454 459
455 460 def dirtyreason(self, ignoreupdate=False):
456 461 """return reason string if it is ``dirty()``
457 462
458 463 Returned string should have enough information for the message
459 464 of exception.
460 465
461 466 This returns None, otherwise.
462 467 """
463 468 if self.dirty(ignoreupdate=ignoreupdate):
464 469 return _("uncommitted changes in subrepository '%s'"
465 470 ) % subrelpath(self)
466 471
467 472 def bailifchanged(self, ignoreupdate=False, hint=None):
468 473 """raise Abort if subrepository is ``dirty()``
469 474 """
470 475 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
471 476 if dirtyreason:
472 477 raise error.Abort(dirtyreason, hint=hint)
473 478
474 479 def basestate(self):
475 480 """current working directory base state, disregarding .hgsubstate
476 481 state and working directory modifications"""
477 482 raise NotImplementedError
478 483
479 484 def checknested(self, path):
480 485 """check if path is a subrepository within this repository"""
481 486 return False
482 487
483 488 def commit(self, text, user, date):
484 489 """commit the current changes to the subrepo with the given
485 490 log message. Use given user and date if possible. Return the
486 491 new state of the subrepo.
487 492 """
488 493 raise NotImplementedError
489 494
490 495 def phase(self, state):
491 496 """returns phase of specified state in the subrepository.
492 497 """
493 498 return phases.public
494 499
495 500 def remove(self):
496 501 """remove the subrepo
497 502
498 503 (should verify the dirstate is not dirty first)
499 504 """
500 505 raise NotImplementedError
501 506
502 507 def get(self, state, overwrite=False):
503 508 """run whatever commands are needed to put the subrepo into
504 509 this state
505 510 """
506 511 raise NotImplementedError
507 512
508 513 def merge(self, state):
509 514 """merge currently-saved state with the new state."""
510 515 raise NotImplementedError
511 516
512 517 def push(self, opts):
513 518 """perform whatever action is analogous to 'hg push'
514 519
515 520 This may be a no-op on some systems.
516 521 """
517 522 raise NotImplementedError
518 523
519 524 def add(self, ui, match, prefix, explicitonly, **opts):
520 525 return []
521 526
522 527 def addremove(self, matcher, prefix, opts, dry_run, similarity):
523 528 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
524 529 return 1
525 530
526 531 def cat(self, match, prefix, **opts):
527 532 return 1
528 533
529 534 def status(self, rev2, **opts):
530 535 return scmutil.status([], [], [], [], [], [], [])
531 536
532 537 def diff(self, ui, diffopts, node2, match, prefix, **opts):
533 538 pass
534 539
535 540 def outgoing(self, ui, dest, opts):
536 541 return 1
537 542
538 543 def incoming(self, ui, source, opts):
539 544 return 1
540 545
541 546 def files(self):
542 547 """return filename iterator"""
543 548 raise NotImplementedError
544 549
545 550 def filedata(self, name):
546 551 """return file data"""
547 552 raise NotImplementedError
548 553
549 554 def fileflags(self, name):
550 555 """return file flags"""
551 556 return ''
552 557
553 558 def getfileset(self, expr):
554 559 """Resolve the fileset expression for this repo"""
555 560 return set()
556 561
557 562 def printfiles(self, ui, m, fm, fmt, subrepos):
558 563 """handle the files command for this subrepo"""
559 564 return 1
560 565
561 566 def archive(self, archiver, prefix, match=None):
562 567 if match is not None:
563 568 files = [f for f in self.files() if match(f)]
564 569 else:
565 570 files = self.files()
566 571 total = len(files)
567 572 relpath = subrelpath(self)
568 573 self.ui.progress(_('archiving (%s)') % relpath, 0,
569 574 unit=_('files'), total=total)
570 575 for i, name in enumerate(files):
571 576 flags = self.fileflags(name)
572 577 mode = 'x' in flags and 0o755 or 0o644
573 578 symlink = 'l' in flags
574 579 archiver.addfile(prefix + self._path + '/' + name,
575 580 mode, symlink, self.filedata(name))
576 581 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
577 582 unit=_('files'), total=total)
578 583 self.ui.progress(_('archiving (%s)') % relpath, None)
579 584 return total
580 585
581 586 def walk(self, match):
582 587 '''
583 588 walk recursively through the directory tree, finding all files
584 589 matched by the match function
585 590 '''
586 591 pass
587 592
588 593 def forget(self, match, prefix):
589 594 return ([], [])
590 595
591 596 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
592 597 """remove the matched files from the subrepository and the filesystem,
593 598 possibly by force and/or after the file has been removed from the
594 599 filesystem. Return 0 on success, 1 on any warning.
595 600 """
596 601 warnings.append(_("warning: removefiles not implemented (%s)")
597 602 % self._path)
598 603 return 1
599 604
600 605 def revert(self, substate, *pats, **opts):
601 606 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
602 607 % (substate[0], substate[2]))
603 608 return []
604 609
605 610 def shortid(self, revid):
606 611 return revid
607 612
608 613 def verify(self):
609 614 '''verify the integrity of the repository. Return 0 on success or
610 615 warning, 1 on any error.
611 616 '''
612 617 return 0
613 618
614 619 @propertycache
615 620 def wvfs(self):
616 621 """return vfs to access the working directory of this subrepository
617 622 """
618 623 return scmutil.vfs(self._ctx.repo().wvfs.join(self._path))
619 624
620 625 @propertycache
621 626 def _relpath(self):
622 627 """return path to this subrepository as seen from outermost repository
623 628 """
624 629 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
625 630
626 631 class hgsubrepo(abstractsubrepo):
627 632 def __init__(self, ctx, path, state, allowcreate):
628 633 super(hgsubrepo, self).__init__(ctx, path)
629 634 self._state = state
630 635 r = ctx.repo()
631 636 root = r.wjoin(path)
632 637 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
633 638 self._repo = hg.repository(r.baseui, root, create=create)
634 639
635 640 # Propagate the parent's --hidden option
636 641 if r is r.unfiltered():
637 642 self._repo = self._repo.unfiltered()
638 643
639 644 self.ui = self._repo.ui
640 645 for s, k in [('ui', 'commitsubrepos')]:
641 646 v = r.ui.config(s, k)
642 647 if v:
643 648 self.ui.setconfig(s, k, v, 'subrepo')
644 649 # internal config: ui._usedassubrepo
645 650 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
646 651 self._initrepo(r, state[0], create)
647 652
648 653 def storeclean(self, path):
649 654 with self._repo.lock():
650 655 return self._storeclean(path)
651 656
652 657 def _storeclean(self, path):
653 658 clean = True
654 659 itercache = self._calcstorehash(path)
655 660 for filehash in self._readstorehashcache(path):
656 661 if filehash != next(itercache, None):
657 662 clean = False
658 663 break
659 664 if clean:
660 665 # if not empty:
661 666 # the cached and current pull states have a different size
662 667 clean = next(itercache, None) is None
663 668 return clean
664 669
665 670 def _calcstorehash(self, remotepath):
666 671 '''calculate a unique "store hash"
667 672
668 673 This method is used to to detect when there are changes that may
669 674 require a push to a given remote path.'''
670 675 # sort the files that will be hashed in increasing (likely) file size
671 676 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
672 677 yield '# %s\n' % _expandedabspath(remotepath)
673 678 vfs = self._repo.vfs
674 679 for relname in filelist:
675 680 filehash = hashlib.sha1(vfs.tryread(relname)).hexdigest()
676 681 yield '%s = %s\n' % (relname, filehash)
677 682
678 683 @propertycache
679 684 def _cachestorehashvfs(self):
680 685 return scmutil.vfs(self._repo.join('cache/storehash'))
681 686
682 687 def _readstorehashcache(self, remotepath):
683 688 '''read the store hash cache for a given remote repository'''
684 689 cachefile = _getstorehashcachename(remotepath)
685 690 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
686 691
687 692 def _cachestorehash(self, remotepath):
688 693 '''cache the current store hash
689 694
690 695 Each remote repo requires its own store hash cache, because a subrepo
691 696 store may be "clean" versus a given remote repo, but not versus another
692 697 '''
693 698 cachefile = _getstorehashcachename(remotepath)
694 699 with self._repo.lock():
695 700 storehash = list(self._calcstorehash(remotepath))
696 701 vfs = self._cachestorehashvfs
697 702 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
698 703
699 704 def _getctx(self):
700 705 '''fetch the context for this subrepo revision, possibly a workingctx
701 706 '''
702 707 if self._ctx.rev() is None:
703 708 return self._repo[None] # workingctx if parent is workingctx
704 709 else:
705 710 rev = self._state[1]
706 711 return self._repo[rev]
707 712
708 713 @annotatesubrepoerror
709 714 def _initrepo(self, parentrepo, source, create):
710 715 self._repo._subparent = parentrepo
711 716 self._repo._subsource = source
712 717
713 718 if create:
714 719 lines = ['[paths]\n']
715 720
716 721 def addpathconfig(key, value):
717 722 if value:
718 723 lines.append('%s = %s\n' % (key, value))
719 724 self.ui.setconfig('paths', key, value, 'subrepo')
720 725
721 726 defpath = _abssource(self._repo, abort=False)
722 727 defpushpath = _abssource(self._repo, True, abort=False)
723 728 addpathconfig('default', defpath)
724 729 if defpath != defpushpath:
725 730 addpathconfig('default-push', defpushpath)
726 731
727 732 fp = self._repo.vfs("hgrc", "w", text=True)
728 733 try:
729 734 fp.write(''.join(lines))
730 735 finally:
731 736 fp.close()
732 737
733 738 @annotatesubrepoerror
734 739 def add(self, ui, match, prefix, explicitonly, **opts):
735 740 return cmdutil.add(ui, self._repo, match,
736 741 self.wvfs.reljoin(prefix, self._path),
737 742 explicitonly, **opts)
738 743
739 744 @annotatesubrepoerror
740 745 def addremove(self, m, prefix, opts, dry_run, similarity):
741 746 # In the same way as sub directories are processed, once in a subrepo,
742 747 # always entry any of its subrepos. Don't corrupt the options that will
743 748 # be used to process sibling subrepos however.
744 749 opts = copy.copy(opts)
745 750 opts['subrepos'] = True
746 751 return scmutil.addremove(self._repo, m,
747 752 self.wvfs.reljoin(prefix, self._path), opts,
748 753 dry_run, similarity)
749 754
750 755 @annotatesubrepoerror
751 756 def cat(self, match, prefix, **opts):
752 757 rev = self._state[1]
753 758 ctx = self._repo[rev]
754 759 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
755 760
756 761 @annotatesubrepoerror
757 762 def status(self, rev2, **opts):
758 763 try:
759 764 rev1 = self._state[1]
760 765 ctx1 = self._repo[rev1]
761 766 ctx2 = self._repo[rev2]
762 767 return self._repo.status(ctx1, ctx2, **opts)
763 768 except error.RepoLookupError as inst:
764 769 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
765 770 % (inst, subrelpath(self)))
766 771 return scmutil.status([], [], [], [], [], [], [])
767 772
768 773 @annotatesubrepoerror
769 774 def diff(self, ui, diffopts, node2, match, prefix, **opts):
770 775 try:
771 776 node1 = node.bin(self._state[1])
772 777 # We currently expect node2 to come from substate and be
773 778 # in hex format
774 779 if node2 is not None:
775 780 node2 = node.bin(node2)
776 781 cmdutil.diffordiffstat(ui, self._repo, diffopts,
777 782 node1, node2, match,
778 783 prefix=posixpath.join(prefix, self._path),
779 784 listsubrepos=True, **opts)
780 785 except error.RepoLookupError as inst:
781 786 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
782 787 % (inst, subrelpath(self)))
783 788
784 789 @annotatesubrepoerror
785 790 def archive(self, archiver, prefix, match=None):
786 791 self._get(self._state + ('hg',))
787 792 total = abstractsubrepo.archive(self, archiver, prefix, match)
788 793 rev = self._state[1]
789 794 ctx = self._repo[rev]
790 795 for subpath in ctx.substate:
791 796 s = subrepo(ctx, subpath, True)
792 797 submatch = matchmod.subdirmatcher(subpath, match)
793 798 total += s.archive(archiver, prefix + self._path + '/', submatch)
794 799 return total
795 800
796 801 @annotatesubrepoerror
797 802 def dirty(self, ignoreupdate=False):
798 803 r = self._state[1]
799 804 if r == '' and not ignoreupdate: # no state recorded
800 805 return True
801 806 w = self._repo[None]
802 807 if r != w.p1().hex() and not ignoreupdate:
803 808 # different version checked out
804 809 return True
805 810 return w.dirty() # working directory changed
806 811
807 812 def basestate(self):
808 813 return self._repo['.'].hex()
809 814
810 815 def checknested(self, path):
811 816 return self._repo._checknested(self._repo.wjoin(path))
812 817
813 818 @annotatesubrepoerror
814 819 def commit(self, text, user, date):
815 820 # don't bother committing in the subrepo if it's only been
816 821 # updated
817 822 if not self.dirty(True):
818 823 return self._repo['.'].hex()
819 824 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
820 825 n = self._repo.commit(text, user, date)
821 826 if not n:
822 827 return self._repo['.'].hex() # different version checked out
823 828 return node.hex(n)
824 829
825 830 @annotatesubrepoerror
826 831 def phase(self, state):
827 832 return self._repo[state].phase()
828 833
829 834 @annotatesubrepoerror
830 835 def remove(self):
831 836 # we can't fully delete the repository as it may contain
832 837 # local-only history
833 838 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
834 839 hg.clean(self._repo, node.nullid, False)
835 840
836 841 def _get(self, state):
837 842 source, revision, kind = state
838 843 if revision in self._repo.unfiltered():
839 844 return True
840 845 self._repo._subsource = source
841 846 srcurl = _abssource(self._repo)
842 847 other = hg.peer(self._repo, {}, srcurl)
843 848 if len(self._repo) == 0:
844 849 self.ui.status(_('cloning subrepo %s from %s\n')
845 850 % (subrelpath(self), srcurl))
846 851 parentrepo = self._repo._subparent
847 852 # use self._repo.vfs instead of self.wvfs to remove .hg only
848 853 self._repo.vfs.rmtree()
849 854 other, cloned = hg.clone(self._repo._subparent.baseui, {},
850 855 other, self._repo.root,
851 856 update=False)
852 857 self._repo = cloned.local()
853 858 self._initrepo(parentrepo, source, create=True)
854 859 self._cachestorehash(srcurl)
855 860 else:
856 861 self.ui.status(_('pulling subrepo %s from %s\n')
857 862 % (subrelpath(self), srcurl))
858 863 cleansub = self.storeclean(srcurl)
859 864 exchange.pull(self._repo, other)
860 865 if cleansub:
861 866 # keep the repo clean after pull
862 867 self._cachestorehash(srcurl)
863 868 return False
864 869
865 870 @annotatesubrepoerror
866 871 def get(self, state, overwrite=False):
867 872 inrepo = self._get(state)
868 873 source, revision, kind = state
869 874 repo = self._repo
870 875 repo.ui.debug("getting subrepo %s\n" % self._path)
871 876 if inrepo:
872 877 urepo = repo.unfiltered()
873 878 ctx = urepo[revision]
874 879 if ctx.hidden():
875 880 urepo.ui.warn(
876 881 _('revision %s in subrepo %s is hidden\n') \
877 882 % (revision[0:12], self._path))
878 883 repo = urepo
879 884 hg.updaterepo(repo, revision, overwrite)
880 885
881 886 @annotatesubrepoerror
882 887 def merge(self, state):
883 888 self._get(state)
884 889 cur = self._repo['.']
885 890 dst = self._repo[state[1]]
886 891 anc = dst.ancestor(cur)
887 892
888 893 def mergefunc():
889 894 if anc == cur and dst.branch() == cur.branch():
890 895 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
891 896 hg.update(self._repo, state[1])
892 897 elif anc == dst:
893 898 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
894 899 else:
895 900 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
896 901 hg.merge(self._repo, state[1], remind=False)
897 902
898 903 wctx = self._repo[None]
899 904 if self.dirty():
900 905 if anc != dst:
901 906 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
902 907 mergefunc()
903 908 else:
904 909 mergefunc()
905 910 else:
906 911 mergefunc()
907 912
908 913 @annotatesubrepoerror
909 914 def push(self, opts):
910 915 force = opts.get('force')
911 916 newbranch = opts.get('new_branch')
912 917 ssh = opts.get('ssh')
913 918
914 919 # push subrepos depth-first for coherent ordering
915 920 c = self._repo['']
916 921 subs = c.substate # only repos that are committed
917 922 for s in sorted(subs):
918 923 if c.sub(s).push(opts) == 0:
919 924 return False
920 925
921 926 dsturl = _abssource(self._repo, True)
922 927 if not force:
923 928 if self.storeclean(dsturl):
924 929 self.ui.status(
925 930 _('no changes made to subrepo %s since last push to %s\n')
926 931 % (subrelpath(self), dsturl))
927 932 return None
928 933 self.ui.status(_('pushing subrepo %s to %s\n') %
929 934 (subrelpath(self), dsturl))
930 935 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
931 936 res = exchange.push(self._repo, other, force, newbranch=newbranch)
932 937
933 938 # the repo is now clean
934 939 self._cachestorehash(dsturl)
935 940 return res.cgresult
936 941
937 942 @annotatesubrepoerror
938 943 def outgoing(self, ui, dest, opts):
939 944 if 'rev' in opts or 'branch' in opts:
940 945 opts = copy.copy(opts)
941 946 opts.pop('rev', None)
942 947 opts.pop('branch', None)
943 948 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
944 949
945 950 @annotatesubrepoerror
946 951 def incoming(self, ui, source, opts):
947 952 if 'rev' in opts or 'branch' in opts:
948 953 opts = copy.copy(opts)
949 954 opts.pop('rev', None)
950 955 opts.pop('branch', None)
951 956 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
952 957
953 958 @annotatesubrepoerror
954 959 def files(self):
955 960 rev = self._state[1]
956 961 ctx = self._repo[rev]
957 962 return ctx.manifest().keys()
958 963
959 964 def filedata(self, name):
960 965 rev = self._state[1]
961 966 return self._repo[rev][name].data()
962 967
963 968 def fileflags(self, name):
964 969 rev = self._state[1]
965 970 ctx = self._repo[rev]
966 971 return ctx.flags(name)
967 972
968 973 @annotatesubrepoerror
969 974 def printfiles(self, ui, m, fm, fmt, subrepos):
970 975 # If the parent context is a workingctx, use the workingctx here for
971 976 # consistency.
972 977 if self._ctx.rev() is None:
973 978 ctx = self._repo[None]
974 979 else:
975 980 rev = self._state[1]
976 981 ctx = self._repo[rev]
977 982 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
978 983
979 984 @annotatesubrepoerror
980 985 def getfileset(self, expr):
981 986 if self._ctx.rev() is None:
982 987 ctx = self._repo[None]
983 988 else:
984 989 rev = self._state[1]
985 990 ctx = self._repo[rev]
986 991
987 992 files = ctx.getfileset(expr)
988 993
989 994 for subpath in ctx.substate:
990 995 sub = ctx.sub(subpath)
991 996
992 997 try:
993 998 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
994 999 except error.LookupError:
995 1000 self.ui.status(_("skipping missing subrepository: %s\n")
996 1001 % self.wvfs.reljoin(reporelpath(self), subpath))
997 1002 return files
998 1003
999 1004 def walk(self, match):
1000 1005 ctx = self._repo[None]
1001 1006 return ctx.walk(match)
1002 1007
1003 1008 @annotatesubrepoerror
1004 1009 def forget(self, match, prefix):
1005 1010 return cmdutil.forget(self.ui, self._repo, match,
1006 1011 self.wvfs.reljoin(prefix, self._path), True)
1007 1012
1008 1013 @annotatesubrepoerror
1009 1014 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1010 1015 return cmdutil.remove(self.ui, self._repo, matcher,
1011 1016 self.wvfs.reljoin(prefix, self._path),
1012 1017 after, force, subrepos)
1013 1018
1014 1019 @annotatesubrepoerror
1015 1020 def revert(self, substate, *pats, **opts):
1016 1021 # reverting a subrepo is a 2 step process:
1017 1022 # 1. if the no_backup is not set, revert all modified
1018 1023 # files inside the subrepo
1019 1024 # 2. update the subrepo to the revision specified in
1020 1025 # the corresponding substate dictionary
1021 1026 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1022 1027 if not opts.get('no_backup'):
1023 1028 # Revert all files on the subrepo, creating backups
1024 1029 # Note that this will not recursively revert subrepos
1025 1030 # We could do it if there was a set:subrepos() predicate
1026 1031 opts = opts.copy()
1027 1032 opts['date'] = None
1028 1033 opts['rev'] = substate[1]
1029 1034
1030 1035 self.filerevert(*pats, **opts)
1031 1036
1032 1037 # Update the repo to the revision specified in the given substate
1033 1038 if not opts.get('dry_run'):
1034 1039 self.get(substate, overwrite=True)
1035 1040
1036 1041 def filerevert(self, *pats, **opts):
1037 1042 ctx = self._repo[opts['rev']]
1038 1043 parents = self._repo.dirstate.parents()
1039 1044 if opts.get('all'):
1040 1045 pats = ['set:modified()']
1041 1046 else:
1042 1047 pats = []
1043 1048 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1044 1049
1045 1050 def shortid(self, revid):
1046 1051 return revid[:12]
1047 1052
1048 1053 def verify(self):
1049 1054 try:
1050 1055 rev = self._state[1]
1051 1056 ctx = self._repo.unfiltered()[rev]
1052 1057 if ctx.hidden():
1053 1058 # Since hidden revisions aren't pushed/pulled, it seems worth an
1054 1059 # explicit warning.
1055 1060 ui = self._repo.ui
1056 1061 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1057 1062 (self._relpath, node.short(self._ctx.node())))
1058 1063 return 0
1059 1064 except error.RepoLookupError:
1060 1065 # A missing subrepo revision may be a case of needing to pull it, so
1061 1066 # don't treat this as an error.
1062 1067 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1063 1068 (self._relpath, node.short(self._ctx.node())))
1064 1069 return 0
1065 1070
1066 1071 @propertycache
1067 1072 def wvfs(self):
1068 1073 """return own wvfs for efficiency and consistency
1069 1074 """
1070 1075 return self._repo.wvfs
1071 1076
1072 1077 @propertycache
1073 1078 def _relpath(self):
1074 1079 """return path to this subrepository as seen from outermost repository
1075 1080 """
1076 1081 # Keep consistent dir separators by avoiding vfs.join(self._path)
1077 1082 return reporelpath(self._repo)
1078 1083
1079 1084 class svnsubrepo(abstractsubrepo):
1080 1085 def __init__(self, ctx, path, state, allowcreate):
1081 1086 super(svnsubrepo, self).__init__(ctx, path)
1082 1087 self._state = state
1083 1088 self._exe = util.findexe('svn')
1084 1089 if not self._exe:
1085 1090 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1086 1091 % self._path)
1087 1092
1088 1093 def _svncommand(self, commands, filename='', failok=False):
1089 1094 cmd = [self._exe]
1090 1095 extrakw = {}
1091 1096 if not self.ui.interactive():
1092 1097 # Making stdin be a pipe should prevent svn from behaving
1093 1098 # interactively even if we can't pass --non-interactive.
1094 1099 extrakw['stdin'] = subprocess.PIPE
1095 1100 # Starting in svn 1.5 --non-interactive is a global flag
1096 1101 # instead of being per-command, but we need to support 1.4 so
1097 1102 # we have to be intelligent about what commands take
1098 1103 # --non-interactive.
1099 1104 if commands[0] in ('update', 'checkout', 'commit'):
1100 1105 cmd.append('--non-interactive')
1101 1106 cmd.extend(commands)
1102 1107 if filename is not None:
1103 1108 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1104 1109 self._path, filename)
1105 1110 cmd.append(path)
1106 1111 env = dict(encoding.environ)
1107 1112 # Avoid localized output, preserve current locale for everything else.
1108 1113 lc_all = env.get('LC_ALL')
1109 1114 if lc_all:
1110 1115 env['LANG'] = lc_all
1111 1116 del env['LC_ALL']
1112 1117 env['LC_MESSAGES'] = 'C'
1113 1118 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1114 1119 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1115 1120 universal_newlines=True, env=env, **extrakw)
1116 1121 stdout, stderr = p.communicate()
1117 1122 stderr = stderr.strip()
1118 1123 if not failok:
1119 1124 if p.returncode:
1120 1125 raise error.Abort(stderr or 'exited with code %d'
1121 1126 % p.returncode)
1122 1127 if stderr:
1123 1128 self.ui.warn(stderr + '\n')
1124 1129 return stdout, stderr
1125 1130
1126 1131 @propertycache
1127 1132 def _svnversion(self):
1128 1133 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1129 1134 m = re.search(r'^(\d+)\.(\d+)', output)
1130 1135 if not m:
1131 1136 raise error.Abort(_('cannot retrieve svn tool version'))
1132 1137 return (int(m.group(1)), int(m.group(2)))
1133 1138
1134 1139 def _wcrevs(self):
1135 1140 # Get the working directory revision as well as the last
1136 1141 # commit revision so we can compare the subrepo state with
1137 1142 # both. We used to store the working directory one.
1138 1143 output, err = self._svncommand(['info', '--xml'])
1139 1144 doc = xml.dom.minidom.parseString(output)
1140 1145 entries = doc.getElementsByTagName('entry')
1141 1146 lastrev, rev = '0', '0'
1142 1147 if entries:
1143 1148 rev = str(entries[0].getAttribute('revision')) or '0'
1144 1149 commits = entries[0].getElementsByTagName('commit')
1145 1150 if commits:
1146 1151 lastrev = str(commits[0].getAttribute('revision')) or '0'
1147 1152 return (lastrev, rev)
1148 1153
1149 1154 def _wcrev(self):
1150 1155 return self._wcrevs()[0]
1151 1156
1152 1157 def _wcchanged(self):
1153 1158 """Return (changes, extchanges, missing) where changes is True
1154 1159 if the working directory was changed, extchanges is
1155 1160 True if any of these changes concern an external entry and missing
1156 1161 is True if any change is a missing entry.
1157 1162 """
1158 1163 output, err = self._svncommand(['status', '--xml'])
1159 1164 externals, changes, missing = [], [], []
1160 1165 doc = xml.dom.minidom.parseString(output)
1161 1166 for e in doc.getElementsByTagName('entry'):
1162 1167 s = e.getElementsByTagName('wc-status')
1163 1168 if not s:
1164 1169 continue
1165 1170 item = s[0].getAttribute('item')
1166 1171 props = s[0].getAttribute('props')
1167 1172 path = e.getAttribute('path')
1168 1173 if item == 'external':
1169 1174 externals.append(path)
1170 1175 elif item == 'missing':
1171 1176 missing.append(path)
1172 1177 if (item not in ('', 'normal', 'unversioned', 'external')
1173 1178 or props not in ('', 'none', 'normal')):
1174 1179 changes.append(path)
1175 1180 for path in changes:
1176 1181 for ext in externals:
1177 1182 if path == ext or path.startswith(ext + pycompat.ossep):
1178 1183 return True, True, bool(missing)
1179 1184 return bool(changes), False, bool(missing)
1180 1185
1181 1186 def dirty(self, ignoreupdate=False):
1182 1187 if not self._wcchanged()[0]:
1183 1188 if self._state[1] in self._wcrevs() or ignoreupdate:
1184 1189 return False
1185 1190 return True
1186 1191
1187 1192 def basestate(self):
1188 1193 lastrev, rev = self._wcrevs()
1189 1194 if lastrev != rev:
1190 1195 # Last committed rev is not the same than rev. We would
1191 1196 # like to take lastrev but we do not know if the subrepo
1192 1197 # URL exists at lastrev. Test it and fallback to rev it
1193 1198 # is not there.
1194 1199 try:
1195 1200 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1196 1201 return lastrev
1197 1202 except error.Abort:
1198 1203 pass
1199 1204 return rev
1200 1205
1201 1206 @annotatesubrepoerror
1202 1207 def commit(self, text, user, date):
1203 1208 # user and date are out of our hands since svn is centralized
1204 1209 changed, extchanged, missing = self._wcchanged()
1205 1210 if not changed:
1206 1211 return self.basestate()
1207 1212 if extchanged:
1208 1213 # Do not try to commit externals
1209 1214 raise error.Abort(_('cannot commit svn externals'))
1210 1215 if missing:
1211 1216 # svn can commit with missing entries but aborting like hg
1212 1217 # seems a better approach.
1213 1218 raise error.Abort(_('cannot commit missing svn entries'))
1214 1219 commitinfo, err = self._svncommand(['commit', '-m', text])
1215 1220 self.ui.status(commitinfo)
1216 1221 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1217 1222 if not newrev:
1218 1223 if not commitinfo.strip():
1219 1224 # Sometimes, our definition of "changed" differs from
1220 1225 # svn one. For instance, svn ignores missing files
1221 1226 # when committing. If there are only missing files, no
1222 1227 # commit is made, no output and no error code.
1223 1228 raise error.Abort(_('failed to commit svn changes'))
1224 1229 raise error.Abort(commitinfo.splitlines()[-1])
1225 1230 newrev = newrev.groups()[0]
1226 1231 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1227 1232 return newrev
1228 1233
1229 1234 @annotatesubrepoerror
1230 1235 def remove(self):
1231 1236 if self.dirty():
1232 1237 self.ui.warn(_('not removing repo %s because '
1233 1238 'it has changes.\n') % self._path)
1234 1239 return
1235 1240 self.ui.note(_('removing subrepo %s\n') % self._path)
1236 1241
1237 1242 self.wvfs.rmtree(forcibly=True)
1238 1243 try:
1239 1244 pwvfs = self._ctx.repo().wvfs
1240 1245 pwvfs.removedirs(pwvfs.dirname(self._path))
1241 1246 except OSError:
1242 1247 pass
1243 1248
1244 1249 @annotatesubrepoerror
1245 1250 def get(self, state, overwrite=False):
1246 1251 if overwrite:
1247 1252 self._svncommand(['revert', '--recursive'])
1248 1253 args = ['checkout']
1249 1254 if self._svnversion >= (1, 5):
1250 1255 args.append('--force')
1251 1256 # The revision must be specified at the end of the URL to properly
1252 1257 # update to a directory which has since been deleted and recreated.
1253 1258 args.append('%s@%s' % (state[0], state[1]))
1254 1259 status, err = self._svncommand(args, failok=True)
1255 1260 _sanitize(self.ui, self.wvfs, '.svn')
1256 1261 if not re.search('Checked out revision [0-9]+.', status):
1257 1262 if ('is already a working copy for a different URL' in err
1258 1263 and (self._wcchanged()[:2] == (False, False))):
1259 1264 # obstructed but clean working copy, so just blow it away.
1260 1265 self.remove()
1261 1266 self.get(state, overwrite=False)
1262 1267 return
1263 1268 raise error.Abort((status or err).splitlines()[-1])
1264 1269 self.ui.status(status)
1265 1270
1266 1271 @annotatesubrepoerror
1267 1272 def merge(self, state):
1268 1273 old = self._state[1]
1269 1274 new = state[1]
1270 1275 wcrev = self._wcrev()
1271 1276 if new != wcrev:
1272 1277 dirty = old == wcrev or self._wcchanged()[0]
1273 1278 if _updateprompt(self.ui, self, dirty, wcrev, new):
1274 1279 self.get(state, False)
1275 1280
1276 1281 def push(self, opts):
1277 1282 # push is a no-op for SVN
1278 1283 return True
1279 1284
1280 1285 @annotatesubrepoerror
1281 1286 def files(self):
1282 1287 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1283 1288 doc = xml.dom.minidom.parseString(output)
1284 1289 paths = []
1285 1290 for e in doc.getElementsByTagName('entry'):
1286 1291 kind = str(e.getAttribute('kind'))
1287 1292 if kind != 'file':
1288 1293 continue
1289 1294 name = ''.join(c.data for c
1290 1295 in e.getElementsByTagName('name')[0].childNodes
1291 1296 if c.nodeType == c.TEXT_NODE)
1292 1297 paths.append(name.encode('utf-8'))
1293 1298 return paths
1294 1299
1295 1300 def filedata(self, name):
1296 1301 return self._svncommand(['cat'], name)[0]
1297 1302
1298 1303
1299 1304 class gitsubrepo(abstractsubrepo):
1300 1305 def __init__(self, ctx, path, state, allowcreate):
1301 1306 super(gitsubrepo, self).__init__(ctx, path)
1302 1307 self._state = state
1303 1308 self._abspath = ctx.repo().wjoin(path)
1304 1309 self._subparent = ctx.repo()
1305 1310 self._ensuregit()
1306 1311
1307 1312 def _ensuregit(self):
1308 1313 try:
1309 1314 self._gitexecutable = 'git'
1310 1315 out, err = self._gitnodir(['--version'])
1311 1316 except OSError as e:
1312 1317 genericerror = _("error executing git for subrepo '%s': %s")
1313 1318 notfoundhint = _("check git is installed and in your PATH")
1314 1319 if e.errno != errno.ENOENT:
1315 1320 raise error.Abort(genericerror % (self._path, e.strerror))
1316 1321 elif pycompat.osname == 'nt':
1317 1322 try:
1318 1323 self._gitexecutable = 'git.cmd'
1319 1324 out, err = self._gitnodir(['--version'])
1320 1325 except OSError as e2:
1321 1326 if e2.errno == errno.ENOENT:
1322 1327 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1323 1328 " for subrepo '%s'") % self._path,
1324 1329 hint=notfoundhint)
1325 1330 else:
1326 1331 raise error.Abort(genericerror % (self._path,
1327 1332 e2.strerror))
1328 1333 else:
1329 1334 raise error.Abort(_("couldn't find git for subrepo '%s'")
1330 1335 % self._path, hint=notfoundhint)
1331 1336 versionstatus = self._checkversion(out)
1332 1337 if versionstatus == 'unknown':
1333 1338 self.ui.warn(_('cannot retrieve git version\n'))
1334 1339 elif versionstatus == 'abort':
1335 1340 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1336 1341 elif versionstatus == 'warning':
1337 1342 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1338 1343
1339 1344 @staticmethod
1340 1345 def _gitversion(out):
1341 1346 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1342 1347 if m:
1343 1348 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1344 1349
1345 1350 m = re.search(r'^git version (\d+)\.(\d+)', out)
1346 1351 if m:
1347 1352 return (int(m.group(1)), int(m.group(2)), 0)
1348 1353
1349 1354 return -1
1350 1355
1351 1356 @staticmethod
1352 1357 def _checkversion(out):
1353 1358 '''ensure git version is new enough
1354 1359
1355 1360 >>> _checkversion = gitsubrepo._checkversion
1356 1361 >>> _checkversion('git version 1.6.0')
1357 1362 'ok'
1358 1363 >>> _checkversion('git version 1.8.5')
1359 1364 'ok'
1360 1365 >>> _checkversion('git version 1.4.0')
1361 1366 'abort'
1362 1367 >>> _checkversion('git version 1.5.0')
1363 1368 'warning'
1364 1369 >>> _checkversion('git version 1.9-rc0')
1365 1370 'ok'
1366 1371 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1367 1372 'ok'
1368 1373 >>> _checkversion('git version 1.9.0.GIT')
1369 1374 'ok'
1370 1375 >>> _checkversion('git version 12345')
1371 1376 'unknown'
1372 1377 >>> _checkversion('no')
1373 1378 'unknown'
1374 1379 '''
1375 1380 version = gitsubrepo._gitversion(out)
1376 1381 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1377 1382 # despite the docstring comment. For now, error on 1.4.0, warn on
1378 1383 # 1.5.0 but attempt to continue.
1379 1384 if version == -1:
1380 1385 return 'unknown'
1381 1386 if version < (1, 5, 0):
1382 1387 return 'abort'
1383 1388 elif version < (1, 6, 0):
1384 1389 return 'warning'
1385 1390 return 'ok'
1386 1391
1387 1392 def _gitcommand(self, commands, env=None, stream=False):
1388 1393 return self._gitdir(commands, env=env, stream=stream)[0]
1389 1394
1390 1395 def _gitdir(self, commands, env=None, stream=False):
1391 1396 return self._gitnodir(commands, env=env, stream=stream,
1392 1397 cwd=self._abspath)
1393 1398
1394 1399 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1395 1400 """Calls the git command
1396 1401
1397 1402 The methods tries to call the git command. versions prior to 1.6.0
1398 1403 are not supported and very probably fail.
1399 1404 """
1400 1405 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1401 1406 if env is None:
1402 1407 env = encoding.environ.copy()
1403 1408 # disable localization for Git output (issue5176)
1404 1409 env['LC_ALL'] = 'C'
1405 1410 # fix for Git CVE-2015-7545
1406 1411 if 'GIT_ALLOW_PROTOCOL' not in env:
1407 1412 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1408 1413 # unless ui.quiet is set, print git's stderr,
1409 1414 # which is mostly progress and useful info
1410 1415 errpipe = None
1411 1416 if self.ui.quiet:
1412 1417 errpipe = open(os.devnull, 'w')
1413 1418 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1414 1419 cwd=cwd, env=env, close_fds=util.closefds,
1415 1420 stdout=subprocess.PIPE, stderr=errpipe)
1416 1421 if stream:
1417 1422 return p.stdout, None
1418 1423
1419 1424 retdata = p.stdout.read().strip()
1420 1425 # wait for the child to exit to avoid race condition.
1421 1426 p.wait()
1422 1427
1423 1428 if p.returncode != 0 and p.returncode != 1:
1424 1429 # there are certain error codes that are ok
1425 1430 command = commands[0]
1426 1431 if command in ('cat-file', 'symbolic-ref'):
1427 1432 return retdata, p.returncode
1428 1433 # for all others, abort
1429 1434 raise error.Abort(_('git %s error %d in %s') %
1430 1435 (command, p.returncode, self._relpath))
1431 1436
1432 1437 return retdata, p.returncode
1433 1438
1434 1439 def _gitmissing(self):
1435 1440 return not self.wvfs.exists('.git')
1436 1441
1437 1442 def _gitstate(self):
1438 1443 return self._gitcommand(['rev-parse', 'HEAD'])
1439 1444
1440 1445 def _gitcurrentbranch(self):
1441 1446 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1442 1447 if err:
1443 1448 current = None
1444 1449 return current
1445 1450
1446 1451 def _gitremote(self, remote):
1447 1452 out = self._gitcommand(['remote', 'show', '-n', remote])
1448 1453 line = out.split('\n')[1]
1449 1454 i = line.index('URL: ') + len('URL: ')
1450 1455 return line[i:]
1451 1456
1452 1457 def _githavelocally(self, revision):
1453 1458 out, code = self._gitdir(['cat-file', '-e', revision])
1454 1459 return code == 0
1455 1460
1456 1461 def _gitisancestor(self, r1, r2):
1457 1462 base = self._gitcommand(['merge-base', r1, r2])
1458 1463 return base == r1
1459 1464
1460 1465 def _gitisbare(self):
1461 1466 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1462 1467
1463 1468 def _gitupdatestat(self):
1464 1469 """This must be run before git diff-index.
1465 1470 diff-index only looks at changes to file stat;
1466 1471 this command looks at file contents and updates the stat."""
1467 1472 self._gitcommand(['update-index', '-q', '--refresh'])
1468 1473
1469 1474 def _gitbranchmap(self):
1470 1475 '''returns 2 things:
1471 1476 a map from git branch to revision
1472 1477 a map from revision to branches'''
1473 1478 branch2rev = {}
1474 1479 rev2branch = {}
1475 1480
1476 1481 out = self._gitcommand(['for-each-ref', '--format',
1477 1482 '%(objectname) %(refname)'])
1478 1483 for line in out.split('\n'):
1479 1484 revision, ref = line.split(' ')
1480 1485 if (not ref.startswith('refs/heads/') and
1481 1486 not ref.startswith('refs/remotes/')):
1482 1487 continue
1483 1488 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1484 1489 continue # ignore remote/HEAD redirects
1485 1490 branch2rev[ref] = revision
1486 1491 rev2branch.setdefault(revision, []).append(ref)
1487 1492 return branch2rev, rev2branch
1488 1493
1489 1494 def _gittracking(self, branches):
1490 1495 'return map of remote branch to local tracking branch'
1491 1496 # assumes no more than one local tracking branch for each remote
1492 1497 tracking = {}
1493 1498 for b in branches:
1494 1499 if b.startswith('refs/remotes/'):
1495 1500 continue
1496 1501 bname = b.split('/', 2)[2]
1497 1502 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1498 1503 if remote:
1499 1504 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1500 1505 tracking['refs/remotes/%s/%s' %
1501 1506 (remote, ref.split('/', 2)[2])] = b
1502 1507 return tracking
1503 1508
1504 1509 def _abssource(self, source):
1505 1510 if '://' not in source:
1506 1511 # recognize the scp syntax as an absolute source
1507 1512 colon = source.find(':')
1508 1513 if colon != -1 and '/' not in source[:colon]:
1509 1514 return source
1510 1515 self._subsource = source
1511 1516 return _abssource(self)
1512 1517
1513 1518 def _fetch(self, source, revision):
1514 1519 if self._gitmissing():
1515 1520 source = self._abssource(source)
1516 1521 self.ui.status(_('cloning subrepo %s from %s\n') %
1517 1522 (self._relpath, source))
1518 1523 self._gitnodir(['clone', source, self._abspath])
1519 1524 if self._githavelocally(revision):
1520 1525 return
1521 1526 self.ui.status(_('pulling subrepo %s from %s\n') %
1522 1527 (self._relpath, self._gitremote('origin')))
1523 1528 # try only origin: the originally cloned repo
1524 1529 self._gitcommand(['fetch'])
1525 1530 if not self._githavelocally(revision):
1526 1531 raise error.Abort(_("revision %s does not exist in subrepo %s\n") %
1527 1532 (revision, self._relpath))
1528 1533
1529 1534 @annotatesubrepoerror
1530 1535 def dirty(self, ignoreupdate=False):
1531 1536 if self._gitmissing():
1532 1537 return self._state[1] != ''
1533 1538 if self._gitisbare():
1534 1539 return True
1535 1540 if not ignoreupdate and self._state[1] != self._gitstate():
1536 1541 # different version checked out
1537 1542 return True
1538 1543 # check for staged changes or modified files; ignore untracked files
1539 1544 self._gitupdatestat()
1540 1545 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1541 1546 return code == 1
1542 1547
1543 1548 def basestate(self):
1544 1549 return self._gitstate()
1545 1550
1546 1551 @annotatesubrepoerror
1547 1552 def get(self, state, overwrite=False):
1548 1553 source, revision, kind = state
1549 1554 if not revision:
1550 1555 self.remove()
1551 1556 return
1552 1557 self._fetch(source, revision)
1553 1558 # if the repo was set to be bare, unbare it
1554 1559 if self._gitisbare():
1555 1560 self._gitcommand(['config', 'core.bare', 'false'])
1556 1561 if self._gitstate() == revision:
1557 1562 self._gitcommand(['reset', '--hard', 'HEAD'])
1558 1563 return
1559 1564 elif self._gitstate() == revision:
1560 1565 if overwrite:
1561 1566 # first reset the index to unmark new files for commit, because
1562 1567 # reset --hard will otherwise throw away files added for commit,
1563 1568 # not just unmark them.
1564 1569 self._gitcommand(['reset', 'HEAD'])
1565 1570 self._gitcommand(['reset', '--hard', 'HEAD'])
1566 1571 return
1567 1572 branch2rev, rev2branch = self._gitbranchmap()
1568 1573
1569 1574 def checkout(args):
1570 1575 cmd = ['checkout']
1571 1576 if overwrite:
1572 1577 # first reset the index to unmark new files for commit, because
1573 1578 # the -f option will otherwise throw away files added for
1574 1579 # commit, not just unmark them.
1575 1580 self._gitcommand(['reset', 'HEAD'])
1576 1581 cmd.append('-f')
1577 1582 self._gitcommand(cmd + args)
1578 1583 _sanitize(self.ui, self.wvfs, '.git')
1579 1584
1580 1585 def rawcheckout():
1581 1586 # no branch to checkout, check it out with no branch
1582 1587 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1583 1588 self._relpath)
1584 1589 self.ui.warn(_('check out a git branch if you intend '
1585 1590 'to make changes\n'))
1586 1591 checkout(['-q', revision])
1587 1592
1588 1593 if revision not in rev2branch:
1589 1594 rawcheckout()
1590 1595 return
1591 1596 branches = rev2branch[revision]
1592 1597 firstlocalbranch = None
1593 1598 for b in branches:
1594 1599 if b == 'refs/heads/master':
1595 1600 # master trumps all other branches
1596 1601 checkout(['refs/heads/master'])
1597 1602 return
1598 1603 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1599 1604 firstlocalbranch = b
1600 1605 if firstlocalbranch:
1601 1606 checkout([firstlocalbranch])
1602 1607 return
1603 1608
1604 1609 tracking = self._gittracking(branch2rev.keys())
1605 1610 # choose a remote branch already tracked if possible
1606 1611 remote = branches[0]
1607 1612 if remote not in tracking:
1608 1613 for b in branches:
1609 1614 if b in tracking:
1610 1615 remote = b
1611 1616 break
1612 1617
1613 1618 if remote not in tracking:
1614 1619 # create a new local tracking branch
1615 1620 local = remote.split('/', 3)[3]
1616 1621 checkout(['-b', local, remote])
1617 1622 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1618 1623 # When updating to a tracked remote branch,
1619 1624 # if the local tracking branch is downstream of it,
1620 1625 # a normal `git pull` would have performed a "fast-forward merge"
1621 1626 # which is equivalent to updating the local branch to the remote.
1622 1627 # Since we are only looking at branching at update, we need to
1623 1628 # detect this situation and perform this action lazily.
1624 1629 if tracking[remote] != self._gitcurrentbranch():
1625 1630 checkout([tracking[remote]])
1626 1631 self._gitcommand(['merge', '--ff', remote])
1627 1632 _sanitize(self.ui, self.wvfs, '.git')
1628 1633 else:
1629 1634 # a real merge would be required, just checkout the revision
1630 1635 rawcheckout()
1631 1636
1632 1637 @annotatesubrepoerror
1633 1638 def commit(self, text, user, date):
1634 1639 if self._gitmissing():
1635 1640 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1636 1641 cmd = ['commit', '-a', '-m', text]
1637 1642 env = encoding.environ.copy()
1638 1643 if user:
1639 1644 cmd += ['--author', user]
1640 1645 if date:
1641 1646 # git's date parser silently ignores when seconds < 1e9
1642 1647 # convert to ISO8601
1643 1648 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1644 1649 '%Y-%m-%dT%H:%M:%S %1%2')
1645 1650 self._gitcommand(cmd, env=env)
1646 1651 # make sure commit works otherwise HEAD might not exist under certain
1647 1652 # circumstances
1648 1653 return self._gitstate()
1649 1654
1650 1655 @annotatesubrepoerror
1651 1656 def merge(self, state):
1652 1657 source, revision, kind = state
1653 1658 self._fetch(source, revision)
1654 1659 base = self._gitcommand(['merge-base', revision, self._state[1]])
1655 1660 self._gitupdatestat()
1656 1661 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1657 1662
1658 1663 def mergefunc():
1659 1664 if base == revision:
1660 1665 self.get(state) # fast forward merge
1661 1666 elif base != self._state[1]:
1662 1667 self._gitcommand(['merge', '--no-commit', revision])
1663 1668 _sanitize(self.ui, self.wvfs, '.git')
1664 1669
1665 1670 if self.dirty():
1666 1671 if self._gitstate() != revision:
1667 1672 dirty = self._gitstate() == self._state[1] or code != 0
1668 1673 if _updateprompt(self.ui, self, dirty,
1669 1674 self._state[1][:7], revision[:7]):
1670 1675 mergefunc()
1671 1676 else:
1672 1677 mergefunc()
1673 1678
1674 1679 @annotatesubrepoerror
1675 1680 def push(self, opts):
1676 1681 force = opts.get('force')
1677 1682
1678 1683 if not self._state[1]:
1679 1684 return True
1680 1685 if self._gitmissing():
1681 1686 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1682 1687 # if a branch in origin contains the revision, nothing to do
1683 1688 branch2rev, rev2branch = self._gitbranchmap()
1684 1689 if self._state[1] in rev2branch:
1685 1690 for b in rev2branch[self._state[1]]:
1686 1691 if b.startswith('refs/remotes/origin/'):
1687 1692 return True
1688 1693 for b, revision in branch2rev.iteritems():
1689 1694 if b.startswith('refs/remotes/origin/'):
1690 1695 if self._gitisancestor(self._state[1], revision):
1691 1696 return True
1692 1697 # otherwise, try to push the currently checked out branch
1693 1698 cmd = ['push']
1694 1699 if force:
1695 1700 cmd.append('--force')
1696 1701
1697 1702 current = self._gitcurrentbranch()
1698 1703 if current:
1699 1704 # determine if the current branch is even useful
1700 1705 if not self._gitisancestor(self._state[1], current):
1701 1706 self.ui.warn(_('unrelated git branch checked out '
1702 1707 'in subrepo %s\n') % self._relpath)
1703 1708 return False
1704 1709 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1705 1710 (current.split('/', 2)[2], self._relpath))
1706 1711 ret = self._gitdir(cmd + ['origin', current])
1707 1712 return ret[1] == 0
1708 1713 else:
1709 1714 self.ui.warn(_('no branch checked out in subrepo %s\n'
1710 1715 'cannot push revision %s\n') %
1711 1716 (self._relpath, self._state[1]))
1712 1717 return False
1713 1718
1714 1719 @annotatesubrepoerror
1715 1720 def add(self, ui, match, prefix, explicitonly, **opts):
1716 1721 if self._gitmissing():
1717 1722 return []
1718 1723
1719 1724 (modified, added, removed,
1720 1725 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1721 1726 clean=True)
1722 1727
1723 1728 tracked = set()
1724 1729 # dirstates 'amn' warn, 'r' is added again
1725 1730 for l in (modified, added, deleted, clean):
1726 1731 tracked.update(l)
1727 1732
1728 1733 # Unknown files not of interest will be rejected by the matcher
1729 1734 files = unknown
1730 1735 files.extend(match.files())
1731 1736
1732 1737 rejected = []
1733 1738
1734 1739 files = [f for f in sorted(set(files)) if match(f)]
1735 1740 for f in files:
1736 1741 exact = match.exact(f)
1737 1742 command = ["add"]
1738 1743 if exact:
1739 1744 command.append("-f") #should be added, even if ignored
1740 1745 if ui.verbose or not exact:
1741 1746 ui.status(_('adding %s\n') % match.rel(f))
1742 1747
1743 1748 if f in tracked: # hg prints 'adding' even if already tracked
1744 1749 if exact:
1745 1750 rejected.append(f)
1746 1751 continue
1747 1752 if not opts.get('dry_run'):
1748 1753 self._gitcommand(command + [f])
1749 1754
1750 1755 for f in rejected:
1751 1756 ui.warn(_("%s already tracked!\n") % match.abs(f))
1752 1757
1753 1758 return rejected
1754 1759
1755 1760 @annotatesubrepoerror
1756 1761 def remove(self):
1757 1762 if self._gitmissing():
1758 1763 return
1759 1764 if self.dirty():
1760 1765 self.ui.warn(_('not removing repo %s because '
1761 1766 'it has changes.\n') % self._relpath)
1762 1767 return
1763 1768 # we can't fully delete the repository as it may contain
1764 1769 # local-only history
1765 1770 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1766 1771 self._gitcommand(['config', 'core.bare', 'true'])
1767 1772 for f, kind in self.wvfs.readdir():
1768 1773 if f == '.git':
1769 1774 continue
1770 1775 if kind == stat.S_IFDIR:
1771 1776 self.wvfs.rmtree(f)
1772 1777 else:
1773 1778 self.wvfs.unlink(f)
1774 1779
1775 1780 def archive(self, archiver, prefix, match=None):
1776 1781 total = 0
1777 1782 source, revision = self._state
1778 1783 if not revision:
1779 1784 return total
1780 1785 self._fetch(source, revision)
1781 1786
1782 1787 # Parse git's native archive command.
1783 1788 # This should be much faster than manually traversing the trees
1784 1789 # and objects with many subprocess calls.
1785 1790 tarstream = self._gitcommand(['archive', revision], stream=True)
1786 1791 tar = tarfile.open(fileobj=tarstream, mode='r|')
1787 1792 relpath = subrelpath(self)
1788 1793 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1789 1794 for i, info in enumerate(tar):
1790 1795 if info.isdir():
1791 1796 continue
1792 1797 if match and not match(info.name):
1793 1798 continue
1794 1799 if info.issym():
1795 1800 data = info.linkname
1796 1801 else:
1797 1802 data = tar.extractfile(info).read()
1798 1803 archiver.addfile(prefix + self._path + '/' + info.name,
1799 1804 info.mode, info.issym(), data)
1800 1805 total += 1
1801 1806 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1802 1807 unit=_('files'))
1803 1808 self.ui.progress(_('archiving (%s)') % relpath, None)
1804 1809 return total
1805 1810
1806 1811
1807 1812 @annotatesubrepoerror
1808 1813 def cat(self, match, prefix, **opts):
1809 1814 rev = self._state[1]
1810 1815 if match.anypats():
1811 1816 return 1 #No support for include/exclude yet
1812 1817
1813 1818 if not match.files():
1814 1819 return 1
1815 1820
1816 1821 for f in match.files():
1817 1822 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1818 1823 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1819 1824 self._ctx.node(),
1820 1825 pathname=self.wvfs.reljoin(prefix, f))
1821 1826 fp.write(output)
1822 1827 fp.close()
1823 1828 return 0
1824 1829
1825 1830
1826 1831 @annotatesubrepoerror
1827 1832 def status(self, rev2, **opts):
1828 1833 rev1 = self._state[1]
1829 1834 if self._gitmissing() or not rev1:
1830 1835 # if the repo is missing, return no results
1831 1836 return scmutil.status([], [], [], [], [], [], [])
1832 1837 modified, added, removed = [], [], []
1833 1838 self._gitupdatestat()
1834 1839 if rev2:
1835 1840 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1836 1841 else:
1837 1842 command = ['diff-index', '--no-renames', rev1]
1838 1843 out = self._gitcommand(command)
1839 1844 for line in out.split('\n'):
1840 1845 tab = line.find('\t')
1841 1846 if tab == -1:
1842 1847 continue
1843 1848 status, f = line[tab - 1], line[tab + 1:]
1844 1849 if status == 'M':
1845 1850 modified.append(f)
1846 1851 elif status == 'A':
1847 1852 added.append(f)
1848 1853 elif status == 'D':
1849 1854 removed.append(f)
1850 1855
1851 1856 deleted, unknown, ignored, clean = [], [], [], []
1852 1857
1853 1858 command = ['status', '--porcelain', '-z']
1854 1859 if opts.get('unknown'):
1855 1860 command += ['--untracked-files=all']
1856 1861 if opts.get('ignored'):
1857 1862 command += ['--ignored']
1858 1863 out = self._gitcommand(command)
1859 1864
1860 1865 changedfiles = set()
1861 1866 changedfiles.update(modified)
1862 1867 changedfiles.update(added)
1863 1868 changedfiles.update(removed)
1864 1869 for line in out.split('\0'):
1865 1870 if not line:
1866 1871 continue
1867 1872 st = line[0:2]
1868 1873 #moves and copies show 2 files on one line
1869 1874 if line.find('\0') >= 0:
1870 1875 filename1, filename2 = line[3:].split('\0')
1871 1876 else:
1872 1877 filename1 = line[3:]
1873 1878 filename2 = None
1874 1879
1875 1880 changedfiles.add(filename1)
1876 1881 if filename2:
1877 1882 changedfiles.add(filename2)
1878 1883
1879 1884 if st == '??':
1880 1885 unknown.append(filename1)
1881 1886 elif st == '!!':
1882 1887 ignored.append(filename1)
1883 1888
1884 1889 if opts.get('clean'):
1885 1890 out = self._gitcommand(['ls-files'])
1886 1891 for f in out.split('\n'):
1887 1892 if not f in changedfiles:
1888 1893 clean.append(f)
1889 1894
1890 1895 return scmutil.status(modified, added, removed, deleted,
1891 1896 unknown, ignored, clean)
1892 1897
1893 1898 @annotatesubrepoerror
1894 1899 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1895 1900 node1 = self._state[1]
1896 1901 cmd = ['diff', '--no-renames']
1897 1902 if opts['stat']:
1898 1903 cmd.append('--stat')
1899 1904 else:
1900 1905 # for Git, this also implies '-p'
1901 1906 cmd.append('-U%d' % diffopts.context)
1902 1907
1903 1908 gitprefix = self.wvfs.reljoin(prefix, self._path)
1904 1909
1905 1910 if diffopts.noprefix:
1906 1911 cmd.extend(['--src-prefix=%s/' % gitprefix,
1907 1912 '--dst-prefix=%s/' % gitprefix])
1908 1913 else:
1909 1914 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1910 1915 '--dst-prefix=b/%s/' % gitprefix])
1911 1916
1912 1917 if diffopts.ignorews:
1913 1918 cmd.append('--ignore-all-space')
1914 1919 if diffopts.ignorewsamount:
1915 1920 cmd.append('--ignore-space-change')
1916 1921 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1917 1922 and diffopts.ignoreblanklines:
1918 1923 cmd.append('--ignore-blank-lines')
1919 1924
1920 1925 cmd.append(node1)
1921 1926 if node2:
1922 1927 cmd.append(node2)
1923 1928
1924 1929 output = ""
1925 1930 if match.always():
1926 1931 output += self._gitcommand(cmd) + '\n'
1927 1932 else:
1928 1933 st = self.status(node2)[:3]
1929 1934 files = [f for sublist in st for f in sublist]
1930 1935 for f in files:
1931 1936 if match(f):
1932 1937 output += self._gitcommand(cmd + ['--', f]) + '\n'
1933 1938
1934 1939 if output.strip():
1935 1940 ui.write(output)
1936 1941
1937 1942 @annotatesubrepoerror
1938 1943 def revert(self, substate, *pats, **opts):
1939 1944 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1940 1945 if not opts.get('no_backup'):
1941 1946 status = self.status(None)
1942 1947 names = status.modified
1943 1948 for name in names:
1944 1949 bakname = scmutil.origpath(self.ui, self._subparent, name)
1945 1950 self.ui.note(_('saving current version of %s as %s\n') %
1946 1951 (name, bakname))
1947 1952 self.wvfs.rename(name, bakname)
1948 1953
1949 1954 if not opts.get('dry_run'):
1950 1955 self.get(substate, overwrite=True)
1951 1956 return []
1952 1957
1953 1958 def shortid(self, revid):
1954 1959 return revid[:7]
1955 1960
1956 1961 types = {
1957 1962 'hg': hgsubrepo,
1958 1963 'svn': svnsubrepo,
1959 1964 'git': gitsubrepo,
1960 1965 }
@@ -1,1778 +1,1778 b''
1 1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
2 2
3 3 $ echo "[ui]" >> $HGRCPATH
4 4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
5 5
6 6 $ hg init t
7 7 $ cd t
8 8
9 9 first revision, no sub
10 10
11 11 $ echo a > a
12 12 $ hg ci -Am0
13 13 adding a
14 14
15 15 add first sub
16 16
17 17 $ echo s = s > .hgsub
18 18 $ hg add .hgsub
19 19 $ hg init s
20 20 $ echo a > s/a
21 21
22 22 Issue2232: committing a subrepo without .hgsub
23 23
24 24 $ hg ci -mbad s
25 25 abort: can't commit subrepos without .hgsub
26 26 [255]
27 27
28 28 $ hg -R s add s/a
29 29 $ hg files -S
30 30 .hgsub
31 31 a
32 32 s/a (glob)
33 33
34 34 $ hg -R s ci -Ams0
35 35 $ hg sum
36 36 parent: 0:f7b1eb17ad24 tip
37 37 0
38 38 branch: default
39 39 commit: 1 added, 1 subrepos
40 40 update: (current)
41 41 phases: 1 draft
42 42 $ hg ci -m1
43 43
44 44 test handling .hgsubstate "added" explicitly.
45 45
46 46 $ hg parents --template '{node}\n{files}\n'
47 47 7cf8cfea66e410e8e3336508dfeec07b3192de51
48 48 .hgsub .hgsubstate
49 49 $ hg rollback -q
50 50 $ hg add .hgsubstate
51 51 $ hg ci -m1
52 52 $ hg parents --template '{node}\n{files}\n'
53 53 7cf8cfea66e410e8e3336508dfeec07b3192de51
54 54 .hgsub .hgsubstate
55 55
56 56 Subrepopath which overlaps with filepath, does not change warnings in remove()
57 57
58 58 $ mkdir snot
59 59 $ touch snot/file
60 60 $ hg remove -S snot/file
61 61 not removing snot/file: file is untracked (glob)
62 62 [1]
63 63 $ hg cat snot/filenot
64 64 snot/filenot: no such file in rev 7cf8cfea66e4 (glob)
65 65 [1]
66 66 $ rm -r snot
67 67
68 68 Revert subrepo and test subrepo fileset keyword:
69 69
70 70 $ echo b > s/a
71 71 $ hg revert --dry-run "set:subrepo('glob:s*')"
72 72 reverting subrepo s
73 73 reverting s/a (glob)
74 74 $ cat s/a
75 75 b
76 76 $ hg revert "set:subrepo('glob:s*')"
77 77 reverting subrepo s
78 78 reverting s/a (glob)
79 79 $ cat s/a
80 80 a
81 81 $ rm s/a.orig
82 82
83 83 Revert subrepo with no backup. The "reverting s/a" line is gone since
84 84 we're really running 'hg update' in the subrepo:
85 85
86 86 $ echo b > s/a
87 87 $ hg revert --no-backup s
88 88 reverting subrepo s
89 89
90 90 Issue2022: update -C
91 91
92 92 $ echo b > s/a
93 93 $ hg sum
94 94 parent: 1:7cf8cfea66e4 tip
95 95 1
96 96 branch: default
97 97 commit: 1 subrepos
98 98 update: (current)
99 99 phases: 2 draft
100 100 $ hg co -C 1
101 101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102 $ hg sum
103 103 parent: 1:7cf8cfea66e4 tip
104 104 1
105 105 branch: default
106 106 commit: (clean)
107 107 update: (current)
108 108 phases: 2 draft
109 109
110 110 commands that require a clean repo should respect subrepos
111 111
112 112 $ echo b >> s/a
113 113 $ hg backout tip
114 114 abort: uncommitted changes in subrepository 's'
115 115 [255]
116 116 $ hg revert -C -R s s/a
117 117
118 118 add sub sub
119 119
120 120 $ echo ss = ss > s/.hgsub
121 121 $ hg init s/ss
122 122 $ echo a > s/ss/a
123 123 $ hg -R s add s/.hgsub
124 124 $ hg -R s/ss add s/ss/a
125 125 $ hg sum
126 126 parent: 1:7cf8cfea66e4 tip
127 127 1
128 128 branch: default
129 129 commit: 1 subrepos
130 130 update: (current)
131 131 phases: 2 draft
132 132 $ hg ci -m2
133 133 committing subrepository s
134 134 committing subrepository s/ss (glob)
135 135 $ hg sum
136 136 parent: 2:df30734270ae tip
137 137 2
138 138 branch: default
139 139 commit: (clean)
140 140 update: (current)
141 141 phases: 3 draft
142 142
143 143 test handling .hgsubstate "modified" explicitly.
144 144
145 145 $ hg parents --template '{node}\n{files}\n'
146 146 df30734270ae757feb35e643b7018e818e78a9aa
147 147 .hgsubstate
148 148 $ hg rollback -q
149 149 $ hg status -A .hgsubstate
150 150 M .hgsubstate
151 151 $ hg ci -m2
152 152 $ hg parents --template '{node}\n{files}\n'
153 153 df30734270ae757feb35e643b7018e818e78a9aa
154 154 .hgsubstate
155 155
156 156 bump sub rev (and check it is ignored by ui.commitsubrepos)
157 157
158 158 $ echo b > s/a
159 159 $ hg -R s ci -ms1
160 160 $ hg --config ui.commitsubrepos=no ci -m3
161 161
162 162 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
163 163
164 164 $ echo c > s/a
165 165 $ hg --config ui.commitsubrepos=no ci -m4
166 166 abort: uncommitted changes in subrepository 's'
167 167 (use --subrepos for recursive commit)
168 168 [255]
169 169 $ hg id
170 170 f6affe3fbfaa+ tip
171 171 $ hg -R s ci -mc
172 172 $ hg id
173 173 f6affe3fbfaa+ tip
174 174 $ echo d > s/a
175 175 $ hg ci -m4
176 176 committing subrepository s
177 177 $ hg tip -R s
178 178 changeset: 4:02dcf1d70411
179 179 tag: tip
180 180 user: test
181 181 date: Thu Jan 01 00:00:00 1970 +0000
182 182 summary: 4
183 183
184 184
185 185 check caching
186 186
187 187 $ hg co 0
188 188 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
189 189 $ hg debugsub
190 190
191 191 restore
192 192
193 193 $ hg co
194 194 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 195 $ hg debugsub
196 196 path s
197 197 source s
198 198 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
199 199
200 200 new branch for merge tests
201 201
202 202 $ hg co 1
203 203 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
204 204 $ echo t = t >> .hgsub
205 205 $ hg init t
206 206 $ echo t > t/t
207 207 $ hg -R t add t
208 208 adding t/t (glob)
209 209
210 210 5
211 211
212 212 $ hg ci -m5 # add sub
213 213 committing subrepository t
214 214 created new head
215 215 $ echo t2 > t/t
216 216
217 217 6
218 218
219 219 $ hg st -R s
220 220 $ hg ci -m6 # change sub
221 221 committing subrepository t
222 222 $ hg debugsub
223 223 path s
224 224 source s
225 225 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
226 226 path t
227 227 source t
228 228 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
229 229 $ echo t3 > t/t
230 230
231 231 7
232 232
233 233 $ hg ci -m7 # change sub again for conflict test
234 234 committing subrepository t
235 235 $ hg rm .hgsub
236 236
237 237 8
238 238
239 239 $ hg ci -m8 # remove sub
240 240
241 241 test handling .hgsubstate "removed" explicitly.
242 242
243 243 $ hg parents --template '{node}\n{files}\n'
244 244 96615c1dad2dc8e3796d7332c77ce69156f7b78e
245 245 .hgsub .hgsubstate
246 246 $ hg rollback -q
247 247 $ hg remove .hgsubstate
248 248 $ hg ci -m8
249 249 $ hg parents --template '{node}\n{files}\n'
250 250 96615c1dad2dc8e3796d7332c77ce69156f7b78e
251 251 .hgsub .hgsubstate
252 252
253 253 merge tests
254 254
255 255 $ hg co -C 3
256 256 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 257 $ hg merge 5 # test adding
258 258 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
259 259 (branch merge, don't forget to commit)
260 260 $ hg debugsub
261 261 path s
262 262 source s
263 263 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
264 264 path t
265 265 source t
266 266 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
267 267 $ hg ci -m9
268 268 created new head
269 269 $ hg merge 6 --debug # test change
270 270 searching for copies back to rev 2
271 271 resolving manifests
272 272 branchmerge: True, force: False, partial: False
273 273 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
274 274 starting 4 threads for background file closing (?)
275 275 .hgsubstate: versions differ -> m (premerge)
276 276 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
277 277 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
278 278 getting subrepo t
279 279 resolving manifests
280 280 branchmerge: False, force: False, partial: False
281 281 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
282 282 t: remote is newer -> g
283 283 getting t
284 284 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
285 285 (branch merge, don't forget to commit)
286 286 $ hg debugsub
287 287 path s
288 288 source s
289 289 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
290 290 path t
291 291 source t
292 292 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
293 293 $ echo conflict > t/t
294 294 $ hg ci -m10
295 295 committing subrepository t
296 296 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
297 297 searching for copies back to rev 2
298 298 resolving manifests
299 299 branchmerge: True, force: False, partial: False
300 300 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
301 301 starting 4 threads for background file closing (?)
302 302 .hgsubstate: versions differ -> m (premerge)
303 303 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
304 304 subrepo t: both sides changed
305 305 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
306 306 starting 4 threads for background file closing (?)
307 307 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
308 308 merging subrepo t
309 309 searching for copies back to rev 2
310 310 resolving manifests
311 311 branchmerge: True, force: False, partial: False
312 312 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
313 313 preserving t for resolve of t
314 314 t: versions differ -> m (premerge)
315 315 picked tool ':merge' for t (binary False symlink False changedelete False)
316 316 merging t
317 317 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
318 318 t: versions differ -> m (merge)
319 319 picked tool ':merge' for t (binary False symlink False changedelete False)
320 320 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
321 321 warning: conflicts while merging t! (edit, then use 'hg resolve --mark')
322 322 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
323 323 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
324 324 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
325 325 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
326 326 (branch merge, don't forget to commit)
327 327
328 328 should conflict
329 329
330 330 $ cat t/t
331 331 <<<<<<< local: 20a0db6fbf6c - test: 10
332 332 conflict
333 333 =======
334 334 t3
335 335 >>>>>>> other: 7af322bc1198 - test: 7
336 336
337 337 11: remove subrepo t
338 338
339 339 $ hg co -C 5
340 340 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 341 $ hg revert -r 4 .hgsub # remove t
342 342 $ hg ci -m11
343 343 created new head
344 344 $ hg debugsub
345 345 path s
346 346 source s
347 347 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
348 348
349 349 local removed, remote changed, keep changed
350 350
351 351 $ hg merge 6
352 remote [merge rev] changed subrepository s which local [working copy] removed
352 remote [merge rev] changed subrepository t which local [working copy] removed
353 353 use (c)hanged version or (d)elete? c
354 354 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
355 355 (branch merge, don't forget to commit)
356 356 BROKEN: should include subrepo t
357 357 $ hg debugsub
358 358 path s
359 359 source s
360 360 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
361 361 $ cat .hgsubstate
362 362 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
363 363 6747d179aa9a688023c4b0cad32e4c92bb7f34ad t
364 364 $ hg ci -m 'local removed, remote changed, keep changed'
365 365 BROKEN: should include subrepo t
366 366 $ hg debugsub
367 367 path s
368 368 source s
369 369 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
370 370 BROKEN: should include subrepo t
371 371 $ cat .hgsubstate
372 372 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
373 373 $ cat t/t
374 374 t2
375 375
376 376 local removed, remote changed, keep removed
377 377
378 378 $ hg co -C 11
379 379 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
380 380 $ hg merge --config ui.interactive=true 6 <<EOF
381 381 > d
382 382 > EOF
383 remote [merge rev] changed subrepository s which local [working copy] removed
383 remote [merge rev] changed subrepository t which local [working copy] removed
384 384 use (c)hanged version or (d)elete? d
385 385 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
386 386 (branch merge, don't forget to commit)
387 387 $ hg debugsub
388 388 path s
389 389 source s
390 390 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
391 391 $ cat .hgsubstate
392 392 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
393 393 $ hg ci -m 'local removed, remote changed, keep removed'
394 394 created new head
395 395 $ hg debugsub
396 396 path s
397 397 source s
398 398 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
399 399 $ cat .hgsubstate
400 400 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
401 401
402 402 local changed, remote removed, keep changed
403 403
404 404 $ hg co -C 6
405 405 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
406 406 $ hg merge 11
407 local [working copy] changed subrepository s which remote [merge rev] removed
407 local [working copy] changed subrepository t which remote [merge rev] removed
408 408 use (c)hanged version or (d)elete? c
409 409 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
410 410 (branch merge, don't forget to commit)
411 411 BROKEN: should include subrepo t
412 412 $ hg debugsub
413 413 path s
414 414 source s
415 415 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
416 416 BROKEN: should include subrepo t
417 417 $ cat .hgsubstate
418 418 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
419 419 $ hg ci -m 'local changed, remote removed, keep changed'
420 420 created new head
421 421 BROKEN: should include subrepo t
422 422 $ hg debugsub
423 423 path s
424 424 source s
425 425 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
426 426 BROKEN: should include subrepo t
427 427 $ cat .hgsubstate
428 428 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
429 429 $ cat t/t
430 430 t2
431 431
432 432 local changed, remote removed, keep removed
433 433
434 434 $ hg co -C 6
435 435 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
436 436 $ hg merge --config ui.interactive=true 11 <<EOF
437 437 > d
438 438 > EOF
439 local [working copy] changed subrepository s which remote [merge rev] removed
439 local [working copy] changed subrepository t which remote [merge rev] removed
440 440 use (c)hanged version or (d)elete? d
441 441 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
442 442 (branch merge, don't forget to commit)
443 443 $ hg debugsub
444 444 path s
445 445 source s
446 446 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
447 447 $ cat .hgsubstate
448 448 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
449 449 $ hg ci -m 'local changed, remote removed, keep removed'
450 450 created new head
451 451 $ hg debugsub
452 452 path s
453 453 source s
454 454 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
455 455 $ cat .hgsubstate
456 456 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
457 457
458 458 clean up to avoid having to fix up the tests below
459 459
460 460 $ hg co -C 10
461 461 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
462 462 $ cat >> $HGRCPATH <<EOF
463 463 > [extensions]
464 464 > strip=
465 465 > EOF
466 466 $ hg strip -r 11:15
467 467 saved backup bundle to $TESTTMP/t/.hg/strip-backup/*-backup.hg (glob)
468 468
469 469 clone
470 470
471 471 $ cd ..
472 472 $ hg clone t tc
473 473 updating to branch default
474 474 cloning subrepo s from $TESTTMP/t/s
475 475 cloning subrepo s/ss from $TESTTMP/t/s/ss (glob)
476 476 cloning subrepo t from $TESTTMP/t/t
477 477 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
478 478 $ cd tc
479 479 $ hg debugsub
480 480 path s
481 481 source s
482 482 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
483 483 path t
484 484 source t
485 485 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
486 486
487 487 push
488 488
489 489 $ echo bah > t/t
490 490 $ hg ci -m11
491 491 committing subrepository t
492 492 $ hg push
493 493 pushing to $TESTTMP/t (glob)
494 494 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
495 495 no changes made to subrepo s since last push to $TESTTMP/t/s
496 496 pushing subrepo t to $TESTTMP/t/t
497 497 searching for changes
498 498 adding changesets
499 499 adding manifests
500 500 adding file changes
501 501 added 1 changesets with 1 changes to 1 files
502 502 searching for changes
503 503 adding changesets
504 504 adding manifests
505 505 adding file changes
506 506 added 1 changesets with 1 changes to 1 files
507 507
508 508 push -f
509 509
510 510 $ echo bah > s/a
511 511 $ hg ci -m12
512 512 committing subrepository s
513 513 $ hg push
514 514 pushing to $TESTTMP/t (glob)
515 515 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
516 516 pushing subrepo s to $TESTTMP/t/s
517 517 searching for changes
518 518 abort: push creates new remote head 12a213df6fa9! (in subrepo s)
519 519 (merge or see 'hg help push' for details about pushing new heads)
520 520 [255]
521 521 $ hg push -f
522 522 pushing to $TESTTMP/t (glob)
523 523 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
524 524 searching for changes
525 525 no changes found
526 526 pushing subrepo s to $TESTTMP/t/s
527 527 searching for changes
528 528 adding changesets
529 529 adding manifests
530 530 adding file changes
531 531 added 1 changesets with 1 changes to 1 files (+1 heads)
532 532 pushing subrepo t to $TESTTMP/t/t
533 533 searching for changes
534 534 no changes found
535 535 searching for changes
536 536 adding changesets
537 537 adding manifests
538 538 adding file changes
539 539 added 1 changesets with 1 changes to 1 files
540 540
541 541 check that unmodified subrepos are not pushed
542 542
543 543 $ hg clone . ../tcc
544 544 updating to branch default
545 545 cloning subrepo s from $TESTTMP/tc/s
546 546 cloning subrepo s/ss from $TESTTMP/tc/s/ss (glob)
547 547 cloning subrepo t from $TESTTMP/tc/t
548 548 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
549 549
550 550 the subrepos on the new clone have nothing to push to its source
551 551
552 552 $ hg push -R ../tcc .
553 553 pushing to .
554 554 no changes made to subrepo s/ss since last push to s/ss (glob)
555 555 no changes made to subrepo s since last push to s
556 556 no changes made to subrepo t since last push to t
557 557 searching for changes
558 558 no changes found
559 559 [1]
560 560
561 561 the subrepos on the source do not have a clean store versus the clone target
562 562 because they were never explicitly pushed to the source
563 563
564 564 $ hg push ../tcc
565 565 pushing to ../tcc
566 566 pushing subrepo s/ss to ../tcc/s/ss (glob)
567 567 searching for changes
568 568 no changes found
569 569 pushing subrepo s to ../tcc/s
570 570 searching for changes
571 571 no changes found
572 572 pushing subrepo t to ../tcc/t
573 573 searching for changes
574 574 no changes found
575 575 searching for changes
576 576 no changes found
577 577 [1]
578 578
579 579 after push their stores become clean
580 580
581 581 $ hg push ../tcc
582 582 pushing to ../tcc
583 583 no changes made to subrepo s/ss since last push to ../tcc/s/ss (glob)
584 584 no changes made to subrepo s since last push to ../tcc/s
585 585 no changes made to subrepo t since last push to ../tcc/t
586 586 searching for changes
587 587 no changes found
588 588 [1]
589 589
590 590 updating a subrepo to a different revision or changing
591 591 its working directory does not make its store dirty
592 592
593 593 $ hg -R s update '.^'
594 594 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
595 595 $ hg push
596 596 pushing to $TESTTMP/t (glob)
597 597 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
598 598 no changes made to subrepo s since last push to $TESTTMP/t/s
599 599 no changes made to subrepo t since last push to $TESTTMP/t/t
600 600 searching for changes
601 601 no changes found
602 602 [1]
603 603 $ echo foo >> s/a
604 604 $ hg push
605 605 pushing to $TESTTMP/t (glob)
606 606 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
607 607 no changes made to subrepo s since last push to $TESTTMP/t/s
608 608 no changes made to subrepo t since last push to $TESTTMP/t/t
609 609 searching for changes
610 610 no changes found
611 611 [1]
612 612 $ hg -R s update -C tip
613 613 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
614 614
615 615 committing into a subrepo makes its store (but not its parent's store) dirty
616 616
617 617 $ echo foo >> s/ss/a
618 618 $ hg -R s/ss commit -m 'test dirty store detection'
619 619
620 620 $ hg out -S -r `hg log -r tip -T "{node|short}"`
621 621 comparing with $TESTTMP/t (glob)
622 622 searching for changes
623 623 no changes found
624 624 comparing with $TESTTMP/t/s
625 625 searching for changes
626 626 no changes found
627 627 comparing with $TESTTMP/t/s/ss
628 628 searching for changes
629 629 changeset: 1:79ea5566a333
630 630 tag: tip
631 631 user: test
632 632 date: Thu Jan 01 00:00:00 1970 +0000
633 633 summary: test dirty store detection
634 634
635 635 comparing with $TESTTMP/t/t
636 636 searching for changes
637 637 no changes found
638 638
639 639 $ hg push
640 640 pushing to $TESTTMP/t (glob)
641 641 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
642 642 searching for changes
643 643 adding changesets
644 644 adding manifests
645 645 adding file changes
646 646 added 1 changesets with 1 changes to 1 files
647 647 no changes made to subrepo s since last push to $TESTTMP/t/s
648 648 no changes made to subrepo t since last push to $TESTTMP/t/t
649 649 searching for changes
650 650 no changes found
651 651 [1]
652 652
653 653 a subrepo store may be clean versus one repo but not versus another
654 654
655 655 $ hg push
656 656 pushing to $TESTTMP/t (glob)
657 657 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
658 658 no changes made to subrepo s since last push to $TESTTMP/t/s
659 659 no changes made to subrepo t since last push to $TESTTMP/t/t
660 660 searching for changes
661 661 no changes found
662 662 [1]
663 663 $ hg push ../tcc
664 664 pushing to ../tcc
665 665 pushing subrepo s/ss to ../tcc/s/ss (glob)
666 666 searching for changes
667 667 adding changesets
668 668 adding manifests
669 669 adding file changes
670 670 added 1 changesets with 1 changes to 1 files
671 671 no changes made to subrepo s since last push to ../tcc/s
672 672 no changes made to subrepo t since last push to ../tcc/t
673 673 searching for changes
674 674 no changes found
675 675 [1]
676 676
677 677 update
678 678
679 679 $ cd ../t
680 680 $ hg up -C # discard our earlier merge
681 681 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
682 682 2 other heads for branch "default"
683 683 $ echo blah > t/t
684 684 $ hg ci -m13
685 685 committing subrepository t
686 686
687 687 backout calls revert internally with minimal opts, which should not raise
688 688 KeyError
689 689
690 690 $ hg backout ".^" --no-commit
691 691 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
692 692 changeset c373c8102e68 backed out, don't forget to commit.
693 693
694 694 $ hg up -C # discard changes
695 695 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
696 696 2 other heads for branch "default"
697 697
698 698 pull
699 699
700 700 $ cd ../tc
701 701 $ hg pull
702 702 pulling from $TESTTMP/t (glob)
703 703 searching for changes
704 704 adding changesets
705 705 adding manifests
706 706 adding file changes
707 707 added 1 changesets with 1 changes to 1 files
708 708 (run 'hg update' to get a working copy)
709 709
710 710 should pull t
711 711
712 712 $ hg incoming -S -r `hg log -r tip -T "{node|short}"`
713 713 comparing with $TESTTMP/t (glob)
714 714 no changes found
715 715 comparing with $TESTTMP/t/s
716 716 searching for changes
717 717 no changes found
718 718 comparing with $TESTTMP/t/s/ss
719 719 searching for changes
720 720 no changes found
721 721 comparing with $TESTTMP/t/t
722 722 searching for changes
723 723 changeset: 5:52c0adc0515a
724 724 tag: tip
725 725 user: test
726 726 date: Thu Jan 01 00:00:00 1970 +0000
727 727 summary: 13
728 728
729 729
730 730 $ hg up
731 731 pulling subrepo t from $TESTTMP/t/t
732 732 searching for changes
733 733 adding changesets
734 734 adding manifests
735 735 adding file changes
736 736 added 1 changesets with 1 changes to 1 files
737 737 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
738 738 2 other heads for branch "default"
739 739 $ cat t/t
740 740 blah
741 741
742 742 bogus subrepo path aborts
743 743
744 744 $ echo 'bogus=[boguspath' >> .hgsub
745 745 $ hg ci -m 'bogus subrepo path'
746 746 abort: missing ] in subrepo source
747 747 [255]
748 748
749 749 Issue1986: merge aborts when trying to merge a subrepo that
750 750 shouldn't need merging
751 751
752 752 # subrepo layout
753 753 #
754 754 # o 5 br
755 755 # /|
756 756 # o | 4 default
757 757 # | |
758 758 # | o 3 br
759 759 # |/|
760 760 # o | 2 default
761 761 # | |
762 762 # | o 1 br
763 763 # |/
764 764 # o 0 default
765 765
766 766 $ cd ..
767 767 $ rm -rf sub
768 768 $ hg init main
769 769 $ cd main
770 770 $ hg init s
771 771 $ cd s
772 772 $ echo a > a
773 773 $ hg ci -Am1
774 774 adding a
775 775 $ hg branch br
776 776 marked working directory as branch br
777 777 (branches are permanent and global, did you want a bookmark?)
778 778 $ echo a >> a
779 779 $ hg ci -m1
780 780 $ hg up default
781 781 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
782 782 $ echo b > b
783 783 $ hg ci -Am1
784 784 adding b
785 785 $ hg up br
786 786 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
787 787 $ hg merge tip
788 788 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
789 789 (branch merge, don't forget to commit)
790 790 $ hg ci -m1
791 791 $ hg up 2
792 792 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
793 793 $ echo c > c
794 794 $ hg ci -Am1
795 795 adding c
796 796 $ hg up 3
797 797 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
798 798 $ hg merge 4
799 799 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
800 800 (branch merge, don't forget to commit)
801 801 $ hg ci -m1
802 802
803 803 # main repo layout:
804 804 #
805 805 # * <-- try to merge default into br again
806 806 # .`|
807 807 # . o 5 br --> substate = 5
808 808 # . |
809 809 # o | 4 default --> substate = 4
810 810 # | |
811 811 # | o 3 br --> substate = 2
812 812 # |/|
813 813 # o | 2 default --> substate = 2
814 814 # | |
815 815 # | o 1 br --> substate = 3
816 816 # |/
817 817 # o 0 default --> substate = 2
818 818
819 819 $ cd ..
820 820 $ echo 's = s' > .hgsub
821 821 $ hg -R s up 2
822 822 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
823 823 $ hg ci -Am1
824 824 adding .hgsub
825 825 $ hg branch br
826 826 marked working directory as branch br
827 827 (branches are permanent and global, did you want a bookmark?)
828 828 $ echo b > b
829 829 $ hg -R s up 3
830 830 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
831 831 $ hg ci -Am1
832 832 adding b
833 833 $ hg up default
834 834 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
835 835 $ echo c > c
836 836 $ hg ci -Am1
837 837 adding c
838 838 $ hg up 1
839 839 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
840 840 $ hg merge 2
841 841 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
842 842 (branch merge, don't forget to commit)
843 843 $ hg ci -m1
844 844 $ hg up 2
845 845 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
846 846 $ hg -R s up 4
847 847 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
848 848 $ echo d > d
849 849 $ hg ci -Am1
850 850 adding d
851 851 $ hg up 3
852 852 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
853 853 $ hg -R s up 5
854 854 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
855 855 $ echo e > e
856 856 $ hg ci -Am1
857 857 adding e
858 858
859 859 $ hg up 5
860 860 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
861 861 $ hg merge 4 # try to merge default into br again
862 862 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
863 863 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
864 864 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
865 865 (branch merge, don't forget to commit)
866 866 $ cd ..
867 867
868 868 test subrepo delete from .hgsubstate
869 869
870 870 $ hg init testdelete
871 871 $ mkdir testdelete/nested testdelete/nested2
872 872 $ hg init testdelete/nested
873 873 $ hg init testdelete/nested2
874 874 $ echo test > testdelete/nested/foo
875 875 $ echo test > testdelete/nested2/foo
876 876 $ hg -R testdelete/nested add
877 877 adding testdelete/nested/foo (glob)
878 878 $ hg -R testdelete/nested2 add
879 879 adding testdelete/nested2/foo (glob)
880 880 $ hg -R testdelete/nested ci -m test
881 881 $ hg -R testdelete/nested2 ci -m test
882 882 $ echo nested = nested > testdelete/.hgsub
883 883 $ echo nested2 = nested2 >> testdelete/.hgsub
884 884 $ hg -R testdelete add
885 885 adding testdelete/.hgsub (glob)
886 886 $ hg -R testdelete ci -m "nested 1 & 2 added"
887 887 $ echo nested = nested > testdelete/.hgsub
888 888 $ hg -R testdelete ci -m "nested 2 deleted"
889 889 $ cat testdelete/.hgsubstate
890 890 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
891 891 $ hg -R testdelete remove testdelete/.hgsub
892 892 $ hg -R testdelete ci -m ".hgsub deleted"
893 893 $ cat testdelete/.hgsubstate
894 894 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
895 895
896 896 test repository cloning
897 897
898 898 $ mkdir mercurial mercurial2
899 899 $ hg init nested_absolute
900 900 $ echo test > nested_absolute/foo
901 901 $ hg -R nested_absolute add
902 902 adding nested_absolute/foo (glob)
903 903 $ hg -R nested_absolute ci -mtest
904 904 $ cd mercurial
905 905 $ hg init nested_relative
906 906 $ echo test2 > nested_relative/foo2
907 907 $ hg -R nested_relative add
908 908 adding nested_relative/foo2 (glob)
909 909 $ hg -R nested_relative ci -mtest2
910 910 $ hg init main
911 911 $ echo "nested_relative = ../nested_relative" > main/.hgsub
912 912 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
913 913 $ hg -R main add
914 914 adding main/.hgsub (glob)
915 915 $ hg -R main ci -m "add subrepos"
916 916 $ cd ..
917 917 $ hg clone mercurial/main mercurial2/main
918 918 updating to branch default
919 919 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
920 920 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
921 921 > mercurial2/main/nested_relative/.hg/hgrc
922 922 [paths]
923 923 default = $TESTTMP/mercurial/nested_absolute
924 924 [paths]
925 925 default = $TESTTMP/mercurial/nested_relative
926 926 $ rm -rf mercurial mercurial2
927 927
928 928 Issue1977: multirepo push should fail if subrepo push fails
929 929
930 930 $ hg init repo
931 931 $ hg init repo/s
932 932 $ echo a > repo/s/a
933 933 $ hg -R repo/s ci -Am0
934 934 adding a
935 935 $ echo s = s > repo/.hgsub
936 936 $ hg -R repo ci -Am1
937 937 adding .hgsub
938 938 $ hg clone repo repo2
939 939 updating to branch default
940 940 cloning subrepo s from $TESTTMP/repo/s
941 941 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
942 942 $ hg -q -R repo2 pull -u
943 943 $ echo 1 > repo2/s/a
944 944 $ hg -R repo2/s ci -m2
945 945 $ hg -q -R repo2/s push
946 946 $ hg -R repo2/s up -C 0
947 947 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
948 948 $ echo 2 > repo2/s/b
949 949 $ hg -R repo2/s ci -m3 -A
950 950 adding b
951 951 created new head
952 952 $ hg -R repo2 ci -m3
953 953 $ hg -q -R repo2 push
954 954 abort: push creates new remote head cc505f09a8b2! (in subrepo s)
955 955 (merge or see 'hg help push' for details about pushing new heads)
956 956 [255]
957 957 $ hg -R repo update
958 958 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
959 959
960 960 test if untracked file is not overwritten
961 961
962 962 (this also tests that updated .hgsubstate is treated as "modified",
963 963 when 'merge.update()' is aborted before 'merge.recordupdates()', even
964 964 if none of mode, size and timestamp of it isn't changed on the
965 965 filesystem (see also issue4583))
966 966
967 967 $ echo issue3276_ok > repo/s/b
968 968 $ hg -R repo2 push -f -q
969 969 $ touch -t 200001010000 repo/.hgsubstate
970 970
971 971 $ cat >> repo/.hg/hgrc <<EOF
972 972 > [fakedirstatewritetime]
973 973 > # emulate invoking dirstate.write() via repo.status()
974 974 > # at 2000-01-01 00:00
975 975 > fakenow = 200001010000
976 976 >
977 977 > [extensions]
978 978 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
979 979 > EOF
980 980 $ hg -R repo update
981 981 b: untracked file differs
982 982 abort: untracked files in working directory differ from files in requested revision (in subrepo s)
983 983 [255]
984 984 $ cat >> repo/.hg/hgrc <<EOF
985 985 > [extensions]
986 986 > fakedirstatewritetime = !
987 987 > EOF
988 988
989 989 $ cat repo/s/b
990 990 issue3276_ok
991 991 $ rm repo/s/b
992 992 $ touch -t 200001010000 repo/.hgsubstate
993 993 $ hg -R repo revert --all
994 994 reverting repo/.hgsubstate (glob)
995 995 reverting subrepo s
996 996 $ hg -R repo update
997 997 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
998 998 $ cat repo/s/b
999 999 2
1000 1000 $ rm -rf repo2 repo
1001 1001
1002 1002
1003 1003 Issue1852 subrepos with relative paths always push/pull relative to default
1004 1004
1005 1005 Prepare a repo with subrepo
1006 1006
1007 1007 $ hg init issue1852a
1008 1008 $ cd issue1852a
1009 1009 $ hg init sub/repo
1010 1010 $ echo test > sub/repo/foo
1011 1011 $ hg -R sub/repo add sub/repo/foo
1012 1012 $ echo sub/repo = sub/repo > .hgsub
1013 1013 $ hg add .hgsub
1014 1014 $ hg ci -mtest
1015 1015 committing subrepository sub/repo (glob)
1016 1016 $ echo test >> sub/repo/foo
1017 1017 $ hg ci -mtest
1018 1018 committing subrepository sub/repo (glob)
1019 1019 $ hg cat sub/repo/foo
1020 1020 test
1021 1021 test
1022 1022 $ mkdir -p tmp/sub/repo
1023 1023 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
1024 1024 $ cat tmp/sub/repo/foo_p
1025 1025 test
1026 1026 $ mv sub/repo sub_
1027 1027 $ hg cat sub/repo/baz
1028 1028 skipping missing subrepository: sub/repo
1029 1029 [1]
1030 1030 $ rm -rf sub/repo
1031 1031 $ mv sub_ sub/repo
1032 1032 $ cd ..
1033 1033
1034 1034 Create repo without default path, pull top repo, and see what happens on update
1035 1035
1036 1036 $ hg init issue1852b
1037 1037 $ hg -R issue1852b pull issue1852a
1038 1038 pulling from issue1852a
1039 1039 requesting all changes
1040 1040 adding changesets
1041 1041 adding manifests
1042 1042 adding file changes
1043 1043 added 2 changesets with 3 changes to 2 files
1044 1044 (run 'hg update' to get a working copy)
1045 1045 $ hg -R issue1852b update
1046 1046 abort: default path for subrepository not found (in subrepo sub/repo) (glob)
1047 1047 [255]
1048 1048
1049 1049 Ensure a full traceback, not just the SubrepoAbort part
1050 1050
1051 1051 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise error\.Abort'
1052 1052 raise error.Abort(_("default path for subrepository not found"))
1053 1053
1054 1054 Pull -u now doesn't help
1055 1055
1056 1056 $ hg -R issue1852b pull -u issue1852a
1057 1057 pulling from issue1852a
1058 1058 searching for changes
1059 1059 no changes found
1060 1060
1061 1061 Try the same, but with pull -u
1062 1062
1063 1063 $ hg init issue1852c
1064 1064 $ hg -R issue1852c pull -r0 -u issue1852a
1065 1065 pulling from issue1852a
1066 1066 adding changesets
1067 1067 adding manifests
1068 1068 adding file changes
1069 1069 added 1 changesets with 2 changes to 2 files
1070 1070 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
1071 1071 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1072 1072
1073 1073 Try to push from the other side
1074 1074
1075 1075 $ hg -R issue1852a push `pwd`/issue1852c
1076 1076 pushing to $TESTTMP/issue1852c (glob)
1077 1077 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
1078 1078 searching for changes
1079 1079 no changes found
1080 1080 searching for changes
1081 1081 adding changesets
1082 1082 adding manifests
1083 1083 adding file changes
1084 1084 added 1 changesets with 1 changes to 1 files
1085 1085
1086 1086 Incoming and outgoing should not use the default path:
1087 1087
1088 1088 $ hg clone -q issue1852a issue1852d
1089 1089 $ hg -R issue1852d outgoing --subrepos issue1852c
1090 1090 comparing with issue1852c
1091 1091 searching for changes
1092 1092 no changes found
1093 1093 comparing with issue1852c/sub/repo
1094 1094 searching for changes
1095 1095 no changes found
1096 1096 [1]
1097 1097 $ hg -R issue1852d incoming --subrepos issue1852c
1098 1098 comparing with issue1852c
1099 1099 searching for changes
1100 1100 no changes found
1101 1101 comparing with issue1852c/sub/repo
1102 1102 searching for changes
1103 1103 no changes found
1104 1104 [1]
1105 1105
1106 1106 Check that merge of a new subrepo doesn't write the uncommitted state to
1107 1107 .hgsubstate (issue4622)
1108 1108
1109 1109 $ hg init issue1852a/addedsub
1110 1110 $ echo zzz > issue1852a/addedsub/zz.txt
1111 1111 $ hg -R issue1852a/addedsub ci -Aqm "initial ZZ"
1112 1112
1113 1113 $ hg clone issue1852a/addedsub issue1852d/addedsub
1114 1114 updating to branch default
1115 1115 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1116 1116
1117 1117 $ echo def > issue1852a/sub/repo/foo
1118 1118 $ hg -R issue1852a ci -SAm 'tweaked subrepo'
1119 1119 adding tmp/sub/repo/foo_p
1120 1120 committing subrepository sub/repo (glob)
1121 1121
1122 1122 $ echo 'addedsub = addedsub' >> issue1852d/.hgsub
1123 1123 $ echo xyz > issue1852d/sub/repo/foo
1124 1124 $ hg -R issue1852d pull -u
1125 1125 pulling from $TESTTMP/issue1852a (glob)
1126 1126 searching for changes
1127 1127 adding changesets
1128 1128 adding manifests
1129 1129 adding file changes
1130 1130 added 1 changesets with 2 changes to 2 files
1131 1131 subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
1132 1132 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1133 1133 pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo (glob)
1134 1134 searching for changes
1135 1135 adding changesets
1136 1136 adding manifests
1137 1137 adding file changes
1138 1138 added 1 changesets with 1 changes to 1 files
1139 1139 subrepository sources for sub/repo differ (glob)
1140 1140 use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
1141 1141 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1142 1142 $ cat issue1852d/.hgsubstate
1143 1143 f42d5c7504a811dda50f5cf3e5e16c3330b87172 sub/repo
1144 1144
1145 1145 Check status of files when none of them belong to the first
1146 1146 subrepository:
1147 1147
1148 1148 $ hg init subrepo-status
1149 1149 $ cd subrepo-status
1150 1150 $ hg init subrepo-1
1151 1151 $ hg init subrepo-2
1152 1152 $ cd subrepo-2
1153 1153 $ touch file
1154 1154 $ hg add file
1155 1155 $ cd ..
1156 1156 $ echo subrepo-1 = subrepo-1 > .hgsub
1157 1157 $ echo subrepo-2 = subrepo-2 >> .hgsub
1158 1158 $ hg add .hgsub
1159 1159 $ hg ci -m 'Added subrepos'
1160 1160 committing subrepository subrepo-2
1161 1161 $ hg st subrepo-2/file
1162 1162
1163 1163 Check that share works with subrepo
1164 1164 $ hg --config extensions.share= share . ../shared
1165 1165 updating working directory
1166 1166 cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
1167 1167 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1168 1168 $ test -f ../shared/subrepo-1/.hg/sharedpath
1169 1169 [1]
1170 1170 $ hg -R ../shared in
1171 1171 abort: repository default not found!
1172 1172 [255]
1173 1173 $ hg -R ../shared/subrepo-2 showconfig paths
1174 1174 paths.default=$TESTTMP/subrepo-status/subrepo-2
1175 1175 $ hg -R ../shared/subrepo-1 sum --remote
1176 1176 parent: -1:000000000000 tip (empty repository)
1177 1177 branch: default
1178 1178 commit: (clean)
1179 1179 update: (current)
1180 1180 remote: (synced)
1181 1181
1182 1182 Check hg update --clean
1183 1183 $ cd $TESTTMP/t
1184 1184 $ rm -r t/t.orig
1185 1185 $ hg status -S --all
1186 1186 C .hgsub
1187 1187 C .hgsubstate
1188 1188 C a
1189 1189 C s/.hgsub
1190 1190 C s/.hgsubstate
1191 1191 C s/a
1192 1192 C s/ss/a
1193 1193 C t/t
1194 1194 $ echo c1 > s/a
1195 1195 $ cd s
1196 1196 $ echo c1 > b
1197 1197 $ echo c1 > c
1198 1198 $ hg add b
1199 1199 $ cd ..
1200 1200 $ hg status -S
1201 1201 M s/a
1202 1202 A s/b
1203 1203 ? s/c
1204 1204 $ hg update -C
1205 1205 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1206 1206 2 other heads for branch "default"
1207 1207 $ hg status -S
1208 1208 ? s/b
1209 1209 ? s/c
1210 1210
1211 1211 Sticky subrepositories, no changes
1212 1212 $ cd $TESTTMP/t
1213 1213 $ hg id
1214 1214 925c17564ef8 tip
1215 1215 $ hg -R s id
1216 1216 12a213df6fa9 tip
1217 1217 $ hg -R t id
1218 1218 52c0adc0515a tip
1219 1219 $ hg update 11
1220 1220 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1221 1221 $ hg id
1222 1222 365661e5936a
1223 1223 $ hg -R s id
1224 1224 fc627a69481f
1225 1225 $ hg -R t id
1226 1226 e95bcfa18a35
1227 1227
1228 1228 Sticky subrepositories, file changes
1229 1229 $ touch s/f1
1230 1230 $ touch t/f1
1231 1231 $ hg add -S s/f1
1232 1232 $ hg add -S t/f1
1233 1233 $ hg id
1234 1234 365661e5936a+
1235 1235 $ hg -R s id
1236 1236 fc627a69481f+
1237 1237 $ hg -R t id
1238 1238 e95bcfa18a35+
1239 1239 $ hg update tip
1240 1240 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
1241 1241 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1242 1242 subrepository sources for s differ
1243 1243 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
1244 1244 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
1245 1245 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1246 1246 subrepository sources for t differ
1247 1247 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
1248 1248 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1249 1249 $ hg id
1250 1250 925c17564ef8+ tip
1251 1251 $ hg -R s id
1252 1252 fc627a69481f+
1253 1253 $ hg -R t id
1254 1254 e95bcfa18a35+
1255 1255 $ hg update --clean tip
1256 1256 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1257 1257
1258 1258 Sticky subrepository, revision updates
1259 1259 $ hg id
1260 1260 925c17564ef8 tip
1261 1261 $ hg -R s id
1262 1262 12a213df6fa9 tip
1263 1263 $ hg -R t id
1264 1264 52c0adc0515a tip
1265 1265 $ cd s
1266 1266 $ hg update -r -2
1267 1267 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1268 1268 $ cd ../t
1269 1269 $ hg update -r 2
1270 1270 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1271 1271 $ cd ..
1272 1272 $ hg update 10
1273 1273 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1274 1274 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1275 1275 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1276 1276 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1277 1277 subrepository sources for t differ (in checked out version)
1278 1278 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1279 1279 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1280 1280 $ hg id
1281 1281 e45c8b14af55+
1282 1282 $ hg -R s id
1283 1283 02dcf1d70411
1284 1284 $ hg -R t id
1285 1285 7af322bc1198
1286 1286
1287 1287 Sticky subrepository, file changes and revision updates
1288 1288 $ touch s/f1
1289 1289 $ touch t/f1
1290 1290 $ hg add -S s/f1
1291 1291 $ hg add -S t/f1
1292 1292 $ hg id
1293 1293 e45c8b14af55+
1294 1294 $ hg -R s id
1295 1295 02dcf1d70411+
1296 1296 $ hg -R t id
1297 1297 7af322bc1198+
1298 1298 $ hg update tip
1299 1299 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1300 1300 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1301 1301 subrepository sources for s differ
1302 1302 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1303 1303 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1304 1304 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1305 1305 subrepository sources for t differ
1306 1306 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1307 1307 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1308 1308 $ hg id
1309 1309 925c17564ef8+ tip
1310 1310 $ hg -R s id
1311 1311 02dcf1d70411+
1312 1312 $ hg -R t id
1313 1313 7af322bc1198+
1314 1314
1315 1315 Sticky repository, update --clean
1316 1316 $ hg update --clean tip
1317 1317 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1318 1318 $ hg id
1319 1319 925c17564ef8 tip
1320 1320 $ hg -R s id
1321 1321 12a213df6fa9 tip
1322 1322 $ hg -R t id
1323 1323 52c0adc0515a tip
1324 1324
1325 1325 Test subrepo already at intended revision:
1326 1326 $ cd s
1327 1327 $ hg update fc627a69481f
1328 1328 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1329 1329 $ cd ..
1330 1330 $ hg update 11
1331 1331 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1332 1332 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1333 1333 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1334 1334 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1335 1335 $ hg id -n
1336 1336 11+
1337 1337 $ hg -R s id
1338 1338 fc627a69481f
1339 1339 $ hg -R t id
1340 1340 e95bcfa18a35
1341 1341
1342 1342 Test that removing .hgsubstate doesn't break anything:
1343 1343
1344 1344 $ hg rm -f .hgsubstate
1345 1345 $ hg ci -mrm
1346 1346 nothing changed
1347 1347 [1]
1348 1348 $ hg log -vr tip
1349 1349 changeset: 13:925c17564ef8
1350 1350 tag: tip
1351 1351 user: test
1352 1352 date: Thu Jan 01 00:00:00 1970 +0000
1353 1353 files: .hgsubstate
1354 1354 description:
1355 1355 13
1356 1356
1357 1357
1358 1358
1359 1359 Test that removing .hgsub removes .hgsubstate:
1360 1360
1361 1361 $ hg rm .hgsub
1362 1362 $ hg ci -mrm2
1363 1363 created new head
1364 1364 $ hg log -vr tip
1365 1365 changeset: 14:2400bccd50af
1366 1366 tag: tip
1367 1367 parent: 11:365661e5936a
1368 1368 user: test
1369 1369 date: Thu Jan 01 00:00:00 1970 +0000
1370 1370 files: .hgsub .hgsubstate
1371 1371 description:
1372 1372 rm2
1373 1373
1374 1374
1375 1375 Test issue3153: diff -S with deleted subrepos
1376 1376
1377 1377 $ hg diff --nodates -S -c .
1378 1378 diff -r 365661e5936a -r 2400bccd50af .hgsub
1379 1379 --- a/.hgsub
1380 1380 +++ /dev/null
1381 1381 @@ -1,2 +0,0 @@
1382 1382 -s = s
1383 1383 -t = t
1384 1384 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1385 1385 --- a/.hgsubstate
1386 1386 +++ /dev/null
1387 1387 @@ -1,2 +0,0 @@
1388 1388 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1389 1389 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1390 1390
1391 1391 Test behavior of add for explicit path in subrepo:
1392 1392 $ cd ..
1393 1393 $ hg init explicit
1394 1394 $ cd explicit
1395 1395 $ echo s = s > .hgsub
1396 1396 $ hg add .hgsub
1397 1397 $ hg init s
1398 1398 $ hg ci -m0
1399 1399 Adding with an explicit path in a subrepo adds the file
1400 1400 $ echo c1 > f1
1401 1401 $ echo c2 > s/f2
1402 1402 $ hg st -S
1403 1403 ? f1
1404 1404 ? s/f2
1405 1405 $ hg add s/f2
1406 1406 $ hg st -S
1407 1407 A s/f2
1408 1408 ? f1
1409 1409 $ hg ci -R s -m0
1410 1410 $ hg ci -Am1
1411 1411 adding f1
1412 1412 Adding with an explicit path in a subrepo with -S has the same behavior
1413 1413 $ echo c3 > f3
1414 1414 $ echo c4 > s/f4
1415 1415 $ hg st -S
1416 1416 ? f3
1417 1417 ? s/f4
1418 1418 $ hg add -S s/f4
1419 1419 $ hg st -S
1420 1420 A s/f4
1421 1421 ? f3
1422 1422 $ hg ci -R s -m1
1423 1423 $ hg ci -Ama2
1424 1424 adding f3
1425 1425 Adding without a path or pattern silently ignores subrepos
1426 1426 $ echo c5 > f5
1427 1427 $ echo c6 > s/f6
1428 1428 $ echo c7 > s/f7
1429 1429 $ hg st -S
1430 1430 ? f5
1431 1431 ? s/f6
1432 1432 ? s/f7
1433 1433 $ hg add
1434 1434 adding f5
1435 1435 $ hg st -S
1436 1436 A f5
1437 1437 ? s/f6
1438 1438 ? s/f7
1439 1439 $ hg ci -R s -Am2
1440 1440 adding f6
1441 1441 adding f7
1442 1442 $ hg ci -m3
1443 1443 Adding without a path or pattern with -S also adds files in subrepos
1444 1444 $ echo c8 > f8
1445 1445 $ echo c9 > s/f9
1446 1446 $ echo c10 > s/f10
1447 1447 $ hg st -S
1448 1448 ? f8
1449 1449 ? s/f10
1450 1450 ? s/f9
1451 1451 $ hg add -S
1452 1452 adding f8
1453 1453 adding s/f10 (glob)
1454 1454 adding s/f9 (glob)
1455 1455 $ hg st -S
1456 1456 A f8
1457 1457 A s/f10
1458 1458 A s/f9
1459 1459 $ hg ci -R s -m3
1460 1460 $ hg ci -m4
1461 1461 Adding with a pattern silently ignores subrepos
1462 1462 $ echo c11 > fm11
1463 1463 $ echo c12 > fn12
1464 1464 $ echo c13 > s/fm13
1465 1465 $ echo c14 > s/fn14
1466 1466 $ hg st -S
1467 1467 ? fm11
1468 1468 ? fn12
1469 1469 ? s/fm13
1470 1470 ? s/fn14
1471 1471 $ hg add 'glob:**fm*'
1472 1472 adding fm11
1473 1473 $ hg st -S
1474 1474 A fm11
1475 1475 ? fn12
1476 1476 ? s/fm13
1477 1477 ? s/fn14
1478 1478 $ hg ci -R s -Am4
1479 1479 adding fm13
1480 1480 adding fn14
1481 1481 $ hg ci -Am5
1482 1482 adding fn12
1483 1483 Adding with a pattern with -S also adds matches in subrepos
1484 1484 $ echo c15 > fm15
1485 1485 $ echo c16 > fn16
1486 1486 $ echo c17 > s/fm17
1487 1487 $ echo c18 > s/fn18
1488 1488 $ hg st -S
1489 1489 ? fm15
1490 1490 ? fn16
1491 1491 ? s/fm17
1492 1492 ? s/fn18
1493 1493 $ hg add -S 'glob:**fm*'
1494 1494 adding fm15
1495 1495 adding s/fm17 (glob)
1496 1496 $ hg st -S
1497 1497 A fm15
1498 1498 A s/fm17
1499 1499 ? fn16
1500 1500 ? s/fn18
1501 1501 $ hg ci -R s -Am5
1502 1502 adding fn18
1503 1503 $ hg ci -Am6
1504 1504 adding fn16
1505 1505
1506 1506 Test behavior of forget for explicit path in subrepo:
1507 1507 Forgetting an explicit path in a subrepo untracks the file
1508 1508 $ echo c19 > s/f19
1509 1509 $ hg add s/f19
1510 1510 $ hg st -S
1511 1511 A s/f19
1512 1512 $ hg forget s/f19
1513 1513 $ hg st -S
1514 1514 ? s/f19
1515 1515 $ rm s/f19
1516 1516 $ cd ..
1517 1517
1518 1518 Courtesy phases synchronisation to publishing server does not block the push
1519 1519 (issue3781)
1520 1520
1521 1521 $ cp -R main issue3781
1522 1522 $ cp -R main issue3781-dest
1523 1523 $ cd issue3781-dest/s
1524 1524 $ hg phase tip # show we have draft changeset
1525 1525 5: draft
1526 1526 $ chmod a-w .hg/store/phaseroots # prevent phase push
1527 1527 $ cd ../../issue3781
1528 1528 $ cat >> .hg/hgrc << EOF
1529 1529 > [paths]
1530 1530 > default=../issue3781-dest/
1531 1531 > EOF
1532 1532 $ hg push --config devel.legacy.exchange=bundle1
1533 1533 pushing to $TESTTMP/issue3781-dest (glob)
1534 1534 pushing subrepo s to $TESTTMP/issue3781-dest/s
1535 1535 searching for changes
1536 1536 no changes found
1537 1537 searching for changes
1538 1538 no changes found
1539 1539 [1]
1540 1540 # clean the push cache
1541 1541 $ rm s/.hg/cache/storehash/*
1542 1542 $ hg push # bundle2+
1543 1543 pushing to $TESTTMP/issue3781-dest (glob)
1544 1544 pushing subrepo s to $TESTTMP/issue3781-dest/s
1545 1545 searching for changes
1546 1546 no changes found
1547 1547 searching for changes
1548 1548 no changes found
1549 1549 [1]
1550 1550 $ cd ..
1551 1551
1552 1552 Test phase choice for newly created commit with "phases.subrepochecks"
1553 1553 configuration
1554 1554
1555 1555 $ cd t
1556 1556 $ hg update -q -r 12
1557 1557
1558 1558 $ cat >> s/ss/.hg/hgrc <<EOF
1559 1559 > [phases]
1560 1560 > new-commit = secret
1561 1561 > EOF
1562 1562 $ cat >> s/.hg/hgrc <<EOF
1563 1563 > [phases]
1564 1564 > new-commit = draft
1565 1565 > EOF
1566 1566 $ echo phasecheck1 >> s/ss/a
1567 1567 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1568 1568 committing subrepository ss
1569 1569 transaction abort!
1570 1570 rollback completed
1571 1571 abort: can't commit in draft phase conflicting secret from subrepository ss
1572 1572 [255]
1573 1573 $ echo phasecheck2 >> s/ss/a
1574 1574 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1575 1575 committing subrepository ss
1576 1576 $ hg -R s/ss phase tip
1577 1577 3: secret
1578 1578 $ hg -R s phase tip
1579 1579 6: draft
1580 1580 $ echo phasecheck3 >> s/ss/a
1581 1581 $ hg -R s commit -S -m phasecheck3
1582 1582 committing subrepository ss
1583 1583 warning: changes are committed in secret phase from subrepository ss
1584 1584 $ hg -R s/ss phase tip
1585 1585 4: secret
1586 1586 $ hg -R s phase tip
1587 1587 7: secret
1588 1588
1589 1589 $ cat >> t/.hg/hgrc <<EOF
1590 1590 > [phases]
1591 1591 > new-commit = draft
1592 1592 > EOF
1593 1593 $ cat >> .hg/hgrc <<EOF
1594 1594 > [phases]
1595 1595 > new-commit = public
1596 1596 > EOF
1597 1597 $ echo phasecheck4 >> s/ss/a
1598 1598 $ echo phasecheck4 >> t/t
1599 1599 $ hg commit -S -m phasecheck4
1600 1600 committing subrepository s
1601 1601 committing subrepository s/ss (glob)
1602 1602 warning: changes are committed in secret phase from subrepository ss
1603 1603 committing subrepository t
1604 1604 warning: changes are committed in secret phase from subrepository s
1605 1605 created new head
1606 1606 $ hg -R s/ss phase tip
1607 1607 5: secret
1608 1608 $ hg -R s phase tip
1609 1609 8: secret
1610 1610 $ hg -R t phase tip
1611 1611 6: draft
1612 1612 $ hg phase tip
1613 1613 15: secret
1614 1614
1615 1615 $ cd ..
1616 1616
1617 1617
1618 1618 Test that commit --secret works on both repo and subrepo (issue4182)
1619 1619
1620 1620 $ cd main
1621 1621 $ echo secret >> b
1622 1622 $ echo secret >> s/b
1623 1623 $ hg commit --secret --subrepo -m "secret"
1624 1624 committing subrepository s
1625 1625 $ hg phase -r .
1626 1626 6: secret
1627 1627 $ cd s
1628 1628 $ hg phase -r .
1629 1629 6: secret
1630 1630 $ cd ../../
1631 1631
1632 1632 Test "subrepos" template keyword
1633 1633
1634 1634 $ cd t
1635 1635 $ hg update -q 15
1636 1636 $ cat > .hgsub <<EOF
1637 1637 > s = s
1638 1638 > EOF
1639 1639 $ hg commit -m "16"
1640 1640 warning: changes are committed in secret phase from subrepository s
1641 1641
1642 1642 (addition of ".hgsub" itself)
1643 1643
1644 1644 $ hg diff --nodates -c 1 .hgsubstate
1645 1645 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1646 1646 --- /dev/null
1647 1647 +++ b/.hgsubstate
1648 1648 @@ -0,0 +1,1 @@
1649 1649 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1650 1650 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1651 1651 f7b1eb17ad24 000000000000
1652 1652 s
1653 1653
1654 1654 (modification of existing entry)
1655 1655
1656 1656 $ hg diff --nodates -c 2 .hgsubstate
1657 1657 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1658 1658 --- a/.hgsubstate
1659 1659 +++ b/.hgsubstate
1660 1660 @@ -1,1 +1,1 @@
1661 1661 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1662 1662 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1663 1663 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1664 1664 7cf8cfea66e4 000000000000
1665 1665 s
1666 1666
1667 1667 (addition of entry)
1668 1668
1669 1669 $ hg diff --nodates -c 5 .hgsubstate
1670 1670 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1671 1671 --- a/.hgsubstate
1672 1672 +++ b/.hgsubstate
1673 1673 @@ -1,1 +1,2 @@
1674 1674 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1675 1675 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1676 1676 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1677 1677 7cf8cfea66e4 000000000000
1678 1678 t
1679 1679
1680 1680 (removal of existing entry)
1681 1681
1682 1682 $ hg diff --nodates -c 16 .hgsubstate
1683 1683 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1684 1684 --- a/.hgsubstate
1685 1685 +++ b/.hgsubstate
1686 1686 @@ -1,2 +1,1 @@
1687 1687 0731af8ca9423976d3743119d0865097c07bdc1b s
1688 1688 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1689 1689 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1690 1690 8bec38d2bd0b 000000000000
1691 1691 t
1692 1692
1693 1693 (merging)
1694 1694
1695 1695 $ hg diff --nodates -c 9 .hgsubstate
1696 1696 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1697 1697 --- a/.hgsubstate
1698 1698 +++ b/.hgsubstate
1699 1699 @@ -1,1 +1,2 @@
1700 1700 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1701 1701 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1702 1702 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1703 1703 f6affe3fbfaa 1f14a2e2d3ec
1704 1704 t
1705 1705
1706 1706 (removal of ".hgsub" itself)
1707 1707
1708 1708 $ hg diff --nodates -c 8 .hgsubstate
1709 1709 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1710 1710 --- a/.hgsubstate
1711 1711 +++ /dev/null
1712 1712 @@ -1,2 +0,0 @@
1713 1713 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1714 1714 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1715 1715 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1716 1716 f94576341bcf 000000000000
1717 1717
1718 1718 Test that '[paths]' is configured correctly at subrepo creation
1719 1719
1720 1720 $ cd $TESTTMP/tc
1721 1721 $ cat > .hgsub <<EOF
1722 1722 > # to clear bogus subrepo path 'bogus=[boguspath'
1723 1723 > s = s
1724 1724 > t = t
1725 1725 > EOF
1726 1726 $ hg update -q --clean null
1727 1727 $ rm -rf s t
1728 1728 $ cat >> .hg/hgrc <<EOF
1729 1729 > [paths]
1730 1730 > default-push = /foo/bar
1731 1731 > EOF
1732 1732 $ hg update -q
1733 1733 $ cat s/.hg/hgrc
1734 1734 [paths]
1735 1735 default = $TESTTMP/t/s
1736 1736 default-push = /foo/bar/s
1737 1737 $ cat s/ss/.hg/hgrc
1738 1738 [paths]
1739 1739 default = $TESTTMP/t/s/ss
1740 1740 default-push = /foo/bar/s/ss
1741 1741 $ cat t/.hg/hgrc
1742 1742 [paths]
1743 1743 default = $TESTTMP/t/t
1744 1744 default-push = /foo/bar/t
1745 1745
1746 1746 $ cd $TESTTMP/t
1747 1747 $ hg up -qC 0
1748 1748 $ echo 'bar' > bar.txt
1749 1749 $ hg ci -Am 'branch before subrepo add'
1750 1750 adding bar.txt
1751 1751 created new head
1752 1752 $ hg merge -r "first(subrepo('s'))"
1753 1753 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1754 1754 (branch merge, don't forget to commit)
1755 1755 $ hg status -S -X '.hgsub*'
1756 1756 A s/a
1757 1757 ? s/b
1758 1758 ? s/c
1759 1759 ? s/f1
1760 1760 $ hg status -S --rev 'p2()'
1761 1761 A bar.txt
1762 1762 ? s/b
1763 1763 ? s/c
1764 1764 ? s/f1
1765 1765 $ hg diff -S -X '.hgsub*' --nodates
1766 1766 diff -r 000000000000 s/a
1767 1767 --- /dev/null
1768 1768 +++ b/s/a
1769 1769 @@ -0,0 +1,1 @@
1770 1770 +a
1771 1771 $ hg diff -S --rev 'p2()' --nodates
1772 1772 diff -r 7cf8cfea66e4 bar.txt
1773 1773 --- /dev/null
1774 1774 +++ b/bar.txt
1775 1775 @@ -0,0 +1,1 @@
1776 1776 +bar
1777 1777
1778 1778 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now