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