##// END OF EJS Templates
cmdutil.bailifchanged: abort for dirty subrepos
Eric Roshan Eisner -
r15231:cd6f10dc default
parent child Browse files
Show More
@@ -1,1260 +1,1264 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 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 node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, tempfile
11 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 12 import match as matchmod
13 13 import subrepo
14 14
15 15 def parsealiases(cmd):
16 16 return cmd.lstrip("^").split("|")
17 17
18 18 def findpossible(cmd, table, strict=False):
19 19 """
20 20 Return cmd -> (aliases, command table entry)
21 21 for each matching command.
22 22 Return debug commands (or their aliases) only if no normal command matches.
23 23 """
24 24 choice = {}
25 25 debugchoice = {}
26 26 for e in table.keys():
27 27 aliases = parsealiases(e)
28 28 found = None
29 29 if cmd in aliases:
30 30 found = cmd
31 31 elif not strict:
32 32 for a in aliases:
33 33 if a.startswith(cmd):
34 34 found = a
35 35 break
36 36 if found is not None:
37 37 if aliases[0].startswith("debug") or found.startswith("debug"):
38 38 debugchoice[found] = (aliases, table[e])
39 39 else:
40 40 choice[found] = (aliases, table[e])
41 41
42 42 if not choice and debugchoice:
43 43 choice = debugchoice
44 44
45 45 return choice
46 46
47 47 def findcmd(cmd, table, strict=True):
48 48 """Return (aliases, command table entry) for command string."""
49 49 choice = findpossible(cmd, table, strict)
50 50
51 51 if cmd in choice:
52 52 return choice[cmd]
53 53
54 54 if len(choice) > 1:
55 55 clist = choice.keys()
56 56 clist.sort()
57 57 raise error.AmbiguousCommand(cmd, clist)
58 58
59 59 if choice:
60 60 return choice.values()[0]
61 61
62 62 raise error.UnknownCommand(cmd)
63 63
64 64 def findrepo(p):
65 65 while not os.path.isdir(os.path.join(p, ".hg")):
66 66 oldp, p = p, os.path.dirname(p)
67 67 if p == oldp:
68 68 return None
69 69
70 70 return p
71 71
72 72 def bailifchanged(repo):
73 73 if repo.dirstate.p2() != nullid:
74 74 raise util.Abort(_('outstanding uncommitted merge'))
75 75 modified, added, removed, deleted = repo.status()[:4]
76 76 if modified or added or removed or deleted:
77 77 raise util.Abort(_("outstanding uncommitted changes"))
78 ctx = repo[None]
79 for s in ctx.substate:
80 if ctx.sub(s).dirty():
81 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
78 82
79 83 def logmessage(ui, opts):
80 84 """ get the log message according to -m and -l option """
81 85 message = opts.get('message')
82 86 logfile = opts.get('logfile')
83 87
84 88 if message and logfile:
85 89 raise util.Abort(_('options --message and --logfile are mutually '
86 90 'exclusive'))
87 91 if not message and logfile:
88 92 try:
89 93 if logfile == '-':
90 94 message = ui.fin.read()
91 95 else:
92 96 message = '\n'.join(util.readfile(logfile).splitlines())
93 97 except IOError, inst:
94 98 raise util.Abort(_("can't read commit message '%s': %s") %
95 99 (logfile, inst.strerror))
96 100 return message
97 101
98 102 def loglimit(opts):
99 103 """get the log limit according to option -l/--limit"""
100 104 limit = opts.get('limit')
101 105 if limit:
102 106 try:
103 107 limit = int(limit)
104 108 except ValueError:
105 109 raise util.Abort(_('limit must be a positive integer'))
106 110 if limit <= 0:
107 111 raise util.Abort(_('limit must be positive'))
108 112 else:
109 113 limit = None
110 114 return limit
111 115
112 116 def makefilename(repo, pat, node, desc=None,
113 117 total=None, seqno=None, revwidth=None, pathname=None):
114 118 node_expander = {
115 119 'H': lambda: hex(node),
116 120 'R': lambda: str(repo.changelog.rev(node)),
117 121 'h': lambda: short(node),
118 122 'm': lambda: re.sub('[^\w]', '_', str(desc))
119 123 }
120 124 expander = {
121 125 '%': lambda: '%',
122 126 'b': lambda: os.path.basename(repo.root),
123 127 }
124 128
125 129 try:
126 130 if node:
127 131 expander.update(node_expander)
128 132 if node:
129 133 expander['r'] = (lambda:
130 134 str(repo.changelog.rev(node)).zfill(revwidth or 0))
131 135 if total is not None:
132 136 expander['N'] = lambda: str(total)
133 137 if seqno is not None:
134 138 expander['n'] = lambda: str(seqno)
135 139 if total is not None and seqno is not None:
136 140 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
137 141 if pathname is not None:
138 142 expander['s'] = lambda: os.path.basename(pathname)
139 143 expander['d'] = lambda: os.path.dirname(pathname) or '.'
140 144 expander['p'] = lambda: pathname
141 145
142 146 newname = []
143 147 patlen = len(pat)
144 148 i = 0
145 149 while i < patlen:
146 150 c = pat[i]
147 151 if c == '%':
148 152 i += 1
149 153 c = pat[i]
150 154 c = expander[c]()
151 155 newname.append(c)
152 156 i += 1
153 157 return ''.join(newname)
154 158 except KeyError, inst:
155 159 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
156 160 inst.args[0])
157 161
158 162 def makefileobj(repo, pat, node=None, desc=None, total=None,
159 163 seqno=None, revwidth=None, mode='wb', pathname=None):
160 164
161 165 writable = mode not in ('r', 'rb')
162 166
163 167 if not pat or pat == '-':
164 168 fp = writable and repo.ui.fout or repo.ui.fin
165 169 if util.safehasattr(fp, 'fileno'):
166 170 return os.fdopen(os.dup(fp.fileno()), mode)
167 171 else:
168 172 # if this fp can't be duped properly, return
169 173 # a dummy object that can be closed
170 174 class wrappedfileobj(object):
171 175 noop = lambda x: None
172 176 def __init__(self, f):
173 177 self.f = f
174 178 def __getattr__(self, attr):
175 179 if attr == 'close':
176 180 return self.noop
177 181 else:
178 182 return getattr(self.f, attr)
179 183
180 184 return wrappedfileobj(fp)
181 185 if util.safehasattr(pat, 'write') and writable:
182 186 return pat
183 187 if util.safehasattr(pat, 'read') and 'r' in mode:
184 188 return pat
185 189 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
186 190 pathname),
187 191 mode)
188 192
189 193 def openrevlog(repo, cmd, file_, opts):
190 194 """opens the changelog, manifest, a filelog or a given revlog"""
191 195 cl = opts['changelog']
192 196 mf = opts['manifest']
193 197 msg = None
194 198 if cl and mf:
195 199 msg = _('cannot specify --changelog and --manifest at the same time')
196 200 elif cl or mf:
197 201 if file_:
198 202 msg = _('cannot specify filename with --changelog or --manifest')
199 203 elif not repo:
200 204 msg = _('cannot specify --changelog or --manifest '
201 205 'without a repository')
202 206 if msg:
203 207 raise util.Abort(msg)
204 208
205 209 r = None
206 210 if repo:
207 211 if cl:
208 212 r = repo.changelog
209 213 elif mf:
210 214 r = repo.manifest
211 215 elif file_:
212 216 filelog = repo.file(file_)
213 217 if len(filelog):
214 218 r = filelog
215 219 if not r:
216 220 if not file_:
217 221 raise error.CommandError(cmd, _('invalid arguments'))
218 222 if not os.path.isfile(file_):
219 223 raise util.Abort(_("revlog '%s' not found") % file_)
220 224 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
221 225 file_[:-2] + ".i")
222 226 return r
223 227
224 228 def copy(ui, repo, pats, opts, rename=False):
225 229 # called with the repo lock held
226 230 #
227 231 # hgsep => pathname that uses "/" to separate directories
228 232 # ossep => pathname that uses os.sep to separate directories
229 233 cwd = repo.getcwd()
230 234 targets = {}
231 235 after = opts.get("after")
232 236 dryrun = opts.get("dry_run")
233 237 wctx = repo[None]
234 238
235 239 def walkpat(pat):
236 240 srcs = []
237 241 badstates = after and '?' or '?r'
238 242 m = scmutil.match(repo[None], [pat], opts, globbed=True)
239 243 for abs in repo.walk(m):
240 244 state = repo.dirstate[abs]
241 245 rel = m.rel(abs)
242 246 exact = m.exact(abs)
243 247 if state in badstates:
244 248 if exact and state == '?':
245 249 ui.warn(_('%s: not copying - file is not managed\n') % rel)
246 250 if exact and state == 'r':
247 251 ui.warn(_('%s: not copying - file has been marked for'
248 252 ' remove\n') % rel)
249 253 continue
250 254 # abs: hgsep
251 255 # rel: ossep
252 256 srcs.append((abs, rel, exact))
253 257 return srcs
254 258
255 259 # abssrc: hgsep
256 260 # relsrc: ossep
257 261 # otarget: ossep
258 262 def copyfile(abssrc, relsrc, otarget, exact):
259 263 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
260 264 reltarget = repo.pathto(abstarget, cwd)
261 265 target = repo.wjoin(abstarget)
262 266 src = repo.wjoin(abssrc)
263 267 state = repo.dirstate[abstarget]
264 268
265 269 scmutil.checkportable(ui, abstarget)
266 270
267 271 # check for collisions
268 272 prevsrc = targets.get(abstarget)
269 273 if prevsrc is not None:
270 274 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
271 275 (reltarget, repo.pathto(abssrc, cwd),
272 276 repo.pathto(prevsrc, cwd)))
273 277 return
274 278
275 279 # check for overwrites
276 280 exists = os.path.lexists(target)
277 281 if not after and exists or after and state in 'mn':
278 282 if not opts['force']:
279 283 ui.warn(_('%s: not overwriting - file exists\n') %
280 284 reltarget)
281 285 return
282 286
283 287 if after:
284 288 if not exists:
285 289 if rename:
286 290 ui.warn(_('%s: not recording move - %s does not exist\n') %
287 291 (relsrc, reltarget))
288 292 else:
289 293 ui.warn(_('%s: not recording copy - %s does not exist\n') %
290 294 (relsrc, reltarget))
291 295 return
292 296 elif not dryrun:
293 297 try:
294 298 if exists:
295 299 os.unlink(target)
296 300 targetdir = os.path.dirname(target) or '.'
297 301 if not os.path.isdir(targetdir):
298 302 os.makedirs(targetdir)
299 303 util.copyfile(src, target)
300 304 srcexists = True
301 305 except IOError, inst:
302 306 if inst.errno == errno.ENOENT:
303 307 ui.warn(_('%s: deleted in working copy\n') % relsrc)
304 308 srcexists = False
305 309 else:
306 310 ui.warn(_('%s: cannot copy - %s\n') %
307 311 (relsrc, inst.strerror))
308 312 return True # report a failure
309 313
310 314 if ui.verbose or not exact:
311 315 if rename:
312 316 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
313 317 else:
314 318 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
315 319
316 320 targets[abstarget] = abssrc
317 321
318 322 # fix up dirstate
319 323 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
320 324 dryrun=dryrun, cwd=cwd)
321 325 if rename and not dryrun:
322 326 if not after and srcexists:
323 327 util.unlinkpath(repo.wjoin(abssrc))
324 328 wctx.forget([abssrc])
325 329
326 330 # pat: ossep
327 331 # dest ossep
328 332 # srcs: list of (hgsep, hgsep, ossep, bool)
329 333 # return: function that takes hgsep and returns ossep
330 334 def targetpathfn(pat, dest, srcs):
331 335 if os.path.isdir(pat):
332 336 abspfx = scmutil.canonpath(repo.root, cwd, pat)
333 337 abspfx = util.localpath(abspfx)
334 338 if destdirexists:
335 339 striplen = len(os.path.split(abspfx)[0])
336 340 else:
337 341 striplen = len(abspfx)
338 342 if striplen:
339 343 striplen += len(os.sep)
340 344 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
341 345 elif destdirexists:
342 346 res = lambda p: os.path.join(dest,
343 347 os.path.basename(util.localpath(p)))
344 348 else:
345 349 res = lambda p: dest
346 350 return res
347 351
348 352 # pat: ossep
349 353 # dest ossep
350 354 # srcs: list of (hgsep, hgsep, ossep, bool)
351 355 # return: function that takes hgsep and returns ossep
352 356 def targetpathafterfn(pat, dest, srcs):
353 357 if matchmod.patkind(pat):
354 358 # a mercurial pattern
355 359 res = lambda p: os.path.join(dest,
356 360 os.path.basename(util.localpath(p)))
357 361 else:
358 362 abspfx = scmutil.canonpath(repo.root, cwd, pat)
359 363 if len(abspfx) < len(srcs[0][0]):
360 364 # A directory. Either the target path contains the last
361 365 # component of the source path or it does not.
362 366 def evalpath(striplen):
363 367 score = 0
364 368 for s in srcs:
365 369 t = os.path.join(dest, util.localpath(s[0])[striplen:])
366 370 if os.path.lexists(t):
367 371 score += 1
368 372 return score
369 373
370 374 abspfx = util.localpath(abspfx)
371 375 striplen = len(abspfx)
372 376 if striplen:
373 377 striplen += len(os.sep)
374 378 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
375 379 score = evalpath(striplen)
376 380 striplen1 = len(os.path.split(abspfx)[0])
377 381 if striplen1:
378 382 striplen1 += len(os.sep)
379 383 if evalpath(striplen1) > score:
380 384 striplen = striplen1
381 385 res = lambda p: os.path.join(dest,
382 386 util.localpath(p)[striplen:])
383 387 else:
384 388 # a file
385 389 if destdirexists:
386 390 res = lambda p: os.path.join(dest,
387 391 os.path.basename(util.localpath(p)))
388 392 else:
389 393 res = lambda p: dest
390 394 return res
391 395
392 396
393 397 pats = scmutil.expandpats(pats)
394 398 if not pats:
395 399 raise util.Abort(_('no source or destination specified'))
396 400 if len(pats) == 1:
397 401 raise util.Abort(_('no destination specified'))
398 402 dest = pats.pop()
399 403 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
400 404 if not destdirexists:
401 405 if len(pats) > 1 or matchmod.patkind(pats[0]):
402 406 raise util.Abort(_('with multiple sources, destination must be an '
403 407 'existing directory'))
404 408 if util.endswithsep(dest):
405 409 raise util.Abort(_('destination %s is not a directory') % dest)
406 410
407 411 tfn = targetpathfn
408 412 if after:
409 413 tfn = targetpathafterfn
410 414 copylist = []
411 415 for pat in pats:
412 416 srcs = walkpat(pat)
413 417 if not srcs:
414 418 continue
415 419 copylist.append((tfn(pat, dest, srcs), srcs))
416 420 if not copylist:
417 421 raise util.Abort(_('no files to copy'))
418 422
419 423 errors = 0
420 424 for targetpath, srcs in copylist:
421 425 for abssrc, relsrc, exact in srcs:
422 426 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
423 427 errors += 1
424 428
425 429 if errors:
426 430 ui.warn(_('(consider using --after)\n'))
427 431
428 432 return errors != 0
429 433
430 434 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
431 435 runargs=None, appendpid=False):
432 436 '''Run a command as a service.'''
433 437
434 438 if opts['daemon'] and not opts['daemon_pipefds']:
435 439 # Signal child process startup with file removal
436 440 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
437 441 os.close(lockfd)
438 442 try:
439 443 if not runargs:
440 444 runargs = util.hgcmd() + sys.argv[1:]
441 445 runargs.append('--daemon-pipefds=%s' % lockpath)
442 446 # Don't pass --cwd to the child process, because we've already
443 447 # changed directory.
444 448 for i in xrange(1, len(runargs)):
445 449 if runargs[i].startswith('--cwd='):
446 450 del runargs[i]
447 451 break
448 452 elif runargs[i].startswith('--cwd'):
449 453 del runargs[i:i + 2]
450 454 break
451 455 def condfn():
452 456 return not os.path.exists(lockpath)
453 457 pid = util.rundetached(runargs, condfn)
454 458 if pid < 0:
455 459 raise util.Abort(_('child process failed to start'))
456 460 finally:
457 461 try:
458 462 os.unlink(lockpath)
459 463 except OSError, e:
460 464 if e.errno != errno.ENOENT:
461 465 raise
462 466 if parentfn:
463 467 return parentfn(pid)
464 468 else:
465 469 return
466 470
467 471 if initfn:
468 472 initfn()
469 473
470 474 if opts['pid_file']:
471 475 mode = appendpid and 'a' or 'w'
472 476 fp = open(opts['pid_file'], mode)
473 477 fp.write(str(os.getpid()) + '\n')
474 478 fp.close()
475 479
476 480 if opts['daemon_pipefds']:
477 481 lockpath = opts['daemon_pipefds']
478 482 try:
479 483 os.setsid()
480 484 except AttributeError:
481 485 pass
482 486 os.unlink(lockpath)
483 487 util.hidewindow()
484 488 sys.stdout.flush()
485 489 sys.stderr.flush()
486 490
487 491 nullfd = os.open(util.nulldev, os.O_RDWR)
488 492 logfilefd = nullfd
489 493 if logfile:
490 494 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
491 495 os.dup2(nullfd, 0)
492 496 os.dup2(logfilefd, 1)
493 497 os.dup2(logfilefd, 2)
494 498 if nullfd not in (0, 1, 2):
495 499 os.close(nullfd)
496 500 if logfile and logfilefd not in (0, 1, 2):
497 501 os.close(logfilefd)
498 502
499 503 if runfn:
500 504 return runfn()
501 505
502 506 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
503 507 opts=None):
504 508 '''export changesets as hg patches.'''
505 509
506 510 total = len(revs)
507 511 revwidth = max([len(str(rev)) for rev in revs])
508 512
509 513 def single(rev, seqno, fp):
510 514 ctx = repo[rev]
511 515 node = ctx.node()
512 516 parents = [p.node() for p in ctx.parents() if p]
513 517 branch = ctx.branch()
514 518 if switch_parent:
515 519 parents.reverse()
516 520 prev = (parents and parents[0]) or nullid
517 521
518 522 shouldclose = False
519 523 if not fp:
520 524 desc_lines = ctx.description().rstrip().split('\n')
521 525 desc = desc_lines[0] #Commit always has a first line.
522 526 fp = makefileobj(repo, template, node, desc=desc, total=total,
523 527 seqno=seqno, revwidth=revwidth, mode='ab')
524 528 if fp != template:
525 529 shouldclose = True
526 530 if fp != sys.stdout and util.safehasattr(fp, 'name'):
527 531 repo.ui.note("%s\n" % fp.name)
528 532
529 533 fp.write("# HG changeset patch\n")
530 534 fp.write("# User %s\n" % ctx.user())
531 535 fp.write("# Date %d %d\n" % ctx.date())
532 536 if branch and branch != 'default':
533 537 fp.write("# Branch %s\n" % branch)
534 538 fp.write("# Node ID %s\n" % hex(node))
535 539 fp.write("# Parent %s\n" % hex(prev))
536 540 if len(parents) > 1:
537 541 fp.write("# Parent %s\n" % hex(parents[1]))
538 542 fp.write(ctx.description().rstrip())
539 543 fp.write("\n\n")
540 544
541 545 for chunk in patch.diff(repo, prev, node, opts=opts):
542 546 fp.write(chunk)
543 547
544 548 if shouldclose:
545 549 fp.close()
546 550
547 551 for seqno, rev in enumerate(revs):
548 552 single(rev, seqno + 1, fp)
549 553
550 554 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
551 555 changes=None, stat=False, fp=None, prefix='',
552 556 listsubrepos=False):
553 557 '''show diff or diffstat.'''
554 558 if fp is None:
555 559 write = ui.write
556 560 else:
557 561 def write(s, **kw):
558 562 fp.write(s)
559 563
560 564 if stat:
561 565 diffopts = diffopts.copy(context=0)
562 566 width = 80
563 567 if not ui.plain():
564 568 width = ui.termwidth()
565 569 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
566 570 prefix=prefix)
567 571 for chunk, label in patch.diffstatui(util.iterlines(chunks),
568 572 width=width,
569 573 git=diffopts.git):
570 574 write(chunk, label=label)
571 575 else:
572 576 for chunk, label in patch.diffui(repo, node1, node2, match,
573 577 changes, diffopts, prefix=prefix):
574 578 write(chunk, label=label)
575 579
576 580 if listsubrepos:
577 581 ctx1 = repo[node1]
578 582 ctx2 = repo[node2]
579 583 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
580 584 if node2 is not None:
581 585 node2 = ctx2.substate[subpath][1]
582 586 submatch = matchmod.narrowmatcher(subpath, match)
583 587 sub.diff(diffopts, node2, submatch, changes=changes,
584 588 stat=stat, fp=fp, prefix=prefix)
585 589
586 590 class changeset_printer(object):
587 591 '''show changeset information when templating not requested.'''
588 592
589 593 def __init__(self, ui, repo, patch, diffopts, buffered):
590 594 self.ui = ui
591 595 self.repo = repo
592 596 self.buffered = buffered
593 597 self.patch = patch
594 598 self.diffopts = diffopts
595 599 self.header = {}
596 600 self.hunk = {}
597 601 self.lastheader = None
598 602 self.footer = None
599 603
600 604 def flush(self, rev):
601 605 if rev in self.header:
602 606 h = self.header[rev]
603 607 if h != self.lastheader:
604 608 self.lastheader = h
605 609 self.ui.write(h)
606 610 del self.header[rev]
607 611 if rev in self.hunk:
608 612 self.ui.write(self.hunk[rev])
609 613 del self.hunk[rev]
610 614 return 1
611 615 return 0
612 616
613 617 def close(self):
614 618 if self.footer:
615 619 self.ui.write(self.footer)
616 620
617 621 def show(self, ctx, copies=None, matchfn=None, **props):
618 622 if self.buffered:
619 623 self.ui.pushbuffer()
620 624 self._show(ctx, copies, matchfn, props)
621 625 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
622 626 else:
623 627 self._show(ctx, copies, matchfn, props)
624 628
625 629 def _show(self, ctx, copies, matchfn, props):
626 630 '''show a single changeset or file revision'''
627 631 changenode = ctx.node()
628 632 rev = ctx.rev()
629 633
630 634 if self.ui.quiet:
631 635 self.ui.write("%d:%s\n" % (rev, short(changenode)),
632 636 label='log.node')
633 637 return
634 638
635 639 log = self.repo.changelog
636 640 date = util.datestr(ctx.date())
637 641
638 642 hexfunc = self.ui.debugflag and hex or short
639 643
640 644 parents = [(p, hexfunc(log.node(p)))
641 645 for p in self._meaningful_parentrevs(log, rev)]
642 646
643 647 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
644 648 label='log.changeset')
645 649
646 650 branch = ctx.branch()
647 651 # don't show the default branch name
648 652 if branch != 'default':
649 653 self.ui.write(_("branch: %s\n") % branch,
650 654 label='log.branch')
651 655 for bookmark in self.repo.nodebookmarks(changenode):
652 656 self.ui.write(_("bookmark: %s\n") % bookmark,
653 657 label='log.bookmark')
654 658 for tag in self.repo.nodetags(changenode):
655 659 self.ui.write(_("tag: %s\n") % tag,
656 660 label='log.tag')
657 661 for parent in parents:
658 662 self.ui.write(_("parent: %d:%s\n") % parent,
659 663 label='log.parent')
660 664
661 665 if self.ui.debugflag:
662 666 mnode = ctx.manifestnode()
663 667 self.ui.write(_("manifest: %d:%s\n") %
664 668 (self.repo.manifest.rev(mnode), hex(mnode)),
665 669 label='ui.debug log.manifest')
666 670 self.ui.write(_("user: %s\n") % ctx.user(),
667 671 label='log.user')
668 672 self.ui.write(_("date: %s\n") % date,
669 673 label='log.date')
670 674
671 675 if self.ui.debugflag:
672 676 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
673 677 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
674 678 files):
675 679 if value:
676 680 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
677 681 label='ui.debug log.files')
678 682 elif ctx.files() and self.ui.verbose:
679 683 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
680 684 label='ui.note log.files')
681 685 if copies and self.ui.verbose:
682 686 copies = ['%s (%s)' % c for c in copies]
683 687 self.ui.write(_("copies: %s\n") % ' '.join(copies),
684 688 label='ui.note log.copies')
685 689
686 690 extra = ctx.extra()
687 691 if extra and self.ui.debugflag:
688 692 for key, value in sorted(extra.items()):
689 693 self.ui.write(_("extra: %s=%s\n")
690 694 % (key, value.encode('string_escape')),
691 695 label='ui.debug log.extra')
692 696
693 697 description = ctx.description().strip()
694 698 if description:
695 699 if self.ui.verbose:
696 700 self.ui.write(_("description:\n"),
697 701 label='ui.note log.description')
698 702 self.ui.write(description,
699 703 label='ui.note log.description')
700 704 self.ui.write("\n\n")
701 705 else:
702 706 self.ui.write(_("summary: %s\n") %
703 707 description.splitlines()[0],
704 708 label='log.summary')
705 709 self.ui.write("\n")
706 710
707 711 self.showpatch(changenode, matchfn)
708 712
709 713 def showpatch(self, node, matchfn):
710 714 if not matchfn:
711 715 matchfn = self.patch
712 716 if matchfn:
713 717 stat = self.diffopts.get('stat')
714 718 diff = self.diffopts.get('patch')
715 719 diffopts = patch.diffopts(self.ui, self.diffopts)
716 720 prev = self.repo.changelog.parents(node)[0]
717 721 if stat:
718 722 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
719 723 match=matchfn, stat=True)
720 724 if diff:
721 725 if stat:
722 726 self.ui.write("\n")
723 727 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
724 728 match=matchfn, stat=False)
725 729 self.ui.write("\n")
726 730
727 731 def _meaningful_parentrevs(self, log, rev):
728 732 """Return list of meaningful (or all if debug) parentrevs for rev.
729 733
730 734 For merges (two non-nullrev revisions) both parents are meaningful.
731 735 Otherwise the first parent revision is considered meaningful if it
732 736 is not the preceding revision.
733 737 """
734 738 parents = log.parentrevs(rev)
735 739 if not self.ui.debugflag and parents[1] == nullrev:
736 740 if parents[0] >= rev - 1:
737 741 parents = []
738 742 else:
739 743 parents = [parents[0]]
740 744 return parents
741 745
742 746
743 747 class changeset_templater(changeset_printer):
744 748 '''format changeset information.'''
745 749
746 750 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
747 751 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
748 752 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
749 753 defaulttempl = {
750 754 'parent': '{rev}:{node|formatnode} ',
751 755 'manifest': '{rev}:{node|formatnode}',
752 756 'file_copy': '{name} ({source})',
753 757 'extra': '{key}={value|stringescape}'
754 758 }
755 759 # filecopy is preserved for compatibility reasons
756 760 defaulttempl['filecopy'] = defaulttempl['file_copy']
757 761 self.t = templater.templater(mapfile, {'formatnode': formatnode},
758 762 cache=defaulttempl)
759 763 self.cache = {}
760 764
761 765 def use_template(self, t):
762 766 '''set template string to use'''
763 767 self.t.cache['changeset'] = t
764 768
765 769 def _meaningful_parentrevs(self, ctx):
766 770 """Return list of meaningful (or all if debug) parentrevs for rev.
767 771 """
768 772 parents = ctx.parents()
769 773 if len(parents) > 1:
770 774 return parents
771 775 if self.ui.debugflag:
772 776 return [parents[0], self.repo['null']]
773 777 if parents[0].rev() >= ctx.rev() - 1:
774 778 return []
775 779 return parents
776 780
777 781 def _show(self, ctx, copies, matchfn, props):
778 782 '''show a single changeset or file revision'''
779 783
780 784 showlist = templatekw.showlist
781 785
782 786 # showparents() behaviour depends on ui trace level which
783 787 # causes unexpected behaviours at templating level and makes
784 788 # it harder to extract it in a standalone function. Its
785 789 # behaviour cannot be changed so leave it here for now.
786 790 def showparents(**args):
787 791 ctx = args['ctx']
788 792 parents = [[('rev', p.rev()), ('node', p.hex())]
789 793 for p in self._meaningful_parentrevs(ctx)]
790 794 return showlist('parent', parents, **args)
791 795
792 796 props = props.copy()
793 797 props.update(templatekw.keywords)
794 798 props['parents'] = showparents
795 799 props['templ'] = self.t
796 800 props['ctx'] = ctx
797 801 props['repo'] = self.repo
798 802 props['revcache'] = {'copies': copies}
799 803 props['cache'] = self.cache
800 804
801 805 # find correct templates for current mode
802 806
803 807 tmplmodes = [
804 808 (True, None),
805 809 (self.ui.verbose, 'verbose'),
806 810 (self.ui.quiet, 'quiet'),
807 811 (self.ui.debugflag, 'debug'),
808 812 ]
809 813
810 814 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
811 815 for mode, postfix in tmplmodes:
812 816 for type in types:
813 817 cur = postfix and ('%s_%s' % (type, postfix)) or type
814 818 if mode and cur in self.t:
815 819 types[type] = cur
816 820
817 821 try:
818 822
819 823 # write header
820 824 if types['header']:
821 825 h = templater.stringify(self.t(types['header'], **props))
822 826 if self.buffered:
823 827 self.header[ctx.rev()] = h
824 828 else:
825 829 if self.lastheader != h:
826 830 self.lastheader = h
827 831 self.ui.write(h)
828 832
829 833 # write changeset metadata, then patch if requested
830 834 key = types['changeset']
831 835 self.ui.write(templater.stringify(self.t(key, **props)))
832 836 self.showpatch(ctx.node(), matchfn)
833 837
834 838 if types['footer']:
835 839 if not self.footer:
836 840 self.footer = templater.stringify(self.t(types['footer'],
837 841 **props))
838 842
839 843 except KeyError, inst:
840 844 msg = _("%s: no key named '%s'")
841 845 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
842 846 except SyntaxError, inst:
843 847 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
844 848
845 849 def show_changeset(ui, repo, opts, buffered=False):
846 850 """show one changeset using template or regular display.
847 851
848 852 Display format will be the first non-empty hit of:
849 853 1. option 'template'
850 854 2. option 'style'
851 855 3. [ui] setting 'logtemplate'
852 856 4. [ui] setting 'style'
853 857 If all of these values are either the unset or the empty string,
854 858 regular display via changeset_printer() is done.
855 859 """
856 860 # options
857 861 patch = False
858 862 if opts.get('patch') or opts.get('stat'):
859 863 patch = scmutil.matchall(repo)
860 864
861 865 tmpl = opts.get('template')
862 866 style = None
863 867 if tmpl:
864 868 tmpl = templater.parsestring(tmpl, quoted=False)
865 869 else:
866 870 style = opts.get('style')
867 871
868 872 # ui settings
869 873 if not (tmpl or style):
870 874 tmpl = ui.config('ui', 'logtemplate')
871 875 if tmpl:
872 876 tmpl = templater.parsestring(tmpl)
873 877 else:
874 878 style = util.expandpath(ui.config('ui', 'style', ''))
875 879
876 880 if not (tmpl or style):
877 881 return changeset_printer(ui, repo, patch, opts, buffered)
878 882
879 883 mapfile = None
880 884 if style and not tmpl:
881 885 mapfile = style
882 886 if not os.path.split(mapfile)[0]:
883 887 mapname = (templater.templatepath('map-cmdline.' + mapfile)
884 888 or templater.templatepath(mapfile))
885 889 if mapname:
886 890 mapfile = mapname
887 891
888 892 try:
889 893 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
890 894 except SyntaxError, inst:
891 895 raise util.Abort(inst.args[0])
892 896 if tmpl:
893 897 t.use_template(tmpl)
894 898 return t
895 899
896 900 def finddate(ui, repo, date):
897 901 """Find the tipmost changeset that matches the given date spec"""
898 902
899 903 df = util.matchdate(date)
900 904 m = scmutil.matchall(repo)
901 905 results = {}
902 906
903 907 def prep(ctx, fns):
904 908 d = ctx.date()
905 909 if df(d[0]):
906 910 results[ctx.rev()] = d
907 911
908 912 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
909 913 rev = ctx.rev()
910 914 if rev in results:
911 915 ui.status(_("Found revision %s from %s\n") %
912 916 (rev, util.datestr(results[rev])))
913 917 return str(rev)
914 918
915 919 raise util.Abort(_("revision matching date not found"))
916 920
917 921 def walkchangerevs(repo, match, opts, prepare):
918 922 '''Iterate over files and the revs in which they changed.
919 923
920 924 Callers most commonly need to iterate backwards over the history
921 925 in which they are interested. Doing so has awful (quadratic-looking)
922 926 performance, so we use iterators in a "windowed" way.
923 927
924 928 We walk a window of revisions in the desired order. Within the
925 929 window, we first walk forwards to gather data, then in the desired
926 930 order (usually backwards) to display it.
927 931
928 932 This function returns an iterator yielding contexts. Before
929 933 yielding each context, the iterator will first call the prepare
930 934 function on each context in the window in forward order.'''
931 935
932 936 def increasing_windows(start, end, windowsize=8, sizelimit=512):
933 937 if start < end:
934 938 while start < end:
935 939 yield start, min(windowsize, end - start)
936 940 start += windowsize
937 941 if windowsize < sizelimit:
938 942 windowsize *= 2
939 943 else:
940 944 while start > end:
941 945 yield start, min(windowsize, start - end - 1)
942 946 start -= windowsize
943 947 if windowsize < sizelimit:
944 948 windowsize *= 2
945 949
946 950 follow = opts.get('follow') or opts.get('follow_first')
947 951
948 952 if not len(repo):
949 953 return []
950 954
951 955 if follow:
952 956 defrange = '%s:0' % repo['.'].rev()
953 957 else:
954 958 defrange = '-1:0'
955 959 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
956 960 if not revs:
957 961 return []
958 962 wanted = set()
959 963 slowpath = match.anypats() or (match.files() and opts.get('removed'))
960 964 fncache = {}
961 965 change = util.cachefunc(repo.changectx)
962 966
963 967 # First step is to fill wanted, the set of revisions that we want to yield.
964 968 # When it does not induce extra cost, we also fill fncache for revisions in
965 969 # wanted: a cache of filenames that were changed (ctx.files()) and that
966 970 # match the file filtering conditions.
967 971
968 972 if not slowpath and not match.files():
969 973 # No files, no patterns. Display all revs.
970 974 wanted = set(revs)
971 975 copies = []
972 976
973 977 if not slowpath:
974 978 # We only have to read through the filelog to find wanted revisions
975 979
976 980 minrev, maxrev = min(revs), max(revs)
977 981 def filerevgen(filelog, last):
978 982 """
979 983 Only files, no patterns. Check the history of each file.
980 984
981 985 Examines filelog entries within minrev, maxrev linkrev range
982 986 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
983 987 tuples in backwards order
984 988 """
985 989 cl_count = len(repo)
986 990 revs = []
987 991 for j in xrange(0, last + 1):
988 992 linkrev = filelog.linkrev(j)
989 993 if linkrev < minrev:
990 994 continue
991 995 # only yield rev for which we have the changelog, it can
992 996 # happen while doing "hg log" during a pull or commit
993 997 if linkrev >= cl_count:
994 998 break
995 999
996 1000 parentlinkrevs = []
997 1001 for p in filelog.parentrevs(j):
998 1002 if p != nullrev:
999 1003 parentlinkrevs.append(filelog.linkrev(p))
1000 1004 n = filelog.node(j)
1001 1005 revs.append((linkrev, parentlinkrevs,
1002 1006 follow and filelog.renamed(n)))
1003 1007
1004 1008 return reversed(revs)
1005 1009 def iterfiles():
1006 1010 for filename in match.files():
1007 1011 yield filename, None
1008 1012 for filename_node in copies:
1009 1013 yield filename_node
1010 1014 for file_, node in iterfiles():
1011 1015 filelog = repo.file(file_)
1012 1016 if not len(filelog):
1013 1017 if node is None:
1014 1018 # A zero count may be a directory or deleted file, so
1015 1019 # try to find matching entries on the slow path.
1016 1020 if follow:
1017 1021 raise util.Abort(
1018 1022 _('cannot follow nonexistent file: "%s"') % file_)
1019 1023 slowpath = True
1020 1024 break
1021 1025 else:
1022 1026 continue
1023 1027
1024 1028 if node is None:
1025 1029 last = len(filelog) - 1
1026 1030 else:
1027 1031 last = filelog.rev(node)
1028 1032
1029 1033
1030 1034 # keep track of all ancestors of the file
1031 1035 ancestors = set([filelog.linkrev(last)])
1032 1036
1033 1037 # iterate from latest to oldest revision
1034 1038 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1035 1039 if not follow:
1036 1040 if rev > maxrev:
1037 1041 continue
1038 1042 else:
1039 1043 # Note that last might not be the first interesting
1040 1044 # rev to us:
1041 1045 # if the file has been changed after maxrev, we'll
1042 1046 # have linkrev(last) > maxrev, and we still need
1043 1047 # to explore the file graph
1044 1048 if rev not in ancestors:
1045 1049 continue
1046 1050 # XXX insert 1327 fix here
1047 1051 if flparentlinkrevs:
1048 1052 ancestors.update(flparentlinkrevs)
1049 1053
1050 1054 fncache.setdefault(rev, []).append(file_)
1051 1055 wanted.add(rev)
1052 1056 if copied:
1053 1057 copies.append(copied)
1054 1058 if slowpath:
1055 1059 # We have to read the changelog to match filenames against
1056 1060 # changed files
1057 1061
1058 1062 if follow:
1059 1063 raise util.Abort(_('can only follow copies/renames for explicit '
1060 1064 'filenames'))
1061 1065
1062 1066 # The slow path checks files modified in every changeset.
1063 1067 for i in sorted(revs):
1064 1068 ctx = change(i)
1065 1069 matches = filter(match, ctx.files())
1066 1070 if matches:
1067 1071 fncache[i] = matches
1068 1072 wanted.add(i)
1069 1073
1070 1074 class followfilter(object):
1071 1075 def __init__(self, onlyfirst=False):
1072 1076 self.startrev = nullrev
1073 1077 self.roots = set()
1074 1078 self.onlyfirst = onlyfirst
1075 1079
1076 1080 def match(self, rev):
1077 1081 def realparents(rev):
1078 1082 if self.onlyfirst:
1079 1083 return repo.changelog.parentrevs(rev)[0:1]
1080 1084 else:
1081 1085 return filter(lambda x: x != nullrev,
1082 1086 repo.changelog.parentrevs(rev))
1083 1087
1084 1088 if self.startrev == nullrev:
1085 1089 self.startrev = rev
1086 1090 return True
1087 1091
1088 1092 if rev > self.startrev:
1089 1093 # forward: all descendants
1090 1094 if not self.roots:
1091 1095 self.roots.add(self.startrev)
1092 1096 for parent in realparents(rev):
1093 1097 if parent in self.roots:
1094 1098 self.roots.add(rev)
1095 1099 return True
1096 1100 else:
1097 1101 # backwards: all parents
1098 1102 if not self.roots:
1099 1103 self.roots.update(realparents(self.startrev))
1100 1104 if rev in self.roots:
1101 1105 self.roots.remove(rev)
1102 1106 self.roots.update(realparents(rev))
1103 1107 return True
1104 1108
1105 1109 return False
1106 1110
1107 1111 # it might be worthwhile to do this in the iterator if the rev range
1108 1112 # is descending and the prune args are all within that range
1109 1113 for rev in opts.get('prune', ()):
1110 1114 rev = repo.changelog.rev(repo.lookup(rev))
1111 1115 ff = followfilter()
1112 1116 stop = min(revs[0], revs[-1])
1113 1117 for x in xrange(rev, stop - 1, -1):
1114 1118 if ff.match(x):
1115 1119 wanted.discard(x)
1116 1120
1117 1121 # Now that wanted is correctly initialized, we can iterate over the
1118 1122 # revision range, yielding only revisions in wanted.
1119 1123 def iterate():
1120 1124 if follow and not match.files():
1121 1125 ff = followfilter(onlyfirst=opts.get('follow_first'))
1122 1126 def want(rev):
1123 1127 return ff.match(rev) and rev in wanted
1124 1128 else:
1125 1129 def want(rev):
1126 1130 return rev in wanted
1127 1131
1128 1132 for i, window in increasing_windows(0, len(revs)):
1129 1133 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1130 1134 for rev in sorted(nrevs):
1131 1135 fns = fncache.get(rev)
1132 1136 ctx = change(rev)
1133 1137 if not fns:
1134 1138 def fns_generator():
1135 1139 for f in ctx.files():
1136 1140 if match(f):
1137 1141 yield f
1138 1142 fns = fns_generator()
1139 1143 prepare(ctx, fns)
1140 1144 for rev in nrevs:
1141 1145 yield change(rev)
1142 1146 return iterate()
1143 1147
1144 1148 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1145 1149 join = lambda f: os.path.join(prefix, f)
1146 1150 bad = []
1147 1151 oldbad = match.bad
1148 1152 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1149 1153 names = []
1150 1154 wctx = repo[None]
1151 1155 cca = None
1152 1156 abort, warn = scmutil.checkportabilityalert(ui)
1153 1157 if abort or warn:
1154 1158 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1155 1159 for f in repo.walk(match):
1156 1160 exact = match.exact(f)
1157 1161 if exact or f not in repo.dirstate:
1158 1162 if cca:
1159 1163 cca(f)
1160 1164 names.append(f)
1161 1165 if ui.verbose or not exact:
1162 1166 ui.status(_('adding %s\n') % match.rel(join(f)))
1163 1167
1164 1168 if listsubrepos:
1165 1169 for subpath in wctx.substate:
1166 1170 sub = wctx.sub(subpath)
1167 1171 try:
1168 1172 submatch = matchmod.narrowmatcher(subpath, match)
1169 1173 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1170 1174 except error.LookupError:
1171 1175 ui.status(_("skipping missing subrepository: %s\n")
1172 1176 % join(subpath))
1173 1177
1174 1178 if not dryrun:
1175 1179 rejected = wctx.add(names, prefix)
1176 1180 bad.extend(f for f in rejected if f in match.files())
1177 1181 return bad
1178 1182
1179 1183 def duplicatecopies(repo, rev, p1, p2):
1180 1184 "Reproduce copies found in the source revision in the dirstate for grafts"
1181 1185 # Here we simulate the copies and renames in the source changeset
1182 1186 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
1183 1187 m1 = repo[rev].manifest()
1184 1188 m2 = repo[p1].manifest()
1185 1189 for k, v in cop.iteritems():
1186 1190 if k in m1:
1187 1191 if v in m1 or v in m2:
1188 1192 repo.dirstate.copy(v, k)
1189 1193 if v in m2 and v not in m1 and k in m2:
1190 1194 repo.dirstate.remove(v)
1191 1195
1192 1196 def commit(ui, repo, commitfunc, pats, opts):
1193 1197 '''commit the specified files or all outstanding changes'''
1194 1198 date = opts.get('date')
1195 1199 if date:
1196 1200 opts['date'] = util.parsedate(date)
1197 1201 message = logmessage(ui, opts)
1198 1202
1199 1203 # extract addremove carefully -- this function can be called from a command
1200 1204 # that doesn't support addremove
1201 1205 if opts.get('addremove'):
1202 1206 scmutil.addremove(repo, pats, opts)
1203 1207
1204 1208 return commitfunc(ui, repo, message,
1205 1209 scmutil.match(repo[None], pats, opts), opts)
1206 1210
1207 1211 def commiteditor(repo, ctx, subs):
1208 1212 if ctx.description():
1209 1213 return ctx.description()
1210 1214 return commitforceeditor(repo, ctx, subs)
1211 1215
1212 1216 def commitforceeditor(repo, ctx, subs):
1213 1217 edittext = []
1214 1218 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1215 1219 if ctx.description():
1216 1220 edittext.append(ctx.description())
1217 1221 edittext.append("")
1218 1222 edittext.append("") # Empty line between message and comments.
1219 1223 edittext.append(_("HG: Enter commit message."
1220 1224 " Lines beginning with 'HG:' are removed."))
1221 1225 edittext.append(_("HG: Leave message empty to abort commit."))
1222 1226 edittext.append("HG: --")
1223 1227 edittext.append(_("HG: user: %s") % ctx.user())
1224 1228 if ctx.p2():
1225 1229 edittext.append(_("HG: branch merge"))
1226 1230 if ctx.branch():
1227 1231 edittext.append(_("HG: branch '%s'") % ctx.branch())
1228 1232 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1229 1233 edittext.extend([_("HG: added %s") % f for f in added])
1230 1234 edittext.extend([_("HG: changed %s") % f for f in modified])
1231 1235 edittext.extend([_("HG: removed %s") % f for f in removed])
1232 1236 if not added and not modified and not removed:
1233 1237 edittext.append(_("HG: no files changed"))
1234 1238 edittext.append("")
1235 1239 # run editor in the repository root
1236 1240 olddir = os.getcwd()
1237 1241 os.chdir(repo.root)
1238 1242 text = repo.ui.edit("\n".join(edittext), ctx.user())
1239 1243 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1240 1244 os.chdir(olddir)
1241 1245
1242 1246 if not text.strip():
1243 1247 raise util.Abort(_("empty commit message"))
1244 1248
1245 1249 return text
1246 1250
1247 1251 def command(table):
1248 1252 '''returns a function object bound to table which can be used as
1249 1253 a decorator for populating table as a command table'''
1250 1254
1251 1255 def cmd(name, options, synopsis=None):
1252 1256 def decorator(func):
1253 1257 if synopsis:
1254 1258 table[name] = func, options[:], synopsis
1255 1259 else:
1256 1260 table[name] = func, options[:]
1257 1261 return func
1258 1262 return decorator
1259 1263
1260 1264 return cmd
@@ -1,859 +1,867 b''
1 1 $ rm -rf sub
2 2 $ mkdir sub
3 3 $ cd sub
4 4 $ hg init t
5 5 $ cd t
6 6
7 7 first revision, no sub
8 8
9 9 $ echo a > a
10 10 $ hg ci -Am0
11 11 adding a
12 12
13 13 add first sub
14 14
15 15 $ echo s = s > .hgsub
16 16 $ hg add .hgsub
17 17 $ hg init s
18 18 $ echo a > s/a
19 19
20 20 Issue2232: committing a subrepo without .hgsub
21 21
22 22 $ hg ci -mbad s
23 23 abort: can't commit subrepos without .hgsub
24 24 [255]
25 25
26 26 $ hg -R s ci -Ams0
27 27 adding a
28 28 $ hg sum
29 29 parent: 0:f7b1eb17ad24 tip
30 30 0
31 31 branch: default
32 32 commit: 1 added, 1 subrepos
33 33 update: (current)
34 34 $ hg ci -m1
35 35 committing subrepository s
36 36
37 37 Issue2022: update -C
38 38
39 39 $ echo b > s/a
40 40 $ hg sum
41 41 parent: 1:7cf8cfea66e4 tip
42 42 1
43 43 branch: default
44 44 commit: 1 subrepos
45 45 update: (current)
46 46 $ hg co -C 1
47 47 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 48 $ hg sum
49 49 parent: 1:7cf8cfea66e4 tip
50 50 1
51 51 branch: default
52 52 commit: (clean)
53 53 update: (current)
54 54
55 commands that require a clean repo should respect subrepos
56
57 $ echo b >> s/a
58 $ hg backout tip
59 abort: uncommitted changes in subrepo s
60 [255]
61 $ hg revert -C -R s s/a
62
55 63 add sub sub
56 64
57 65 $ echo ss = ss > s/.hgsub
58 66 $ hg init s/ss
59 67 $ echo a > s/ss/a
60 68 $ hg -R s add s/.hgsub
61 69 $ hg -R s/ss add s/ss/a
62 70 $ hg sum
63 71 parent: 1:7cf8cfea66e4 tip
64 72 1
65 73 branch: default
66 74 commit: 1 subrepos
67 75 update: (current)
68 76 $ hg ci -m2
69 77 committing subrepository s
70 78 committing subrepository s/ss
71 79 $ hg sum
72 80 parent: 2:df30734270ae tip
73 81 2
74 82 branch: default
75 83 commit: (clean)
76 84 update: (current)
77 85
78 86 bump sub rev (and check it is ignored by ui.commitsubrepos)
79 87
80 88 $ echo b > s/a
81 89 $ hg -R s ci -ms1
82 90 $ hg --config ui.commitsubrepos=no ci -m3
83 91 committing subrepository s
84 92
85 93 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
86 94
87 95 $ echo c > s/a
88 96 $ hg --config ui.commitsubrepos=no ci -m4
89 97 abort: uncommitted changes in subrepo s
90 98 [255]
91 99 $ hg ci -m4
92 100 committing subrepository s
93 101 $ hg tip -R s
94 102 changeset: 3:1c833a7a9e3a
95 103 tag: tip
96 104 user: test
97 105 date: Thu Jan 01 00:00:00 1970 +0000
98 106 summary: 4
99 107
100 108
101 109 check caching
102 110
103 111 $ hg co 0
104 112 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
105 113 $ hg debugsub
106 114
107 115 restore
108 116
109 117 $ hg co
110 118 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 119 $ hg debugsub
112 120 path s
113 121 source s
114 122 revision 1c833a7a9e3a4445c711aaf0f012379cd0d4034e
115 123
116 124 new branch for merge tests
117 125
118 126 $ hg co 1
119 127 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 128 $ echo t = t >> .hgsub
121 129 $ hg init t
122 130 $ echo t > t/t
123 131 $ hg -R t add t
124 132 adding t/t
125 133
126 134 5
127 135
128 136 $ hg ci -m5 # add sub
129 137 committing subrepository t
130 138 created new head
131 139 $ echo t2 > t/t
132 140
133 141 6
134 142
135 143 $ hg st -R s
136 144 $ hg ci -m6 # change sub
137 145 committing subrepository t
138 146 $ hg debugsub
139 147 path s
140 148 source s
141 149 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
142 150 path t
143 151 source t
144 152 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
145 153 $ echo t3 > t/t
146 154
147 155 7
148 156
149 157 $ hg ci -m7 # change sub again for conflict test
150 158 committing subrepository t
151 159 $ hg rm .hgsub
152 160
153 161 8
154 162
155 163 $ hg ci -m8 # remove sub
156 164
157 165 merge tests
158 166
159 167 $ hg co -C 3
160 168 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
161 169 $ hg merge 5 # test adding
162 170 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
163 171 (branch merge, don't forget to commit)
164 172 $ hg debugsub
165 173 path s
166 174 source s
167 175 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
168 176 path t
169 177 source t
170 178 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
171 179 $ hg ci -m9
172 180 created new head
173 181 $ hg merge 6 --debug # test change
174 182 searching for copies back to rev 2
175 183 resolving manifests
176 184 overwrite None partial False
177 185 ancestor 1f14a2e2d3ec local f0d2028bf86d+ remote 1831e14459c4
178 186 .hgsubstate: versions differ -> m
179 187 updating: .hgsubstate 1/1 files (100.00%)
180 188 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
181 189 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
182 190 getting subrepo t
183 191 resolving manifests
184 192 overwrite True partial False
185 193 ancestor 60ca1237c194+ local 60ca1237c194+ remote 6747d179aa9a
186 194 t: remote is newer -> g
187 195 updating: t 1/1 files (100.00%)
188 196 getting t
189 197 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
190 198 (branch merge, don't forget to commit)
191 199 $ hg debugsub
192 200 path s
193 201 source s
194 202 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
195 203 path t
196 204 source t
197 205 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
198 206 $ echo conflict > t/t
199 207 $ hg ci -m10
200 208 committing subrepository t
201 209 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
202 210 searching for copies back to rev 2
203 211 resolving manifests
204 212 overwrite None partial False
205 213 ancestor 1831e14459c4 local e45c8b14af55+ remote f94576341bcf
206 214 .hgsubstate: versions differ -> m
207 215 updating: .hgsubstate 1/1 files (100.00%)
208 216 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
209 217 subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
210 218 merging subrepo t
211 219 searching for copies back to rev 2
212 220 resolving manifests
213 221 overwrite None partial False
214 222 ancestor 6747d179aa9a local 20a0db6fbf6c+ remote 7af322bc1198
215 223 t: versions differ -> m
216 224 preserving t for resolve of t
217 225 updating: t 1/1 files (100.00%)
218 226 picked tool 'internal:merge' for t (binary False symlink False)
219 227 merging t
220 228 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
221 229 warning: conflicts during merge.
222 230 merging t failed!
223 231 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
224 232 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
225 233 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
226 234 (branch merge, don't forget to commit)
227 235
228 236 should conflict
229 237
230 238 $ cat t/t
231 239 <<<<<<< local
232 240 conflict
233 241 =======
234 242 t3
235 243 >>>>>>> other
236 244
237 245 clone
238 246
239 247 $ cd ..
240 248 $ hg clone t tc
241 249 updating to branch default
242 250 cloning subrepo s from $TESTTMP/sub/t/s
243 251 cloning subrepo s/ss from $TESTTMP/sub/t/s/ss
244 252 cloning subrepo t from $TESTTMP/sub/t/t
245 253 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
246 254 $ cd tc
247 255 $ hg debugsub
248 256 path s
249 257 source s
250 258 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
251 259 path t
252 260 source t
253 261 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
254 262
255 263 push
256 264
257 265 $ echo bah > t/t
258 266 $ hg ci -m11
259 267 committing subrepository t
260 268 $ hg push
261 269 pushing to $TESTTMP/sub/t
262 270 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
263 271 searching for changes
264 272 no changes found
265 273 pushing subrepo s to $TESTTMP/sub/t/s
266 274 searching for changes
267 275 no changes found
268 276 pushing subrepo t to $TESTTMP/sub/t/t
269 277 searching for changes
270 278 adding changesets
271 279 adding manifests
272 280 adding file changes
273 281 added 1 changesets with 1 changes to 1 files
274 282 searching for changes
275 283 adding changesets
276 284 adding manifests
277 285 adding file changes
278 286 added 1 changesets with 1 changes to 1 files
279 287
280 288 push -f
281 289
282 290 $ echo bah > s/a
283 291 $ hg ci -m12
284 292 committing subrepository s
285 293 $ hg push
286 294 pushing to $TESTTMP/sub/t
287 295 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
288 296 searching for changes
289 297 no changes found
290 298 pushing subrepo s to $TESTTMP/sub/t/s
291 299 searching for changes
292 300 abort: push creates new remote head 12a213df6fa9!
293 301 (did you forget to merge? use push -f to force)
294 302 [255]
295 303 $ hg push -f
296 304 pushing to $TESTTMP/sub/t
297 305 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss
298 306 searching for changes
299 307 no changes found
300 308 pushing subrepo s to $TESTTMP/sub/t/s
301 309 searching for changes
302 310 adding changesets
303 311 adding manifests
304 312 adding file changes
305 313 added 1 changesets with 1 changes to 1 files (+1 heads)
306 314 pushing subrepo t to $TESTTMP/sub/t/t
307 315 searching for changes
308 316 no changes found
309 317 searching for changes
310 318 adding changesets
311 319 adding manifests
312 320 adding file changes
313 321 added 1 changesets with 1 changes to 1 files
314 322
315 323 update
316 324
317 325 $ cd ../t
318 326 $ hg up -C # discard our earlier merge
319 327 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
320 328 $ echo blah > t/t
321 329 $ hg ci -m13
322 330 committing subrepository t
323 331
324 332 pull
325 333
326 334 $ cd ../tc
327 335 $ hg pull
328 336 pulling from $TESTTMP/sub/t
329 337 searching for changes
330 338 adding changesets
331 339 adding manifests
332 340 adding file changes
333 341 added 1 changesets with 1 changes to 1 files
334 342 (run 'hg update' to get a working copy)
335 343
336 344 should pull t
337 345
338 346 $ hg up
339 347 pulling subrepo t from $TESTTMP/sub/t/t
340 348 searching for changes
341 349 adding changesets
342 350 adding manifests
343 351 adding file changes
344 352 added 1 changesets with 1 changes to 1 files
345 353 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
346 354 $ cat t/t
347 355 blah
348 356
349 357 bogus subrepo path aborts
350 358
351 359 $ echo 'bogus=[boguspath' >> .hgsub
352 360 $ hg ci -m 'bogus subrepo path'
353 361 abort: missing ] in subrepo source
354 362 [255]
355 363
356 364 Issue1986: merge aborts when trying to merge a subrepo that
357 365 shouldn't need merging
358 366
359 367 # subrepo layout
360 368 #
361 369 # o 5 br
362 370 # /|
363 371 # o | 4 default
364 372 # | |
365 373 # | o 3 br
366 374 # |/|
367 375 # o | 2 default
368 376 # | |
369 377 # | o 1 br
370 378 # |/
371 379 # o 0 default
372 380
373 381 $ cd ..
374 382 $ rm -rf sub
375 383 $ hg init main
376 384 $ cd main
377 385 $ hg init s
378 386 $ cd s
379 387 $ echo a > a
380 388 $ hg ci -Am1
381 389 adding a
382 390 $ hg branch br
383 391 marked working directory as branch br
384 392 $ echo a >> a
385 393 $ hg ci -m1
386 394 $ hg up default
387 395 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
388 396 $ echo b > b
389 397 $ hg ci -Am1
390 398 adding b
391 399 $ hg up br
392 400 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
393 401 $ hg merge tip
394 402 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
395 403 (branch merge, don't forget to commit)
396 404 $ hg ci -m1
397 405 $ hg up 2
398 406 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
399 407 $ echo c > c
400 408 $ hg ci -Am1
401 409 adding c
402 410 $ hg up 3
403 411 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
404 412 $ hg merge 4
405 413 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
406 414 (branch merge, don't forget to commit)
407 415 $ hg ci -m1
408 416
409 417 # main repo layout:
410 418 #
411 419 # * <-- try to merge default into br again
412 420 # .`|
413 421 # . o 5 br --> substate = 5
414 422 # . |
415 423 # o | 4 default --> substate = 4
416 424 # | |
417 425 # | o 3 br --> substate = 2
418 426 # |/|
419 427 # o | 2 default --> substate = 2
420 428 # | |
421 429 # | o 1 br --> substate = 3
422 430 # |/
423 431 # o 0 default --> substate = 2
424 432
425 433 $ cd ..
426 434 $ echo 's = s' > .hgsub
427 435 $ hg -R s up 2
428 436 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
429 437 $ hg ci -Am1
430 438 adding .hgsub
431 439 committing subrepository s
432 440 $ hg branch br
433 441 marked working directory as branch br
434 442 $ echo b > b
435 443 $ hg -R s up 3
436 444 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 445 $ hg ci -Am1
438 446 adding b
439 447 committing subrepository s
440 448 $ hg up default
441 449 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
442 450 $ echo c > c
443 451 $ hg ci -Am1
444 452 adding c
445 453 $ hg up 1
446 454 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
447 455 $ hg merge 2
448 456 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
449 457 (branch merge, don't forget to commit)
450 458 $ hg ci -m1
451 459 $ hg up 2
452 460 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
453 461 $ hg -R s up 4
454 462 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
455 463 $ echo d > d
456 464 $ hg ci -Am1
457 465 adding d
458 466 committing subrepository s
459 467 $ hg up 3
460 468 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
461 469 $ hg -R s up 5
462 470 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
463 471 $ echo e > e
464 472 $ hg ci -Am1
465 473 adding e
466 474 committing subrepository s
467 475
468 476 $ hg up 5
469 477 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
470 478 $ hg merge 4 # try to merge default into br again
471 479 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
472 480 (branch merge, don't forget to commit)
473 481 $ cd ..
474 482
475 483 test subrepo delete from .hgsubstate
476 484
477 485 $ hg init testdelete
478 486 $ mkdir testdelete/nested testdelete/nested2
479 487 $ hg init testdelete/nested
480 488 $ hg init testdelete/nested2
481 489 $ echo test > testdelete/nested/foo
482 490 $ echo test > testdelete/nested2/foo
483 491 $ hg -R testdelete/nested add
484 492 adding testdelete/nested/foo
485 493 $ hg -R testdelete/nested2 add
486 494 adding testdelete/nested2/foo
487 495 $ hg -R testdelete/nested ci -m test
488 496 $ hg -R testdelete/nested2 ci -m test
489 497 $ echo nested = nested > testdelete/.hgsub
490 498 $ echo nested2 = nested2 >> testdelete/.hgsub
491 499 $ hg -R testdelete add
492 500 adding testdelete/.hgsub
493 501 $ hg -R testdelete ci -m "nested 1 & 2 added"
494 502 committing subrepository nested
495 503 committing subrepository nested2
496 504 $ echo nested = nested > testdelete/.hgsub
497 505 $ hg -R testdelete ci -m "nested 2 deleted"
498 506 $ cat testdelete/.hgsubstate
499 507 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
500 508 $ hg -R testdelete remove testdelete/.hgsub
501 509 $ hg -R testdelete ci -m ".hgsub deleted"
502 510 $ cat testdelete/.hgsubstate
503 511 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
504 512
505 513 test repository cloning
506 514
507 515 $ mkdir mercurial mercurial2
508 516 $ hg init nested_absolute
509 517 $ echo test > nested_absolute/foo
510 518 $ hg -R nested_absolute add
511 519 adding nested_absolute/foo
512 520 $ hg -R nested_absolute ci -mtest
513 521 $ cd mercurial
514 522 $ hg init nested_relative
515 523 $ echo test2 > nested_relative/foo2
516 524 $ hg -R nested_relative add
517 525 adding nested_relative/foo2
518 526 $ hg -R nested_relative ci -mtest2
519 527 $ hg init main
520 528 $ echo "nested_relative = ../nested_relative" > main/.hgsub
521 529 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
522 530 $ hg -R main add
523 531 adding main/.hgsub
524 532 $ hg -R main ci -m "add subrepos"
525 533 committing subrepository nested_absolute
526 534 committing subrepository nested_relative
527 535 $ cd ..
528 536 $ hg clone mercurial/main mercurial2/main
529 537 updating to branch default
530 538 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
531 539 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
532 540 > mercurial2/main/nested_relative/.hg/hgrc
533 541 [paths]
534 542 default = $TESTTMP/sub/mercurial/nested_absolute
535 543 [paths]
536 544 default = $TESTTMP/sub/mercurial/nested_relative
537 545 $ rm -rf mercurial mercurial2
538 546
539 547 Issue1977: multirepo push should fail if subrepo push fails
540 548
541 549 $ hg init repo
542 550 $ hg init repo/s
543 551 $ echo a > repo/s/a
544 552 $ hg -R repo/s ci -Am0
545 553 adding a
546 554 $ echo s = s > repo/.hgsub
547 555 $ hg -R repo ci -Am1
548 556 adding .hgsub
549 557 committing subrepository s
550 558 $ hg clone repo repo2
551 559 updating to branch default
552 560 cloning subrepo s from $TESTTMP/sub/repo/s
553 561 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
554 562 $ hg -q -R repo2 pull -u
555 563 $ echo 1 > repo2/s/a
556 564 $ hg -R repo2/s ci -m2
557 565 $ hg -q -R repo2/s push
558 566 $ hg -R repo2/s up -C 0
559 567 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
560 568 $ echo 2 > repo2/s/a
561 569 $ hg -R repo2/s ci -m3
562 570 created new head
563 571 $ hg -R repo2 ci -m3
564 572 committing subrepository s
565 573 $ hg -q -R repo2 push
566 574 abort: push creates new remote head 9d66565e64e1!
567 575 (did you forget to merge? use push -f to force)
568 576 [255]
569 577 $ hg -R repo update
570 578 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
571 579 $ rm -rf repo2 repo
572 580
573 581
574 582 Issue1852 subrepos with relative paths always push/pull relative to default
575 583
576 584 Prepare a repo with subrepo
577 585
578 586 $ hg init issue1852a
579 587 $ cd issue1852a
580 588 $ hg init sub/repo
581 589 $ echo test > sub/repo/foo
582 590 $ hg -R sub/repo add sub/repo/foo
583 591 $ echo sub/repo = sub/repo > .hgsub
584 592 $ hg add .hgsub
585 593 $ hg ci -mtest
586 594 committing subrepository sub/repo
587 595 $ echo test >> sub/repo/foo
588 596 $ hg ci -mtest
589 597 committing subrepository sub/repo
590 598 $ cd ..
591 599
592 600 Create repo without default path, pull top repo, and see what happens on update
593 601
594 602 $ hg init issue1852b
595 603 $ hg -R issue1852b pull issue1852a
596 604 pulling from issue1852a
597 605 requesting all changes
598 606 adding changesets
599 607 adding manifests
600 608 adding file changes
601 609 added 2 changesets with 3 changes to 2 files
602 610 (run 'hg update' to get a working copy)
603 611 $ hg -R issue1852b update
604 612 abort: default path for subrepository sub/repo not found
605 613 [255]
606 614
607 615 Pull -u now doesn't help
608 616
609 617 $ hg -R issue1852b pull -u issue1852a
610 618 pulling from issue1852a
611 619 searching for changes
612 620 no changes found
613 621
614 622 Try the same, but with pull -u
615 623
616 624 $ hg init issue1852c
617 625 $ hg -R issue1852c pull -r0 -u issue1852a
618 626 pulling from issue1852a
619 627 adding changesets
620 628 adding manifests
621 629 adding file changes
622 630 added 1 changesets with 2 changes to 2 files
623 631 cloning subrepo sub/repo from issue1852a/sub/repo
624 632 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
625 633
626 634 Try to push from the other side
627 635
628 636 $ hg -R issue1852a push `pwd`/issue1852c
629 637 pushing to $TESTTMP/sub/issue1852c
630 638 pushing subrepo sub/repo to $TESTTMP/sub/issue1852c/sub/repo
631 639 searching for changes
632 640 no changes found
633 641 searching for changes
634 642 adding changesets
635 643 adding manifests
636 644 adding file changes
637 645 added 1 changesets with 1 changes to 1 files
638 646
639 647 Incoming and outgoing should not use the default path:
640 648
641 649 $ hg clone -q issue1852a issue1852d
642 650 $ hg -R issue1852d outgoing --subrepos issue1852c
643 651 comparing with issue1852c
644 652 searching for changes
645 653 no changes found
646 654 comparing with issue1852c/sub/repo
647 655 searching for changes
648 656 no changes found
649 657 [1]
650 658 $ hg -R issue1852d incoming --subrepos issue1852c
651 659 comparing with issue1852c
652 660 searching for changes
653 661 no changes found
654 662 comparing with issue1852c/sub/repo
655 663 searching for changes
656 664 no changes found
657 665 [1]
658 666
659 667 Check status of files when none of them belong to the first
660 668 subrepository:
661 669
662 670 $ hg init subrepo-status
663 671 $ cd subrepo-status
664 672 $ hg init subrepo-1
665 673 $ hg init subrepo-2
666 674 $ cd subrepo-2
667 675 $ touch file
668 676 $ hg add file
669 677 $ cd ..
670 678 $ echo subrepo-1 = subrepo-1 > .hgsub
671 679 $ echo subrepo-2 = subrepo-2 >> .hgsub
672 680 $ hg add .hgsub
673 681 $ hg ci -m 'Added subrepos'
674 682 committing subrepository subrepo-1
675 683 committing subrepository subrepo-2
676 684 $ hg st subrepo-2/file
677 685
678 686 Check hg update --clean
679 687 $ cd $TESTTMP/sub/t
680 688 $ rm -r t/t.orig
681 689 $ hg status -S --all
682 690 C .hgsub
683 691 C .hgsubstate
684 692 C a
685 693 C s/.hgsub
686 694 C s/.hgsubstate
687 695 C s/a
688 696 C s/ss/a
689 697 C t/t
690 698 $ echo c1 > s/a
691 699 $ cd s
692 700 $ echo c1 > b
693 701 $ echo c1 > c
694 702 $ hg add b
695 703 $ cd ..
696 704 $ hg status -S
697 705 M s/a
698 706 A s/b
699 707 ? s/c
700 708 $ hg update -C
701 709 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
702 710 $ hg status -S
703 711 ? s/b
704 712 ? s/c
705 713
706 714 Sticky subrepositories, no changes
707 715 $ cd $TESTTMP/sub/t
708 716 $ hg id
709 717 925c17564ef8 tip
710 718 $ hg -R s id
711 719 12a213df6fa9 tip
712 720 $ hg -R t id
713 721 52c0adc0515a tip
714 722 $ hg update 11
715 723 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
716 724 $ hg id
717 725 365661e5936a
718 726 $ hg -R s id
719 727 fc627a69481f
720 728 $ hg -R t id
721 729 e95bcfa18a35
722 730
723 731 Sticky subrepositorys, file changes
724 732 $ touch s/f1
725 733 $ touch t/f1
726 734 $ hg add -S s/f1
727 735 $ hg add -S t/f1
728 736 $ hg id
729 737 365661e5936a
730 738 $ hg -R s id
731 739 fc627a69481f+
732 740 $ hg -R t id
733 741 e95bcfa18a35+
734 742 $ hg update tip
735 743 subrepository sources for s differ
736 744 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)?
737 745 l
738 746 subrepository sources for t differ
739 747 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)?
740 748 l
741 749 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
742 750 $ hg id
743 751 925c17564ef8+ tip
744 752 $ hg -R s id
745 753 fc627a69481f+
746 754 $ hg -R t id
747 755 e95bcfa18a35+
748 756 $ hg update --clean tip
749 757 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
750 758
751 759 Sticky subrepository, revision updates
752 760 $ hg id
753 761 925c17564ef8 tip
754 762 $ hg -R s id
755 763 12a213df6fa9 tip
756 764 $ hg -R t id
757 765 52c0adc0515a tip
758 766 $ cd s
759 767 $ hg update -r -2
760 768 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
761 769 $ cd ../t
762 770 $ hg update -r 2
763 771 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
764 772 $ cd ..
765 773 $ hg update 10
766 774 subrepository sources for t differ (in checked out version)
767 775 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)?
768 776 l
769 777 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
770 778 $ hg id
771 779 e45c8b14af55+
772 780 $ hg -R s id
773 781 1c833a7a9e3a
774 782 $ hg -R t id
775 783 7af322bc1198
776 784
777 785 Sticky subrepository, file changes and revision updates
778 786 $ touch s/f1
779 787 $ touch t/f1
780 788 $ hg add -S s/f1
781 789 $ hg add -S t/f1
782 790 $ hg id
783 791 e45c8b14af55+
784 792 $ hg -R s id
785 793 1c833a7a9e3a+
786 794 $ hg -R t id
787 795 7af322bc1198+
788 796 $ hg update tip
789 797 subrepository sources for s differ
790 798 use (l)ocal source (1c833a7a9e3a) or (r)emote source (12a213df6fa9)?
791 799 l
792 800 subrepository sources for t differ
793 801 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)?
794 802 l
795 803 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
796 804 $ hg id
797 805 925c17564ef8 tip
798 806 $ hg -R s id
799 807 1c833a7a9e3a+
800 808 $ hg -R t id
801 809 7af322bc1198+
802 810
803 811 Sticky repository, update --clean
804 812 $ hg update --clean tip
805 813 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
806 814 $ hg id
807 815 925c17564ef8 tip
808 816 $ hg -R s id
809 817 12a213df6fa9 tip
810 818 $ hg -R t id
811 819 52c0adc0515a tip
812 820
813 821 Test subrepo already at intended revision:
814 822 $ cd s
815 823 $ hg update fc627a69481f
816 824 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
817 825 $ cd ..
818 826 $ hg update 11
819 827 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
820 828 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
821 829 $ hg id -n
822 830 11+
823 831 $ hg -R s id
824 832 fc627a69481f
825 833 $ hg -R t id
826 834 e95bcfa18a35
827 835
828 836 Test that removing .hgsubstate doesn't break anything:
829 837
830 838 $ hg rm -f .hgsubstate
831 839 $ hg ci -mrm
832 840 committing subrepository s
833 841 committing subrepository t
834 842 created new head
835 843 $ hg log -vr tip
836 844 changeset: 14:3941e0aa5236
837 845 tag: tip
838 846 parent: 11:365661e5936a
839 847 user: test
840 848 date: Thu Jan 01 00:00:00 1970 +0000
841 849 description:
842 850 rm
843 851
844 852
845 853
846 854 Test that removing .hgsub removes .hgsubstate:
847 855
848 856 $ hg rm .hgsub
849 857 $ hg ci -mrm2
850 858 $ hg log -vr tip
851 859 changeset: 15:8b31de9d13d1
852 860 tag: tip
853 861 user: test
854 862 date: Thu Jan 01 00:00:00 1970 +0000
855 863 files: .hgsub .hgsubstate
856 864 description:
857 865 rm2
858 866
859 867
General Comments 0
You need to be logged in to leave comments. Login now