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