##// END OF EJS Templates
subrepo: don't pass the outer repo's --rev or --branch to subrepo outgoing()...
Matt Harbison -
r24875:5135c2be stable
parent child Browse files
Show More
@@ -1,1827 +1,1831 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 if 'rev' in opts or 'branch' in opts:
877 opts = copy.copy(opts)
878 opts.pop('rev', None)
879 opts.pop('branch', None)
876 880 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
877 881
878 882 @annotatesubrepoerror
879 883 def incoming(self, ui, source, opts):
880 884 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
881 885
882 886 @annotatesubrepoerror
883 887 def files(self):
884 888 rev = self._state[1]
885 889 ctx = self._repo[rev]
886 890 return ctx.manifest().keys()
887 891
888 892 def filedata(self, name):
889 893 rev = self._state[1]
890 894 return self._repo[rev][name].data()
891 895
892 896 def fileflags(self, name):
893 897 rev = self._state[1]
894 898 ctx = self._repo[rev]
895 899 return ctx.flags(name)
896 900
897 901 @annotatesubrepoerror
898 902 def printfiles(self, ui, m, fm, fmt):
899 903 # If the parent context is a workingctx, use the workingctx here for
900 904 # consistency.
901 905 if self._ctx.rev() is None:
902 906 ctx = self._repo[None]
903 907 else:
904 908 rev = self._state[1]
905 909 ctx = self._repo[rev]
906 910 return cmdutil.files(ui, ctx, m, fm, fmt, True)
907 911
908 912 def walk(self, match):
909 913 ctx = self._repo[None]
910 914 return ctx.walk(match)
911 915
912 916 @annotatesubrepoerror
913 917 def forget(self, match, prefix):
914 918 return cmdutil.forget(self.ui, self._repo, match,
915 919 self.wvfs.reljoin(prefix, self._path), True)
916 920
917 921 @annotatesubrepoerror
918 922 def removefiles(self, matcher, prefix, after, force, subrepos):
919 923 return cmdutil.remove(self.ui, self._repo, matcher,
920 924 self.wvfs.reljoin(prefix, self._path),
921 925 after, force, subrepos)
922 926
923 927 @annotatesubrepoerror
924 928 def revert(self, substate, *pats, **opts):
925 929 # reverting a subrepo is a 2 step process:
926 930 # 1. if the no_backup is not set, revert all modified
927 931 # files inside the subrepo
928 932 # 2. update the subrepo to the revision specified in
929 933 # the corresponding substate dictionary
930 934 self.ui.status(_('reverting subrepo %s\n') % substate[0])
931 935 if not opts.get('no_backup'):
932 936 # Revert all files on the subrepo, creating backups
933 937 # Note that this will not recursively revert subrepos
934 938 # We could do it if there was a set:subrepos() predicate
935 939 opts = opts.copy()
936 940 opts['date'] = None
937 941 opts['rev'] = substate[1]
938 942
939 943 self.filerevert(*pats, **opts)
940 944
941 945 # Update the repo to the revision specified in the given substate
942 946 if not opts.get('dry_run'):
943 947 self.get(substate, overwrite=True)
944 948
945 949 def filerevert(self, *pats, **opts):
946 950 ctx = self._repo[opts['rev']]
947 951 parents = self._repo.dirstate.parents()
948 952 if opts.get('all'):
949 953 pats = ['set:modified()']
950 954 else:
951 955 pats = []
952 956 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
953 957
954 958 def shortid(self, revid):
955 959 return revid[:12]
956 960
957 961 @propertycache
958 962 def wvfs(self):
959 963 """return own wvfs for efficiency and consitency
960 964 """
961 965 return self._repo.wvfs
962 966
963 967 @propertycache
964 968 def _relpath(self):
965 969 """return path to this subrepository as seen from outermost repository
966 970 """
967 971 # Keep consistent dir separators by avoiding vfs.join(self._path)
968 972 return reporelpath(self._repo)
969 973
970 974 class svnsubrepo(abstractsubrepo):
971 975 def __init__(self, ctx, path, state):
972 976 super(svnsubrepo, self).__init__(ctx, path)
973 977 self._state = state
974 978 self._exe = util.findexe('svn')
975 979 if not self._exe:
976 980 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
977 981 % self._path)
978 982
979 983 def _svncommand(self, commands, filename='', failok=False):
980 984 cmd = [self._exe]
981 985 extrakw = {}
982 986 if not self.ui.interactive():
983 987 # Making stdin be a pipe should prevent svn from behaving
984 988 # interactively even if we can't pass --non-interactive.
985 989 extrakw['stdin'] = subprocess.PIPE
986 990 # Starting in svn 1.5 --non-interactive is a global flag
987 991 # instead of being per-command, but we need to support 1.4 so
988 992 # we have to be intelligent about what commands take
989 993 # --non-interactive.
990 994 if commands[0] in ('update', 'checkout', 'commit'):
991 995 cmd.append('--non-interactive')
992 996 cmd.extend(commands)
993 997 if filename is not None:
994 998 path = self.wvfs.reljoin(self._ctx.repo().origroot,
995 999 self._path, filename)
996 1000 cmd.append(path)
997 1001 env = dict(os.environ)
998 1002 # Avoid localized output, preserve current locale for everything else.
999 1003 lc_all = env.get('LC_ALL')
1000 1004 if lc_all:
1001 1005 env['LANG'] = lc_all
1002 1006 del env['LC_ALL']
1003 1007 env['LC_MESSAGES'] = 'C'
1004 1008 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1005 1009 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1006 1010 universal_newlines=True, env=env, **extrakw)
1007 1011 stdout, stderr = p.communicate()
1008 1012 stderr = stderr.strip()
1009 1013 if not failok:
1010 1014 if p.returncode:
1011 1015 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
1012 1016 if stderr:
1013 1017 self.ui.warn(stderr + '\n')
1014 1018 return stdout, stderr
1015 1019
1016 1020 @propertycache
1017 1021 def _svnversion(self):
1018 1022 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1019 1023 m = re.search(r'^(\d+)\.(\d+)', output)
1020 1024 if not m:
1021 1025 raise util.Abort(_('cannot retrieve svn tool version'))
1022 1026 return (int(m.group(1)), int(m.group(2)))
1023 1027
1024 1028 def _wcrevs(self):
1025 1029 # Get the working directory revision as well as the last
1026 1030 # commit revision so we can compare the subrepo state with
1027 1031 # both. We used to store the working directory one.
1028 1032 output, err = self._svncommand(['info', '--xml'])
1029 1033 doc = xml.dom.minidom.parseString(output)
1030 1034 entries = doc.getElementsByTagName('entry')
1031 1035 lastrev, rev = '0', '0'
1032 1036 if entries:
1033 1037 rev = str(entries[0].getAttribute('revision')) or '0'
1034 1038 commits = entries[0].getElementsByTagName('commit')
1035 1039 if commits:
1036 1040 lastrev = str(commits[0].getAttribute('revision')) or '0'
1037 1041 return (lastrev, rev)
1038 1042
1039 1043 def _wcrev(self):
1040 1044 return self._wcrevs()[0]
1041 1045
1042 1046 def _wcchanged(self):
1043 1047 """Return (changes, extchanges, missing) where changes is True
1044 1048 if the working directory was changed, extchanges is
1045 1049 True if any of these changes concern an external entry and missing
1046 1050 is True if any change is a missing entry.
1047 1051 """
1048 1052 output, err = self._svncommand(['status', '--xml'])
1049 1053 externals, changes, missing = [], [], []
1050 1054 doc = xml.dom.minidom.parseString(output)
1051 1055 for e in doc.getElementsByTagName('entry'):
1052 1056 s = e.getElementsByTagName('wc-status')
1053 1057 if not s:
1054 1058 continue
1055 1059 item = s[0].getAttribute('item')
1056 1060 props = s[0].getAttribute('props')
1057 1061 path = e.getAttribute('path')
1058 1062 if item == 'external':
1059 1063 externals.append(path)
1060 1064 elif item == 'missing':
1061 1065 missing.append(path)
1062 1066 if (item not in ('', 'normal', 'unversioned', 'external')
1063 1067 or props not in ('', 'none', 'normal')):
1064 1068 changes.append(path)
1065 1069 for path in changes:
1066 1070 for ext in externals:
1067 1071 if path == ext or path.startswith(ext + os.sep):
1068 1072 return True, True, bool(missing)
1069 1073 return bool(changes), False, bool(missing)
1070 1074
1071 1075 def dirty(self, ignoreupdate=False):
1072 1076 if not self._wcchanged()[0]:
1073 1077 if self._state[1] in self._wcrevs() or ignoreupdate:
1074 1078 return False
1075 1079 return True
1076 1080
1077 1081 def basestate(self):
1078 1082 lastrev, rev = self._wcrevs()
1079 1083 if lastrev != rev:
1080 1084 # Last committed rev is not the same than rev. We would
1081 1085 # like to take lastrev but we do not know if the subrepo
1082 1086 # URL exists at lastrev. Test it and fallback to rev it
1083 1087 # is not there.
1084 1088 try:
1085 1089 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1086 1090 return lastrev
1087 1091 except error.Abort:
1088 1092 pass
1089 1093 return rev
1090 1094
1091 1095 @annotatesubrepoerror
1092 1096 def commit(self, text, user, date):
1093 1097 # user and date are out of our hands since svn is centralized
1094 1098 changed, extchanged, missing = self._wcchanged()
1095 1099 if not changed:
1096 1100 return self.basestate()
1097 1101 if extchanged:
1098 1102 # Do not try to commit externals
1099 1103 raise util.Abort(_('cannot commit svn externals'))
1100 1104 if missing:
1101 1105 # svn can commit with missing entries but aborting like hg
1102 1106 # seems a better approach.
1103 1107 raise util.Abort(_('cannot commit missing svn entries'))
1104 1108 commitinfo, err = self._svncommand(['commit', '-m', text])
1105 1109 self.ui.status(commitinfo)
1106 1110 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1107 1111 if not newrev:
1108 1112 if not commitinfo.strip():
1109 1113 # Sometimes, our definition of "changed" differs from
1110 1114 # svn one. For instance, svn ignores missing files
1111 1115 # when committing. If there are only missing files, no
1112 1116 # commit is made, no output and no error code.
1113 1117 raise util.Abort(_('failed to commit svn changes'))
1114 1118 raise util.Abort(commitinfo.splitlines()[-1])
1115 1119 newrev = newrev.groups()[0]
1116 1120 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1117 1121 return newrev
1118 1122
1119 1123 @annotatesubrepoerror
1120 1124 def remove(self):
1121 1125 if self.dirty():
1122 1126 self.ui.warn(_('not removing repo %s because '
1123 1127 'it has changes.\n') % self._path)
1124 1128 return
1125 1129 self.ui.note(_('removing subrepo %s\n') % self._path)
1126 1130
1127 1131 self.wvfs.rmtree(forcibly=True)
1128 1132 try:
1129 1133 self._ctx.repo().wvfs.removedirs(os.path.dirname(self._path))
1130 1134 except OSError:
1131 1135 pass
1132 1136
1133 1137 @annotatesubrepoerror
1134 1138 def get(self, state, overwrite=False):
1135 1139 if overwrite:
1136 1140 self._svncommand(['revert', '--recursive'])
1137 1141 args = ['checkout']
1138 1142 if self._svnversion >= (1, 5):
1139 1143 args.append('--force')
1140 1144 # The revision must be specified at the end of the URL to properly
1141 1145 # update to a directory which has since been deleted and recreated.
1142 1146 args.append('%s@%s' % (state[0], state[1]))
1143 1147 status, err = self._svncommand(args, failok=True)
1144 1148 _sanitize(self.ui, self.wvfs, '.svn')
1145 1149 if not re.search('Checked out revision [0-9]+.', status):
1146 1150 if ('is already a working copy for a different URL' in err
1147 1151 and (self._wcchanged()[:2] == (False, False))):
1148 1152 # obstructed but clean working copy, so just blow it away.
1149 1153 self.remove()
1150 1154 self.get(state, overwrite=False)
1151 1155 return
1152 1156 raise util.Abort((status or err).splitlines()[-1])
1153 1157 self.ui.status(status)
1154 1158
1155 1159 @annotatesubrepoerror
1156 1160 def merge(self, state):
1157 1161 old = self._state[1]
1158 1162 new = state[1]
1159 1163 wcrev = self._wcrev()
1160 1164 if new != wcrev:
1161 1165 dirty = old == wcrev or self._wcchanged()[0]
1162 1166 if _updateprompt(self.ui, self, dirty, wcrev, new):
1163 1167 self.get(state, False)
1164 1168
1165 1169 def push(self, opts):
1166 1170 # push is a no-op for SVN
1167 1171 return True
1168 1172
1169 1173 @annotatesubrepoerror
1170 1174 def files(self):
1171 1175 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1172 1176 doc = xml.dom.minidom.parseString(output)
1173 1177 paths = []
1174 1178 for e in doc.getElementsByTagName('entry'):
1175 1179 kind = str(e.getAttribute('kind'))
1176 1180 if kind != 'file':
1177 1181 continue
1178 1182 name = ''.join(c.data for c
1179 1183 in e.getElementsByTagName('name')[0].childNodes
1180 1184 if c.nodeType == c.TEXT_NODE)
1181 1185 paths.append(name.encode('utf-8'))
1182 1186 return paths
1183 1187
1184 1188 def filedata(self, name):
1185 1189 return self._svncommand(['cat'], name)[0]
1186 1190
1187 1191
1188 1192 class gitsubrepo(abstractsubrepo):
1189 1193 def __init__(self, ctx, path, state):
1190 1194 super(gitsubrepo, self).__init__(ctx, path)
1191 1195 self._state = state
1192 1196 self._abspath = ctx.repo().wjoin(path)
1193 1197 self._subparent = ctx.repo()
1194 1198 self._ensuregit()
1195 1199
1196 1200 def _ensuregit(self):
1197 1201 try:
1198 1202 self._gitexecutable = 'git'
1199 1203 out, err = self._gitnodir(['--version'])
1200 1204 except OSError, e:
1201 1205 if e.errno != 2 or os.name != 'nt':
1202 1206 raise
1203 1207 self._gitexecutable = 'git.cmd'
1204 1208 out, err = self._gitnodir(['--version'])
1205 1209 versionstatus = self._checkversion(out)
1206 1210 if versionstatus == 'unknown':
1207 1211 self.ui.warn(_('cannot retrieve git version\n'))
1208 1212 elif versionstatus == 'abort':
1209 1213 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1210 1214 elif versionstatus == 'warning':
1211 1215 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1212 1216
1213 1217 @staticmethod
1214 1218 def _gitversion(out):
1215 1219 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1216 1220 if m:
1217 1221 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1218 1222
1219 1223 m = re.search(r'^git version (\d+)\.(\d+)', out)
1220 1224 if m:
1221 1225 return (int(m.group(1)), int(m.group(2)), 0)
1222 1226
1223 1227 return -1
1224 1228
1225 1229 @staticmethod
1226 1230 def _checkversion(out):
1227 1231 '''ensure git version is new enough
1228 1232
1229 1233 >>> _checkversion = gitsubrepo._checkversion
1230 1234 >>> _checkversion('git version 1.6.0')
1231 1235 'ok'
1232 1236 >>> _checkversion('git version 1.8.5')
1233 1237 'ok'
1234 1238 >>> _checkversion('git version 1.4.0')
1235 1239 'abort'
1236 1240 >>> _checkversion('git version 1.5.0')
1237 1241 'warning'
1238 1242 >>> _checkversion('git version 1.9-rc0')
1239 1243 'ok'
1240 1244 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1241 1245 'ok'
1242 1246 >>> _checkversion('git version 1.9.0.GIT')
1243 1247 'ok'
1244 1248 >>> _checkversion('git version 12345')
1245 1249 'unknown'
1246 1250 >>> _checkversion('no')
1247 1251 'unknown'
1248 1252 '''
1249 1253 version = gitsubrepo._gitversion(out)
1250 1254 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1251 1255 # despite the docstring comment. For now, error on 1.4.0, warn on
1252 1256 # 1.5.0 but attempt to continue.
1253 1257 if version == -1:
1254 1258 return 'unknown'
1255 1259 if version < (1, 5, 0):
1256 1260 return 'abort'
1257 1261 elif version < (1, 6, 0):
1258 1262 return 'warning'
1259 1263 return 'ok'
1260 1264
1261 1265 def _gitcommand(self, commands, env=None, stream=False):
1262 1266 return self._gitdir(commands, env=env, stream=stream)[0]
1263 1267
1264 1268 def _gitdir(self, commands, env=None, stream=False):
1265 1269 return self._gitnodir(commands, env=env, stream=stream,
1266 1270 cwd=self._abspath)
1267 1271
1268 1272 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1269 1273 """Calls the git command
1270 1274
1271 1275 The methods tries to call the git command. versions prior to 1.6.0
1272 1276 are not supported and very probably fail.
1273 1277 """
1274 1278 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1275 1279 # unless ui.quiet is set, print git's stderr,
1276 1280 # which is mostly progress and useful info
1277 1281 errpipe = None
1278 1282 if self.ui.quiet:
1279 1283 errpipe = open(os.devnull, 'w')
1280 1284 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1281 1285 cwd=cwd, env=env, close_fds=util.closefds,
1282 1286 stdout=subprocess.PIPE, stderr=errpipe)
1283 1287 if stream:
1284 1288 return p.stdout, None
1285 1289
1286 1290 retdata = p.stdout.read().strip()
1287 1291 # wait for the child to exit to avoid race condition.
1288 1292 p.wait()
1289 1293
1290 1294 if p.returncode != 0 and p.returncode != 1:
1291 1295 # there are certain error codes that are ok
1292 1296 command = commands[0]
1293 1297 if command in ('cat-file', 'symbolic-ref'):
1294 1298 return retdata, p.returncode
1295 1299 # for all others, abort
1296 1300 raise util.Abort('git %s error %d in %s' %
1297 1301 (command, p.returncode, self._relpath))
1298 1302
1299 1303 return retdata, p.returncode
1300 1304
1301 1305 def _gitmissing(self):
1302 1306 return not self.wvfs.exists('.git')
1303 1307
1304 1308 def _gitstate(self):
1305 1309 return self._gitcommand(['rev-parse', 'HEAD'])
1306 1310
1307 1311 def _gitcurrentbranch(self):
1308 1312 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1309 1313 if err:
1310 1314 current = None
1311 1315 return current
1312 1316
1313 1317 def _gitremote(self, remote):
1314 1318 out = self._gitcommand(['remote', 'show', '-n', remote])
1315 1319 line = out.split('\n')[1]
1316 1320 i = line.index('URL: ') + len('URL: ')
1317 1321 return line[i:]
1318 1322
1319 1323 def _githavelocally(self, revision):
1320 1324 out, code = self._gitdir(['cat-file', '-e', revision])
1321 1325 return code == 0
1322 1326
1323 1327 def _gitisancestor(self, r1, r2):
1324 1328 base = self._gitcommand(['merge-base', r1, r2])
1325 1329 return base == r1
1326 1330
1327 1331 def _gitisbare(self):
1328 1332 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1329 1333
1330 1334 def _gitupdatestat(self):
1331 1335 """This must be run before git diff-index.
1332 1336 diff-index only looks at changes to file stat;
1333 1337 this command looks at file contents and updates the stat."""
1334 1338 self._gitcommand(['update-index', '-q', '--refresh'])
1335 1339
1336 1340 def _gitbranchmap(self):
1337 1341 '''returns 2 things:
1338 1342 a map from git branch to revision
1339 1343 a map from revision to branches'''
1340 1344 branch2rev = {}
1341 1345 rev2branch = {}
1342 1346
1343 1347 out = self._gitcommand(['for-each-ref', '--format',
1344 1348 '%(objectname) %(refname)'])
1345 1349 for line in out.split('\n'):
1346 1350 revision, ref = line.split(' ')
1347 1351 if (not ref.startswith('refs/heads/') and
1348 1352 not ref.startswith('refs/remotes/')):
1349 1353 continue
1350 1354 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1351 1355 continue # ignore remote/HEAD redirects
1352 1356 branch2rev[ref] = revision
1353 1357 rev2branch.setdefault(revision, []).append(ref)
1354 1358 return branch2rev, rev2branch
1355 1359
1356 1360 def _gittracking(self, branches):
1357 1361 'return map of remote branch to local tracking branch'
1358 1362 # assumes no more than one local tracking branch for each remote
1359 1363 tracking = {}
1360 1364 for b in branches:
1361 1365 if b.startswith('refs/remotes/'):
1362 1366 continue
1363 1367 bname = b.split('/', 2)[2]
1364 1368 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1365 1369 if remote:
1366 1370 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1367 1371 tracking['refs/remotes/%s/%s' %
1368 1372 (remote, ref.split('/', 2)[2])] = b
1369 1373 return tracking
1370 1374
1371 1375 def _abssource(self, source):
1372 1376 if '://' not in source:
1373 1377 # recognize the scp syntax as an absolute source
1374 1378 colon = source.find(':')
1375 1379 if colon != -1 and '/' not in source[:colon]:
1376 1380 return source
1377 1381 self._subsource = source
1378 1382 return _abssource(self)
1379 1383
1380 1384 def _fetch(self, source, revision):
1381 1385 if self._gitmissing():
1382 1386 source = self._abssource(source)
1383 1387 self.ui.status(_('cloning subrepo %s from %s\n') %
1384 1388 (self._relpath, source))
1385 1389 self._gitnodir(['clone', source, self._abspath])
1386 1390 if self._githavelocally(revision):
1387 1391 return
1388 1392 self.ui.status(_('pulling subrepo %s from %s\n') %
1389 1393 (self._relpath, self._gitremote('origin')))
1390 1394 # try only origin: the originally cloned repo
1391 1395 self._gitcommand(['fetch'])
1392 1396 if not self._githavelocally(revision):
1393 1397 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1394 1398 (revision, self._relpath))
1395 1399
1396 1400 @annotatesubrepoerror
1397 1401 def dirty(self, ignoreupdate=False):
1398 1402 if self._gitmissing():
1399 1403 return self._state[1] != ''
1400 1404 if self._gitisbare():
1401 1405 return True
1402 1406 if not ignoreupdate and self._state[1] != self._gitstate():
1403 1407 # different version checked out
1404 1408 return True
1405 1409 # check for staged changes or modified files; ignore untracked files
1406 1410 self._gitupdatestat()
1407 1411 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1408 1412 return code == 1
1409 1413
1410 1414 def basestate(self):
1411 1415 return self._gitstate()
1412 1416
1413 1417 @annotatesubrepoerror
1414 1418 def get(self, state, overwrite=False):
1415 1419 source, revision, kind = state
1416 1420 if not revision:
1417 1421 self.remove()
1418 1422 return
1419 1423 self._fetch(source, revision)
1420 1424 # if the repo was set to be bare, unbare it
1421 1425 if self._gitisbare():
1422 1426 self._gitcommand(['config', 'core.bare', 'false'])
1423 1427 if self._gitstate() == revision:
1424 1428 self._gitcommand(['reset', '--hard', 'HEAD'])
1425 1429 return
1426 1430 elif self._gitstate() == revision:
1427 1431 if overwrite:
1428 1432 # first reset the index to unmark new files for commit, because
1429 1433 # reset --hard will otherwise throw away files added for commit,
1430 1434 # not just unmark them.
1431 1435 self._gitcommand(['reset', 'HEAD'])
1432 1436 self._gitcommand(['reset', '--hard', 'HEAD'])
1433 1437 return
1434 1438 branch2rev, rev2branch = self._gitbranchmap()
1435 1439
1436 1440 def checkout(args):
1437 1441 cmd = ['checkout']
1438 1442 if overwrite:
1439 1443 # first reset the index to unmark new files for commit, because
1440 1444 # the -f option will otherwise throw away files added for
1441 1445 # commit, not just unmark them.
1442 1446 self._gitcommand(['reset', 'HEAD'])
1443 1447 cmd.append('-f')
1444 1448 self._gitcommand(cmd + args)
1445 1449 _sanitize(self.ui, self.wvfs, '.git')
1446 1450
1447 1451 def rawcheckout():
1448 1452 # no branch to checkout, check it out with no branch
1449 1453 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1450 1454 self._relpath)
1451 1455 self.ui.warn(_('check out a git branch if you intend '
1452 1456 'to make changes\n'))
1453 1457 checkout(['-q', revision])
1454 1458
1455 1459 if revision not in rev2branch:
1456 1460 rawcheckout()
1457 1461 return
1458 1462 branches = rev2branch[revision]
1459 1463 firstlocalbranch = None
1460 1464 for b in branches:
1461 1465 if b == 'refs/heads/master':
1462 1466 # master trumps all other branches
1463 1467 checkout(['refs/heads/master'])
1464 1468 return
1465 1469 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1466 1470 firstlocalbranch = b
1467 1471 if firstlocalbranch:
1468 1472 checkout([firstlocalbranch])
1469 1473 return
1470 1474
1471 1475 tracking = self._gittracking(branch2rev.keys())
1472 1476 # choose a remote branch already tracked if possible
1473 1477 remote = branches[0]
1474 1478 if remote not in tracking:
1475 1479 for b in branches:
1476 1480 if b in tracking:
1477 1481 remote = b
1478 1482 break
1479 1483
1480 1484 if remote not in tracking:
1481 1485 # create a new local tracking branch
1482 1486 local = remote.split('/', 3)[3]
1483 1487 checkout(['-b', local, remote])
1484 1488 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1485 1489 # When updating to a tracked remote branch,
1486 1490 # if the local tracking branch is downstream of it,
1487 1491 # a normal `git pull` would have performed a "fast-forward merge"
1488 1492 # which is equivalent to updating the local branch to the remote.
1489 1493 # Since we are only looking at branching at update, we need to
1490 1494 # detect this situation and perform this action lazily.
1491 1495 if tracking[remote] != self._gitcurrentbranch():
1492 1496 checkout([tracking[remote]])
1493 1497 self._gitcommand(['merge', '--ff', remote])
1494 1498 _sanitize(self.ui, self.wvfs, '.git')
1495 1499 else:
1496 1500 # a real merge would be required, just checkout the revision
1497 1501 rawcheckout()
1498 1502
1499 1503 @annotatesubrepoerror
1500 1504 def commit(self, text, user, date):
1501 1505 if self._gitmissing():
1502 1506 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1503 1507 cmd = ['commit', '-a', '-m', text]
1504 1508 env = os.environ.copy()
1505 1509 if user:
1506 1510 cmd += ['--author', user]
1507 1511 if date:
1508 1512 # git's date parser silently ignores when seconds < 1e9
1509 1513 # convert to ISO8601
1510 1514 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1511 1515 '%Y-%m-%dT%H:%M:%S %1%2')
1512 1516 self._gitcommand(cmd, env=env)
1513 1517 # make sure commit works otherwise HEAD might not exist under certain
1514 1518 # circumstances
1515 1519 return self._gitstate()
1516 1520
1517 1521 @annotatesubrepoerror
1518 1522 def merge(self, state):
1519 1523 source, revision, kind = state
1520 1524 self._fetch(source, revision)
1521 1525 base = self._gitcommand(['merge-base', revision, self._state[1]])
1522 1526 self._gitupdatestat()
1523 1527 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1524 1528
1525 1529 def mergefunc():
1526 1530 if base == revision:
1527 1531 self.get(state) # fast forward merge
1528 1532 elif base != self._state[1]:
1529 1533 self._gitcommand(['merge', '--no-commit', revision])
1530 1534 _sanitize(self.ui, self.wvfs, '.git')
1531 1535
1532 1536 if self.dirty():
1533 1537 if self._gitstate() != revision:
1534 1538 dirty = self._gitstate() == self._state[1] or code != 0
1535 1539 if _updateprompt(self.ui, self, dirty,
1536 1540 self._state[1][:7], revision[:7]):
1537 1541 mergefunc()
1538 1542 else:
1539 1543 mergefunc()
1540 1544
1541 1545 @annotatesubrepoerror
1542 1546 def push(self, opts):
1543 1547 force = opts.get('force')
1544 1548
1545 1549 if not self._state[1]:
1546 1550 return True
1547 1551 if self._gitmissing():
1548 1552 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1549 1553 # if a branch in origin contains the revision, nothing to do
1550 1554 branch2rev, rev2branch = self._gitbranchmap()
1551 1555 if self._state[1] in rev2branch:
1552 1556 for b in rev2branch[self._state[1]]:
1553 1557 if b.startswith('refs/remotes/origin/'):
1554 1558 return True
1555 1559 for b, revision in branch2rev.iteritems():
1556 1560 if b.startswith('refs/remotes/origin/'):
1557 1561 if self._gitisancestor(self._state[1], revision):
1558 1562 return True
1559 1563 # otherwise, try to push the currently checked out branch
1560 1564 cmd = ['push']
1561 1565 if force:
1562 1566 cmd.append('--force')
1563 1567
1564 1568 current = self._gitcurrentbranch()
1565 1569 if current:
1566 1570 # determine if the current branch is even useful
1567 1571 if not self._gitisancestor(self._state[1], current):
1568 1572 self.ui.warn(_('unrelated git branch checked out '
1569 1573 'in subrepo %s\n') % self._relpath)
1570 1574 return False
1571 1575 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1572 1576 (current.split('/', 2)[2], self._relpath))
1573 1577 ret = self._gitdir(cmd + ['origin', current])
1574 1578 return ret[1] == 0
1575 1579 else:
1576 1580 self.ui.warn(_('no branch checked out in subrepo %s\n'
1577 1581 'cannot push revision %s\n') %
1578 1582 (self._relpath, self._state[1]))
1579 1583 return False
1580 1584
1581 1585 @annotatesubrepoerror
1582 1586 def add(self, ui, match, prefix, explicitonly, **opts):
1583 1587 if self._gitmissing():
1584 1588 return []
1585 1589
1586 1590 (modified, added, removed,
1587 1591 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1588 1592 clean=True)
1589 1593
1590 1594 tracked = set()
1591 1595 # dirstates 'amn' warn, 'r' is added again
1592 1596 for l in (modified, added, deleted, clean):
1593 1597 tracked.update(l)
1594 1598
1595 1599 # Unknown files not of interest will be rejected by the matcher
1596 1600 files = unknown
1597 1601 files.extend(match.files())
1598 1602
1599 1603 rejected = []
1600 1604
1601 1605 files = [f for f in sorted(set(files)) if match(f)]
1602 1606 for f in files:
1603 1607 exact = match.exact(f)
1604 1608 command = ["add"]
1605 1609 if exact:
1606 1610 command.append("-f") #should be added, even if ignored
1607 1611 if ui.verbose or not exact:
1608 1612 ui.status(_('adding %s\n') % match.rel(f))
1609 1613
1610 1614 if f in tracked: # hg prints 'adding' even if already tracked
1611 1615 if exact:
1612 1616 rejected.append(f)
1613 1617 continue
1614 1618 if not opts.get('dry_run'):
1615 1619 self._gitcommand(command + [f])
1616 1620
1617 1621 for f in rejected:
1618 1622 ui.warn(_("%s already tracked!\n") % match.abs(f))
1619 1623
1620 1624 return rejected
1621 1625
1622 1626 @annotatesubrepoerror
1623 1627 def remove(self):
1624 1628 if self._gitmissing():
1625 1629 return
1626 1630 if self.dirty():
1627 1631 self.ui.warn(_('not removing repo %s because '
1628 1632 'it has changes.\n') % self._relpath)
1629 1633 return
1630 1634 # we can't fully delete the repository as it may contain
1631 1635 # local-only history
1632 1636 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1633 1637 self._gitcommand(['config', 'core.bare', 'true'])
1634 1638 for f, kind in self.wvfs.readdir():
1635 1639 if f == '.git':
1636 1640 continue
1637 1641 if kind == stat.S_IFDIR:
1638 1642 self.wvfs.rmtree(f)
1639 1643 else:
1640 1644 self.wvfs.unlink(f)
1641 1645
1642 1646 def archive(self, archiver, prefix, match=None):
1643 1647 total = 0
1644 1648 source, revision = self._state
1645 1649 if not revision:
1646 1650 return total
1647 1651 self._fetch(source, revision)
1648 1652
1649 1653 # Parse git's native archive command.
1650 1654 # This should be much faster than manually traversing the trees
1651 1655 # and objects with many subprocess calls.
1652 1656 tarstream = self._gitcommand(['archive', revision], stream=True)
1653 1657 tar = tarfile.open(fileobj=tarstream, mode='r|')
1654 1658 relpath = subrelpath(self)
1655 1659 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1656 1660 for i, info in enumerate(tar):
1657 1661 if info.isdir():
1658 1662 continue
1659 1663 if match and not match(info.name):
1660 1664 continue
1661 1665 if info.issym():
1662 1666 data = info.linkname
1663 1667 else:
1664 1668 data = tar.extractfile(info).read()
1665 1669 archiver.addfile(self.wvfs.reljoin(prefix, self._path, info.name),
1666 1670 info.mode, info.issym(), data)
1667 1671 total += 1
1668 1672 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1669 1673 unit=_('files'))
1670 1674 self.ui.progress(_('archiving (%s)') % relpath, None)
1671 1675 return total
1672 1676
1673 1677
1674 1678 @annotatesubrepoerror
1675 1679 def cat(self, match, prefix, **opts):
1676 1680 rev = self._state[1]
1677 1681 if match.anypats():
1678 1682 return 1 #No support for include/exclude yet
1679 1683
1680 1684 if not match.files():
1681 1685 return 1
1682 1686
1683 1687 for f in match.files():
1684 1688 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1685 1689 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1686 1690 self._ctx.node(),
1687 1691 pathname=self.wvfs.reljoin(prefix, f))
1688 1692 fp.write(output)
1689 1693 fp.close()
1690 1694 return 0
1691 1695
1692 1696
1693 1697 @annotatesubrepoerror
1694 1698 def status(self, rev2, **opts):
1695 1699 rev1 = self._state[1]
1696 1700 if self._gitmissing() or not rev1:
1697 1701 # if the repo is missing, return no results
1698 1702 return scmutil.status([], [], [], [], [], [], [])
1699 1703 modified, added, removed = [], [], []
1700 1704 self._gitupdatestat()
1701 1705 if rev2:
1702 1706 command = ['diff-tree', rev1, rev2]
1703 1707 else:
1704 1708 command = ['diff-index', rev1]
1705 1709 out = self._gitcommand(command)
1706 1710 for line in out.split('\n'):
1707 1711 tab = line.find('\t')
1708 1712 if tab == -1:
1709 1713 continue
1710 1714 status, f = line[tab - 1], line[tab + 1:]
1711 1715 if status == 'M':
1712 1716 modified.append(f)
1713 1717 elif status == 'A':
1714 1718 added.append(f)
1715 1719 elif status == 'D':
1716 1720 removed.append(f)
1717 1721
1718 1722 deleted, unknown, ignored, clean = [], [], [], []
1719 1723
1720 1724 command = ['status', '--porcelain', '-z']
1721 1725 if opts.get('unknown'):
1722 1726 command += ['--untracked-files=all']
1723 1727 if opts.get('ignored'):
1724 1728 command += ['--ignored']
1725 1729 out = self._gitcommand(command)
1726 1730
1727 1731 changedfiles = set()
1728 1732 changedfiles.update(modified)
1729 1733 changedfiles.update(added)
1730 1734 changedfiles.update(removed)
1731 1735 for line in out.split('\0'):
1732 1736 if not line:
1733 1737 continue
1734 1738 st = line[0:2]
1735 1739 #moves and copies show 2 files on one line
1736 1740 if line.find('\0') >= 0:
1737 1741 filename1, filename2 = line[3:].split('\0')
1738 1742 else:
1739 1743 filename1 = line[3:]
1740 1744 filename2 = None
1741 1745
1742 1746 changedfiles.add(filename1)
1743 1747 if filename2:
1744 1748 changedfiles.add(filename2)
1745 1749
1746 1750 if st == '??':
1747 1751 unknown.append(filename1)
1748 1752 elif st == '!!':
1749 1753 ignored.append(filename1)
1750 1754
1751 1755 if opts.get('clean'):
1752 1756 out = self._gitcommand(['ls-files'])
1753 1757 for f in out.split('\n'):
1754 1758 if not f in changedfiles:
1755 1759 clean.append(f)
1756 1760
1757 1761 return scmutil.status(modified, added, removed, deleted,
1758 1762 unknown, ignored, clean)
1759 1763
1760 1764 @annotatesubrepoerror
1761 1765 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1762 1766 node1 = self._state[1]
1763 1767 cmd = ['diff']
1764 1768 if opts['stat']:
1765 1769 cmd.append('--stat')
1766 1770 else:
1767 1771 # for Git, this also implies '-p'
1768 1772 cmd.append('-U%d' % diffopts.context)
1769 1773
1770 1774 gitprefix = self.wvfs.reljoin(prefix, self._path)
1771 1775
1772 1776 if diffopts.noprefix:
1773 1777 cmd.extend(['--src-prefix=%s/' % gitprefix,
1774 1778 '--dst-prefix=%s/' % gitprefix])
1775 1779 else:
1776 1780 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1777 1781 '--dst-prefix=b/%s/' % gitprefix])
1778 1782
1779 1783 if diffopts.ignorews:
1780 1784 cmd.append('--ignore-all-space')
1781 1785 if diffopts.ignorewsamount:
1782 1786 cmd.append('--ignore-space-change')
1783 1787 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1784 1788 and diffopts.ignoreblanklines:
1785 1789 cmd.append('--ignore-blank-lines')
1786 1790
1787 1791 cmd.append(node1)
1788 1792 if node2:
1789 1793 cmd.append(node2)
1790 1794
1791 1795 output = ""
1792 1796 if match.always():
1793 1797 output += self._gitcommand(cmd) + '\n'
1794 1798 else:
1795 1799 st = self.status(node2)[:3]
1796 1800 files = [f for sublist in st for f in sublist]
1797 1801 for f in files:
1798 1802 if match(f):
1799 1803 output += self._gitcommand(cmd + ['--', f]) + '\n'
1800 1804
1801 1805 if output.strip():
1802 1806 ui.write(output)
1803 1807
1804 1808 @annotatesubrepoerror
1805 1809 def revert(self, substate, *pats, **opts):
1806 1810 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1807 1811 if not opts.get('no_backup'):
1808 1812 status = self.status(None)
1809 1813 names = status.modified
1810 1814 for name in names:
1811 1815 bakname = "%s.orig" % name
1812 1816 self.ui.note(_('saving current version of %s as %s\n') %
1813 1817 (name, bakname))
1814 1818 self.wvfs.rename(name, bakname)
1815 1819
1816 1820 if not opts.get('dry_run'):
1817 1821 self.get(substate, overwrite=True)
1818 1822 return []
1819 1823
1820 1824 def shortid(self, revid):
1821 1825 return revid[:7]
1822 1826
1823 1827 types = {
1824 1828 'hg': hgsubrepo,
1825 1829 'svn': svnsubrepo,
1826 1830 'git': gitsubrepo,
1827 1831 }
@@ -1,1657 +1,1677 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
602 $ hg out -S -r `hg log -r tip -T "{node|short}"`
603 comparing with $TESTTMP/t (glob)
604 searching for changes
605 no changes found
606 comparing with $TESTTMP/t/s
607 searching for changes
608 no changes found
609 comparing with $TESTTMP/t/s/ss
610 searching for changes
611 changeset: 1:79ea5566a333
612 tag: tip
613 user: test
614 date: Thu Jan 01 00:00:00 1970 +0000
615 summary: test dirty store detection
616
617 comparing with $TESTTMP/t/t
618 searching for changes
619 no changes found
620
601 621 $ hg push
602 622 pushing to $TESTTMP/t (glob)
603 623 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
604 624 searching for changes
605 625 adding changesets
606 626 adding manifests
607 627 adding file changes
608 628 added 1 changesets with 1 changes to 1 files
609 629 no changes made to subrepo s since last push to $TESTTMP/t/s
610 630 no changes made to subrepo t since last push to $TESTTMP/t/t
611 631 searching for changes
612 632 no changes found
613 633 [1]
614 634
615 635 a subrepo store may be clean versus one repo but not versus another
616 636
617 637 $ hg push
618 638 pushing to $TESTTMP/t (glob)
619 639 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
620 640 no changes made to subrepo s since last push to $TESTTMP/t/s
621 641 no changes made to subrepo t since last push to $TESTTMP/t/t
622 642 searching for changes
623 643 no changes found
624 644 [1]
625 645 $ hg push ../tcc
626 646 pushing to ../tcc
627 647 pushing subrepo s/ss to ../tcc/s/ss (glob)
628 648 searching for changes
629 649 adding changesets
630 650 adding manifests
631 651 adding file changes
632 652 added 1 changesets with 1 changes to 1 files
633 653 no changes made to subrepo s since last push to ../tcc/s
634 654 no changes made to subrepo t since last push to ../tcc/t
635 655 searching for changes
636 656 no changes found
637 657 [1]
638 658
639 659 update
640 660
641 661 $ cd ../t
642 662 $ hg up -C # discard our earlier merge
643 663 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
644 664 $ echo blah > t/t
645 665 $ hg ci -m13
646 666 committing subrepository t
647 667
648 668 backout calls revert internally with minimal opts, which should not raise
649 669 KeyError
650 670
651 671 $ hg backout ".^"
652 672 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
653 673 changeset c373c8102e68 backed out, don't forget to commit.
654 674
655 675 $ hg up -C # discard changes
656 676 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
657 677
658 678 pull
659 679
660 680 $ cd ../tc
661 681 $ hg pull
662 682 pulling from $TESTTMP/t (glob)
663 683 searching for changes
664 684 adding changesets
665 685 adding manifests
666 686 adding file changes
667 687 added 1 changesets with 1 changes to 1 files
668 688 (run 'hg update' to get a working copy)
669 689
670 690 should pull t
671 691
672 692 $ hg up
673 693 pulling subrepo t from $TESTTMP/t/t
674 694 searching for changes
675 695 adding changesets
676 696 adding manifests
677 697 adding file changes
678 698 added 1 changesets with 1 changes to 1 files
679 699 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
680 700 $ cat t/t
681 701 blah
682 702
683 703 bogus subrepo path aborts
684 704
685 705 $ echo 'bogus=[boguspath' >> .hgsub
686 706 $ hg ci -m 'bogus subrepo path'
687 707 abort: missing ] in subrepo source
688 708 [255]
689 709
690 710 Issue1986: merge aborts when trying to merge a subrepo that
691 711 shouldn't need merging
692 712
693 713 # subrepo layout
694 714 #
695 715 # o 5 br
696 716 # /|
697 717 # o | 4 default
698 718 # | |
699 719 # | o 3 br
700 720 # |/|
701 721 # o | 2 default
702 722 # | |
703 723 # | o 1 br
704 724 # |/
705 725 # o 0 default
706 726
707 727 $ cd ..
708 728 $ rm -rf sub
709 729 $ hg init main
710 730 $ cd main
711 731 $ hg init s
712 732 $ cd s
713 733 $ echo a > a
714 734 $ hg ci -Am1
715 735 adding a
716 736 $ hg branch br
717 737 marked working directory as branch br
718 738 (branches are permanent and global, did you want a bookmark?)
719 739 $ echo a >> a
720 740 $ hg ci -m1
721 741 $ hg up default
722 742 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
723 743 $ echo b > b
724 744 $ hg ci -Am1
725 745 adding b
726 746 $ hg up br
727 747 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
728 748 $ hg merge tip
729 749 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
730 750 (branch merge, don't forget to commit)
731 751 $ hg ci -m1
732 752 $ hg up 2
733 753 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
734 754 $ echo c > c
735 755 $ hg ci -Am1
736 756 adding c
737 757 $ hg up 3
738 758 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
739 759 $ hg merge 4
740 760 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
741 761 (branch merge, don't forget to commit)
742 762 $ hg ci -m1
743 763
744 764 # main repo layout:
745 765 #
746 766 # * <-- try to merge default into br again
747 767 # .`|
748 768 # . o 5 br --> substate = 5
749 769 # . |
750 770 # o | 4 default --> substate = 4
751 771 # | |
752 772 # | o 3 br --> substate = 2
753 773 # |/|
754 774 # o | 2 default --> substate = 2
755 775 # | |
756 776 # | o 1 br --> substate = 3
757 777 # |/
758 778 # o 0 default --> substate = 2
759 779
760 780 $ cd ..
761 781 $ echo 's = s' > .hgsub
762 782 $ hg -R s up 2
763 783 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
764 784 $ hg ci -Am1
765 785 adding .hgsub
766 786 $ hg branch br
767 787 marked working directory as branch br
768 788 (branches are permanent and global, did you want a bookmark?)
769 789 $ echo b > b
770 790 $ hg -R s up 3
771 791 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
772 792 $ hg ci -Am1
773 793 adding b
774 794 $ hg up default
775 795 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
776 796 $ echo c > c
777 797 $ hg ci -Am1
778 798 adding c
779 799 $ hg up 1
780 800 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
781 801 $ hg merge 2
782 802 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
783 803 (branch merge, don't forget to commit)
784 804 $ hg ci -m1
785 805 $ hg up 2
786 806 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
787 807 $ hg -R s up 4
788 808 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
789 809 $ echo d > d
790 810 $ hg ci -Am1
791 811 adding d
792 812 $ hg up 3
793 813 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
794 814 $ hg -R s up 5
795 815 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
796 816 $ echo e > e
797 817 $ hg ci -Am1
798 818 adding e
799 819
800 820 $ hg up 5
801 821 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
802 822 $ hg merge 4 # try to merge default into br again
803 823 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
804 824 (M)erge, keep (l)ocal or keep (r)emote? m
805 825 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
806 826 (branch merge, don't forget to commit)
807 827 $ cd ..
808 828
809 829 test subrepo delete from .hgsubstate
810 830
811 831 $ hg init testdelete
812 832 $ mkdir testdelete/nested testdelete/nested2
813 833 $ hg init testdelete/nested
814 834 $ hg init testdelete/nested2
815 835 $ echo test > testdelete/nested/foo
816 836 $ echo test > testdelete/nested2/foo
817 837 $ hg -R testdelete/nested add
818 838 adding testdelete/nested/foo (glob)
819 839 $ hg -R testdelete/nested2 add
820 840 adding testdelete/nested2/foo (glob)
821 841 $ hg -R testdelete/nested ci -m test
822 842 $ hg -R testdelete/nested2 ci -m test
823 843 $ echo nested = nested > testdelete/.hgsub
824 844 $ echo nested2 = nested2 >> testdelete/.hgsub
825 845 $ hg -R testdelete add
826 846 adding testdelete/.hgsub (glob)
827 847 $ hg -R testdelete ci -m "nested 1 & 2 added"
828 848 $ echo nested = nested > testdelete/.hgsub
829 849 $ hg -R testdelete ci -m "nested 2 deleted"
830 850 $ cat testdelete/.hgsubstate
831 851 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
832 852 $ hg -R testdelete remove testdelete/.hgsub
833 853 $ hg -R testdelete ci -m ".hgsub deleted"
834 854 $ cat testdelete/.hgsubstate
835 855 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
836 856
837 857 test repository cloning
838 858
839 859 $ mkdir mercurial mercurial2
840 860 $ hg init nested_absolute
841 861 $ echo test > nested_absolute/foo
842 862 $ hg -R nested_absolute add
843 863 adding nested_absolute/foo (glob)
844 864 $ hg -R nested_absolute ci -mtest
845 865 $ cd mercurial
846 866 $ hg init nested_relative
847 867 $ echo test2 > nested_relative/foo2
848 868 $ hg -R nested_relative add
849 869 adding nested_relative/foo2 (glob)
850 870 $ hg -R nested_relative ci -mtest2
851 871 $ hg init main
852 872 $ echo "nested_relative = ../nested_relative" > main/.hgsub
853 873 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
854 874 $ hg -R main add
855 875 adding main/.hgsub (glob)
856 876 $ hg -R main ci -m "add subrepos"
857 877 $ cd ..
858 878 $ hg clone mercurial/main mercurial2/main
859 879 updating to branch default
860 880 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
861 881 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
862 882 > mercurial2/main/nested_relative/.hg/hgrc
863 883 [paths]
864 884 default = $TESTTMP/mercurial/nested_absolute
865 885 [paths]
866 886 default = $TESTTMP/mercurial/nested_relative
867 887 $ rm -rf mercurial mercurial2
868 888
869 889 Issue1977: multirepo push should fail if subrepo push fails
870 890
871 891 $ hg init repo
872 892 $ hg init repo/s
873 893 $ echo a > repo/s/a
874 894 $ hg -R repo/s ci -Am0
875 895 adding a
876 896 $ echo s = s > repo/.hgsub
877 897 $ hg -R repo ci -Am1
878 898 adding .hgsub
879 899 $ hg clone repo repo2
880 900 updating to branch default
881 901 cloning subrepo s from $TESTTMP/repo/s
882 902 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
883 903 $ hg -q -R repo2 pull -u
884 904 $ echo 1 > repo2/s/a
885 905 $ hg -R repo2/s ci -m2
886 906 $ hg -q -R repo2/s push
887 907 $ hg -R repo2/s up -C 0
888 908 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
889 909 $ echo 2 > repo2/s/b
890 910 $ hg -R repo2/s ci -m3 -A
891 911 adding b
892 912 created new head
893 913 $ hg -R repo2 ci -m3
894 914 $ hg -q -R repo2 push
895 915 abort: push creates new remote head cc505f09a8b2! (in subrepo s)
896 916 (merge or see "hg help push" for details about pushing new heads)
897 917 [255]
898 918 $ hg -R repo update
899 919 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
900 920
901 921 test if untracked file is not overwritten
902 922
903 923 $ echo issue3276_ok > repo/s/b
904 924 $ hg -R repo2 push -f -q
905 925 $ touch -t 200001010000 repo/.hgsubstate
906 926 $ hg -R repo status --config debug.dirstate.delaywrite=2 repo/.hgsubstate
907 927 $ hg -R repo update
908 928 b: untracked file differs
909 929 abort: untracked files in working directory differ from files in requested revision (in subrepo s)
910 930 [255]
911 931
912 932 $ cat repo/s/b
913 933 issue3276_ok
914 934 $ rm repo/s/b
915 935 $ touch -t 200001010000 repo/.hgsubstate
916 936 $ hg -R repo revert --all
917 937 reverting repo/.hgsubstate (glob)
918 938 reverting subrepo s
919 939 $ hg -R repo update
920 940 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
921 941 $ cat repo/s/b
922 942 2
923 943 $ rm -rf repo2 repo
924 944
925 945
926 946 Issue1852 subrepos with relative paths always push/pull relative to default
927 947
928 948 Prepare a repo with subrepo
929 949
930 950 $ hg init issue1852a
931 951 $ cd issue1852a
932 952 $ hg init sub/repo
933 953 $ echo test > sub/repo/foo
934 954 $ hg -R sub/repo add sub/repo/foo
935 955 $ echo sub/repo = sub/repo > .hgsub
936 956 $ hg add .hgsub
937 957 $ hg ci -mtest
938 958 committing subrepository sub/repo (glob)
939 959 $ echo test >> sub/repo/foo
940 960 $ hg ci -mtest
941 961 committing subrepository sub/repo (glob)
942 962 $ hg cat sub/repo/foo
943 963 test
944 964 test
945 965 $ mkdir -p tmp/sub/repo
946 966 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
947 967 $ cat tmp/sub/repo/foo_p
948 968 test
949 969 $ mv sub/repo sub_
950 970 $ hg cat sub/repo/baz
951 971 skipping missing subrepository: sub/repo
952 972 [1]
953 973 $ rm -rf sub/repo
954 974 $ mv sub_ sub/repo
955 975 $ cd ..
956 976
957 977 Create repo without default path, pull top repo, and see what happens on update
958 978
959 979 $ hg init issue1852b
960 980 $ hg -R issue1852b pull issue1852a
961 981 pulling from issue1852a
962 982 requesting all changes
963 983 adding changesets
964 984 adding manifests
965 985 adding file changes
966 986 added 2 changesets with 3 changes to 2 files
967 987 (run 'hg update' to get a working copy)
968 988 $ hg -R issue1852b update
969 989 abort: default path for subrepository not found (in subrepo sub/repo) (glob)
970 990 [255]
971 991
972 992 Ensure a full traceback, not just the SubrepoAbort part
973 993
974 994 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise util\.Abort'
975 995 raise util.Abort(_("default path for subrepository not found"))
976 996
977 997 Pull -u now doesn't help
978 998
979 999 $ hg -R issue1852b pull -u issue1852a
980 1000 pulling from issue1852a
981 1001 searching for changes
982 1002 no changes found
983 1003
984 1004 Try the same, but with pull -u
985 1005
986 1006 $ hg init issue1852c
987 1007 $ hg -R issue1852c pull -r0 -u issue1852a
988 1008 pulling from issue1852a
989 1009 adding changesets
990 1010 adding manifests
991 1011 adding file changes
992 1012 added 1 changesets with 2 changes to 2 files
993 1013 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
994 1014 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
995 1015
996 1016 Try to push from the other side
997 1017
998 1018 $ hg -R issue1852a push `pwd`/issue1852c
999 1019 pushing to $TESTTMP/issue1852c (glob)
1000 1020 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
1001 1021 searching for changes
1002 1022 no changes found
1003 1023 searching for changes
1004 1024 adding changesets
1005 1025 adding manifests
1006 1026 adding file changes
1007 1027 added 1 changesets with 1 changes to 1 files
1008 1028
1009 1029 Incoming and outgoing should not use the default path:
1010 1030
1011 1031 $ hg clone -q issue1852a issue1852d
1012 1032 $ hg -R issue1852d outgoing --subrepos issue1852c
1013 1033 comparing with issue1852c
1014 1034 searching for changes
1015 1035 no changes found
1016 1036 comparing with issue1852c/sub/repo
1017 1037 searching for changes
1018 1038 no changes found
1019 1039 [1]
1020 1040 $ hg -R issue1852d incoming --subrepos issue1852c
1021 1041 comparing with issue1852c
1022 1042 searching for changes
1023 1043 no changes found
1024 1044 comparing with issue1852c/sub/repo
1025 1045 searching for changes
1026 1046 no changes found
1027 1047 [1]
1028 1048
1029 1049 Check that merge of a new subrepo doesn't write the uncommitted state to
1030 1050 .hgsubstate (issue4622)
1031 1051
1032 1052 $ hg init issue1852a/addedsub
1033 1053 $ echo zzz > issue1852a/addedsub/zz.txt
1034 1054 $ hg -R issue1852a/addedsub ci -Aqm "initial ZZ"
1035 1055
1036 1056 $ hg clone issue1852a/addedsub issue1852d/addedsub
1037 1057 updating to branch default
1038 1058 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1039 1059
1040 1060 $ echo def > issue1852a/sub/repo/foo
1041 1061 $ hg -R issue1852a ci -SAm 'tweaked subrepo'
1042 1062 adding tmp/sub/repo/foo_p
1043 1063 committing subrepository sub/repo (glob)
1044 1064
1045 1065 $ echo 'addedsub = addedsub' >> issue1852d/.hgsub
1046 1066 $ echo xyz > issue1852d/sub/repo/foo
1047 1067 $ hg -R issue1852d pull -u
1048 1068 pulling from $TESTTMP/issue1852a (glob)
1049 1069 searching for changes
1050 1070 adding changesets
1051 1071 adding manifests
1052 1072 adding file changes
1053 1073 added 1 changesets with 2 changes to 2 files
1054 1074 subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
1055 1075 (M)erge, keep (l)ocal or keep (r)emote? m
1056 1076 pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo (glob)
1057 1077 searching for changes
1058 1078 adding changesets
1059 1079 adding manifests
1060 1080 adding file changes
1061 1081 added 1 changesets with 1 changes to 1 files
1062 1082 subrepository sources for sub/repo differ (glob)
1063 1083 use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
1064 1084 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1065 1085 $ cat issue1852d/.hgsubstate
1066 1086 f42d5c7504a811dda50f5cf3e5e16c3330b87172 sub/repo
1067 1087
1068 1088 Check status of files when none of them belong to the first
1069 1089 subrepository:
1070 1090
1071 1091 $ hg init subrepo-status
1072 1092 $ cd subrepo-status
1073 1093 $ hg init subrepo-1
1074 1094 $ hg init subrepo-2
1075 1095 $ cd subrepo-2
1076 1096 $ touch file
1077 1097 $ hg add file
1078 1098 $ cd ..
1079 1099 $ echo subrepo-1 = subrepo-1 > .hgsub
1080 1100 $ echo subrepo-2 = subrepo-2 >> .hgsub
1081 1101 $ hg add .hgsub
1082 1102 $ hg ci -m 'Added subrepos'
1083 1103 committing subrepository subrepo-2
1084 1104 $ hg st subrepo-2/file
1085 1105
1086 1106 Check that share works with subrepo
1087 1107 $ hg --config extensions.share= share . ../shared
1088 1108 updating working directory
1089 1109 cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
1090 1110 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1091 1111 $ test -f ../shared/subrepo-1/.hg/sharedpath
1092 1112 [1]
1093 1113 $ hg -R ../shared in
1094 1114 abort: repository default not found!
1095 1115 [255]
1096 1116 $ hg -R ../shared/subrepo-2 showconfig paths
1097 1117 paths.default=$TESTTMP/subrepo-status/subrepo-2
1098 1118 $ hg -R ../shared/subrepo-1 sum --remote
1099 1119 parent: -1:000000000000 tip (empty repository)
1100 1120 branch: default
1101 1121 commit: (clean)
1102 1122 update: (current)
1103 1123 remote: (synced)
1104 1124
1105 1125 Check hg update --clean
1106 1126 $ cd $TESTTMP/t
1107 1127 $ rm -r t/t.orig
1108 1128 $ hg status -S --all
1109 1129 C .hgsub
1110 1130 C .hgsubstate
1111 1131 C a
1112 1132 C s/.hgsub
1113 1133 C s/.hgsubstate
1114 1134 C s/a
1115 1135 C s/ss/a
1116 1136 C t/t
1117 1137 $ echo c1 > s/a
1118 1138 $ cd s
1119 1139 $ echo c1 > b
1120 1140 $ echo c1 > c
1121 1141 $ hg add b
1122 1142 $ cd ..
1123 1143 $ hg status -S
1124 1144 M s/a
1125 1145 A s/b
1126 1146 ? s/c
1127 1147 $ hg update -C
1128 1148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1129 1149 $ hg status -S
1130 1150 ? s/b
1131 1151 ? s/c
1132 1152
1133 1153 Sticky subrepositories, no changes
1134 1154 $ cd $TESTTMP/t
1135 1155 $ hg id
1136 1156 925c17564ef8 tip
1137 1157 $ hg -R s id
1138 1158 12a213df6fa9 tip
1139 1159 $ hg -R t id
1140 1160 52c0adc0515a tip
1141 1161 $ hg update 11
1142 1162 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1143 1163 $ hg id
1144 1164 365661e5936a
1145 1165 $ hg -R s id
1146 1166 fc627a69481f
1147 1167 $ hg -R t id
1148 1168 e95bcfa18a35
1149 1169
1150 1170 Sticky subrepositories, file changes
1151 1171 $ touch s/f1
1152 1172 $ touch t/f1
1153 1173 $ hg add -S s/f1
1154 1174 $ hg add -S t/f1
1155 1175 $ hg id
1156 1176 365661e5936a+
1157 1177 $ hg -R s id
1158 1178 fc627a69481f+
1159 1179 $ hg -R t id
1160 1180 e95bcfa18a35+
1161 1181 $ hg update tip
1162 1182 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
1163 1183 (M)erge, keep (l)ocal or keep (r)emote? m
1164 1184 subrepository sources for s differ
1165 1185 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
1166 1186 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
1167 1187 (M)erge, keep (l)ocal or keep (r)emote? m
1168 1188 subrepository sources for t differ
1169 1189 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
1170 1190 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1171 1191 $ hg id
1172 1192 925c17564ef8+ tip
1173 1193 $ hg -R s id
1174 1194 fc627a69481f+
1175 1195 $ hg -R t id
1176 1196 e95bcfa18a35+
1177 1197 $ hg update --clean tip
1178 1198 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1179 1199
1180 1200 Sticky subrepository, revision updates
1181 1201 $ hg id
1182 1202 925c17564ef8 tip
1183 1203 $ hg -R s id
1184 1204 12a213df6fa9 tip
1185 1205 $ hg -R t id
1186 1206 52c0adc0515a tip
1187 1207 $ cd s
1188 1208 $ hg update -r -2
1189 1209 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1190 1210 $ cd ../t
1191 1211 $ hg update -r 2
1192 1212 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1193 1213 $ cd ..
1194 1214 $ hg update 10
1195 1215 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1196 1216 (M)erge, keep (l)ocal or keep (r)emote? m
1197 1217 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1198 1218 (M)erge, keep (l)ocal or keep (r)emote? m
1199 1219 subrepository sources for t differ (in checked out version)
1200 1220 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1201 1221 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1202 1222 $ hg id
1203 1223 e45c8b14af55+
1204 1224 $ hg -R s id
1205 1225 02dcf1d70411
1206 1226 $ hg -R t id
1207 1227 7af322bc1198
1208 1228
1209 1229 Sticky subrepository, file changes and revision updates
1210 1230 $ touch s/f1
1211 1231 $ touch t/f1
1212 1232 $ hg add -S s/f1
1213 1233 $ hg add -S t/f1
1214 1234 $ hg id
1215 1235 e45c8b14af55+
1216 1236 $ hg -R s id
1217 1237 02dcf1d70411+
1218 1238 $ hg -R t id
1219 1239 7af322bc1198+
1220 1240 $ hg update tip
1221 1241 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1222 1242 (M)erge, keep (l)ocal or keep (r)emote? m
1223 1243 subrepository sources for s differ
1224 1244 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1225 1245 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1226 1246 (M)erge, keep (l)ocal or keep (r)emote? m
1227 1247 subrepository sources for t differ
1228 1248 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1229 1249 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1230 1250 $ hg id
1231 1251 925c17564ef8+ tip
1232 1252 $ hg -R s id
1233 1253 02dcf1d70411+
1234 1254 $ hg -R t id
1235 1255 7af322bc1198+
1236 1256
1237 1257 Sticky repository, update --clean
1238 1258 $ hg update --clean tip
1239 1259 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1240 1260 $ hg id
1241 1261 925c17564ef8 tip
1242 1262 $ hg -R s id
1243 1263 12a213df6fa9 tip
1244 1264 $ hg -R t id
1245 1265 52c0adc0515a tip
1246 1266
1247 1267 Test subrepo already at intended revision:
1248 1268 $ cd s
1249 1269 $ hg update fc627a69481f
1250 1270 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1251 1271 $ cd ..
1252 1272 $ hg update 11
1253 1273 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1254 1274 (M)erge, keep (l)ocal or keep (r)emote? m
1255 1275 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1256 1276 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1257 1277 $ hg id -n
1258 1278 11+
1259 1279 $ hg -R s id
1260 1280 fc627a69481f
1261 1281 $ hg -R t id
1262 1282 e95bcfa18a35
1263 1283
1264 1284 Test that removing .hgsubstate doesn't break anything:
1265 1285
1266 1286 $ hg rm -f .hgsubstate
1267 1287 $ hg ci -mrm
1268 1288 nothing changed
1269 1289 [1]
1270 1290 $ hg log -vr tip
1271 1291 changeset: 13:925c17564ef8
1272 1292 tag: tip
1273 1293 user: test
1274 1294 date: Thu Jan 01 00:00:00 1970 +0000
1275 1295 files: .hgsubstate
1276 1296 description:
1277 1297 13
1278 1298
1279 1299
1280 1300
1281 1301 Test that removing .hgsub removes .hgsubstate:
1282 1302
1283 1303 $ hg rm .hgsub
1284 1304 $ hg ci -mrm2
1285 1305 created new head
1286 1306 $ hg log -vr tip
1287 1307 changeset: 14:2400bccd50af
1288 1308 tag: tip
1289 1309 parent: 11:365661e5936a
1290 1310 user: test
1291 1311 date: Thu Jan 01 00:00:00 1970 +0000
1292 1312 files: .hgsub .hgsubstate
1293 1313 description:
1294 1314 rm2
1295 1315
1296 1316
1297 1317 Test issue3153: diff -S with deleted subrepos
1298 1318
1299 1319 $ hg diff --nodates -S -c .
1300 1320 diff -r 365661e5936a -r 2400bccd50af .hgsub
1301 1321 --- a/.hgsub
1302 1322 +++ /dev/null
1303 1323 @@ -1,2 +0,0 @@
1304 1324 -s = s
1305 1325 -t = t
1306 1326 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1307 1327 --- a/.hgsubstate
1308 1328 +++ /dev/null
1309 1329 @@ -1,2 +0,0 @@
1310 1330 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1311 1331 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1312 1332
1313 1333 Test behavior of add for explicit path in subrepo:
1314 1334 $ cd ..
1315 1335 $ hg init explicit
1316 1336 $ cd explicit
1317 1337 $ echo s = s > .hgsub
1318 1338 $ hg add .hgsub
1319 1339 $ hg init s
1320 1340 $ hg ci -m0
1321 1341 Adding with an explicit path in a subrepo adds the file
1322 1342 $ echo c1 > f1
1323 1343 $ echo c2 > s/f2
1324 1344 $ hg st -S
1325 1345 ? f1
1326 1346 ? s/f2
1327 1347 $ hg add s/f2
1328 1348 $ hg st -S
1329 1349 A s/f2
1330 1350 ? f1
1331 1351 $ hg ci -R s -m0
1332 1352 $ hg ci -Am1
1333 1353 adding f1
1334 1354 Adding with an explicit path in a subrepo with -S has the same behavior
1335 1355 $ echo c3 > f3
1336 1356 $ echo c4 > s/f4
1337 1357 $ hg st -S
1338 1358 ? f3
1339 1359 ? s/f4
1340 1360 $ hg add -S s/f4
1341 1361 $ hg st -S
1342 1362 A s/f4
1343 1363 ? f3
1344 1364 $ hg ci -R s -m1
1345 1365 $ hg ci -Ama2
1346 1366 adding f3
1347 1367 Adding without a path or pattern silently ignores subrepos
1348 1368 $ echo c5 > f5
1349 1369 $ echo c6 > s/f6
1350 1370 $ echo c7 > s/f7
1351 1371 $ hg st -S
1352 1372 ? f5
1353 1373 ? s/f6
1354 1374 ? s/f7
1355 1375 $ hg add
1356 1376 adding f5
1357 1377 $ hg st -S
1358 1378 A f5
1359 1379 ? s/f6
1360 1380 ? s/f7
1361 1381 $ hg ci -R s -Am2
1362 1382 adding f6
1363 1383 adding f7
1364 1384 $ hg ci -m3
1365 1385 Adding without a path or pattern with -S also adds files in subrepos
1366 1386 $ echo c8 > f8
1367 1387 $ echo c9 > s/f9
1368 1388 $ echo c10 > s/f10
1369 1389 $ hg st -S
1370 1390 ? f8
1371 1391 ? s/f10
1372 1392 ? s/f9
1373 1393 $ hg add -S
1374 1394 adding f8
1375 1395 adding s/f10 (glob)
1376 1396 adding s/f9 (glob)
1377 1397 $ hg st -S
1378 1398 A f8
1379 1399 A s/f10
1380 1400 A s/f9
1381 1401 $ hg ci -R s -m3
1382 1402 $ hg ci -m4
1383 1403 Adding with a pattern silently ignores subrepos
1384 1404 $ echo c11 > fm11
1385 1405 $ echo c12 > fn12
1386 1406 $ echo c13 > s/fm13
1387 1407 $ echo c14 > s/fn14
1388 1408 $ hg st -S
1389 1409 ? fm11
1390 1410 ? fn12
1391 1411 ? s/fm13
1392 1412 ? s/fn14
1393 1413 $ hg add 'glob:**fm*'
1394 1414 adding fm11
1395 1415 $ hg st -S
1396 1416 A fm11
1397 1417 ? fn12
1398 1418 ? s/fm13
1399 1419 ? s/fn14
1400 1420 $ hg ci -R s -Am4
1401 1421 adding fm13
1402 1422 adding fn14
1403 1423 $ hg ci -Am5
1404 1424 adding fn12
1405 1425 Adding with a pattern with -S also adds matches in subrepos
1406 1426 $ echo c15 > fm15
1407 1427 $ echo c16 > fn16
1408 1428 $ echo c17 > s/fm17
1409 1429 $ echo c18 > s/fn18
1410 1430 $ hg st -S
1411 1431 ? fm15
1412 1432 ? fn16
1413 1433 ? s/fm17
1414 1434 ? s/fn18
1415 1435 $ hg add -S 'glob:**fm*'
1416 1436 adding fm15
1417 1437 adding s/fm17 (glob)
1418 1438 $ hg st -S
1419 1439 A fm15
1420 1440 A s/fm17
1421 1441 ? fn16
1422 1442 ? s/fn18
1423 1443 $ hg ci -R s -Am5
1424 1444 adding fn18
1425 1445 $ hg ci -Am6
1426 1446 adding fn16
1427 1447
1428 1448 Test behavior of forget for explicit path in subrepo:
1429 1449 Forgetting an explicit path in a subrepo untracks the file
1430 1450 $ echo c19 > s/f19
1431 1451 $ hg add s/f19
1432 1452 $ hg st -S
1433 1453 A s/f19
1434 1454 $ hg forget s/f19
1435 1455 $ hg st -S
1436 1456 ? s/f19
1437 1457 $ rm s/f19
1438 1458 $ cd ..
1439 1459
1440 1460 Courtesy phases synchronisation to publishing server does not block the push
1441 1461 (issue3781)
1442 1462
1443 1463 $ cp -r main issue3781
1444 1464 $ cp -r main issue3781-dest
1445 1465 $ cd issue3781-dest/s
1446 1466 $ hg phase tip # show we have draft changeset
1447 1467 5: draft
1448 1468 $ chmod a-w .hg/store/phaseroots # prevent phase push
1449 1469 $ cd ../../issue3781
1450 1470 $ cat >> .hg/hgrc << EOF
1451 1471 > [paths]
1452 1472 > default=../issue3781-dest/
1453 1473 > EOF
1454 1474 $ hg push
1455 1475 pushing to $TESTTMP/issue3781-dest (glob)
1456 1476 pushing subrepo s to $TESTTMP/issue3781-dest/s
1457 1477 searching for changes
1458 1478 no changes found
1459 1479 searching for changes
1460 1480 no changes found
1461 1481 [1]
1462 1482 $ cd ..
1463 1483
1464 1484 Test phase choice for newly created commit with "phases.subrepochecks"
1465 1485 configuration
1466 1486
1467 1487 $ cd t
1468 1488 $ hg update -q -r 12
1469 1489
1470 1490 $ cat >> s/ss/.hg/hgrc <<EOF
1471 1491 > [phases]
1472 1492 > new-commit = secret
1473 1493 > EOF
1474 1494 $ cat >> s/.hg/hgrc <<EOF
1475 1495 > [phases]
1476 1496 > new-commit = draft
1477 1497 > EOF
1478 1498 $ echo phasecheck1 >> s/ss/a
1479 1499 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1480 1500 committing subrepository ss
1481 1501 transaction abort!
1482 1502 rollback completed
1483 1503 abort: can't commit in draft phase conflicting secret from subrepository ss
1484 1504 [255]
1485 1505 $ echo phasecheck2 >> s/ss/a
1486 1506 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1487 1507 committing subrepository ss
1488 1508 $ hg -R s/ss phase tip
1489 1509 3: secret
1490 1510 $ hg -R s phase tip
1491 1511 6: draft
1492 1512 $ echo phasecheck3 >> s/ss/a
1493 1513 $ hg -R s commit -S -m phasecheck3
1494 1514 committing subrepository ss
1495 1515 warning: changes are committed in secret phase from subrepository ss
1496 1516 $ hg -R s/ss phase tip
1497 1517 4: secret
1498 1518 $ hg -R s phase tip
1499 1519 7: secret
1500 1520
1501 1521 $ cat >> t/.hg/hgrc <<EOF
1502 1522 > [phases]
1503 1523 > new-commit = draft
1504 1524 > EOF
1505 1525 $ cat >> .hg/hgrc <<EOF
1506 1526 > [phases]
1507 1527 > new-commit = public
1508 1528 > EOF
1509 1529 $ echo phasecheck4 >> s/ss/a
1510 1530 $ echo phasecheck4 >> t/t
1511 1531 $ hg commit -S -m phasecheck4
1512 1532 committing subrepository s
1513 1533 committing subrepository s/ss (glob)
1514 1534 warning: changes are committed in secret phase from subrepository ss
1515 1535 committing subrepository t
1516 1536 warning: changes are committed in secret phase from subrepository s
1517 1537 created new head
1518 1538 $ hg -R s/ss phase tip
1519 1539 5: secret
1520 1540 $ hg -R s phase tip
1521 1541 8: secret
1522 1542 $ hg -R t phase tip
1523 1543 6: draft
1524 1544 $ hg phase tip
1525 1545 15: secret
1526 1546
1527 1547 $ cd ..
1528 1548
1529 1549
1530 1550 Test that commit --secret works on both repo and subrepo (issue4182)
1531 1551
1532 1552 $ cd main
1533 1553 $ echo secret >> b
1534 1554 $ echo secret >> s/b
1535 1555 $ hg commit --secret --subrepo -m "secret"
1536 1556 committing subrepository s
1537 1557 $ hg phase -r .
1538 1558 6: secret
1539 1559 $ cd s
1540 1560 $ hg phase -r .
1541 1561 6: secret
1542 1562 $ cd ../../
1543 1563
1544 1564 Test "subrepos" template keyword
1545 1565
1546 1566 $ cd t
1547 1567 $ hg update -q 15
1548 1568 $ cat > .hgsub <<EOF
1549 1569 > s = s
1550 1570 > EOF
1551 1571 $ hg commit -m "16"
1552 1572 warning: changes are committed in secret phase from subrepository s
1553 1573
1554 1574 (addition of ".hgsub" itself)
1555 1575
1556 1576 $ hg diff --nodates -c 1 .hgsubstate
1557 1577 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1558 1578 --- /dev/null
1559 1579 +++ b/.hgsubstate
1560 1580 @@ -0,0 +1,1 @@
1561 1581 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1562 1582 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1563 1583 f7b1eb17ad24 000000000000
1564 1584 s
1565 1585
1566 1586 (modification of existing entry)
1567 1587
1568 1588 $ hg diff --nodates -c 2 .hgsubstate
1569 1589 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1570 1590 --- a/.hgsubstate
1571 1591 +++ b/.hgsubstate
1572 1592 @@ -1,1 +1,1 @@
1573 1593 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1574 1594 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1575 1595 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1576 1596 7cf8cfea66e4 000000000000
1577 1597 s
1578 1598
1579 1599 (addition of entry)
1580 1600
1581 1601 $ hg diff --nodates -c 5 .hgsubstate
1582 1602 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1583 1603 --- a/.hgsubstate
1584 1604 +++ b/.hgsubstate
1585 1605 @@ -1,1 +1,2 @@
1586 1606 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1587 1607 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1588 1608 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1589 1609 7cf8cfea66e4 000000000000
1590 1610 t
1591 1611
1592 1612 (removal of existing entry)
1593 1613
1594 1614 $ hg diff --nodates -c 16 .hgsubstate
1595 1615 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1596 1616 --- a/.hgsubstate
1597 1617 +++ b/.hgsubstate
1598 1618 @@ -1,2 +1,1 @@
1599 1619 0731af8ca9423976d3743119d0865097c07bdc1b s
1600 1620 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1601 1621 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1602 1622 8bec38d2bd0b 000000000000
1603 1623 t
1604 1624
1605 1625 (merging)
1606 1626
1607 1627 $ hg diff --nodates -c 9 .hgsubstate
1608 1628 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1609 1629 --- a/.hgsubstate
1610 1630 +++ b/.hgsubstate
1611 1631 @@ -1,1 +1,2 @@
1612 1632 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1613 1633 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1614 1634 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1615 1635 f6affe3fbfaa 1f14a2e2d3ec
1616 1636 t
1617 1637
1618 1638 (removal of ".hgsub" itself)
1619 1639
1620 1640 $ hg diff --nodates -c 8 .hgsubstate
1621 1641 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1622 1642 --- a/.hgsubstate
1623 1643 +++ /dev/null
1624 1644 @@ -1,2 +0,0 @@
1625 1645 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1626 1646 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1627 1647 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1628 1648 f94576341bcf 000000000000
1629 1649
1630 1650 Test that '[paths]' is configured correctly at subrepo creation
1631 1651
1632 1652 $ cd $TESTTMP/tc
1633 1653 $ cat > .hgsub <<EOF
1634 1654 > # to clear bogus subrepo path 'bogus=[boguspath'
1635 1655 > s = s
1636 1656 > t = t
1637 1657 > EOF
1638 1658 $ hg update -q --clean null
1639 1659 $ rm -rf s t
1640 1660 $ cat >> .hg/hgrc <<EOF
1641 1661 > [paths]
1642 1662 > default-push = /foo/bar
1643 1663 > EOF
1644 1664 $ hg update -q
1645 1665 $ cat s/.hg/hgrc
1646 1666 [paths]
1647 1667 default = $TESTTMP/t/s
1648 1668 default-push = /foo/bar/s
1649 1669 $ cat s/ss/.hg/hgrc
1650 1670 [paths]
1651 1671 default = $TESTTMP/t/s/ss
1652 1672 default-push = /foo/bar/s/ss
1653 1673 $ cat t/.hg/hgrc
1654 1674 [paths]
1655 1675 default = $TESTTMP/t/t
1656 1676 default-push = /foo/bar/t
1657 1677 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now