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