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