##// END OF EJS Templates
log: make log work even if first parameter doesn't exist...
Mads Kiilerich -
r18340:8802277c default
parent child Browse files
Show More
@@ -1,2009 +1,2009
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, context, repair, graphmod, revset, phases, obsolete
14 14 import changelog
15 15 import lock as lockmod
16 16
17 17 def parsealiases(cmd):
18 18 return cmd.lstrip("^").split("|")
19 19
20 20 def findpossible(cmd, table, strict=False):
21 21 """
22 22 Return cmd -> (aliases, command table entry)
23 23 for each matching command.
24 24 Return debug commands (or their aliases) only if no normal command matches.
25 25 """
26 26 choice = {}
27 27 debugchoice = {}
28 28
29 29 if cmd in table:
30 30 # short-circuit exact matches, "log" alias beats "^log|history"
31 31 keys = [cmd]
32 32 else:
33 33 keys = table.keys()
34 34
35 35 for e in keys:
36 36 aliases = parsealiases(e)
37 37 found = None
38 38 if cmd in aliases:
39 39 found = cmd
40 40 elif not strict:
41 41 for a in aliases:
42 42 if a.startswith(cmd):
43 43 found = a
44 44 break
45 45 if found is not None:
46 46 if aliases[0].startswith("debug") or found.startswith("debug"):
47 47 debugchoice[found] = (aliases, table[e])
48 48 else:
49 49 choice[found] = (aliases, table[e])
50 50
51 51 if not choice and debugchoice:
52 52 choice = debugchoice
53 53
54 54 return choice
55 55
56 56 def findcmd(cmd, table, strict=True):
57 57 """Return (aliases, command table entry) for command string."""
58 58 choice = findpossible(cmd, table, strict)
59 59
60 60 if cmd in choice:
61 61 return choice[cmd]
62 62
63 63 if len(choice) > 1:
64 64 clist = choice.keys()
65 65 clist.sort()
66 66 raise error.AmbiguousCommand(cmd, clist)
67 67
68 68 if choice:
69 69 return choice.values()[0]
70 70
71 71 raise error.UnknownCommand(cmd)
72 72
73 73 def findrepo(p):
74 74 while not os.path.isdir(os.path.join(p, ".hg")):
75 75 oldp, p = p, os.path.dirname(p)
76 76 if p == oldp:
77 77 return None
78 78
79 79 return p
80 80
81 81 def bailifchanged(repo):
82 82 if repo.dirstate.p2() != nullid:
83 83 raise util.Abort(_('outstanding uncommitted merge'))
84 84 modified, added, removed, deleted = repo.status()[:4]
85 85 if modified or added or removed or deleted:
86 86 raise util.Abort(_("outstanding uncommitted changes"))
87 87 ctx = repo[None]
88 88 for s in ctx.substate:
89 89 if ctx.sub(s).dirty():
90 90 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
91 91
92 92 def logmessage(ui, opts):
93 93 """ get the log message according to -m and -l option """
94 94 message = opts.get('message')
95 95 logfile = opts.get('logfile')
96 96
97 97 if message and logfile:
98 98 raise util.Abort(_('options --message and --logfile are mutually '
99 99 'exclusive'))
100 100 if not message and logfile:
101 101 try:
102 102 if logfile == '-':
103 103 message = ui.fin.read()
104 104 else:
105 105 message = '\n'.join(util.readfile(logfile).splitlines())
106 106 except IOError, inst:
107 107 raise util.Abort(_("can't read commit message '%s': %s") %
108 108 (logfile, inst.strerror))
109 109 return message
110 110
111 111 def loglimit(opts):
112 112 """get the log limit according to option -l/--limit"""
113 113 limit = opts.get('limit')
114 114 if limit:
115 115 try:
116 116 limit = int(limit)
117 117 except ValueError:
118 118 raise util.Abort(_('limit must be a positive integer'))
119 119 if limit <= 0:
120 120 raise util.Abort(_('limit must be positive'))
121 121 else:
122 122 limit = None
123 123 return limit
124 124
125 125 def makefilename(repo, pat, node, desc=None,
126 126 total=None, seqno=None, revwidth=None, pathname=None):
127 127 node_expander = {
128 128 'H': lambda: hex(node),
129 129 'R': lambda: str(repo.changelog.rev(node)),
130 130 'h': lambda: short(node),
131 131 'm': lambda: re.sub('[^\w]', '_', str(desc))
132 132 }
133 133 expander = {
134 134 '%': lambda: '%',
135 135 'b': lambda: os.path.basename(repo.root),
136 136 }
137 137
138 138 try:
139 139 if node:
140 140 expander.update(node_expander)
141 141 if node:
142 142 expander['r'] = (lambda:
143 143 str(repo.changelog.rev(node)).zfill(revwidth or 0))
144 144 if total is not None:
145 145 expander['N'] = lambda: str(total)
146 146 if seqno is not None:
147 147 expander['n'] = lambda: str(seqno)
148 148 if total is not None and seqno is not None:
149 149 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
150 150 if pathname is not None:
151 151 expander['s'] = lambda: os.path.basename(pathname)
152 152 expander['d'] = lambda: os.path.dirname(pathname) or '.'
153 153 expander['p'] = lambda: pathname
154 154
155 155 newname = []
156 156 patlen = len(pat)
157 157 i = 0
158 158 while i < patlen:
159 159 c = pat[i]
160 160 if c == '%':
161 161 i += 1
162 162 c = pat[i]
163 163 c = expander[c]()
164 164 newname.append(c)
165 165 i += 1
166 166 return ''.join(newname)
167 167 except KeyError, inst:
168 168 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
169 169 inst.args[0])
170 170
171 171 def makefileobj(repo, pat, node=None, desc=None, total=None,
172 172 seqno=None, revwidth=None, mode='wb', pathname=None):
173 173
174 174 writable = mode not in ('r', 'rb')
175 175
176 176 if not pat or pat == '-':
177 177 fp = writable and repo.ui.fout or repo.ui.fin
178 178 if util.safehasattr(fp, 'fileno'):
179 179 return os.fdopen(os.dup(fp.fileno()), mode)
180 180 else:
181 181 # if this fp can't be duped properly, return
182 182 # a dummy object that can be closed
183 183 class wrappedfileobj(object):
184 184 noop = lambda x: None
185 185 def __init__(self, f):
186 186 self.f = f
187 187 def __getattr__(self, attr):
188 188 if attr == 'close':
189 189 return self.noop
190 190 else:
191 191 return getattr(self.f, attr)
192 192
193 193 return wrappedfileobj(fp)
194 194 if util.safehasattr(pat, 'write') and writable:
195 195 return pat
196 196 if util.safehasattr(pat, 'read') and 'r' in mode:
197 197 return pat
198 198 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
199 199 pathname),
200 200 mode)
201 201
202 202 def openrevlog(repo, cmd, file_, opts):
203 203 """opens the changelog, manifest, a filelog or a given revlog"""
204 204 cl = opts['changelog']
205 205 mf = opts['manifest']
206 206 msg = None
207 207 if cl and mf:
208 208 msg = _('cannot specify --changelog and --manifest at the same time')
209 209 elif cl or mf:
210 210 if file_:
211 211 msg = _('cannot specify filename with --changelog or --manifest')
212 212 elif not repo:
213 213 msg = _('cannot specify --changelog or --manifest '
214 214 'without a repository')
215 215 if msg:
216 216 raise util.Abort(msg)
217 217
218 218 r = None
219 219 if repo:
220 220 if cl:
221 221 r = repo.changelog
222 222 elif mf:
223 223 r = repo.manifest
224 224 elif file_:
225 225 filelog = repo.file(file_)
226 226 if len(filelog):
227 227 r = filelog
228 228 if not r:
229 229 if not file_:
230 230 raise error.CommandError(cmd, _('invalid arguments'))
231 231 if not os.path.isfile(file_):
232 232 raise util.Abort(_("revlog '%s' not found") % file_)
233 233 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
234 234 file_[:-2] + ".i")
235 235 return r
236 236
237 237 def copy(ui, repo, pats, opts, rename=False):
238 238 # called with the repo lock held
239 239 #
240 240 # hgsep => pathname that uses "/" to separate directories
241 241 # ossep => pathname that uses os.sep to separate directories
242 242 cwd = repo.getcwd()
243 243 targets = {}
244 244 after = opts.get("after")
245 245 dryrun = opts.get("dry_run")
246 246 wctx = repo[None]
247 247
248 248 def walkpat(pat):
249 249 srcs = []
250 250 badstates = after and '?' or '?r'
251 251 m = scmutil.match(repo[None], [pat], opts, globbed=True)
252 252 for abs in repo.walk(m):
253 253 state = repo.dirstate[abs]
254 254 rel = m.rel(abs)
255 255 exact = m.exact(abs)
256 256 if state in badstates:
257 257 if exact and state == '?':
258 258 ui.warn(_('%s: not copying - file is not managed\n') % rel)
259 259 if exact and state == 'r':
260 260 ui.warn(_('%s: not copying - file has been marked for'
261 261 ' remove\n') % rel)
262 262 continue
263 263 # abs: hgsep
264 264 # rel: ossep
265 265 srcs.append((abs, rel, exact))
266 266 return srcs
267 267
268 268 # abssrc: hgsep
269 269 # relsrc: ossep
270 270 # otarget: ossep
271 271 def copyfile(abssrc, relsrc, otarget, exact):
272 272 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
273 273 if '/' in abstarget:
274 274 # We cannot normalize abstarget itself, this would prevent
275 275 # case only renames, like a => A.
276 276 abspath, absname = abstarget.rsplit('/', 1)
277 277 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
278 278 reltarget = repo.pathto(abstarget, cwd)
279 279 target = repo.wjoin(abstarget)
280 280 src = repo.wjoin(abssrc)
281 281 state = repo.dirstate[abstarget]
282 282
283 283 scmutil.checkportable(ui, abstarget)
284 284
285 285 # check for collisions
286 286 prevsrc = targets.get(abstarget)
287 287 if prevsrc is not None:
288 288 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
289 289 (reltarget, repo.pathto(abssrc, cwd),
290 290 repo.pathto(prevsrc, cwd)))
291 291 return
292 292
293 293 # check for overwrites
294 294 exists = os.path.lexists(target)
295 295 samefile = False
296 296 if exists and abssrc != abstarget:
297 297 if (repo.dirstate.normalize(abssrc) ==
298 298 repo.dirstate.normalize(abstarget)):
299 299 if not rename:
300 300 ui.warn(_("%s: can't copy - same file\n") % reltarget)
301 301 return
302 302 exists = False
303 303 samefile = True
304 304
305 305 if not after and exists or after and state in 'mn':
306 306 if not opts['force']:
307 307 ui.warn(_('%s: not overwriting - file exists\n') %
308 308 reltarget)
309 309 return
310 310
311 311 if after:
312 312 if not exists:
313 313 if rename:
314 314 ui.warn(_('%s: not recording move - %s does not exist\n') %
315 315 (relsrc, reltarget))
316 316 else:
317 317 ui.warn(_('%s: not recording copy - %s does not exist\n') %
318 318 (relsrc, reltarget))
319 319 return
320 320 elif not dryrun:
321 321 try:
322 322 if exists:
323 323 os.unlink(target)
324 324 targetdir = os.path.dirname(target) or '.'
325 325 if not os.path.isdir(targetdir):
326 326 os.makedirs(targetdir)
327 327 if samefile:
328 328 tmp = target + "~hgrename"
329 329 os.rename(src, tmp)
330 330 os.rename(tmp, target)
331 331 else:
332 332 util.copyfile(src, target)
333 333 srcexists = True
334 334 except IOError, inst:
335 335 if inst.errno == errno.ENOENT:
336 336 ui.warn(_('%s: deleted in working copy\n') % relsrc)
337 337 srcexists = False
338 338 else:
339 339 ui.warn(_('%s: cannot copy - %s\n') %
340 340 (relsrc, inst.strerror))
341 341 return True # report a failure
342 342
343 343 if ui.verbose or not exact:
344 344 if rename:
345 345 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
346 346 else:
347 347 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
348 348
349 349 targets[abstarget] = abssrc
350 350
351 351 # fix up dirstate
352 352 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
353 353 dryrun=dryrun, cwd=cwd)
354 354 if rename and not dryrun:
355 355 if not after and srcexists and not samefile:
356 356 util.unlinkpath(repo.wjoin(abssrc))
357 357 wctx.forget([abssrc])
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 targetpathfn(pat, dest, srcs):
364 364 if os.path.isdir(pat):
365 365 abspfx = scmutil.canonpath(repo.root, cwd, pat)
366 366 abspfx = util.localpath(abspfx)
367 367 if destdirexists:
368 368 striplen = len(os.path.split(abspfx)[0])
369 369 else:
370 370 striplen = len(abspfx)
371 371 if striplen:
372 372 striplen += len(os.sep)
373 373 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
374 374 elif destdirexists:
375 375 res = lambda p: os.path.join(dest,
376 376 os.path.basename(util.localpath(p)))
377 377 else:
378 378 res = lambda p: dest
379 379 return res
380 380
381 381 # pat: ossep
382 382 # dest ossep
383 383 # srcs: list of (hgsep, hgsep, ossep, bool)
384 384 # return: function that takes hgsep and returns ossep
385 385 def targetpathafterfn(pat, dest, srcs):
386 386 if matchmod.patkind(pat):
387 387 # a mercurial pattern
388 388 res = lambda p: os.path.join(dest,
389 389 os.path.basename(util.localpath(p)))
390 390 else:
391 391 abspfx = scmutil.canonpath(repo.root, cwd, pat)
392 392 if len(abspfx) < len(srcs[0][0]):
393 393 # A directory. Either the target path contains the last
394 394 # component of the source path or it does not.
395 395 def evalpath(striplen):
396 396 score = 0
397 397 for s in srcs:
398 398 t = os.path.join(dest, util.localpath(s[0])[striplen:])
399 399 if os.path.lexists(t):
400 400 score += 1
401 401 return score
402 402
403 403 abspfx = util.localpath(abspfx)
404 404 striplen = len(abspfx)
405 405 if striplen:
406 406 striplen += len(os.sep)
407 407 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
408 408 score = evalpath(striplen)
409 409 striplen1 = len(os.path.split(abspfx)[0])
410 410 if striplen1:
411 411 striplen1 += len(os.sep)
412 412 if evalpath(striplen1) > score:
413 413 striplen = striplen1
414 414 res = lambda p: os.path.join(dest,
415 415 util.localpath(p)[striplen:])
416 416 else:
417 417 # a file
418 418 if destdirexists:
419 419 res = lambda p: os.path.join(dest,
420 420 os.path.basename(util.localpath(p)))
421 421 else:
422 422 res = lambda p: dest
423 423 return res
424 424
425 425
426 426 pats = scmutil.expandpats(pats)
427 427 if not pats:
428 428 raise util.Abort(_('no source or destination specified'))
429 429 if len(pats) == 1:
430 430 raise util.Abort(_('no destination specified'))
431 431 dest = pats.pop()
432 432 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
433 433 if not destdirexists:
434 434 if len(pats) > 1 or matchmod.patkind(pats[0]):
435 435 raise util.Abort(_('with multiple sources, destination must be an '
436 436 'existing directory'))
437 437 if util.endswithsep(dest):
438 438 raise util.Abort(_('destination %s is not a directory') % dest)
439 439
440 440 tfn = targetpathfn
441 441 if after:
442 442 tfn = targetpathafterfn
443 443 copylist = []
444 444 for pat in pats:
445 445 srcs = walkpat(pat)
446 446 if not srcs:
447 447 continue
448 448 copylist.append((tfn(pat, dest, srcs), srcs))
449 449 if not copylist:
450 450 raise util.Abort(_('no files to copy'))
451 451
452 452 errors = 0
453 453 for targetpath, srcs in copylist:
454 454 for abssrc, relsrc, exact in srcs:
455 455 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
456 456 errors += 1
457 457
458 458 if errors:
459 459 ui.warn(_('(consider using --after)\n'))
460 460
461 461 return errors != 0
462 462
463 463 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
464 464 runargs=None, appendpid=False):
465 465 '''Run a command as a service.'''
466 466
467 467 if opts['daemon'] and not opts['daemon_pipefds']:
468 468 # Signal child process startup with file removal
469 469 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
470 470 os.close(lockfd)
471 471 try:
472 472 if not runargs:
473 473 runargs = util.hgcmd() + sys.argv[1:]
474 474 runargs.append('--daemon-pipefds=%s' % lockpath)
475 475 # Don't pass --cwd to the child process, because we've already
476 476 # changed directory.
477 477 for i in xrange(1, len(runargs)):
478 478 if runargs[i].startswith('--cwd='):
479 479 del runargs[i]
480 480 break
481 481 elif runargs[i].startswith('--cwd'):
482 482 del runargs[i:i + 2]
483 483 break
484 484 def condfn():
485 485 return not os.path.exists(lockpath)
486 486 pid = util.rundetached(runargs, condfn)
487 487 if pid < 0:
488 488 raise util.Abort(_('child process failed to start'))
489 489 finally:
490 490 try:
491 491 os.unlink(lockpath)
492 492 except OSError, e:
493 493 if e.errno != errno.ENOENT:
494 494 raise
495 495 if parentfn:
496 496 return parentfn(pid)
497 497 else:
498 498 return
499 499
500 500 if initfn:
501 501 initfn()
502 502
503 503 if opts['pid_file']:
504 504 mode = appendpid and 'a' or 'w'
505 505 fp = open(opts['pid_file'], mode)
506 506 fp.write(str(os.getpid()) + '\n')
507 507 fp.close()
508 508
509 509 if opts['daemon_pipefds']:
510 510 lockpath = opts['daemon_pipefds']
511 511 try:
512 512 os.setsid()
513 513 except AttributeError:
514 514 pass
515 515 os.unlink(lockpath)
516 516 util.hidewindow()
517 517 sys.stdout.flush()
518 518 sys.stderr.flush()
519 519
520 520 nullfd = os.open(os.devnull, os.O_RDWR)
521 521 logfilefd = nullfd
522 522 if logfile:
523 523 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
524 524 os.dup2(nullfd, 0)
525 525 os.dup2(logfilefd, 1)
526 526 os.dup2(logfilefd, 2)
527 527 if nullfd not in (0, 1, 2):
528 528 os.close(nullfd)
529 529 if logfile and logfilefd not in (0, 1, 2):
530 530 os.close(logfilefd)
531 531
532 532 if runfn:
533 533 return runfn()
534 534
535 535 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
536 536 opts=None):
537 537 '''export changesets as hg patches.'''
538 538
539 539 total = len(revs)
540 540 revwidth = max([len(str(rev)) for rev in revs])
541 541
542 542 def single(rev, seqno, fp):
543 543 ctx = repo[rev]
544 544 node = ctx.node()
545 545 parents = [p.node() for p in ctx.parents() if p]
546 546 branch = ctx.branch()
547 547 if switch_parent:
548 548 parents.reverse()
549 549 prev = (parents and parents[0]) or nullid
550 550
551 551 shouldclose = False
552 552 if not fp and len(template) > 0:
553 553 desc_lines = ctx.description().rstrip().split('\n')
554 554 desc = desc_lines[0] #Commit always has a first line.
555 555 fp = makefileobj(repo, template, node, desc=desc, total=total,
556 556 seqno=seqno, revwidth=revwidth, mode='ab')
557 557 if fp != template:
558 558 shouldclose = True
559 559 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
560 560 repo.ui.note("%s\n" % fp.name)
561 561
562 562 if not fp:
563 563 write = repo.ui.write
564 564 else:
565 565 def write(s, **kw):
566 566 fp.write(s)
567 567
568 568
569 569 write("# HG changeset patch\n")
570 570 write("# User %s\n" % ctx.user())
571 571 write("# Date %d %d\n" % ctx.date())
572 572 if branch and branch != 'default':
573 573 write("# Branch %s\n" % branch)
574 574 write("# Node ID %s\n" % hex(node))
575 575 write("# Parent %s\n" % hex(prev))
576 576 if len(parents) > 1:
577 577 write("# Parent %s\n" % hex(parents[1]))
578 578 write(ctx.description().rstrip())
579 579 write("\n\n")
580 580
581 581 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
582 582 write(chunk, label=label)
583 583
584 584 if shouldclose:
585 585 fp.close()
586 586
587 587 for seqno, rev in enumerate(revs):
588 588 single(rev, seqno + 1, fp)
589 589
590 590 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
591 591 changes=None, stat=False, fp=None, prefix='',
592 592 listsubrepos=False):
593 593 '''show diff or diffstat.'''
594 594 if fp is None:
595 595 write = ui.write
596 596 else:
597 597 def write(s, **kw):
598 598 fp.write(s)
599 599
600 600 if stat:
601 601 diffopts = diffopts.copy(context=0)
602 602 width = 80
603 603 if not ui.plain():
604 604 width = ui.termwidth()
605 605 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
606 606 prefix=prefix)
607 607 for chunk, label in patch.diffstatui(util.iterlines(chunks),
608 608 width=width,
609 609 git=diffopts.git):
610 610 write(chunk, label=label)
611 611 else:
612 612 for chunk, label in patch.diffui(repo, node1, node2, match,
613 613 changes, diffopts, prefix=prefix):
614 614 write(chunk, label=label)
615 615
616 616 if listsubrepos:
617 617 ctx1 = repo[node1]
618 618 ctx2 = repo[node2]
619 619 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
620 620 tempnode2 = node2
621 621 try:
622 622 if node2 is not None:
623 623 tempnode2 = ctx2.substate[subpath][1]
624 624 except KeyError:
625 625 # A subrepo that existed in node1 was deleted between node1 and
626 626 # node2 (inclusive). Thus, ctx2's substate won't contain that
627 627 # subpath. The best we can do is to ignore it.
628 628 tempnode2 = None
629 629 submatch = matchmod.narrowmatcher(subpath, match)
630 630 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
631 631 stat=stat, fp=fp, prefix=prefix)
632 632
633 633 class changeset_printer(object):
634 634 '''show changeset information when templating not requested.'''
635 635
636 636 def __init__(self, ui, repo, patch, diffopts, buffered):
637 637 self.ui = ui
638 638 self.repo = repo
639 639 self.buffered = buffered
640 640 self.patch = patch
641 641 self.diffopts = diffopts
642 642 self.header = {}
643 643 self.hunk = {}
644 644 self.lastheader = None
645 645 self.footer = None
646 646
647 647 def flush(self, rev):
648 648 if rev in self.header:
649 649 h = self.header[rev]
650 650 if h != self.lastheader:
651 651 self.lastheader = h
652 652 self.ui.write(h)
653 653 del self.header[rev]
654 654 if rev in self.hunk:
655 655 self.ui.write(self.hunk[rev])
656 656 del self.hunk[rev]
657 657 return 1
658 658 return 0
659 659
660 660 def close(self):
661 661 if self.footer:
662 662 self.ui.write(self.footer)
663 663
664 664 def show(self, ctx, copies=None, matchfn=None, **props):
665 665 if self.buffered:
666 666 self.ui.pushbuffer()
667 667 self._show(ctx, copies, matchfn, props)
668 668 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
669 669 else:
670 670 self._show(ctx, copies, matchfn, props)
671 671
672 672 def _show(self, ctx, copies, matchfn, props):
673 673 '''show a single changeset or file revision'''
674 674 changenode = ctx.node()
675 675 rev = ctx.rev()
676 676
677 677 if self.ui.quiet:
678 678 self.ui.write("%d:%s\n" % (rev, short(changenode)),
679 679 label='log.node')
680 680 return
681 681
682 682 log = self.repo.changelog
683 683 date = util.datestr(ctx.date())
684 684
685 685 hexfunc = self.ui.debugflag and hex or short
686 686
687 687 parents = [(p, hexfunc(log.node(p)))
688 688 for p in self._meaningful_parentrevs(log, rev)]
689 689
690 690 # i18n: column positioning for "hg log"
691 691 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
692 692 label='log.changeset changeset.%s' % ctx.phasestr())
693 693
694 694 branch = ctx.branch()
695 695 # don't show the default branch name
696 696 if branch != 'default':
697 697 # i18n: column positioning for "hg log"
698 698 self.ui.write(_("branch: %s\n") % branch,
699 699 label='log.branch')
700 700 for bookmark in self.repo.nodebookmarks(changenode):
701 701 # i18n: column positioning for "hg log"
702 702 self.ui.write(_("bookmark: %s\n") % bookmark,
703 703 label='log.bookmark')
704 704 for tag in self.repo.nodetags(changenode):
705 705 # i18n: column positioning for "hg log"
706 706 self.ui.write(_("tag: %s\n") % tag,
707 707 label='log.tag')
708 708 if self.ui.debugflag and ctx.phase():
709 709 # i18n: column positioning for "hg log"
710 710 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
711 711 label='log.phase')
712 712 for parent in parents:
713 713 # i18n: column positioning for "hg log"
714 714 self.ui.write(_("parent: %d:%s\n") % parent,
715 715 label='log.parent changeset.%s' % ctx.phasestr())
716 716
717 717 if self.ui.debugflag:
718 718 mnode = ctx.manifestnode()
719 719 # i18n: column positioning for "hg log"
720 720 self.ui.write(_("manifest: %d:%s\n") %
721 721 (self.repo.manifest.rev(mnode), hex(mnode)),
722 722 label='ui.debug log.manifest')
723 723 # i18n: column positioning for "hg log"
724 724 self.ui.write(_("user: %s\n") % ctx.user(),
725 725 label='log.user')
726 726 # i18n: column positioning for "hg log"
727 727 self.ui.write(_("date: %s\n") % date,
728 728 label='log.date')
729 729
730 730 if self.ui.debugflag:
731 731 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
732 732 for key, value in zip([# i18n: column positioning for "hg log"
733 733 _("files:"),
734 734 # i18n: column positioning for "hg log"
735 735 _("files+:"),
736 736 # i18n: column positioning for "hg log"
737 737 _("files-:")], files):
738 738 if value:
739 739 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
740 740 label='ui.debug log.files')
741 741 elif ctx.files() and self.ui.verbose:
742 742 # i18n: column positioning for "hg log"
743 743 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
744 744 label='ui.note log.files')
745 745 if copies and self.ui.verbose:
746 746 copies = ['%s (%s)' % c for c in copies]
747 747 # i18n: column positioning for "hg log"
748 748 self.ui.write(_("copies: %s\n") % ' '.join(copies),
749 749 label='ui.note log.copies')
750 750
751 751 extra = ctx.extra()
752 752 if extra and self.ui.debugflag:
753 753 for key, value in sorted(extra.items()):
754 754 # i18n: column positioning for "hg log"
755 755 self.ui.write(_("extra: %s=%s\n")
756 756 % (key, value.encode('string_escape')),
757 757 label='ui.debug log.extra')
758 758
759 759 description = ctx.description().strip()
760 760 if description:
761 761 if self.ui.verbose:
762 762 self.ui.write(_("description:\n"),
763 763 label='ui.note log.description')
764 764 self.ui.write(description,
765 765 label='ui.note log.description')
766 766 self.ui.write("\n\n")
767 767 else:
768 768 # i18n: column positioning for "hg log"
769 769 self.ui.write(_("summary: %s\n") %
770 770 description.splitlines()[0],
771 771 label='log.summary')
772 772 self.ui.write("\n")
773 773
774 774 self.showpatch(changenode, matchfn)
775 775
776 776 def showpatch(self, node, matchfn):
777 777 if not matchfn:
778 778 matchfn = self.patch
779 779 if matchfn:
780 780 stat = self.diffopts.get('stat')
781 781 diff = self.diffopts.get('patch')
782 782 diffopts = patch.diffopts(self.ui, self.diffopts)
783 783 prev = self.repo.changelog.parents(node)[0]
784 784 if stat:
785 785 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
786 786 match=matchfn, stat=True)
787 787 if diff:
788 788 if stat:
789 789 self.ui.write("\n")
790 790 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
791 791 match=matchfn, stat=False)
792 792 self.ui.write("\n")
793 793
794 794 def _meaningful_parentrevs(self, log, rev):
795 795 """Return list of meaningful (or all if debug) parentrevs for rev.
796 796
797 797 For merges (two non-nullrev revisions) both parents are meaningful.
798 798 Otherwise the first parent revision is considered meaningful if it
799 799 is not the preceding revision.
800 800 """
801 801 parents = log.parentrevs(rev)
802 802 if not self.ui.debugflag and parents[1] == nullrev:
803 803 if parents[0] >= rev - 1:
804 804 parents = []
805 805 else:
806 806 parents = [parents[0]]
807 807 return parents
808 808
809 809
810 810 class changeset_templater(changeset_printer):
811 811 '''format changeset information.'''
812 812
813 813 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
814 814 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
815 815 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
816 816 defaulttempl = {
817 817 'parent': '{rev}:{node|formatnode} ',
818 818 'manifest': '{rev}:{node|formatnode}',
819 819 'file_copy': '{name} ({source})',
820 820 'extra': '{key}={value|stringescape}'
821 821 }
822 822 # filecopy is preserved for compatibility reasons
823 823 defaulttempl['filecopy'] = defaulttempl['file_copy']
824 824 self.t = templater.templater(mapfile, {'formatnode': formatnode},
825 825 cache=defaulttempl)
826 826 self.cache = {}
827 827
828 828 def use_template(self, t):
829 829 '''set template string to use'''
830 830 self.t.cache['changeset'] = t
831 831
832 832 def _meaningful_parentrevs(self, ctx):
833 833 """Return list of meaningful (or all if debug) parentrevs for rev.
834 834 """
835 835 parents = ctx.parents()
836 836 if len(parents) > 1:
837 837 return parents
838 838 if self.ui.debugflag:
839 839 return [parents[0], self.repo['null']]
840 840 if parents[0].rev() >= ctx.rev() - 1:
841 841 return []
842 842 return parents
843 843
844 844 def _show(self, ctx, copies, matchfn, props):
845 845 '''show a single changeset or file revision'''
846 846
847 847 showlist = templatekw.showlist
848 848
849 849 # showparents() behaviour depends on ui trace level which
850 850 # causes unexpected behaviours at templating level and makes
851 851 # it harder to extract it in a standalone function. Its
852 852 # behaviour cannot be changed so leave it here for now.
853 853 def showparents(**args):
854 854 ctx = args['ctx']
855 855 parents = [[('rev', p.rev()), ('node', p.hex())]
856 856 for p in self._meaningful_parentrevs(ctx)]
857 857 return showlist('parent', parents, **args)
858 858
859 859 props = props.copy()
860 860 props.update(templatekw.keywords)
861 861 props['parents'] = showparents
862 862 props['templ'] = self.t
863 863 props['ctx'] = ctx
864 864 props['repo'] = self.repo
865 865 props['revcache'] = {'copies': copies}
866 866 props['cache'] = self.cache
867 867
868 868 # find correct templates for current mode
869 869
870 870 tmplmodes = [
871 871 (True, None),
872 872 (self.ui.verbose, 'verbose'),
873 873 (self.ui.quiet, 'quiet'),
874 874 (self.ui.debugflag, 'debug'),
875 875 ]
876 876
877 877 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
878 878 for mode, postfix in tmplmodes:
879 879 for type in types:
880 880 cur = postfix and ('%s_%s' % (type, postfix)) or type
881 881 if mode and cur in self.t:
882 882 types[type] = cur
883 883
884 884 try:
885 885
886 886 # write header
887 887 if types['header']:
888 888 h = templater.stringify(self.t(types['header'], **props))
889 889 if self.buffered:
890 890 self.header[ctx.rev()] = h
891 891 else:
892 892 if self.lastheader != h:
893 893 self.lastheader = h
894 894 self.ui.write(h)
895 895
896 896 # write changeset metadata, then patch if requested
897 897 key = types['changeset']
898 898 self.ui.write(templater.stringify(self.t(key, **props)))
899 899 self.showpatch(ctx.node(), matchfn)
900 900
901 901 if types['footer']:
902 902 if not self.footer:
903 903 self.footer = templater.stringify(self.t(types['footer'],
904 904 **props))
905 905
906 906 except KeyError, inst:
907 907 msg = _("%s: no key named '%s'")
908 908 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
909 909 except SyntaxError, inst:
910 910 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
911 911
912 912 def show_changeset(ui, repo, opts, buffered=False):
913 913 """show one changeset using template or regular display.
914 914
915 915 Display format will be the first non-empty hit of:
916 916 1. option 'template'
917 917 2. option 'style'
918 918 3. [ui] setting 'logtemplate'
919 919 4. [ui] setting 'style'
920 920 If all of these values are either the unset or the empty string,
921 921 regular display via changeset_printer() is done.
922 922 """
923 923 # options
924 924 patch = False
925 925 if opts.get('patch') or opts.get('stat'):
926 926 patch = scmutil.matchall(repo)
927 927
928 928 tmpl = opts.get('template')
929 929 style = None
930 930 if tmpl:
931 931 tmpl = templater.parsestring(tmpl, quoted=False)
932 932 else:
933 933 style = opts.get('style')
934 934
935 935 # ui settings
936 936 if not (tmpl or style):
937 937 tmpl = ui.config('ui', 'logtemplate')
938 938 if tmpl:
939 939 try:
940 940 tmpl = templater.parsestring(tmpl)
941 941 except SyntaxError:
942 942 tmpl = templater.parsestring(tmpl, quoted=False)
943 943 else:
944 944 style = util.expandpath(ui.config('ui', 'style', ''))
945 945
946 946 if not (tmpl or style):
947 947 return changeset_printer(ui, repo, patch, opts, buffered)
948 948
949 949 mapfile = None
950 950 if style and not tmpl:
951 951 mapfile = style
952 952 if not os.path.split(mapfile)[0]:
953 953 mapname = (templater.templatepath('map-cmdline.' + mapfile)
954 954 or templater.templatepath(mapfile))
955 955 if mapname:
956 956 mapfile = mapname
957 957
958 958 try:
959 959 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
960 960 except SyntaxError, inst:
961 961 raise util.Abort(inst.args[0])
962 962 if tmpl:
963 963 t.use_template(tmpl)
964 964 return t
965 965
966 966 def finddate(ui, repo, date):
967 967 """Find the tipmost changeset that matches the given date spec"""
968 968
969 969 df = util.matchdate(date)
970 970 m = scmutil.matchall(repo)
971 971 results = {}
972 972
973 973 def prep(ctx, fns):
974 974 d = ctx.date()
975 975 if df(d[0]):
976 976 results[ctx.rev()] = d
977 977
978 978 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
979 979 rev = ctx.rev()
980 980 if rev in results:
981 981 ui.status(_("found revision %s from %s\n") %
982 982 (rev, util.datestr(results[rev])))
983 983 return str(rev)
984 984
985 985 raise util.Abort(_("revision matching date not found"))
986 986
987 987 def increasingwindows(start, end, windowsize=8, sizelimit=512):
988 988 if start < end:
989 989 while start < end:
990 990 yield start, min(windowsize, end - start)
991 991 start += windowsize
992 992 if windowsize < sizelimit:
993 993 windowsize *= 2
994 994 else:
995 995 while start > end:
996 996 yield start, min(windowsize, start - end - 1)
997 997 start -= windowsize
998 998 if windowsize < sizelimit:
999 999 windowsize *= 2
1000 1000
1001 1001 def walkchangerevs(repo, match, opts, prepare):
1002 1002 '''Iterate over files and the revs in which they changed.
1003 1003
1004 1004 Callers most commonly need to iterate backwards over the history
1005 1005 in which they are interested. Doing so has awful (quadratic-looking)
1006 1006 performance, so we use iterators in a "windowed" way.
1007 1007
1008 1008 We walk a window of revisions in the desired order. Within the
1009 1009 window, we first walk forwards to gather data, then in the desired
1010 1010 order (usually backwards) to display it.
1011 1011
1012 1012 This function returns an iterator yielding contexts. Before
1013 1013 yielding each context, the iterator will first call the prepare
1014 1014 function on each context in the window in forward order.'''
1015 1015
1016 1016 follow = opts.get('follow') or opts.get('follow_first')
1017 1017
1018 1018 if not len(repo):
1019 1019 return []
1020 1020 if opts.get('rev'):
1021 1021 revs = scmutil.revrange(repo, opts.get('rev'))
1022 1022 elif follow:
1023 1023 revs = repo.revs('reverse(:.)')
1024 1024 else:
1025 1025 revs = list(repo)
1026 1026 revs.reverse()
1027 1027 if not revs:
1028 1028 return []
1029 1029 wanted = set()
1030 1030 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1031 1031 fncache = {}
1032 1032 change = repo.changectx
1033 1033
1034 1034 # First step is to fill wanted, the set of revisions that we want to yield.
1035 1035 # When it does not induce extra cost, we also fill fncache for revisions in
1036 1036 # wanted: a cache of filenames that were changed (ctx.files()) and that
1037 1037 # match the file filtering conditions.
1038 1038
1039 1039 if not slowpath and not match.files():
1040 1040 # No files, no patterns. Display all revs.
1041 1041 wanted = set(revs)
1042 1042 copies = []
1043 1043
1044 1044 if not slowpath and match.files():
1045 1045 # We only have to read through the filelog to find wanted revisions
1046 1046
1047 1047 minrev, maxrev = min(revs), max(revs)
1048 1048 def filerevgen(filelog, last):
1049 1049 """
1050 1050 Only files, no patterns. Check the history of each file.
1051 1051
1052 1052 Examines filelog entries within minrev, maxrev linkrev range
1053 1053 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1054 1054 tuples in backwards order
1055 1055 """
1056 1056 cl_count = len(repo)
1057 1057 revs = []
1058 1058 for j in xrange(0, last + 1):
1059 1059 linkrev = filelog.linkrev(j)
1060 1060 if linkrev < minrev:
1061 1061 continue
1062 1062 # only yield rev for which we have the changelog, it can
1063 1063 # happen while doing "hg log" during a pull or commit
1064 1064 if linkrev >= cl_count:
1065 1065 break
1066 1066
1067 1067 parentlinkrevs = []
1068 1068 for p in filelog.parentrevs(j):
1069 1069 if p != nullrev:
1070 1070 parentlinkrevs.append(filelog.linkrev(p))
1071 1071 n = filelog.node(j)
1072 1072 revs.append((linkrev, parentlinkrevs,
1073 1073 follow and filelog.renamed(n)))
1074 1074
1075 1075 return reversed(revs)
1076 1076 def iterfiles():
1077 1077 pctx = repo['.']
1078 1078 for filename in match.files():
1079 1079 if follow:
1080 1080 if filename not in pctx:
1081 1081 raise util.Abort(_('cannot follow file not in parent '
1082 1082 'revision: "%s"') % filename)
1083 1083 yield filename, pctx[filename].filenode()
1084 1084 else:
1085 1085 yield filename, None
1086 1086 for filename_node in copies:
1087 1087 yield filename_node
1088 1088 for file_, node in iterfiles():
1089 1089 filelog = repo.file(file_)
1090 1090 if not len(filelog):
1091 1091 if node is None:
1092 1092 # A zero count may be a directory or deleted file, so
1093 1093 # try to find matching entries on the slow path.
1094 1094 if follow:
1095 1095 raise util.Abort(
1096 1096 _('cannot follow nonexistent file: "%s"') % file_)
1097 1097 slowpath = True
1098 1098 break
1099 1099 else:
1100 1100 continue
1101 1101
1102 1102 if node is None:
1103 1103 last = len(filelog) - 1
1104 1104 else:
1105 1105 last = filelog.rev(node)
1106 1106
1107 1107
1108 1108 # keep track of all ancestors of the file
1109 1109 ancestors = set([filelog.linkrev(last)])
1110 1110
1111 1111 # iterate from latest to oldest revision
1112 1112 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1113 1113 if not follow:
1114 1114 if rev > maxrev:
1115 1115 continue
1116 1116 else:
1117 1117 # Note that last might not be the first interesting
1118 1118 # rev to us:
1119 1119 # if the file has been changed after maxrev, we'll
1120 1120 # have linkrev(last) > maxrev, and we still need
1121 1121 # to explore the file graph
1122 1122 if rev not in ancestors:
1123 1123 continue
1124 1124 # XXX insert 1327 fix here
1125 1125 if flparentlinkrevs:
1126 1126 ancestors.update(flparentlinkrevs)
1127 1127
1128 1128 fncache.setdefault(rev, []).append(file_)
1129 1129 wanted.add(rev)
1130 1130 if copied:
1131 1131 copies.append(copied)
1132 1132
1133 1133 # We decided to fall back to the slowpath because at least one
1134 1134 # of the paths was not a file. Check to see if at least one of them
1135 1135 # existed in history, otherwise simply return
1136 1136 if slowpath:
1137 1137 for path in match.files():
1138 1138 if path == '.' or path in repo.store:
1139 1139 break
1140 else:
1141 return []
1140 else:
1141 return []
1142 1142
1143 1143 if slowpath:
1144 1144 # We have to read the changelog to match filenames against
1145 1145 # changed files
1146 1146
1147 1147 if follow:
1148 1148 raise util.Abort(_('can only follow copies/renames for explicit '
1149 1149 'filenames'))
1150 1150
1151 1151 # The slow path checks files modified in every changeset.
1152 1152 for i in sorted(revs):
1153 1153 ctx = change(i)
1154 1154 matches = filter(match, ctx.files())
1155 1155 if matches:
1156 1156 fncache[i] = matches
1157 1157 wanted.add(i)
1158 1158
1159 1159 class followfilter(object):
1160 1160 def __init__(self, onlyfirst=False):
1161 1161 self.startrev = nullrev
1162 1162 self.roots = set()
1163 1163 self.onlyfirst = onlyfirst
1164 1164
1165 1165 def match(self, rev):
1166 1166 def realparents(rev):
1167 1167 if self.onlyfirst:
1168 1168 return repo.changelog.parentrevs(rev)[0:1]
1169 1169 else:
1170 1170 return filter(lambda x: x != nullrev,
1171 1171 repo.changelog.parentrevs(rev))
1172 1172
1173 1173 if self.startrev == nullrev:
1174 1174 self.startrev = rev
1175 1175 return True
1176 1176
1177 1177 if rev > self.startrev:
1178 1178 # forward: all descendants
1179 1179 if not self.roots:
1180 1180 self.roots.add(self.startrev)
1181 1181 for parent in realparents(rev):
1182 1182 if parent in self.roots:
1183 1183 self.roots.add(rev)
1184 1184 return True
1185 1185 else:
1186 1186 # backwards: all parents
1187 1187 if not self.roots:
1188 1188 self.roots.update(realparents(self.startrev))
1189 1189 if rev in self.roots:
1190 1190 self.roots.remove(rev)
1191 1191 self.roots.update(realparents(rev))
1192 1192 return True
1193 1193
1194 1194 return False
1195 1195
1196 1196 # it might be worthwhile to do this in the iterator if the rev range
1197 1197 # is descending and the prune args are all within that range
1198 1198 for rev in opts.get('prune', ()):
1199 1199 rev = repo[rev].rev()
1200 1200 ff = followfilter()
1201 1201 stop = min(revs[0], revs[-1])
1202 1202 for x in xrange(rev, stop - 1, -1):
1203 1203 if ff.match(x):
1204 1204 wanted.discard(x)
1205 1205
1206 1206 # Now that wanted is correctly initialized, we can iterate over the
1207 1207 # revision range, yielding only revisions in wanted.
1208 1208 def iterate():
1209 1209 if follow and not match.files():
1210 1210 ff = followfilter(onlyfirst=opts.get('follow_first'))
1211 1211 def want(rev):
1212 1212 return ff.match(rev) and rev in wanted
1213 1213 else:
1214 1214 def want(rev):
1215 1215 return rev in wanted
1216 1216
1217 1217 for i, window in increasingwindows(0, len(revs)):
1218 1218 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1219 1219 for rev in sorted(nrevs):
1220 1220 fns = fncache.get(rev)
1221 1221 ctx = change(rev)
1222 1222 if not fns:
1223 1223 def fns_generator():
1224 1224 for f in ctx.files():
1225 1225 if match(f):
1226 1226 yield f
1227 1227 fns = fns_generator()
1228 1228 prepare(ctx, fns)
1229 1229 for rev in nrevs:
1230 1230 yield change(rev)
1231 1231 return iterate()
1232 1232
1233 1233 def _makegraphfilematcher(repo, pats, followfirst):
1234 1234 # When displaying a revision with --patch --follow FILE, we have
1235 1235 # to know which file of the revision must be diffed. With
1236 1236 # --follow, we want the names of the ancestors of FILE in the
1237 1237 # revision, stored in "fcache". "fcache" is populated by
1238 1238 # reproducing the graph traversal already done by --follow revset
1239 1239 # and relating linkrevs to file names (which is not "correct" but
1240 1240 # good enough).
1241 1241 fcache = {}
1242 1242 fcacheready = [False]
1243 1243 pctx = repo['.']
1244 1244 wctx = repo[None]
1245 1245
1246 1246 def populate():
1247 1247 for fn in pats:
1248 1248 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1249 1249 for c in i:
1250 1250 fcache.setdefault(c.linkrev(), set()).add(c.path())
1251 1251
1252 1252 def filematcher(rev):
1253 1253 if not fcacheready[0]:
1254 1254 # Lazy initialization
1255 1255 fcacheready[0] = True
1256 1256 populate()
1257 1257 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1258 1258
1259 1259 return filematcher
1260 1260
1261 1261 def _makegraphlogrevset(repo, pats, opts, revs):
1262 1262 """Return (expr, filematcher) where expr is a revset string built
1263 1263 from log options and file patterns or None. If --stat or --patch
1264 1264 are not passed filematcher is None. Otherwise it is a callable
1265 1265 taking a revision number and returning a match objects filtering
1266 1266 the files to be detailed when displaying the revision.
1267 1267 """
1268 1268 opt2revset = {
1269 1269 'no_merges': ('not merge()', None),
1270 1270 'only_merges': ('merge()', None),
1271 1271 '_ancestors': ('ancestors(%(val)s)', None),
1272 1272 '_fancestors': ('_firstancestors(%(val)s)', None),
1273 1273 '_descendants': ('descendants(%(val)s)', None),
1274 1274 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1275 1275 '_matchfiles': ('_matchfiles(%(val)s)', None),
1276 1276 'date': ('date(%(val)r)', None),
1277 1277 'branch': ('branch(%(val)r)', ' or '),
1278 1278 '_patslog': ('filelog(%(val)r)', ' or '),
1279 1279 '_patsfollow': ('follow(%(val)r)', ' or '),
1280 1280 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1281 1281 'keyword': ('keyword(%(val)r)', ' or '),
1282 1282 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1283 1283 'user': ('user(%(val)r)', ' or '),
1284 1284 }
1285 1285
1286 1286 opts = dict(opts)
1287 1287 # follow or not follow?
1288 1288 follow = opts.get('follow') or opts.get('follow_first')
1289 1289 followfirst = opts.get('follow_first') and 1 or 0
1290 1290 # --follow with FILE behaviour depends on revs...
1291 1291 startrev = revs[0]
1292 1292 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1293 1293
1294 1294 # branch and only_branch are really aliases and must be handled at
1295 1295 # the same time
1296 1296 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1297 1297 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1298 1298 # pats/include/exclude are passed to match.match() directly in
1299 1299 # _matchfiles() revset but walkchangerevs() builds its matcher with
1300 1300 # scmutil.match(). The difference is input pats are globbed on
1301 1301 # platforms without shell expansion (windows).
1302 1302 pctx = repo[None]
1303 1303 match, pats = scmutil.matchandpats(pctx, pats, opts)
1304 1304 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1305 1305 if not slowpath:
1306 1306 for f in match.files():
1307 1307 if follow and f not in pctx:
1308 1308 raise util.Abort(_('cannot follow file not in parent '
1309 1309 'revision: "%s"') % f)
1310 1310 filelog = repo.file(f)
1311 1311 if not len(filelog):
1312 1312 # A zero count may be a directory or deleted file, so
1313 1313 # try to find matching entries on the slow path.
1314 1314 if follow:
1315 1315 raise util.Abort(
1316 1316 _('cannot follow nonexistent file: "%s"') % f)
1317 1317 slowpath = True
1318 1318
1319 1319 # We decided to fall back to the slowpath because at least one
1320 1320 # of the paths was not a file. Check to see if at least one of them
1321 1321 # existed in history - in that case, we'll continue down the
1322 1322 # slowpath; otherwise, we can turn off the slowpath
1323 1323 if slowpath:
1324 1324 for path in match.files():
1325 1325 if path == '.' or path in repo.store:
1326 1326 break
1327 1327 else:
1328 1328 slowpath = False
1329 1329
1330 1330 if slowpath:
1331 1331 # See walkchangerevs() slow path.
1332 1332 #
1333 1333 if follow:
1334 1334 raise util.Abort(_('can only follow copies/renames for explicit '
1335 1335 'filenames'))
1336 1336 # pats/include/exclude cannot be represented as separate
1337 1337 # revset expressions as their filtering logic applies at file
1338 1338 # level. For instance "-I a -X a" matches a revision touching
1339 1339 # "a" and "b" while "file(a) and not file(b)" does
1340 1340 # not. Besides, filesets are evaluated against the working
1341 1341 # directory.
1342 1342 matchargs = ['r:', 'd:relpath']
1343 1343 for p in pats:
1344 1344 matchargs.append('p:' + p)
1345 1345 for p in opts.get('include', []):
1346 1346 matchargs.append('i:' + p)
1347 1347 for p in opts.get('exclude', []):
1348 1348 matchargs.append('x:' + p)
1349 1349 matchargs = ','.join(('%r' % p) for p in matchargs)
1350 1350 opts['_matchfiles'] = matchargs
1351 1351 else:
1352 1352 if follow:
1353 1353 fpats = ('_patsfollow', '_patsfollowfirst')
1354 1354 fnopats = (('_ancestors', '_fancestors'),
1355 1355 ('_descendants', '_fdescendants'))
1356 1356 if pats:
1357 1357 # follow() revset interprets its file argument as a
1358 1358 # manifest entry, so use match.files(), not pats.
1359 1359 opts[fpats[followfirst]] = list(match.files())
1360 1360 else:
1361 1361 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1362 1362 else:
1363 1363 opts['_patslog'] = list(pats)
1364 1364
1365 1365 filematcher = None
1366 1366 if opts.get('patch') or opts.get('stat'):
1367 1367 if follow:
1368 1368 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1369 1369 else:
1370 1370 filematcher = lambda rev: match
1371 1371
1372 1372 expr = []
1373 1373 for op, val in opts.iteritems():
1374 1374 if not val:
1375 1375 continue
1376 1376 if op not in opt2revset:
1377 1377 continue
1378 1378 revop, andor = opt2revset[op]
1379 1379 if '%(val)' not in revop:
1380 1380 expr.append(revop)
1381 1381 else:
1382 1382 if not isinstance(val, list):
1383 1383 e = revop % {'val': val}
1384 1384 else:
1385 1385 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1386 1386 expr.append(e)
1387 1387
1388 1388 if expr:
1389 1389 expr = '(' + ' and '.join(expr) + ')'
1390 1390 else:
1391 1391 expr = None
1392 1392 return expr, filematcher
1393 1393
1394 1394 def getgraphlogrevs(repo, pats, opts):
1395 1395 """Return (revs, expr, filematcher) where revs is an iterable of
1396 1396 revision numbers, expr is a revset string built from log options
1397 1397 and file patterns or None, and used to filter 'revs'. If --stat or
1398 1398 --patch are not passed filematcher is None. Otherwise it is a
1399 1399 callable taking a revision number and returning a match objects
1400 1400 filtering the files to be detailed when displaying the revision.
1401 1401 """
1402 1402 if not len(repo):
1403 1403 return [], None, None
1404 1404 limit = loglimit(opts)
1405 1405 # Default --rev value depends on --follow but --follow behaviour
1406 1406 # depends on revisions resolved from --rev...
1407 1407 follow = opts.get('follow') or opts.get('follow_first')
1408 1408 possiblyunsorted = False # whether revs might need sorting
1409 1409 if opts.get('rev'):
1410 1410 revs = scmutil.revrange(repo, opts['rev'])
1411 1411 # Don't sort here because _makegraphlogrevset might depend on the
1412 1412 # order of revs
1413 1413 possiblyunsorted = True
1414 1414 else:
1415 1415 if follow and len(repo) > 0:
1416 1416 revs = repo.revs('reverse(:.)')
1417 1417 else:
1418 1418 revs = list(repo.changelog)
1419 1419 revs.reverse()
1420 1420 if not revs:
1421 1421 return [], None, None
1422 1422 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1423 1423 if possiblyunsorted:
1424 1424 revs.sort(reverse=True)
1425 1425 if expr:
1426 1426 # Revset matchers often operate faster on revisions in changelog
1427 1427 # order, because most filters deal with the changelog.
1428 1428 revs.reverse()
1429 1429 matcher = revset.match(repo.ui, expr)
1430 1430 # Revset matches can reorder revisions. "A or B" typically returns
1431 1431 # returns the revision matching A then the revision matching B. Sort
1432 1432 # again to fix that.
1433 1433 revs = matcher(repo, revs)
1434 1434 revs.sort(reverse=True)
1435 1435 if limit is not None:
1436 1436 revs = revs[:limit]
1437 1437
1438 1438 return revs, expr, filematcher
1439 1439
1440 1440 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1441 1441 filematcher=None):
1442 1442 seen, state = [], graphmod.asciistate()
1443 1443 for rev, type, ctx, parents in dag:
1444 1444 char = 'o'
1445 1445 if ctx.node() in showparents:
1446 1446 char = '@'
1447 1447 elif ctx.obsolete():
1448 1448 char = 'x'
1449 1449 copies = None
1450 1450 if getrenamed and ctx.rev():
1451 1451 copies = []
1452 1452 for fn in ctx.files():
1453 1453 rename = getrenamed(fn, ctx.rev())
1454 1454 if rename:
1455 1455 copies.append((fn, rename[0]))
1456 1456 revmatchfn = None
1457 1457 if filematcher is not None:
1458 1458 revmatchfn = filematcher(ctx.rev())
1459 1459 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1460 1460 lines = displayer.hunk.pop(rev).split('\n')
1461 1461 if not lines[-1]:
1462 1462 del lines[-1]
1463 1463 displayer.flush(rev)
1464 1464 edges = edgefn(type, char, lines, seen, rev, parents)
1465 1465 for type, char, lines, coldata in edges:
1466 1466 graphmod.ascii(ui, state, type, char, lines, coldata)
1467 1467 displayer.close()
1468 1468
1469 1469 def graphlog(ui, repo, *pats, **opts):
1470 1470 # Parameters are identical to log command ones
1471 1471 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1472 1472 revdag = graphmod.dagwalker(repo, revs)
1473 1473
1474 1474 getrenamed = None
1475 1475 if opts.get('copies'):
1476 1476 endrev = None
1477 1477 if opts.get('rev'):
1478 1478 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1479 1479 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1480 1480 displayer = show_changeset(ui, repo, opts, buffered=True)
1481 1481 showparents = [ctx.node() for ctx in repo[None].parents()]
1482 1482 displaygraph(ui, revdag, displayer, showparents,
1483 1483 graphmod.asciiedges, getrenamed, filematcher)
1484 1484
1485 1485 def checkunsupportedgraphflags(pats, opts):
1486 1486 for op in ["newest_first"]:
1487 1487 if op in opts and opts[op]:
1488 1488 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1489 1489 % op.replace("_", "-"))
1490 1490
1491 1491 def graphrevs(repo, nodes, opts):
1492 1492 limit = loglimit(opts)
1493 1493 nodes.reverse()
1494 1494 if limit is not None:
1495 1495 nodes = nodes[:limit]
1496 1496 return graphmod.nodes(repo, nodes)
1497 1497
1498 1498 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1499 1499 join = lambda f: os.path.join(prefix, f)
1500 1500 bad = []
1501 1501 oldbad = match.bad
1502 1502 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1503 1503 names = []
1504 1504 wctx = repo[None]
1505 1505 cca = None
1506 1506 abort, warn = scmutil.checkportabilityalert(ui)
1507 1507 if abort or warn:
1508 1508 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1509 1509 for f in repo.walk(match):
1510 1510 exact = match.exact(f)
1511 1511 if exact or not explicitonly and f not in repo.dirstate:
1512 1512 if cca:
1513 1513 cca(f)
1514 1514 names.append(f)
1515 1515 if ui.verbose or not exact:
1516 1516 ui.status(_('adding %s\n') % match.rel(join(f)))
1517 1517
1518 1518 for subpath in wctx.substate:
1519 1519 sub = wctx.sub(subpath)
1520 1520 try:
1521 1521 submatch = matchmod.narrowmatcher(subpath, match)
1522 1522 if listsubrepos:
1523 1523 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1524 1524 False))
1525 1525 else:
1526 1526 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1527 1527 True))
1528 1528 except error.LookupError:
1529 1529 ui.status(_("skipping missing subrepository: %s\n")
1530 1530 % join(subpath))
1531 1531
1532 1532 if not dryrun:
1533 1533 rejected = wctx.add(names, prefix)
1534 1534 bad.extend(f for f in rejected if f in match.files())
1535 1535 return bad
1536 1536
1537 1537 def forget(ui, repo, match, prefix, explicitonly):
1538 1538 join = lambda f: os.path.join(prefix, f)
1539 1539 bad = []
1540 1540 oldbad = match.bad
1541 1541 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1542 1542 wctx = repo[None]
1543 1543 forgot = []
1544 1544 s = repo.status(match=match, clean=True)
1545 1545 forget = sorted(s[0] + s[1] + s[3] + s[6])
1546 1546 if explicitonly:
1547 1547 forget = [f for f in forget if match.exact(f)]
1548 1548
1549 1549 for subpath in wctx.substate:
1550 1550 sub = wctx.sub(subpath)
1551 1551 try:
1552 1552 submatch = matchmod.narrowmatcher(subpath, match)
1553 1553 subbad, subforgot = sub.forget(ui, submatch, prefix)
1554 1554 bad.extend([subpath + '/' + f for f in subbad])
1555 1555 forgot.extend([subpath + '/' + f for f in subforgot])
1556 1556 except error.LookupError:
1557 1557 ui.status(_("skipping missing subrepository: %s\n")
1558 1558 % join(subpath))
1559 1559
1560 1560 if not explicitonly:
1561 1561 for f in match.files():
1562 1562 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1563 1563 if f not in forgot:
1564 1564 if os.path.exists(match.rel(join(f))):
1565 1565 ui.warn(_('not removing %s: '
1566 1566 'file is already untracked\n')
1567 1567 % match.rel(join(f)))
1568 1568 bad.append(f)
1569 1569
1570 1570 for f in forget:
1571 1571 if ui.verbose or not match.exact(f):
1572 1572 ui.status(_('removing %s\n') % match.rel(join(f)))
1573 1573
1574 1574 rejected = wctx.forget(forget, prefix)
1575 1575 bad.extend(f for f in rejected if f in match.files())
1576 1576 forgot.extend(forget)
1577 1577 return bad, forgot
1578 1578
1579 1579 def duplicatecopies(repo, rev, p1):
1580 1580 "Reproduce copies found in the source revision in the dirstate for grafts"
1581 1581 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1582 1582 repo.dirstate.copy(src, dst)
1583 1583
1584 1584 def commit(ui, repo, commitfunc, pats, opts):
1585 1585 '''commit the specified files or all outstanding changes'''
1586 1586 date = opts.get('date')
1587 1587 if date:
1588 1588 opts['date'] = util.parsedate(date)
1589 1589 message = logmessage(ui, opts)
1590 1590
1591 1591 # extract addremove carefully -- this function can be called from a command
1592 1592 # that doesn't support addremove
1593 1593 if opts.get('addremove'):
1594 1594 scmutil.addremove(repo, pats, opts)
1595 1595
1596 1596 return commitfunc(ui, repo, message,
1597 1597 scmutil.match(repo[None], pats, opts), opts)
1598 1598
1599 1599 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1600 1600 ui.note(_('amending changeset %s\n') % old)
1601 1601 base = old.p1()
1602 1602
1603 1603 wlock = lock = newid = None
1604 1604 try:
1605 1605 wlock = repo.wlock()
1606 1606 lock = repo.lock()
1607 1607 tr = repo.transaction('amend')
1608 1608 try:
1609 1609 # See if we got a message from -m or -l, if not, open the editor
1610 1610 # with the message of the changeset to amend
1611 1611 message = logmessage(ui, opts)
1612 1612 # ensure logfile does not conflict with later enforcement of the
1613 1613 # message. potential logfile content has been processed by
1614 1614 # `logmessage` anyway.
1615 1615 opts.pop('logfile')
1616 1616 # First, do a regular commit to record all changes in the working
1617 1617 # directory (if there are any)
1618 1618 ui.callhooks = False
1619 1619 currentbookmark = repo._bookmarkcurrent
1620 1620 try:
1621 1621 repo._bookmarkcurrent = None
1622 1622 opts['message'] = 'temporary amend commit for %s' % old
1623 1623 node = commit(ui, repo, commitfunc, pats, opts)
1624 1624 finally:
1625 1625 repo._bookmarkcurrent = currentbookmark
1626 1626 ui.callhooks = True
1627 1627 ctx = repo[node]
1628 1628
1629 1629 # Participating changesets:
1630 1630 #
1631 1631 # node/ctx o - new (intermediate) commit that contains changes
1632 1632 # | from working dir to go into amending commit
1633 1633 # | (or a workingctx if there were no changes)
1634 1634 # |
1635 1635 # old o - changeset to amend
1636 1636 # |
1637 1637 # base o - parent of amending changeset
1638 1638
1639 1639 # Update extra dict from amended commit (e.g. to preserve graft
1640 1640 # source)
1641 1641 extra.update(old.extra())
1642 1642
1643 1643 # Also update it from the intermediate commit or from the wctx
1644 1644 extra.update(ctx.extra())
1645 1645
1646 1646 files = set(old.files())
1647 1647
1648 1648 # Second, we use either the commit we just did, or if there were no
1649 1649 # changes the parent of the working directory as the version of the
1650 1650 # files in the final amend commit
1651 1651 if node:
1652 1652 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1653 1653
1654 1654 user = ctx.user()
1655 1655 date = ctx.date()
1656 1656 # Recompute copies (avoid recording a -> b -> a)
1657 1657 copied = copies.pathcopies(base, ctx)
1658 1658
1659 1659 # Prune files which were reverted by the updates: if old
1660 1660 # introduced file X and our intermediate commit, node,
1661 1661 # renamed that file, then those two files are the same and
1662 1662 # we can discard X from our list of files. Likewise if X
1663 1663 # was deleted, it's no longer relevant
1664 1664 files.update(ctx.files())
1665 1665
1666 1666 def samefile(f):
1667 1667 if f in ctx.manifest():
1668 1668 a = ctx.filectx(f)
1669 1669 if f in base.manifest():
1670 1670 b = base.filectx(f)
1671 1671 return (not a.cmp(b)
1672 1672 and a.flags() == b.flags())
1673 1673 else:
1674 1674 return False
1675 1675 else:
1676 1676 return f not in base.manifest()
1677 1677 files = [f for f in files if not samefile(f)]
1678 1678
1679 1679 def filectxfn(repo, ctx_, path):
1680 1680 try:
1681 1681 fctx = ctx[path]
1682 1682 flags = fctx.flags()
1683 1683 mctx = context.memfilectx(fctx.path(), fctx.data(),
1684 1684 islink='l' in flags,
1685 1685 isexec='x' in flags,
1686 1686 copied=copied.get(path))
1687 1687 return mctx
1688 1688 except KeyError:
1689 1689 raise IOError
1690 1690 else:
1691 1691 ui.note(_('copying changeset %s to %s\n') % (old, base))
1692 1692
1693 1693 # Use version of files as in the old cset
1694 1694 def filectxfn(repo, ctx_, path):
1695 1695 try:
1696 1696 return old.filectx(path)
1697 1697 except KeyError:
1698 1698 raise IOError
1699 1699
1700 1700 user = opts.get('user') or old.user()
1701 1701 date = opts.get('date') or old.date()
1702 1702 editmsg = False
1703 1703 if not message:
1704 1704 editmsg = True
1705 1705 message = old.description()
1706 1706
1707 1707 pureextra = extra.copy()
1708 1708 extra['amend_source'] = old.hex()
1709 1709
1710 1710 new = context.memctx(repo,
1711 1711 parents=[base.node(), nullid],
1712 1712 text=message,
1713 1713 files=files,
1714 1714 filectxfn=filectxfn,
1715 1715 user=user,
1716 1716 date=date,
1717 1717 extra=extra)
1718 1718 if editmsg:
1719 1719 new._text = commitforceeditor(repo, new, [])
1720 1720
1721 1721 newdesc = changelog.stripdesc(new.description())
1722 1722 if ((not node)
1723 1723 and newdesc == old.description()
1724 1724 and user == old.user()
1725 1725 and date == old.date()
1726 1726 and pureextra == old.extra()):
1727 1727 # nothing changed. continuing here would create a new node
1728 1728 # anyway because of the amend_source noise.
1729 1729 #
1730 1730 # This not what we expect from amend.
1731 1731 return old.node()
1732 1732
1733 1733 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1734 1734 try:
1735 1735 repo.ui.setconfig('phases', 'new-commit', old.phase())
1736 1736 newid = repo.commitctx(new)
1737 1737 finally:
1738 1738 repo.ui.setconfig('phases', 'new-commit', ph)
1739 1739 if newid != old.node():
1740 1740 # Reroute the working copy parent to the new changeset
1741 1741 repo.setparents(newid, nullid)
1742 1742
1743 1743 # Move bookmarks from old parent to amend commit
1744 1744 bms = repo.nodebookmarks(old.node())
1745 1745 if bms:
1746 1746 marks = repo._bookmarks
1747 1747 for bm in bms:
1748 1748 marks[bm] = newid
1749 1749 marks.write()
1750 1750 #commit the whole amend process
1751 1751 if obsolete._enabled and newid != old.node():
1752 1752 # mark the new changeset as successor of the rewritten one
1753 1753 new = repo[newid]
1754 1754 obs = [(old, (new,))]
1755 1755 if node:
1756 1756 obs.append((ctx, ()))
1757 1757
1758 1758 obsolete.createmarkers(repo, obs)
1759 1759 tr.close()
1760 1760 finally:
1761 1761 tr.release()
1762 1762 if (not obsolete._enabled) and newid != old.node():
1763 1763 # Strip the intermediate commit (if there was one) and the amended
1764 1764 # commit
1765 1765 if node:
1766 1766 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1767 1767 ui.note(_('stripping amended changeset %s\n') % old)
1768 1768 repair.strip(ui, repo, old.node(), topic='amend-backup')
1769 1769 finally:
1770 1770 if newid is None:
1771 1771 repo.dirstate.invalidate()
1772 1772 lockmod.release(wlock, lock)
1773 1773 return newid
1774 1774
1775 1775 def commiteditor(repo, ctx, subs):
1776 1776 if ctx.description():
1777 1777 return ctx.description()
1778 1778 return commitforceeditor(repo, ctx, subs)
1779 1779
1780 1780 def commitforceeditor(repo, ctx, subs):
1781 1781 edittext = []
1782 1782 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1783 1783 if ctx.description():
1784 1784 edittext.append(ctx.description())
1785 1785 edittext.append("")
1786 1786 edittext.append("") # Empty line between message and comments.
1787 1787 edittext.append(_("HG: Enter commit message."
1788 1788 " Lines beginning with 'HG:' are removed."))
1789 1789 edittext.append(_("HG: Leave message empty to abort commit."))
1790 1790 edittext.append("HG: --")
1791 1791 edittext.append(_("HG: user: %s") % ctx.user())
1792 1792 if ctx.p2():
1793 1793 edittext.append(_("HG: branch merge"))
1794 1794 if ctx.branch():
1795 1795 edittext.append(_("HG: branch '%s'") % ctx.branch())
1796 1796 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1797 1797 edittext.extend([_("HG: added %s") % f for f in added])
1798 1798 edittext.extend([_("HG: changed %s") % f for f in modified])
1799 1799 edittext.extend([_("HG: removed %s") % f for f in removed])
1800 1800 if not added and not modified and not removed:
1801 1801 edittext.append(_("HG: no files changed"))
1802 1802 edittext.append("")
1803 1803 # run editor in the repository root
1804 1804 olddir = os.getcwd()
1805 1805 os.chdir(repo.root)
1806 1806 text = repo.ui.edit("\n".join(edittext), ctx.user())
1807 1807 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1808 1808 os.chdir(olddir)
1809 1809
1810 1810 if not text.strip():
1811 1811 raise util.Abort(_("empty commit message"))
1812 1812
1813 1813 return text
1814 1814
1815 1815 def revert(ui, repo, ctx, parents, *pats, **opts):
1816 1816 parent, p2 = parents
1817 1817 node = ctx.node()
1818 1818
1819 1819 mf = ctx.manifest()
1820 1820 if node == parent:
1821 1821 pmf = mf
1822 1822 else:
1823 1823 pmf = None
1824 1824
1825 1825 # need all matching names in dirstate and manifest of target rev,
1826 1826 # so have to walk both. do not print errors if files exist in one
1827 1827 # but not other.
1828 1828
1829 1829 names = {}
1830 1830
1831 1831 wlock = repo.wlock()
1832 1832 try:
1833 1833 # walk dirstate.
1834 1834
1835 1835 m = scmutil.match(repo[None], pats, opts)
1836 1836 m.bad = lambda x, y: False
1837 1837 for abs in repo.walk(m):
1838 1838 names[abs] = m.rel(abs), m.exact(abs)
1839 1839
1840 1840 # walk target manifest.
1841 1841
1842 1842 def badfn(path, msg):
1843 1843 if path in names:
1844 1844 return
1845 1845 if path in ctx.substate:
1846 1846 return
1847 1847 path_ = path + '/'
1848 1848 for f in names:
1849 1849 if f.startswith(path_):
1850 1850 return
1851 1851 ui.warn("%s: %s\n" % (m.rel(path), msg))
1852 1852
1853 1853 m = scmutil.match(ctx, pats, opts)
1854 1854 m.bad = badfn
1855 1855 for abs in ctx.walk(m):
1856 1856 if abs not in names:
1857 1857 names[abs] = m.rel(abs), m.exact(abs)
1858 1858
1859 1859 # get the list of subrepos that must be reverted
1860 1860 targetsubs = [s for s in ctx.substate if m(s)]
1861 1861 m = scmutil.matchfiles(repo, names)
1862 1862 changes = repo.status(match=m)[:4]
1863 1863 modified, added, removed, deleted = map(set, changes)
1864 1864
1865 1865 # if f is a rename, also revert the source
1866 1866 cwd = repo.getcwd()
1867 1867 for f in added:
1868 1868 src = repo.dirstate.copied(f)
1869 1869 if src and src not in names and repo.dirstate[src] == 'r':
1870 1870 removed.add(src)
1871 1871 names[src] = (repo.pathto(src, cwd), True)
1872 1872
1873 1873 def removeforget(abs):
1874 1874 if repo.dirstate[abs] == 'a':
1875 1875 return _('forgetting %s\n')
1876 1876 return _('removing %s\n')
1877 1877
1878 1878 revert = ([], _('reverting %s\n'))
1879 1879 add = ([], _('adding %s\n'))
1880 1880 remove = ([], removeforget)
1881 1881 undelete = ([], _('undeleting %s\n'))
1882 1882
1883 1883 disptable = (
1884 1884 # dispatch table:
1885 1885 # file state
1886 1886 # action if in target manifest
1887 1887 # action if not in target manifest
1888 1888 # make backup if in target manifest
1889 1889 # make backup if not in target manifest
1890 1890 (modified, revert, remove, True, True),
1891 1891 (added, revert, remove, True, False),
1892 1892 (removed, undelete, None, False, False),
1893 1893 (deleted, revert, remove, False, False),
1894 1894 )
1895 1895
1896 1896 for abs, (rel, exact) in sorted(names.items()):
1897 1897 mfentry = mf.get(abs)
1898 1898 target = repo.wjoin(abs)
1899 1899 def handle(xlist, dobackup):
1900 1900 xlist[0].append(abs)
1901 1901 if (dobackup and not opts.get('no_backup') and
1902 1902 os.path.lexists(target)):
1903 1903 bakname = "%s.orig" % rel
1904 1904 ui.note(_('saving current version of %s as %s\n') %
1905 1905 (rel, bakname))
1906 1906 if not opts.get('dry_run'):
1907 1907 util.rename(target, bakname)
1908 1908 if ui.verbose or not exact:
1909 1909 msg = xlist[1]
1910 1910 if not isinstance(msg, basestring):
1911 1911 msg = msg(abs)
1912 1912 ui.status(msg % rel)
1913 1913 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1914 1914 if abs not in table:
1915 1915 continue
1916 1916 # file has changed in dirstate
1917 1917 if mfentry:
1918 1918 handle(hitlist, backuphit)
1919 1919 elif misslist is not None:
1920 1920 handle(misslist, backupmiss)
1921 1921 break
1922 1922 else:
1923 1923 if abs not in repo.dirstate:
1924 1924 if mfentry:
1925 1925 handle(add, True)
1926 1926 elif exact:
1927 1927 ui.warn(_('file not managed: %s\n') % rel)
1928 1928 continue
1929 1929 # file has not changed in dirstate
1930 1930 if node == parent:
1931 1931 if exact:
1932 1932 ui.warn(_('no changes needed to %s\n') % rel)
1933 1933 continue
1934 1934 if pmf is None:
1935 1935 # only need parent manifest in this unlikely case,
1936 1936 # so do not read by default
1937 1937 pmf = repo[parent].manifest()
1938 1938 if abs in pmf and mfentry:
1939 1939 # if version of file is same in parent and target
1940 1940 # manifests, do nothing
1941 1941 if (pmf[abs] != mfentry or
1942 1942 pmf.flags(abs) != mf.flags(abs)):
1943 1943 handle(revert, False)
1944 1944 else:
1945 1945 handle(remove, False)
1946 1946
1947 1947 if not opts.get('dry_run'):
1948 1948 def checkout(f):
1949 1949 fc = ctx[f]
1950 1950 repo.wwrite(f, fc.data(), fc.flags())
1951 1951
1952 1952 audit_path = scmutil.pathauditor(repo.root)
1953 1953 for f in remove[0]:
1954 1954 if repo.dirstate[f] == 'a':
1955 1955 repo.dirstate.drop(f)
1956 1956 continue
1957 1957 audit_path(f)
1958 1958 try:
1959 1959 util.unlinkpath(repo.wjoin(f))
1960 1960 except OSError:
1961 1961 pass
1962 1962 repo.dirstate.remove(f)
1963 1963
1964 1964 normal = None
1965 1965 if node == parent:
1966 1966 # We're reverting to our parent. If possible, we'd like status
1967 1967 # to report the file as clean. We have to use normallookup for
1968 1968 # merges to avoid losing information about merged/dirty files.
1969 1969 if p2 != nullid:
1970 1970 normal = repo.dirstate.normallookup
1971 1971 else:
1972 1972 normal = repo.dirstate.normal
1973 1973 for f in revert[0]:
1974 1974 checkout(f)
1975 1975 if normal:
1976 1976 normal(f)
1977 1977
1978 1978 for f in add[0]:
1979 1979 checkout(f)
1980 1980 repo.dirstate.add(f)
1981 1981
1982 1982 normal = repo.dirstate.normallookup
1983 1983 if node == parent and p2 == nullid:
1984 1984 normal = repo.dirstate.normal
1985 1985 for f in undelete[0]:
1986 1986 checkout(f)
1987 1987 normal(f)
1988 1988
1989 1989 if targetsubs:
1990 1990 # Revert the subrepos on the revert list
1991 1991 for sub in targetsubs:
1992 1992 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
1993 1993 finally:
1994 1994 wlock.release()
1995 1995
1996 1996 def command(table):
1997 1997 '''returns a function object bound to table which can be used as
1998 1998 a decorator for populating table as a command table'''
1999 1999
2000 2000 def cmd(name, options=(), synopsis=None):
2001 2001 def decorator(func):
2002 2002 if synopsis:
2003 2003 table[name] = func, list(options), synopsis
2004 2004 else:
2005 2005 table[name] = func, list(options)
2006 2006 return func
2007 2007 return decorator
2008 2008
2009 2009 return cmd
@@ -1,1273 +1,1299
1 1 The g is crafted to have 2 filelog topological heads in a linear
2 2 changeset graph
3 3
4 4 $ hg init a
5 5 $ cd a
6 6 $ echo a > a
7 7 $ echo f > f
8 8 $ hg ci -Ama -d '1 0'
9 9 adding a
10 10 adding f
11 11
12 12 $ hg cp a b
13 13 $ hg cp f g
14 14 $ hg ci -mb -d '2 0'
15 15
16 16 $ mkdir dir
17 17 $ hg mv b dir
18 18 $ echo g >> g
19 19 $ echo f >> f
20 20 $ hg ci -mc -d '3 0'
21 21
22 22 $ hg mv a b
23 23 $ hg cp -f f g
24 24 $ echo a > d
25 25 $ hg add d
26 26 $ hg ci -md -d '4 0'
27 27
28 28 $ hg mv dir/b e
29 29 $ hg ci -me -d '5 0'
30 30
31 31 $ hg log a
32 32 changeset: 0:9161b9aeaf16
33 33 user: test
34 34 date: Thu Jan 01 00:00:01 1970 +0000
35 35 summary: a
36 36
37 log on directory
38
39 $ hg log dir
40 changeset: 4:7e4639b4691b
41 tag: tip
42 user: test
43 date: Thu Jan 01 00:00:05 1970 +0000
44 summary: e
45
46 changeset: 2:f8954cd4dc1f
47 user: test
48 date: Thu Jan 01 00:00:03 1970 +0000
49 summary: c
50
51 $ hg log somethingthatdoesntexist dir
52 changeset: 4:7e4639b4691b
53 tag: tip
54 user: test
55 date: Thu Jan 01 00:00:05 1970 +0000
56 summary: e
57
58 changeset: 2:f8954cd4dc1f
59 user: test
60 date: Thu Jan 01 00:00:03 1970 +0000
61 summary: c
62
37 63
38 64 -f, directory
39 65
40 66 $ hg log -f dir
41 67 abort: cannot follow file not in parent revision: "dir"
42 68 [255]
43 69
44 70 -f, but no args
45 71
46 72 $ hg log -f
47 73 changeset: 4:7e4639b4691b
48 74 tag: tip
49 75 user: test
50 76 date: Thu Jan 01 00:00:05 1970 +0000
51 77 summary: e
52 78
53 79 changeset: 3:2ca5ba701980
54 80 user: test
55 81 date: Thu Jan 01 00:00:04 1970 +0000
56 82 summary: d
57 83
58 84 changeset: 2:f8954cd4dc1f
59 85 user: test
60 86 date: Thu Jan 01 00:00:03 1970 +0000
61 87 summary: c
62 88
63 89 changeset: 1:d89b0a12d229
64 90 user: test
65 91 date: Thu Jan 01 00:00:02 1970 +0000
66 92 summary: b
67 93
68 94 changeset: 0:9161b9aeaf16
69 95 user: test
70 96 date: Thu Jan 01 00:00:01 1970 +0000
71 97 summary: a
72 98
73 99
74 100 one rename
75 101
76 102 $ hg up -q 2
77 103 $ hg log -vf a
78 104 changeset: 0:9161b9aeaf16
79 105 user: test
80 106 date: Thu Jan 01 00:00:01 1970 +0000
81 107 files: a f
82 108 description:
83 109 a
84 110
85 111
86 112
87 113 many renames
88 114
89 115 $ hg up -q tip
90 116 $ hg log -vf e
91 117 changeset: 4:7e4639b4691b
92 118 tag: tip
93 119 user: test
94 120 date: Thu Jan 01 00:00:05 1970 +0000
95 121 files: dir/b e
96 122 description:
97 123 e
98 124
99 125
100 126 changeset: 2:f8954cd4dc1f
101 127 user: test
102 128 date: Thu Jan 01 00:00:03 1970 +0000
103 129 files: b dir/b f g
104 130 description:
105 131 c
106 132
107 133
108 134 changeset: 1:d89b0a12d229
109 135 user: test
110 136 date: Thu Jan 01 00:00:02 1970 +0000
111 137 files: b g
112 138 description:
113 139 b
114 140
115 141
116 142 changeset: 0:9161b9aeaf16
117 143 user: test
118 144 date: Thu Jan 01 00:00:01 1970 +0000
119 145 files: a f
120 146 description:
121 147 a
122 148
123 149
124 150
125 151
126 152 log -pf dir/b
127 153
128 154 $ hg up -q 3
129 155 $ hg log -pf dir/b
130 156 changeset: 2:f8954cd4dc1f
131 157 user: test
132 158 date: Thu Jan 01 00:00:03 1970 +0000
133 159 summary: c
134 160
135 161 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
136 162 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
137 163 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
138 164 @@ -0,0 +1,1 @@
139 165 +a
140 166
141 167 changeset: 1:d89b0a12d229
142 168 user: test
143 169 date: Thu Jan 01 00:00:02 1970 +0000
144 170 summary: b
145 171
146 172 diff -r 9161b9aeaf16 -r d89b0a12d229 b
147 173 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
148 174 +++ b/b Thu Jan 01 00:00:02 1970 +0000
149 175 @@ -0,0 +1,1 @@
150 176 +a
151 177
152 178 changeset: 0:9161b9aeaf16
153 179 user: test
154 180 date: Thu Jan 01 00:00:01 1970 +0000
155 181 summary: a
156 182
157 183 diff -r 000000000000 -r 9161b9aeaf16 a
158 184 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
159 185 +++ b/a Thu Jan 01 00:00:01 1970 +0000
160 186 @@ -0,0 +1,1 @@
161 187 +a
162 188
163 189
164 190 log -vf dir/b
165 191
166 192 $ hg log -vf dir/b
167 193 changeset: 2:f8954cd4dc1f
168 194 user: test
169 195 date: Thu Jan 01 00:00:03 1970 +0000
170 196 files: b dir/b f g
171 197 description:
172 198 c
173 199
174 200
175 201 changeset: 1:d89b0a12d229
176 202 user: test
177 203 date: Thu Jan 01 00:00:02 1970 +0000
178 204 files: b g
179 205 description:
180 206 b
181 207
182 208
183 209 changeset: 0:9161b9aeaf16
184 210 user: test
185 211 date: Thu Jan 01 00:00:01 1970 +0000
186 212 files: a f
187 213 description:
188 214 a
189 215
190 216
191 217
192 218
193 219 -f and multiple filelog heads
194 220
195 221 $ hg up -q 2
196 222 $ hg log -f g --template '{rev}\n'
197 223 2
198 224 1
199 225 0
200 226 $ hg up -q tip
201 227 $ hg log -f g --template '{rev}\n'
202 228 3
203 229 2
204 230 0
205 231
206 232
207 233 log copies with --copies
208 234
209 235 $ hg log -vC --template '{rev} {file_copies}\n'
210 236 4 e (dir/b)
211 237 3 b (a)g (f)
212 238 2 dir/b (b)
213 239 1 b (a)g (f)
214 240 0
215 241
216 242 log copies switch without --copies, with old filecopy template
217 243
218 244 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
219 245 4
220 246 3
221 247 2
222 248 1
223 249 0
224 250
225 251 log copies switch with --copies
226 252
227 253 $ hg log -vC --template '{rev} {file_copies_switch}\n'
228 254 4 e (dir/b)
229 255 3 b (a)g (f)
230 256 2 dir/b (b)
231 257 1 b (a)g (f)
232 258 0
233 259
234 260
235 261 log copies with hardcoded style and with --style=default
236 262
237 263 $ hg log -vC -r4
238 264 changeset: 4:7e4639b4691b
239 265 tag: tip
240 266 user: test
241 267 date: Thu Jan 01 00:00:05 1970 +0000
242 268 files: dir/b e
243 269 copies: e (dir/b)
244 270 description:
245 271 e
246 272
247 273
248 274 $ hg log -vC -r4 --style=default
249 275 changeset: 4:7e4639b4691b
250 276 tag: tip
251 277 user: test
252 278 date: Thu Jan 01 00:00:05 1970 +0000
253 279 files: dir/b e
254 280 copies: e (dir/b)
255 281 description:
256 282 e
257 283
258 284
259 285
260 286
261 287 log copies, non-linear manifest
262 288
263 289 $ hg up -C 3
264 290 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
265 291 $ hg mv dir/b e
266 292 $ echo foo > foo
267 293 $ hg ci -Ame2 -d '6 0'
268 294 adding foo
269 295 created new head
270 296 $ hg log -v --template '{rev} {file_copies}\n' -r 5
271 297 5 e (dir/b)
272 298
273 299
274 300 log copies, execute bit set
275 301
276 302 #if execbit
277 303 $ chmod +x e
278 304 $ hg ci -me3 -d '7 0'
279 305 $ hg log -v --template '{rev} {file_copies}\n' -r 6
280 306 6
281 307 #endif
282 308
283 309
284 310 log -p d
285 311
286 312 $ hg log -pv d
287 313 changeset: 3:2ca5ba701980
288 314 user: test
289 315 date: Thu Jan 01 00:00:04 1970 +0000
290 316 files: a b d g
291 317 description:
292 318 d
293 319
294 320
295 321 diff -r f8954cd4dc1f -r 2ca5ba701980 d
296 322 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
297 323 +++ b/d Thu Jan 01 00:00:04 1970 +0000
298 324 @@ -0,0 +1,1 @@
299 325 +a
300 326
301 327
302 328
303 329 log --removed file
304 330
305 331 $ hg log --removed -v a
306 332 changeset: 3:2ca5ba701980
307 333 user: test
308 334 date: Thu Jan 01 00:00:04 1970 +0000
309 335 files: a b d g
310 336 description:
311 337 d
312 338
313 339
314 340 changeset: 0:9161b9aeaf16
315 341 user: test
316 342 date: Thu Jan 01 00:00:01 1970 +0000
317 343 files: a f
318 344 description:
319 345 a
320 346
321 347
322 348
323 349 log --removed revrange file
324 350
325 351 $ hg log --removed -v -r0:2 a
326 352 changeset: 0:9161b9aeaf16
327 353 user: test
328 354 date: Thu Jan 01 00:00:01 1970 +0000
329 355 files: a f
330 356 description:
331 357 a
332 358
333 359
334 360 $ cd ..
335 361
336 362 log --follow tests
337 363
338 364 $ hg init follow
339 365 $ cd follow
340 366
341 367 $ echo base > base
342 368 $ hg ci -Ambase -d '1 0'
343 369 adding base
344 370
345 371 $ echo r1 >> base
346 372 $ hg ci -Amr1 -d '1 0'
347 373 $ echo r2 >> base
348 374 $ hg ci -Amr2 -d '1 0'
349 375
350 376 $ hg up -C 1
351 377 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
352 378 $ echo b1 > b1
353 379 $ hg ci -Amb1 -d '1 0'
354 380 adding b1
355 381 created new head
356 382
357 383
358 384 log -f
359 385
360 386 $ hg log -f
361 387 changeset: 3:e62f78d544b4
362 388 tag: tip
363 389 parent: 1:3d5bf5654eda
364 390 user: test
365 391 date: Thu Jan 01 00:00:01 1970 +0000
366 392 summary: b1
367 393
368 394 changeset: 1:3d5bf5654eda
369 395 user: test
370 396 date: Thu Jan 01 00:00:01 1970 +0000
371 397 summary: r1
372 398
373 399 changeset: 0:67e992f2c4f3
374 400 user: test
375 401 date: Thu Jan 01 00:00:01 1970 +0000
376 402 summary: base
377 403
378 404
379 405
380 406 log -f -r 1:tip
381 407
382 408 $ hg up -C 0
383 409 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
384 410 $ echo b2 > b2
385 411 $ hg ci -Amb2 -d '1 0'
386 412 adding b2
387 413 created new head
388 414 $ hg log -f -r 1:tip
389 415 changeset: 1:3d5bf5654eda
390 416 user: test
391 417 date: Thu Jan 01 00:00:01 1970 +0000
392 418 summary: r1
393 419
394 420 changeset: 2:60c670bf5b30
395 421 user: test
396 422 date: Thu Jan 01 00:00:01 1970 +0000
397 423 summary: r2
398 424
399 425 changeset: 3:e62f78d544b4
400 426 parent: 1:3d5bf5654eda
401 427 user: test
402 428 date: Thu Jan 01 00:00:01 1970 +0000
403 429 summary: b1
404 430
405 431
406 432
407 433 log -r . with two parents
408 434
409 435 $ hg up -C 3
410 436 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
411 437 $ hg merge tip
412 438 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
413 439 (branch merge, don't forget to commit)
414 440 $ hg log -r .
415 441 changeset: 3:e62f78d544b4
416 442 parent: 1:3d5bf5654eda
417 443 user: test
418 444 date: Thu Jan 01 00:00:01 1970 +0000
419 445 summary: b1
420 446
421 447
422 448
423 449 log -r . with one parent
424 450
425 451 $ hg ci -mm12 -d '1 0'
426 452 $ hg log -r .
427 453 changeset: 5:302e9dd6890d
428 454 tag: tip
429 455 parent: 3:e62f78d544b4
430 456 parent: 4:ddb82e70d1a1
431 457 user: test
432 458 date: Thu Jan 01 00:00:01 1970 +0000
433 459 summary: m12
434 460
435 461
436 462 $ echo postm >> b1
437 463 $ hg ci -Amb1.1 -d'1 0'
438 464
439 465
440 466 log --follow-first
441 467
442 468 $ hg log --follow-first
443 469 changeset: 6:2404bbcab562
444 470 tag: tip
445 471 user: test
446 472 date: Thu Jan 01 00:00:01 1970 +0000
447 473 summary: b1.1
448 474
449 475 changeset: 5:302e9dd6890d
450 476 parent: 3:e62f78d544b4
451 477 parent: 4:ddb82e70d1a1
452 478 user: test
453 479 date: Thu Jan 01 00:00:01 1970 +0000
454 480 summary: m12
455 481
456 482 changeset: 3:e62f78d544b4
457 483 parent: 1:3d5bf5654eda
458 484 user: test
459 485 date: Thu Jan 01 00:00:01 1970 +0000
460 486 summary: b1
461 487
462 488 changeset: 1:3d5bf5654eda
463 489 user: test
464 490 date: Thu Jan 01 00:00:01 1970 +0000
465 491 summary: r1
466 492
467 493 changeset: 0:67e992f2c4f3
468 494 user: test
469 495 date: Thu Jan 01 00:00:01 1970 +0000
470 496 summary: base
471 497
472 498
473 499
474 500 log -P 2
475 501
476 502 $ hg log -P 2
477 503 changeset: 6:2404bbcab562
478 504 tag: tip
479 505 user: test
480 506 date: Thu Jan 01 00:00:01 1970 +0000
481 507 summary: b1.1
482 508
483 509 changeset: 5:302e9dd6890d
484 510 parent: 3:e62f78d544b4
485 511 parent: 4:ddb82e70d1a1
486 512 user: test
487 513 date: Thu Jan 01 00:00:01 1970 +0000
488 514 summary: m12
489 515
490 516 changeset: 4:ddb82e70d1a1
491 517 parent: 0:67e992f2c4f3
492 518 user: test
493 519 date: Thu Jan 01 00:00:01 1970 +0000
494 520 summary: b2
495 521
496 522 changeset: 3:e62f78d544b4
497 523 parent: 1:3d5bf5654eda
498 524 user: test
499 525 date: Thu Jan 01 00:00:01 1970 +0000
500 526 summary: b1
501 527
502 528
503 529
504 530 log -r tip -p --git
505 531
506 532 $ hg log -r tip -p --git
507 533 changeset: 6:2404bbcab562
508 534 tag: tip
509 535 user: test
510 536 date: Thu Jan 01 00:00:01 1970 +0000
511 537 summary: b1.1
512 538
513 539 diff --git a/b1 b/b1
514 540 --- a/b1
515 541 +++ b/b1
516 542 @@ -1,1 +1,2 @@
517 543 b1
518 544 +postm
519 545
520 546
521 547
522 548 log -r ""
523 549
524 550 $ hg log -r ''
525 551 hg: parse error: empty query
526 552 [255]
527 553
528 554 log -r <some unknown node id>
529 555
530 556 $ hg log -r 1000000000000000000000000000000000000000
531 557 abort: unknown revision '1000000000000000000000000000000000000000'!
532 558 [255]
533 559
534 560 log -k r1
535 561
536 562 $ hg log -k r1
537 563 changeset: 1:3d5bf5654eda
538 564 user: test
539 565 date: Thu Jan 01 00:00:01 1970 +0000
540 566 summary: r1
541 567
542 568 log -p -l2 --color=always
543 569
544 570 $ hg --config extensions.color= --config color.mode=ansi \
545 571 > log -p -l2 --color=always
546 572 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
547 573 tag: tip
548 574 user: test
549 575 date: Thu Jan 01 00:00:01 1970 +0000
550 576 summary: b1.1
551 577
552 578 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
553 579 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
554 580 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
555 581 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
556 582 b1
557 583 \x1b[0;32m+postm\x1b[0m (esc)
558 584
559 585 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
560 586 parent: 3:e62f78d544b4
561 587 parent: 4:ddb82e70d1a1
562 588 user: test
563 589 date: Thu Jan 01 00:00:01 1970 +0000
564 590 summary: m12
565 591
566 592 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
567 593 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
568 594 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
569 595 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
570 596 \x1b[0;32m+b2\x1b[0m (esc)
571 597
572 598
573 599
574 600 log -r tip --stat
575 601
576 602 $ hg log -r tip --stat
577 603 changeset: 6:2404bbcab562
578 604 tag: tip
579 605 user: test
580 606 date: Thu Jan 01 00:00:01 1970 +0000
581 607 summary: b1.1
582 608
583 609 b1 | 1 +
584 610 1 files changed, 1 insertions(+), 0 deletions(-)
585 611
586 612
587 613 $ cd ..
588 614
589 615
590 616 User
591 617
592 618 $ hg init usertest
593 619 $ cd usertest
594 620
595 621 $ echo a > a
596 622 $ hg ci -A -m "a" -u "User One <user1@example.org>"
597 623 adding a
598 624 $ echo b > b
599 625 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
600 626 adding b
601 627
602 628 $ hg log -u "User One <user1@example.org>"
603 629 changeset: 0:29a4c94f1924
604 630 user: User One <user1@example.org>
605 631 date: Thu Jan 01 00:00:00 1970 +0000
606 632 summary: a
607 633
608 634 $ hg log -u "user1" -u "user2"
609 635 changeset: 1:e834b5e69c0e
610 636 tag: tip
611 637 user: User Two <user2@example.org>
612 638 date: Thu Jan 01 00:00:00 1970 +0000
613 639 summary: b
614 640
615 641 changeset: 0:29a4c94f1924
616 642 user: User One <user1@example.org>
617 643 date: Thu Jan 01 00:00:00 1970 +0000
618 644 summary: a
619 645
620 646 $ hg log -u "user3"
621 647
622 648 $ cd ..
623 649
624 650 $ hg init branches
625 651 $ cd branches
626 652
627 653 $ echo a > a
628 654 $ hg ci -A -m "commit on default"
629 655 adding a
630 656 $ hg branch test
631 657 marked working directory as branch test
632 658 (branches are permanent and global, did you want a bookmark?)
633 659 $ echo b > b
634 660 $ hg ci -A -m "commit on test"
635 661 adding b
636 662
637 663 $ hg up default
638 664 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
639 665 $ echo c > c
640 666 $ hg ci -A -m "commit on default"
641 667 adding c
642 668 $ hg up test
643 669 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
644 670 $ echo c > c
645 671 $ hg ci -A -m "commit on test"
646 672 adding c
647 673
648 674
649 675 log -b default
650 676
651 677 $ hg log -b default
652 678 changeset: 2:c3a4f03cc9a7
653 679 parent: 0:24427303d56f
654 680 user: test
655 681 date: Thu Jan 01 00:00:00 1970 +0000
656 682 summary: commit on default
657 683
658 684 changeset: 0:24427303d56f
659 685 user: test
660 686 date: Thu Jan 01 00:00:00 1970 +0000
661 687 summary: commit on default
662 688
663 689
664 690
665 691 log -b test
666 692
667 693 $ hg log -b test
668 694 changeset: 3:f5d8de11c2e2
669 695 branch: test
670 696 tag: tip
671 697 parent: 1:d32277701ccb
672 698 user: test
673 699 date: Thu Jan 01 00:00:00 1970 +0000
674 700 summary: commit on test
675 701
676 702 changeset: 1:d32277701ccb
677 703 branch: test
678 704 user: test
679 705 date: Thu Jan 01 00:00:00 1970 +0000
680 706 summary: commit on test
681 707
682 708
683 709
684 710 log -b dummy
685 711
686 712 $ hg log -b dummy
687 713 abort: unknown revision 'dummy'!
688 714 [255]
689 715
690 716
691 717 log -b .
692 718
693 719 $ hg log -b .
694 720 changeset: 3:f5d8de11c2e2
695 721 branch: test
696 722 tag: tip
697 723 parent: 1:d32277701ccb
698 724 user: test
699 725 date: Thu Jan 01 00:00:00 1970 +0000
700 726 summary: commit on test
701 727
702 728 changeset: 1:d32277701ccb
703 729 branch: test
704 730 user: test
705 731 date: Thu Jan 01 00:00:00 1970 +0000
706 732 summary: commit on test
707 733
708 734
709 735
710 736 log -b default -b test
711 737
712 738 $ hg log -b default -b test
713 739 changeset: 3:f5d8de11c2e2
714 740 branch: test
715 741 tag: tip
716 742 parent: 1:d32277701ccb
717 743 user: test
718 744 date: Thu Jan 01 00:00:00 1970 +0000
719 745 summary: commit on test
720 746
721 747 changeset: 2:c3a4f03cc9a7
722 748 parent: 0:24427303d56f
723 749 user: test
724 750 date: Thu Jan 01 00:00:00 1970 +0000
725 751 summary: commit on default
726 752
727 753 changeset: 1:d32277701ccb
728 754 branch: test
729 755 user: test
730 756 date: Thu Jan 01 00:00:00 1970 +0000
731 757 summary: commit on test
732 758
733 759 changeset: 0:24427303d56f
734 760 user: test
735 761 date: Thu Jan 01 00:00:00 1970 +0000
736 762 summary: commit on default
737 763
738 764
739 765
740 766 log -b default -b .
741 767
742 768 $ hg log -b default -b .
743 769 changeset: 3:f5d8de11c2e2
744 770 branch: test
745 771 tag: tip
746 772 parent: 1:d32277701ccb
747 773 user: test
748 774 date: Thu Jan 01 00:00:00 1970 +0000
749 775 summary: commit on test
750 776
751 777 changeset: 2:c3a4f03cc9a7
752 778 parent: 0:24427303d56f
753 779 user: test
754 780 date: Thu Jan 01 00:00:00 1970 +0000
755 781 summary: commit on default
756 782
757 783 changeset: 1:d32277701ccb
758 784 branch: test
759 785 user: test
760 786 date: Thu Jan 01 00:00:00 1970 +0000
761 787 summary: commit on test
762 788
763 789 changeset: 0:24427303d56f
764 790 user: test
765 791 date: Thu Jan 01 00:00:00 1970 +0000
766 792 summary: commit on default
767 793
768 794
769 795
770 796 log -b . -b test
771 797
772 798 $ hg log -b . -b test
773 799 changeset: 3:f5d8de11c2e2
774 800 branch: test
775 801 tag: tip
776 802 parent: 1:d32277701ccb
777 803 user: test
778 804 date: Thu Jan 01 00:00:00 1970 +0000
779 805 summary: commit on test
780 806
781 807 changeset: 1:d32277701ccb
782 808 branch: test
783 809 user: test
784 810 date: Thu Jan 01 00:00:00 1970 +0000
785 811 summary: commit on test
786 812
787 813
788 814
789 815 log -b 2
790 816
791 817 $ hg log -b 2
792 818 changeset: 2:c3a4f03cc9a7
793 819 parent: 0:24427303d56f
794 820 user: test
795 821 date: Thu Jan 01 00:00:00 1970 +0000
796 822 summary: commit on default
797 823
798 824 changeset: 0:24427303d56f
799 825 user: test
800 826 date: Thu Jan 01 00:00:00 1970 +0000
801 827 summary: commit on default
802 828
803 829
804 830
805 831 log -p --cwd dir (in subdir)
806 832
807 833 $ mkdir dir
808 834 $ hg log -p --cwd dir
809 835 changeset: 3:f5d8de11c2e2
810 836 branch: test
811 837 tag: tip
812 838 parent: 1:d32277701ccb
813 839 user: test
814 840 date: Thu Jan 01 00:00:00 1970 +0000
815 841 summary: commit on test
816 842
817 843 diff -r d32277701ccb -r f5d8de11c2e2 c
818 844 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
819 845 +++ b/c Thu Jan 01 00:00:00 1970 +0000
820 846 @@ -0,0 +1,1 @@
821 847 +c
822 848
823 849 changeset: 2:c3a4f03cc9a7
824 850 parent: 0:24427303d56f
825 851 user: test
826 852 date: Thu Jan 01 00:00:00 1970 +0000
827 853 summary: commit on default
828 854
829 855 diff -r 24427303d56f -r c3a4f03cc9a7 c
830 856 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
831 857 +++ b/c Thu Jan 01 00:00:00 1970 +0000
832 858 @@ -0,0 +1,1 @@
833 859 +c
834 860
835 861 changeset: 1:d32277701ccb
836 862 branch: test
837 863 user: test
838 864 date: Thu Jan 01 00:00:00 1970 +0000
839 865 summary: commit on test
840 866
841 867 diff -r 24427303d56f -r d32277701ccb b
842 868 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
843 869 +++ b/b Thu Jan 01 00:00:00 1970 +0000
844 870 @@ -0,0 +1,1 @@
845 871 +b
846 872
847 873 changeset: 0:24427303d56f
848 874 user: test
849 875 date: Thu Jan 01 00:00:00 1970 +0000
850 876 summary: commit on default
851 877
852 878 diff -r 000000000000 -r 24427303d56f a
853 879 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
854 880 +++ b/a Thu Jan 01 00:00:00 1970 +0000
855 881 @@ -0,0 +1,1 @@
856 882 +a
857 883
858 884
859 885
860 886 log -p -R repo
861 887
862 888 $ cd dir
863 889 $ hg log -p -R .. ../a
864 890 changeset: 0:24427303d56f
865 891 user: test
866 892 date: Thu Jan 01 00:00:00 1970 +0000
867 893 summary: commit on default
868 894
869 895 diff -r 000000000000 -r 24427303d56f a
870 896 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
871 897 +++ b/a Thu Jan 01 00:00:00 1970 +0000
872 898 @@ -0,0 +1,1 @@
873 899 +a
874 900
875 901
876 902 $ cd ../..
877 903
878 904 $ hg init follow2
879 905 $ cd follow2
880 906
881 907 # Build the following history:
882 908 # tip - o - x - o - x - x
883 909 # \ /
884 910 # o - o - o - x
885 911 # \ /
886 912 # o
887 913 #
888 914 # Where "o" is a revision containing "foo" and
889 915 # "x" is a revision without "foo"
890 916
891 917 $ touch init
892 918 $ hg ci -A -m "init, unrelated"
893 919 adding init
894 920 $ echo 'foo' > init
895 921 $ hg ci -m "change, unrelated"
896 922 $ echo 'foo' > foo
897 923 $ hg ci -A -m "add unrelated old foo"
898 924 adding foo
899 925 $ hg rm foo
900 926 $ hg ci -m "delete foo, unrelated"
901 927 $ echo 'related' > foo
902 928 $ hg ci -A -m "add foo, related"
903 929 adding foo
904 930
905 931 $ hg up 0
906 932 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
907 933 $ touch branch
908 934 $ hg ci -A -m "first branch, unrelated"
909 935 adding branch
910 936 created new head
911 937 $ touch foo
912 938 $ hg ci -A -m "create foo, related"
913 939 adding foo
914 940 $ echo 'change' > foo
915 941 $ hg ci -m "change foo, related"
916 942
917 943 $ hg up 6
918 944 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
919 945 $ echo 'change foo in branch' > foo
920 946 $ hg ci -m "change foo in branch, related"
921 947 created new head
922 948 $ hg merge 7
923 949 merging foo
924 950 warning: conflicts during merge.
925 951 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
926 952 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
927 953 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
928 954 [1]
929 955 $ echo 'merge 1' > foo
930 956 $ hg resolve -m foo
931 957 $ hg ci -m "First merge, related"
932 958
933 959 $ hg merge 4
934 960 merging foo
935 961 warning: conflicts during merge.
936 962 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
937 963 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
938 964 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
939 965 [1]
940 966 $ echo 'merge 2' > foo
941 967 $ hg resolve -m foo
942 968 $ hg ci -m "Last merge, related"
943 969
944 970 $ hg log --graph
945 971 @ changeset: 10:4dae8563d2c5
946 972 |\ tag: tip
947 973 | | parent: 9:7b35701b003e
948 974 | | parent: 4:88176d361b69
949 975 | | user: test
950 976 | | date: Thu Jan 01 00:00:00 1970 +0000
951 977 | | summary: Last merge, related
952 978 | |
953 979 | o changeset: 9:7b35701b003e
954 980 | |\ parent: 8:e5416ad8a855
955 981 | | | parent: 7:87fe3144dcfa
956 982 | | | user: test
957 983 | | | date: Thu Jan 01 00:00:00 1970 +0000
958 984 | | | summary: First merge, related
959 985 | | |
960 986 | | o changeset: 8:e5416ad8a855
961 987 | | | parent: 6:dc6c325fe5ee
962 988 | | | user: test
963 989 | | | date: Thu Jan 01 00:00:00 1970 +0000
964 990 | | | summary: change foo in branch, related
965 991 | | |
966 992 | o | changeset: 7:87fe3144dcfa
967 993 | |/ user: test
968 994 | | date: Thu Jan 01 00:00:00 1970 +0000
969 995 | | summary: change foo, related
970 996 | |
971 997 | o changeset: 6:dc6c325fe5ee
972 998 | | user: test
973 999 | | date: Thu Jan 01 00:00:00 1970 +0000
974 1000 | | summary: create foo, related
975 1001 | |
976 1002 | o changeset: 5:73db34516eb9
977 1003 | | parent: 0:e87515fd044a
978 1004 | | user: test
979 1005 | | date: Thu Jan 01 00:00:00 1970 +0000
980 1006 | | summary: first branch, unrelated
981 1007 | |
982 1008 o | changeset: 4:88176d361b69
983 1009 | | user: test
984 1010 | | date: Thu Jan 01 00:00:00 1970 +0000
985 1011 | | summary: add foo, related
986 1012 | |
987 1013 o | changeset: 3:dd78ae4afb56
988 1014 | | user: test
989 1015 | | date: Thu Jan 01 00:00:00 1970 +0000
990 1016 | | summary: delete foo, unrelated
991 1017 | |
992 1018 o | changeset: 2:c4c64aedf0f7
993 1019 | | user: test
994 1020 | | date: Thu Jan 01 00:00:00 1970 +0000
995 1021 | | summary: add unrelated old foo
996 1022 | |
997 1023 o | changeset: 1:e5faa7440653
998 1024 |/ user: test
999 1025 | date: Thu Jan 01 00:00:00 1970 +0000
1000 1026 | summary: change, unrelated
1001 1027 |
1002 1028 o changeset: 0:e87515fd044a
1003 1029 user: test
1004 1030 date: Thu Jan 01 00:00:00 1970 +0000
1005 1031 summary: init, unrelated
1006 1032
1007 1033
1008 1034 $ hg --traceback log -f foo
1009 1035 changeset: 10:4dae8563d2c5
1010 1036 tag: tip
1011 1037 parent: 9:7b35701b003e
1012 1038 parent: 4:88176d361b69
1013 1039 user: test
1014 1040 date: Thu Jan 01 00:00:00 1970 +0000
1015 1041 summary: Last merge, related
1016 1042
1017 1043 changeset: 9:7b35701b003e
1018 1044 parent: 8:e5416ad8a855
1019 1045 parent: 7:87fe3144dcfa
1020 1046 user: test
1021 1047 date: Thu Jan 01 00:00:00 1970 +0000
1022 1048 summary: First merge, related
1023 1049
1024 1050 changeset: 8:e5416ad8a855
1025 1051 parent: 6:dc6c325fe5ee
1026 1052 user: test
1027 1053 date: Thu Jan 01 00:00:00 1970 +0000
1028 1054 summary: change foo in branch, related
1029 1055
1030 1056 changeset: 7:87fe3144dcfa
1031 1057 user: test
1032 1058 date: Thu Jan 01 00:00:00 1970 +0000
1033 1059 summary: change foo, related
1034 1060
1035 1061 changeset: 6:dc6c325fe5ee
1036 1062 user: test
1037 1063 date: Thu Jan 01 00:00:00 1970 +0000
1038 1064 summary: create foo, related
1039 1065
1040 1066 changeset: 4:88176d361b69
1041 1067 user: test
1042 1068 date: Thu Jan 01 00:00:00 1970 +0000
1043 1069 summary: add foo, related
1044 1070
1045 1071
1046 1072 Also check when maxrev < lastrevfilelog
1047 1073
1048 1074 $ hg --traceback log -f -r4 foo
1049 1075 changeset: 4:88176d361b69
1050 1076 user: test
1051 1077 date: Thu Jan 01 00:00:00 1970 +0000
1052 1078 summary: add foo, related
1053 1079
1054 1080 $ cd ..
1055 1081
1056 1082 Issue2383: hg log showing _less_ differences than hg diff
1057 1083
1058 1084 $ hg init issue2383
1059 1085 $ cd issue2383
1060 1086
1061 1087 Create a test repo:
1062 1088
1063 1089 $ echo a > a
1064 1090 $ hg ci -Am0
1065 1091 adding a
1066 1092 $ echo b > b
1067 1093 $ hg ci -Am1
1068 1094 adding b
1069 1095 $ hg co 0
1070 1096 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1071 1097 $ echo b > a
1072 1098 $ hg ci -m2
1073 1099 created new head
1074 1100
1075 1101 Merge:
1076 1102
1077 1103 $ hg merge
1078 1104 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1079 1105 (branch merge, don't forget to commit)
1080 1106
1081 1107 Make sure there's a file listed in the merge to trigger the bug:
1082 1108
1083 1109 $ echo c > a
1084 1110 $ hg ci -m3
1085 1111
1086 1112 Two files shown here in diff:
1087 1113
1088 1114 $ hg diff --rev 2:3
1089 1115 diff -r b09be438c43a -r 8e07aafe1edc a
1090 1116 --- a/a Thu Jan 01 00:00:00 1970 +0000
1091 1117 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1092 1118 @@ -1,1 +1,1 @@
1093 1119 -b
1094 1120 +c
1095 1121 diff -r b09be438c43a -r 8e07aafe1edc b
1096 1122 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1097 1123 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1098 1124 @@ -0,0 +1,1 @@
1099 1125 +b
1100 1126
1101 1127 Diff here should be the same:
1102 1128
1103 1129 $ hg log -vpr 3
1104 1130 changeset: 3:8e07aafe1edc
1105 1131 tag: tip
1106 1132 parent: 2:b09be438c43a
1107 1133 parent: 1:925d80f479bb
1108 1134 user: test
1109 1135 date: Thu Jan 01 00:00:00 1970 +0000
1110 1136 files: a
1111 1137 description:
1112 1138 3
1113 1139
1114 1140
1115 1141 diff -r b09be438c43a -r 8e07aafe1edc a
1116 1142 --- a/a Thu Jan 01 00:00:00 1970 +0000
1117 1143 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1118 1144 @@ -1,1 +1,1 @@
1119 1145 -b
1120 1146 +c
1121 1147 diff -r b09be438c43a -r 8e07aafe1edc b
1122 1148 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1123 1149 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1124 1150 @@ -0,0 +1,1 @@
1125 1151 +b
1126 1152
1127 1153 $ cd ..
1128 1154
1129 1155 'hg log -r rev fn' when last(filelog(fn)) != rev
1130 1156
1131 1157 $ hg init simplelog
1132 1158 $ cd simplelog
1133 1159 $ echo f > a
1134 1160 $ hg ci -Am'a' -d '0 0'
1135 1161 adding a
1136 1162 $ echo f >> a
1137 1163 $ hg ci -Am'a bis' -d '1 0'
1138 1164
1139 1165 $ hg log -r0 a
1140 1166 changeset: 0:9f758d63dcde
1141 1167 user: test
1142 1168 date: Thu Jan 01 00:00:00 1970 +0000
1143 1169 summary: a
1144 1170
1145 1171 enable obsolete to test hidden feature
1146 1172
1147 1173 $ cat > ${TESTTMP}/obs.py << EOF
1148 1174 > import mercurial.obsolete
1149 1175 > mercurial.obsolete._enabled = True
1150 1176 > EOF
1151 1177 $ echo '[extensions]' >> $HGRCPATH
1152 1178 $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
1153 1179
1154 1180 $ hg log --template='{rev}:{node}\n'
1155 1181 1:a765632148dc55d38c35c4f247c618701886cb2f
1156 1182 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1157 1183 $ hg debugobsolete a765632148dc55d38c35c4f247c618701886cb2f
1158 1184 $ hg up null -q
1159 1185 $ hg log --template='{rev}:{node}\n'
1160 1186 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1161 1187 $ hg log --template='{rev}:{node}\n' --hidden
1162 1188 1:a765632148dc55d38c35c4f247c618701886cb2f
1163 1189 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1164 1190
1165 1191 test that parent prevent a changeset to be hidden
1166 1192
1167 1193 $ hg up 1 -q --hidden
1168 1194 $ hg log --template='{rev}:{node}\n'
1169 1195 1:a765632148dc55d38c35c4f247c618701886cb2f
1170 1196 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1171 1197
1172 1198 test that second parent prevent a changeset to be hidden too
1173 1199
1174 1200 $ hg debugsetparents 0 1 # nothing suitable to merge here
1175 1201 $ hg log --template='{rev}:{node}\n'
1176 1202 1:a765632148dc55d38c35c4f247c618701886cb2f
1177 1203 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1178 1204
1179 1205 clear extensions configuration
1180 1206 $ echo '[extensions]' >> $HGRCPATH
1181 1207 $ echo "obs=!" >> $HGRCPATH
1182 1208 $ cd ..
1183 1209
1184 1210 test -u/-k for problematic encoding
1185 1211 # unicode: cp932:
1186 1212 # u30A2 0x83 0x41(= 'A')
1187 1213 # u30C2 0x83 0x61(= 'a')
1188 1214
1189 1215 $ hg init problematicencoding
1190 1216 $ cd problematicencoding
1191 1217
1192 1218 $ python > setup.sh <<EOF
1193 1219 > print u'''
1194 1220 > echo a > text
1195 1221 > hg add text
1196 1222 > hg --encoding utf-8 commit -u '\u30A2' -m none
1197 1223 > echo b > text
1198 1224 > hg --encoding utf-8 commit -u '\u30C2' -m none
1199 1225 > echo c > text
1200 1226 > hg --encoding utf-8 commit -u none -m '\u30A2'
1201 1227 > echo d > text
1202 1228 > hg --encoding utf-8 commit -u none -m '\u30C2'
1203 1229 > '''.encode('utf-8')
1204 1230 > EOF
1205 1231 $ sh < setup.sh
1206 1232
1207 1233 test in problematic encoding
1208 1234 $ python > test.sh <<EOF
1209 1235 > print u'''
1210 1236 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
1211 1237 > echo ====
1212 1238 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
1213 1239 > echo ====
1214 1240 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
1215 1241 > echo ====
1216 1242 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
1217 1243 > '''.encode('cp932')
1218 1244 > EOF
1219 1245 $ sh < test.sh
1220 1246 0
1221 1247 ====
1222 1248 1
1223 1249 ====
1224 1250 2
1225 1251 0
1226 1252 ====
1227 1253 3
1228 1254 1
1229 1255
1230 1256 $ cd ..
1231 1257
1232 1258 test hg log on non-existent files and on directories
1233 1259 $ hg init issue1340
1234 1260 $ cd issue1340
1235 1261 $ mkdir d1; mkdir D2; mkdir D3.i; mkdir d4.hg; mkdir d5.d; mkdir .d6
1236 1262 $ echo 1 > d1/f1
1237 1263 $ echo 1 > D2/f1
1238 1264 $ echo 1 > D3.i/f1
1239 1265 $ echo 1 > d4.hg/f1
1240 1266 $ echo 1 > d5.d/f1
1241 1267 $ echo 1 > .d6/f1
1242 1268 $ hg -q add .
1243 1269 $ hg commit -m "a bunch of weird directories"
1244 1270 $ hg log -l1 d1/f1 | grep changeset
1245 1271 changeset: 0:65624cd9070a
1246 1272 $ hg log -l1 f1
1247 1273 $ hg log -l1 . | grep changeset
1248 1274 changeset: 0:65624cd9070a
1249 1275 $ hg log -l1 ./ | grep changeset
1250 1276 changeset: 0:65624cd9070a
1251 1277 $ hg log -l1 d1 | grep changeset
1252 1278 changeset: 0:65624cd9070a
1253 1279 $ hg log -l1 D2 | grep changeset
1254 1280 changeset: 0:65624cd9070a
1255 1281 $ hg log -l1 D2/f1 | grep changeset
1256 1282 changeset: 0:65624cd9070a
1257 1283 $ hg log -l1 D3.i | grep changeset
1258 1284 changeset: 0:65624cd9070a
1259 1285 $ hg log -l1 D3.i/f1 | grep changeset
1260 1286 changeset: 0:65624cd9070a
1261 1287 $ hg log -l1 d4.hg | grep changeset
1262 1288 changeset: 0:65624cd9070a
1263 1289 $ hg log -l1 d4.hg/f1 | grep changeset
1264 1290 changeset: 0:65624cd9070a
1265 1291 $ hg log -l1 d5.d | grep changeset
1266 1292 changeset: 0:65624cd9070a
1267 1293 $ hg log -l1 d5.d/f1 | grep changeset
1268 1294 changeset: 0:65624cd9070a
1269 1295 $ hg log -l1 .d6 | grep changeset
1270 1296 changeset: 0:65624cd9070a
1271 1297 $ hg log -l1 .d6/f1 | grep changeset
1272 1298 changeset: 0:65624cd9070a
1273 1299 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now