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