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