##// END OF EJS Templates
archive: always use portable path component separators with subrepos...
Matt Harbison -
r24924:41cd8171 stable
parent child Browse files
Show More
@@ -1,1840 +1,1839 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 archiver.addfile(self.wvfs.reljoin(prefix, self._path, name),
520 archiver.addfile(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
573 573 # Propagate the parent's --hidden option
574 574 if r is r.unfiltered():
575 575 self._repo = self._repo.unfiltered()
576 576
577 577 self.ui = self._repo.ui
578 578 for s, k in [('ui', 'commitsubrepos')]:
579 579 v = r.ui.config(s, k)
580 580 if v:
581 581 self.ui.setconfig(s, k, v, 'subrepo')
582 582 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
583 583 self._initrepo(r, state[0], create)
584 584
585 585 def storeclean(self, path):
586 586 lock = self._repo.lock()
587 587 try:
588 588 return self._storeclean(path)
589 589 finally:
590 590 lock.release()
591 591
592 592 def _storeclean(self, path):
593 593 clean = True
594 594 itercache = self._calcstorehash(path)
595 595 try:
596 596 for filehash in self._readstorehashcache(path):
597 597 if filehash != itercache.next():
598 598 clean = False
599 599 break
600 600 except StopIteration:
601 601 # the cached and current pull states have a different size
602 602 clean = False
603 603 if clean:
604 604 try:
605 605 itercache.next()
606 606 # the cached and current pull states have a different size
607 607 clean = False
608 608 except StopIteration:
609 609 pass
610 610 return clean
611 611
612 612 def _calcstorehash(self, remotepath):
613 613 '''calculate a unique "store hash"
614 614
615 615 This method is used to to detect when there are changes that may
616 616 require a push to a given remote path.'''
617 617 # sort the files that will be hashed in increasing (likely) file size
618 618 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
619 619 yield '# %s\n' % _expandedabspath(remotepath)
620 620 vfs = self._repo.vfs
621 621 for relname in filelist:
622 622 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
623 623 yield '%s = %s\n' % (relname, filehash)
624 624
625 625 @propertycache
626 626 def _cachestorehashvfs(self):
627 627 return scmutil.vfs(self._repo.join('cache/storehash'))
628 628
629 629 def _readstorehashcache(self, remotepath):
630 630 '''read the store hash cache for a given remote repository'''
631 631 cachefile = _getstorehashcachename(remotepath)
632 632 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
633 633
634 634 def _cachestorehash(self, remotepath):
635 635 '''cache the current store hash
636 636
637 637 Each remote repo requires its own store hash cache, because a subrepo
638 638 store may be "clean" versus a given remote repo, but not versus another
639 639 '''
640 640 cachefile = _getstorehashcachename(remotepath)
641 641 lock = self._repo.lock()
642 642 try:
643 643 storehash = list(self._calcstorehash(remotepath))
644 644 vfs = self._cachestorehashvfs
645 645 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
646 646 finally:
647 647 lock.release()
648 648
649 649 @annotatesubrepoerror
650 650 def _initrepo(self, parentrepo, source, create):
651 651 self._repo._subparent = parentrepo
652 652 self._repo._subsource = source
653 653
654 654 if create:
655 655 lines = ['[paths]\n']
656 656
657 657 def addpathconfig(key, value):
658 658 if value:
659 659 lines.append('%s = %s\n' % (key, value))
660 660 self.ui.setconfig('paths', key, value, 'subrepo')
661 661
662 662 defpath = _abssource(self._repo, abort=False)
663 663 defpushpath = _abssource(self._repo, True, abort=False)
664 664 addpathconfig('default', defpath)
665 665 if defpath != defpushpath:
666 666 addpathconfig('default-push', defpushpath)
667 667
668 668 fp = self._repo.vfs("hgrc", "w", text=True)
669 669 try:
670 670 fp.write(''.join(lines))
671 671 finally:
672 672 fp.close()
673 673
674 674 @annotatesubrepoerror
675 675 def add(self, ui, match, prefix, explicitonly, **opts):
676 676 return cmdutil.add(ui, self._repo, match,
677 677 self.wvfs.reljoin(prefix, self._path),
678 678 explicitonly, **opts)
679 679
680 680 @annotatesubrepoerror
681 681 def addremove(self, m, prefix, opts, dry_run, similarity):
682 682 # In the same way as sub directories are processed, once in a subrepo,
683 683 # always entry any of its subrepos. Don't corrupt the options that will
684 684 # be used to process sibling subrepos however.
685 685 opts = copy.copy(opts)
686 686 opts['subrepos'] = True
687 687 return scmutil.addremove(self._repo, m,
688 688 self.wvfs.reljoin(prefix, self._path), opts,
689 689 dry_run, similarity)
690 690
691 691 @annotatesubrepoerror
692 692 def cat(self, match, prefix, **opts):
693 693 rev = self._state[1]
694 694 ctx = self._repo[rev]
695 695 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
696 696
697 697 @annotatesubrepoerror
698 698 def status(self, rev2, **opts):
699 699 try:
700 700 rev1 = self._state[1]
701 701 ctx1 = self._repo[rev1]
702 702 ctx2 = self._repo[rev2]
703 703 return self._repo.status(ctx1, ctx2, **opts)
704 704 except error.RepoLookupError, inst:
705 705 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
706 706 % (inst, subrelpath(self)))
707 707 return scmutil.status([], [], [], [], [], [], [])
708 708
709 709 @annotatesubrepoerror
710 710 def diff(self, ui, diffopts, node2, match, prefix, **opts):
711 711 try:
712 712 node1 = node.bin(self._state[1])
713 713 # We currently expect node2 to come from substate and be
714 714 # in hex format
715 715 if node2 is not None:
716 716 node2 = node.bin(node2)
717 717 cmdutil.diffordiffstat(ui, self._repo, diffopts,
718 718 node1, node2, match,
719 719 prefix=posixpath.join(prefix, self._path),
720 720 listsubrepos=True, **opts)
721 721 except error.RepoLookupError, inst:
722 722 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
723 723 % (inst, subrelpath(self)))
724 724
725 725 @annotatesubrepoerror
726 726 def archive(self, archiver, prefix, match=None):
727 727 self._get(self._state + ('hg',))
728 728 total = abstractsubrepo.archive(self, archiver, prefix, match)
729 729 rev = self._state[1]
730 730 ctx = self._repo[rev]
731 731 for subpath in ctx.substate:
732 732 s = subrepo(ctx, subpath)
733 733 submatch = matchmod.narrowmatcher(subpath, match)
734 total += s.archive(
735 archiver, self.wvfs.reljoin(prefix, self._path), submatch)
734 total += s.archive(archiver, prefix + self._path + '/', submatch)
736 735 return total
737 736
738 737 @annotatesubrepoerror
739 738 def dirty(self, ignoreupdate=False):
740 739 r = self._state[1]
741 740 if r == '' and not ignoreupdate: # no state recorded
742 741 return True
743 742 w = self._repo[None]
744 743 if r != w.p1().hex() and not ignoreupdate:
745 744 # different version checked out
746 745 return True
747 746 return w.dirty() # working directory changed
748 747
749 748 def basestate(self):
750 749 return self._repo['.'].hex()
751 750
752 751 def checknested(self, path):
753 752 return self._repo._checknested(self._repo.wjoin(path))
754 753
755 754 @annotatesubrepoerror
756 755 def commit(self, text, user, date):
757 756 # don't bother committing in the subrepo if it's only been
758 757 # updated
759 758 if not self.dirty(True):
760 759 return self._repo['.'].hex()
761 760 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
762 761 n = self._repo.commit(text, user, date)
763 762 if not n:
764 763 return self._repo['.'].hex() # different version checked out
765 764 return node.hex(n)
766 765
767 766 @annotatesubrepoerror
768 767 def phase(self, state):
769 768 return self._repo[state].phase()
770 769
771 770 @annotatesubrepoerror
772 771 def remove(self):
773 772 # we can't fully delete the repository as it may contain
774 773 # local-only history
775 774 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
776 775 hg.clean(self._repo, node.nullid, False)
777 776
778 777 def _get(self, state):
779 778 source, revision, kind = state
780 779 if revision in self._repo.unfiltered():
781 780 return True
782 781 self._repo._subsource = source
783 782 srcurl = _abssource(self._repo)
784 783 other = hg.peer(self._repo, {}, srcurl)
785 784 if len(self._repo) == 0:
786 785 self.ui.status(_('cloning subrepo %s from %s\n')
787 786 % (subrelpath(self), srcurl))
788 787 parentrepo = self._repo._subparent
789 788 # use self._repo.vfs instead of self.wvfs to remove .hg only
790 789 self._repo.vfs.rmtree()
791 790 other, cloned = hg.clone(self._repo._subparent.baseui, {},
792 791 other, self._repo.root,
793 792 update=False)
794 793 self._repo = cloned.local()
795 794 self._initrepo(parentrepo, source, create=True)
796 795 self._cachestorehash(srcurl)
797 796 else:
798 797 self.ui.status(_('pulling subrepo %s from %s\n')
799 798 % (subrelpath(self), srcurl))
800 799 cleansub = self.storeclean(srcurl)
801 800 exchange.pull(self._repo, other)
802 801 if cleansub:
803 802 # keep the repo clean after pull
804 803 self._cachestorehash(srcurl)
805 804 return False
806 805
807 806 @annotatesubrepoerror
808 807 def get(self, state, overwrite=False):
809 808 inrepo = self._get(state)
810 809 source, revision, kind = state
811 810 repo = self._repo
812 811 repo.ui.debug("getting subrepo %s\n" % self._path)
813 812 if inrepo:
814 813 urepo = repo.unfiltered()
815 814 ctx = urepo[revision]
816 815 if ctx.hidden():
817 816 urepo.ui.warn(
818 817 _('revision %s in subrepo %s is hidden\n') \
819 818 % (revision[0:12], self._path))
820 819 repo = urepo
821 820 hg.updaterepo(repo, revision, overwrite)
822 821
823 822 @annotatesubrepoerror
824 823 def merge(self, state):
825 824 self._get(state)
826 825 cur = self._repo['.']
827 826 dst = self._repo[state[1]]
828 827 anc = dst.ancestor(cur)
829 828
830 829 def mergefunc():
831 830 if anc == cur and dst.branch() == cur.branch():
832 831 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
833 832 hg.update(self._repo, state[1])
834 833 elif anc == dst:
835 834 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
836 835 else:
837 836 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
838 837 hg.merge(self._repo, state[1], remind=False)
839 838
840 839 wctx = self._repo[None]
841 840 if self.dirty():
842 841 if anc != dst:
843 842 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
844 843 mergefunc()
845 844 else:
846 845 mergefunc()
847 846 else:
848 847 mergefunc()
849 848
850 849 @annotatesubrepoerror
851 850 def push(self, opts):
852 851 force = opts.get('force')
853 852 newbranch = opts.get('new_branch')
854 853 ssh = opts.get('ssh')
855 854
856 855 # push subrepos depth-first for coherent ordering
857 856 c = self._repo['']
858 857 subs = c.substate # only repos that are committed
859 858 for s in sorted(subs):
860 859 if c.sub(s).push(opts) == 0:
861 860 return False
862 861
863 862 dsturl = _abssource(self._repo, True)
864 863 if not force:
865 864 if self.storeclean(dsturl):
866 865 self.ui.status(
867 866 _('no changes made to subrepo %s since last push to %s\n')
868 867 % (subrelpath(self), dsturl))
869 868 return None
870 869 self.ui.status(_('pushing subrepo %s to %s\n') %
871 870 (subrelpath(self), dsturl))
872 871 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
873 872 res = exchange.push(self._repo, other, force, newbranch=newbranch)
874 873
875 874 # the repo is now clean
876 875 self._cachestorehash(dsturl)
877 876 return res.cgresult
878 877
879 878 @annotatesubrepoerror
880 879 def outgoing(self, ui, dest, opts):
881 880 if 'rev' in opts or 'branch' in opts:
882 881 opts = copy.copy(opts)
883 882 opts.pop('rev', None)
884 883 opts.pop('branch', None)
885 884 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
886 885
887 886 @annotatesubrepoerror
888 887 def incoming(self, ui, source, opts):
889 888 if 'rev' in opts or 'branch' in opts:
890 889 opts = copy.copy(opts)
891 890 opts.pop('rev', None)
892 891 opts.pop('branch', None)
893 892 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
894 893
895 894 @annotatesubrepoerror
896 895 def files(self):
897 896 rev = self._state[1]
898 897 ctx = self._repo[rev]
899 898 return ctx.manifest().keys()
900 899
901 900 def filedata(self, name):
902 901 rev = self._state[1]
903 902 return self._repo[rev][name].data()
904 903
905 904 def fileflags(self, name):
906 905 rev = self._state[1]
907 906 ctx = self._repo[rev]
908 907 return ctx.flags(name)
909 908
910 909 @annotatesubrepoerror
911 910 def printfiles(self, ui, m, fm, fmt):
912 911 # If the parent context is a workingctx, use the workingctx here for
913 912 # consistency.
914 913 if self._ctx.rev() is None:
915 914 ctx = self._repo[None]
916 915 else:
917 916 rev = self._state[1]
918 917 ctx = self._repo[rev]
919 918 return cmdutil.files(ui, ctx, m, fm, fmt, True)
920 919
921 920 def walk(self, match):
922 921 ctx = self._repo[None]
923 922 return ctx.walk(match)
924 923
925 924 @annotatesubrepoerror
926 925 def forget(self, match, prefix):
927 926 return cmdutil.forget(self.ui, self._repo, match,
928 927 self.wvfs.reljoin(prefix, self._path), True)
929 928
930 929 @annotatesubrepoerror
931 930 def removefiles(self, matcher, prefix, after, force, subrepos):
932 931 return cmdutil.remove(self.ui, self._repo, matcher,
933 932 self.wvfs.reljoin(prefix, self._path),
934 933 after, force, subrepos)
935 934
936 935 @annotatesubrepoerror
937 936 def revert(self, substate, *pats, **opts):
938 937 # reverting a subrepo is a 2 step process:
939 938 # 1. if the no_backup is not set, revert all modified
940 939 # files inside the subrepo
941 940 # 2. update the subrepo to the revision specified in
942 941 # the corresponding substate dictionary
943 942 self.ui.status(_('reverting subrepo %s\n') % substate[0])
944 943 if not opts.get('no_backup'):
945 944 # Revert all files on the subrepo, creating backups
946 945 # Note that this will not recursively revert subrepos
947 946 # We could do it if there was a set:subrepos() predicate
948 947 opts = opts.copy()
949 948 opts['date'] = None
950 949 opts['rev'] = substate[1]
951 950
952 951 self.filerevert(*pats, **opts)
953 952
954 953 # Update the repo to the revision specified in the given substate
955 954 if not opts.get('dry_run'):
956 955 self.get(substate, overwrite=True)
957 956
958 957 def filerevert(self, *pats, **opts):
959 958 ctx = self._repo[opts['rev']]
960 959 parents = self._repo.dirstate.parents()
961 960 if opts.get('all'):
962 961 pats = ['set:modified()']
963 962 else:
964 963 pats = []
965 964 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
966 965
967 966 def shortid(self, revid):
968 967 return revid[:12]
969 968
970 969 @propertycache
971 970 def wvfs(self):
972 971 """return own wvfs for efficiency and consitency
973 972 """
974 973 return self._repo.wvfs
975 974
976 975 @propertycache
977 976 def _relpath(self):
978 977 """return path to this subrepository as seen from outermost repository
979 978 """
980 979 # Keep consistent dir separators by avoiding vfs.join(self._path)
981 980 return reporelpath(self._repo)
982 981
983 982 class svnsubrepo(abstractsubrepo):
984 983 def __init__(self, ctx, path, state):
985 984 super(svnsubrepo, self).__init__(ctx, path)
986 985 self._state = state
987 986 self._exe = util.findexe('svn')
988 987 if not self._exe:
989 988 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
990 989 % self._path)
991 990
992 991 def _svncommand(self, commands, filename='', failok=False):
993 992 cmd = [self._exe]
994 993 extrakw = {}
995 994 if not self.ui.interactive():
996 995 # Making stdin be a pipe should prevent svn from behaving
997 996 # interactively even if we can't pass --non-interactive.
998 997 extrakw['stdin'] = subprocess.PIPE
999 998 # Starting in svn 1.5 --non-interactive is a global flag
1000 999 # instead of being per-command, but we need to support 1.4 so
1001 1000 # we have to be intelligent about what commands take
1002 1001 # --non-interactive.
1003 1002 if commands[0] in ('update', 'checkout', 'commit'):
1004 1003 cmd.append('--non-interactive')
1005 1004 cmd.extend(commands)
1006 1005 if filename is not None:
1007 1006 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1008 1007 self._path, filename)
1009 1008 cmd.append(path)
1010 1009 env = dict(os.environ)
1011 1010 # Avoid localized output, preserve current locale for everything else.
1012 1011 lc_all = env.get('LC_ALL')
1013 1012 if lc_all:
1014 1013 env['LANG'] = lc_all
1015 1014 del env['LC_ALL']
1016 1015 env['LC_MESSAGES'] = 'C'
1017 1016 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1018 1017 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1019 1018 universal_newlines=True, env=env, **extrakw)
1020 1019 stdout, stderr = p.communicate()
1021 1020 stderr = stderr.strip()
1022 1021 if not failok:
1023 1022 if p.returncode:
1024 1023 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
1025 1024 if stderr:
1026 1025 self.ui.warn(stderr + '\n')
1027 1026 return stdout, stderr
1028 1027
1029 1028 @propertycache
1030 1029 def _svnversion(self):
1031 1030 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1032 1031 m = re.search(r'^(\d+)\.(\d+)', output)
1033 1032 if not m:
1034 1033 raise util.Abort(_('cannot retrieve svn tool version'))
1035 1034 return (int(m.group(1)), int(m.group(2)))
1036 1035
1037 1036 def _wcrevs(self):
1038 1037 # Get the working directory revision as well as the last
1039 1038 # commit revision so we can compare the subrepo state with
1040 1039 # both. We used to store the working directory one.
1041 1040 output, err = self._svncommand(['info', '--xml'])
1042 1041 doc = xml.dom.minidom.parseString(output)
1043 1042 entries = doc.getElementsByTagName('entry')
1044 1043 lastrev, rev = '0', '0'
1045 1044 if entries:
1046 1045 rev = str(entries[0].getAttribute('revision')) or '0'
1047 1046 commits = entries[0].getElementsByTagName('commit')
1048 1047 if commits:
1049 1048 lastrev = str(commits[0].getAttribute('revision')) or '0'
1050 1049 return (lastrev, rev)
1051 1050
1052 1051 def _wcrev(self):
1053 1052 return self._wcrevs()[0]
1054 1053
1055 1054 def _wcchanged(self):
1056 1055 """Return (changes, extchanges, missing) where changes is True
1057 1056 if the working directory was changed, extchanges is
1058 1057 True if any of these changes concern an external entry and missing
1059 1058 is True if any change is a missing entry.
1060 1059 """
1061 1060 output, err = self._svncommand(['status', '--xml'])
1062 1061 externals, changes, missing = [], [], []
1063 1062 doc = xml.dom.minidom.parseString(output)
1064 1063 for e in doc.getElementsByTagName('entry'):
1065 1064 s = e.getElementsByTagName('wc-status')
1066 1065 if not s:
1067 1066 continue
1068 1067 item = s[0].getAttribute('item')
1069 1068 props = s[0].getAttribute('props')
1070 1069 path = e.getAttribute('path')
1071 1070 if item == 'external':
1072 1071 externals.append(path)
1073 1072 elif item == 'missing':
1074 1073 missing.append(path)
1075 1074 if (item not in ('', 'normal', 'unversioned', 'external')
1076 1075 or props not in ('', 'none', 'normal')):
1077 1076 changes.append(path)
1078 1077 for path in changes:
1079 1078 for ext in externals:
1080 1079 if path == ext or path.startswith(ext + os.sep):
1081 1080 return True, True, bool(missing)
1082 1081 return bool(changes), False, bool(missing)
1083 1082
1084 1083 def dirty(self, ignoreupdate=False):
1085 1084 if not self._wcchanged()[0]:
1086 1085 if self._state[1] in self._wcrevs() or ignoreupdate:
1087 1086 return False
1088 1087 return True
1089 1088
1090 1089 def basestate(self):
1091 1090 lastrev, rev = self._wcrevs()
1092 1091 if lastrev != rev:
1093 1092 # Last committed rev is not the same than rev. We would
1094 1093 # like to take lastrev but we do not know if the subrepo
1095 1094 # URL exists at lastrev. Test it and fallback to rev it
1096 1095 # is not there.
1097 1096 try:
1098 1097 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1099 1098 return lastrev
1100 1099 except error.Abort:
1101 1100 pass
1102 1101 return rev
1103 1102
1104 1103 @annotatesubrepoerror
1105 1104 def commit(self, text, user, date):
1106 1105 # user and date are out of our hands since svn is centralized
1107 1106 changed, extchanged, missing = self._wcchanged()
1108 1107 if not changed:
1109 1108 return self.basestate()
1110 1109 if extchanged:
1111 1110 # Do not try to commit externals
1112 1111 raise util.Abort(_('cannot commit svn externals'))
1113 1112 if missing:
1114 1113 # svn can commit with missing entries but aborting like hg
1115 1114 # seems a better approach.
1116 1115 raise util.Abort(_('cannot commit missing svn entries'))
1117 1116 commitinfo, err = self._svncommand(['commit', '-m', text])
1118 1117 self.ui.status(commitinfo)
1119 1118 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1120 1119 if not newrev:
1121 1120 if not commitinfo.strip():
1122 1121 # Sometimes, our definition of "changed" differs from
1123 1122 # svn one. For instance, svn ignores missing files
1124 1123 # when committing. If there are only missing files, no
1125 1124 # commit is made, no output and no error code.
1126 1125 raise util.Abort(_('failed to commit svn changes'))
1127 1126 raise util.Abort(commitinfo.splitlines()[-1])
1128 1127 newrev = newrev.groups()[0]
1129 1128 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1130 1129 return newrev
1131 1130
1132 1131 @annotatesubrepoerror
1133 1132 def remove(self):
1134 1133 if self.dirty():
1135 1134 self.ui.warn(_('not removing repo %s because '
1136 1135 'it has changes.\n') % self._path)
1137 1136 return
1138 1137 self.ui.note(_('removing subrepo %s\n') % self._path)
1139 1138
1140 1139 self.wvfs.rmtree(forcibly=True)
1141 1140 try:
1142 1141 self._ctx.repo().wvfs.removedirs(os.path.dirname(self._path))
1143 1142 except OSError:
1144 1143 pass
1145 1144
1146 1145 @annotatesubrepoerror
1147 1146 def get(self, state, overwrite=False):
1148 1147 if overwrite:
1149 1148 self._svncommand(['revert', '--recursive'])
1150 1149 args = ['checkout']
1151 1150 if self._svnversion >= (1, 5):
1152 1151 args.append('--force')
1153 1152 # The revision must be specified at the end of the URL to properly
1154 1153 # update to a directory which has since been deleted and recreated.
1155 1154 args.append('%s@%s' % (state[0], state[1]))
1156 1155 status, err = self._svncommand(args, failok=True)
1157 1156 _sanitize(self.ui, self.wvfs, '.svn')
1158 1157 if not re.search('Checked out revision [0-9]+.', status):
1159 1158 if ('is already a working copy for a different URL' in err
1160 1159 and (self._wcchanged()[:2] == (False, False))):
1161 1160 # obstructed but clean working copy, so just blow it away.
1162 1161 self.remove()
1163 1162 self.get(state, overwrite=False)
1164 1163 return
1165 1164 raise util.Abort((status or err).splitlines()[-1])
1166 1165 self.ui.status(status)
1167 1166
1168 1167 @annotatesubrepoerror
1169 1168 def merge(self, state):
1170 1169 old = self._state[1]
1171 1170 new = state[1]
1172 1171 wcrev = self._wcrev()
1173 1172 if new != wcrev:
1174 1173 dirty = old == wcrev or self._wcchanged()[0]
1175 1174 if _updateprompt(self.ui, self, dirty, wcrev, new):
1176 1175 self.get(state, False)
1177 1176
1178 1177 def push(self, opts):
1179 1178 # push is a no-op for SVN
1180 1179 return True
1181 1180
1182 1181 @annotatesubrepoerror
1183 1182 def files(self):
1184 1183 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1185 1184 doc = xml.dom.minidom.parseString(output)
1186 1185 paths = []
1187 1186 for e in doc.getElementsByTagName('entry'):
1188 1187 kind = str(e.getAttribute('kind'))
1189 1188 if kind != 'file':
1190 1189 continue
1191 1190 name = ''.join(c.data for c
1192 1191 in e.getElementsByTagName('name')[0].childNodes
1193 1192 if c.nodeType == c.TEXT_NODE)
1194 1193 paths.append(name.encode('utf-8'))
1195 1194 return paths
1196 1195
1197 1196 def filedata(self, name):
1198 1197 return self._svncommand(['cat'], name)[0]
1199 1198
1200 1199
1201 1200 class gitsubrepo(abstractsubrepo):
1202 1201 def __init__(self, ctx, path, state):
1203 1202 super(gitsubrepo, self).__init__(ctx, path)
1204 1203 self._state = state
1205 1204 self._abspath = ctx.repo().wjoin(path)
1206 1205 self._subparent = ctx.repo()
1207 1206 self._ensuregit()
1208 1207
1209 1208 def _ensuregit(self):
1210 1209 try:
1211 1210 self._gitexecutable = 'git'
1212 1211 out, err = self._gitnodir(['--version'])
1213 1212 except OSError, e:
1214 1213 if e.errno != 2 or os.name != 'nt':
1215 1214 raise
1216 1215 self._gitexecutable = 'git.cmd'
1217 1216 out, err = self._gitnodir(['--version'])
1218 1217 versionstatus = self._checkversion(out)
1219 1218 if versionstatus == 'unknown':
1220 1219 self.ui.warn(_('cannot retrieve git version\n'))
1221 1220 elif versionstatus == 'abort':
1222 1221 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1223 1222 elif versionstatus == 'warning':
1224 1223 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1225 1224
1226 1225 @staticmethod
1227 1226 def _gitversion(out):
1228 1227 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1229 1228 if m:
1230 1229 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1231 1230
1232 1231 m = re.search(r'^git version (\d+)\.(\d+)', out)
1233 1232 if m:
1234 1233 return (int(m.group(1)), int(m.group(2)), 0)
1235 1234
1236 1235 return -1
1237 1236
1238 1237 @staticmethod
1239 1238 def _checkversion(out):
1240 1239 '''ensure git version is new enough
1241 1240
1242 1241 >>> _checkversion = gitsubrepo._checkversion
1243 1242 >>> _checkversion('git version 1.6.0')
1244 1243 'ok'
1245 1244 >>> _checkversion('git version 1.8.5')
1246 1245 'ok'
1247 1246 >>> _checkversion('git version 1.4.0')
1248 1247 'abort'
1249 1248 >>> _checkversion('git version 1.5.0')
1250 1249 'warning'
1251 1250 >>> _checkversion('git version 1.9-rc0')
1252 1251 'ok'
1253 1252 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1254 1253 'ok'
1255 1254 >>> _checkversion('git version 1.9.0.GIT')
1256 1255 'ok'
1257 1256 >>> _checkversion('git version 12345')
1258 1257 'unknown'
1259 1258 >>> _checkversion('no')
1260 1259 'unknown'
1261 1260 '''
1262 1261 version = gitsubrepo._gitversion(out)
1263 1262 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1264 1263 # despite the docstring comment. For now, error on 1.4.0, warn on
1265 1264 # 1.5.0 but attempt to continue.
1266 1265 if version == -1:
1267 1266 return 'unknown'
1268 1267 if version < (1, 5, 0):
1269 1268 return 'abort'
1270 1269 elif version < (1, 6, 0):
1271 1270 return 'warning'
1272 1271 return 'ok'
1273 1272
1274 1273 def _gitcommand(self, commands, env=None, stream=False):
1275 1274 return self._gitdir(commands, env=env, stream=stream)[0]
1276 1275
1277 1276 def _gitdir(self, commands, env=None, stream=False):
1278 1277 return self._gitnodir(commands, env=env, stream=stream,
1279 1278 cwd=self._abspath)
1280 1279
1281 1280 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1282 1281 """Calls the git command
1283 1282
1284 1283 The methods tries to call the git command. versions prior to 1.6.0
1285 1284 are not supported and very probably fail.
1286 1285 """
1287 1286 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1288 1287 # unless ui.quiet is set, print git's stderr,
1289 1288 # which is mostly progress and useful info
1290 1289 errpipe = None
1291 1290 if self.ui.quiet:
1292 1291 errpipe = open(os.devnull, 'w')
1293 1292 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1294 1293 cwd=cwd, env=env, close_fds=util.closefds,
1295 1294 stdout=subprocess.PIPE, stderr=errpipe)
1296 1295 if stream:
1297 1296 return p.stdout, None
1298 1297
1299 1298 retdata = p.stdout.read().strip()
1300 1299 # wait for the child to exit to avoid race condition.
1301 1300 p.wait()
1302 1301
1303 1302 if p.returncode != 0 and p.returncode != 1:
1304 1303 # there are certain error codes that are ok
1305 1304 command = commands[0]
1306 1305 if command in ('cat-file', 'symbolic-ref'):
1307 1306 return retdata, p.returncode
1308 1307 # for all others, abort
1309 1308 raise util.Abort('git %s error %d in %s' %
1310 1309 (command, p.returncode, self._relpath))
1311 1310
1312 1311 return retdata, p.returncode
1313 1312
1314 1313 def _gitmissing(self):
1315 1314 return not self.wvfs.exists('.git')
1316 1315
1317 1316 def _gitstate(self):
1318 1317 return self._gitcommand(['rev-parse', 'HEAD'])
1319 1318
1320 1319 def _gitcurrentbranch(self):
1321 1320 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1322 1321 if err:
1323 1322 current = None
1324 1323 return current
1325 1324
1326 1325 def _gitremote(self, remote):
1327 1326 out = self._gitcommand(['remote', 'show', '-n', remote])
1328 1327 line = out.split('\n')[1]
1329 1328 i = line.index('URL: ') + len('URL: ')
1330 1329 return line[i:]
1331 1330
1332 1331 def _githavelocally(self, revision):
1333 1332 out, code = self._gitdir(['cat-file', '-e', revision])
1334 1333 return code == 0
1335 1334
1336 1335 def _gitisancestor(self, r1, r2):
1337 1336 base = self._gitcommand(['merge-base', r1, r2])
1338 1337 return base == r1
1339 1338
1340 1339 def _gitisbare(self):
1341 1340 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1342 1341
1343 1342 def _gitupdatestat(self):
1344 1343 """This must be run before git diff-index.
1345 1344 diff-index only looks at changes to file stat;
1346 1345 this command looks at file contents and updates the stat."""
1347 1346 self._gitcommand(['update-index', '-q', '--refresh'])
1348 1347
1349 1348 def _gitbranchmap(self):
1350 1349 '''returns 2 things:
1351 1350 a map from git branch to revision
1352 1351 a map from revision to branches'''
1353 1352 branch2rev = {}
1354 1353 rev2branch = {}
1355 1354
1356 1355 out = self._gitcommand(['for-each-ref', '--format',
1357 1356 '%(objectname) %(refname)'])
1358 1357 for line in out.split('\n'):
1359 1358 revision, ref = line.split(' ')
1360 1359 if (not ref.startswith('refs/heads/') and
1361 1360 not ref.startswith('refs/remotes/')):
1362 1361 continue
1363 1362 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1364 1363 continue # ignore remote/HEAD redirects
1365 1364 branch2rev[ref] = revision
1366 1365 rev2branch.setdefault(revision, []).append(ref)
1367 1366 return branch2rev, rev2branch
1368 1367
1369 1368 def _gittracking(self, branches):
1370 1369 'return map of remote branch to local tracking branch'
1371 1370 # assumes no more than one local tracking branch for each remote
1372 1371 tracking = {}
1373 1372 for b in branches:
1374 1373 if b.startswith('refs/remotes/'):
1375 1374 continue
1376 1375 bname = b.split('/', 2)[2]
1377 1376 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1378 1377 if remote:
1379 1378 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1380 1379 tracking['refs/remotes/%s/%s' %
1381 1380 (remote, ref.split('/', 2)[2])] = b
1382 1381 return tracking
1383 1382
1384 1383 def _abssource(self, source):
1385 1384 if '://' not in source:
1386 1385 # recognize the scp syntax as an absolute source
1387 1386 colon = source.find(':')
1388 1387 if colon != -1 and '/' not in source[:colon]:
1389 1388 return source
1390 1389 self._subsource = source
1391 1390 return _abssource(self)
1392 1391
1393 1392 def _fetch(self, source, revision):
1394 1393 if self._gitmissing():
1395 1394 source = self._abssource(source)
1396 1395 self.ui.status(_('cloning subrepo %s from %s\n') %
1397 1396 (self._relpath, source))
1398 1397 self._gitnodir(['clone', source, self._abspath])
1399 1398 if self._githavelocally(revision):
1400 1399 return
1401 1400 self.ui.status(_('pulling subrepo %s from %s\n') %
1402 1401 (self._relpath, self._gitremote('origin')))
1403 1402 # try only origin: the originally cloned repo
1404 1403 self._gitcommand(['fetch'])
1405 1404 if not self._githavelocally(revision):
1406 1405 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1407 1406 (revision, self._relpath))
1408 1407
1409 1408 @annotatesubrepoerror
1410 1409 def dirty(self, ignoreupdate=False):
1411 1410 if self._gitmissing():
1412 1411 return self._state[1] != ''
1413 1412 if self._gitisbare():
1414 1413 return True
1415 1414 if not ignoreupdate and self._state[1] != self._gitstate():
1416 1415 # different version checked out
1417 1416 return True
1418 1417 # check for staged changes or modified files; ignore untracked files
1419 1418 self._gitupdatestat()
1420 1419 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1421 1420 return code == 1
1422 1421
1423 1422 def basestate(self):
1424 1423 return self._gitstate()
1425 1424
1426 1425 @annotatesubrepoerror
1427 1426 def get(self, state, overwrite=False):
1428 1427 source, revision, kind = state
1429 1428 if not revision:
1430 1429 self.remove()
1431 1430 return
1432 1431 self._fetch(source, revision)
1433 1432 # if the repo was set to be bare, unbare it
1434 1433 if self._gitisbare():
1435 1434 self._gitcommand(['config', 'core.bare', 'false'])
1436 1435 if self._gitstate() == revision:
1437 1436 self._gitcommand(['reset', '--hard', 'HEAD'])
1438 1437 return
1439 1438 elif self._gitstate() == revision:
1440 1439 if overwrite:
1441 1440 # first reset the index to unmark new files for commit, because
1442 1441 # reset --hard will otherwise throw away files added for commit,
1443 1442 # not just unmark them.
1444 1443 self._gitcommand(['reset', 'HEAD'])
1445 1444 self._gitcommand(['reset', '--hard', 'HEAD'])
1446 1445 return
1447 1446 branch2rev, rev2branch = self._gitbranchmap()
1448 1447
1449 1448 def checkout(args):
1450 1449 cmd = ['checkout']
1451 1450 if overwrite:
1452 1451 # first reset the index to unmark new files for commit, because
1453 1452 # the -f option will otherwise throw away files added for
1454 1453 # commit, not just unmark them.
1455 1454 self._gitcommand(['reset', 'HEAD'])
1456 1455 cmd.append('-f')
1457 1456 self._gitcommand(cmd + args)
1458 1457 _sanitize(self.ui, self.wvfs, '.git')
1459 1458
1460 1459 def rawcheckout():
1461 1460 # no branch to checkout, check it out with no branch
1462 1461 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1463 1462 self._relpath)
1464 1463 self.ui.warn(_('check out a git branch if you intend '
1465 1464 'to make changes\n'))
1466 1465 checkout(['-q', revision])
1467 1466
1468 1467 if revision not in rev2branch:
1469 1468 rawcheckout()
1470 1469 return
1471 1470 branches = rev2branch[revision]
1472 1471 firstlocalbranch = None
1473 1472 for b in branches:
1474 1473 if b == 'refs/heads/master':
1475 1474 # master trumps all other branches
1476 1475 checkout(['refs/heads/master'])
1477 1476 return
1478 1477 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1479 1478 firstlocalbranch = b
1480 1479 if firstlocalbranch:
1481 1480 checkout([firstlocalbranch])
1482 1481 return
1483 1482
1484 1483 tracking = self._gittracking(branch2rev.keys())
1485 1484 # choose a remote branch already tracked if possible
1486 1485 remote = branches[0]
1487 1486 if remote not in tracking:
1488 1487 for b in branches:
1489 1488 if b in tracking:
1490 1489 remote = b
1491 1490 break
1492 1491
1493 1492 if remote not in tracking:
1494 1493 # create a new local tracking branch
1495 1494 local = remote.split('/', 3)[3]
1496 1495 checkout(['-b', local, remote])
1497 1496 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1498 1497 # When updating to a tracked remote branch,
1499 1498 # if the local tracking branch is downstream of it,
1500 1499 # a normal `git pull` would have performed a "fast-forward merge"
1501 1500 # which is equivalent to updating the local branch to the remote.
1502 1501 # Since we are only looking at branching at update, we need to
1503 1502 # detect this situation and perform this action lazily.
1504 1503 if tracking[remote] != self._gitcurrentbranch():
1505 1504 checkout([tracking[remote]])
1506 1505 self._gitcommand(['merge', '--ff', remote])
1507 1506 _sanitize(self.ui, self.wvfs, '.git')
1508 1507 else:
1509 1508 # a real merge would be required, just checkout the revision
1510 1509 rawcheckout()
1511 1510
1512 1511 @annotatesubrepoerror
1513 1512 def commit(self, text, user, date):
1514 1513 if self._gitmissing():
1515 1514 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1516 1515 cmd = ['commit', '-a', '-m', text]
1517 1516 env = os.environ.copy()
1518 1517 if user:
1519 1518 cmd += ['--author', user]
1520 1519 if date:
1521 1520 # git's date parser silently ignores when seconds < 1e9
1522 1521 # convert to ISO8601
1523 1522 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1524 1523 '%Y-%m-%dT%H:%M:%S %1%2')
1525 1524 self._gitcommand(cmd, env=env)
1526 1525 # make sure commit works otherwise HEAD might not exist under certain
1527 1526 # circumstances
1528 1527 return self._gitstate()
1529 1528
1530 1529 @annotatesubrepoerror
1531 1530 def merge(self, state):
1532 1531 source, revision, kind = state
1533 1532 self._fetch(source, revision)
1534 1533 base = self._gitcommand(['merge-base', revision, self._state[1]])
1535 1534 self._gitupdatestat()
1536 1535 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1537 1536
1538 1537 def mergefunc():
1539 1538 if base == revision:
1540 1539 self.get(state) # fast forward merge
1541 1540 elif base != self._state[1]:
1542 1541 self._gitcommand(['merge', '--no-commit', revision])
1543 1542 _sanitize(self.ui, self.wvfs, '.git')
1544 1543
1545 1544 if self.dirty():
1546 1545 if self._gitstate() != revision:
1547 1546 dirty = self._gitstate() == self._state[1] or code != 0
1548 1547 if _updateprompt(self.ui, self, dirty,
1549 1548 self._state[1][:7], revision[:7]):
1550 1549 mergefunc()
1551 1550 else:
1552 1551 mergefunc()
1553 1552
1554 1553 @annotatesubrepoerror
1555 1554 def push(self, opts):
1556 1555 force = opts.get('force')
1557 1556
1558 1557 if not self._state[1]:
1559 1558 return True
1560 1559 if self._gitmissing():
1561 1560 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1562 1561 # if a branch in origin contains the revision, nothing to do
1563 1562 branch2rev, rev2branch = self._gitbranchmap()
1564 1563 if self._state[1] in rev2branch:
1565 1564 for b in rev2branch[self._state[1]]:
1566 1565 if b.startswith('refs/remotes/origin/'):
1567 1566 return True
1568 1567 for b, revision in branch2rev.iteritems():
1569 1568 if b.startswith('refs/remotes/origin/'):
1570 1569 if self._gitisancestor(self._state[1], revision):
1571 1570 return True
1572 1571 # otherwise, try to push the currently checked out branch
1573 1572 cmd = ['push']
1574 1573 if force:
1575 1574 cmd.append('--force')
1576 1575
1577 1576 current = self._gitcurrentbranch()
1578 1577 if current:
1579 1578 # determine if the current branch is even useful
1580 1579 if not self._gitisancestor(self._state[1], current):
1581 1580 self.ui.warn(_('unrelated git branch checked out '
1582 1581 'in subrepo %s\n') % self._relpath)
1583 1582 return False
1584 1583 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1585 1584 (current.split('/', 2)[2], self._relpath))
1586 1585 ret = self._gitdir(cmd + ['origin', current])
1587 1586 return ret[1] == 0
1588 1587 else:
1589 1588 self.ui.warn(_('no branch checked out in subrepo %s\n'
1590 1589 'cannot push revision %s\n') %
1591 1590 (self._relpath, self._state[1]))
1592 1591 return False
1593 1592
1594 1593 @annotatesubrepoerror
1595 1594 def add(self, ui, match, prefix, explicitonly, **opts):
1596 1595 if self._gitmissing():
1597 1596 return []
1598 1597
1599 1598 (modified, added, removed,
1600 1599 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1601 1600 clean=True)
1602 1601
1603 1602 tracked = set()
1604 1603 # dirstates 'amn' warn, 'r' is added again
1605 1604 for l in (modified, added, deleted, clean):
1606 1605 tracked.update(l)
1607 1606
1608 1607 # Unknown files not of interest will be rejected by the matcher
1609 1608 files = unknown
1610 1609 files.extend(match.files())
1611 1610
1612 1611 rejected = []
1613 1612
1614 1613 files = [f for f in sorted(set(files)) if match(f)]
1615 1614 for f in files:
1616 1615 exact = match.exact(f)
1617 1616 command = ["add"]
1618 1617 if exact:
1619 1618 command.append("-f") #should be added, even if ignored
1620 1619 if ui.verbose or not exact:
1621 1620 ui.status(_('adding %s\n') % match.rel(f))
1622 1621
1623 1622 if f in tracked: # hg prints 'adding' even if already tracked
1624 1623 if exact:
1625 1624 rejected.append(f)
1626 1625 continue
1627 1626 if not opts.get('dry_run'):
1628 1627 self._gitcommand(command + [f])
1629 1628
1630 1629 for f in rejected:
1631 1630 ui.warn(_("%s already tracked!\n") % match.abs(f))
1632 1631
1633 1632 return rejected
1634 1633
1635 1634 @annotatesubrepoerror
1636 1635 def remove(self):
1637 1636 if self._gitmissing():
1638 1637 return
1639 1638 if self.dirty():
1640 1639 self.ui.warn(_('not removing repo %s because '
1641 1640 'it has changes.\n') % self._relpath)
1642 1641 return
1643 1642 # we can't fully delete the repository as it may contain
1644 1643 # local-only history
1645 1644 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1646 1645 self._gitcommand(['config', 'core.bare', 'true'])
1647 1646 for f, kind in self.wvfs.readdir():
1648 1647 if f == '.git':
1649 1648 continue
1650 1649 if kind == stat.S_IFDIR:
1651 1650 self.wvfs.rmtree(f)
1652 1651 else:
1653 1652 self.wvfs.unlink(f)
1654 1653
1655 1654 def archive(self, archiver, prefix, match=None):
1656 1655 total = 0
1657 1656 source, revision = self._state
1658 1657 if not revision:
1659 1658 return total
1660 1659 self._fetch(source, revision)
1661 1660
1662 1661 # Parse git's native archive command.
1663 1662 # This should be much faster than manually traversing the trees
1664 1663 # and objects with many subprocess calls.
1665 1664 tarstream = self._gitcommand(['archive', revision], stream=True)
1666 1665 tar = tarfile.open(fileobj=tarstream, mode='r|')
1667 1666 relpath = subrelpath(self)
1668 1667 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1669 1668 for i, info in enumerate(tar):
1670 1669 if info.isdir():
1671 1670 continue
1672 1671 if match and not match(info.name):
1673 1672 continue
1674 1673 if info.issym():
1675 1674 data = info.linkname
1676 1675 else:
1677 1676 data = tar.extractfile(info).read()
1678 archiver.addfile(self.wvfs.reljoin(prefix, self._path, info.name),
1677 archiver.addfile(prefix + self._path + '/' + info.name,
1679 1678 info.mode, info.issym(), data)
1680 1679 total += 1
1681 1680 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1682 1681 unit=_('files'))
1683 1682 self.ui.progress(_('archiving (%s)') % relpath, None)
1684 1683 return total
1685 1684
1686 1685
1687 1686 @annotatesubrepoerror
1688 1687 def cat(self, match, prefix, **opts):
1689 1688 rev = self._state[1]
1690 1689 if match.anypats():
1691 1690 return 1 #No support for include/exclude yet
1692 1691
1693 1692 if not match.files():
1694 1693 return 1
1695 1694
1696 1695 for f in match.files():
1697 1696 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1698 1697 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1699 1698 self._ctx.node(),
1700 1699 pathname=self.wvfs.reljoin(prefix, f))
1701 1700 fp.write(output)
1702 1701 fp.close()
1703 1702 return 0
1704 1703
1705 1704
1706 1705 @annotatesubrepoerror
1707 1706 def status(self, rev2, **opts):
1708 1707 rev1 = self._state[1]
1709 1708 if self._gitmissing() or not rev1:
1710 1709 # if the repo is missing, return no results
1711 1710 return scmutil.status([], [], [], [], [], [], [])
1712 1711 modified, added, removed = [], [], []
1713 1712 self._gitupdatestat()
1714 1713 if rev2:
1715 1714 command = ['diff-tree', rev1, rev2]
1716 1715 else:
1717 1716 command = ['diff-index', rev1]
1718 1717 out = self._gitcommand(command)
1719 1718 for line in out.split('\n'):
1720 1719 tab = line.find('\t')
1721 1720 if tab == -1:
1722 1721 continue
1723 1722 status, f = line[tab - 1], line[tab + 1:]
1724 1723 if status == 'M':
1725 1724 modified.append(f)
1726 1725 elif status == 'A':
1727 1726 added.append(f)
1728 1727 elif status == 'D':
1729 1728 removed.append(f)
1730 1729
1731 1730 deleted, unknown, ignored, clean = [], [], [], []
1732 1731
1733 1732 command = ['status', '--porcelain', '-z']
1734 1733 if opts.get('unknown'):
1735 1734 command += ['--untracked-files=all']
1736 1735 if opts.get('ignored'):
1737 1736 command += ['--ignored']
1738 1737 out = self._gitcommand(command)
1739 1738
1740 1739 changedfiles = set()
1741 1740 changedfiles.update(modified)
1742 1741 changedfiles.update(added)
1743 1742 changedfiles.update(removed)
1744 1743 for line in out.split('\0'):
1745 1744 if not line:
1746 1745 continue
1747 1746 st = line[0:2]
1748 1747 #moves and copies show 2 files on one line
1749 1748 if line.find('\0') >= 0:
1750 1749 filename1, filename2 = line[3:].split('\0')
1751 1750 else:
1752 1751 filename1 = line[3:]
1753 1752 filename2 = None
1754 1753
1755 1754 changedfiles.add(filename1)
1756 1755 if filename2:
1757 1756 changedfiles.add(filename2)
1758 1757
1759 1758 if st == '??':
1760 1759 unknown.append(filename1)
1761 1760 elif st == '!!':
1762 1761 ignored.append(filename1)
1763 1762
1764 1763 if opts.get('clean'):
1765 1764 out = self._gitcommand(['ls-files'])
1766 1765 for f in out.split('\n'):
1767 1766 if not f in changedfiles:
1768 1767 clean.append(f)
1769 1768
1770 1769 return scmutil.status(modified, added, removed, deleted,
1771 1770 unknown, ignored, clean)
1772 1771
1773 1772 @annotatesubrepoerror
1774 1773 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1775 1774 node1 = self._state[1]
1776 1775 cmd = ['diff']
1777 1776 if opts['stat']:
1778 1777 cmd.append('--stat')
1779 1778 else:
1780 1779 # for Git, this also implies '-p'
1781 1780 cmd.append('-U%d' % diffopts.context)
1782 1781
1783 1782 gitprefix = self.wvfs.reljoin(prefix, self._path)
1784 1783
1785 1784 if diffopts.noprefix:
1786 1785 cmd.extend(['--src-prefix=%s/' % gitprefix,
1787 1786 '--dst-prefix=%s/' % gitprefix])
1788 1787 else:
1789 1788 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1790 1789 '--dst-prefix=b/%s/' % gitprefix])
1791 1790
1792 1791 if diffopts.ignorews:
1793 1792 cmd.append('--ignore-all-space')
1794 1793 if diffopts.ignorewsamount:
1795 1794 cmd.append('--ignore-space-change')
1796 1795 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1797 1796 and diffopts.ignoreblanklines:
1798 1797 cmd.append('--ignore-blank-lines')
1799 1798
1800 1799 cmd.append(node1)
1801 1800 if node2:
1802 1801 cmd.append(node2)
1803 1802
1804 1803 output = ""
1805 1804 if match.always():
1806 1805 output += self._gitcommand(cmd) + '\n'
1807 1806 else:
1808 1807 st = self.status(node2)[:3]
1809 1808 files = [f for sublist in st for f in sublist]
1810 1809 for f in files:
1811 1810 if match(f):
1812 1811 output += self._gitcommand(cmd + ['--', f]) + '\n'
1813 1812
1814 1813 if output.strip():
1815 1814 ui.write(output)
1816 1815
1817 1816 @annotatesubrepoerror
1818 1817 def revert(self, substate, *pats, **opts):
1819 1818 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1820 1819 if not opts.get('no_backup'):
1821 1820 status = self.status(None)
1822 1821 names = status.modified
1823 1822 for name in names:
1824 1823 bakname = "%s.orig" % name
1825 1824 self.ui.note(_('saving current version of %s as %s\n') %
1826 1825 (name, bakname))
1827 1826 self.wvfs.rename(name, bakname)
1828 1827
1829 1828 if not opts.get('dry_run'):
1830 1829 self.get(substate, overwrite=True)
1831 1830 return []
1832 1831
1833 1832 def shortid(self, revid):
1834 1833 return revid[:7]
1835 1834
1836 1835 types = {
1837 1836 'hg': hgsubrepo,
1838 1837 'svn': svnsubrepo,
1839 1838 'git': gitsubrepo,
1840 1839 }
@@ -1,547 +1,543 b''
1 1 Preparing the subrepository 'sub2'
2 2
3 3 $ hg init sub2
4 4 $ echo sub2 > sub2/sub2
5 5 $ hg add -R sub2
6 6 adding sub2/sub2 (glob)
7 7 $ hg commit -R sub2 -m "sub2 import"
8 8
9 9 Preparing the 'sub1' repo which depends on the subrepo 'sub2'
10 10
11 11 $ hg init sub1
12 12 $ echo sub1 > sub1/sub1
13 13 $ echo "sub2 = ../sub2" > sub1/.hgsub
14 14 $ hg clone sub2 sub1/sub2
15 15 updating to branch default
16 16 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
17 17 $ hg add -R sub1
18 18 adding sub1/.hgsub (glob)
19 19 adding sub1/sub1 (glob)
20 20 $ hg commit -R sub1 -m "sub1 import"
21 21
22 22 Preparing the 'main' repo which depends on the subrepo 'sub1'
23 23
24 24 $ hg init main
25 25 $ echo main > main/main
26 26 $ echo "sub1 = ../sub1" > main/.hgsub
27 27 $ hg clone sub1 main/sub1
28 28 updating to branch default
29 29 cloning subrepo sub2 from $TESTTMP/sub2
30 30 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
31 31 $ hg add -R main
32 32 adding main/.hgsub (glob)
33 33 adding main/main (glob)
34 34 $ hg commit -R main -m "main import"
35 35
36 36 Cleaning both repositories, just as a clone -U
37 37
38 38 $ hg up -C -R sub2 null
39 39 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
40 40 $ hg up -C -R sub1 null
41 41 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
42 42 $ hg up -C -R main null
43 43 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
44 44 $ rm -rf main/sub1
45 45 $ rm -rf sub1/sub2
46 46
47 47 Clone main
48 48
49 49 $ hg --config extensions.largefiles= clone main cloned
50 50 updating to branch default
51 51 cloning subrepo sub1 from $TESTTMP/sub1
52 52 cloning subrepo sub1/sub2 from $TESTTMP/sub2 (glob)
53 53 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 54
55 55 Largefiles is NOT enabled in the clone if the source repo doesn't require it
56 56 $ cat cloned/.hg/hgrc
57 57 # example repository config (see "hg help config" for more info)
58 58 [paths]
59 59 default = $TESTTMP/main (glob)
60 60
61 61 # path aliases to other clones of this repo in URLs or filesystem paths
62 62 # (see "hg help config.paths" for more info)
63 63 #
64 64 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
65 65 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
66 66 # my-clone = /home/jdoe/jdoes-clone
67 67
68 68 [ui]
69 69 # name and email (local to this repository, optional), e.g.
70 70 # username = Jane Doe <jdoe@example.com>
71 71
72 72 Checking cloned repo ids
73 73
74 74 $ printf "cloned " ; hg id -R cloned
75 75 cloned 7f491f53a367 tip
76 76 $ printf "cloned/sub1 " ; hg id -R cloned/sub1
77 77 cloned/sub1 fc3b4ce2696f tip
78 78 $ printf "cloned/sub1/sub2 " ; hg id -R cloned/sub1/sub2
79 79 cloned/sub1/sub2 c57a0840e3ba tip
80 80
81 81 debugsub output for main and sub1
82 82
83 83 $ hg debugsub -R cloned
84 84 path sub1
85 85 source ../sub1
86 86 revision fc3b4ce2696f7741438c79207583768f2ce6b0dd
87 87 $ hg debugsub -R cloned/sub1
88 88 path sub2
89 89 source ../sub2
90 90 revision c57a0840e3badd667ef3c3ef65471609acb2ba3c
91 91
92 92 Modifying deeply nested 'sub2'
93 93
94 94 $ echo modified > cloned/sub1/sub2/sub2
95 95 $ hg commit --subrepos -m "deep nested modif should trigger a commit" -R cloned
96 96 committing subrepository sub1
97 97 committing subrepository sub1/sub2 (glob)
98 98
99 99 Checking modified node ids
100 100
101 101 $ printf "cloned " ; hg id -R cloned
102 102 cloned ffe6649062fe tip
103 103 $ printf "cloned/sub1 " ; hg id -R cloned/sub1
104 104 cloned/sub1 2ecb03bf44a9 tip
105 105 $ printf "cloned/sub1/sub2 " ; hg id -R cloned/sub1/sub2
106 106 cloned/sub1/sub2 53dd3430bcaf tip
107 107
108 108 debugsub output for main and sub1
109 109
110 110 $ hg debugsub -R cloned
111 111 path sub1
112 112 source ../sub1
113 113 revision 2ecb03bf44a94e749e8669481dd9069526ce7cb9
114 114 $ hg debugsub -R cloned/sub1
115 115 path sub2
116 116 source ../sub2
117 117 revision 53dd3430bcaf5ab4a7c48262bcad6d441f510487
118 118
119 119 Check that deep archiving works
120 120
121 121 $ cd cloned
122 122 $ echo 'test' > sub1/sub2/test.txt
123 123 $ hg --config extensions.largefiles=! add sub1/sub2/test.txt
124 124 $ mkdir sub1/sub2/folder
125 125 $ echo 'subfolder' > sub1/sub2/folder/test.txt
126 126 $ hg ci -ASm "add test.txt"
127 127 adding sub1/sub2/folder/test.txt
128 128 committing subrepository sub1
129 129 committing subrepository sub1/sub2 (glob)
130 130
131 131 .. but first take a detour through some deep removal testing
132 132
133 133 $ hg remove -S -I 're:.*.txt' .
134 134 removing sub1/sub2/folder/test.txt (glob)
135 135 removing sub1/sub2/test.txt (glob)
136 136 $ hg status -S
137 137 R sub1/sub2/folder/test.txt
138 138 R sub1/sub2/test.txt
139 139 $ hg update -Cq
140 140 $ hg remove -I 're:.*.txt' sub1
141 141 $ hg status -S
142 142 $ hg remove sub1/sub2/folder/test.txt
143 143 $ hg remove sub1/.hgsubstate
144 144 $ mv sub1/.hgsub sub1/x.hgsub
145 145 $ hg status -S
146 146 warning: subrepo spec file 'sub1/.hgsub' not found (glob)
147 147 R sub1/.hgsubstate
148 148 R sub1/sub2/folder/test.txt
149 149 ! sub1/.hgsub
150 150 ? sub1/x.hgsub
151 151 $ mv sub1/x.hgsub sub1/.hgsub
152 152 $ hg update -Cq
153 153 $ touch sub1/foo
154 154 $ hg forget sub1/sub2/folder/test.txt
155 155 $ rm sub1/sub2/test.txt
156 156
157 157 Test relative path printing + subrepos
158 158 $ mkdir -p foo/bar
159 159 $ cd foo
160 160 $ touch bar/abc
161 161 $ hg addremove -S ..
162 162 adding ../sub1/sub2/folder/test.txt (glob)
163 163 removing ../sub1/sub2/test.txt (glob)
164 164 adding ../sub1/foo (glob)
165 165 adding bar/abc (glob)
166 166 $ cd ..
167 167 $ hg status -S
168 168 A foo/bar/abc
169 169 A sub1/foo
170 170 R sub1/sub2/test.txt
171 171 $ hg update -Cq
172 172 $ touch sub1/sub2/folder/bar
173 173 $ hg addremove sub1/sub2
174 174 adding sub1/sub2/folder/bar (glob)
175 175 $ hg status -S
176 176 A sub1/sub2/folder/bar
177 177 ? foo/bar/abc
178 178 ? sub1/foo
179 179 $ hg update -Cq
180 180 $ hg addremove sub1
181 181 adding sub1/sub2/folder/bar (glob)
182 182 adding sub1/foo (glob)
183 183 $ hg update -Cq
184 184 $ rm sub1/sub2/folder/test.txt
185 185 $ rm sub1/sub2/test.txt
186 186 $ hg ci -ASm "remove test.txt"
187 187 adding sub1/sub2/folder/bar
188 188 removing sub1/sub2/folder/test.txt
189 189 removing sub1/sub2/test.txt
190 190 adding sub1/foo
191 191 adding foo/bar/abc
192 192 committing subrepository sub1
193 193 committing subrepository sub1/sub2 (glob)
194 194
195 195 $ hg forget sub1/sub2/sub2
196 196 $ echo x > sub1/sub2/x.txt
197 197 $ hg add sub1/sub2/x.txt
198 198
199 199 Files sees uncommitted adds and removes in subrepos
200 200 $ hg files -S
201 201 .hgsub
202 202 .hgsubstate
203 203 foo/bar/abc (glob)
204 204 main
205 205 sub1/.hgsub (glob)
206 206 sub1/.hgsubstate (glob)
207 207 sub1/foo (glob)
208 208 sub1/sub1 (glob)
209 209 sub1/sub2/folder/bar (glob)
210 210 sub1/sub2/x.txt (glob)
211 211
212 212 $ hg rollback -q
213 213 $ hg up -Cq
214 214
215 215 $ hg --config extensions.largefiles=! archive -S ../archive_all
216 216 $ find ../archive_all | sort
217 217 ../archive_all
218 218 ../archive_all/.hg_archival.txt
219 219 ../archive_all/.hgsub
220 220 ../archive_all/.hgsubstate
221 221 ../archive_all/main
222 222 ../archive_all/sub1
223 223 ../archive_all/sub1/.hgsub
224 224 ../archive_all/sub1/.hgsubstate
225 225 ../archive_all/sub1/sub1
226 226 ../archive_all/sub1/sub2
227 227 ../archive_all/sub1/sub2/folder
228 228 ../archive_all/sub1/sub2/folder/test.txt
229 229 ../archive_all/sub1/sub2/sub2
230 230 ../archive_all/sub1/sub2/test.txt
231 231
232 232 Check that archive -X works in deep subrepos
233 233
234 234 $ hg --config extensions.largefiles=! archive -S -X '**test*' ../archive_exclude
235 235 $ find ../archive_exclude | sort
236 236 ../archive_exclude
237 237 ../archive_exclude/.hg_archival.txt
238 238 ../archive_exclude/.hgsub
239 239 ../archive_exclude/.hgsubstate
240 240 ../archive_exclude/main
241 241 ../archive_exclude/sub1
242 242 ../archive_exclude/sub1/.hgsub
243 243 ../archive_exclude/sub1/.hgsubstate
244 244 ../archive_exclude/sub1/sub1
245 245 ../archive_exclude/sub1/sub2
246 246 ../archive_exclude/sub1/sub2/sub2
247 247
248 248 $ hg --config extensions.largefiles=! archive -S -I '**test*' ../archive_include
249 249 $ find ../archive_include | sort
250 250 ../archive_include
251 251 ../archive_include/sub1
252 252 ../archive_include/sub1/sub2
253 253 ../archive_include/sub1/sub2/folder
254 254 ../archive_include/sub1/sub2/folder/test.txt
255 255 ../archive_include/sub1/sub2/test.txt
256 256
257 257 Check that deep archive works with largefiles (which overrides hgsubrepo impl)
258 258 This also tests the repo.ui regression in 43fb170a23bd, and that lf subrepo
259 259 subrepos are archived properly.
260 260 Note that add --large through a subrepo currently adds the file as a normal file
261 261
262 262 $ echo "large" > sub1/sub2/large.bin
263 263 $ hg --config extensions.largefiles= add --large -R sub1/sub2 sub1/sub2/large.bin
264 264 $ echo "large" > large.bin
265 265 $ hg --config extensions.largefiles= add --large large.bin
266 266 $ hg --config extensions.largefiles= ci -S -m "add large files"
267 267 committing subrepository sub1
268 268 committing subrepository sub1/sub2 (glob)
269 269
270 270 $ hg --config extensions.largefiles= archive -S ../archive_lf
271 271 $ find ../archive_lf | sort
272 272 ../archive_lf
273 273 ../archive_lf/.hg_archival.txt
274 274 ../archive_lf/.hgsub
275 275 ../archive_lf/.hgsubstate
276 276 ../archive_lf/large.bin
277 277 ../archive_lf/main
278 278 ../archive_lf/sub1
279 279 ../archive_lf/sub1/.hgsub
280 280 ../archive_lf/sub1/.hgsubstate
281 281 ../archive_lf/sub1/sub1
282 282 ../archive_lf/sub1/sub2
283 283 ../archive_lf/sub1/sub2/folder
284 284 ../archive_lf/sub1/sub2/folder/test.txt
285 285 ../archive_lf/sub1/sub2/large.bin
286 286 ../archive_lf/sub1/sub2/sub2
287 287 ../archive_lf/sub1/sub2/test.txt
288 288 $ rm -rf ../archive_lf
289 289
290 290 Exclude large files from main and sub-sub repo
291 291
292 292 $ hg --config extensions.largefiles= archive -S -X '**.bin' ../archive_lf
293 293 $ find ../archive_lf | sort
294 294 ../archive_lf
295 295 ../archive_lf/.hg_archival.txt
296 296 ../archive_lf/.hgsub
297 297 ../archive_lf/.hgsubstate
298 298 ../archive_lf/main
299 299 ../archive_lf/sub1
300 300 ../archive_lf/sub1/.hgsub
301 301 ../archive_lf/sub1/.hgsubstate
302 302 ../archive_lf/sub1/sub1
303 303 ../archive_lf/sub1/sub2
304 304 ../archive_lf/sub1/sub2/folder
305 305 ../archive_lf/sub1/sub2/folder/test.txt
306 306 ../archive_lf/sub1/sub2/sub2
307 307 ../archive_lf/sub1/sub2/test.txt
308 308 $ rm -rf ../archive_lf
309 309
310 310 Exclude normal files from main and sub-sub repo
311 311
312 $ hg --config extensions.largefiles= archive -S -X '**.txt' ../archive_lf
313 $ find ../archive_lf | sort
314 ../archive_lf
315 ../archive_lf/.hgsub
316 ../archive_lf/.hgsubstate
317 ../archive_lf/large.bin
318 ../archive_lf/main
319 ../archive_lf/sub1
320 ../archive_lf/sub1/.hgsub
321 ../archive_lf/sub1/.hgsubstate
322 ../archive_lf/sub1/sub1
323 ../archive_lf/sub1/sub2
324 ../archive_lf/sub1/sub2/large.bin
325 ../archive_lf/sub1/sub2/sub2
326 $ rm -rf ../archive_lf
312 $ hg --config extensions.largefiles= archive -S -X '**.txt' ../archive_lf.tgz
313 $ tar -tzf ../archive_lf.tgz | sort
314 archive_lf/.hgsub
315 archive_lf/.hgsubstate
316 archive_lf/large.bin
317 archive_lf/main
318 archive_lf/sub1/.hgsub
319 archive_lf/sub1/.hgsubstate
320 archive_lf/sub1/sub1
321 archive_lf/sub1/sub2/large.bin
322 archive_lf/sub1/sub2/sub2
327 323
328 324 Include normal files from within a largefiles subrepo
329 325
330 326 $ hg --config extensions.largefiles= archive -S -I '**.txt' ../archive_lf
331 327 $ find ../archive_lf | sort
332 328 ../archive_lf
333 329 ../archive_lf/.hg_archival.txt
334 330 ../archive_lf/sub1
335 331 ../archive_lf/sub1/sub2
336 332 ../archive_lf/sub1/sub2/folder
337 333 ../archive_lf/sub1/sub2/folder/test.txt
338 334 ../archive_lf/sub1/sub2/test.txt
339 335 $ rm -rf ../archive_lf
340 336
341 337 Include large files from within a largefiles subrepo
342 338
343 339 $ hg --config extensions.largefiles= archive -S -I '**.bin' ../archive_lf
344 340 $ find ../archive_lf | sort
345 341 ../archive_lf
346 342 ../archive_lf/large.bin
347 343 ../archive_lf/sub1
348 344 ../archive_lf/sub1/sub2
349 345 ../archive_lf/sub1/sub2/large.bin
350 346 $ rm -rf ../archive_lf
351 347
352 348 Find an exact largefile match in a largefiles subrepo
353 349
354 350 $ hg --config extensions.largefiles= archive -S -I 'sub1/sub2/large.bin' ../archive_lf
355 351 $ find ../archive_lf | sort
356 352 ../archive_lf
357 353 ../archive_lf/sub1
358 354 ../archive_lf/sub1/sub2
359 355 ../archive_lf/sub1/sub2/large.bin
360 356 $ rm -rf ../archive_lf
361 357
362 358 The local repo enables largefiles if a largefiles repo is cloned
363 359 $ hg showconfig extensions
364 360 abort: repository requires features unknown to this Mercurial: largefiles!
365 361 (see http://mercurial.selenic.com/wiki/MissingRequirement for more information)
366 362 [255]
367 363 $ hg --config extensions.largefiles= clone -qU . ../lfclone
368 364 $ cat ../lfclone/.hg/hgrc
369 365 # example repository config (see "hg help config" for more info)
370 366 [paths]
371 367 default = $TESTTMP/cloned (glob)
372 368
373 369 # path aliases to other clones of this repo in URLs or filesystem paths
374 370 # (see "hg help config.paths" for more info)
375 371 #
376 372 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
377 373 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
378 374 # my-clone = /home/jdoe/jdoes-clone
379 375
380 376 [ui]
381 377 # name and email (local to this repository, optional), e.g.
382 378 # username = Jane Doe <jdoe@example.com>
383 379
384 380 [extensions]
385 381 largefiles=
386 382
387 383 Find an exact match to a standin (should archive nothing)
388 384 $ hg --config extensions.largefiles= archive -S -I 'sub/sub2/.hglf/large.bin' ../archive_lf
389 385 $ find ../archive_lf 2> /dev/null | sort
390 386
391 387 $ cat >> $HGRCPATH <<EOF
392 388 > [extensions]
393 389 > largefiles=
394 390 > [largefiles]
395 391 > patterns=glob:**.dat
396 392 > EOF
397 393
398 394 Test forget through a deep subrepo with the largefiles extension, both a
399 395 largefile and a normal file. Then a largefile that hasn't been committed yet.
400 396 $ touch sub1/sub2/untracked.txt
401 397 $ touch sub1/sub2/large.dat
402 398 $ hg forget sub1/sub2/large.bin sub1/sub2/test.txt sub1/sub2/untracked.txt
403 399 not removing sub1/sub2/untracked.txt: file is already untracked (glob)
404 400 [1]
405 401 $ hg add --large --dry-run -v sub1/sub2/untracked.txt
406 402 adding sub1/sub2/untracked.txt as a largefile (glob)
407 403 $ hg add --large -v sub1/sub2/untracked.txt
408 404 adding sub1/sub2/untracked.txt as a largefile (glob)
409 405 $ hg add --normal -v sub1/sub2/large.dat
410 406 adding sub1/sub2/large.dat (glob)
411 407 $ hg forget -v sub1/sub2/untracked.txt
412 408 removing sub1/sub2/untracked.txt (glob)
413 409 $ hg status -S
414 410 A sub1/sub2/large.dat
415 411 R sub1/sub2/large.bin
416 412 R sub1/sub2/test.txt
417 413 ? foo/bar/abc
418 414 ? sub1/sub2/untracked.txt
419 415 ? sub1/sub2/x.txt
420 416 $ hg add sub1/sub2
421 417 $ hg ci -Sqm 'forget testing'
422 418
423 419 Test issue4330: commit a directory where only normal files have changed
424 420 $ touch foo/bar/large.dat
425 421 $ hg add --large foo/bar/large.dat
426 422 $ hg ci -m 'add foo/bar/large.dat'
427 423 $ touch a.txt
428 424 $ touch a.dat
429 425 $ hg add -v foo/bar/abc a.txt a.dat
430 426 adding a.dat as a largefile
431 427 adding a.txt
432 428 adding foo/bar/abc (glob)
433 429 $ hg ci -m 'dir commit with only normal file deltas' foo/bar
434 430 $ hg status
435 431 A a.dat
436 432 A a.txt
437 433
438 434 Test a directory commit with a changed largefile and a changed normal file
439 435 $ echo changed > foo/bar/large.dat
440 436 $ echo changed > foo/bar/abc
441 437 $ hg ci -m 'dir commit with normal and lf file deltas' foo
442 438 $ hg status
443 439 A a.dat
444 440 A a.txt
445 441
446 442 $ hg ci -m "add a.*"
447 443 $ hg mv a.dat b.dat
448 444 $ hg mv foo/bar/abc foo/bar/def
449 445 $ hg status -C
450 446 A b.dat
451 447 a.dat
452 448 A foo/bar/def
453 449 foo/bar/abc
454 450 R a.dat
455 451 R foo/bar/abc
456 452
457 453 $ hg ci -m "move large and normal"
458 454 $ hg status -C --rev '.^' --rev .
459 455 A b.dat
460 456 a.dat
461 457 A foo/bar/def
462 458 foo/bar/abc
463 459 R a.dat
464 460 R foo/bar/abc
465 461
466 462
467 463 $ echo foo > main
468 464 $ hg ci -m "mod parent only"
469 465 $ hg init sub3
470 466 $ echo "sub3 = sub3" >> .hgsub
471 467 $ echo xyz > sub3/a.txt
472 468 $ hg add sub3/a.txt
473 469 $ hg ci -Sm "add sub3"
474 470 committing subrepository sub3
475 471 $ cat .hgsub | grep -v sub3 > .hgsub1
476 472 $ mv .hgsub1 .hgsub
477 473 $ hg ci -m "remove sub3"
478 474
479 475 $ hg log -r "subrepo()" --style compact
480 476 0 7f491f53a367 1970-01-01 00:00 +0000 test
481 477 main import
482 478
483 479 1 ffe6649062fe 1970-01-01 00:00 +0000 test
484 480 deep nested modif should trigger a commit
485 481
486 482 2 9bb10eebee29 1970-01-01 00:00 +0000 test
487 483 add test.txt
488 484
489 485 3 7c64f035294f 1970-01-01 00:00 +0000 test
490 486 add large files
491 487
492 488 4 f734a59e2e35 1970-01-01 00:00 +0000 test
493 489 forget testing
494 490
495 491 11 9685a22af5db 1970-01-01 00:00 +0000 test
496 492 add sub3
497 493
498 494 12[tip] 2e0485b475b9 1970-01-01 00:00 +0000 test
499 495 remove sub3
500 496
501 497 $ hg log -r "subrepo('sub3')" --style compact
502 498 11 9685a22af5db 1970-01-01 00:00 +0000 test
503 499 add sub3
504 500
505 501 12[tip] 2e0485b475b9 1970-01-01 00:00 +0000 test
506 502 remove sub3
507 503
508 504 $ hg log -r "subrepo('bogus')" --style compact
509 505
510 506
511 507 Test .hgsubstate in the R state
512 508
513 509 $ hg rm .hgsub .hgsubstate
514 510 $ hg ci -m 'trash subrepo tracking'
515 511
516 512 $ hg log -r "subrepo('re:sub\d+')" --style compact
517 513 0 7f491f53a367 1970-01-01 00:00 +0000 test
518 514 main import
519 515
520 516 1 ffe6649062fe 1970-01-01 00:00 +0000 test
521 517 deep nested modif should trigger a commit
522 518
523 519 2 9bb10eebee29 1970-01-01 00:00 +0000 test
524 520 add test.txt
525 521
526 522 3 7c64f035294f 1970-01-01 00:00 +0000 test
527 523 add large files
528 524
529 525 4 f734a59e2e35 1970-01-01 00:00 +0000 test
530 526 forget testing
531 527
532 528 11 9685a22af5db 1970-01-01 00:00 +0000 test
533 529 add sub3
534 530
535 531 12 2e0485b475b9 1970-01-01 00:00 +0000 test
536 532 remove sub3
537 533
538 534 13[tip] a68b2c361653 1970-01-01 00:00 +0000 test
539 535 trash subrepo tracking
540 536
541 537
542 538 Restore the trashed subrepo tracking
543 539
544 540 $ hg rollback -q
545 541 $ hg update -Cq .
546 542
547 543 $ cd ..
@@ -1,1101 +1,1109 b''
1 1 #require git
2 2
3 3 make git commits repeatable
4 4
5 5 $ echo "[core]" >> $HOME/.gitconfig
6 6 $ echo "autocrlf = false" >> $HOME/.gitconfig
7 7 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
8 8 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
9 9 $ GIT_AUTHOR_DATE='1234567891 +0000'; export GIT_AUTHOR_DATE
10 10 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
11 11 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
12 12 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
13 13 $ GIT_CONFIG_NOSYSTEM=1; export GIT_CONFIG_NOSYSTEM
14 14
15 15 root hg repo
16 16
17 17 $ hg init t
18 18 $ cd t
19 19 $ echo a > a
20 20 $ hg add a
21 21 $ hg commit -m a
22 22 $ cd ..
23 23
24 24 new external git repo
25 25
26 26 $ mkdir gitroot
27 27 $ cd gitroot
28 28 $ git init -q
29 29 $ echo g > g
30 30 $ git add g
31 31 $ git commit -q -m g
32 32
33 33 add subrepo clone
34 34
35 35 $ cd ../t
36 36 $ echo 's = [git]../gitroot' > .hgsub
37 37 $ git clone -q ../gitroot s
38 38 $ hg add .hgsub
39 39 $ hg commit -m 'new git subrepo'
40 40 $ hg debugsub
41 41 path s
42 42 source ../gitroot
43 43 revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
44 44
45 45 record a new commit from upstream from a different branch
46 46
47 47 $ cd ../gitroot
48 48 $ git checkout -q -b testing
49 49 $ echo gg >> g
50 50 $ git commit -q -a -m gg
51 51
52 52 $ cd ../t/s
53 53 $ git pull -q >/dev/null 2>/dev/null
54 54 $ git checkout -q -b testing origin/testing >/dev/null
55 55
56 56 $ cd ..
57 57 $ hg status --subrepos
58 58 M s/g
59 59 $ hg commit -m 'update git subrepo'
60 60 $ hg debugsub
61 61 path s
62 62 source ../gitroot
63 63 revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
64 64
65 65 make $GITROOT pushable, by replacing it with a clone with nothing checked out
66 66
67 67 $ cd ..
68 68 $ git clone gitroot gitrootbare --bare -q
69 69 $ rm -rf gitroot
70 70 $ mv gitrootbare gitroot
71 71
72 72 clone root
73 73
74 74 $ cd t
75 75 $ hg clone . ../tc 2> /dev/null
76 76 updating to branch default
77 77 cloning subrepo s from $TESTTMP/gitroot
78 78 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 79 $ cd ../tc
80 80 $ hg debugsub
81 81 path s
82 82 source ../gitroot
83 83 revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
84 84
85 85 update to previous substate
86 86
87 87 $ hg update 1 -q
88 88 $ cat s/g
89 89 g
90 90 $ hg debugsub
91 91 path s
92 92 source ../gitroot
93 93 revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
94 94
95 95 clone root, make local change
96 96
97 97 $ cd ../t
98 98 $ hg clone . ../ta 2> /dev/null
99 99 updating to branch default
100 100 cloning subrepo s from $TESTTMP/gitroot
101 101 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102
103 103 $ cd ../ta
104 104 $ echo ggg >> s/g
105 105 $ hg status --subrepos
106 106 M s/g
107 107 $ hg diff --subrepos
108 108 diff --git a/s/g b/s/g
109 109 index 089258f..85341ee 100644
110 110 --- a/s/g
111 111 +++ b/s/g
112 112 @@ -1,2 +1,3 @@
113 113 g
114 114 gg
115 115 +ggg
116 116 $ hg commit --subrepos -m ggg
117 117 committing subrepository s
118 118 $ hg debugsub
119 119 path s
120 120 source ../gitroot
121 121 revision 79695940086840c99328513acbe35f90fcd55e57
122 122
123 123 clone root separately, make different local change
124 124
125 125 $ cd ../t
126 126 $ hg clone . ../tb 2> /dev/null
127 127 updating to branch default
128 128 cloning subrepo s from $TESTTMP/gitroot
129 129 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
130 130
131 131 $ cd ../tb/s
132 132 $ hg status --subrepos
133 133 $ echo f > f
134 134 $ hg status --subrepos
135 135 ? s/f
136 136 $ hg add .
137 137 adding f
138 138 $ git add f
139 139 $ cd ..
140 140
141 141 $ hg status --subrepos
142 142 A s/f
143 143 $ hg commit --subrepos -m f
144 144 committing subrepository s
145 145 $ hg debugsub
146 146 path s
147 147 source ../gitroot
148 148 revision aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
149 149
150 150 user b push changes
151 151
152 152 $ hg push 2>/dev/null
153 153 pushing to $TESTTMP/t (glob)
154 154 pushing branch testing of subrepo s
155 155 searching for changes
156 156 adding changesets
157 157 adding manifests
158 158 adding file changes
159 159 added 1 changesets with 1 changes to 1 files
160 160
161 161 user a pulls, merges, commits
162 162
163 163 $ cd ../ta
164 164 $ hg pull
165 165 pulling from $TESTTMP/t (glob)
166 166 searching for changes
167 167 adding changesets
168 168 adding manifests
169 169 adding file changes
170 170 added 1 changesets with 1 changes to 1 files (+1 heads)
171 171 (run 'hg heads' to see heads, 'hg merge' to merge)
172 172 $ hg merge 2>/dev/null
173 173 subrepository s diverged (local revision: 7969594, remote revision: aa84837)
174 174 (M)erge, keep (l)ocal or keep (r)emote? m
175 175 pulling subrepo s from $TESTTMP/gitroot
176 176 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
177 177 (branch merge, don't forget to commit)
178 178 $ hg st --subrepos s
179 179 A s/f
180 180 $ cat s/f
181 181 f
182 182 $ cat s/g
183 183 g
184 184 gg
185 185 ggg
186 186 $ hg commit --subrepos -m 'merge'
187 187 committing subrepository s
188 188 $ hg status --subrepos --rev 1:5
189 189 M .hgsubstate
190 190 M s/g
191 191 A s/f
192 192 $ hg debugsub
193 193 path s
194 194 source ../gitroot
195 195 revision f47b465e1bce645dbf37232a00574aa1546ca8d3
196 196 $ hg push 2>/dev/null
197 197 pushing to $TESTTMP/t (glob)
198 198 pushing branch testing of subrepo s
199 199 searching for changes
200 200 adding changesets
201 201 adding manifests
202 202 adding file changes
203 203 added 2 changesets with 2 changes to 1 files
204 204
205 205 make upstream git changes
206 206
207 207 $ cd ..
208 208 $ git clone -q gitroot gitclone
209 209 $ cd gitclone
210 210 $ echo ff >> f
211 211 $ git commit -q -a -m ff
212 212 $ echo fff >> f
213 213 $ git commit -q -a -m fff
214 214 $ git push origin testing 2>/dev/null
215 215
216 216 make and push changes to hg without updating the subrepo
217 217
218 218 $ cd ../t
219 219 $ hg clone . ../td 2>&1 | egrep -v '^Cloning into|^done\.'
220 220 updating to branch default
221 221 cloning subrepo s from $TESTTMP/gitroot
222 222 checking out detached HEAD in subrepo s
223 223 check out a git branch if you intend to make changes
224 224 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
225 225 $ cd ../td
226 226 $ echo aa >> a
227 227 $ hg commit -m aa
228 228 $ hg push
229 229 pushing to $TESTTMP/t (glob)
230 230 searching for changes
231 231 adding changesets
232 232 adding manifests
233 233 adding file changes
234 234 added 1 changesets with 1 changes to 1 files
235 235
236 236 sync to upstream git, distribute changes
237 237
238 238 $ cd ../ta
239 239 $ hg pull -u -q
240 240 $ cd s
241 241 $ git pull -q >/dev/null 2>/dev/null
242 242 $ cd ..
243 243 $ hg commit -m 'git upstream sync'
244 244 $ hg debugsub
245 245 path s
246 246 source ../gitroot
247 247 revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
248 248 $ hg push -q
249 249
250 250 $ cd ../tb
251 251 $ hg pull -q
252 252 $ hg update 2>/dev/null
253 253 pulling subrepo s from $TESTTMP/gitroot
254 254 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
255 255 $ hg debugsub
256 256 path s
257 257 source ../gitroot
258 258 revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
259 259
260 260 create a new git branch
261 261
262 262 $ cd s
263 263 $ git checkout -b b2
264 264 Switched to a new branch 'b2'
265 265 $ echo a>a
266 266 $ git add a
267 267 $ git commit -qm 'add a'
268 268 $ cd ..
269 269 $ hg commit -m 'add branch in s'
270 270
271 271 pulling new git branch should not create tracking branch named 'origin/b2'
272 272 (issue3870)
273 273 $ cd ../td/s
274 274 $ git remote set-url origin $TESTTMP/tb/s
275 275 $ git branch --no-track oldtesting
276 276 $ cd ..
277 277 $ hg pull -q ../tb
278 278 $ hg up
279 279 From $TESTTMP/tb/s
280 280 * [new branch] b2 -> origin/b2
281 281 Previous HEAD position was f47b465... merge
282 282 Switched to a new branch 'b2'
283 283 pulling subrepo s from $TESTTMP/tb/s
284 284 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
285 285
286 286 update to a revision without the subrepo, keeping the local git repository
287 287
288 288 $ cd ../t
289 289 $ hg up 0
290 290 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
291 291 $ ls -a s
292 292 .
293 293 ..
294 294 .git
295 295
296 296 $ hg up 2
297 297 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
298 298 $ ls -a s
299 299 .
300 300 ..
301 301 .git
302 302 g
303 303
304 304 archive subrepos
305 305
306 306 $ cd ../tc
307 307 $ hg pull -q
308 308 $ hg archive --subrepos -r 5 ../archive 2>/dev/null
309 309 pulling subrepo s from $TESTTMP/gitroot
310 310 $ cd ../archive
311 311 $ cat s/f
312 312 f
313 313 $ cat s/g
314 314 g
315 315 gg
316 316 ggg
317 317
318 318 $ hg -R ../tc archive --subrepo -r 5 -X ../tc/**f ../archive_x 2>/dev/null
319 319 $ find ../archive_x | sort | grep -v pax_global_header
320 320 ../archive_x
321 321 ../archive_x/.hg_archival.txt
322 322 ../archive_x/.hgsub
323 323 ../archive_x/.hgsubstate
324 324 ../archive_x/a
325 325 ../archive_x/s
326 326 ../archive_x/s/g
327 327
328 $ hg -R ../tc archive -S ../archive.tgz 2>/dev/null
329 $ tar -tzf ../archive.tgz | sort
330 archive/.hg_archival.txt
331 archive/.hgsub
332 archive/.hgsubstate
333 archive/a
334 archive/s/g
335
328 336 create nested repo
329 337
330 338 $ cd ..
331 339 $ hg init outer
332 340 $ cd outer
333 341 $ echo b>b
334 342 $ hg add b
335 343 $ hg commit -m b
336 344
337 345 $ hg clone ../t inner 2> /dev/null
338 346 updating to branch default
339 347 cloning subrepo s from $TESTTMP/gitroot
340 348 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 349 $ echo inner = inner > .hgsub
342 350 $ hg add .hgsub
343 351 $ hg commit -m 'nested sub'
344 352
345 353 nested commit
346 354
347 355 $ echo ffff >> inner/s/f
348 356 $ hg status --subrepos
349 357 M inner/s/f
350 358 $ hg commit --subrepos -m nested
351 359 committing subrepository inner
352 360 committing subrepository inner/s (glob)
353 361
354 362 nested archive
355 363
356 364 $ hg archive --subrepos ../narchive
357 365 $ ls ../narchive/inner/s | grep -v pax_global_header
358 366 f
359 367 g
360 368
361 369 relative source expansion
362 370
363 371 $ cd ..
364 372 $ mkdir d
365 373 $ hg clone t d/t 2> /dev/null
366 374 updating to branch default
367 375 cloning subrepo s from $TESTTMP/gitroot
368 376 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
369 377
370 378 Don't crash if the subrepo is missing
371 379
372 380 $ hg clone t missing -q
373 381 $ cd missing
374 382 $ rm -rf s
375 383 $ hg status -S
376 384 $ hg sum | grep commit
377 385 commit: 1 subrepos
378 386 $ hg push -q
379 387 abort: subrepo s is missing (in subrepo s)
380 388 [255]
381 389 $ hg commit --subrepos -qm missing
382 390 abort: subrepo s is missing (in subrepo s)
383 391 [255]
384 392
385 393 #if symlink
386 394 Don't crash if subrepo is a broken symlink
387 395 $ ln -s broken s
388 396 $ hg status -S
389 397 $ hg push -q
390 398 abort: subrepo s is missing (in subrepo s)
391 399 [255]
392 400 $ hg commit --subrepos -qm missing
393 401 abort: subrepo s is missing (in subrepo s)
394 402 [255]
395 403 $ rm s
396 404 #endif
397 405
398 406 $ hg update -C 2> /dev/null
399 407 cloning subrepo s from $TESTTMP/gitroot
400 408 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
401 409 $ hg sum | grep commit
402 410 commit: (clean)
403 411
404 412 Don't crash if the .hgsubstate entry is missing
405 413
406 414 $ hg update 1 -q
407 415 $ hg rm .hgsubstate
408 416 $ hg commit .hgsubstate -m 'no substate'
409 417 nothing changed
410 418 [1]
411 419 $ hg tag -l nosubstate
412 420 $ hg manifest
413 421 .hgsub
414 422 .hgsubstate
415 423 a
416 424
417 425 $ hg status -S
418 426 R .hgsubstate
419 427 $ hg sum | grep commit
420 428 commit: 1 removed, 1 subrepos (new branch head)
421 429
422 430 $ hg commit -m 'restore substate'
423 431 nothing changed
424 432 [1]
425 433 $ hg manifest
426 434 .hgsub
427 435 .hgsubstate
428 436 a
429 437 $ hg sum | grep commit
430 438 commit: 1 removed, 1 subrepos (new branch head)
431 439
432 440 $ hg update -qC nosubstate
433 441 $ ls s
434 442 g
435 443
436 444 issue3109: false positives in git diff-index
437 445
438 446 $ hg update -q
439 447 $ touch -t 200001010000 s/g
440 448 $ hg status --subrepos
441 449 $ touch -t 200001010000 s/g
442 450 $ hg sum | grep commit
443 451 commit: (clean)
444 452
445 453 Check hg update --clean
446 454 $ cd $TESTTMP/ta
447 455 $ echo > s/g
448 456 $ cd s
449 457 $ echo c1 > f1
450 458 $ echo c1 > f2
451 459 $ git add f1
452 460 $ cd ..
453 461 $ hg status -S
454 462 M s/g
455 463 A s/f1
456 464 ? s/f2
457 465 $ ls s
458 466 f
459 467 f1
460 468 f2
461 469 g
462 470 $ hg update --clean
463 471 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
464 472 $ hg status -S
465 473 ? s/f1
466 474 ? s/f2
467 475 $ ls s
468 476 f
469 477 f1
470 478 f2
471 479 g
472 480
473 481 Sticky subrepositories, no changes
474 482 $ cd $TESTTMP/ta
475 483 $ hg id -n
476 484 7
477 485 $ cd s
478 486 $ git rev-parse HEAD
479 487 32a343883b74769118bb1d3b4b1fbf9156f4dddc
480 488 $ cd ..
481 489 $ hg update 1 > /dev/null 2>&1
482 490 $ hg id -n
483 491 1
484 492 $ cd s
485 493 $ git rev-parse HEAD
486 494 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
487 495 $ cd ..
488 496
489 497 Sticky subrepositories, file changes
490 498 $ touch s/f1
491 499 $ cd s
492 500 $ git add f1
493 501 $ cd ..
494 502 $ hg id -n
495 503 1+
496 504 $ cd s
497 505 $ git rev-parse HEAD
498 506 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
499 507 $ cd ..
500 508 $ hg update 4
501 509 subrepository s diverged (local revision: da5f5b1, remote revision: aa84837)
502 510 (M)erge, keep (l)ocal or keep (r)emote? m
503 511 subrepository sources for s differ
504 512 use (l)ocal source (da5f5b1) or (r)emote source (aa84837)? l
505 513 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
506 514 $ hg id -n
507 515 4+
508 516 $ cd s
509 517 $ git rev-parse HEAD
510 518 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
511 519 $ cd ..
512 520 $ hg update --clean tip > /dev/null 2>&1
513 521
514 522 Sticky subrepository, revision updates
515 523 $ hg id -n
516 524 7
517 525 $ cd s
518 526 $ git rev-parse HEAD
519 527 32a343883b74769118bb1d3b4b1fbf9156f4dddc
520 528 $ cd ..
521 529 $ cd s
522 530 $ git checkout aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
523 531 Previous HEAD position was 32a3438... fff
524 532 HEAD is now at aa84837... f
525 533 $ cd ..
526 534 $ hg update 1
527 535 subrepository s diverged (local revision: 32a3438, remote revision: da5f5b1)
528 536 (M)erge, keep (l)ocal or keep (r)emote? m
529 537 subrepository sources for s differ (in checked out version)
530 538 use (l)ocal source (32a3438) or (r)emote source (da5f5b1)? l
531 539 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
532 540 $ hg id -n
533 541 1+
534 542 $ cd s
535 543 $ git rev-parse HEAD
536 544 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
537 545 $ cd ..
538 546
539 547 Sticky subrepository, file changes and revision updates
540 548 $ touch s/f1
541 549 $ cd s
542 550 $ git add f1
543 551 $ git rev-parse HEAD
544 552 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
545 553 $ cd ..
546 554 $ hg id -n
547 555 1+
548 556 $ hg update 7
549 557 subrepository s diverged (local revision: 32a3438, remote revision: 32a3438)
550 558 (M)erge, keep (l)ocal or keep (r)emote? m
551 559 subrepository sources for s differ
552 560 use (l)ocal source (32a3438) or (r)emote source (32a3438)? l
553 561 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
554 562 $ hg id -n
555 563 7+
556 564 $ cd s
557 565 $ git rev-parse HEAD
558 566 aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
559 567 $ cd ..
560 568
561 569 Sticky repository, update --clean
562 570 $ hg update --clean tip 2>/dev/null
563 571 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
564 572 $ hg id -n
565 573 7
566 574 $ cd s
567 575 $ git rev-parse HEAD
568 576 32a343883b74769118bb1d3b4b1fbf9156f4dddc
569 577 $ cd ..
570 578
571 579 Test subrepo already at intended revision:
572 580 $ cd s
573 581 $ git checkout 32a343883b74769118bb1d3b4b1fbf9156f4dddc
574 582 HEAD is now at 32a3438... fff
575 583 $ cd ..
576 584 $ hg update 1
577 585 Previous HEAD position was 32a3438... fff
578 586 HEAD is now at da5f5b1... g
579 587 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
580 588 $ hg id -n
581 589 1
582 590 $ cd s
583 591 $ git rev-parse HEAD
584 592 da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
585 593 $ cd ..
586 594
587 595 Test forgetting files, not implemented in git subrepo, used to
588 596 traceback
589 597 #if no-windows
590 598 $ hg forget 'notafile*'
591 599 notafile*: No such file or directory
592 600 [1]
593 601 #else
594 602 $ hg forget 'notafile'
595 603 notafile: * (glob)
596 604 [1]
597 605 #endif
598 606
599 607 $ cd ..
600 608
601 609 Test sanitizing ".hg/hgrc" in subrepo
602 610
603 611 $ cd t
604 612 $ hg tip -q
605 613 7:af6d2edbb0d3
606 614 $ hg update -q -C af6d2edbb0d3
607 615 $ cd s
608 616 $ git checkout -q -b sanitize-test
609 617 $ mkdir .hg
610 618 $ echo '.hg/hgrc in git repo' > .hg/hgrc
611 619 $ mkdir -p sub/.hg
612 620 $ echo 'sub/.hg/hgrc in git repo' > sub/.hg/hgrc
613 621 $ git add .hg sub
614 622 $ git commit -qm 'add .hg/hgrc to be sanitized at hg update'
615 623 $ git push -q origin sanitize-test
616 624 $ cd ..
617 625 $ grep ' s$' .hgsubstate
618 626 32a343883b74769118bb1d3b4b1fbf9156f4dddc s
619 627 $ hg commit -qm 'commit with git revision including .hg/hgrc'
620 628 $ hg parents -q
621 629 8:3473d20bddcf
622 630 $ grep ' s$' .hgsubstate
623 631 c4069473b459cf27fd4d7c2f50c4346b4e936599 s
624 632 $ cd ..
625 633
626 634 $ hg -R tc pull -q
627 635 $ hg -R tc update -q -C 3473d20bddcf 2>&1 | sort
628 636 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/.hg' (glob)
629 637 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/sub/.hg' (glob)
630 638 $ cd tc
631 639 $ hg parents -q
632 640 8:3473d20bddcf
633 641 $ grep ' s$' .hgsubstate
634 642 c4069473b459cf27fd4d7c2f50c4346b4e936599 s
635 643 $ test -f s/.hg/hgrc
636 644 [1]
637 645 $ test -f s/sub/.hg/hgrc
638 646 [1]
639 647 $ cd ..
640 648
641 649 additional test for "git merge --ff" route:
642 650
643 651 $ cd t
644 652 $ hg tip -q
645 653 8:3473d20bddcf
646 654 $ hg update -q -C af6d2edbb0d3
647 655 $ cd s
648 656 $ git checkout -q testing
649 657 $ mkdir .hg
650 658 $ echo '.hg/hgrc in git repo' > .hg/hgrc
651 659 $ mkdir -p sub/.hg
652 660 $ echo 'sub/.hg/hgrc in git repo' > sub/.hg/hgrc
653 661 $ git add .hg sub
654 662 $ git commit -qm 'add .hg/hgrc to be sanitized at hg update (git merge --ff)'
655 663 $ git push -q origin testing
656 664 $ cd ..
657 665 $ grep ' s$' .hgsubstate
658 666 32a343883b74769118bb1d3b4b1fbf9156f4dddc s
659 667 $ hg commit -qm 'commit with git revision including .hg/hgrc'
660 668 $ hg parents -q
661 669 9:ed23f7fe024e
662 670 $ grep ' s$' .hgsubstate
663 671 f262643c1077219fbd3858d54e78ef050ef84fbf s
664 672 $ cd ..
665 673
666 674 $ cd tc
667 675 $ hg update -q -C af6d2edbb0d3
668 676 $ test -f s/.hg/hgrc
669 677 [1]
670 678 $ test -f s/sub/.hg/hgrc
671 679 [1]
672 680 $ cd ..
673 681 $ hg -R tc pull -q
674 682 $ hg -R tc update -q -C ed23f7fe024e 2>&1 | sort
675 683 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/.hg' (glob)
676 684 warning: removing potentially hostile 'hgrc' in '$TESTTMP/tc/s/sub/.hg' (glob)
677 685 $ cd tc
678 686 $ hg parents -q
679 687 9:ed23f7fe024e
680 688 $ grep ' s$' .hgsubstate
681 689 f262643c1077219fbd3858d54e78ef050ef84fbf s
682 690 $ test -f s/.hg/hgrc
683 691 [1]
684 692 $ test -f s/sub/.hg/hgrc
685 693 [1]
686 694
687 695 Test that sanitizing is omitted in meta data area:
688 696
689 697 $ mkdir s/.git/.hg
690 698 $ echo '.hg/hgrc in git metadata area' > s/.git/.hg/hgrc
691 699 $ hg update -q -C af6d2edbb0d3
692 700 checking out detached HEAD in subrepo s
693 701 check out a git branch if you intend to make changes
694 702
695 703 check differences made by most recent change
696 704 $ cd s
697 705 $ cat > foobar << EOF
698 706 > woopwoop
699 707 >
700 708 > foo
701 709 > bar
702 710 > EOF
703 711 $ git add foobar
704 712 $ cd ..
705 713
706 714 $ hg diff --subrepos
707 715 diff --git a/s/foobar b/s/foobar
708 716 new file mode 100644
709 717 index 0000000..8a5a5e2
710 718 --- /dev/null
711 719 +++ b/s/foobar
712 720 @@ -0,0 +1,4 @@
713 721 +woopwoop
714 722 +
715 723 +foo
716 724 +bar
717 725
718 726 $ hg commit --subrepos -m "Added foobar"
719 727 committing subrepository s
720 728 created new head
721 729
722 730 $ hg diff -c . --subrepos --nodates
723 731 diff -r af6d2edbb0d3 -r 255ee8cf690e .hgsubstate
724 732 --- a/.hgsubstate
725 733 +++ b/.hgsubstate
726 734 @@ -1,1 +1,1 @@
727 735 -32a343883b74769118bb1d3b4b1fbf9156f4dddc s
728 736 +fd4dbf828a5b2fcd36b2bcf21ea773820970d129 s
729 737 diff --git a/s/foobar b/s/foobar
730 738 new file mode 100644
731 739 index 0000000..8a5a5e2
732 740 --- /dev/null
733 741 +++ b/s/foobar
734 742 @@ -0,0 +1,4 @@
735 743 +woopwoop
736 744 +
737 745 +foo
738 746 +bar
739 747
740 748 check output when only diffing the subrepository
741 749 $ hg diff -c . --subrepos s
742 750 diff --git a/s/foobar b/s/foobar
743 751 new file mode 100644
744 752 index 0000000..8a5a5e2
745 753 --- /dev/null
746 754 +++ b/s/foobar
747 755 @@ -0,0 +1,4 @@
748 756 +woopwoop
749 757 +
750 758 +foo
751 759 +bar
752 760
753 761 check output when diffing something else
754 762 $ hg diff -c . --subrepos .hgsubstate --nodates
755 763 diff -r af6d2edbb0d3 -r 255ee8cf690e .hgsubstate
756 764 --- a/.hgsubstate
757 765 +++ b/.hgsubstate
758 766 @@ -1,1 +1,1 @@
759 767 -32a343883b74769118bb1d3b4b1fbf9156f4dddc s
760 768 +fd4dbf828a5b2fcd36b2bcf21ea773820970d129 s
761 769
762 770 add new changes, including whitespace
763 771 $ cd s
764 772 $ cat > foobar << EOF
765 773 > woop woop
766 774 >
767 775 > foo
768 776 > bar
769 777 > EOF
770 778 $ echo foo > barfoo
771 779 $ git add barfoo
772 780 $ cd ..
773 781
774 782 $ hg diff --subrepos --ignore-all-space
775 783 diff --git a/s/barfoo b/s/barfoo
776 784 new file mode 100644
777 785 index 0000000..257cc56
778 786 --- /dev/null
779 787 +++ b/s/barfoo
780 788 @@ -0,0 +1 @@
781 789 +foo
782 790 $ hg diff --subrepos s/foobar
783 791 diff --git a/s/foobar b/s/foobar
784 792 index 8a5a5e2..bd5812a 100644
785 793 --- a/s/foobar
786 794 +++ b/s/foobar
787 795 @@ -1,4 +1,4 @@
788 796 -woopwoop
789 797 +woop woop
790 798
791 799 foo
792 800 bar
793 801
794 802 execute a diffstat
795 803 the output contains a regex, because git 1.7.10 and 1.7.11
796 804 change the amount of whitespace
797 805 $ hg diff --subrepos --stat
798 806 \s*barfoo |\s*1 + (re)
799 807 \s*foobar |\s*2 +- (re)
800 808 2 files changed, 2 insertions\(\+\), 1 deletions?\(-\) (re)
801 809
802 810 adding an include should ignore the other elements
803 811 $ hg diff --subrepos -I s/foobar
804 812 diff --git a/s/foobar b/s/foobar
805 813 index 8a5a5e2..bd5812a 100644
806 814 --- a/s/foobar
807 815 +++ b/s/foobar
808 816 @@ -1,4 +1,4 @@
809 817 -woopwoop
810 818 +woop woop
811 819
812 820 foo
813 821 bar
814 822
815 823 adding an exclude should ignore this element
816 824 $ hg diff --subrepos -X s/foobar
817 825 diff --git a/s/barfoo b/s/barfoo
818 826 new file mode 100644
819 827 index 0000000..257cc56
820 828 --- /dev/null
821 829 +++ b/s/barfoo
822 830 @@ -0,0 +1 @@
823 831 +foo
824 832
825 833 moving a file should show a removal and an add
826 834 $ hg revert --all
827 835 reverting subrepo ../gitroot
828 836 $ cd s
829 837 $ git mv foobar woop
830 838 $ cd ..
831 839 $ hg diff --subrepos
832 840 diff --git a/s/foobar b/s/foobar
833 841 deleted file mode 100644
834 842 index 8a5a5e2..0000000
835 843 --- a/s/foobar
836 844 +++ /dev/null
837 845 @@ -1,4 +0,0 @@
838 846 -woopwoop
839 847 -
840 848 -foo
841 849 -bar
842 850 diff --git a/s/woop b/s/woop
843 851 new file mode 100644
844 852 index 0000000..8a5a5e2
845 853 --- /dev/null
846 854 +++ b/s/woop
847 855 @@ -0,0 +1,4 @@
848 856 +woopwoop
849 857 +
850 858 +foo
851 859 +bar
852 860 $ rm s/woop
853 861
854 862 revert the subrepository
855 863 $ hg revert --all
856 864 reverting subrepo ../gitroot
857 865
858 866 $ hg status --subrepos
859 867 ? s/barfoo
860 868 ? s/foobar.orig
861 869
862 870 $ mv s/foobar.orig s/foobar
863 871
864 872 $ hg revert --no-backup s
865 873 reverting subrepo ../gitroot
866 874
867 875 $ hg status --subrepos
868 876 ? s/barfoo
869 877
870 878 show file at specific revision
871 879 $ cat > s/foobar << EOF
872 880 > woop woop
873 881 > fooo bar
874 882 > EOF
875 883 $ hg commit --subrepos -m "updated foobar"
876 884 committing subrepository s
877 885 $ cat > s/foobar << EOF
878 886 > current foobar
879 887 > (should not be visible using hg cat)
880 888 > EOF
881 889
882 890 $ hg cat -r . s/foobar
883 891 woop woop
884 892 fooo bar (no-eol)
885 893 $ hg cat -r "parents(.)" s/foobar > catparents
886 894
887 895 $ mkdir -p tmp/s
888 896
889 897 $ hg cat -r "parents(.)" --output tmp/%% s/foobar
890 898 $ diff tmp/% catparents
891 899
892 900 $ hg cat -r "parents(.)" --output tmp/%s s/foobar
893 901 $ diff tmp/foobar catparents
894 902
895 903 $ hg cat -r "parents(.)" --output tmp/%d/otherfoobar s/foobar
896 904 $ diff tmp/s/otherfoobar catparents
897 905
898 906 $ hg cat -r "parents(.)" --output tmp/%p s/foobar
899 907 $ diff tmp/s/foobar catparents
900 908
901 909 $ hg cat -r "parents(.)" --output tmp/%H s/foobar
902 910 $ diff tmp/255ee8cf690ec86e99b1e80147ea93ece117cd9d catparents
903 911
904 912 $ hg cat -r "parents(.)" --output tmp/%R s/foobar
905 913 $ diff tmp/10 catparents
906 914
907 915 $ hg cat -r "parents(.)" --output tmp/%h s/foobar
908 916 $ diff tmp/255ee8cf690e catparents
909 917
910 918 $ rm tmp/10
911 919 $ hg cat -r "parents(.)" --output tmp/%r s/foobar
912 920 $ diff tmp/10 catparents
913 921
914 922 $ mkdir tmp/tc
915 923 $ hg cat -r "parents(.)" --output tmp/%b/foobar s/foobar
916 924 $ diff tmp/tc/foobar catparents
917 925
918 926 cleanup
919 927 $ rm -r tmp
920 928 $ rm catparents
921 929
922 930 add git files, using either files or patterns
923 931 $ echo "hsss! hsssssssh!" > s/snake.python
924 932 $ echo "ccc" > s/c.c
925 933 $ echo "cpp" > s/cpp.cpp
926 934
927 935 $ hg add s/snake.python s/c.c s/cpp.cpp
928 936 $ hg st --subrepos s
929 937 M s/foobar
930 938 A s/c.c
931 939 A s/cpp.cpp
932 940 A s/snake.python
933 941 ? s/barfoo
934 942 $ hg revert s
935 943 reverting subrepo ../gitroot
936 944
937 945 $ hg add --subrepos "glob:**.python"
938 946 adding s/snake.python (glob)
939 947 $ hg st --subrepos s
940 948 A s/snake.python
941 949 ? s/barfoo
942 950 ? s/c.c
943 951 ? s/cpp.cpp
944 952 ? s/foobar.orig
945 953 $ hg revert s
946 954 reverting subrepo ../gitroot
947 955
948 956 $ hg add --subrepos s
949 957 adding s/barfoo (glob)
950 958 adding s/c.c (glob)
951 959 adding s/cpp.cpp (glob)
952 960 adding s/foobar.orig (glob)
953 961 adding s/snake.python (glob)
954 962 $ hg st --subrepos s
955 963 A s/barfoo
956 964 A s/c.c
957 965 A s/cpp.cpp
958 966 A s/foobar.orig
959 967 A s/snake.python
960 968 $ hg revert s
961 969 reverting subrepo ../gitroot
962 970 make sure everything is reverted correctly
963 971 $ hg st --subrepos s
964 972 ? s/barfoo
965 973 ? s/c.c
966 974 ? s/cpp.cpp
967 975 ? s/foobar.orig
968 976 ? s/snake.python
969 977
970 978 $ hg add --subrepos --exclude "path:s/c.c"
971 979 adding s/barfoo (glob)
972 980 adding s/cpp.cpp (glob)
973 981 adding s/foobar.orig (glob)
974 982 adding s/snake.python (glob)
975 983 $ hg st --subrepos s
976 984 A s/barfoo
977 985 A s/cpp.cpp
978 986 A s/foobar.orig
979 987 A s/snake.python
980 988 ? s/c.c
981 989 $ hg revert --all -q
982 990
983 991 .hgignore should not have influence in subrepos
984 992 $ cat > .hgignore << EOF
985 993 > syntax: glob
986 994 > *.python
987 995 > EOF
988 996 $ hg add .hgignore
989 997 $ hg add --subrepos "glob:**.python" s/barfoo
990 998 adding s/snake.python (glob)
991 999 $ hg st --subrepos s
992 1000 A s/barfoo
993 1001 A s/snake.python
994 1002 ? s/c.c
995 1003 ? s/cpp.cpp
996 1004 ? s/foobar.orig
997 1005 $ hg revert --all -q
998 1006
999 1007 .gitignore should have influence,
1000 1008 except for explicitly added files (no patterns)
1001 1009 $ cat > s/.gitignore << EOF
1002 1010 > *.python
1003 1011 > EOF
1004 1012 $ hg add s/.gitignore
1005 1013 $ hg st --subrepos s
1006 1014 A s/.gitignore
1007 1015 ? s/barfoo
1008 1016 ? s/c.c
1009 1017 ? s/cpp.cpp
1010 1018 ? s/foobar.orig
1011 1019 $ hg st --subrepos s --all
1012 1020 A s/.gitignore
1013 1021 ? s/barfoo
1014 1022 ? s/c.c
1015 1023 ? s/cpp.cpp
1016 1024 ? s/foobar.orig
1017 1025 I s/snake.python
1018 1026 C s/f
1019 1027 C s/foobar
1020 1028 C s/g
1021 1029 $ hg add --subrepos "glob:**.python"
1022 1030 $ hg st --subrepos s
1023 1031 A s/.gitignore
1024 1032 ? s/barfoo
1025 1033 ? s/c.c
1026 1034 ? s/cpp.cpp
1027 1035 ? s/foobar.orig
1028 1036 $ hg add --subrepos s/snake.python
1029 1037 $ hg st --subrepos s
1030 1038 A s/.gitignore
1031 1039 A s/snake.python
1032 1040 ? s/barfoo
1033 1041 ? s/c.c
1034 1042 ? s/cpp.cpp
1035 1043 ? s/foobar.orig
1036 1044
1037 1045 correctly do a dry run
1038 1046 $ hg add --subrepos s --dry-run
1039 1047 adding s/barfoo (glob)
1040 1048 adding s/c.c (glob)
1041 1049 adding s/cpp.cpp (glob)
1042 1050 adding s/foobar.orig (glob)
1043 1051 $ hg st --subrepos s
1044 1052 A s/.gitignore
1045 1053 A s/snake.python
1046 1054 ? s/barfoo
1047 1055 ? s/c.c
1048 1056 ? s/cpp.cpp
1049 1057 ? s/foobar.orig
1050 1058
1051 1059 error given when adding an already tracked file
1052 1060 $ hg add s/.gitignore
1053 1061 s/.gitignore already tracked!
1054 1062 [1]
1055 1063 $ hg add s/g
1056 1064 s/g already tracked!
1057 1065 [1]
1058 1066
1059 1067 removed files can be re-added
1060 1068 removing files using 'rm' or 'git rm' has the same effect,
1061 1069 since we ignore the staging area
1062 1070 $ hg ci --subrepos -m 'snake'
1063 1071 committing subrepository s
1064 1072 $ cd s
1065 1073 $ rm snake.python
1066 1074 (remove leftover .hg so Mercurial doesn't look for a root here)
1067 1075 $ rm -rf .hg
1068 1076 $ hg status --subrepos --all .
1069 1077 R snake.python
1070 1078 ? barfoo
1071 1079 ? c.c
1072 1080 ? cpp.cpp
1073 1081 ? foobar.orig
1074 1082 C .gitignore
1075 1083 C f
1076 1084 C foobar
1077 1085 C g
1078 1086 $ git rm snake.python
1079 1087 rm 'snake.python'
1080 1088 $ hg status --subrepos --all .
1081 1089 R snake.python
1082 1090 ? barfoo
1083 1091 ? c.c
1084 1092 ? cpp.cpp
1085 1093 ? foobar.orig
1086 1094 C .gitignore
1087 1095 C f
1088 1096 C foobar
1089 1097 C g
1090 1098 $ touch snake.python
1091 1099 $ cd ..
1092 1100 $ hg add s/snake.python
1093 1101 $ hg status -S
1094 1102 M s/snake.python
1095 1103 ? .hgignore
1096 1104 ? s/barfoo
1097 1105 ? s/c.c
1098 1106 ? s/cpp.cpp
1099 1107 ? s/foobar.orig
1100 1108
1101 1109 $ cd ..
@@ -1,579 +1,591 b''
1 1 Create test repository:
2 2
3 3 $ hg init repo
4 4 $ cd repo
5 5 $ echo x1 > x.txt
6 6
7 7 $ hg init foo
8 8 $ cd foo
9 9 $ echo y1 > y.txt
10 10
11 11 $ hg init bar
12 12 $ cd bar
13 13 $ echo z1 > z.txt
14 14
15 15 $ cd ..
16 16 $ echo 'bar = bar' > .hgsub
17 17
18 18 $ cd ..
19 19 $ echo 'foo = foo' > .hgsub
20 20
21 21 Add files --- .hgsub files must go first to trigger subrepos:
22 22
23 23 $ hg add -S .hgsub
24 24 $ hg add -S foo/.hgsub
25 25 $ hg add -S foo/bar
26 26 adding foo/bar/z.txt (glob)
27 27 $ hg add -S
28 28 adding x.txt
29 29 adding foo/y.txt (glob)
30 30
31 31 Test recursive status without committing anything:
32 32
33 33 $ hg status -S
34 34 A .hgsub
35 35 A foo/.hgsub
36 36 A foo/bar/z.txt
37 37 A foo/y.txt
38 38 A x.txt
39 39
40 40 Test recursive diff without committing anything:
41 41
42 42 $ hg diff --nodates -S foo
43 43 diff -r 000000000000 foo/.hgsub
44 44 --- /dev/null
45 45 +++ b/foo/.hgsub
46 46 @@ -0,0 +1,1 @@
47 47 +bar = bar
48 48 diff -r 000000000000 foo/y.txt
49 49 --- /dev/null
50 50 +++ b/foo/y.txt
51 51 @@ -0,0 +1,1 @@
52 52 +y1
53 53 diff -r 000000000000 foo/bar/z.txt
54 54 --- /dev/null
55 55 +++ b/foo/bar/z.txt
56 56 @@ -0,0 +1,1 @@
57 57 +z1
58 58
59 59 Commits:
60 60
61 61 $ hg commit -m fails
62 62 abort: uncommitted changes in subrepository 'foo'
63 63 (use --subrepos for recursive commit)
64 64 [255]
65 65
66 66 The --subrepos flag overwrite the config setting:
67 67
68 68 $ hg commit -m 0-0-0 --config ui.commitsubrepos=No --subrepos
69 69 committing subrepository foo
70 70 committing subrepository foo/bar (glob)
71 71
72 72 $ cd foo
73 73 $ echo y2 >> y.txt
74 74 $ hg commit -m 0-1-0
75 75
76 76 $ cd bar
77 77 $ echo z2 >> z.txt
78 78 $ hg commit -m 0-1-1
79 79
80 80 $ cd ..
81 81 $ hg commit -m 0-2-1
82 82
83 83 $ cd ..
84 84 $ hg commit -m 1-2-1
85 85
86 86 Change working directory:
87 87
88 88 $ echo y3 >> foo/y.txt
89 89 $ echo z3 >> foo/bar/z.txt
90 90 $ hg status -S
91 91 M foo/bar/z.txt
92 92 M foo/y.txt
93 93 $ hg diff --nodates -S
94 94 diff -r d254738c5f5e foo/y.txt
95 95 --- a/foo/y.txt
96 96 +++ b/foo/y.txt
97 97 @@ -1,2 +1,3 @@
98 98 y1
99 99 y2
100 100 +y3
101 101 diff -r 9647f22de499 foo/bar/z.txt
102 102 --- a/foo/bar/z.txt
103 103 +++ b/foo/bar/z.txt
104 104 @@ -1,2 +1,3 @@
105 105 z1
106 106 z2
107 107 +z3
108 108
109 109 Status call crossing repository boundaries:
110 110
111 111 $ hg status -S foo/bar/z.txt
112 112 M foo/bar/z.txt
113 113 $ hg status -S -I 'foo/?.txt'
114 114 M foo/y.txt
115 115 $ hg status -S -I '**/?.txt'
116 116 M foo/bar/z.txt
117 117 M foo/y.txt
118 118 $ hg diff --nodates -S -I '**/?.txt'
119 119 diff -r d254738c5f5e foo/y.txt
120 120 --- a/foo/y.txt
121 121 +++ b/foo/y.txt
122 122 @@ -1,2 +1,3 @@
123 123 y1
124 124 y2
125 125 +y3
126 126 diff -r 9647f22de499 foo/bar/z.txt
127 127 --- a/foo/bar/z.txt
128 128 +++ b/foo/bar/z.txt
129 129 @@ -1,2 +1,3 @@
130 130 z1
131 131 z2
132 132 +z3
133 133
134 134 Status from within a subdirectory:
135 135
136 136 $ mkdir dir
137 137 $ cd dir
138 138 $ echo a1 > a.txt
139 139 $ hg status -S
140 140 M foo/bar/z.txt
141 141 M foo/y.txt
142 142 ? dir/a.txt
143 143 $ hg diff --nodates -S
144 144 diff -r d254738c5f5e foo/y.txt
145 145 --- a/foo/y.txt
146 146 +++ b/foo/y.txt
147 147 @@ -1,2 +1,3 @@
148 148 y1
149 149 y2
150 150 +y3
151 151 diff -r 9647f22de499 foo/bar/z.txt
152 152 --- a/foo/bar/z.txt
153 153 +++ b/foo/bar/z.txt
154 154 @@ -1,2 +1,3 @@
155 155 z1
156 156 z2
157 157 +z3
158 158
159 159 Status with relative path:
160 160
161 161 $ hg status -S ..
162 162 M ../foo/bar/z.txt
163 163 M ../foo/y.txt
164 164 ? a.txt
165 165
166 166 XXX: filtering lfilesrepo.status() in 3.3-rc causes these files to be listed as
167 167 added instead of modified.
168 168 $ hg status -S .. --config extensions.largefiles=
169 169 M ../foo/bar/z.txt
170 170 M ../foo/y.txt
171 171 ? a.txt
172 172
173 173 $ hg diff --nodates -S ..
174 174 diff -r d254738c5f5e foo/y.txt
175 175 --- a/foo/y.txt
176 176 +++ b/foo/y.txt
177 177 @@ -1,2 +1,3 @@
178 178 y1
179 179 y2
180 180 +y3
181 181 diff -r 9647f22de499 foo/bar/z.txt
182 182 --- a/foo/bar/z.txt
183 183 +++ b/foo/bar/z.txt
184 184 @@ -1,2 +1,3 @@
185 185 z1
186 186 z2
187 187 +z3
188 188 $ cd ..
189 189
190 190 Cleanup and final commit:
191 191
192 192 $ rm -r dir
193 193 $ hg commit --subrepos -m 2-3-2
194 194 committing subrepository foo
195 195 committing subrepository foo/bar (glob)
196 196
197 197 Test explicit path commands within subrepos: add/forget
198 198 $ echo z1 > foo/bar/z2.txt
199 199 $ hg status -S
200 200 ? foo/bar/z2.txt
201 201 $ hg add foo/bar/z2.txt
202 202 $ hg status -S
203 203 A foo/bar/z2.txt
204 204 $ hg forget foo/bar/z2.txt
205 205 $ hg status -S
206 206 ? foo/bar/z2.txt
207 207 $ hg forget foo/bar/z2.txt
208 208 not removing foo/bar/z2.txt: file is already untracked (glob)
209 209 [1]
210 210 $ hg status -S
211 211 ? foo/bar/z2.txt
212 212 $ rm foo/bar/z2.txt
213 213
214 214 Log with the relationships between repo and its subrepo:
215 215
216 216 $ hg log --template '{rev}:{node|short} {desc}\n'
217 217 2:1326fa26d0c0 2-3-2
218 218 1:4b3c9ff4f66b 1-2-1
219 219 0:23376cbba0d8 0-0-0
220 220
221 221 $ hg -R foo log --template '{rev}:{node|short} {desc}\n'
222 222 3:65903cebad86 2-3-2
223 223 2:d254738c5f5e 0-2-1
224 224 1:8629ce7dcc39 0-1-0
225 225 0:af048e97ade2 0-0-0
226 226
227 227 $ hg -R foo/bar log --template '{rev}:{node|short} {desc}\n'
228 228 2:31ecbdafd357 2-3-2
229 229 1:9647f22de499 0-1-1
230 230 0:4904098473f9 0-0-0
231 231
232 232 Status between revisions:
233 233
234 234 $ hg status -S
235 235 $ hg status -S --rev 0:1
236 236 M .hgsubstate
237 237 M foo/.hgsubstate
238 238 M foo/bar/z.txt
239 239 M foo/y.txt
240 240 $ hg diff --nodates -S -I '**/?.txt' --rev 0:1
241 241 diff -r af048e97ade2 -r d254738c5f5e foo/y.txt
242 242 --- a/foo/y.txt
243 243 +++ b/foo/y.txt
244 244 @@ -1,1 +1,2 @@
245 245 y1
246 246 +y2
247 247 diff -r 4904098473f9 -r 9647f22de499 foo/bar/z.txt
248 248 --- a/foo/bar/z.txt
249 249 +++ b/foo/bar/z.txt
250 250 @@ -1,1 +1,2 @@
251 251 z1
252 252 +z2
253 253
254 254 Enable progress extension for archive tests:
255 255
256 256 $ cp $HGRCPATH $HGRCPATH.no-progress
257 257 $ cat >> $HGRCPATH <<EOF
258 258 > [extensions]
259 259 > progress =
260 260 > [progress]
261 261 > assume-tty = 1
262 262 > delay = 0
263 263 > # set changedelay really large so we don't see nested topics
264 264 > changedelay = 30000
265 265 > format = topic bar number
266 266 > refresh = 0
267 267 > width = 60
268 268 > EOF
269 269
270 270 Test archiving to a directory tree (the doubled lines in the output
271 271 only show up in the test output, not in real usage):
272 272
273 273 $ hg archive --subrepos ../archive
274 274 \r (no-eol) (esc)
275 275 archiving [ ] 0/3\r (no-eol) (esc)
276 276 archiving [ ] 0/3\r (no-eol) (esc)
277 277 archiving [=============> ] 1/3\r (no-eol) (esc)
278 278 archiving [=============> ] 1/3\r (no-eol) (esc)
279 279 archiving [===========================> ] 2/3\r (no-eol) (esc)
280 280 archiving [===========================> ] 2/3\r (no-eol) (esc)
281 281 archiving [==========================================>] 3/3\r (no-eol) (esc)
282 282 archiving [==========================================>] 3/3\r (no-eol) (esc)
283 283 \r (no-eol) (esc)
284 284 \r (no-eol) (esc)
285 285 archiving (foo) [ ] 0/3\r (no-eol) (esc)
286 286 archiving (foo) [ ] 0/3\r (no-eol) (esc)
287 287 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
288 288 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
289 289 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
290 290 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
291 291 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
292 292 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
293 293 \r (no-eol) (esc)
294 294 \r (no-eol) (esc)
295 295 archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc)
296 296 archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc)
297 297 archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
298 298 archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
299 299 \r (no-eol) (esc)
300 300 $ find ../archive | sort
301 301 ../archive
302 302 ../archive/.hg_archival.txt
303 303 ../archive/.hgsub
304 304 ../archive/.hgsubstate
305 305 ../archive/foo
306 306 ../archive/foo/.hgsub
307 307 ../archive/foo/.hgsubstate
308 308 ../archive/foo/bar
309 309 ../archive/foo/bar/z.txt
310 310 ../archive/foo/y.txt
311 311 ../archive/x.txt
312 312
313 313 Test archiving to zip file (unzip output is unstable):
314 314
315 315 $ hg archive --subrepos ../archive.zip
316 316 \r (no-eol) (esc)
317 317 archiving [ ] 0/3\r (no-eol) (esc)
318 318 archiving [ ] 0/3\r (no-eol) (esc)
319 319 archiving [=============> ] 1/3\r (no-eol) (esc)
320 320 archiving [=============> ] 1/3\r (no-eol) (esc)
321 321 archiving [===========================> ] 2/3\r (no-eol) (esc)
322 322 archiving [===========================> ] 2/3\r (no-eol) (esc)
323 323 archiving [==========================================>] 3/3\r (no-eol) (esc)
324 324 archiving [==========================================>] 3/3\r (no-eol) (esc)
325 325 \r (no-eol) (esc)
326 326 \r (no-eol) (esc)
327 327 archiving (foo) [ ] 0/3\r (no-eol) (esc)
328 328 archiving (foo) [ ] 0/3\r (no-eol) (esc)
329 329 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
330 330 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
331 331 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
332 332 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
333 333 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
334 334 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
335 335 \r (no-eol) (esc)
336 336 \r (no-eol) (esc)
337 337 archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc)
338 338 archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc)
339 339 archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
340 340 archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
341 341 \r (no-eol) (esc)
342 342
343 343 Test archiving a revision that references a subrepo that is not yet
344 344 cloned:
345 345
346 346 #if hardlink
347 347 $ hg clone -U . ../empty
348 348 \r (no-eol) (esc)
349 349 linking [ <=> ] 1\r (no-eol) (esc)
350 350 linking [ <=> ] 2\r (no-eol) (esc)
351 351 linking [ <=> ] 3\r (no-eol) (esc)
352 352 linking [ <=> ] 4\r (no-eol) (esc)
353 353 linking [ <=> ] 5\r (no-eol) (esc)
354 354 linking [ <=> ] 6\r (no-eol) (esc)
355 355 linking [ <=> ] 7\r (no-eol) (esc)
356 356 linking [ <=> ] 8\r (no-eol) (esc)
357 357 \r (no-eol) (esc)
358 358 #else
359 359 $ hg clone -U . ../empty
360 360 \r (no-eol) (esc)
361 361 linking [ <=> ] 1 (no-eol)
362 362 #endif
363 363
364 364 $ cd ../empty
365 365 #if hardlink
366 366 $ hg archive --subrepos -r tip ../archive.tar.gz
367 367 \r (no-eol) (esc)
368 368 archiving [ ] 0/3\r (no-eol) (esc)
369 369 archiving [ ] 0/3\r (no-eol) (esc)
370 370 archiving [=============> ] 1/3\r (no-eol) (esc)
371 371 archiving [=============> ] 1/3\r (no-eol) (esc)
372 372 archiving [===========================> ] 2/3\r (no-eol) (esc)
373 373 archiving [===========================> ] 2/3\r (no-eol) (esc)
374 374 archiving [==========================================>] 3/3\r (no-eol) (esc)
375 375 archiving [==========================================>] 3/3\r (no-eol) (esc)
376 376 \r (no-eol) (esc)
377 377 \r (no-eol) (esc)
378 378 linking [ <=> ] 1\r (no-eol) (esc)
379 379 linking [ <=> ] 2\r (no-eol) (esc)
380 380 linking [ <=> ] 3\r (no-eol) (esc)
381 381 linking [ <=> ] 4\r (no-eol) (esc)
382 382 linking [ <=> ] 5\r (no-eol) (esc)
383 383 linking [ <=> ] 6\r (no-eol) (esc)
384 384 linking [ <=> ] 7\r (no-eol) (esc)
385 385 linking [ <=> ] 8\r (no-eol) (esc)
386 386 \r (no-eol) (esc)
387 387 \r (no-eol) (esc)
388 388 archiving (foo) [ ] 0/3\r (no-eol) (esc)
389 389 archiving (foo) [ ] 0/3\r (no-eol) (esc)
390 390 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
391 391 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
392 392 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
393 393 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
394 394 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
395 395 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
396 396 \r (no-eol) (esc)
397 397 \r (no-eol) (esc)
398 398 linking [ <=> ] 1\r (no-eol) (esc)
399 399 linking [ <=> ] 2\r (no-eol) (esc)
400 400 linking [ <=> ] 3\r (no-eol) (esc)
401 401 linking [ <=> ] 4\r (no-eol) (esc)
402 402 linking [ <=> ] 5\r (no-eol) (esc)
403 403 linking [ <=> ] 6\r (no-eol) (esc)
404 404 \r (no-eol) (esc)
405 405 \r (no-eol) (esc)
406 406 archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc)
407 407 archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc)
408 408 archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
409 409 archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
410 410 \r (no-eol) (esc)
411 411 cloning subrepo foo from $TESTTMP/repo/foo
412 412 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar (glob)
413 413 #else
414 414 Note there's a slight output glitch on non-hardlink systems: the last
415 415 "linking" progress topic never gets closed, leading to slight output corruption on that platform.
416 416 $ hg archive --subrepos -r tip ../archive.tar.gz
417 417 \r (no-eol) (esc)
418 418 archiving [ ] 0/3\r (no-eol) (esc)
419 419 archiving [ ] 0/3\r (no-eol) (esc)
420 420 archiving [=============> ] 1/3\r (no-eol) (esc)
421 421 archiving [=============> ] 1/3\r (no-eol) (esc)
422 422 archiving [===========================> ] 2/3\r (no-eol) (esc)
423 423 archiving [===========================> ] 2/3\r (no-eol) (esc)
424 424 archiving [==========================================>] 3/3\r (no-eol) (esc)
425 425 archiving [==========================================>] 3/3\r (no-eol) (esc)
426 426 \r (no-eol) (esc)
427 427 \r (no-eol) (esc)
428 428 linking [ <=> ] 1\r (no-eol) (esc)
429 429 \r (no-eol) (esc)
430 430 \r (no-eol) (esc)
431 431 \r (no-eol) (esc)
432 432 \r (no-eol) (esc)
433 433 linking [ <=> ] 1cloning subrepo foo from $TESTTMP/repo/foo
434 434 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar (glob)
435 435 #endif
436 436
437 Archive + subrepos uses '/' for all component separators
438
439 $ tar -tzf ../archive.tar.gz | sort
440 archive/.hg_archival.txt
441 archive/.hgsub
442 archive/.hgsubstate
443 archive/foo/.hgsub
444 archive/foo/.hgsubstate
445 archive/foo/bar/z.txt
446 archive/foo/y.txt
447 archive/x.txt
448
437 449 The newly cloned subrepos contain no working copy:
438 450
439 451 $ hg -R foo summary
440 452 parent: -1:000000000000 (no revision checked out)
441 453 branch: default
442 454 commit: (clean)
443 455 update: 4 new changesets (update)
444 456
445 457 Disable progress extension and cleanup:
446 458
447 459 $ mv $HGRCPATH.no-progress $HGRCPATH
448 460
449 461 Test archiving when there is a directory in the way for a subrepo
450 462 created by archive:
451 463
452 464 $ hg clone -U . ../almost-empty
453 465 $ cd ../almost-empty
454 466 $ mkdir foo
455 467 $ echo f > foo/f
456 468 $ hg archive --subrepos -r tip archive
457 469 cloning subrepo foo from $TESTTMP/empty/foo
458 470 abort: destination '$TESTTMP/almost-empty/foo' is not empty (in subrepo foo) (glob)
459 471 [255]
460 472
461 473 Clone and test outgoing:
462 474
463 475 $ cd ..
464 476 $ hg clone repo repo2
465 477 updating to branch default
466 478 cloning subrepo foo from $TESTTMP/repo/foo
467 479 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar (glob)
468 480 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
469 481 $ cd repo2
470 482 $ hg outgoing -S
471 483 comparing with $TESTTMP/repo (glob)
472 484 searching for changes
473 485 no changes found
474 486 comparing with $TESTTMP/repo/foo
475 487 searching for changes
476 488 no changes found
477 489 comparing with $TESTTMP/repo/foo/bar
478 490 searching for changes
479 491 no changes found
480 492 [1]
481 493
482 494 Make nested change:
483 495
484 496 $ echo y4 >> foo/y.txt
485 497 $ hg diff --nodates -S
486 498 diff -r 65903cebad86 foo/y.txt
487 499 --- a/foo/y.txt
488 500 +++ b/foo/y.txt
489 501 @@ -1,3 +1,4 @@
490 502 y1
491 503 y2
492 504 y3
493 505 +y4
494 506 $ hg commit --subrepos -m 3-4-2
495 507 committing subrepository foo
496 508 $ hg outgoing -S
497 509 comparing with $TESTTMP/repo (glob)
498 510 searching for changes
499 511 changeset: 3:2655b8ecc4ee
500 512 tag: tip
501 513 user: test
502 514 date: Thu Jan 01 00:00:00 1970 +0000
503 515 summary: 3-4-2
504 516
505 517 comparing with $TESTTMP/repo/foo
506 518 searching for changes
507 519 changeset: 4:e96193d6cb36
508 520 tag: tip
509 521 user: test
510 522 date: Thu Jan 01 00:00:00 1970 +0000
511 523 summary: 3-4-2
512 524
513 525 comparing with $TESTTMP/repo/foo/bar
514 526 searching for changes
515 527 no changes found
516 528
517 529
518 530 Switch to original repo and setup default path:
519 531
520 532 $ cd ../repo
521 533 $ echo '[paths]' >> .hg/hgrc
522 534 $ echo 'default = ../repo2' >> .hg/hgrc
523 535
524 536 Test incoming:
525 537
526 538 $ hg incoming -S
527 539 comparing with $TESTTMP/repo2 (glob)
528 540 searching for changes
529 541 changeset: 3:2655b8ecc4ee
530 542 tag: tip
531 543 user: test
532 544 date: Thu Jan 01 00:00:00 1970 +0000
533 545 summary: 3-4-2
534 546
535 547 comparing with $TESTTMP/repo2/foo
536 548 searching for changes
537 549 changeset: 4:e96193d6cb36
538 550 tag: tip
539 551 user: test
540 552 date: Thu Jan 01 00:00:00 1970 +0000
541 553 summary: 3-4-2
542 554
543 555 comparing with $TESTTMP/repo2/foo/bar
544 556 searching for changes
545 557 no changes found
546 558
547 559 $ hg incoming -S --bundle incoming.hg
548 560 abort: cannot combine --bundle and --subrepos
549 561 [255]
550 562
551 563 Test missing subrepo:
552 564
553 565 $ rm -r foo
554 566 $ hg status -S
555 567 warning: error "unknown revision '65903cebad86f1a84bd4f1134f62fa7dcb7a1c98'" in subrepository "foo"
556 568
557 569 Issue2619: IndexError: list index out of range on hg add with subrepos
558 570 The subrepo must sorts after the explicit filename.
559 571
560 572 $ cd ..
561 573 $ hg init test
562 574 $ cd test
563 575 $ hg init x
564 576 $ echo abc > abc.txt
565 577 $ hg ci -Am "abc"
566 578 adding abc.txt
567 579 $ echo "x = x" >> .hgsub
568 580 $ hg add .hgsub
569 581 $ touch a x/a
570 582 $ hg add a x/a
571 583
572 584 $ hg ci -Sm "added x"
573 585 committing subrepository x
574 586 $ echo abc > x/a
575 587 $ hg revert --rev '.^' "set:subrepo('glob:x*')"
576 588 abort: subrepository 'x' does not exist in 25ac2c9b3180!
577 589 [255]
578 590
579 591 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now