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