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