##// END OF EJS Templates
templater: add new docheader/footer components for XML (issue4135)...
Matt Mackall -
r26222:3095b102 default
parent child Browse files
Show More
@@ -1,3348 +1,3359 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, bin, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, tempfile, cStringIO, shutil
11 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 12 import match as matchmod
13 13 import repair, graphmod, revset, phases, obsolete, pathutil
14 14 import changelog
15 15 import bookmarks
16 16 import encoding
17 17 import formatter
18 18 import crecord as crecordmod
19 19 import lock as lockmod
20 20
21 21 def ishunk(x):
22 22 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
23 23 return isinstance(x, hunkclasses)
24 24
25 25 def newandmodified(chunks, originalchunks):
26 26 newlyaddedandmodifiedfiles = set()
27 27 for chunk in chunks:
28 28 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
29 29 originalchunks:
30 30 newlyaddedandmodifiedfiles.add(chunk.header.filename())
31 31 return newlyaddedandmodifiedfiles
32 32
33 33 def parsealiases(cmd):
34 34 return cmd.lstrip("^").split("|")
35 35
36 36 def setupwrapcolorwrite(ui):
37 37 # wrap ui.write so diff output can be labeled/colorized
38 38 def wrapwrite(orig, *args, **kw):
39 39 label = kw.pop('label', '')
40 40 for chunk, l in patch.difflabel(lambda: args):
41 41 orig(chunk, label=label + l)
42 42
43 43 oldwrite = ui.write
44 44 def wrap(*args, **kwargs):
45 45 return wrapwrite(oldwrite, *args, **kwargs)
46 46 setattr(ui, 'write', wrap)
47 47 return oldwrite
48 48
49 49 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
50 50 if usecurses:
51 51 if testfile:
52 52 recordfn = crecordmod.testdecorator(testfile,
53 53 crecordmod.testchunkselector)
54 54 else:
55 55 recordfn = crecordmod.chunkselector
56 56
57 57 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
58 58
59 59 else:
60 60 return patch.filterpatch(ui, originalhunks, operation)
61 61
62 62 def recordfilter(ui, originalhunks, operation=None):
63 63 """ Prompts the user to filter the originalhunks and return a list of
64 64 selected hunks.
65 65 *operation* is used for ui purposes to indicate the user
66 66 what kind of filtering they are doing: reverting, commiting, shelving, etc.
67 67 *operation* has to be a translated string.
68 68 """
69 69 usecurses = ui.configbool('experimental', 'crecord', False)
70 70 testfile = ui.config('experimental', 'crecordtest', None)
71 71 oldwrite = setupwrapcolorwrite(ui)
72 72 try:
73 73 newchunks = filterchunks(ui, originalhunks, usecurses, testfile,
74 74 operation)
75 75 finally:
76 76 ui.write = oldwrite
77 77 return newchunks
78 78
79 79 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
80 80 filterfn, *pats, **opts):
81 81 import merge as mergemod
82 82
83 83 if not ui.interactive():
84 84 if cmdsuggest:
85 85 msg = _('running non-interactively, use %s instead') % cmdsuggest
86 86 else:
87 87 msg = _('running non-interactively')
88 88 raise util.Abort(msg)
89 89
90 90 # make sure username is set before going interactive
91 91 if not opts.get('user'):
92 92 ui.username() # raise exception, username not provided
93 93
94 94 def recordfunc(ui, repo, message, match, opts):
95 95 """This is generic record driver.
96 96
97 97 Its job is to interactively filter local changes, and
98 98 accordingly prepare working directory into a state in which the
99 99 job can be delegated to a non-interactive commit command such as
100 100 'commit' or 'qrefresh'.
101 101
102 102 After the actual job is done by non-interactive command, the
103 103 working directory is restored to its original state.
104 104
105 105 In the end we'll record interesting changes, and everything else
106 106 will be left in place, so the user can continue working.
107 107 """
108 108
109 109 checkunfinished(repo, commit=True)
110 110 merge = len(repo[None].parents()) > 1
111 111 if merge:
112 112 raise util.Abort(_('cannot partially commit a merge '
113 113 '(use "hg commit" instead)'))
114 114
115 115 status = repo.status(match=match)
116 116 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
117 117 diffopts.nodates = True
118 118 diffopts.git = True
119 119 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
120 120 originalchunks = patch.parsepatch(originaldiff)
121 121
122 122 # 1. filter patch, so we have intending-to apply subset of it
123 123 try:
124 124 chunks = filterfn(ui, originalchunks)
125 125 except patch.PatchError as err:
126 126 raise util.Abort(_('error parsing patch: %s') % err)
127 127
128 128 # We need to keep a backup of files that have been newly added and
129 129 # modified during the recording process because there is a previous
130 130 # version without the edit in the workdir
131 131 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
132 132 contenders = set()
133 133 for h in chunks:
134 134 try:
135 135 contenders.update(set(h.files()))
136 136 except AttributeError:
137 137 pass
138 138
139 139 changed = status.modified + status.added + status.removed
140 140 newfiles = [f for f in changed if f in contenders]
141 141 if not newfiles:
142 142 ui.status(_('no changes to record\n'))
143 143 return 0
144 144
145 145 modified = set(status.modified)
146 146
147 147 # 2. backup changed files, so we can restore them in the end
148 148
149 149 if backupall:
150 150 tobackup = changed
151 151 else:
152 152 tobackup = [f for f in newfiles if f in modified or f in \
153 153 newlyaddedandmodifiedfiles]
154 154 backups = {}
155 155 if tobackup:
156 156 backupdir = repo.join('record-backups')
157 157 try:
158 158 os.mkdir(backupdir)
159 159 except OSError as err:
160 160 if err.errno != errno.EEXIST:
161 161 raise
162 162 try:
163 163 # backup continues
164 164 for f in tobackup:
165 165 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
166 166 dir=backupdir)
167 167 os.close(fd)
168 168 ui.debug('backup %r as %r\n' % (f, tmpname))
169 169 util.copyfile(repo.wjoin(f), tmpname)
170 170 shutil.copystat(repo.wjoin(f), tmpname)
171 171 backups[f] = tmpname
172 172
173 173 fp = cStringIO.StringIO()
174 174 for c in chunks:
175 175 fname = c.filename()
176 176 if fname in backups:
177 177 c.write(fp)
178 178 dopatch = fp.tell()
179 179 fp.seek(0)
180 180
181 181 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
182 182 # 3a. apply filtered patch to clean repo (clean)
183 183 if backups:
184 184 # Equivalent to hg.revert
185 185 choices = lambda key: key in backups
186 186 mergemod.update(repo, repo.dirstate.p1(),
187 187 False, True, choices)
188 188
189 189 # 3b. (apply)
190 190 if dopatch:
191 191 try:
192 192 ui.debug('applying patch\n')
193 193 ui.debug(fp.getvalue())
194 194 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
195 195 except patch.PatchError as err:
196 196 raise util.Abort(str(err))
197 197 del fp
198 198
199 199 # 4. We prepared working directory according to filtered
200 200 # patch. Now is the time to delegate the job to
201 201 # commit/qrefresh or the like!
202 202
203 203 # Make all of the pathnames absolute.
204 204 newfiles = [repo.wjoin(nf) for nf in newfiles]
205 205 return commitfunc(ui, repo, *newfiles, **opts)
206 206 finally:
207 207 # 5. finally restore backed-up files
208 208 try:
209 209 dirstate = repo.dirstate
210 210 for realname, tmpname in backups.iteritems():
211 211 ui.debug('restoring %r to %r\n' % (tmpname, realname))
212 212
213 213 if dirstate[realname] == 'n':
214 214 # without normallookup, restoring timestamp
215 215 # may cause partially committed files
216 216 # to be treated as unmodified
217 217 dirstate.normallookup(realname)
218 218
219 219 util.copyfile(tmpname, repo.wjoin(realname))
220 220 # Our calls to copystat() here and above are a
221 221 # hack to trick any editors that have f open that
222 222 # we haven't modified them.
223 223 #
224 224 # Also note that this racy as an editor could
225 225 # notice the file's mtime before we've finished
226 226 # writing it.
227 227 shutil.copystat(tmpname, repo.wjoin(realname))
228 228 os.unlink(tmpname)
229 229 if tobackup:
230 230 os.rmdir(backupdir)
231 231 except OSError:
232 232 pass
233 233
234 234 def recordinwlock(ui, repo, message, match, opts):
235 235 wlock = repo.wlock()
236 236 try:
237 237 return recordfunc(ui, repo, message, match, opts)
238 238 finally:
239 239 wlock.release()
240 240
241 241 return commit(ui, repo, recordinwlock, pats, opts)
242 242
243 243 def findpossible(cmd, table, strict=False):
244 244 """
245 245 Return cmd -> (aliases, command table entry)
246 246 for each matching command.
247 247 Return debug commands (or their aliases) only if no normal command matches.
248 248 """
249 249 choice = {}
250 250 debugchoice = {}
251 251
252 252 if cmd in table:
253 253 # short-circuit exact matches, "log" alias beats "^log|history"
254 254 keys = [cmd]
255 255 else:
256 256 keys = table.keys()
257 257
258 258 allcmds = []
259 259 for e in keys:
260 260 aliases = parsealiases(e)
261 261 allcmds.extend(aliases)
262 262 found = None
263 263 if cmd in aliases:
264 264 found = cmd
265 265 elif not strict:
266 266 for a in aliases:
267 267 if a.startswith(cmd):
268 268 found = a
269 269 break
270 270 if found is not None:
271 271 if aliases[0].startswith("debug") or found.startswith("debug"):
272 272 debugchoice[found] = (aliases, table[e])
273 273 else:
274 274 choice[found] = (aliases, table[e])
275 275
276 276 if not choice and debugchoice:
277 277 choice = debugchoice
278 278
279 279 return choice, allcmds
280 280
281 281 def findcmd(cmd, table, strict=True):
282 282 """Return (aliases, command table entry) for command string."""
283 283 choice, allcmds = findpossible(cmd, table, strict)
284 284
285 285 if cmd in choice:
286 286 return choice[cmd]
287 287
288 288 if len(choice) > 1:
289 289 clist = choice.keys()
290 290 clist.sort()
291 291 raise error.AmbiguousCommand(cmd, clist)
292 292
293 293 if choice:
294 294 return choice.values()[0]
295 295
296 296 raise error.UnknownCommand(cmd, allcmds)
297 297
298 298 def findrepo(p):
299 299 while not os.path.isdir(os.path.join(p, ".hg")):
300 300 oldp, p = p, os.path.dirname(p)
301 301 if p == oldp:
302 302 return None
303 303
304 304 return p
305 305
306 306 def bailifchanged(repo, merge=True):
307 307 if merge and repo.dirstate.p2() != nullid:
308 308 raise util.Abort(_('outstanding uncommitted merge'))
309 309 modified, added, removed, deleted = repo.status()[:4]
310 310 if modified or added or removed or deleted:
311 311 raise util.Abort(_('uncommitted changes'))
312 312 ctx = repo[None]
313 313 for s in sorted(ctx.substate):
314 314 ctx.sub(s).bailifchanged()
315 315
316 316 def logmessage(ui, opts):
317 317 """ get the log message according to -m and -l option """
318 318 message = opts.get('message')
319 319 logfile = opts.get('logfile')
320 320
321 321 if message and logfile:
322 322 raise util.Abort(_('options --message and --logfile are mutually '
323 323 'exclusive'))
324 324 if not message and logfile:
325 325 try:
326 326 if logfile == '-':
327 327 message = ui.fin.read()
328 328 else:
329 329 message = '\n'.join(util.readfile(logfile).splitlines())
330 330 except IOError as inst:
331 331 raise util.Abort(_("can't read commit message '%s': %s") %
332 332 (logfile, inst.strerror))
333 333 return message
334 334
335 335 def mergeeditform(ctxorbool, baseformname):
336 336 """return appropriate editform name (referencing a committemplate)
337 337
338 338 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
339 339 merging is committed.
340 340
341 341 This returns baseformname with '.merge' appended if it is a merge,
342 342 otherwise '.normal' is appended.
343 343 """
344 344 if isinstance(ctxorbool, bool):
345 345 if ctxorbool:
346 346 return baseformname + ".merge"
347 347 elif 1 < len(ctxorbool.parents()):
348 348 return baseformname + ".merge"
349 349
350 350 return baseformname + ".normal"
351 351
352 352 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
353 353 editform='', **opts):
354 354 """get appropriate commit message editor according to '--edit' option
355 355
356 356 'finishdesc' is a function to be called with edited commit message
357 357 (= 'description' of the new changeset) just after editing, but
358 358 before checking empty-ness. It should return actual text to be
359 359 stored into history. This allows to change description before
360 360 storing.
361 361
362 362 'extramsg' is a extra message to be shown in the editor instead of
363 363 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
364 364 is automatically added.
365 365
366 366 'editform' is a dot-separated list of names, to distinguish
367 367 the purpose of commit text editing.
368 368
369 369 'getcommiteditor' returns 'commitforceeditor' regardless of
370 370 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
371 371 they are specific for usage in MQ.
372 372 """
373 373 if edit or finishdesc or extramsg:
374 374 return lambda r, c, s: commitforceeditor(r, c, s,
375 375 finishdesc=finishdesc,
376 376 extramsg=extramsg,
377 377 editform=editform)
378 378 elif editform:
379 379 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
380 380 else:
381 381 return commiteditor
382 382
383 383 def loglimit(opts):
384 384 """get the log limit according to option -l/--limit"""
385 385 limit = opts.get('limit')
386 386 if limit:
387 387 try:
388 388 limit = int(limit)
389 389 except ValueError:
390 390 raise util.Abort(_('limit must be a positive integer'))
391 391 if limit <= 0:
392 392 raise util.Abort(_('limit must be positive'))
393 393 else:
394 394 limit = None
395 395 return limit
396 396
397 397 def makefilename(repo, pat, node, desc=None,
398 398 total=None, seqno=None, revwidth=None, pathname=None):
399 399 node_expander = {
400 400 'H': lambda: hex(node),
401 401 'R': lambda: str(repo.changelog.rev(node)),
402 402 'h': lambda: short(node),
403 403 'm': lambda: re.sub('[^\w]', '_', str(desc))
404 404 }
405 405 expander = {
406 406 '%': lambda: '%',
407 407 'b': lambda: os.path.basename(repo.root),
408 408 }
409 409
410 410 try:
411 411 if node:
412 412 expander.update(node_expander)
413 413 if node:
414 414 expander['r'] = (lambda:
415 415 str(repo.changelog.rev(node)).zfill(revwidth or 0))
416 416 if total is not None:
417 417 expander['N'] = lambda: str(total)
418 418 if seqno is not None:
419 419 expander['n'] = lambda: str(seqno)
420 420 if total is not None and seqno is not None:
421 421 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
422 422 if pathname is not None:
423 423 expander['s'] = lambda: os.path.basename(pathname)
424 424 expander['d'] = lambda: os.path.dirname(pathname) or '.'
425 425 expander['p'] = lambda: pathname
426 426
427 427 newname = []
428 428 patlen = len(pat)
429 429 i = 0
430 430 while i < patlen:
431 431 c = pat[i]
432 432 if c == '%':
433 433 i += 1
434 434 c = pat[i]
435 435 c = expander[c]()
436 436 newname.append(c)
437 437 i += 1
438 438 return ''.join(newname)
439 439 except KeyError as inst:
440 440 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
441 441 inst.args[0])
442 442
443 443 def makefileobj(repo, pat, node=None, desc=None, total=None,
444 444 seqno=None, revwidth=None, mode='wb', modemap=None,
445 445 pathname=None):
446 446
447 447 writable = mode not in ('r', 'rb')
448 448
449 449 if not pat or pat == '-':
450 450 if writable:
451 451 fp = repo.ui.fout
452 452 else:
453 453 fp = repo.ui.fin
454 454 if util.safehasattr(fp, 'fileno'):
455 455 return os.fdopen(os.dup(fp.fileno()), mode)
456 456 else:
457 457 # if this fp can't be duped properly, return
458 458 # a dummy object that can be closed
459 459 class wrappedfileobj(object):
460 460 noop = lambda x: None
461 461 def __init__(self, f):
462 462 self.f = f
463 463 def __getattr__(self, attr):
464 464 if attr == 'close':
465 465 return self.noop
466 466 else:
467 467 return getattr(self.f, attr)
468 468
469 469 return wrappedfileobj(fp)
470 470 if util.safehasattr(pat, 'write') and writable:
471 471 return pat
472 472 if util.safehasattr(pat, 'read') and 'r' in mode:
473 473 return pat
474 474 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
475 475 if modemap is not None:
476 476 mode = modemap.get(fn, mode)
477 477 if mode == 'wb':
478 478 modemap[fn] = 'ab'
479 479 return open(fn, mode)
480 480
481 481 def openrevlog(repo, cmd, file_, opts):
482 482 """opens the changelog, manifest, a filelog or a given revlog"""
483 483 cl = opts['changelog']
484 484 mf = opts['manifest']
485 485 dir = opts['dir']
486 486 msg = None
487 487 if cl and mf:
488 488 msg = _('cannot specify --changelog and --manifest at the same time')
489 489 elif cl and dir:
490 490 msg = _('cannot specify --changelog and --dir at the same time')
491 491 elif cl or mf:
492 492 if file_:
493 493 msg = _('cannot specify filename with --changelog or --manifest')
494 494 elif not repo:
495 495 msg = _('cannot specify --changelog or --manifest or --dir '
496 496 'without a repository')
497 497 if msg:
498 498 raise util.Abort(msg)
499 499
500 500 r = None
501 501 if repo:
502 502 if cl:
503 503 r = repo.unfiltered().changelog
504 504 elif dir:
505 505 if 'treemanifest' not in repo.requirements:
506 506 raise util.Abort(_("--dir can only be used on repos with "
507 507 "treemanifest enabled"))
508 508 dirlog = repo.dirlog(file_)
509 509 if len(dirlog):
510 510 r = dirlog
511 511 elif mf:
512 512 r = repo.manifest
513 513 elif file_:
514 514 filelog = repo.file(file_)
515 515 if len(filelog):
516 516 r = filelog
517 517 if not r:
518 518 if not file_:
519 519 raise error.CommandError(cmd, _('invalid arguments'))
520 520 if not os.path.isfile(file_):
521 521 raise util.Abort(_("revlog '%s' not found") % file_)
522 522 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
523 523 file_[:-2] + ".i")
524 524 return r
525 525
526 526 def copy(ui, repo, pats, opts, rename=False):
527 527 # called with the repo lock held
528 528 #
529 529 # hgsep => pathname that uses "/" to separate directories
530 530 # ossep => pathname that uses os.sep to separate directories
531 531 cwd = repo.getcwd()
532 532 targets = {}
533 533 after = opts.get("after")
534 534 dryrun = opts.get("dry_run")
535 535 wctx = repo[None]
536 536
537 537 def walkpat(pat):
538 538 srcs = []
539 539 if after:
540 540 badstates = '?'
541 541 else:
542 542 badstates = '?r'
543 543 m = scmutil.match(repo[None], [pat], opts, globbed=True)
544 544 for abs in repo.walk(m):
545 545 state = repo.dirstate[abs]
546 546 rel = m.rel(abs)
547 547 exact = m.exact(abs)
548 548 if state in badstates:
549 549 if exact and state == '?':
550 550 ui.warn(_('%s: not copying - file is not managed\n') % rel)
551 551 if exact and state == 'r':
552 552 ui.warn(_('%s: not copying - file has been marked for'
553 553 ' remove\n') % rel)
554 554 continue
555 555 # abs: hgsep
556 556 # rel: ossep
557 557 srcs.append((abs, rel, exact))
558 558 return srcs
559 559
560 560 # abssrc: hgsep
561 561 # relsrc: ossep
562 562 # otarget: ossep
563 563 def copyfile(abssrc, relsrc, otarget, exact):
564 564 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
565 565 if '/' in abstarget:
566 566 # We cannot normalize abstarget itself, this would prevent
567 567 # case only renames, like a => A.
568 568 abspath, absname = abstarget.rsplit('/', 1)
569 569 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
570 570 reltarget = repo.pathto(abstarget, cwd)
571 571 target = repo.wjoin(abstarget)
572 572 src = repo.wjoin(abssrc)
573 573 state = repo.dirstate[abstarget]
574 574
575 575 scmutil.checkportable(ui, abstarget)
576 576
577 577 # check for collisions
578 578 prevsrc = targets.get(abstarget)
579 579 if prevsrc is not None:
580 580 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
581 581 (reltarget, repo.pathto(abssrc, cwd),
582 582 repo.pathto(prevsrc, cwd)))
583 583 return
584 584
585 585 # check for overwrites
586 586 exists = os.path.lexists(target)
587 587 samefile = False
588 588 if exists and abssrc != abstarget:
589 589 if (repo.dirstate.normalize(abssrc) ==
590 590 repo.dirstate.normalize(abstarget)):
591 591 if not rename:
592 592 ui.warn(_("%s: can't copy - same file\n") % reltarget)
593 593 return
594 594 exists = False
595 595 samefile = True
596 596
597 597 if not after and exists or after and state in 'mn':
598 598 if not opts['force']:
599 599 ui.warn(_('%s: not overwriting - file exists\n') %
600 600 reltarget)
601 601 return
602 602
603 603 if after:
604 604 if not exists:
605 605 if rename:
606 606 ui.warn(_('%s: not recording move - %s does not exist\n') %
607 607 (relsrc, reltarget))
608 608 else:
609 609 ui.warn(_('%s: not recording copy - %s does not exist\n') %
610 610 (relsrc, reltarget))
611 611 return
612 612 elif not dryrun:
613 613 try:
614 614 if exists:
615 615 os.unlink(target)
616 616 targetdir = os.path.dirname(target) or '.'
617 617 if not os.path.isdir(targetdir):
618 618 os.makedirs(targetdir)
619 619 if samefile:
620 620 tmp = target + "~hgrename"
621 621 os.rename(src, tmp)
622 622 os.rename(tmp, target)
623 623 else:
624 624 util.copyfile(src, target)
625 625 srcexists = True
626 626 except IOError as inst:
627 627 if inst.errno == errno.ENOENT:
628 628 ui.warn(_('%s: deleted in working directory\n') % relsrc)
629 629 srcexists = False
630 630 else:
631 631 ui.warn(_('%s: cannot copy - %s\n') %
632 632 (relsrc, inst.strerror))
633 633 return True # report a failure
634 634
635 635 if ui.verbose or not exact:
636 636 if rename:
637 637 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
638 638 else:
639 639 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
640 640
641 641 targets[abstarget] = abssrc
642 642
643 643 # fix up dirstate
644 644 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
645 645 dryrun=dryrun, cwd=cwd)
646 646 if rename and not dryrun:
647 647 if not after and srcexists and not samefile:
648 648 util.unlinkpath(repo.wjoin(abssrc))
649 649 wctx.forget([abssrc])
650 650
651 651 # pat: ossep
652 652 # dest ossep
653 653 # srcs: list of (hgsep, hgsep, ossep, bool)
654 654 # return: function that takes hgsep and returns ossep
655 655 def targetpathfn(pat, dest, srcs):
656 656 if os.path.isdir(pat):
657 657 abspfx = pathutil.canonpath(repo.root, cwd, pat)
658 658 abspfx = util.localpath(abspfx)
659 659 if destdirexists:
660 660 striplen = len(os.path.split(abspfx)[0])
661 661 else:
662 662 striplen = len(abspfx)
663 663 if striplen:
664 664 striplen += len(os.sep)
665 665 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
666 666 elif destdirexists:
667 667 res = lambda p: os.path.join(dest,
668 668 os.path.basename(util.localpath(p)))
669 669 else:
670 670 res = lambda p: dest
671 671 return res
672 672
673 673 # pat: ossep
674 674 # dest ossep
675 675 # srcs: list of (hgsep, hgsep, ossep, bool)
676 676 # return: function that takes hgsep and returns ossep
677 677 def targetpathafterfn(pat, dest, srcs):
678 678 if matchmod.patkind(pat):
679 679 # a mercurial pattern
680 680 res = lambda p: os.path.join(dest,
681 681 os.path.basename(util.localpath(p)))
682 682 else:
683 683 abspfx = pathutil.canonpath(repo.root, cwd, pat)
684 684 if len(abspfx) < len(srcs[0][0]):
685 685 # A directory. Either the target path contains the last
686 686 # component of the source path or it does not.
687 687 def evalpath(striplen):
688 688 score = 0
689 689 for s in srcs:
690 690 t = os.path.join(dest, util.localpath(s[0])[striplen:])
691 691 if os.path.lexists(t):
692 692 score += 1
693 693 return score
694 694
695 695 abspfx = util.localpath(abspfx)
696 696 striplen = len(abspfx)
697 697 if striplen:
698 698 striplen += len(os.sep)
699 699 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
700 700 score = evalpath(striplen)
701 701 striplen1 = len(os.path.split(abspfx)[0])
702 702 if striplen1:
703 703 striplen1 += len(os.sep)
704 704 if evalpath(striplen1) > score:
705 705 striplen = striplen1
706 706 res = lambda p: os.path.join(dest,
707 707 util.localpath(p)[striplen:])
708 708 else:
709 709 # a file
710 710 if destdirexists:
711 711 res = lambda p: os.path.join(dest,
712 712 os.path.basename(util.localpath(p)))
713 713 else:
714 714 res = lambda p: dest
715 715 return res
716 716
717 717 pats = scmutil.expandpats(pats)
718 718 if not pats:
719 719 raise util.Abort(_('no source or destination specified'))
720 720 if len(pats) == 1:
721 721 raise util.Abort(_('no destination specified'))
722 722 dest = pats.pop()
723 723 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
724 724 if not destdirexists:
725 725 if len(pats) > 1 or matchmod.patkind(pats[0]):
726 726 raise util.Abort(_('with multiple sources, destination must be an '
727 727 'existing directory'))
728 728 if util.endswithsep(dest):
729 729 raise util.Abort(_('destination %s is not a directory') % dest)
730 730
731 731 tfn = targetpathfn
732 732 if after:
733 733 tfn = targetpathafterfn
734 734 copylist = []
735 735 for pat in pats:
736 736 srcs = walkpat(pat)
737 737 if not srcs:
738 738 continue
739 739 copylist.append((tfn(pat, dest, srcs), srcs))
740 740 if not copylist:
741 741 raise util.Abort(_('no files to copy'))
742 742
743 743 errors = 0
744 744 for targetpath, srcs in copylist:
745 745 for abssrc, relsrc, exact in srcs:
746 746 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
747 747 errors += 1
748 748
749 749 if errors:
750 750 ui.warn(_('(consider using --after)\n'))
751 751
752 752 return errors != 0
753 753
754 754 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
755 755 runargs=None, appendpid=False):
756 756 '''Run a command as a service.'''
757 757
758 758 def writepid(pid):
759 759 if opts['pid_file']:
760 760 if appendpid:
761 761 mode = 'a'
762 762 else:
763 763 mode = 'w'
764 764 fp = open(opts['pid_file'], mode)
765 765 fp.write(str(pid) + '\n')
766 766 fp.close()
767 767
768 768 if opts['daemon'] and not opts['daemon_pipefds']:
769 769 # Signal child process startup with file removal
770 770 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
771 771 os.close(lockfd)
772 772 try:
773 773 if not runargs:
774 774 runargs = util.hgcmd() + sys.argv[1:]
775 775 runargs.append('--daemon-pipefds=%s' % lockpath)
776 776 # Don't pass --cwd to the child process, because we've already
777 777 # changed directory.
778 778 for i in xrange(1, len(runargs)):
779 779 if runargs[i].startswith('--cwd='):
780 780 del runargs[i]
781 781 break
782 782 elif runargs[i].startswith('--cwd'):
783 783 del runargs[i:i + 2]
784 784 break
785 785 def condfn():
786 786 return not os.path.exists(lockpath)
787 787 pid = util.rundetached(runargs, condfn)
788 788 if pid < 0:
789 789 raise util.Abort(_('child process failed to start'))
790 790 writepid(pid)
791 791 finally:
792 792 try:
793 793 os.unlink(lockpath)
794 794 except OSError as e:
795 795 if e.errno != errno.ENOENT:
796 796 raise
797 797 if parentfn:
798 798 return parentfn(pid)
799 799 else:
800 800 return
801 801
802 802 if initfn:
803 803 initfn()
804 804
805 805 if not opts['daemon']:
806 806 writepid(os.getpid())
807 807
808 808 if opts['daemon_pipefds']:
809 809 lockpath = opts['daemon_pipefds']
810 810 try:
811 811 os.setsid()
812 812 except AttributeError:
813 813 pass
814 814 os.unlink(lockpath)
815 815 util.hidewindow()
816 816 sys.stdout.flush()
817 817 sys.stderr.flush()
818 818
819 819 nullfd = os.open(os.devnull, os.O_RDWR)
820 820 logfilefd = nullfd
821 821 if logfile:
822 822 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
823 823 os.dup2(nullfd, 0)
824 824 os.dup2(logfilefd, 1)
825 825 os.dup2(logfilefd, 2)
826 826 if nullfd not in (0, 1, 2):
827 827 os.close(nullfd)
828 828 if logfile and logfilefd not in (0, 1, 2):
829 829 os.close(logfilefd)
830 830
831 831 if runfn:
832 832 return runfn()
833 833
834 834 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
835 835 """Utility function used by commands.import to import a single patch
836 836
837 837 This function is explicitly defined here to help the evolve extension to
838 838 wrap this part of the import logic.
839 839
840 840 The API is currently a bit ugly because it a simple code translation from
841 841 the import command. Feel free to make it better.
842 842
843 843 :hunk: a patch (as a binary string)
844 844 :parents: nodes that will be parent of the created commit
845 845 :opts: the full dict of option passed to the import command
846 846 :msgs: list to save commit message to.
847 847 (used in case we need to save it when failing)
848 848 :updatefunc: a function that update a repo to a given node
849 849 updatefunc(<repo>, <node>)
850 850 """
851 851 # avoid cycle context -> subrepo -> cmdutil
852 852 import context
853 853 tmpname, message, user, date, branch, nodeid, p1, p2 = \
854 854 patch.extract(ui, hunk)
855 855
856 856 update = not opts.get('bypass')
857 857 strip = opts["strip"]
858 858 prefix = opts["prefix"]
859 859 sim = float(opts.get('similarity') or 0)
860 860 if not tmpname:
861 861 return (None, None, False)
862 862 msg = _('applied to working directory')
863 863
864 864 rejects = False
865 865 dsguard = None
866 866
867 867 try:
868 868 cmdline_message = logmessage(ui, opts)
869 869 if cmdline_message:
870 870 # pickup the cmdline msg
871 871 message = cmdline_message
872 872 elif message:
873 873 # pickup the patch msg
874 874 message = message.strip()
875 875 else:
876 876 # launch the editor
877 877 message = None
878 878 ui.debug('message:\n%s\n' % message)
879 879
880 880 if len(parents) == 1:
881 881 parents.append(repo[nullid])
882 882 if opts.get('exact'):
883 883 if not nodeid or not p1:
884 884 raise util.Abort(_('not a Mercurial patch'))
885 885 p1 = repo[p1]
886 886 p2 = repo[p2 or nullid]
887 887 elif p2:
888 888 try:
889 889 p1 = repo[p1]
890 890 p2 = repo[p2]
891 891 # Without any options, consider p2 only if the
892 892 # patch is being applied on top of the recorded
893 893 # first parent.
894 894 if p1 != parents[0]:
895 895 p1 = parents[0]
896 896 p2 = repo[nullid]
897 897 except error.RepoError:
898 898 p1, p2 = parents
899 899 if p2.node() == nullid:
900 900 ui.warn(_("warning: import the patch as a normal revision\n"
901 901 "(use --exact to import the patch as a merge)\n"))
902 902 else:
903 903 p1, p2 = parents
904 904
905 905 n = None
906 906 if update:
907 907 dsguard = dirstateguard(repo, 'tryimportone')
908 908 if p1 != parents[0]:
909 909 updatefunc(repo, p1.node())
910 910 if p2 != parents[1]:
911 911 repo.setparents(p1.node(), p2.node())
912 912
913 913 if opts.get('exact') or opts.get('import_branch'):
914 914 repo.dirstate.setbranch(branch or 'default')
915 915
916 916 partial = opts.get('partial', False)
917 917 files = set()
918 918 try:
919 919 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
920 920 files=files, eolmode=None, similarity=sim / 100.0)
921 921 except patch.PatchError as e:
922 922 if not partial:
923 923 raise util.Abort(str(e))
924 924 if partial:
925 925 rejects = True
926 926
927 927 files = list(files)
928 928 if opts.get('no_commit'):
929 929 if message:
930 930 msgs.append(message)
931 931 else:
932 932 if opts.get('exact') or p2:
933 933 # If you got here, you either use --force and know what
934 934 # you are doing or used --exact or a merge patch while
935 935 # being updated to its first parent.
936 936 m = None
937 937 else:
938 938 m = scmutil.matchfiles(repo, files or [])
939 939 editform = mergeeditform(repo[None], 'import.normal')
940 940 if opts.get('exact'):
941 941 editor = None
942 942 else:
943 943 editor = getcommiteditor(editform=editform, **opts)
944 944 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
945 945 try:
946 946 if partial:
947 947 repo.ui.setconfig('ui', 'allowemptycommit', True)
948 948 n = repo.commit(message, opts.get('user') or user,
949 949 opts.get('date') or date, match=m,
950 950 editor=editor)
951 951 finally:
952 952 repo.ui.restoreconfig(allowemptyback)
953 953 dsguard.close()
954 954 else:
955 955 if opts.get('exact') or opts.get('import_branch'):
956 956 branch = branch or 'default'
957 957 else:
958 958 branch = p1.branch()
959 959 store = patch.filestore()
960 960 try:
961 961 files = set()
962 962 try:
963 963 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
964 964 files, eolmode=None)
965 965 except patch.PatchError as e:
966 966 raise util.Abort(str(e))
967 967 if opts.get('exact'):
968 968 editor = None
969 969 else:
970 970 editor = getcommiteditor(editform='import.bypass')
971 971 memctx = context.makememctx(repo, (p1.node(), p2.node()),
972 972 message,
973 973 opts.get('user') or user,
974 974 opts.get('date') or date,
975 975 branch, files, store,
976 976 editor=editor)
977 977 n = memctx.commit()
978 978 finally:
979 979 store.close()
980 980 if opts.get('exact') and opts.get('no_commit'):
981 981 # --exact with --no-commit is still useful in that it does merge
982 982 # and branch bits
983 983 ui.warn(_("warning: can't check exact import with --no-commit\n"))
984 984 elif opts.get('exact') and hex(n) != nodeid:
985 985 raise util.Abort(_('patch is damaged or loses information'))
986 986 if n:
987 987 # i18n: refers to a short changeset id
988 988 msg = _('created %s') % short(n)
989 989 return (msg, n, rejects)
990 990 finally:
991 991 lockmod.release(dsguard)
992 992 os.unlink(tmpname)
993 993
994 994 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
995 995 opts=None):
996 996 '''export changesets as hg patches.'''
997 997
998 998 total = len(revs)
999 999 revwidth = max([len(str(rev)) for rev in revs])
1000 1000 filemode = {}
1001 1001
1002 1002 def single(rev, seqno, fp):
1003 1003 ctx = repo[rev]
1004 1004 node = ctx.node()
1005 1005 parents = [p.node() for p in ctx.parents() if p]
1006 1006 branch = ctx.branch()
1007 1007 if switch_parent:
1008 1008 parents.reverse()
1009 1009
1010 1010 if parents:
1011 1011 prev = parents[0]
1012 1012 else:
1013 1013 prev = nullid
1014 1014
1015 1015 shouldclose = False
1016 1016 if not fp and len(template) > 0:
1017 1017 desc_lines = ctx.description().rstrip().split('\n')
1018 1018 desc = desc_lines[0] #Commit always has a first line.
1019 1019 fp = makefileobj(repo, template, node, desc=desc, total=total,
1020 1020 seqno=seqno, revwidth=revwidth, mode='wb',
1021 1021 modemap=filemode)
1022 1022 if fp != template:
1023 1023 shouldclose = True
1024 1024 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
1025 1025 repo.ui.note("%s\n" % fp.name)
1026 1026
1027 1027 if not fp:
1028 1028 write = repo.ui.write
1029 1029 else:
1030 1030 def write(s, **kw):
1031 1031 fp.write(s)
1032 1032
1033 1033 write("# HG changeset patch\n")
1034 1034 write("# User %s\n" % ctx.user())
1035 1035 write("# Date %d %d\n" % ctx.date())
1036 1036 write("# %s\n" % util.datestr(ctx.date()))
1037 1037 if branch and branch != 'default':
1038 1038 write("# Branch %s\n" % branch)
1039 1039 write("# Node ID %s\n" % hex(node))
1040 1040 write("# Parent %s\n" % hex(prev))
1041 1041 if len(parents) > 1:
1042 1042 write("# Parent %s\n" % hex(parents[1]))
1043 1043 write(ctx.description().rstrip())
1044 1044 write("\n\n")
1045 1045
1046 1046 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
1047 1047 write(chunk, label=label)
1048 1048
1049 1049 if shouldclose:
1050 1050 fp.close()
1051 1051
1052 1052 for seqno, rev in enumerate(revs):
1053 1053 single(rev, seqno + 1, fp)
1054 1054
1055 1055 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1056 1056 changes=None, stat=False, fp=None, prefix='',
1057 1057 root='', listsubrepos=False):
1058 1058 '''show diff or diffstat.'''
1059 1059 if fp is None:
1060 1060 write = ui.write
1061 1061 else:
1062 1062 def write(s, **kw):
1063 1063 fp.write(s)
1064 1064
1065 1065 if root:
1066 1066 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1067 1067 else:
1068 1068 relroot = ''
1069 1069 if relroot != '':
1070 1070 # XXX relative roots currently don't work if the root is within a
1071 1071 # subrepo
1072 1072 uirelroot = match.uipath(relroot)
1073 1073 relroot += '/'
1074 1074 for matchroot in match.files():
1075 1075 if not matchroot.startswith(relroot):
1076 1076 ui.warn(_('warning: %s not inside relative root %s\n') % (
1077 1077 match.uipath(matchroot), uirelroot))
1078 1078
1079 1079 if stat:
1080 1080 diffopts = diffopts.copy(context=0)
1081 1081 width = 80
1082 1082 if not ui.plain():
1083 1083 width = ui.termwidth()
1084 1084 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1085 1085 prefix=prefix, relroot=relroot)
1086 1086 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1087 1087 width=width,
1088 1088 git=diffopts.git):
1089 1089 write(chunk, label=label)
1090 1090 else:
1091 1091 for chunk, label in patch.diffui(repo, node1, node2, match,
1092 1092 changes, diffopts, prefix=prefix,
1093 1093 relroot=relroot):
1094 1094 write(chunk, label=label)
1095 1095
1096 1096 if listsubrepos:
1097 1097 ctx1 = repo[node1]
1098 1098 ctx2 = repo[node2]
1099 1099 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1100 1100 tempnode2 = node2
1101 1101 try:
1102 1102 if node2 is not None:
1103 1103 tempnode2 = ctx2.substate[subpath][1]
1104 1104 except KeyError:
1105 1105 # A subrepo that existed in node1 was deleted between node1 and
1106 1106 # node2 (inclusive). Thus, ctx2's substate won't contain that
1107 1107 # subpath. The best we can do is to ignore it.
1108 1108 tempnode2 = None
1109 1109 submatch = matchmod.narrowmatcher(subpath, match)
1110 1110 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1111 1111 stat=stat, fp=fp, prefix=prefix)
1112 1112
1113 1113 class changeset_printer(object):
1114 1114 '''show changeset information when templating not requested.'''
1115 1115
1116 1116 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1117 1117 self.ui = ui
1118 1118 self.repo = repo
1119 1119 self.buffered = buffered
1120 1120 self.matchfn = matchfn
1121 1121 self.diffopts = diffopts
1122 1122 self.header = {}
1123 1123 self.hunk = {}
1124 1124 self.lastheader = None
1125 1125 self.footer = None
1126 1126
1127 1127 def flush(self, ctx):
1128 1128 rev = ctx.rev()
1129 1129 if rev in self.header:
1130 1130 h = self.header[rev]
1131 1131 if h != self.lastheader:
1132 1132 self.lastheader = h
1133 1133 self.ui.write(h)
1134 1134 del self.header[rev]
1135 1135 if rev in self.hunk:
1136 1136 self.ui.write(self.hunk[rev])
1137 1137 del self.hunk[rev]
1138 1138 return 1
1139 1139 return 0
1140 1140
1141 1141 def close(self):
1142 1142 if self.footer:
1143 1143 self.ui.write(self.footer)
1144 1144
1145 1145 def show(self, ctx, copies=None, matchfn=None, **props):
1146 1146 if self.buffered:
1147 1147 self.ui.pushbuffer()
1148 1148 self._show(ctx, copies, matchfn, props)
1149 1149 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1150 1150 else:
1151 1151 self._show(ctx, copies, matchfn, props)
1152 1152
1153 1153 def _show(self, ctx, copies, matchfn, props):
1154 1154 '''show a single changeset or file revision'''
1155 1155 changenode = ctx.node()
1156 1156 rev = ctx.rev()
1157 1157 if self.ui.debugflag:
1158 1158 hexfunc = hex
1159 1159 else:
1160 1160 hexfunc = short
1161 1161 # as of now, wctx.node() and wctx.rev() return None, but we want to
1162 1162 # show the same values as {node} and {rev} templatekw
1163 1163 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1164 1164
1165 1165 if self.ui.quiet:
1166 1166 self.ui.write("%d:%s\n" % revnode, label='log.node')
1167 1167 return
1168 1168
1169 1169 date = util.datestr(ctx.date())
1170 1170
1171 1171 # i18n: column positioning for "hg log"
1172 1172 self.ui.write(_("changeset: %d:%s\n") % revnode,
1173 1173 label='log.changeset changeset.%s' % ctx.phasestr())
1174 1174
1175 1175 # branches are shown first before any other names due to backwards
1176 1176 # compatibility
1177 1177 branch = ctx.branch()
1178 1178 # don't show the default branch name
1179 1179 if branch != 'default':
1180 1180 # i18n: column positioning for "hg log"
1181 1181 self.ui.write(_("branch: %s\n") % branch,
1182 1182 label='log.branch')
1183 1183
1184 1184 for name, ns in self.repo.names.iteritems():
1185 1185 # branches has special logic already handled above, so here we just
1186 1186 # skip it
1187 1187 if name == 'branches':
1188 1188 continue
1189 1189 # we will use the templatename as the color name since those two
1190 1190 # should be the same
1191 1191 for name in ns.names(self.repo, changenode):
1192 1192 self.ui.write(ns.logfmt % name,
1193 1193 label='log.%s' % ns.colorname)
1194 1194 if self.ui.debugflag:
1195 1195 # i18n: column positioning for "hg log"
1196 1196 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1197 1197 label='log.phase')
1198 1198 for pctx in self._meaningful_parentrevs(ctx):
1199 1199 label = 'log.parent changeset.%s' % pctx.phasestr()
1200 1200 # i18n: column positioning for "hg log"
1201 1201 self.ui.write(_("parent: %d:%s\n")
1202 1202 % (pctx.rev(), hexfunc(pctx.node())),
1203 1203 label=label)
1204 1204
1205 1205 if self.ui.debugflag and rev is not None:
1206 1206 mnode = ctx.manifestnode()
1207 1207 # i18n: column positioning for "hg log"
1208 1208 self.ui.write(_("manifest: %d:%s\n") %
1209 1209 (self.repo.manifest.rev(mnode), hex(mnode)),
1210 1210 label='ui.debug log.manifest')
1211 1211 # i18n: column positioning for "hg log"
1212 1212 self.ui.write(_("user: %s\n") % ctx.user(),
1213 1213 label='log.user')
1214 1214 # i18n: column positioning for "hg log"
1215 1215 self.ui.write(_("date: %s\n") % date,
1216 1216 label='log.date')
1217 1217
1218 1218 if self.ui.debugflag:
1219 1219 files = ctx.p1().status(ctx)[:3]
1220 1220 for key, value in zip([# i18n: column positioning for "hg log"
1221 1221 _("files:"),
1222 1222 # i18n: column positioning for "hg log"
1223 1223 _("files+:"),
1224 1224 # i18n: column positioning for "hg log"
1225 1225 _("files-:")], files):
1226 1226 if value:
1227 1227 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1228 1228 label='ui.debug log.files')
1229 1229 elif ctx.files() and self.ui.verbose:
1230 1230 # i18n: column positioning for "hg log"
1231 1231 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1232 1232 label='ui.note log.files')
1233 1233 if copies and self.ui.verbose:
1234 1234 copies = ['%s (%s)' % c for c in copies]
1235 1235 # i18n: column positioning for "hg log"
1236 1236 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1237 1237 label='ui.note log.copies')
1238 1238
1239 1239 extra = ctx.extra()
1240 1240 if extra and self.ui.debugflag:
1241 1241 for key, value in sorted(extra.items()):
1242 1242 # i18n: column positioning for "hg log"
1243 1243 self.ui.write(_("extra: %s=%s\n")
1244 1244 % (key, value.encode('string_escape')),
1245 1245 label='ui.debug log.extra')
1246 1246
1247 1247 description = ctx.description().strip()
1248 1248 if description:
1249 1249 if self.ui.verbose:
1250 1250 self.ui.write(_("description:\n"),
1251 1251 label='ui.note log.description')
1252 1252 self.ui.write(description,
1253 1253 label='ui.note log.description')
1254 1254 self.ui.write("\n\n")
1255 1255 else:
1256 1256 # i18n: column positioning for "hg log"
1257 1257 self.ui.write(_("summary: %s\n") %
1258 1258 description.splitlines()[0],
1259 1259 label='log.summary')
1260 1260 self.ui.write("\n")
1261 1261
1262 1262 self.showpatch(changenode, matchfn)
1263 1263
1264 1264 def showpatch(self, node, matchfn):
1265 1265 if not matchfn:
1266 1266 matchfn = self.matchfn
1267 1267 if matchfn:
1268 1268 stat = self.diffopts.get('stat')
1269 1269 diff = self.diffopts.get('patch')
1270 1270 diffopts = patch.diffallopts(self.ui, self.diffopts)
1271 1271 prev = self.repo.changelog.parents(node)[0]
1272 1272 if stat:
1273 1273 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1274 1274 match=matchfn, stat=True)
1275 1275 if diff:
1276 1276 if stat:
1277 1277 self.ui.write("\n")
1278 1278 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1279 1279 match=matchfn, stat=False)
1280 1280 self.ui.write("\n")
1281 1281
1282 1282 def _meaningful_parentrevs(self, ctx):
1283 1283 """Return list of meaningful (or all if debug) parentrevs for rev.
1284 1284
1285 1285 For merges (two non-nullrev revisions) both parents are meaningful.
1286 1286 Otherwise the first parent revision is considered meaningful if it
1287 1287 is not the preceding revision.
1288 1288 """
1289 1289 parents = ctx.parents()
1290 1290 if len(parents) > 1:
1291 1291 return parents
1292 1292 if self.ui.debugflag:
1293 1293 return [parents[0], self.repo['null']]
1294 1294 if parents[0].rev() >= scmutil.intrev(ctx.rev()) - 1:
1295 1295 return []
1296 1296 return parents
1297 1297
1298 1298 class jsonchangeset(changeset_printer):
1299 1299 '''format changeset information.'''
1300 1300
1301 1301 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1302 1302 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1303 1303 self.cache = {}
1304 1304 self._first = True
1305 1305
1306 1306 def close(self):
1307 1307 if not self._first:
1308 1308 self.ui.write("\n]\n")
1309 1309 else:
1310 1310 self.ui.write("[]\n")
1311 1311
1312 1312 def _show(self, ctx, copies, matchfn, props):
1313 1313 '''show a single changeset or file revision'''
1314 1314 rev = ctx.rev()
1315 1315 if rev is None:
1316 1316 jrev = jnode = 'null'
1317 1317 else:
1318 1318 jrev = str(rev)
1319 1319 jnode = '"%s"' % hex(ctx.node())
1320 1320 j = encoding.jsonescape
1321 1321
1322 1322 if self._first:
1323 1323 self.ui.write("[\n {")
1324 1324 self._first = False
1325 1325 else:
1326 1326 self.ui.write(",\n {")
1327 1327
1328 1328 if self.ui.quiet:
1329 1329 self.ui.write('\n "rev": %s' % jrev)
1330 1330 self.ui.write(',\n "node": %s' % jnode)
1331 1331 self.ui.write('\n }')
1332 1332 return
1333 1333
1334 1334 self.ui.write('\n "rev": %s' % jrev)
1335 1335 self.ui.write(',\n "node": %s' % jnode)
1336 1336 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1337 1337 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1338 1338 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1339 1339 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1340 1340 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1341 1341
1342 1342 self.ui.write(',\n "bookmarks": [%s]' %
1343 1343 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1344 1344 self.ui.write(',\n "tags": [%s]' %
1345 1345 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1346 1346 self.ui.write(',\n "parents": [%s]' %
1347 1347 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1348 1348
1349 1349 if self.ui.debugflag:
1350 1350 if rev is None:
1351 1351 jmanifestnode = 'null'
1352 1352 else:
1353 1353 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1354 1354 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1355 1355
1356 1356 self.ui.write(',\n "extra": {%s}' %
1357 1357 ", ".join('"%s": "%s"' % (j(k), j(v))
1358 1358 for k, v in ctx.extra().items()))
1359 1359
1360 1360 files = ctx.p1().status(ctx)
1361 1361 self.ui.write(',\n "modified": [%s]' %
1362 1362 ", ".join('"%s"' % j(f) for f in files[0]))
1363 1363 self.ui.write(',\n "added": [%s]' %
1364 1364 ", ".join('"%s"' % j(f) for f in files[1]))
1365 1365 self.ui.write(',\n "removed": [%s]' %
1366 1366 ", ".join('"%s"' % j(f) for f in files[2]))
1367 1367
1368 1368 elif self.ui.verbose:
1369 1369 self.ui.write(',\n "files": [%s]' %
1370 1370 ", ".join('"%s"' % j(f) for f in ctx.files()))
1371 1371
1372 1372 if copies:
1373 1373 self.ui.write(',\n "copies": {%s}' %
1374 1374 ", ".join('"%s": "%s"' % (j(k), j(v))
1375 1375 for k, v in copies))
1376 1376
1377 1377 matchfn = self.matchfn
1378 1378 if matchfn:
1379 1379 stat = self.diffopts.get('stat')
1380 1380 diff = self.diffopts.get('patch')
1381 1381 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1382 1382 node, prev = ctx.node(), ctx.p1().node()
1383 1383 if stat:
1384 1384 self.ui.pushbuffer()
1385 1385 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1386 1386 match=matchfn, stat=True)
1387 1387 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1388 1388 if diff:
1389 1389 self.ui.pushbuffer()
1390 1390 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1391 1391 match=matchfn, stat=False)
1392 1392 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1393 1393
1394 1394 self.ui.write("\n }")
1395 1395
1396 1396 class changeset_templater(changeset_printer):
1397 1397 '''format changeset information.'''
1398 1398
1399 1399 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1400 1400 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1401 1401 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1402 1402 defaulttempl = {
1403 1403 'parent': '{rev}:{node|formatnode} ',
1404 1404 'manifest': '{rev}:{node|formatnode}',
1405 1405 'file_copy': '{name} ({source})',
1406 1406 'extra': '{key}={value|stringescape}'
1407 1407 }
1408 1408 # filecopy is preserved for compatibility reasons
1409 1409 defaulttempl['filecopy'] = defaulttempl['file_copy']
1410 1410 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1411 1411 cache=defaulttempl)
1412 1412 if tmpl:
1413 1413 self.t.cache['changeset'] = tmpl
1414 1414
1415 1415 self.cache = {}
1416 1416
1417 1417 # find correct templates for current mode
1418 1418 tmplmodes = [
1419 1419 (True, None),
1420 1420 (self.ui.verbose, 'verbose'),
1421 1421 (self.ui.quiet, 'quiet'),
1422 1422 (self.ui.debugflag, 'debug'),
1423 1423 ]
1424 1424
1425 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset'}
1425 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1426 'docheader': '', 'docfooter': ''}
1426 1427 for mode, postfix in tmplmodes:
1427 1428 for t in self._parts:
1428 1429 cur = t
1429 1430 if postfix:
1430 1431 cur += "_" + postfix
1431 1432 if mode and cur in self.t:
1432 1433 self._parts[t] = cur
1433 1434
1435 if self._parts['docheader']:
1436 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1437
1438 def close(self):
1439 if self._parts['docfooter']:
1440 if not self.footer:
1441 self.footer = ""
1442 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1443 return super(changeset_templater, self).close()
1444
1434 1445 def _show(self, ctx, copies, matchfn, props):
1435 1446 '''show a single changeset or file revision'''
1436 1447
1437 1448 showlist = templatekw.showlist
1438 1449
1439 1450 # showparents() behavior depends on ui trace level which
1440 1451 # causes unexpected behaviors at templating level and makes
1441 1452 # it harder to extract it in a standalone function. Its
1442 1453 # behavior cannot be changed so leave it here for now.
1443 1454 def showparents(**args):
1444 1455 ctx = args['ctx']
1445 1456 parents = [[('rev', p.rev()),
1446 1457 ('node', p.hex()),
1447 1458 ('phase', p.phasestr())]
1448 1459 for p in self._meaningful_parentrevs(ctx)]
1449 1460 return showlist('parent', parents, **args)
1450 1461
1451 1462 props = props.copy()
1452 1463 props.update(templatekw.keywords)
1453 1464 props['parents'] = showparents
1454 1465 props['templ'] = self.t
1455 1466 props['ctx'] = ctx
1456 1467 props['repo'] = self.repo
1457 1468 props['revcache'] = {'copies': copies}
1458 1469 props['cache'] = self.cache
1459 1470
1460 1471 try:
1461 1472 # write header
1462 1473 if self._parts['header']:
1463 1474 h = templater.stringify(self.t(self._parts['header'], **props))
1464 1475 if self.buffered:
1465 1476 self.header[ctx.rev()] = h
1466 1477 else:
1467 1478 if self.lastheader != h:
1468 1479 self.lastheader = h
1469 1480 self.ui.write(h)
1470 1481
1471 1482 # write changeset metadata, then patch if requested
1472 1483 key = self._parts['changeset']
1473 1484 self.ui.write(templater.stringify(self.t(key, **props)))
1474 1485 self.showpatch(ctx.node(), matchfn)
1475 1486
1476 1487 if self._parts['footer']:
1477 1488 if not self.footer:
1478 1489 self.footer = templater.stringify(
1479 1490 self.t(self._parts['footer'], **props))
1480 1491 except KeyError as inst:
1481 1492 msg = _("%s: no key named '%s'")
1482 1493 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1483 1494 except SyntaxError as inst:
1484 1495 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1485 1496
1486 1497 def gettemplate(ui, tmpl, style):
1487 1498 """
1488 1499 Find the template matching the given template spec or style.
1489 1500 """
1490 1501
1491 1502 # ui settings
1492 1503 if not tmpl and not style: # template are stronger than style
1493 1504 tmpl = ui.config('ui', 'logtemplate')
1494 1505 if tmpl:
1495 1506 try:
1496 1507 tmpl = templater.unquotestring(tmpl)
1497 1508 except SyntaxError:
1498 1509 pass
1499 1510 return tmpl, None
1500 1511 else:
1501 1512 style = util.expandpath(ui.config('ui', 'style', ''))
1502 1513
1503 1514 if not tmpl and style:
1504 1515 mapfile = style
1505 1516 if not os.path.split(mapfile)[0]:
1506 1517 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1507 1518 or templater.templatepath(mapfile))
1508 1519 if mapname:
1509 1520 mapfile = mapname
1510 1521 return None, mapfile
1511 1522
1512 1523 if not tmpl:
1513 1524 return None, None
1514 1525
1515 1526 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1516 1527
1517 1528 def show_changeset(ui, repo, opts, buffered=False):
1518 1529 """show one changeset using template or regular display.
1519 1530
1520 1531 Display format will be the first non-empty hit of:
1521 1532 1. option 'template'
1522 1533 2. option 'style'
1523 1534 3. [ui] setting 'logtemplate'
1524 1535 4. [ui] setting 'style'
1525 1536 If all of these values are either the unset or the empty string,
1526 1537 regular display via changeset_printer() is done.
1527 1538 """
1528 1539 # options
1529 1540 matchfn = None
1530 1541 if opts.get('patch') or opts.get('stat'):
1531 1542 matchfn = scmutil.matchall(repo)
1532 1543
1533 1544 if opts.get('template') == 'json':
1534 1545 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1535 1546
1536 1547 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1537 1548
1538 1549 if not tmpl and not mapfile:
1539 1550 return changeset_printer(ui, repo, matchfn, opts, buffered)
1540 1551
1541 1552 try:
1542 1553 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1543 1554 buffered)
1544 1555 except SyntaxError as inst:
1545 1556 raise util.Abort(inst.args[0])
1546 1557 return t
1547 1558
1548 1559 def showmarker(ui, marker):
1549 1560 """utility function to display obsolescence marker in a readable way
1550 1561
1551 1562 To be used by debug function."""
1552 1563 ui.write(hex(marker.precnode()))
1553 1564 for repl in marker.succnodes():
1554 1565 ui.write(' ')
1555 1566 ui.write(hex(repl))
1556 1567 ui.write(' %X ' % marker.flags())
1557 1568 parents = marker.parentnodes()
1558 1569 if parents is not None:
1559 1570 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1560 1571 ui.write('(%s) ' % util.datestr(marker.date()))
1561 1572 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1562 1573 sorted(marker.metadata().items())
1563 1574 if t[0] != 'date')))
1564 1575 ui.write('\n')
1565 1576
1566 1577 def finddate(ui, repo, date):
1567 1578 """Find the tipmost changeset that matches the given date spec"""
1568 1579
1569 1580 df = util.matchdate(date)
1570 1581 m = scmutil.matchall(repo)
1571 1582 results = {}
1572 1583
1573 1584 def prep(ctx, fns):
1574 1585 d = ctx.date()
1575 1586 if df(d[0]):
1576 1587 results[ctx.rev()] = d
1577 1588
1578 1589 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1579 1590 rev = ctx.rev()
1580 1591 if rev in results:
1581 1592 ui.status(_("found revision %s from %s\n") %
1582 1593 (rev, util.datestr(results[rev])))
1583 1594 return str(rev)
1584 1595
1585 1596 raise util.Abort(_("revision matching date not found"))
1586 1597
1587 1598 def increasingwindows(windowsize=8, sizelimit=512):
1588 1599 while True:
1589 1600 yield windowsize
1590 1601 if windowsize < sizelimit:
1591 1602 windowsize *= 2
1592 1603
1593 1604 class FileWalkError(Exception):
1594 1605 pass
1595 1606
1596 1607 def walkfilerevs(repo, match, follow, revs, fncache):
1597 1608 '''Walks the file history for the matched files.
1598 1609
1599 1610 Returns the changeset revs that are involved in the file history.
1600 1611
1601 1612 Throws FileWalkError if the file history can't be walked using
1602 1613 filelogs alone.
1603 1614 '''
1604 1615 wanted = set()
1605 1616 copies = []
1606 1617 minrev, maxrev = min(revs), max(revs)
1607 1618 def filerevgen(filelog, last):
1608 1619 """
1609 1620 Only files, no patterns. Check the history of each file.
1610 1621
1611 1622 Examines filelog entries within minrev, maxrev linkrev range
1612 1623 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1613 1624 tuples in backwards order
1614 1625 """
1615 1626 cl_count = len(repo)
1616 1627 revs = []
1617 1628 for j in xrange(0, last + 1):
1618 1629 linkrev = filelog.linkrev(j)
1619 1630 if linkrev < minrev:
1620 1631 continue
1621 1632 # only yield rev for which we have the changelog, it can
1622 1633 # happen while doing "hg log" during a pull or commit
1623 1634 if linkrev >= cl_count:
1624 1635 break
1625 1636
1626 1637 parentlinkrevs = []
1627 1638 for p in filelog.parentrevs(j):
1628 1639 if p != nullrev:
1629 1640 parentlinkrevs.append(filelog.linkrev(p))
1630 1641 n = filelog.node(j)
1631 1642 revs.append((linkrev, parentlinkrevs,
1632 1643 follow and filelog.renamed(n)))
1633 1644
1634 1645 return reversed(revs)
1635 1646 def iterfiles():
1636 1647 pctx = repo['.']
1637 1648 for filename in match.files():
1638 1649 if follow:
1639 1650 if filename not in pctx:
1640 1651 raise util.Abort(_('cannot follow file not in parent '
1641 1652 'revision: "%s"') % filename)
1642 1653 yield filename, pctx[filename].filenode()
1643 1654 else:
1644 1655 yield filename, None
1645 1656 for filename_node in copies:
1646 1657 yield filename_node
1647 1658
1648 1659 for file_, node in iterfiles():
1649 1660 filelog = repo.file(file_)
1650 1661 if not len(filelog):
1651 1662 if node is None:
1652 1663 # A zero count may be a directory or deleted file, so
1653 1664 # try to find matching entries on the slow path.
1654 1665 if follow:
1655 1666 raise util.Abort(
1656 1667 _('cannot follow nonexistent file: "%s"') % file_)
1657 1668 raise FileWalkError("Cannot walk via filelog")
1658 1669 else:
1659 1670 continue
1660 1671
1661 1672 if node is None:
1662 1673 last = len(filelog) - 1
1663 1674 else:
1664 1675 last = filelog.rev(node)
1665 1676
1666 1677 # keep track of all ancestors of the file
1667 1678 ancestors = set([filelog.linkrev(last)])
1668 1679
1669 1680 # iterate from latest to oldest revision
1670 1681 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1671 1682 if not follow:
1672 1683 if rev > maxrev:
1673 1684 continue
1674 1685 else:
1675 1686 # Note that last might not be the first interesting
1676 1687 # rev to us:
1677 1688 # if the file has been changed after maxrev, we'll
1678 1689 # have linkrev(last) > maxrev, and we still need
1679 1690 # to explore the file graph
1680 1691 if rev not in ancestors:
1681 1692 continue
1682 1693 # XXX insert 1327 fix here
1683 1694 if flparentlinkrevs:
1684 1695 ancestors.update(flparentlinkrevs)
1685 1696
1686 1697 fncache.setdefault(rev, []).append(file_)
1687 1698 wanted.add(rev)
1688 1699 if copied:
1689 1700 copies.append(copied)
1690 1701
1691 1702 return wanted
1692 1703
1693 1704 class _followfilter(object):
1694 1705 def __init__(self, repo, onlyfirst=False):
1695 1706 self.repo = repo
1696 1707 self.startrev = nullrev
1697 1708 self.roots = set()
1698 1709 self.onlyfirst = onlyfirst
1699 1710
1700 1711 def match(self, rev):
1701 1712 def realparents(rev):
1702 1713 if self.onlyfirst:
1703 1714 return self.repo.changelog.parentrevs(rev)[0:1]
1704 1715 else:
1705 1716 return filter(lambda x: x != nullrev,
1706 1717 self.repo.changelog.parentrevs(rev))
1707 1718
1708 1719 if self.startrev == nullrev:
1709 1720 self.startrev = rev
1710 1721 return True
1711 1722
1712 1723 if rev > self.startrev:
1713 1724 # forward: all descendants
1714 1725 if not self.roots:
1715 1726 self.roots.add(self.startrev)
1716 1727 for parent in realparents(rev):
1717 1728 if parent in self.roots:
1718 1729 self.roots.add(rev)
1719 1730 return True
1720 1731 else:
1721 1732 # backwards: all parents
1722 1733 if not self.roots:
1723 1734 self.roots.update(realparents(self.startrev))
1724 1735 if rev in self.roots:
1725 1736 self.roots.remove(rev)
1726 1737 self.roots.update(realparents(rev))
1727 1738 return True
1728 1739
1729 1740 return False
1730 1741
1731 1742 def walkchangerevs(repo, match, opts, prepare):
1732 1743 '''Iterate over files and the revs in which they changed.
1733 1744
1734 1745 Callers most commonly need to iterate backwards over the history
1735 1746 in which they are interested. Doing so has awful (quadratic-looking)
1736 1747 performance, so we use iterators in a "windowed" way.
1737 1748
1738 1749 We walk a window of revisions in the desired order. Within the
1739 1750 window, we first walk forwards to gather data, then in the desired
1740 1751 order (usually backwards) to display it.
1741 1752
1742 1753 This function returns an iterator yielding contexts. Before
1743 1754 yielding each context, the iterator will first call the prepare
1744 1755 function on each context in the window in forward order.'''
1745 1756
1746 1757 follow = opts.get('follow') or opts.get('follow_first')
1747 1758 revs = _logrevs(repo, opts)
1748 1759 if not revs:
1749 1760 return []
1750 1761 wanted = set()
1751 1762 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1752 1763 opts.get('removed'))
1753 1764 fncache = {}
1754 1765 change = repo.changectx
1755 1766
1756 1767 # First step is to fill wanted, the set of revisions that we want to yield.
1757 1768 # When it does not induce extra cost, we also fill fncache for revisions in
1758 1769 # wanted: a cache of filenames that were changed (ctx.files()) and that
1759 1770 # match the file filtering conditions.
1760 1771
1761 1772 if match.always():
1762 1773 # No files, no patterns. Display all revs.
1763 1774 wanted = revs
1764 1775 elif not slowpath:
1765 1776 # We only have to read through the filelog to find wanted revisions
1766 1777
1767 1778 try:
1768 1779 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1769 1780 except FileWalkError:
1770 1781 slowpath = True
1771 1782
1772 1783 # We decided to fall back to the slowpath because at least one
1773 1784 # of the paths was not a file. Check to see if at least one of them
1774 1785 # existed in history, otherwise simply return
1775 1786 for path in match.files():
1776 1787 if path == '.' or path in repo.store:
1777 1788 break
1778 1789 else:
1779 1790 return []
1780 1791
1781 1792 if slowpath:
1782 1793 # We have to read the changelog to match filenames against
1783 1794 # changed files
1784 1795
1785 1796 if follow:
1786 1797 raise util.Abort(_('can only follow copies/renames for explicit '
1787 1798 'filenames'))
1788 1799
1789 1800 # The slow path checks files modified in every changeset.
1790 1801 # This is really slow on large repos, so compute the set lazily.
1791 1802 class lazywantedset(object):
1792 1803 def __init__(self):
1793 1804 self.set = set()
1794 1805 self.revs = set(revs)
1795 1806
1796 1807 # No need to worry about locality here because it will be accessed
1797 1808 # in the same order as the increasing window below.
1798 1809 def __contains__(self, value):
1799 1810 if value in self.set:
1800 1811 return True
1801 1812 elif not value in self.revs:
1802 1813 return False
1803 1814 else:
1804 1815 self.revs.discard(value)
1805 1816 ctx = change(value)
1806 1817 matches = filter(match, ctx.files())
1807 1818 if matches:
1808 1819 fncache[value] = matches
1809 1820 self.set.add(value)
1810 1821 return True
1811 1822 return False
1812 1823
1813 1824 def discard(self, value):
1814 1825 self.revs.discard(value)
1815 1826 self.set.discard(value)
1816 1827
1817 1828 wanted = lazywantedset()
1818 1829
1819 1830 # it might be worthwhile to do this in the iterator if the rev range
1820 1831 # is descending and the prune args are all within that range
1821 1832 for rev in opts.get('prune', ()):
1822 1833 rev = repo[rev].rev()
1823 1834 ff = _followfilter(repo)
1824 1835 stop = min(revs[0], revs[-1])
1825 1836 for x in xrange(rev, stop - 1, -1):
1826 1837 if ff.match(x):
1827 1838 wanted = wanted - [x]
1828 1839
1829 1840 # Now that wanted is correctly initialized, we can iterate over the
1830 1841 # revision range, yielding only revisions in wanted.
1831 1842 def iterate():
1832 1843 if follow and match.always():
1833 1844 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1834 1845 def want(rev):
1835 1846 return ff.match(rev) and rev in wanted
1836 1847 else:
1837 1848 def want(rev):
1838 1849 return rev in wanted
1839 1850
1840 1851 it = iter(revs)
1841 1852 stopiteration = False
1842 1853 for windowsize in increasingwindows():
1843 1854 nrevs = []
1844 1855 for i in xrange(windowsize):
1845 1856 rev = next(it, None)
1846 1857 if rev is None:
1847 1858 stopiteration = True
1848 1859 break
1849 1860 elif want(rev):
1850 1861 nrevs.append(rev)
1851 1862 for rev in sorted(nrevs):
1852 1863 fns = fncache.get(rev)
1853 1864 ctx = change(rev)
1854 1865 if not fns:
1855 1866 def fns_generator():
1856 1867 for f in ctx.files():
1857 1868 if match(f):
1858 1869 yield f
1859 1870 fns = fns_generator()
1860 1871 prepare(ctx, fns)
1861 1872 for rev in nrevs:
1862 1873 yield change(rev)
1863 1874
1864 1875 if stopiteration:
1865 1876 break
1866 1877
1867 1878 return iterate()
1868 1879
1869 1880 def _makefollowlogfilematcher(repo, files, followfirst):
1870 1881 # When displaying a revision with --patch --follow FILE, we have
1871 1882 # to know which file of the revision must be diffed. With
1872 1883 # --follow, we want the names of the ancestors of FILE in the
1873 1884 # revision, stored in "fcache". "fcache" is populated by
1874 1885 # reproducing the graph traversal already done by --follow revset
1875 1886 # and relating linkrevs to file names (which is not "correct" but
1876 1887 # good enough).
1877 1888 fcache = {}
1878 1889 fcacheready = [False]
1879 1890 pctx = repo['.']
1880 1891
1881 1892 def populate():
1882 1893 for fn in files:
1883 1894 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1884 1895 for c in i:
1885 1896 fcache.setdefault(c.linkrev(), set()).add(c.path())
1886 1897
1887 1898 def filematcher(rev):
1888 1899 if not fcacheready[0]:
1889 1900 # Lazy initialization
1890 1901 fcacheready[0] = True
1891 1902 populate()
1892 1903 return scmutil.matchfiles(repo, fcache.get(rev, []))
1893 1904
1894 1905 return filematcher
1895 1906
1896 1907 def _makenofollowlogfilematcher(repo, pats, opts):
1897 1908 '''hook for extensions to override the filematcher for non-follow cases'''
1898 1909 return None
1899 1910
1900 1911 def _makelogrevset(repo, pats, opts, revs):
1901 1912 """Return (expr, filematcher) where expr is a revset string built
1902 1913 from log options and file patterns or None. If --stat or --patch
1903 1914 are not passed filematcher is None. Otherwise it is a callable
1904 1915 taking a revision number and returning a match objects filtering
1905 1916 the files to be detailed when displaying the revision.
1906 1917 """
1907 1918 opt2revset = {
1908 1919 'no_merges': ('not merge()', None),
1909 1920 'only_merges': ('merge()', None),
1910 1921 '_ancestors': ('ancestors(%(val)s)', None),
1911 1922 '_fancestors': ('_firstancestors(%(val)s)', None),
1912 1923 '_descendants': ('descendants(%(val)s)', None),
1913 1924 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1914 1925 '_matchfiles': ('_matchfiles(%(val)s)', None),
1915 1926 'date': ('date(%(val)r)', None),
1916 1927 'branch': ('branch(%(val)r)', ' or '),
1917 1928 '_patslog': ('filelog(%(val)r)', ' or '),
1918 1929 '_patsfollow': ('follow(%(val)r)', ' or '),
1919 1930 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1920 1931 'keyword': ('keyword(%(val)r)', ' or '),
1921 1932 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1922 1933 'user': ('user(%(val)r)', ' or '),
1923 1934 }
1924 1935
1925 1936 opts = dict(opts)
1926 1937 # follow or not follow?
1927 1938 follow = opts.get('follow') or opts.get('follow_first')
1928 1939 if opts.get('follow_first'):
1929 1940 followfirst = 1
1930 1941 else:
1931 1942 followfirst = 0
1932 1943 # --follow with FILE behavior depends on revs...
1933 1944 it = iter(revs)
1934 1945 startrev = it.next()
1935 1946 followdescendants = startrev < next(it, startrev)
1936 1947
1937 1948 # branch and only_branch are really aliases and must be handled at
1938 1949 # the same time
1939 1950 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1940 1951 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1941 1952 # pats/include/exclude are passed to match.match() directly in
1942 1953 # _matchfiles() revset but walkchangerevs() builds its matcher with
1943 1954 # scmutil.match(). The difference is input pats are globbed on
1944 1955 # platforms without shell expansion (windows).
1945 1956 wctx = repo[None]
1946 1957 match, pats = scmutil.matchandpats(wctx, pats, opts)
1947 1958 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1948 1959 opts.get('removed'))
1949 1960 if not slowpath:
1950 1961 for f in match.files():
1951 1962 if follow and f not in wctx:
1952 1963 # If the file exists, it may be a directory, so let it
1953 1964 # take the slow path.
1954 1965 if os.path.exists(repo.wjoin(f)):
1955 1966 slowpath = True
1956 1967 continue
1957 1968 else:
1958 1969 raise util.Abort(_('cannot follow file not in parent '
1959 1970 'revision: "%s"') % f)
1960 1971 filelog = repo.file(f)
1961 1972 if not filelog:
1962 1973 # A zero count may be a directory or deleted file, so
1963 1974 # try to find matching entries on the slow path.
1964 1975 if follow:
1965 1976 raise util.Abort(
1966 1977 _('cannot follow nonexistent file: "%s"') % f)
1967 1978 slowpath = True
1968 1979
1969 1980 # We decided to fall back to the slowpath because at least one
1970 1981 # of the paths was not a file. Check to see if at least one of them
1971 1982 # existed in history - in that case, we'll continue down the
1972 1983 # slowpath; otherwise, we can turn off the slowpath
1973 1984 if slowpath:
1974 1985 for path in match.files():
1975 1986 if path == '.' or path in repo.store:
1976 1987 break
1977 1988 else:
1978 1989 slowpath = False
1979 1990
1980 1991 fpats = ('_patsfollow', '_patsfollowfirst')
1981 1992 fnopats = (('_ancestors', '_fancestors'),
1982 1993 ('_descendants', '_fdescendants'))
1983 1994 if slowpath:
1984 1995 # See walkchangerevs() slow path.
1985 1996 #
1986 1997 # pats/include/exclude cannot be represented as separate
1987 1998 # revset expressions as their filtering logic applies at file
1988 1999 # level. For instance "-I a -X a" matches a revision touching
1989 2000 # "a" and "b" while "file(a) and not file(b)" does
1990 2001 # not. Besides, filesets are evaluated against the working
1991 2002 # directory.
1992 2003 matchargs = ['r:', 'd:relpath']
1993 2004 for p in pats:
1994 2005 matchargs.append('p:' + p)
1995 2006 for p in opts.get('include', []):
1996 2007 matchargs.append('i:' + p)
1997 2008 for p in opts.get('exclude', []):
1998 2009 matchargs.append('x:' + p)
1999 2010 matchargs = ','.join(('%r' % p) for p in matchargs)
2000 2011 opts['_matchfiles'] = matchargs
2001 2012 if follow:
2002 2013 opts[fnopats[0][followfirst]] = '.'
2003 2014 else:
2004 2015 if follow:
2005 2016 if pats:
2006 2017 # follow() revset interprets its file argument as a
2007 2018 # manifest entry, so use match.files(), not pats.
2008 2019 opts[fpats[followfirst]] = list(match.files())
2009 2020 else:
2010 2021 op = fnopats[followdescendants][followfirst]
2011 2022 opts[op] = 'rev(%d)' % startrev
2012 2023 else:
2013 2024 opts['_patslog'] = list(pats)
2014 2025
2015 2026 filematcher = None
2016 2027 if opts.get('patch') or opts.get('stat'):
2017 2028 # When following files, track renames via a special matcher.
2018 2029 # If we're forced to take the slowpath it means we're following
2019 2030 # at least one pattern/directory, so don't bother with rename tracking.
2020 2031 if follow and not match.always() and not slowpath:
2021 2032 # _makefollowlogfilematcher expects its files argument to be
2022 2033 # relative to the repo root, so use match.files(), not pats.
2023 2034 filematcher = _makefollowlogfilematcher(repo, match.files(),
2024 2035 followfirst)
2025 2036 else:
2026 2037 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2027 2038 if filematcher is None:
2028 2039 filematcher = lambda rev: match
2029 2040
2030 2041 expr = []
2031 2042 for op, val in sorted(opts.iteritems()):
2032 2043 if not val:
2033 2044 continue
2034 2045 if op not in opt2revset:
2035 2046 continue
2036 2047 revop, andor = opt2revset[op]
2037 2048 if '%(val)' not in revop:
2038 2049 expr.append(revop)
2039 2050 else:
2040 2051 if not isinstance(val, list):
2041 2052 e = revop % {'val': val}
2042 2053 else:
2043 2054 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2044 2055 expr.append(e)
2045 2056
2046 2057 if expr:
2047 2058 expr = '(' + ' and '.join(expr) + ')'
2048 2059 else:
2049 2060 expr = None
2050 2061 return expr, filematcher
2051 2062
2052 2063 def _logrevs(repo, opts):
2053 2064 # Default --rev value depends on --follow but --follow behavior
2054 2065 # depends on revisions resolved from --rev...
2055 2066 follow = opts.get('follow') or opts.get('follow_first')
2056 2067 if opts.get('rev'):
2057 2068 revs = scmutil.revrange(repo, opts['rev'])
2058 2069 elif follow and repo.dirstate.p1() == nullid:
2059 2070 revs = revset.baseset()
2060 2071 elif follow:
2061 2072 revs = repo.revs('reverse(:.)')
2062 2073 else:
2063 2074 revs = revset.spanset(repo)
2064 2075 revs.reverse()
2065 2076 return revs
2066 2077
2067 2078 def getgraphlogrevs(repo, pats, opts):
2068 2079 """Return (revs, expr, filematcher) where revs is an iterable of
2069 2080 revision numbers, expr is a revset string built from log options
2070 2081 and file patterns or None, and used to filter 'revs'. If --stat or
2071 2082 --patch are not passed filematcher is None. Otherwise it is a
2072 2083 callable taking a revision number and returning a match objects
2073 2084 filtering the files to be detailed when displaying the revision.
2074 2085 """
2075 2086 limit = loglimit(opts)
2076 2087 revs = _logrevs(repo, opts)
2077 2088 if not revs:
2078 2089 return revset.baseset(), None, None
2079 2090 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2080 2091 if opts.get('rev'):
2081 2092 # User-specified revs might be unsorted, but don't sort before
2082 2093 # _makelogrevset because it might depend on the order of revs
2083 2094 revs.sort(reverse=True)
2084 2095 if expr:
2085 2096 # Revset matchers often operate faster on revisions in changelog
2086 2097 # order, because most filters deal with the changelog.
2087 2098 revs.reverse()
2088 2099 matcher = revset.match(repo.ui, expr)
2089 2100 # Revset matches can reorder revisions. "A or B" typically returns
2090 2101 # returns the revision matching A then the revision matching B. Sort
2091 2102 # again to fix that.
2092 2103 revs = matcher(repo, revs)
2093 2104 revs.sort(reverse=True)
2094 2105 if limit is not None:
2095 2106 limitedrevs = []
2096 2107 for idx, rev in enumerate(revs):
2097 2108 if idx >= limit:
2098 2109 break
2099 2110 limitedrevs.append(rev)
2100 2111 revs = revset.baseset(limitedrevs)
2101 2112
2102 2113 return revs, expr, filematcher
2103 2114
2104 2115 def getlogrevs(repo, pats, opts):
2105 2116 """Return (revs, expr, filematcher) where revs is an iterable of
2106 2117 revision numbers, expr is a revset string built from log options
2107 2118 and file patterns or None, and used to filter 'revs'. If --stat or
2108 2119 --patch are not passed filematcher is None. Otherwise it is a
2109 2120 callable taking a revision number and returning a match objects
2110 2121 filtering the files to be detailed when displaying the revision.
2111 2122 """
2112 2123 limit = loglimit(opts)
2113 2124 revs = _logrevs(repo, opts)
2114 2125 if not revs:
2115 2126 return revset.baseset([]), None, None
2116 2127 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2117 2128 if expr:
2118 2129 # Revset matchers often operate faster on revisions in changelog
2119 2130 # order, because most filters deal with the changelog.
2120 2131 if not opts.get('rev'):
2121 2132 revs.reverse()
2122 2133 matcher = revset.match(repo.ui, expr)
2123 2134 # Revset matches can reorder revisions. "A or B" typically returns
2124 2135 # returns the revision matching A then the revision matching B. Sort
2125 2136 # again to fix that.
2126 2137 revs = matcher(repo, revs)
2127 2138 if not opts.get('rev'):
2128 2139 revs.sort(reverse=True)
2129 2140 if limit is not None:
2130 2141 limitedrevs = []
2131 2142 for idx, r in enumerate(revs):
2132 2143 if limit <= idx:
2133 2144 break
2134 2145 limitedrevs.append(r)
2135 2146 revs = revset.baseset(limitedrevs)
2136 2147
2137 2148 return revs, expr, filematcher
2138 2149
2139 2150 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2140 2151 filematcher=None):
2141 2152 seen, state = [], graphmod.asciistate()
2142 2153 for rev, type, ctx, parents in dag:
2143 2154 char = 'o'
2144 2155 if ctx.node() in showparents:
2145 2156 char = '@'
2146 2157 elif ctx.obsolete():
2147 2158 char = 'x'
2148 2159 elif ctx.closesbranch():
2149 2160 char = '_'
2150 2161 copies = None
2151 2162 if getrenamed and ctx.rev():
2152 2163 copies = []
2153 2164 for fn in ctx.files():
2154 2165 rename = getrenamed(fn, ctx.rev())
2155 2166 if rename:
2156 2167 copies.append((fn, rename[0]))
2157 2168 revmatchfn = None
2158 2169 if filematcher is not None:
2159 2170 revmatchfn = filematcher(ctx.rev())
2160 2171 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2161 2172 lines = displayer.hunk.pop(rev).split('\n')
2162 2173 if not lines[-1]:
2163 2174 del lines[-1]
2164 2175 displayer.flush(ctx)
2165 2176 edges = edgefn(type, char, lines, seen, rev, parents)
2166 2177 for type, char, lines, coldata in edges:
2167 2178 graphmod.ascii(ui, state, type, char, lines, coldata)
2168 2179 displayer.close()
2169 2180
2170 2181 def graphlog(ui, repo, *pats, **opts):
2171 2182 # Parameters are identical to log command ones
2172 2183 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2173 2184 revdag = graphmod.dagwalker(repo, revs)
2174 2185
2175 2186 getrenamed = None
2176 2187 if opts.get('copies'):
2177 2188 endrev = None
2178 2189 if opts.get('rev'):
2179 2190 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2180 2191 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2181 2192 displayer = show_changeset(ui, repo, opts, buffered=True)
2182 2193 showparents = [ctx.node() for ctx in repo[None].parents()]
2183 2194 displaygraph(ui, revdag, displayer, showparents,
2184 2195 graphmod.asciiedges, getrenamed, filematcher)
2185 2196
2186 2197 def checkunsupportedgraphflags(pats, opts):
2187 2198 for op in ["newest_first"]:
2188 2199 if op in opts and opts[op]:
2189 2200 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2190 2201 % op.replace("_", "-"))
2191 2202
2192 2203 def graphrevs(repo, nodes, opts):
2193 2204 limit = loglimit(opts)
2194 2205 nodes.reverse()
2195 2206 if limit is not None:
2196 2207 nodes = nodes[:limit]
2197 2208 return graphmod.nodes(repo, nodes)
2198 2209
2199 2210 def add(ui, repo, match, prefix, explicitonly, **opts):
2200 2211 join = lambda f: os.path.join(prefix, f)
2201 2212 bad = []
2202 2213
2203 2214 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2204 2215 names = []
2205 2216 wctx = repo[None]
2206 2217 cca = None
2207 2218 abort, warn = scmutil.checkportabilityalert(ui)
2208 2219 if abort or warn:
2209 2220 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2210 2221
2211 2222 badmatch = matchmod.badmatch(match, badfn)
2212 2223 dirstate = repo.dirstate
2213 2224 # We don't want to just call wctx.walk here, since it would return a lot of
2214 2225 # clean files, which we aren't interested in and takes time.
2215 2226 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2216 2227 True, False, full=False)):
2217 2228 exact = match.exact(f)
2218 2229 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2219 2230 if cca:
2220 2231 cca(f)
2221 2232 names.append(f)
2222 2233 if ui.verbose or not exact:
2223 2234 ui.status(_('adding %s\n') % match.rel(f))
2224 2235
2225 2236 for subpath in sorted(wctx.substate):
2226 2237 sub = wctx.sub(subpath)
2227 2238 try:
2228 2239 submatch = matchmod.narrowmatcher(subpath, match)
2229 2240 if opts.get('subrepos'):
2230 2241 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2231 2242 else:
2232 2243 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2233 2244 except error.LookupError:
2234 2245 ui.status(_("skipping missing subrepository: %s\n")
2235 2246 % join(subpath))
2236 2247
2237 2248 if not opts.get('dry_run'):
2238 2249 rejected = wctx.add(names, prefix)
2239 2250 bad.extend(f for f in rejected if f in match.files())
2240 2251 return bad
2241 2252
2242 2253 def forget(ui, repo, match, prefix, explicitonly):
2243 2254 join = lambda f: os.path.join(prefix, f)
2244 2255 bad = []
2245 2256 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2246 2257 wctx = repo[None]
2247 2258 forgot = []
2248 2259
2249 2260 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2250 2261 forget = sorted(s[0] + s[1] + s[3] + s[6])
2251 2262 if explicitonly:
2252 2263 forget = [f for f in forget if match.exact(f)]
2253 2264
2254 2265 for subpath in sorted(wctx.substate):
2255 2266 sub = wctx.sub(subpath)
2256 2267 try:
2257 2268 submatch = matchmod.narrowmatcher(subpath, match)
2258 2269 subbad, subforgot = sub.forget(submatch, prefix)
2259 2270 bad.extend([subpath + '/' + f for f in subbad])
2260 2271 forgot.extend([subpath + '/' + f for f in subforgot])
2261 2272 except error.LookupError:
2262 2273 ui.status(_("skipping missing subrepository: %s\n")
2263 2274 % join(subpath))
2264 2275
2265 2276 if not explicitonly:
2266 2277 for f in match.files():
2267 2278 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2268 2279 if f not in forgot:
2269 2280 if repo.wvfs.exists(f):
2270 2281 # Don't complain if the exact case match wasn't given.
2271 2282 # But don't do this until after checking 'forgot', so
2272 2283 # that subrepo files aren't normalized, and this op is
2273 2284 # purely from data cached by the status walk above.
2274 2285 if repo.dirstate.normalize(f) in repo.dirstate:
2275 2286 continue
2276 2287 ui.warn(_('not removing %s: '
2277 2288 'file is already untracked\n')
2278 2289 % match.rel(f))
2279 2290 bad.append(f)
2280 2291
2281 2292 for f in forget:
2282 2293 if ui.verbose or not match.exact(f):
2283 2294 ui.status(_('removing %s\n') % match.rel(f))
2284 2295
2285 2296 rejected = wctx.forget(forget, prefix)
2286 2297 bad.extend(f for f in rejected if f in match.files())
2287 2298 forgot.extend(f for f in forget if f not in rejected)
2288 2299 return bad, forgot
2289 2300
2290 2301 def files(ui, ctx, m, fm, fmt, subrepos):
2291 2302 rev = ctx.rev()
2292 2303 ret = 1
2293 2304 ds = ctx.repo().dirstate
2294 2305
2295 2306 for f in ctx.matches(m):
2296 2307 if rev is None and ds[f] == 'r':
2297 2308 continue
2298 2309 fm.startitem()
2299 2310 if ui.verbose:
2300 2311 fc = ctx[f]
2301 2312 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2302 2313 fm.data(abspath=f)
2303 2314 fm.write('path', fmt, m.rel(f))
2304 2315 ret = 0
2305 2316
2306 2317 for subpath in sorted(ctx.substate):
2307 2318 def matchessubrepo(subpath):
2308 2319 return (m.always() or m.exact(subpath)
2309 2320 or any(f.startswith(subpath + '/') for f in m.files()))
2310 2321
2311 2322 if subrepos or matchessubrepo(subpath):
2312 2323 sub = ctx.sub(subpath)
2313 2324 try:
2314 2325 submatch = matchmod.narrowmatcher(subpath, m)
2315 2326 if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
2316 2327 ret = 0
2317 2328 except error.LookupError:
2318 2329 ui.status(_("skipping missing subrepository: %s\n")
2319 2330 % m.abs(subpath))
2320 2331
2321 2332 return ret
2322 2333
2323 2334 def remove(ui, repo, m, prefix, after, force, subrepos):
2324 2335 join = lambda f: os.path.join(prefix, f)
2325 2336 ret = 0
2326 2337 s = repo.status(match=m, clean=True)
2327 2338 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2328 2339
2329 2340 wctx = repo[None]
2330 2341
2331 2342 for subpath in sorted(wctx.substate):
2332 2343 def matchessubrepo(matcher, subpath):
2333 2344 if matcher.exact(subpath):
2334 2345 return True
2335 2346 for f in matcher.files():
2336 2347 if f.startswith(subpath):
2337 2348 return True
2338 2349 return False
2339 2350
2340 2351 if subrepos or matchessubrepo(m, subpath):
2341 2352 sub = wctx.sub(subpath)
2342 2353 try:
2343 2354 submatch = matchmod.narrowmatcher(subpath, m)
2344 2355 if sub.removefiles(submatch, prefix, after, force, subrepos):
2345 2356 ret = 1
2346 2357 except error.LookupError:
2347 2358 ui.status(_("skipping missing subrepository: %s\n")
2348 2359 % join(subpath))
2349 2360
2350 2361 # warn about failure to delete explicit files/dirs
2351 2362 deleteddirs = util.dirs(deleted)
2352 2363 for f in m.files():
2353 2364 def insubrepo():
2354 2365 for subpath in wctx.substate:
2355 2366 if f.startswith(subpath):
2356 2367 return True
2357 2368 return False
2358 2369
2359 2370 isdir = f in deleteddirs or wctx.hasdir(f)
2360 2371 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2361 2372 continue
2362 2373
2363 2374 if repo.wvfs.exists(f):
2364 2375 if repo.wvfs.isdir(f):
2365 2376 ui.warn(_('not removing %s: no tracked files\n')
2366 2377 % m.rel(f))
2367 2378 else:
2368 2379 ui.warn(_('not removing %s: file is untracked\n')
2369 2380 % m.rel(f))
2370 2381 # missing files will generate a warning elsewhere
2371 2382 ret = 1
2372 2383
2373 2384 if force:
2374 2385 list = modified + deleted + clean + added
2375 2386 elif after:
2376 2387 list = deleted
2377 2388 for f in modified + added + clean:
2378 2389 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2379 2390 ret = 1
2380 2391 else:
2381 2392 list = deleted + clean
2382 2393 for f in modified:
2383 2394 ui.warn(_('not removing %s: file is modified (use -f'
2384 2395 ' to force removal)\n') % m.rel(f))
2385 2396 ret = 1
2386 2397 for f in added:
2387 2398 ui.warn(_('not removing %s: file has been marked for add'
2388 2399 ' (use forget to undo)\n') % m.rel(f))
2389 2400 ret = 1
2390 2401
2391 2402 for f in sorted(list):
2392 2403 if ui.verbose or not m.exact(f):
2393 2404 ui.status(_('removing %s\n') % m.rel(f))
2394 2405
2395 2406 wlock = repo.wlock()
2396 2407 try:
2397 2408 if not after:
2398 2409 for f in list:
2399 2410 if f in added:
2400 2411 continue # we never unlink added files on remove
2401 2412 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2402 2413 repo[None].forget(list)
2403 2414 finally:
2404 2415 wlock.release()
2405 2416
2406 2417 return ret
2407 2418
2408 2419 def cat(ui, repo, ctx, matcher, prefix, **opts):
2409 2420 err = 1
2410 2421
2411 2422 def write(path):
2412 2423 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2413 2424 pathname=os.path.join(prefix, path))
2414 2425 data = ctx[path].data()
2415 2426 if opts.get('decode'):
2416 2427 data = repo.wwritedata(path, data)
2417 2428 fp.write(data)
2418 2429 fp.close()
2419 2430
2420 2431 # Automation often uses hg cat on single files, so special case it
2421 2432 # for performance to avoid the cost of parsing the manifest.
2422 2433 if len(matcher.files()) == 1 and not matcher.anypats():
2423 2434 file = matcher.files()[0]
2424 2435 mf = repo.manifest
2425 2436 mfnode = ctx.manifestnode()
2426 2437 if mfnode and mf.find(mfnode, file)[0]:
2427 2438 write(file)
2428 2439 return 0
2429 2440
2430 2441 # Don't warn about "missing" files that are really in subrepos
2431 2442 def badfn(path, msg):
2432 2443 for subpath in ctx.substate:
2433 2444 if path.startswith(subpath):
2434 2445 return
2435 2446 matcher.bad(path, msg)
2436 2447
2437 2448 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2438 2449 write(abs)
2439 2450 err = 0
2440 2451
2441 2452 for subpath in sorted(ctx.substate):
2442 2453 sub = ctx.sub(subpath)
2443 2454 try:
2444 2455 submatch = matchmod.narrowmatcher(subpath, matcher)
2445 2456
2446 2457 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2447 2458 **opts):
2448 2459 err = 0
2449 2460 except error.RepoLookupError:
2450 2461 ui.status(_("skipping missing subrepository: %s\n")
2451 2462 % os.path.join(prefix, subpath))
2452 2463
2453 2464 return err
2454 2465
2455 2466 def commit(ui, repo, commitfunc, pats, opts):
2456 2467 '''commit the specified files or all outstanding changes'''
2457 2468 date = opts.get('date')
2458 2469 if date:
2459 2470 opts['date'] = util.parsedate(date)
2460 2471 message = logmessage(ui, opts)
2461 2472 matcher = scmutil.match(repo[None], pats, opts)
2462 2473
2463 2474 # extract addremove carefully -- this function can be called from a command
2464 2475 # that doesn't support addremove
2465 2476 if opts.get('addremove'):
2466 2477 if scmutil.addremove(repo, matcher, "", opts) != 0:
2467 2478 raise util.Abort(
2468 2479 _("failed to mark all new/missing files as added/removed"))
2469 2480
2470 2481 return commitfunc(ui, repo, message, matcher, opts)
2471 2482
2472 2483 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2473 2484 # avoid cycle context -> subrepo -> cmdutil
2474 2485 import context
2475 2486
2476 2487 # amend will reuse the existing user if not specified, but the obsolete
2477 2488 # marker creation requires that the current user's name is specified.
2478 2489 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2479 2490 ui.username() # raise exception if username not set
2480 2491
2481 2492 ui.note(_('amending changeset %s\n') % old)
2482 2493 base = old.p1()
2483 2494 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2484 2495
2485 2496 wlock = dsguard = lock = newid = None
2486 2497 try:
2487 2498 wlock = repo.wlock()
2488 2499 dsguard = dirstateguard(repo, 'amend')
2489 2500 lock = repo.lock()
2490 2501 tr = repo.transaction('amend')
2491 2502 try:
2492 2503 # See if we got a message from -m or -l, if not, open the editor
2493 2504 # with the message of the changeset to amend
2494 2505 message = logmessage(ui, opts)
2495 2506 # ensure logfile does not conflict with later enforcement of the
2496 2507 # message. potential logfile content has been processed by
2497 2508 # `logmessage` anyway.
2498 2509 opts.pop('logfile')
2499 2510 # First, do a regular commit to record all changes in the working
2500 2511 # directory (if there are any)
2501 2512 ui.callhooks = False
2502 2513 activebookmark = repo._activebookmark
2503 2514 try:
2504 2515 repo._activebookmark = None
2505 2516 opts['message'] = 'temporary amend commit for %s' % old
2506 2517 node = commit(ui, repo, commitfunc, pats, opts)
2507 2518 finally:
2508 2519 repo._activebookmark = activebookmark
2509 2520 ui.callhooks = True
2510 2521 ctx = repo[node]
2511 2522
2512 2523 # Participating changesets:
2513 2524 #
2514 2525 # node/ctx o - new (intermediate) commit that contains changes
2515 2526 # | from working dir to go into amending commit
2516 2527 # | (or a workingctx if there were no changes)
2517 2528 # |
2518 2529 # old o - changeset to amend
2519 2530 # |
2520 2531 # base o - parent of amending changeset
2521 2532
2522 2533 # Update extra dict from amended commit (e.g. to preserve graft
2523 2534 # source)
2524 2535 extra.update(old.extra())
2525 2536
2526 2537 # Also update it from the intermediate commit or from the wctx
2527 2538 extra.update(ctx.extra())
2528 2539
2529 2540 if len(old.parents()) > 1:
2530 2541 # ctx.files() isn't reliable for merges, so fall back to the
2531 2542 # slower repo.status() method
2532 2543 files = set([fn for st in repo.status(base, old)[:3]
2533 2544 for fn in st])
2534 2545 else:
2535 2546 files = set(old.files())
2536 2547
2537 2548 # Second, we use either the commit we just did, or if there were no
2538 2549 # changes the parent of the working directory as the version of the
2539 2550 # files in the final amend commit
2540 2551 if node:
2541 2552 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2542 2553
2543 2554 user = ctx.user()
2544 2555 date = ctx.date()
2545 2556 # Recompute copies (avoid recording a -> b -> a)
2546 2557 copied = copies.pathcopies(base, ctx)
2547 2558 if old.p2:
2548 2559 copied.update(copies.pathcopies(old.p2(), ctx))
2549 2560
2550 2561 # Prune files which were reverted by the updates: if old
2551 2562 # introduced file X and our intermediate commit, node,
2552 2563 # renamed that file, then those two files are the same and
2553 2564 # we can discard X from our list of files. Likewise if X
2554 2565 # was deleted, it's no longer relevant
2555 2566 files.update(ctx.files())
2556 2567
2557 2568 def samefile(f):
2558 2569 if f in ctx.manifest():
2559 2570 a = ctx.filectx(f)
2560 2571 if f in base.manifest():
2561 2572 b = base.filectx(f)
2562 2573 return (not a.cmp(b)
2563 2574 and a.flags() == b.flags())
2564 2575 else:
2565 2576 return False
2566 2577 else:
2567 2578 return f not in base.manifest()
2568 2579 files = [f for f in files if not samefile(f)]
2569 2580
2570 2581 def filectxfn(repo, ctx_, path):
2571 2582 try:
2572 2583 fctx = ctx[path]
2573 2584 flags = fctx.flags()
2574 2585 mctx = context.memfilectx(repo,
2575 2586 fctx.path(), fctx.data(),
2576 2587 islink='l' in flags,
2577 2588 isexec='x' in flags,
2578 2589 copied=copied.get(path))
2579 2590 return mctx
2580 2591 except KeyError:
2581 2592 return None
2582 2593 else:
2583 2594 ui.note(_('copying changeset %s to %s\n') % (old, base))
2584 2595
2585 2596 # Use version of files as in the old cset
2586 2597 def filectxfn(repo, ctx_, path):
2587 2598 try:
2588 2599 return old.filectx(path)
2589 2600 except KeyError:
2590 2601 return None
2591 2602
2592 2603 user = opts.get('user') or old.user()
2593 2604 date = opts.get('date') or old.date()
2594 2605 editform = mergeeditform(old, 'commit.amend')
2595 2606 editor = getcommiteditor(editform=editform, **opts)
2596 2607 if not message:
2597 2608 editor = getcommiteditor(edit=True, editform=editform)
2598 2609 message = old.description()
2599 2610
2600 2611 pureextra = extra.copy()
2601 2612 extra['amend_source'] = old.hex()
2602 2613
2603 2614 new = context.memctx(repo,
2604 2615 parents=[base.node(), old.p2().node()],
2605 2616 text=message,
2606 2617 files=files,
2607 2618 filectxfn=filectxfn,
2608 2619 user=user,
2609 2620 date=date,
2610 2621 extra=extra,
2611 2622 editor=editor)
2612 2623
2613 2624 newdesc = changelog.stripdesc(new.description())
2614 2625 if ((not node)
2615 2626 and newdesc == old.description()
2616 2627 and user == old.user()
2617 2628 and date == old.date()
2618 2629 and pureextra == old.extra()):
2619 2630 # nothing changed. continuing here would create a new node
2620 2631 # anyway because of the amend_source noise.
2621 2632 #
2622 2633 # This not what we expect from amend.
2623 2634 return old.node()
2624 2635
2625 2636 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2626 2637 try:
2627 2638 if opts.get('secret'):
2628 2639 commitphase = 'secret'
2629 2640 else:
2630 2641 commitphase = old.phase()
2631 2642 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2632 2643 newid = repo.commitctx(new)
2633 2644 finally:
2634 2645 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2635 2646 if newid != old.node():
2636 2647 # Reroute the working copy parent to the new changeset
2637 2648 repo.setparents(newid, nullid)
2638 2649
2639 2650 # Move bookmarks from old parent to amend commit
2640 2651 bms = repo.nodebookmarks(old.node())
2641 2652 if bms:
2642 2653 marks = repo._bookmarks
2643 2654 for bm in bms:
2644 2655 ui.debug('moving bookmarks %r from %s to %s\n' %
2645 2656 (marks, old.hex(), hex(newid)))
2646 2657 marks[bm] = newid
2647 2658 marks.recordchange(tr)
2648 2659 #commit the whole amend process
2649 2660 if createmarkers:
2650 2661 # mark the new changeset as successor of the rewritten one
2651 2662 new = repo[newid]
2652 2663 obs = [(old, (new,))]
2653 2664 if node:
2654 2665 obs.append((ctx, ()))
2655 2666
2656 2667 obsolete.createmarkers(repo, obs)
2657 2668 tr.close()
2658 2669 finally:
2659 2670 tr.release()
2660 2671 dsguard.close()
2661 2672 if not createmarkers and newid != old.node():
2662 2673 # Strip the intermediate commit (if there was one) and the amended
2663 2674 # commit
2664 2675 if node:
2665 2676 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2666 2677 ui.note(_('stripping amended changeset %s\n') % old)
2667 2678 repair.strip(ui, repo, old.node(), topic='amend-backup')
2668 2679 finally:
2669 2680 lockmod.release(lock, dsguard, wlock)
2670 2681 return newid
2671 2682
2672 2683 def commiteditor(repo, ctx, subs, editform=''):
2673 2684 if ctx.description():
2674 2685 return ctx.description()
2675 2686 return commitforceeditor(repo, ctx, subs, editform=editform)
2676 2687
2677 2688 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2678 2689 editform=''):
2679 2690 if not extramsg:
2680 2691 extramsg = _("Leave message empty to abort commit.")
2681 2692
2682 2693 forms = [e for e in editform.split('.') if e]
2683 2694 forms.insert(0, 'changeset')
2684 2695 while forms:
2685 2696 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2686 2697 if tmpl:
2687 2698 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2688 2699 break
2689 2700 forms.pop()
2690 2701 else:
2691 2702 committext = buildcommittext(repo, ctx, subs, extramsg)
2692 2703
2693 2704 # run editor in the repository root
2694 2705 olddir = os.getcwd()
2695 2706 os.chdir(repo.root)
2696 2707 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2697 2708 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2698 2709 os.chdir(olddir)
2699 2710
2700 2711 if finishdesc:
2701 2712 text = finishdesc(text)
2702 2713 if not text.strip():
2703 2714 raise util.Abort(_("empty commit message"))
2704 2715
2705 2716 return text
2706 2717
2707 2718 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2708 2719 ui = repo.ui
2709 2720 tmpl, mapfile = gettemplate(ui, tmpl, None)
2710 2721
2711 2722 try:
2712 2723 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2713 2724 except SyntaxError as inst:
2714 2725 raise util.Abort(inst.args[0])
2715 2726
2716 2727 for k, v in repo.ui.configitems('committemplate'):
2717 2728 if k != 'changeset':
2718 2729 t.t.cache[k] = v
2719 2730
2720 2731 if not extramsg:
2721 2732 extramsg = '' # ensure that extramsg is string
2722 2733
2723 2734 ui.pushbuffer()
2724 2735 t.show(ctx, extramsg=extramsg)
2725 2736 return ui.popbuffer()
2726 2737
2727 2738 def buildcommittext(repo, ctx, subs, extramsg):
2728 2739 edittext = []
2729 2740 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2730 2741 if ctx.description():
2731 2742 edittext.append(ctx.description())
2732 2743 edittext.append("")
2733 2744 edittext.append("") # Empty line between message and comments.
2734 2745 edittext.append(_("HG: Enter commit message."
2735 2746 " Lines beginning with 'HG:' are removed."))
2736 2747 edittext.append("HG: %s" % extramsg)
2737 2748 edittext.append("HG: --")
2738 2749 edittext.append(_("HG: user: %s") % ctx.user())
2739 2750 if ctx.p2():
2740 2751 edittext.append(_("HG: branch merge"))
2741 2752 if ctx.branch():
2742 2753 edittext.append(_("HG: branch '%s'") % ctx.branch())
2743 2754 if bookmarks.isactivewdirparent(repo):
2744 2755 edittext.append(_("HG: bookmark '%s'") % repo._activebookmark)
2745 2756 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2746 2757 edittext.extend([_("HG: added %s") % f for f in added])
2747 2758 edittext.extend([_("HG: changed %s") % f for f in modified])
2748 2759 edittext.extend([_("HG: removed %s") % f for f in removed])
2749 2760 if not added and not modified and not removed:
2750 2761 edittext.append(_("HG: no files changed"))
2751 2762 edittext.append("")
2752 2763
2753 2764 return "\n".join(edittext)
2754 2765
2755 2766 def commitstatus(repo, node, branch, bheads=None, opts={}):
2756 2767 ctx = repo[node]
2757 2768 parents = ctx.parents()
2758 2769
2759 2770 if (not opts.get('amend') and bheads and node not in bheads and not
2760 2771 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2761 2772 repo.ui.status(_('created new head\n'))
2762 2773 # The message is not printed for initial roots. For the other
2763 2774 # changesets, it is printed in the following situations:
2764 2775 #
2765 2776 # Par column: for the 2 parents with ...
2766 2777 # N: null or no parent
2767 2778 # B: parent is on another named branch
2768 2779 # C: parent is a regular non head changeset
2769 2780 # H: parent was a branch head of the current branch
2770 2781 # Msg column: whether we print "created new head" message
2771 2782 # In the following, it is assumed that there already exists some
2772 2783 # initial branch heads of the current branch, otherwise nothing is
2773 2784 # printed anyway.
2774 2785 #
2775 2786 # Par Msg Comment
2776 2787 # N N y additional topo root
2777 2788 #
2778 2789 # B N y additional branch root
2779 2790 # C N y additional topo head
2780 2791 # H N n usual case
2781 2792 #
2782 2793 # B B y weird additional branch root
2783 2794 # C B y branch merge
2784 2795 # H B n merge with named branch
2785 2796 #
2786 2797 # C C y additional head from merge
2787 2798 # C H n merge with a head
2788 2799 #
2789 2800 # H H n head merge: head count decreases
2790 2801
2791 2802 if not opts.get('close_branch'):
2792 2803 for r in parents:
2793 2804 if r.closesbranch() and r.branch() == branch:
2794 2805 repo.ui.status(_('reopening closed branch head %d\n') % r)
2795 2806
2796 2807 if repo.ui.debugflag:
2797 2808 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2798 2809 elif repo.ui.verbose:
2799 2810 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2800 2811
2801 2812 def revert(ui, repo, ctx, parents, *pats, **opts):
2802 2813 parent, p2 = parents
2803 2814 node = ctx.node()
2804 2815
2805 2816 mf = ctx.manifest()
2806 2817 if node == p2:
2807 2818 parent = p2
2808 2819 if node == parent:
2809 2820 pmf = mf
2810 2821 else:
2811 2822 pmf = None
2812 2823
2813 2824 # need all matching names in dirstate and manifest of target rev,
2814 2825 # so have to walk both. do not print errors if files exist in one
2815 2826 # but not other. in both cases, filesets should be evaluated against
2816 2827 # workingctx to get consistent result (issue4497). this means 'set:**'
2817 2828 # cannot be used to select missing files from target rev.
2818 2829
2819 2830 # `names` is a mapping for all elements in working copy and target revision
2820 2831 # The mapping is in the form:
2821 2832 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2822 2833 names = {}
2823 2834
2824 2835 wlock = repo.wlock()
2825 2836 try:
2826 2837 ## filling of the `names` mapping
2827 2838 # walk dirstate to fill `names`
2828 2839
2829 2840 interactive = opts.get('interactive', False)
2830 2841 wctx = repo[None]
2831 2842 m = scmutil.match(wctx, pats, opts)
2832 2843
2833 2844 # we'll need this later
2834 2845 targetsubs = sorted(s for s in wctx.substate if m(s))
2835 2846
2836 2847 if not m.always():
2837 2848 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2838 2849 names[abs] = m.rel(abs), m.exact(abs)
2839 2850
2840 2851 # walk target manifest to fill `names`
2841 2852
2842 2853 def badfn(path, msg):
2843 2854 if path in names:
2844 2855 return
2845 2856 if path in ctx.substate:
2846 2857 return
2847 2858 path_ = path + '/'
2848 2859 for f in names:
2849 2860 if f.startswith(path_):
2850 2861 return
2851 2862 ui.warn("%s: %s\n" % (m.rel(path), msg))
2852 2863
2853 2864 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2854 2865 if abs not in names:
2855 2866 names[abs] = m.rel(abs), m.exact(abs)
2856 2867
2857 2868 # Find status of all file in `names`.
2858 2869 m = scmutil.matchfiles(repo, names)
2859 2870
2860 2871 changes = repo.status(node1=node, match=m,
2861 2872 unknown=True, ignored=True, clean=True)
2862 2873 else:
2863 2874 changes = repo.status(node1=node, match=m)
2864 2875 for kind in changes:
2865 2876 for abs in kind:
2866 2877 names[abs] = m.rel(abs), m.exact(abs)
2867 2878
2868 2879 m = scmutil.matchfiles(repo, names)
2869 2880
2870 2881 modified = set(changes.modified)
2871 2882 added = set(changes.added)
2872 2883 removed = set(changes.removed)
2873 2884 _deleted = set(changes.deleted)
2874 2885 unknown = set(changes.unknown)
2875 2886 unknown.update(changes.ignored)
2876 2887 clean = set(changes.clean)
2877 2888 modadded = set()
2878 2889
2879 2890 # split between files known in target manifest and the others
2880 2891 smf = set(mf)
2881 2892
2882 2893 # determine the exact nature of the deleted changesets
2883 2894 deladded = _deleted - smf
2884 2895 deleted = _deleted - deladded
2885 2896
2886 2897 # We need to account for the state of the file in the dirstate,
2887 2898 # even when we revert against something else than parent. This will
2888 2899 # slightly alter the behavior of revert (doing back up or not, delete
2889 2900 # or just forget etc).
2890 2901 if parent == node:
2891 2902 dsmodified = modified
2892 2903 dsadded = added
2893 2904 dsremoved = removed
2894 2905 # store all local modifications, useful later for rename detection
2895 2906 localchanges = dsmodified | dsadded
2896 2907 modified, added, removed = set(), set(), set()
2897 2908 else:
2898 2909 changes = repo.status(node1=parent, match=m)
2899 2910 dsmodified = set(changes.modified)
2900 2911 dsadded = set(changes.added)
2901 2912 dsremoved = set(changes.removed)
2902 2913 # store all local modifications, useful later for rename detection
2903 2914 localchanges = dsmodified | dsadded
2904 2915
2905 2916 # only take into account for removes between wc and target
2906 2917 clean |= dsremoved - removed
2907 2918 dsremoved &= removed
2908 2919 # distinct between dirstate remove and other
2909 2920 removed -= dsremoved
2910 2921
2911 2922 modadded = added & dsmodified
2912 2923 added -= modadded
2913 2924
2914 2925 # tell newly modified apart.
2915 2926 dsmodified &= modified
2916 2927 dsmodified |= modified & dsadded # dirstate added may needs backup
2917 2928 modified -= dsmodified
2918 2929
2919 2930 # We need to wait for some post-processing to update this set
2920 2931 # before making the distinction. The dirstate will be used for
2921 2932 # that purpose.
2922 2933 dsadded = added
2923 2934
2924 2935 # in case of merge, files that are actually added can be reported as
2925 2936 # modified, we need to post process the result
2926 2937 if p2 != nullid:
2927 2938 if pmf is None:
2928 2939 # only need parent manifest in the merge case,
2929 2940 # so do not read by default
2930 2941 pmf = repo[parent].manifest()
2931 2942 mergeadd = dsmodified - set(pmf)
2932 2943 dsadded |= mergeadd
2933 2944 dsmodified -= mergeadd
2934 2945
2935 2946 # if f is a rename, update `names` to also revert the source
2936 2947 cwd = repo.getcwd()
2937 2948 for f in localchanges:
2938 2949 src = repo.dirstate.copied(f)
2939 2950 # XXX should we check for rename down to target node?
2940 2951 if src and src not in names and repo.dirstate[src] == 'r':
2941 2952 dsremoved.add(src)
2942 2953 names[src] = (repo.pathto(src, cwd), True)
2943 2954
2944 2955 # distinguish between file to forget and the other
2945 2956 added = set()
2946 2957 for abs in dsadded:
2947 2958 if repo.dirstate[abs] != 'a':
2948 2959 added.add(abs)
2949 2960 dsadded -= added
2950 2961
2951 2962 for abs in deladded:
2952 2963 if repo.dirstate[abs] == 'a':
2953 2964 dsadded.add(abs)
2954 2965 deladded -= dsadded
2955 2966
2956 2967 # For files marked as removed, we check if an unknown file is present at
2957 2968 # the same path. If a such file exists it may need to be backed up.
2958 2969 # Making the distinction at this stage helps have simpler backup
2959 2970 # logic.
2960 2971 removunk = set()
2961 2972 for abs in removed:
2962 2973 target = repo.wjoin(abs)
2963 2974 if os.path.lexists(target):
2964 2975 removunk.add(abs)
2965 2976 removed -= removunk
2966 2977
2967 2978 dsremovunk = set()
2968 2979 for abs in dsremoved:
2969 2980 target = repo.wjoin(abs)
2970 2981 if os.path.lexists(target):
2971 2982 dsremovunk.add(abs)
2972 2983 dsremoved -= dsremovunk
2973 2984
2974 2985 # action to be actually performed by revert
2975 2986 # (<list of file>, message>) tuple
2976 2987 actions = {'revert': ([], _('reverting %s\n')),
2977 2988 'add': ([], _('adding %s\n')),
2978 2989 'remove': ([], _('removing %s\n')),
2979 2990 'drop': ([], _('removing %s\n')),
2980 2991 'forget': ([], _('forgetting %s\n')),
2981 2992 'undelete': ([], _('undeleting %s\n')),
2982 2993 'noop': (None, _('no changes needed to %s\n')),
2983 2994 'unknown': (None, _('file not managed: %s\n')),
2984 2995 }
2985 2996
2986 2997 # "constant" that convey the backup strategy.
2987 2998 # All set to `discard` if `no-backup` is set do avoid checking
2988 2999 # no_backup lower in the code.
2989 3000 # These values are ordered for comparison purposes
2990 3001 backup = 2 # unconditionally do backup
2991 3002 check = 1 # check if the existing file differs from target
2992 3003 discard = 0 # never do backup
2993 3004 if opts.get('no_backup'):
2994 3005 backup = check = discard
2995 3006
2996 3007 backupanddel = actions['remove']
2997 3008 if not opts.get('no_backup'):
2998 3009 backupanddel = actions['drop']
2999 3010
3000 3011 disptable = (
3001 3012 # dispatch table:
3002 3013 # file state
3003 3014 # action
3004 3015 # make backup
3005 3016
3006 3017 ## Sets that results that will change file on disk
3007 3018 # Modified compared to target, no local change
3008 3019 (modified, actions['revert'], discard),
3009 3020 # Modified compared to target, but local file is deleted
3010 3021 (deleted, actions['revert'], discard),
3011 3022 # Modified compared to target, local change
3012 3023 (dsmodified, actions['revert'], backup),
3013 3024 # Added since target
3014 3025 (added, actions['remove'], discard),
3015 3026 # Added in working directory
3016 3027 (dsadded, actions['forget'], discard),
3017 3028 # Added since target, have local modification
3018 3029 (modadded, backupanddel, backup),
3019 3030 # Added since target but file is missing in working directory
3020 3031 (deladded, actions['drop'], discard),
3021 3032 # Removed since target, before working copy parent
3022 3033 (removed, actions['add'], discard),
3023 3034 # Same as `removed` but an unknown file exists at the same path
3024 3035 (removunk, actions['add'], check),
3025 3036 # Removed since targe, marked as such in working copy parent
3026 3037 (dsremoved, actions['undelete'], discard),
3027 3038 # Same as `dsremoved` but an unknown file exists at the same path
3028 3039 (dsremovunk, actions['undelete'], check),
3029 3040 ## the following sets does not result in any file changes
3030 3041 # File with no modification
3031 3042 (clean, actions['noop'], discard),
3032 3043 # Existing file, not tracked anywhere
3033 3044 (unknown, actions['unknown'], discard),
3034 3045 )
3035 3046
3036 3047 for abs, (rel, exact) in sorted(names.items()):
3037 3048 # target file to be touch on disk (relative to cwd)
3038 3049 target = repo.wjoin(abs)
3039 3050 # search the entry in the dispatch table.
3040 3051 # if the file is in any of these sets, it was touched in the working
3041 3052 # directory parent and we are sure it needs to be reverted.
3042 3053 for table, (xlist, msg), dobackup in disptable:
3043 3054 if abs not in table:
3044 3055 continue
3045 3056 if xlist is not None:
3046 3057 xlist.append(abs)
3047 3058 if dobackup and (backup <= dobackup
3048 3059 or wctx[abs].cmp(ctx[abs])):
3049 3060 bakname = "%s.orig" % rel
3050 3061 ui.note(_('saving current version of %s as %s\n') %
3051 3062 (rel, bakname))
3052 3063 if not opts.get('dry_run'):
3053 3064 if interactive:
3054 3065 util.copyfile(target, bakname)
3055 3066 else:
3056 3067 util.rename(target, bakname)
3057 3068 if ui.verbose or not exact:
3058 3069 if not isinstance(msg, basestring):
3059 3070 msg = msg(abs)
3060 3071 ui.status(msg % rel)
3061 3072 elif exact:
3062 3073 ui.warn(msg % rel)
3063 3074 break
3064 3075
3065 3076 if not opts.get('dry_run'):
3066 3077 needdata = ('revert', 'add', 'undelete')
3067 3078 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3068 3079 _performrevert(repo, parents, ctx, actions, interactive)
3069 3080
3070 3081 if targetsubs:
3071 3082 # Revert the subrepos on the revert list
3072 3083 for sub in targetsubs:
3073 3084 try:
3074 3085 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3075 3086 except KeyError:
3076 3087 raise util.Abort("subrepository '%s' does not exist in %s!"
3077 3088 % (sub, short(ctx.node())))
3078 3089 finally:
3079 3090 wlock.release()
3080 3091
3081 3092 def _revertprefetch(repo, ctx, *files):
3082 3093 """Let extension changing the storage layer prefetch content"""
3083 3094 pass
3084 3095
3085 3096 def _performrevert(repo, parents, ctx, actions, interactive=False):
3086 3097 """function that actually perform all the actions computed for revert
3087 3098
3088 3099 This is an independent function to let extension to plug in and react to
3089 3100 the imminent revert.
3090 3101
3091 3102 Make sure you have the working directory locked when calling this function.
3092 3103 """
3093 3104 parent, p2 = parents
3094 3105 node = ctx.node()
3095 3106 def checkout(f):
3096 3107 fc = ctx[f]
3097 3108 repo.wwrite(f, fc.data(), fc.flags())
3098 3109
3099 3110 audit_path = pathutil.pathauditor(repo.root)
3100 3111 for f in actions['forget'][0]:
3101 3112 repo.dirstate.drop(f)
3102 3113 for f in actions['remove'][0]:
3103 3114 audit_path(f)
3104 3115 try:
3105 3116 util.unlinkpath(repo.wjoin(f))
3106 3117 except OSError:
3107 3118 pass
3108 3119 repo.dirstate.remove(f)
3109 3120 for f in actions['drop'][0]:
3110 3121 audit_path(f)
3111 3122 repo.dirstate.remove(f)
3112 3123
3113 3124 normal = None
3114 3125 if node == parent:
3115 3126 # We're reverting to our parent. If possible, we'd like status
3116 3127 # to report the file as clean. We have to use normallookup for
3117 3128 # merges to avoid losing information about merged/dirty files.
3118 3129 if p2 != nullid:
3119 3130 normal = repo.dirstate.normallookup
3120 3131 else:
3121 3132 normal = repo.dirstate.normal
3122 3133
3123 3134 newlyaddedandmodifiedfiles = set()
3124 3135 if interactive:
3125 3136 # Prompt the user for changes to revert
3126 3137 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3127 3138 m = scmutil.match(ctx, torevert, {})
3128 3139 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3129 3140 diffopts.nodates = True
3130 3141 diffopts.git = True
3131 3142 reversehunks = repo.ui.configbool('experimental',
3132 3143 'revertalternateinteractivemode',
3133 3144 True)
3134 3145 if reversehunks:
3135 3146 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3136 3147 else:
3137 3148 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3138 3149 originalchunks = patch.parsepatch(diff)
3139 3150
3140 3151 try:
3141 3152
3142 3153 chunks = recordfilter(repo.ui, originalchunks)
3143 3154 if reversehunks:
3144 3155 chunks = patch.reversehunks(chunks)
3145 3156
3146 3157 except patch.PatchError as err:
3147 3158 raise util.Abort(_('error parsing patch: %s') % err)
3148 3159
3149 3160 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3150 3161 # Apply changes
3151 3162 fp = cStringIO.StringIO()
3152 3163 for c in chunks:
3153 3164 c.write(fp)
3154 3165 dopatch = fp.tell()
3155 3166 fp.seek(0)
3156 3167 if dopatch:
3157 3168 try:
3158 3169 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3159 3170 except patch.PatchError as err:
3160 3171 raise util.Abort(str(err))
3161 3172 del fp
3162 3173 else:
3163 3174 for f in actions['revert'][0]:
3164 3175 checkout(f)
3165 3176 if normal:
3166 3177 normal(f)
3167 3178
3168 3179 for f in actions['add'][0]:
3169 3180 # Don't checkout modified files, they are already created by the diff
3170 3181 if f not in newlyaddedandmodifiedfiles:
3171 3182 checkout(f)
3172 3183 repo.dirstate.add(f)
3173 3184
3174 3185 normal = repo.dirstate.normallookup
3175 3186 if node == parent and p2 == nullid:
3176 3187 normal = repo.dirstate.normal
3177 3188 for f in actions['undelete'][0]:
3178 3189 checkout(f)
3179 3190 normal(f)
3180 3191
3181 3192 copied = copies.pathcopies(repo[parent], ctx)
3182 3193
3183 3194 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3184 3195 if f in copied:
3185 3196 repo.dirstate.copy(copied[f], f)
3186 3197
3187 3198 def command(table):
3188 3199 """Returns a function object to be used as a decorator for making commands.
3189 3200
3190 3201 This function receives a command table as its argument. The table should
3191 3202 be a dict.
3192 3203
3193 3204 The returned function can be used as a decorator for adding commands
3194 3205 to that command table. This function accepts multiple arguments to define
3195 3206 a command.
3196 3207
3197 3208 The first argument is the command name.
3198 3209
3199 3210 The options argument is an iterable of tuples defining command arguments.
3200 3211 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3201 3212
3202 3213 The synopsis argument defines a short, one line summary of how to use the
3203 3214 command. This shows up in the help output.
3204 3215
3205 3216 The norepo argument defines whether the command does not require a
3206 3217 local repository. Most commands operate against a repository, thus the
3207 3218 default is False.
3208 3219
3209 3220 The optionalrepo argument defines whether the command optionally requires
3210 3221 a local repository.
3211 3222
3212 3223 The inferrepo argument defines whether to try to find a repository from the
3213 3224 command line arguments. If True, arguments will be examined for potential
3214 3225 repository locations. See ``findrepo()``. If a repository is found, it
3215 3226 will be used.
3216 3227 """
3217 3228 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3218 3229 inferrepo=False):
3219 3230 def decorator(func):
3220 3231 if synopsis:
3221 3232 table[name] = func, list(options), synopsis
3222 3233 else:
3223 3234 table[name] = func, list(options)
3224 3235
3225 3236 if norepo:
3226 3237 # Avoid import cycle.
3227 3238 import commands
3228 3239 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3229 3240
3230 3241 if optionalrepo:
3231 3242 import commands
3232 3243 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3233 3244
3234 3245 if inferrepo:
3235 3246 import commands
3236 3247 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3237 3248
3238 3249 return func
3239 3250 return decorator
3240 3251
3241 3252 return cmd
3242 3253
3243 3254 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3244 3255 # commands.outgoing. "missing" is "missing" of the result of
3245 3256 # "findcommonoutgoing()"
3246 3257 outgoinghooks = util.hooks()
3247 3258
3248 3259 # a list of (ui, repo) functions called by commands.summary
3249 3260 summaryhooks = util.hooks()
3250 3261
3251 3262 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3252 3263 #
3253 3264 # functions should return tuple of booleans below, if 'changes' is None:
3254 3265 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3255 3266 #
3256 3267 # otherwise, 'changes' is a tuple of tuples below:
3257 3268 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3258 3269 # - (desturl, destbranch, destpeer, outgoing)
3259 3270 summaryremotehooks = util.hooks()
3260 3271
3261 3272 # A list of state files kept by multistep operations like graft.
3262 3273 # Since graft cannot be aborted, it is considered 'clearable' by update.
3263 3274 # note: bisect is intentionally excluded
3264 3275 # (state file, clearable, allowcommit, error, hint)
3265 3276 unfinishedstates = [
3266 3277 ('graftstate', True, False, _('graft in progress'),
3267 3278 _("use 'hg graft --continue' or 'hg update' to abort")),
3268 3279 ('updatestate', True, False, _('last update was interrupted'),
3269 3280 _("use 'hg update' to get a consistent checkout"))
3270 3281 ]
3271 3282
3272 3283 def checkunfinished(repo, commit=False):
3273 3284 '''Look for an unfinished multistep operation, like graft, and abort
3274 3285 if found. It's probably good to check this right before
3275 3286 bailifchanged().
3276 3287 '''
3277 3288 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3278 3289 if commit and allowcommit:
3279 3290 continue
3280 3291 if repo.vfs.exists(f):
3281 3292 raise util.Abort(msg, hint=hint)
3282 3293
3283 3294 def clearunfinished(repo):
3284 3295 '''Check for unfinished operations (as above), and clear the ones
3285 3296 that are clearable.
3286 3297 '''
3287 3298 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3288 3299 if not clearable and repo.vfs.exists(f):
3289 3300 raise util.Abort(msg, hint=hint)
3290 3301 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3291 3302 if clearable and repo.vfs.exists(f):
3292 3303 util.unlink(repo.join(f))
3293 3304
3294 3305 class dirstateguard(object):
3295 3306 '''Restore dirstate at unexpected failure.
3296 3307
3297 3308 At the construction, this class does:
3298 3309
3299 3310 - write current ``repo.dirstate`` out, and
3300 3311 - save ``.hg/dirstate`` into the backup file
3301 3312
3302 3313 This restores ``.hg/dirstate`` from backup file, if ``release()``
3303 3314 is invoked before ``close()``.
3304 3315
3305 3316 This just removes the backup file at ``close()`` before ``release()``.
3306 3317 '''
3307 3318
3308 3319 def __init__(self, repo, name):
3309 3320 repo.dirstate.write()
3310 3321 self._repo = repo
3311 3322 self._filename = 'dirstate.backup.%s.%d' % (name, id(self))
3312 3323 repo.vfs.write(self._filename, repo.vfs.tryread('dirstate'))
3313 3324 self._active = True
3314 3325 self._closed = False
3315 3326
3316 3327 def __del__(self):
3317 3328 if self._active: # still active
3318 3329 # this may occur, even if this class is used correctly:
3319 3330 # for example, releasing other resources like transaction
3320 3331 # may raise exception before ``dirstateguard.release`` in
3321 3332 # ``release(tr, ....)``.
3322 3333 self._abort()
3323 3334
3324 3335 def close(self):
3325 3336 if not self._active: # already inactivated
3326 3337 msg = (_("can't close already inactivated backup: %s")
3327 3338 % self._filename)
3328 3339 raise util.Abort(msg)
3329 3340
3330 3341 self._repo.vfs.unlink(self._filename)
3331 3342 self._active = False
3332 3343 self._closed = True
3333 3344
3334 3345 def _abort(self):
3335 3346 # this "invalidate()" prevents "wlock.release()" from writing
3336 3347 # changes of dirstate out after restoring to original status
3337 3348 self._repo.dirstate.invalidate()
3338 3349
3339 3350 self._repo.vfs.rename(self._filename, 'dirstate')
3340 3351 self._active = False
3341 3352
3342 3353 def release(self):
3343 3354 if not self._closed:
3344 3355 if not self._active: # already inactivated
3345 3356 msg = (_("can't release already inactivated backup: %s")
3346 3357 % self._filename)
3347 3358 raise util.Abort(msg)
3348 3359 self._abort()
@@ -1,20 +1,20 b''
1 header = '<?xml version="1.0"?>\n<log>\n'
2 footer = '</log>\n'
1 docheader = '<?xml version="1.0"?>\n<log>\n'
2 docfooter = '</log>\n'
3 3
4 4 changeset = '<logentry revision="{rev}" node="{node}">\n{branches}{bookmarks}{tags}{parents}<author email="{author|email|xmlescape}">{author|person|xmlescape}</author>\n<date>{date|rfc3339date}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n</logentry>\n'
5 5 changeset_verbose = '<logentry revision="{rev}" node="{node}">\n{branches}{bookmarks}{tags}{parents}<author email="{author|email|xmlescape}">{author|person|xmlescape}</author>\n<date>{date|rfc3339date}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n<paths>\n{file_adds}{file_dels}{file_mods}</paths>\n{file_copies}</logentry>\n'
6 6 changeset_debug = '<logentry revision="{rev}" node="{node}">\n{branches}{bookmarks}{tags}{parents}<author email="{author|email|xmlescape}">{author|person|xmlescape}</author>\n<date>{date|rfc3339date}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n<paths>\n{file_adds}{file_dels}{file_mods}</paths>\n{file_copies}{extras}</logentry>\n'
7 7
8 8 file_add = '<path action="A">{file_add|xmlescape}</path>\n'
9 9 file_mod = '<path action="M">{file_mod|xmlescape}</path>\n'
10 10 file_del = '<path action="R">{file_del|xmlescape}</path>\n'
11 11
12 12 start_file_copies = '<copies>\n'
13 13 file_copy = '<copy source="{source|xmlescape}">{name|xmlescape}</copy>\n'
14 14 end_file_copies = '</copies>\n'
15 15
16 16 parent = '<parent revision="{rev}" node="{node}" />\n'
17 17 branch = '<branch>{branch|xmlescape}</branch>\n'
18 18 tag = '<tag>{tag|xmlescape}</tag>\n'
19 19 bookmark = '<bookmark>{bookmark|xmlescape}</bookmark>\n'
20 20 extra = '<extra key="{key|xmlescape}">{value|xmlescape}</extra>\n'
@@ -1,3428 +1,3433 b''
1 1 $ hg init a
2 2 $ cd a
3 3 $ echo a > a
4 4 $ hg add a
5 5 $ echo line 1 > b
6 6 $ echo line 2 >> b
7 7 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
8 8
9 9 $ hg add b
10 10 $ echo other 1 > c
11 11 $ echo other 2 >> c
12 12 $ echo >> c
13 13 $ echo other 3 >> c
14 14 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
15 15
16 16 $ hg add c
17 17 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
18 18 $ echo c >> c
19 19 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
20 20
21 21 $ echo foo > .hg/branch
22 22 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
23 23
24 24 $ hg co -q 3
25 25 $ echo other 4 >> d
26 26 $ hg add d
27 27 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
28 28
29 29 $ hg merge -q foo
30 30 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
31 31
32 32 Second branch starting at nullrev:
33 33
34 34 $ hg update null
35 35 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
36 36 $ echo second > second
37 37 $ hg add second
38 38 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
39 39 created new head
40 40
41 41 $ echo third > third
42 42 $ hg add third
43 43 $ hg mv second fourth
44 44 $ hg commit -m third -d "2020-01-01 10:01"
45 45
46 46 $ hg log --template '{join(file_copies, ",\n")}\n' -r .
47 47 fourth (second)
48 48 $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
49 49 second -> fourth
50 50 $ hg log -T '{rev} {ifcontains("fourth", file_copies, "t", "f")}\n' -r .:7
51 51 8 t
52 52 7 f
53 53
54 54 Working-directory revision has special identifiers, though they are still
55 55 experimental:
56 56
57 57 $ hg log -r 'wdir()' -T '{rev}:{node}\n'
58 58 2147483647:ffffffffffffffffffffffffffffffffffffffff
59 59
60 60 Some keywords are invalid for working-directory revision, but they should
61 61 never cause crash:
62 62
63 63 $ hg log -r 'wdir()' -T '{manifest}\n'
64 64
65 65
66 66 Quoting for ui.logtemplate
67 67
68 68 $ hg tip --config "ui.logtemplate={rev}\n"
69 69 8
70 70 $ hg tip --config "ui.logtemplate='{rev}\n'"
71 71 8
72 72 $ hg tip --config 'ui.logtemplate="{rev}\n"'
73 73 8
74 74
75 75 Make sure user/global hgrc does not affect tests
76 76
77 77 $ echo '[ui]' > .hg/hgrc
78 78 $ echo 'logtemplate =' >> .hg/hgrc
79 79 $ echo 'style =' >> .hg/hgrc
80 80
81 81 Add some simple styles to settings
82 82
83 83 $ echo '[templates]' >> .hg/hgrc
84 84 $ printf 'simple = "{rev}\\n"\n' >> .hg/hgrc
85 85 $ printf 'simple2 = {rev}\\n\n' >> .hg/hgrc
86 86
87 87 $ hg log -l1 -Tsimple
88 88 8
89 89 $ hg log -l1 -Tsimple2
90 90 8
91 91
92 92 Test templates and style maps in files:
93 93
94 94 $ echo "{rev}" > tmpl
95 95 $ hg log -l1 -T./tmpl
96 96 8
97 97 $ hg log -l1 -Tblah/blah
98 98 blah/blah (no-eol)
99 99
100 100 $ printf 'changeset = "{rev}\\n"\n' > map-simple
101 101 $ hg log -l1 -T./map-simple
102 102 8
103 103
104 104 Template should precede style option
105 105
106 106 $ hg log -l1 --style default -T '{rev}\n'
107 107 8
108 108
109 109 Add a commit with empty description, to ensure that the templates
110 110 below will omit the description line.
111 111
112 112 $ echo c >> c
113 113 $ hg add c
114 114 $ hg commit -qm ' '
115 115
116 116 Default style is like normal output. Phases style should be the same
117 117 as default style, except for extra phase lines.
118 118
119 119 $ hg log > log.out
120 120 $ hg log --style default > style.out
121 121 $ cmp log.out style.out || diff -u log.out style.out
122 122 $ hg log -T phases > phases.out
123 123 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
124 124 +phase: draft
125 125 +phase: draft
126 126 +phase: draft
127 127 +phase: draft
128 128 +phase: draft
129 129 +phase: draft
130 130 +phase: draft
131 131 +phase: draft
132 132 +phase: draft
133 133 +phase: draft
134 134
135 135 $ hg log -v > log.out
136 136 $ hg log -v --style default > style.out
137 137 $ cmp log.out style.out || diff -u log.out style.out
138 138 $ hg log -v -T phases > phases.out
139 139 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
140 140 +phase: draft
141 141 +phase: draft
142 142 +phase: draft
143 143 +phase: draft
144 144 +phase: draft
145 145 +phase: draft
146 146 +phase: draft
147 147 +phase: draft
148 148 +phase: draft
149 149 +phase: draft
150 150
151 151 $ hg log -q > log.out
152 152 $ hg log -q --style default > style.out
153 153 $ cmp log.out style.out || diff -u log.out style.out
154 154 $ hg log -q -T phases > phases.out
155 155 $ cmp log.out phases.out || diff -u log.out phases.out
156 156
157 157 $ hg log --debug > log.out
158 158 $ hg log --debug --style default > style.out
159 159 $ cmp log.out style.out || diff -u log.out style.out
160 160 $ hg log --debug -T phases > phases.out
161 161 $ cmp log.out phases.out || diff -u log.out phases.out
162 162
163 163 Default style of working-directory revision should also be the same (but
164 164 date may change while running tests):
165 165
166 166 $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
167 167 $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
168 168 $ cmp log.out style.out || diff -u log.out style.out
169 169
170 170 $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
171 171 $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
172 172 $ cmp log.out style.out || diff -u log.out style.out
173 173
174 174 $ hg log -r 'wdir()' -q > log.out
175 175 $ hg log -r 'wdir()' -q --style default > style.out
176 176 $ cmp log.out style.out || diff -u log.out style.out
177 177
178 178 $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
179 179 $ hg log -r 'wdir()' --debug --style default \
180 180 > | sed 's|^date:.*|date:|' > style.out
181 181 $ cmp log.out style.out || diff -u log.out style.out
182 182
183 183 Default style should also preserve color information (issue2866):
184 184
185 185 $ cp $HGRCPATH $HGRCPATH-bak
186 186 $ cat <<EOF >> $HGRCPATH
187 187 > [extensions]
188 188 > color=
189 189 > EOF
190 190
191 191 $ hg --color=debug log > log.out
192 192 $ hg --color=debug log --style default > style.out
193 193 $ cmp log.out style.out || diff -u log.out style.out
194 194 $ hg --color=debug log -T phases > phases.out
195 195 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
196 196 +[log.phase|phase: draft]
197 197 +[log.phase|phase: draft]
198 198 +[log.phase|phase: draft]
199 199 +[log.phase|phase: draft]
200 200 +[log.phase|phase: draft]
201 201 +[log.phase|phase: draft]
202 202 +[log.phase|phase: draft]
203 203 +[log.phase|phase: draft]
204 204 +[log.phase|phase: draft]
205 205 +[log.phase|phase: draft]
206 206
207 207 $ hg --color=debug -v log > log.out
208 208 $ hg --color=debug -v log --style default > style.out
209 209 $ cmp log.out style.out || diff -u log.out style.out
210 210 $ hg --color=debug -v log -T phases > phases.out
211 211 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
212 212 +[log.phase|phase: draft]
213 213 +[log.phase|phase: draft]
214 214 +[log.phase|phase: draft]
215 215 +[log.phase|phase: draft]
216 216 +[log.phase|phase: draft]
217 217 +[log.phase|phase: draft]
218 218 +[log.phase|phase: draft]
219 219 +[log.phase|phase: draft]
220 220 +[log.phase|phase: draft]
221 221 +[log.phase|phase: draft]
222 222
223 223 $ hg --color=debug -q log > log.out
224 224 $ hg --color=debug -q log --style default > style.out
225 225 $ cmp log.out style.out || diff -u log.out style.out
226 226 $ hg --color=debug -q log -T phases > phases.out
227 227 $ cmp log.out phases.out || diff -u log.out phases.out
228 228
229 229 $ hg --color=debug --debug log > log.out
230 230 $ hg --color=debug --debug log --style default > style.out
231 231 $ cmp log.out style.out || diff -u log.out style.out
232 232 $ hg --color=debug --debug log -T phases > phases.out
233 233 $ cmp log.out phases.out || diff -u log.out phases.out
234 234
235 235 $ mv $HGRCPATH-bak $HGRCPATH
236 236
237 237 Remove commit with empty commit message, so as to not pollute further
238 238 tests.
239 239
240 240 $ hg --config extensions.strip= strip -q .
241 241
242 242 Revision with no copies (used to print a traceback):
243 243
244 244 $ hg tip -v --template '\n'
245 245
246 246
247 247 Compact style works:
248 248
249 249 $ hg log -Tcompact
250 250 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
251 251 third
252 252
253 253 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
254 254 second
255 255
256 256 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
257 257 merge
258 258
259 259 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
260 260 new head
261 261
262 262 4 bbe44766e73d 1970-01-17 04:53 +0000 person
263 263 new branch
264 264
265 265 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
266 266 no user, no domain
267 267
268 268 2 97054abb4ab8 1970-01-14 21:20 +0000 other
269 269 no person
270 270
271 271 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
272 272 other 1
273 273
274 274 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
275 275 line 1
276 276
277 277
278 278 $ hg log -v --style compact
279 279 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
280 280 third
281 281
282 282 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
283 283 second
284 284
285 285 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
286 286 merge
287 287
288 288 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
289 289 new head
290 290
291 291 4 bbe44766e73d 1970-01-17 04:53 +0000 person
292 292 new branch
293 293
294 294 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
295 295 no user, no domain
296 296
297 297 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
298 298 no person
299 299
300 300 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
301 301 other 1
302 302 other 2
303 303
304 304 other 3
305 305
306 306 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
307 307 line 1
308 308 line 2
309 309
310 310
311 311 $ hg log --debug --style compact
312 312 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
313 313 third
314 314
315 315 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
316 316 second
317 317
318 318 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
319 319 merge
320 320
321 321 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
322 322 new head
323 323
324 324 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
325 325 new branch
326 326
327 327 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
328 328 no user, no domain
329 329
330 330 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
331 331 no person
332 332
333 333 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
334 334 other 1
335 335 other 2
336 336
337 337 other 3
338 338
339 339 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
340 340 line 1
341 341 line 2
342 342
343 343
344 344 Test xml styles:
345 345
346 $ hg log --style xml -r 'not all()'
347 <?xml version="1.0"?>
348 <log>
349 </log>
350
346 351 $ hg log --style xml
347 352 <?xml version="1.0"?>
348 353 <log>
349 354 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
350 355 <tag>tip</tag>
351 356 <author email="test">test</author>
352 357 <date>2020-01-01T10:01:00+00:00</date>
353 358 <msg xml:space="preserve">third</msg>
354 359 </logentry>
355 360 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
356 361 <parent revision="-1" node="0000000000000000000000000000000000000000" />
357 362 <author email="user@hostname">User Name</author>
358 363 <date>1970-01-12T13:46:40+00:00</date>
359 364 <msg xml:space="preserve">second</msg>
360 365 </logentry>
361 366 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
362 367 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
363 368 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
364 369 <author email="person">person</author>
365 370 <date>1970-01-18T08:40:01+00:00</date>
366 371 <msg xml:space="preserve">merge</msg>
367 372 </logentry>
368 373 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
369 374 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
370 375 <author email="person">person</author>
371 376 <date>1970-01-18T08:40:00+00:00</date>
372 377 <msg xml:space="preserve">new head</msg>
373 378 </logentry>
374 379 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
375 380 <branch>foo</branch>
376 381 <author email="person">person</author>
377 382 <date>1970-01-17T04:53:20+00:00</date>
378 383 <msg xml:space="preserve">new branch</msg>
379 384 </logentry>
380 385 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
381 386 <author email="person">person</author>
382 387 <date>1970-01-16T01:06:40+00:00</date>
383 388 <msg xml:space="preserve">no user, no domain</msg>
384 389 </logentry>
385 390 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
386 391 <author email="other@place">other</author>
387 392 <date>1970-01-14T21:20:00+00:00</date>
388 393 <msg xml:space="preserve">no person</msg>
389 394 </logentry>
390 395 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
391 396 <author email="other@place">A. N. Other</author>
392 397 <date>1970-01-13T17:33:20+00:00</date>
393 398 <msg xml:space="preserve">other 1
394 399 other 2
395 400
396 401 other 3</msg>
397 402 </logentry>
398 403 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
399 404 <author email="user@hostname">User Name</author>
400 405 <date>1970-01-12T13:46:40+00:00</date>
401 406 <msg xml:space="preserve">line 1
402 407 line 2</msg>
403 408 </logentry>
404 409 </log>
405 410
406 411 $ hg log -v --style xml
407 412 <?xml version="1.0"?>
408 413 <log>
409 414 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
410 415 <tag>tip</tag>
411 416 <author email="test">test</author>
412 417 <date>2020-01-01T10:01:00+00:00</date>
413 418 <msg xml:space="preserve">third</msg>
414 419 <paths>
415 420 <path action="A">fourth</path>
416 421 <path action="A">third</path>
417 422 <path action="R">second</path>
418 423 </paths>
419 424 <copies>
420 425 <copy source="second">fourth</copy>
421 426 </copies>
422 427 </logentry>
423 428 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
424 429 <parent revision="-1" node="0000000000000000000000000000000000000000" />
425 430 <author email="user@hostname">User Name</author>
426 431 <date>1970-01-12T13:46:40+00:00</date>
427 432 <msg xml:space="preserve">second</msg>
428 433 <paths>
429 434 <path action="A">second</path>
430 435 </paths>
431 436 </logentry>
432 437 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
433 438 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
434 439 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
435 440 <author email="person">person</author>
436 441 <date>1970-01-18T08:40:01+00:00</date>
437 442 <msg xml:space="preserve">merge</msg>
438 443 <paths>
439 444 </paths>
440 445 </logentry>
441 446 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
442 447 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
443 448 <author email="person">person</author>
444 449 <date>1970-01-18T08:40:00+00:00</date>
445 450 <msg xml:space="preserve">new head</msg>
446 451 <paths>
447 452 <path action="A">d</path>
448 453 </paths>
449 454 </logentry>
450 455 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
451 456 <branch>foo</branch>
452 457 <author email="person">person</author>
453 458 <date>1970-01-17T04:53:20+00:00</date>
454 459 <msg xml:space="preserve">new branch</msg>
455 460 <paths>
456 461 </paths>
457 462 </logentry>
458 463 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
459 464 <author email="person">person</author>
460 465 <date>1970-01-16T01:06:40+00:00</date>
461 466 <msg xml:space="preserve">no user, no domain</msg>
462 467 <paths>
463 468 <path action="M">c</path>
464 469 </paths>
465 470 </logentry>
466 471 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
467 472 <author email="other@place">other</author>
468 473 <date>1970-01-14T21:20:00+00:00</date>
469 474 <msg xml:space="preserve">no person</msg>
470 475 <paths>
471 476 <path action="A">c</path>
472 477 </paths>
473 478 </logentry>
474 479 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
475 480 <author email="other@place">A. N. Other</author>
476 481 <date>1970-01-13T17:33:20+00:00</date>
477 482 <msg xml:space="preserve">other 1
478 483 other 2
479 484
480 485 other 3</msg>
481 486 <paths>
482 487 <path action="A">b</path>
483 488 </paths>
484 489 </logentry>
485 490 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
486 491 <author email="user@hostname">User Name</author>
487 492 <date>1970-01-12T13:46:40+00:00</date>
488 493 <msg xml:space="preserve">line 1
489 494 line 2</msg>
490 495 <paths>
491 496 <path action="A">a</path>
492 497 </paths>
493 498 </logentry>
494 499 </log>
495 500
496 501 $ hg log --debug --style xml
497 502 <?xml version="1.0"?>
498 503 <log>
499 504 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
500 505 <tag>tip</tag>
501 506 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
502 507 <parent revision="-1" node="0000000000000000000000000000000000000000" />
503 508 <author email="test">test</author>
504 509 <date>2020-01-01T10:01:00+00:00</date>
505 510 <msg xml:space="preserve">third</msg>
506 511 <paths>
507 512 <path action="A">fourth</path>
508 513 <path action="A">third</path>
509 514 <path action="R">second</path>
510 515 </paths>
511 516 <copies>
512 517 <copy source="second">fourth</copy>
513 518 </copies>
514 519 <extra key="branch">default</extra>
515 520 </logentry>
516 521 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
517 522 <parent revision="-1" node="0000000000000000000000000000000000000000" />
518 523 <parent revision="-1" node="0000000000000000000000000000000000000000" />
519 524 <author email="user@hostname">User Name</author>
520 525 <date>1970-01-12T13:46:40+00:00</date>
521 526 <msg xml:space="preserve">second</msg>
522 527 <paths>
523 528 <path action="A">second</path>
524 529 </paths>
525 530 <extra key="branch">default</extra>
526 531 </logentry>
527 532 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
528 533 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
529 534 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
530 535 <author email="person">person</author>
531 536 <date>1970-01-18T08:40:01+00:00</date>
532 537 <msg xml:space="preserve">merge</msg>
533 538 <paths>
534 539 </paths>
535 540 <extra key="branch">default</extra>
536 541 </logentry>
537 542 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
538 543 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
539 544 <parent revision="-1" node="0000000000000000000000000000000000000000" />
540 545 <author email="person">person</author>
541 546 <date>1970-01-18T08:40:00+00:00</date>
542 547 <msg xml:space="preserve">new head</msg>
543 548 <paths>
544 549 <path action="A">d</path>
545 550 </paths>
546 551 <extra key="branch">default</extra>
547 552 </logentry>
548 553 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
549 554 <branch>foo</branch>
550 555 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
551 556 <parent revision="-1" node="0000000000000000000000000000000000000000" />
552 557 <author email="person">person</author>
553 558 <date>1970-01-17T04:53:20+00:00</date>
554 559 <msg xml:space="preserve">new branch</msg>
555 560 <paths>
556 561 </paths>
557 562 <extra key="branch">foo</extra>
558 563 </logentry>
559 564 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
560 565 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
561 566 <parent revision="-1" node="0000000000000000000000000000000000000000" />
562 567 <author email="person">person</author>
563 568 <date>1970-01-16T01:06:40+00:00</date>
564 569 <msg xml:space="preserve">no user, no domain</msg>
565 570 <paths>
566 571 <path action="M">c</path>
567 572 </paths>
568 573 <extra key="branch">default</extra>
569 574 </logentry>
570 575 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
571 576 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
572 577 <parent revision="-1" node="0000000000000000000000000000000000000000" />
573 578 <author email="other@place">other</author>
574 579 <date>1970-01-14T21:20:00+00:00</date>
575 580 <msg xml:space="preserve">no person</msg>
576 581 <paths>
577 582 <path action="A">c</path>
578 583 </paths>
579 584 <extra key="branch">default</extra>
580 585 </logentry>
581 586 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
582 587 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
583 588 <parent revision="-1" node="0000000000000000000000000000000000000000" />
584 589 <author email="other@place">A. N. Other</author>
585 590 <date>1970-01-13T17:33:20+00:00</date>
586 591 <msg xml:space="preserve">other 1
587 592 other 2
588 593
589 594 other 3</msg>
590 595 <paths>
591 596 <path action="A">b</path>
592 597 </paths>
593 598 <extra key="branch">default</extra>
594 599 </logentry>
595 600 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
596 601 <parent revision="-1" node="0000000000000000000000000000000000000000" />
597 602 <parent revision="-1" node="0000000000000000000000000000000000000000" />
598 603 <author email="user@hostname">User Name</author>
599 604 <date>1970-01-12T13:46:40+00:00</date>
600 605 <msg xml:space="preserve">line 1
601 606 line 2</msg>
602 607 <paths>
603 608 <path action="A">a</path>
604 609 </paths>
605 610 <extra key="branch">default</extra>
606 611 </logentry>
607 612 </log>
608 613
609 614
610 615 Test JSON style:
611 616
612 617 $ hg log -k nosuch -Tjson
613 618 []
614 619
615 620 $ hg log -qr . -Tjson
616 621 [
617 622 {
618 623 "rev": 8,
619 624 "node": "95c24699272ef57d062b8bccc32c878bf841784a"
620 625 }
621 626 ]
622 627
623 628 $ hg log -vpr . -Tjson --stat
624 629 [
625 630 {
626 631 "rev": 8,
627 632 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
628 633 "branch": "default",
629 634 "phase": "draft",
630 635 "user": "test",
631 636 "date": [1577872860, 0],
632 637 "desc": "third",
633 638 "bookmarks": [],
634 639 "tags": ["tip"],
635 640 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
636 641 "files": ["fourth", "second", "third"],
637 642 "diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
638 643 "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n"
639 644 }
640 645 ]
641 646
642 647 honor --git but not format-breaking diffopts
643 648 $ hg --config diff.noprefix=True log --git -vpr . -Tjson
644 649 [
645 650 {
646 651 "rev": 8,
647 652 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
648 653 "branch": "default",
649 654 "phase": "draft",
650 655 "user": "test",
651 656 "date": [1577872860, 0],
652 657 "desc": "third",
653 658 "bookmarks": [],
654 659 "tags": ["tip"],
655 660 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
656 661 "files": ["fourth", "second", "third"],
657 662 "diff": "diff --git a/second b/fourth\nrename from second\nrename to fourth\ndiff --git a/third b/third\nnew file mode 100644\n--- /dev/null\n+++ b/third\n@@ -0,0 +1,1 @@\n+third\n"
658 663 }
659 664 ]
660 665
661 666 $ hg log -T json
662 667 [
663 668 {
664 669 "rev": 8,
665 670 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
666 671 "branch": "default",
667 672 "phase": "draft",
668 673 "user": "test",
669 674 "date": [1577872860, 0],
670 675 "desc": "third",
671 676 "bookmarks": [],
672 677 "tags": ["tip"],
673 678 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"]
674 679 },
675 680 {
676 681 "rev": 7,
677 682 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
678 683 "branch": "default",
679 684 "phase": "draft",
680 685 "user": "User Name <user@hostname>",
681 686 "date": [1000000, 0],
682 687 "desc": "second",
683 688 "bookmarks": [],
684 689 "tags": [],
685 690 "parents": ["0000000000000000000000000000000000000000"]
686 691 },
687 692 {
688 693 "rev": 6,
689 694 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
690 695 "branch": "default",
691 696 "phase": "draft",
692 697 "user": "person",
693 698 "date": [1500001, 0],
694 699 "desc": "merge",
695 700 "bookmarks": [],
696 701 "tags": [],
697 702 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"]
698 703 },
699 704 {
700 705 "rev": 5,
701 706 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
702 707 "branch": "default",
703 708 "phase": "draft",
704 709 "user": "person",
705 710 "date": [1500000, 0],
706 711 "desc": "new head",
707 712 "bookmarks": [],
708 713 "tags": [],
709 714 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
710 715 },
711 716 {
712 717 "rev": 4,
713 718 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
714 719 "branch": "foo",
715 720 "phase": "draft",
716 721 "user": "person",
717 722 "date": [1400000, 0],
718 723 "desc": "new branch",
719 724 "bookmarks": [],
720 725 "tags": [],
721 726 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
722 727 },
723 728 {
724 729 "rev": 3,
725 730 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
726 731 "branch": "default",
727 732 "phase": "draft",
728 733 "user": "person",
729 734 "date": [1300000, 0],
730 735 "desc": "no user, no domain",
731 736 "bookmarks": [],
732 737 "tags": [],
733 738 "parents": ["97054abb4ab824450e9164180baf491ae0078465"]
734 739 },
735 740 {
736 741 "rev": 2,
737 742 "node": "97054abb4ab824450e9164180baf491ae0078465",
738 743 "branch": "default",
739 744 "phase": "draft",
740 745 "user": "other@place",
741 746 "date": [1200000, 0],
742 747 "desc": "no person",
743 748 "bookmarks": [],
744 749 "tags": [],
745 750 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"]
746 751 },
747 752 {
748 753 "rev": 1,
749 754 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
750 755 "branch": "default",
751 756 "phase": "draft",
752 757 "user": "A. N. Other <other@place>",
753 758 "date": [1100000, 0],
754 759 "desc": "other 1\nother 2\n\nother 3",
755 760 "bookmarks": [],
756 761 "tags": [],
757 762 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"]
758 763 },
759 764 {
760 765 "rev": 0,
761 766 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
762 767 "branch": "default",
763 768 "phase": "draft",
764 769 "user": "User Name <user@hostname>",
765 770 "date": [1000000, 0],
766 771 "desc": "line 1\nline 2",
767 772 "bookmarks": [],
768 773 "tags": [],
769 774 "parents": ["0000000000000000000000000000000000000000"]
770 775 }
771 776 ]
772 777
773 778 $ hg heads -v -Tjson
774 779 [
775 780 {
776 781 "rev": 8,
777 782 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
778 783 "branch": "default",
779 784 "phase": "draft",
780 785 "user": "test",
781 786 "date": [1577872860, 0],
782 787 "desc": "third",
783 788 "bookmarks": [],
784 789 "tags": ["tip"],
785 790 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
786 791 "files": ["fourth", "second", "third"]
787 792 },
788 793 {
789 794 "rev": 6,
790 795 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
791 796 "branch": "default",
792 797 "phase": "draft",
793 798 "user": "person",
794 799 "date": [1500001, 0],
795 800 "desc": "merge",
796 801 "bookmarks": [],
797 802 "tags": [],
798 803 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
799 804 "files": []
800 805 },
801 806 {
802 807 "rev": 4,
803 808 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
804 809 "branch": "foo",
805 810 "phase": "draft",
806 811 "user": "person",
807 812 "date": [1400000, 0],
808 813 "desc": "new branch",
809 814 "bookmarks": [],
810 815 "tags": [],
811 816 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
812 817 "files": []
813 818 }
814 819 ]
815 820
816 821 $ hg log --debug -Tjson
817 822 [
818 823 {
819 824 "rev": 8,
820 825 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
821 826 "branch": "default",
822 827 "phase": "draft",
823 828 "user": "test",
824 829 "date": [1577872860, 0],
825 830 "desc": "third",
826 831 "bookmarks": [],
827 832 "tags": ["tip"],
828 833 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
829 834 "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
830 835 "extra": {"branch": "default"},
831 836 "modified": [],
832 837 "added": ["fourth", "third"],
833 838 "removed": ["second"]
834 839 },
835 840 {
836 841 "rev": 7,
837 842 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
838 843 "branch": "default",
839 844 "phase": "draft",
840 845 "user": "User Name <user@hostname>",
841 846 "date": [1000000, 0],
842 847 "desc": "second",
843 848 "bookmarks": [],
844 849 "tags": [],
845 850 "parents": ["0000000000000000000000000000000000000000"],
846 851 "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
847 852 "extra": {"branch": "default"},
848 853 "modified": [],
849 854 "added": ["second"],
850 855 "removed": []
851 856 },
852 857 {
853 858 "rev": 6,
854 859 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
855 860 "branch": "default",
856 861 "phase": "draft",
857 862 "user": "person",
858 863 "date": [1500001, 0],
859 864 "desc": "merge",
860 865 "bookmarks": [],
861 866 "tags": [],
862 867 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
863 868 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
864 869 "extra": {"branch": "default"},
865 870 "modified": [],
866 871 "added": [],
867 872 "removed": []
868 873 },
869 874 {
870 875 "rev": 5,
871 876 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
872 877 "branch": "default",
873 878 "phase": "draft",
874 879 "user": "person",
875 880 "date": [1500000, 0],
876 881 "desc": "new head",
877 882 "bookmarks": [],
878 883 "tags": [],
879 884 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
880 885 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
881 886 "extra": {"branch": "default"},
882 887 "modified": [],
883 888 "added": ["d"],
884 889 "removed": []
885 890 },
886 891 {
887 892 "rev": 4,
888 893 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
889 894 "branch": "foo",
890 895 "phase": "draft",
891 896 "user": "person",
892 897 "date": [1400000, 0],
893 898 "desc": "new branch",
894 899 "bookmarks": [],
895 900 "tags": [],
896 901 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
897 902 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
898 903 "extra": {"branch": "foo"},
899 904 "modified": [],
900 905 "added": [],
901 906 "removed": []
902 907 },
903 908 {
904 909 "rev": 3,
905 910 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
906 911 "branch": "default",
907 912 "phase": "draft",
908 913 "user": "person",
909 914 "date": [1300000, 0],
910 915 "desc": "no user, no domain",
911 916 "bookmarks": [],
912 917 "tags": [],
913 918 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
914 919 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
915 920 "extra": {"branch": "default"},
916 921 "modified": ["c"],
917 922 "added": [],
918 923 "removed": []
919 924 },
920 925 {
921 926 "rev": 2,
922 927 "node": "97054abb4ab824450e9164180baf491ae0078465",
923 928 "branch": "default",
924 929 "phase": "draft",
925 930 "user": "other@place",
926 931 "date": [1200000, 0],
927 932 "desc": "no person",
928 933 "bookmarks": [],
929 934 "tags": [],
930 935 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
931 936 "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
932 937 "extra": {"branch": "default"},
933 938 "modified": [],
934 939 "added": ["c"],
935 940 "removed": []
936 941 },
937 942 {
938 943 "rev": 1,
939 944 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
940 945 "branch": "default",
941 946 "phase": "draft",
942 947 "user": "A. N. Other <other@place>",
943 948 "date": [1100000, 0],
944 949 "desc": "other 1\nother 2\n\nother 3",
945 950 "bookmarks": [],
946 951 "tags": [],
947 952 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
948 953 "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
949 954 "extra": {"branch": "default"},
950 955 "modified": [],
951 956 "added": ["b"],
952 957 "removed": []
953 958 },
954 959 {
955 960 "rev": 0,
956 961 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
957 962 "branch": "default",
958 963 "phase": "draft",
959 964 "user": "User Name <user@hostname>",
960 965 "date": [1000000, 0],
961 966 "desc": "line 1\nline 2",
962 967 "bookmarks": [],
963 968 "tags": [],
964 969 "parents": ["0000000000000000000000000000000000000000"],
965 970 "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
966 971 "extra": {"branch": "default"},
967 972 "modified": [],
968 973 "added": ["a"],
969 974 "removed": []
970 975 }
971 976 ]
972 977
973 978 Error if style not readable:
974 979
975 980 #if unix-permissions no-root
976 981 $ touch q
977 982 $ chmod 0 q
978 983 $ hg log --style ./q
979 984 abort: Permission denied: ./q
980 985 [255]
981 986 #endif
982 987
983 988 Error if no style:
984 989
985 990 $ hg log --style notexist
986 991 abort: style 'notexist' not found
987 992 (available styles: bisect, changelog, compact, default, phases, status, xml)
988 993 [255]
989 994
990 995 $ hg log -T list
991 996 available styles: bisect, changelog, compact, default, phases, status, xml
992 997 abort: specify a template
993 998 [255]
994 999
995 1000 Error if style missing key:
996 1001
997 1002 $ echo 'q = q' > t
998 1003 $ hg log --style ./t
999 1004 abort: "changeset" not in template map
1000 1005 [255]
1001 1006
1002 1007 Error if style missing value:
1003 1008
1004 1009 $ echo 'changeset =' > t
1005 1010 $ hg log --style t
1006 1011 abort: t:1: missing value
1007 1012 [255]
1008 1013
1009 1014 Error if include fails:
1010 1015
1011 1016 $ echo 'changeset = q' >> t
1012 1017 #if unix-permissions no-root
1013 1018 $ hg log --style ./t
1014 1019 abort: template file ./q: Permission denied
1015 1020 [255]
1016 1021 $ rm q
1017 1022 #endif
1018 1023
1019 1024 Include works:
1020 1025
1021 1026 $ echo '{rev}' > q
1022 1027 $ hg log --style ./t
1023 1028 8
1024 1029 7
1025 1030 6
1026 1031 5
1027 1032 4
1028 1033 3
1029 1034 2
1030 1035 1
1031 1036 0
1032 1037
1033 1038 Check that {phase} works correctly on parents:
1034 1039
1035 1040 $ cat << EOF > parentphase
1036 1041 > changeset_debug = '{rev} ({phase}):{parents}\n'
1037 1042 > parent = ' {rev} ({phase})'
1038 1043 > EOF
1039 1044 $ hg phase -r 5 --public
1040 1045 $ hg phase -r 7 --secret --force
1041 1046 $ hg log --debug -G --style ./parentphase
1042 1047 @ 8 (secret): 7 (secret) -1 (public)
1043 1048 |
1044 1049 o 7 (secret): -1 (public) -1 (public)
1045 1050
1046 1051 o 6 (draft): 5 (public) 4 (draft)
1047 1052 |\
1048 1053 | o 5 (public): 3 (public) -1 (public)
1049 1054 | |
1050 1055 o | 4 (draft): 3 (public) -1 (public)
1051 1056 |/
1052 1057 o 3 (public): 2 (public) -1 (public)
1053 1058 |
1054 1059 o 2 (public): 1 (public) -1 (public)
1055 1060 |
1056 1061 o 1 (public): 0 (public) -1 (public)
1057 1062 |
1058 1063 o 0 (public): -1 (public) -1 (public)
1059 1064
1060 1065
1061 1066 Missing non-standard names give no error (backward compatibility):
1062 1067
1063 1068 $ echo "changeset = '{c}'" > t
1064 1069 $ hg log --style ./t
1065 1070
1066 1071 Defining non-standard name works:
1067 1072
1068 1073 $ cat <<EOF > t
1069 1074 > changeset = '{c}'
1070 1075 > c = q
1071 1076 > EOF
1072 1077 $ hg log --style ./t
1073 1078 8
1074 1079 7
1075 1080 6
1076 1081 5
1077 1082 4
1078 1083 3
1079 1084 2
1080 1085 1
1081 1086 0
1082 1087
1083 1088 ui.style works:
1084 1089
1085 1090 $ echo '[ui]' > .hg/hgrc
1086 1091 $ echo 'style = t' >> .hg/hgrc
1087 1092 $ hg log
1088 1093 8
1089 1094 7
1090 1095 6
1091 1096 5
1092 1097 4
1093 1098 3
1094 1099 2
1095 1100 1
1096 1101 0
1097 1102
1098 1103
1099 1104 Issue338:
1100 1105
1101 1106 $ hg log --style=changelog > changelog
1102 1107
1103 1108 $ cat changelog
1104 1109 2020-01-01 test <test>
1105 1110
1106 1111 * fourth, second, third:
1107 1112 third
1108 1113 [95c24699272e] [tip]
1109 1114
1110 1115 1970-01-12 User Name <user@hostname>
1111 1116
1112 1117 * second:
1113 1118 second
1114 1119 [29114dbae42b]
1115 1120
1116 1121 1970-01-18 person <person>
1117 1122
1118 1123 * merge
1119 1124 [d41e714fe50d]
1120 1125
1121 1126 * d:
1122 1127 new head
1123 1128 [13207e5a10d9]
1124 1129
1125 1130 1970-01-17 person <person>
1126 1131
1127 1132 * new branch
1128 1133 [bbe44766e73d] <foo>
1129 1134
1130 1135 1970-01-16 person <person>
1131 1136
1132 1137 * c:
1133 1138 no user, no domain
1134 1139 [10e46f2dcbf4]
1135 1140
1136 1141 1970-01-14 other <other@place>
1137 1142
1138 1143 * c:
1139 1144 no person
1140 1145 [97054abb4ab8]
1141 1146
1142 1147 1970-01-13 A. N. Other <other@place>
1143 1148
1144 1149 * b:
1145 1150 other 1 other 2
1146 1151
1147 1152 other 3
1148 1153 [b608e9d1a3f0]
1149 1154
1150 1155 1970-01-12 User Name <user@hostname>
1151 1156
1152 1157 * a:
1153 1158 line 1 line 2
1154 1159 [1e4e1b8f71e0]
1155 1160
1156 1161
1157 1162 Issue2130: xml output for 'hg heads' is malformed
1158 1163
1159 1164 $ hg heads --style changelog
1160 1165 2020-01-01 test <test>
1161 1166
1162 1167 * fourth, second, third:
1163 1168 third
1164 1169 [95c24699272e] [tip]
1165 1170
1166 1171 1970-01-18 person <person>
1167 1172
1168 1173 * merge
1169 1174 [d41e714fe50d]
1170 1175
1171 1176 1970-01-17 person <person>
1172 1177
1173 1178 * new branch
1174 1179 [bbe44766e73d] <foo>
1175 1180
1176 1181
1177 1182 Keys work:
1178 1183
1179 1184 $ for key in author branch branches date desc file_adds file_dels file_mods \
1180 1185 > file_copies file_copies_switch files \
1181 1186 > manifest node parents rev tags diffstat extras \
1182 1187 > p1rev p2rev p1node p2node; do
1183 1188 > for mode in '' --verbose --debug; do
1184 1189 > hg log $mode --template "$key$mode: {$key}\n"
1185 1190 > done
1186 1191 > done
1187 1192 author: test
1188 1193 author: User Name <user@hostname>
1189 1194 author: person
1190 1195 author: person
1191 1196 author: person
1192 1197 author: person
1193 1198 author: other@place
1194 1199 author: A. N. Other <other@place>
1195 1200 author: User Name <user@hostname>
1196 1201 author--verbose: test
1197 1202 author--verbose: User Name <user@hostname>
1198 1203 author--verbose: person
1199 1204 author--verbose: person
1200 1205 author--verbose: person
1201 1206 author--verbose: person
1202 1207 author--verbose: other@place
1203 1208 author--verbose: A. N. Other <other@place>
1204 1209 author--verbose: User Name <user@hostname>
1205 1210 author--debug: test
1206 1211 author--debug: User Name <user@hostname>
1207 1212 author--debug: person
1208 1213 author--debug: person
1209 1214 author--debug: person
1210 1215 author--debug: person
1211 1216 author--debug: other@place
1212 1217 author--debug: A. N. Other <other@place>
1213 1218 author--debug: User Name <user@hostname>
1214 1219 branch: default
1215 1220 branch: default
1216 1221 branch: default
1217 1222 branch: default
1218 1223 branch: foo
1219 1224 branch: default
1220 1225 branch: default
1221 1226 branch: default
1222 1227 branch: default
1223 1228 branch--verbose: default
1224 1229 branch--verbose: default
1225 1230 branch--verbose: default
1226 1231 branch--verbose: default
1227 1232 branch--verbose: foo
1228 1233 branch--verbose: default
1229 1234 branch--verbose: default
1230 1235 branch--verbose: default
1231 1236 branch--verbose: default
1232 1237 branch--debug: default
1233 1238 branch--debug: default
1234 1239 branch--debug: default
1235 1240 branch--debug: default
1236 1241 branch--debug: foo
1237 1242 branch--debug: default
1238 1243 branch--debug: default
1239 1244 branch--debug: default
1240 1245 branch--debug: default
1241 1246 branches:
1242 1247 branches:
1243 1248 branches:
1244 1249 branches:
1245 1250 branches: foo
1246 1251 branches:
1247 1252 branches:
1248 1253 branches:
1249 1254 branches:
1250 1255 branches--verbose:
1251 1256 branches--verbose:
1252 1257 branches--verbose:
1253 1258 branches--verbose:
1254 1259 branches--verbose: foo
1255 1260 branches--verbose:
1256 1261 branches--verbose:
1257 1262 branches--verbose:
1258 1263 branches--verbose:
1259 1264 branches--debug:
1260 1265 branches--debug:
1261 1266 branches--debug:
1262 1267 branches--debug:
1263 1268 branches--debug: foo
1264 1269 branches--debug:
1265 1270 branches--debug:
1266 1271 branches--debug:
1267 1272 branches--debug:
1268 1273 date: 1577872860.00
1269 1274 date: 1000000.00
1270 1275 date: 1500001.00
1271 1276 date: 1500000.00
1272 1277 date: 1400000.00
1273 1278 date: 1300000.00
1274 1279 date: 1200000.00
1275 1280 date: 1100000.00
1276 1281 date: 1000000.00
1277 1282 date--verbose: 1577872860.00
1278 1283 date--verbose: 1000000.00
1279 1284 date--verbose: 1500001.00
1280 1285 date--verbose: 1500000.00
1281 1286 date--verbose: 1400000.00
1282 1287 date--verbose: 1300000.00
1283 1288 date--verbose: 1200000.00
1284 1289 date--verbose: 1100000.00
1285 1290 date--verbose: 1000000.00
1286 1291 date--debug: 1577872860.00
1287 1292 date--debug: 1000000.00
1288 1293 date--debug: 1500001.00
1289 1294 date--debug: 1500000.00
1290 1295 date--debug: 1400000.00
1291 1296 date--debug: 1300000.00
1292 1297 date--debug: 1200000.00
1293 1298 date--debug: 1100000.00
1294 1299 date--debug: 1000000.00
1295 1300 desc: third
1296 1301 desc: second
1297 1302 desc: merge
1298 1303 desc: new head
1299 1304 desc: new branch
1300 1305 desc: no user, no domain
1301 1306 desc: no person
1302 1307 desc: other 1
1303 1308 other 2
1304 1309
1305 1310 other 3
1306 1311 desc: line 1
1307 1312 line 2
1308 1313 desc--verbose: third
1309 1314 desc--verbose: second
1310 1315 desc--verbose: merge
1311 1316 desc--verbose: new head
1312 1317 desc--verbose: new branch
1313 1318 desc--verbose: no user, no domain
1314 1319 desc--verbose: no person
1315 1320 desc--verbose: other 1
1316 1321 other 2
1317 1322
1318 1323 other 3
1319 1324 desc--verbose: line 1
1320 1325 line 2
1321 1326 desc--debug: third
1322 1327 desc--debug: second
1323 1328 desc--debug: merge
1324 1329 desc--debug: new head
1325 1330 desc--debug: new branch
1326 1331 desc--debug: no user, no domain
1327 1332 desc--debug: no person
1328 1333 desc--debug: other 1
1329 1334 other 2
1330 1335
1331 1336 other 3
1332 1337 desc--debug: line 1
1333 1338 line 2
1334 1339 file_adds: fourth third
1335 1340 file_adds: second
1336 1341 file_adds:
1337 1342 file_adds: d
1338 1343 file_adds:
1339 1344 file_adds:
1340 1345 file_adds: c
1341 1346 file_adds: b
1342 1347 file_adds: a
1343 1348 file_adds--verbose: fourth third
1344 1349 file_adds--verbose: second
1345 1350 file_adds--verbose:
1346 1351 file_adds--verbose: d
1347 1352 file_adds--verbose:
1348 1353 file_adds--verbose:
1349 1354 file_adds--verbose: c
1350 1355 file_adds--verbose: b
1351 1356 file_adds--verbose: a
1352 1357 file_adds--debug: fourth third
1353 1358 file_adds--debug: second
1354 1359 file_adds--debug:
1355 1360 file_adds--debug: d
1356 1361 file_adds--debug:
1357 1362 file_adds--debug:
1358 1363 file_adds--debug: c
1359 1364 file_adds--debug: b
1360 1365 file_adds--debug: a
1361 1366 file_dels: second
1362 1367 file_dels:
1363 1368 file_dels:
1364 1369 file_dels:
1365 1370 file_dels:
1366 1371 file_dels:
1367 1372 file_dels:
1368 1373 file_dels:
1369 1374 file_dels:
1370 1375 file_dels--verbose: second
1371 1376 file_dels--verbose:
1372 1377 file_dels--verbose:
1373 1378 file_dels--verbose:
1374 1379 file_dels--verbose:
1375 1380 file_dels--verbose:
1376 1381 file_dels--verbose:
1377 1382 file_dels--verbose:
1378 1383 file_dels--verbose:
1379 1384 file_dels--debug: second
1380 1385 file_dels--debug:
1381 1386 file_dels--debug:
1382 1387 file_dels--debug:
1383 1388 file_dels--debug:
1384 1389 file_dels--debug:
1385 1390 file_dels--debug:
1386 1391 file_dels--debug:
1387 1392 file_dels--debug:
1388 1393 file_mods:
1389 1394 file_mods:
1390 1395 file_mods:
1391 1396 file_mods:
1392 1397 file_mods:
1393 1398 file_mods: c
1394 1399 file_mods:
1395 1400 file_mods:
1396 1401 file_mods:
1397 1402 file_mods--verbose:
1398 1403 file_mods--verbose:
1399 1404 file_mods--verbose:
1400 1405 file_mods--verbose:
1401 1406 file_mods--verbose:
1402 1407 file_mods--verbose: c
1403 1408 file_mods--verbose:
1404 1409 file_mods--verbose:
1405 1410 file_mods--verbose:
1406 1411 file_mods--debug:
1407 1412 file_mods--debug:
1408 1413 file_mods--debug:
1409 1414 file_mods--debug:
1410 1415 file_mods--debug:
1411 1416 file_mods--debug: c
1412 1417 file_mods--debug:
1413 1418 file_mods--debug:
1414 1419 file_mods--debug:
1415 1420 file_copies: fourth (second)
1416 1421 file_copies:
1417 1422 file_copies:
1418 1423 file_copies:
1419 1424 file_copies:
1420 1425 file_copies:
1421 1426 file_copies:
1422 1427 file_copies:
1423 1428 file_copies:
1424 1429 file_copies--verbose: fourth (second)
1425 1430 file_copies--verbose:
1426 1431 file_copies--verbose:
1427 1432 file_copies--verbose:
1428 1433 file_copies--verbose:
1429 1434 file_copies--verbose:
1430 1435 file_copies--verbose:
1431 1436 file_copies--verbose:
1432 1437 file_copies--verbose:
1433 1438 file_copies--debug: fourth (second)
1434 1439 file_copies--debug:
1435 1440 file_copies--debug:
1436 1441 file_copies--debug:
1437 1442 file_copies--debug:
1438 1443 file_copies--debug:
1439 1444 file_copies--debug:
1440 1445 file_copies--debug:
1441 1446 file_copies--debug:
1442 1447 file_copies_switch:
1443 1448 file_copies_switch:
1444 1449 file_copies_switch:
1445 1450 file_copies_switch:
1446 1451 file_copies_switch:
1447 1452 file_copies_switch:
1448 1453 file_copies_switch:
1449 1454 file_copies_switch:
1450 1455 file_copies_switch:
1451 1456 file_copies_switch--verbose:
1452 1457 file_copies_switch--verbose:
1453 1458 file_copies_switch--verbose:
1454 1459 file_copies_switch--verbose:
1455 1460 file_copies_switch--verbose:
1456 1461 file_copies_switch--verbose:
1457 1462 file_copies_switch--verbose:
1458 1463 file_copies_switch--verbose:
1459 1464 file_copies_switch--verbose:
1460 1465 file_copies_switch--debug:
1461 1466 file_copies_switch--debug:
1462 1467 file_copies_switch--debug:
1463 1468 file_copies_switch--debug:
1464 1469 file_copies_switch--debug:
1465 1470 file_copies_switch--debug:
1466 1471 file_copies_switch--debug:
1467 1472 file_copies_switch--debug:
1468 1473 file_copies_switch--debug:
1469 1474 files: fourth second third
1470 1475 files: second
1471 1476 files:
1472 1477 files: d
1473 1478 files:
1474 1479 files: c
1475 1480 files: c
1476 1481 files: b
1477 1482 files: a
1478 1483 files--verbose: fourth second third
1479 1484 files--verbose: second
1480 1485 files--verbose:
1481 1486 files--verbose: d
1482 1487 files--verbose:
1483 1488 files--verbose: c
1484 1489 files--verbose: c
1485 1490 files--verbose: b
1486 1491 files--verbose: a
1487 1492 files--debug: fourth second third
1488 1493 files--debug: second
1489 1494 files--debug:
1490 1495 files--debug: d
1491 1496 files--debug:
1492 1497 files--debug: c
1493 1498 files--debug: c
1494 1499 files--debug: b
1495 1500 files--debug: a
1496 1501 manifest: 6:94961b75a2da
1497 1502 manifest: 5:f2dbc354b94e
1498 1503 manifest: 4:4dc3def4f9b4
1499 1504 manifest: 4:4dc3def4f9b4
1500 1505 manifest: 3:cb5a1327723b
1501 1506 manifest: 3:cb5a1327723b
1502 1507 manifest: 2:6e0e82995c35
1503 1508 manifest: 1:4e8d705b1e53
1504 1509 manifest: 0:a0c8bcbbb45c
1505 1510 manifest--verbose: 6:94961b75a2da
1506 1511 manifest--verbose: 5:f2dbc354b94e
1507 1512 manifest--verbose: 4:4dc3def4f9b4
1508 1513 manifest--verbose: 4:4dc3def4f9b4
1509 1514 manifest--verbose: 3:cb5a1327723b
1510 1515 manifest--verbose: 3:cb5a1327723b
1511 1516 manifest--verbose: 2:6e0e82995c35
1512 1517 manifest--verbose: 1:4e8d705b1e53
1513 1518 manifest--verbose: 0:a0c8bcbbb45c
1514 1519 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
1515 1520 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
1516 1521 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1517 1522 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1518 1523 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1519 1524 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1520 1525 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
1521 1526 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
1522 1527 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
1523 1528 node: 95c24699272ef57d062b8bccc32c878bf841784a
1524 1529 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1525 1530 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1526 1531 node: 13207e5a10d9fd28ec424934298e176197f2c67f
1527 1532 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1528 1533 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1529 1534 node: 97054abb4ab824450e9164180baf491ae0078465
1530 1535 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1531 1536 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1532 1537 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
1533 1538 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1534 1539 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1535 1540 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1536 1541 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1537 1542 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1538 1543 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1539 1544 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1540 1545 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1541 1546 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
1542 1547 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1543 1548 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1544 1549 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1545 1550 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1546 1551 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1547 1552 node--debug: 97054abb4ab824450e9164180baf491ae0078465
1548 1553 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1549 1554 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1550 1555 parents:
1551 1556 parents: -1:000000000000
1552 1557 parents: 5:13207e5a10d9 4:bbe44766e73d
1553 1558 parents: 3:10e46f2dcbf4
1554 1559 parents:
1555 1560 parents:
1556 1561 parents:
1557 1562 parents:
1558 1563 parents:
1559 1564 parents--verbose:
1560 1565 parents--verbose: -1:000000000000
1561 1566 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
1562 1567 parents--verbose: 3:10e46f2dcbf4
1563 1568 parents--verbose:
1564 1569 parents--verbose:
1565 1570 parents--verbose:
1566 1571 parents--verbose:
1567 1572 parents--verbose:
1568 1573 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
1569 1574 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1570 1575 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1571 1576 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1572 1577 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1573 1578 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
1574 1579 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
1575 1580 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
1576 1581 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1577 1582 rev: 8
1578 1583 rev: 7
1579 1584 rev: 6
1580 1585 rev: 5
1581 1586 rev: 4
1582 1587 rev: 3
1583 1588 rev: 2
1584 1589 rev: 1
1585 1590 rev: 0
1586 1591 rev--verbose: 8
1587 1592 rev--verbose: 7
1588 1593 rev--verbose: 6
1589 1594 rev--verbose: 5
1590 1595 rev--verbose: 4
1591 1596 rev--verbose: 3
1592 1597 rev--verbose: 2
1593 1598 rev--verbose: 1
1594 1599 rev--verbose: 0
1595 1600 rev--debug: 8
1596 1601 rev--debug: 7
1597 1602 rev--debug: 6
1598 1603 rev--debug: 5
1599 1604 rev--debug: 4
1600 1605 rev--debug: 3
1601 1606 rev--debug: 2
1602 1607 rev--debug: 1
1603 1608 rev--debug: 0
1604 1609 tags: tip
1605 1610 tags:
1606 1611 tags:
1607 1612 tags:
1608 1613 tags:
1609 1614 tags:
1610 1615 tags:
1611 1616 tags:
1612 1617 tags:
1613 1618 tags--verbose: tip
1614 1619 tags--verbose:
1615 1620 tags--verbose:
1616 1621 tags--verbose:
1617 1622 tags--verbose:
1618 1623 tags--verbose:
1619 1624 tags--verbose:
1620 1625 tags--verbose:
1621 1626 tags--verbose:
1622 1627 tags--debug: tip
1623 1628 tags--debug:
1624 1629 tags--debug:
1625 1630 tags--debug:
1626 1631 tags--debug:
1627 1632 tags--debug:
1628 1633 tags--debug:
1629 1634 tags--debug:
1630 1635 tags--debug:
1631 1636 diffstat: 3: +2/-1
1632 1637 diffstat: 1: +1/-0
1633 1638 diffstat: 0: +0/-0
1634 1639 diffstat: 1: +1/-0
1635 1640 diffstat: 0: +0/-0
1636 1641 diffstat: 1: +1/-0
1637 1642 diffstat: 1: +4/-0
1638 1643 diffstat: 1: +2/-0
1639 1644 diffstat: 1: +1/-0
1640 1645 diffstat--verbose: 3: +2/-1
1641 1646 diffstat--verbose: 1: +1/-0
1642 1647 diffstat--verbose: 0: +0/-0
1643 1648 diffstat--verbose: 1: +1/-0
1644 1649 diffstat--verbose: 0: +0/-0
1645 1650 diffstat--verbose: 1: +1/-0
1646 1651 diffstat--verbose: 1: +4/-0
1647 1652 diffstat--verbose: 1: +2/-0
1648 1653 diffstat--verbose: 1: +1/-0
1649 1654 diffstat--debug: 3: +2/-1
1650 1655 diffstat--debug: 1: +1/-0
1651 1656 diffstat--debug: 0: +0/-0
1652 1657 diffstat--debug: 1: +1/-0
1653 1658 diffstat--debug: 0: +0/-0
1654 1659 diffstat--debug: 1: +1/-0
1655 1660 diffstat--debug: 1: +4/-0
1656 1661 diffstat--debug: 1: +2/-0
1657 1662 diffstat--debug: 1: +1/-0
1658 1663 extras: branch=default
1659 1664 extras: branch=default
1660 1665 extras: branch=default
1661 1666 extras: branch=default
1662 1667 extras: branch=foo
1663 1668 extras: branch=default
1664 1669 extras: branch=default
1665 1670 extras: branch=default
1666 1671 extras: branch=default
1667 1672 extras--verbose: branch=default
1668 1673 extras--verbose: branch=default
1669 1674 extras--verbose: branch=default
1670 1675 extras--verbose: branch=default
1671 1676 extras--verbose: branch=foo
1672 1677 extras--verbose: branch=default
1673 1678 extras--verbose: branch=default
1674 1679 extras--verbose: branch=default
1675 1680 extras--verbose: branch=default
1676 1681 extras--debug: branch=default
1677 1682 extras--debug: branch=default
1678 1683 extras--debug: branch=default
1679 1684 extras--debug: branch=default
1680 1685 extras--debug: branch=foo
1681 1686 extras--debug: branch=default
1682 1687 extras--debug: branch=default
1683 1688 extras--debug: branch=default
1684 1689 extras--debug: branch=default
1685 1690 p1rev: 7
1686 1691 p1rev: -1
1687 1692 p1rev: 5
1688 1693 p1rev: 3
1689 1694 p1rev: 3
1690 1695 p1rev: 2
1691 1696 p1rev: 1
1692 1697 p1rev: 0
1693 1698 p1rev: -1
1694 1699 p1rev--verbose: 7
1695 1700 p1rev--verbose: -1
1696 1701 p1rev--verbose: 5
1697 1702 p1rev--verbose: 3
1698 1703 p1rev--verbose: 3
1699 1704 p1rev--verbose: 2
1700 1705 p1rev--verbose: 1
1701 1706 p1rev--verbose: 0
1702 1707 p1rev--verbose: -1
1703 1708 p1rev--debug: 7
1704 1709 p1rev--debug: -1
1705 1710 p1rev--debug: 5
1706 1711 p1rev--debug: 3
1707 1712 p1rev--debug: 3
1708 1713 p1rev--debug: 2
1709 1714 p1rev--debug: 1
1710 1715 p1rev--debug: 0
1711 1716 p1rev--debug: -1
1712 1717 p2rev: -1
1713 1718 p2rev: -1
1714 1719 p2rev: 4
1715 1720 p2rev: -1
1716 1721 p2rev: -1
1717 1722 p2rev: -1
1718 1723 p2rev: -1
1719 1724 p2rev: -1
1720 1725 p2rev: -1
1721 1726 p2rev--verbose: -1
1722 1727 p2rev--verbose: -1
1723 1728 p2rev--verbose: 4
1724 1729 p2rev--verbose: -1
1725 1730 p2rev--verbose: -1
1726 1731 p2rev--verbose: -1
1727 1732 p2rev--verbose: -1
1728 1733 p2rev--verbose: -1
1729 1734 p2rev--verbose: -1
1730 1735 p2rev--debug: -1
1731 1736 p2rev--debug: -1
1732 1737 p2rev--debug: 4
1733 1738 p2rev--debug: -1
1734 1739 p2rev--debug: -1
1735 1740 p2rev--debug: -1
1736 1741 p2rev--debug: -1
1737 1742 p2rev--debug: -1
1738 1743 p2rev--debug: -1
1739 1744 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1740 1745 p1node: 0000000000000000000000000000000000000000
1741 1746 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
1742 1747 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1743 1748 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1744 1749 p1node: 97054abb4ab824450e9164180baf491ae0078465
1745 1750 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1746 1751 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1747 1752 p1node: 0000000000000000000000000000000000000000
1748 1753 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1749 1754 p1node--verbose: 0000000000000000000000000000000000000000
1750 1755 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1751 1756 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1752 1757 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1753 1758 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1754 1759 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1755 1760 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1756 1761 p1node--verbose: 0000000000000000000000000000000000000000
1757 1762 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1758 1763 p1node--debug: 0000000000000000000000000000000000000000
1759 1764 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1760 1765 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1761 1766 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1762 1767 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
1763 1768 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1764 1769 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1765 1770 p1node--debug: 0000000000000000000000000000000000000000
1766 1771 p2node: 0000000000000000000000000000000000000000
1767 1772 p2node: 0000000000000000000000000000000000000000
1768 1773 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1769 1774 p2node: 0000000000000000000000000000000000000000
1770 1775 p2node: 0000000000000000000000000000000000000000
1771 1776 p2node: 0000000000000000000000000000000000000000
1772 1777 p2node: 0000000000000000000000000000000000000000
1773 1778 p2node: 0000000000000000000000000000000000000000
1774 1779 p2node: 0000000000000000000000000000000000000000
1775 1780 p2node--verbose: 0000000000000000000000000000000000000000
1776 1781 p2node--verbose: 0000000000000000000000000000000000000000
1777 1782 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1778 1783 p2node--verbose: 0000000000000000000000000000000000000000
1779 1784 p2node--verbose: 0000000000000000000000000000000000000000
1780 1785 p2node--verbose: 0000000000000000000000000000000000000000
1781 1786 p2node--verbose: 0000000000000000000000000000000000000000
1782 1787 p2node--verbose: 0000000000000000000000000000000000000000
1783 1788 p2node--verbose: 0000000000000000000000000000000000000000
1784 1789 p2node--debug: 0000000000000000000000000000000000000000
1785 1790 p2node--debug: 0000000000000000000000000000000000000000
1786 1791 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1787 1792 p2node--debug: 0000000000000000000000000000000000000000
1788 1793 p2node--debug: 0000000000000000000000000000000000000000
1789 1794 p2node--debug: 0000000000000000000000000000000000000000
1790 1795 p2node--debug: 0000000000000000000000000000000000000000
1791 1796 p2node--debug: 0000000000000000000000000000000000000000
1792 1797 p2node--debug: 0000000000000000000000000000000000000000
1793 1798
1794 1799 Filters work:
1795 1800
1796 1801 $ hg log --template '{author|domain}\n'
1797 1802
1798 1803 hostname
1799 1804
1800 1805
1801 1806
1802 1807
1803 1808 place
1804 1809 place
1805 1810 hostname
1806 1811
1807 1812 $ hg log --template '{author|person}\n'
1808 1813 test
1809 1814 User Name
1810 1815 person
1811 1816 person
1812 1817 person
1813 1818 person
1814 1819 other
1815 1820 A. N. Other
1816 1821 User Name
1817 1822
1818 1823 $ hg log --template '{author|user}\n'
1819 1824 test
1820 1825 user
1821 1826 person
1822 1827 person
1823 1828 person
1824 1829 person
1825 1830 other
1826 1831 other
1827 1832 user
1828 1833
1829 1834 $ hg log --template '{date|date}\n'
1830 1835 Wed Jan 01 10:01:00 2020 +0000
1831 1836 Mon Jan 12 13:46:40 1970 +0000
1832 1837 Sun Jan 18 08:40:01 1970 +0000
1833 1838 Sun Jan 18 08:40:00 1970 +0000
1834 1839 Sat Jan 17 04:53:20 1970 +0000
1835 1840 Fri Jan 16 01:06:40 1970 +0000
1836 1841 Wed Jan 14 21:20:00 1970 +0000
1837 1842 Tue Jan 13 17:33:20 1970 +0000
1838 1843 Mon Jan 12 13:46:40 1970 +0000
1839 1844
1840 1845 $ hg log --template '{date|isodate}\n'
1841 1846 2020-01-01 10:01 +0000
1842 1847 1970-01-12 13:46 +0000
1843 1848 1970-01-18 08:40 +0000
1844 1849 1970-01-18 08:40 +0000
1845 1850 1970-01-17 04:53 +0000
1846 1851 1970-01-16 01:06 +0000
1847 1852 1970-01-14 21:20 +0000
1848 1853 1970-01-13 17:33 +0000
1849 1854 1970-01-12 13:46 +0000
1850 1855
1851 1856 $ hg log --template '{date|isodatesec}\n'
1852 1857 2020-01-01 10:01:00 +0000
1853 1858 1970-01-12 13:46:40 +0000
1854 1859 1970-01-18 08:40:01 +0000
1855 1860 1970-01-18 08:40:00 +0000
1856 1861 1970-01-17 04:53:20 +0000
1857 1862 1970-01-16 01:06:40 +0000
1858 1863 1970-01-14 21:20:00 +0000
1859 1864 1970-01-13 17:33:20 +0000
1860 1865 1970-01-12 13:46:40 +0000
1861 1866
1862 1867 $ hg log --template '{date|rfc822date}\n'
1863 1868 Wed, 01 Jan 2020 10:01:00 +0000
1864 1869 Mon, 12 Jan 1970 13:46:40 +0000
1865 1870 Sun, 18 Jan 1970 08:40:01 +0000
1866 1871 Sun, 18 Jan 1970 08:40:00 +0000
1867 1872 Sat, 17 Jan 1970 04:53:20 +0000
1868 1873 Fri, 16 Jan 1970 01:06:40 +0000
1869 1874 Wed, 14 Jan 1970 21:20:00 +0000
1870 1875 Tue, 13 Jan 1970 17:33:20 +0000
1871 1876 Mon, 12 Jan 1970 13:46:40 +0000
1872 1877
1873 1878 $ hg log --template '{desc|firstline}\n'
1874 1879 third
1875 1880 second
1876 1881 merge
1877 1882 new head
1878 1883 new branch
1879 1884 no user, no domain
1880 1885 no person
1881 1886 other 1
1882 1887 line 1
1883 1888
1884 1889 $ hg log --template '{node|short}\n'
1885 1890 95c24699272e
1886 1891 29114dbae42b
1887 1892 d41e714fe50d
1888 1893 13207e5a10d9
1889 1894 bbe44766e73d
1890 1895 10e46f2dcbf4
1891 1896 97054abb4ab8
1892 1897 b608e9d1a3f0
1893 1898 1e4e1b8f71e0
1894 1899
1895 1900 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
1896 1901 <changeset author="test"/>
1897 1902 <changeset author="User Name &lt;user@hostname&gt;"/>
1898 1903 <changeset author="person"/>
1899 1904 <changeset author="person"/>
1900 1905 <changeset author="person"/>
1901 1906 <changeset author="person"/>
1902 1907 <changeset author="other@place"/>
1903 1908 <changeset author="A. N. Other &lt;other@place&gt;"/>
1904 1909 <changeset author="User Name &lt;user@hostname&gt;"/>
1905 1910
1906 1911 $ hg log --template '{rev}: {children}\n'
1907 1912 8:
1908 1913 7: 8:95c24699272e
1909 1914 6:
1910 1915 5: 6:d41e714fe50d
1911 1916 4: 6:d41e714fe50d
1912 1917 3: 4:bbe44766e73d 5:13207e5a10d9
1913 1918 2: 3:10e46f2dcbf4
1914 1919 1: 2:97054abb4ab8
1915 1920 0: 1:b608e9d1a3f0
1916 1921
1917 1922 Formatnode filter works:
1918 1923
1919 1924 $ hg -q log -r 0 --template '{node|formatnode}\n'
1920 1925 1e4e1b8f71e0
1921 1926
1922 1927 $ hg log -r 0 --template '{node|formatnode}\n'
1923 1928 1e4e1b8f71e0
1924 1929
1925 1930 $ hg -v log -r 0 --template '{node|formatnode}\n'
1926 1931 1e4e1b8f71e0
1927 1932
1928 1933 $ hg --debug log -r 0 --template '{node|formatnode}\n'
1929 1934 1e4e1b8f71e05681d422154f5421e385fec3454f
1930 1935
1931 1936 Age filter:
1932 1937
1933 1938 $ hg init unstable-hash
1934 1939 $ cd unstable-hash
1935 1940 $ hg log --template '{date|age}\n' > /dev/null || exit 1
1936 1941
1937 1942 >>> from datetime import datetime, timedelta
1938 1943 >>> fp = open('a', 'w')
1939 1944 >>> n = datetime.now() + timedelta(366 * 7)
1940 1945 >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
1941 1946 >>> fp.close()
1942 1947 $ hg add a
1943 1948 $ hg commit -m future -d "`cat a`"
1944 1949
1945 1950 $ hg log -l1 --template '{date|age}\n'
1946 1951 7 years from now
1947 1952
1948 1953 $ cd ..
1949 1954 $ rm -rf unstable-hash
1950 1955
1951 1956 Add a dummy commit to make up for the instability of the above:
1952 1957
1953 1958 $ echo a > a
1954 1959 $ hg add a
1955 1960 $ hg ci -m future
1956 1961
1957 1962 Count filter:
1958 1963
1959 1964 $ hg log -l1 --template '{node|count} {node|short|count}\n'
1960 1965 40 12
1961 1966
1962 1967 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
1963 1968 0 1 4
1964 1969
1965 1970 $ hg log -G --template '{rev}: children: {children|count}, \
1966 1971 > tags: {tags|count}, file_adds: {file_adds|count}, \
1967 1972 > ancestors: {revset("ancestors(%s)", rev)|count}'
1968 1973 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
1969 1974 |
1970 1975 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
1971 1976 |
1972 1977 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
1973 1978
1974 1979 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
1975 1980 |\
1976 1981 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
1977 1982 | |
1978 1983 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
1979 1984 |/
1980 1985 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
1981 1986 |
1982 1987 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
1983 1988 |
1984 1989 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
1985 1990 |
1986 1991 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
1987 1992
1988 1993
1989 1994 Upper/lower filters:
1990 1995
1991 1996 $ hg log -r0 --template '{branch|upper}\n'
1992 1997 DEFAULT
1993 1998 $ hg log -r0 --template '{author|lower}\n'
1994 1999 user name <user@hostname>
1995 2000 $ hg log -r0 --template '{date|upper}\n'
1996 2001 abort: template filter 'upper' is not compatible with keyword 'date'
1997 2002 [255]
1998 2003
1999 2004 Add a commit that does all possible modifications at once
2000 2005
2001 2006 $ echo modify >> third
2002 2007 $ touch b
2003 2008 $ hg add b
2004 2009 $ hg mv fourth fifth
2005 2010 $ hg rm a
2006 2011 $ hg ci -m "Modify, add, remove, rename"
2007 2012
2008 2013 Check the status template
2009 2014
2010 2015 $ cat <<EOF >> $HGRCPATH
2011 2016 > [extensions]
2012 2017 > color=
2013 2018 > EOF
2014 2019
2015 2020 $ hg log -T status -r 10
2016 2021 changeset: 10:0f9759ec227a
2017 2022 tag: tip
2018 2023 user: test
2019 2024 date: Thu Jan 01 00:00:00 1970 +0000
2020 2025 summary: Modify, add, remove, rename
2021 2026 files:
2022 2027 M third
2023 2028 A b
2024 2029 A fifth
2025 2030 R a
2026 2031 R fourth
2027 2032
2028 2033 $ hg log -T status -C -r 10
2029 2034 changeset: 10:0f9759ec227a
2030 2035 tag: tip
2031 2036 user: test
2032 2037 date: Thu Jan 01 00:00:00 1970 +0000
2033 2038 summary: Modify, add, remove, rename
2034 2039 files:
2035 2040 M third
2036 2041 A b
2037 2042 A fifth
2038 2043 fourth
2039 2044 R a
2040 2045 R fourth
2041 2046
2042 2047 $ hg log -T status -C -r 10 -v
2043 2048 changeset: 10:0f9759ec227a
2044 2049 tag: tip
2045 2050 user: test
2046 2051 date: Thu Jan 01 00:00:00 1970 +0000
2047 2052 description:
2048 2053 Modify, add, remove, rename
2049 2054
2050 2055 files:
2051 2056 M third
2052 2057 A b
2053 2058 A fifth
2054 2059 fourth
2055 2060 R a
2056 2061 R fourth
2057 2062
2058 2063 $ hg log -T status -C -r 10 --debug
2059 2064 changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c
2060 2065 tag: tip
2061 2066 phase: secret
2062 2067 parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066
2063 2068 parent: -1:0000000000000000000000000000000000000000
2064 2069 manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567
2065 2070 user: test
2066 2071 date: Thu Jan 01 00:00:00 1970 +0000
2067 2072 extra: branch=default
2068 2073 description:
2069 2074 Modify, add, remove, rename
2070 2075
2071 2076 files:
2072 2077 M third
2073 2078 A b
2074 2079 A fifth
2075 2080 fourth
2076 2081 R a
2077 2082 R fourth
2078 2083
2079 2084 $ hg log -T status -C -r 10 --quiet
2080 2085 10:0f9759ec227a
2081 2086 $ hg --color=debug log -T status -r 10
2082 2087 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2083 2088 [log.tag|tag: tip]
2084 2089 [log.user|user: test]
2085 2090 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2086 2091 [log.summary|summary: Modify, add, remove, rename]
2087 2092 [ui.note log.files|files:]
2088 2093 [status.modified|M third]
2089 2094 [status.added|A b]
2090 2095 [status.added|A fifth]
2091 2096 [status.removed|R a]
2092 2097 [status.removed|R fourth]
2093 2098
2094 2099 $ hg --color=debug log -T status -C -r 10
2095 2100 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2096 2101 [log.tag|tag: tip]
2097 2102 [log.user|user: test]
2098 2103 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2099 2104 [log.summary|summary: Modify, add, remove, rename]
2100 2105 [ui.note log.files|files:]
2101 2106 [status.modified|M third]
2102 2107 [status.added|A b]
2103 2108 [status.added|A fifth]
2104 2109 [status.copied| fourth]
2105 2110 [status.removed|R a]
2106 2111 [status.removed|R fourth]
2107 2112
2108 2113 $ hg --color=debug log -T status -C -r 10 -v
2109 2114 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2110 2115 [log.tag|tag: tip]
2111 2116 [log.user|user: test]
2112 2117 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2113 2118 [ui.note log.description|description:]
2114 2119 [ui.note log.description|Modify, add, remove, rename]
2115 2120
2116 2121 [ui.note log.files|files:]
2117 2122 [status.modified|M third]
2118 2123 [status.added|A b]
2119 2124 [status.added|A fifth]
2120 2125 [status.copied| fourth]
2121 2126 [status.removed|R a]
2122 2127 [status.removed|R fourth]
2123 2128
2124 2129 $ hg --color=debug log -T status -C -r 10 --debug
2125 2130 [log.changeset changeset.secret|changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
2126 2131 [log.tag|tag: tip]
2127 2132 [log.phase|phase: secret]
2128 2133 [log.parent changeset.secret|parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066]
2129 2134 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2130 2135 [ui.debug log.manifest|manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567]
2131 2136 [log.user|user: test]
2132 2137 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2133 2138 [ui.debug log.extra|extra: branch=default]
2134 2139 [ui.note log.description|description:]
2135 2140 [ui.note log.description|Modify, add, remove, rename]
2136 2141
2137 2142 [ui.note log.files|files:]
2138 2143 [status.modified|M third]
2139 2144 [status.added|A b]
2140 2145 [status.added|A fifth]
2141 2146 [status.copied| fourth]
2142 2147 [status.removed|R a]
2143 2148 [status.removed|R fourth]
2144 2149
2145 2150 $ hg --color=debug log -T status -C -r 10 --quiet
2146 2151 [log.node|10:0f9759ec227a]
2147 2152
2148 2153 Check the bisect template
2149 2154
2150 2155 $ hg bisect -g 1
2151 2156 $ hg bisect -b 3 --noupdate
2152 2157 Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
2153 2158 $ hg log -T bisect -r 0:4
2154 2159 changeset: 0:1e4e1b8f71e0
2155 2160 bisect: good (implicit)
2156 2161 user: User Name <user@hostname>
2157 2162 date: Mon Jan 12 13:46:40 1970 +0000
2158 2163 summary: line 1
2159 2164
2160 2165 changeset: 1:b608e9d1a3f0
2161 2166 bisect: good
2162 2167 user: A. N. Other <other@place>
2163 2168 date: Tue Jan 13 17:33:20 1970 +0000
2164 2169 summary: other 1
2165 2170
2166 2171 changeset: 2:97054abb4ab8
2167 2172 bisect: untested
2168 2173 user: other@place
2169 2174 date: Wed Jan 14 21:20:00 1970 +0000
2170 2175 summary: no person
2171 2176
2172 2177 changeset: 3:10e46f2dcbf4
2173 2178 bisect: bad
2174 2179 user: person
2175 2180 date: Fri Jan 16 01:06:40 1970 +0000
2176 2181 summary: no user, no domain
2177 2182
2178 2183 changeset: 4:bbe44766e73d
2179 2184 bisect: bad (implicit)
2180 2185 branch: foo
2181 2186 user: person
2182 2187 date: Sat Jan 17 04:53:20 1970 +0000
2183 2188 summary: new branch
2184 2189
2185 2190 $ hg log --debug -T bisect -r 0:4
2186 2191 changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2187 2192 bisect: good (implicit)
2188 2193 phase: public
2189 2194 parent: -1:0000000000000000000000000000000000000000
2190 2195 parent: -1:0000000000000000000000000000000000000000
2191 2196 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
2192 2197 user: User Name <user@hostname>
2193 2198 date: Mon Jan 12 13:46:40 1970 +0000
2194 2199 files+: a
2195 2200 extra: branch=default
2196 2201 description:
2197 2202 line 1
2198 2203 line 2
2199 2204
2200 2205
2201 2206 changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2202 2207 bisect: good
2203 2208 phase: public
2204 2209 parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2205 2210 parent: -1:0000000000000000000000000000000000000000
2206 2211 manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
2207 2212 user: A. N. Other <other@place>
2208 2213 date: Tue Jan 13 17:33:20 1970 +0000
2209 2214 files+: b
2210 2215 extra: branch=default
2211 2216 description:
2212 2217 other 1
2213 2218 other 2
2214 2219
2215 2220 other 3
2216 2221
2217 2222
2218 2223 changeset: 2:97054abb4ab824450e9164180baf491ae0078465
2219 2224 bisect: untested
2220 2225 phase: public
2221 2226 parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2222 2227 parent: -1:0000000000000000000000000000000000000000
2223 2228 manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
2224 2229 user: other@place
2225 2230 date: Wed Jan 14 21:20:00 1970 +0000
2226 2231 files+: c
2227 2232 extra: branch=default
2228 2233 description:
2229 2234 no person
2230 2235
2231 2236
2232 2237 changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2233 2238 bisect: bad
2234 2239 phase: public
2235 2240 parent: 2:97054abb4ab824450e9164180baf491ae0078465
2236 2241 parent: -1:0000000000000000000000000000000000000000
2237 2242 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2238 2243 user: person
2239 2244 date: Fri Jan 16 01:06:40 1970 +0000
2240 2245 files: c
2241 2246 extra: branch=default
2242 2247 description:
2243 2248 no user, no domain
2244 2249
2245 2250
2246 2251 changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
2247 2252 bisect: bad (implicit)
2248 2253 branch: foo
2249 2254 phase: draft
2250 2255 parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2251 2256 parent: -1:0000000000000000000000000000000000000000
2252 2257 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2253 2258 user: person
2254 2259 date: Sat Jan 17 04:53:20 1970 +0000
2255 2260 extra: branch=foo
2256 2261 description:
2257 2262 new branch
2258 2263
2259 2264
2260 2265 $ hg log -v -T bisect -r 0:4
2261 2266 changeset: 0:1e4e1b8f71e0
2262 2267 bisect: good (implicit)
2263 2268 user: User Name <user@hostname>
2264 2269 date: Mon Jan 12 13:46:40 1970 +0000
2265 2270 files: a
2266 2271 description:
2267 2272 line 1
2268 2273 line 2
2269 2274
2270 2275
2271 2276 changeset: 1:b608e9d1a3f0
2272 2277 bisect: good
2273 2278 user: A. N. Other <other@place>
2274 2279 date: Tue Jan 13 17:33:20 1970 +0000
2275 2280 files: b
2276 2281 description:
2277 2282 other 1
2278 2283 other 2
2279 2284
2280 2285 other 3
2281 2286
2282 2287
2283 2288 changeset: 2:97054abb4ab8
2284 2289 bisect: untested
2285 2290 user: other@place
2286 2291 date: Wed Jan 14 21:20:00 1970 +0000
2287 2292 files: c
2288 2293 description:
2289 2294 no person
2290 2295
2291 2296
2292 2297 changeset: 3:10e46f2dcbf4
2293 2298 bisect: bad
2294 2299 user: person
2295 2300 date: Fri Jan 16 01:06:40 1970 +0000
2296 2301 files: c
2297 2302 description:
2298 2303 no user, no domain
2299 2304
2300 2305
2301 2306 changeset: 4:bbe44766e73d
2302 2307 bisect: bad (implicit)
2303 2308 branch: foo
2304 2309 user: person
2305 2310 date: Sat Jan 17 04:53:20 1970 +0000
2306 2311 description:
2307 2312 new branch
2308 2313
2309 2314
2310 2315 $ hg --color=debug log -T bisect -r 0:4
2311 2316 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2312 2317 [log.bisect bisect.good|bisect: good (implicit)]
2313 2318 [log.user|user: User Name <user@hostname>]
2314 2319 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2315 2320 [log.summary|summary: line 1]
2316 2321
2317 2322 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2318 2323 [log.bisect bisect.good|bisect: good]
2319 2324 [log.user|user: A. N. Other <other@place>]
2320 2325 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2321 2326 [log.summary|summary: other 1]
2322 2327
2323 2328 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2324 2329 [log.bisect bisect.untested|bisect: untested]
2325 2330 [log.user|user: other@place]
2326 2331 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2327 2332 [log.summary|summary: no person]
2328 2333
2329 2334 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2330 2335 [log.bisect bisect.bad|bisect: bad]
2331 2336 [log.user|user: person]
2332 2337 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2333 2338 [log.summary|summary: no user, no domain]
2334 2339
2335 2340 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2336 2341 [log.bisect bisect.bad|bisect: bad (implicit)]
2337 2342 [log.branch|branch: foo]
2338 2343 [log.user|user: person]
2339 2344 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2340 2345 [log.summary|summary: new branch]
2341 2346
2342 2347 $ hg --color=debug log --debug -T bisect -r 0:4
2343 2348 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2344 2349 [log.bisect bisect.good|bisect: good (implicit)]
2345 2350 [log.phase|phase: public]
2346 2351 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2347 2352 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2348 2353 [ui.debug log.manifest|manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
2349 2354 [log.user|user: User Name <user@hostname>]
2350 2355 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2351 2356 [ui.debug log.files|files+: a]
2352 2357 [ui.debug log.extra|extra: branch=default]
2353 2358 [ui.note log.description|description:]
2354 2359 [ui.note log.description|line 1
2355 2360 line 2]
2356 2361
2357 2362
2358 2363 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2359 2364 [log.bisect bisect.good|bisect: good]
2360 2365 [log.phase|phase: public]
2361 2366 [log.parent changeset.public|parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2362 2367 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2363 2368 [ui.debug log.manifest|manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
2364 2369 [log.user|user: A. N. Other <other@place>]
2365 2370 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2366 2371 [ui.debug log.files|files+: b]
2367 2372 [ui.debug log.extra|extra: branch=default]
2368 2373 [ui.note log.description|description:]
2369 2374 [ui.note log.description|other 1
2370 2375 other 2
2371 2376
2372 2377 other 3]
2373 2378
2374 2379
2375 2380 [log.changeset changeset.public|changeset: 2:97054abb4ab824450e9164180baf491ae0078465]
2376 2381 [log.bisect bisect.untested|bisect: untested]
2377 2382 [log.phase|phase: public]
2378 2383 [log.parent changeset.public|parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2379 2384 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2380 2385 [ui.debug log.manifest|manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
2381 2386 [log.user|user: other@place]
2382 2387 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2383 2388 [ui.debug log.files|files+: c]
2384 2389 [ui.debug log.extra|extra: branch=default]
2385 2390 [ui.note log.description|description:]
2386 2391 [ui.note log.description|no person]
2387 2392
2388 2393
2389 2394 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2390 2395 [log.bisect bisect.bad|bisect: bad]
2391 2396 [log.phase|phase: public]
2392 2397 [log.parent changeset.public|parent: 2:97054abb4ab824450e9164180baf491ae0078465]
2393 2398 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2394 2399 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2395 2400 [log.user|user: person]
2396 2401 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2397 2402 [ui.debug log.files|files: c]
2398 2403 [ui.debug log.extra|extra: branch=default]
2399 2404 [ui.note log.description|description:]
2400 2405 [ui.note log.description|no user, no domain]
2401 2406
2402 2407
2403 2408 [log.changeset changeset.draft|changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
2404 2409 [log.bisect bisect.bad|bisect: bad (implicit)]
2405 2410 [log.branch|branch: foo]
2406 2411 [log.phase|phase: draft]
2407 2412 [log.parent changeset.public|parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2408 2413 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2409 2414 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2410 2415 [log.user|user: person]
2411 2416 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2412 2417 [ui.debug log.extra|extra: branch=foo]
2413 2418 [ui.note log.description|description:]
2414 2419 [ui.note log.description|new branch]
2415 2420
2416 2421
2417 2422 $ hg --color=debug log -v -T bisect -r 0:4
2418 2423 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2419 2424 [log.bisect bisect.good|bisect: good (implicit)]
2420 2425 [log.user|user: User Name <user@hostname>]
2421 2426 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2422 2427 [ui.note log.files|files: a]
2423 2428 [ui.note log.description|description:]
2424 2429 [ui.note log.description|line 1
2425 2430 line 2]
2426 2431
2427 2432
2428 2433 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2429 2434 [log.bisect bisect.good|bisect: good]
2430 2435 [log.user|user: A. N. Other <other@place>]
2431 2436 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2432 2437 [ui.note log.files|files: b]
2433 2438 [ui.note log.description|description:]
2434 2439 [ui.note log.description|other 1
2435 2440 other 2
2436 2441
2437 2442 other 3]
2438 2443
2439 2444
2440 2445 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2441 2446 [log.bisect bisect.untested|bisect: untested]
2442 2447 [log.user|user: other@place]
2443 2448 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2444 2449 [ui.note log.files|files: c]
2445 2450 [ui.note log.description|description:]
2446 2451 [ui.note log.description|no person]
2447 2452
2448 2453
2449 2454 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2450 2455 [log.bisect bisect.bad|bisect: bad]
2451 2456 [log.user|user: person]
2452 2457 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2453 2458 [ui.note log.files|files: c]
2454 2459 [ui.note log.description|description:]
2455 2460 [ui.note log.description|no user, no domain]
2456 2461
2457 2462
2458 2463 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2459 2464 [log.bisect bisect.bad|bisect: bad (implicit)]
2460 2465 [log.branch|branch: foo]
2461 2466 [log.user|user: person]
2462 2467 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2463 2468 [ui.note log.description|description:]
2464 2469 [ui.note log.description|new branch]
2465 2470
2466 2471
2467 2472 $ hg bisect --reset
2468 2473
2469 2474 Error on syntax:
2470 2475
2471 2476 $ echo 'x = "f' >> t
2472 2477 $ hg log
2473 2478 abort: t:3: unmatched quotes
2474 2479 [255]
2475 2480
2476 2481 $ hg log -T '{date'
2477 2482 hg: parse error at 1: unterminated template expansion
2478 2483 [255]
2479 2484
2480 2485 Behind the scenes, this will throw TypeError
2481 2486
2482 2487 $ hg log -l 3 --template '{date|obfuscate}\n'
2483 2488 abort: template filter 'obfuscate' is not compatible with keyword 'date'
2484 2489 [255]
2485 2490
2486 2491 Behind the scenes, this will throw a ValueError
2487 2492
2488 2493 $ hg log -l 3 --template 'line: {desc|shortdate}\n'
2489 2494 abort: template filter 'shortdate' is not compatible with keyword 'desc'
2490 2495 [255]
2491 2496
2492 2497 Behind the scenes, this will throw AttributeError
2493 2498
2494 2499 $ hg log -l 3 --template 'line: {date|escape}\n'
2495 2500 abort: template filter 'escape' is not compatible with keyword 'date'
2496 2501 [255]
2497 2502
2498 2503 $ hg log -l 3 --template 'line: {extras|localdate}\n'
2499 2504 hg: parse error: localdate expects a date information
2500 2505 [255]
2501 2506
2502 2507 Behind the scenes, this will throw ValueError
2503 2508
2504 2509 $ hg tip --template '{author|email|date}\n'
2505 2510 hg: parse error: date expects a date information
2506 2511 [255]
2507 2512
2508 2513 Error in nested template:
2509 2514
2510 2515 $ hg log -T '{"date'
2511 2516 hg: parse error at 2: unterminated string
2512 2517 [255]
2513 2518
2514 2519 $ hg log -T '{"foo{date|=}"}'
2515 2520 hg: parse error at 11: syntax error
2516 2521 [255]
2517 2522
2518 2523 Thrown an error if a template function doesn't exist
2519 2524
2520 2525 $ hg tip --template '{foo()}\n'
2521 2526 hg: parse error: unknown function 'foo'
2522 2527 [255]
2523 2528
2524 2529 Pass generator object created by template function to filter
2525 2530
2526 2531 $ hg log -l 1 --template '{if(author, author)|user}\n'
2527 2532 test
2528 2533
2529 2534 Test diff function:
2530 2535
2531 2536 $ hg diff -c 8
2532 2537 diff -r 29114dbae42b -r 95c24699272e fourth
2533 2538 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2534 2539 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2535 2540 @@ -0,0 +1,1 @@
2536 2541 +second
2537 2542 diff -r 29114dbae42b -r 95c24699272e second
2538 2543 --- a/second Mon Jan 12 13:46:40 1970 +0000
2539 2544 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2540 2545 @@ -1,1 +0,0 @@
2541 2546 -second
2542 2547 diff -r 29114dbae42b -r 95c24699272e third
2543 2548 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2544 2549 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2545 2550 @@ -0,0 +1,1 @@
2546 2551 +third
2547 2552
2548 2553 $ hg log -r 8 -T "{diff()}"
2549 2554 diff -r 29114dbae42b -r 95c24699272e fourth
2550 2555 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2551 2556 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2552 2557 @@ -0,0 +1,1 @@
2553 2558 +second
2554 2559 diff -r 29114dbae42b -r 95c24699272e second
2555 2560 --- a/second Mon Jan 12 13:46:40 1970 +0000
2556 2561 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2557 2562 @@ -1,1 +0,0 @@
2558 2563 -second
2559 2564 diff -r 29114dbae42b -r 95c24699272e third
2560 2565 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2561 2566 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2562 2567 @@ -0,0 +1,1 @@
2563 2568 +third
2564 2569
2565 2570 $ hg log -r 8 -T "{diff('glob:f*')}"
2566 2571 diff -r 29114dbae42b -r 95c24699272e fourth
2567 2572 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2568 2573 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2569 2574 @@ -0,0 +1,1 @@
2570 2575 +second
2571 2576
2572 2577 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
2573 2578 diff -r 29114dbae42b -r 95c24699272e second
2574 2579 --- a/second Mon Jan 12 13:46:40 1970 +0000
2575 2580 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2576 2581 @@ -1,1 +0,0 @@
2577 2582 -second
2578 2583 diff -r 29114dbae42b -r 95c24699272e third
2579 2584 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2580 2585 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2581 2586 @@ -0,0 +1,1 @@
2582 2587 +third
2583 2588
2584 2589 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
2585 2590 diff -r 29114dbae42b -r 95c24699272e fourth
2586 2591 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2587 2592 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2588 2593 @@ -0,0 +1,1 @@
2589 2594 +second
2590 2595
2591 2596 $ cd ..
2592 2597
2593 2598
2594 2599 latesttag:
2595 2600
2596 2601 $ hg init latesttag
2597 2602 $ cd latesttag
2598 2603
2599 2604 $ echo a > file
2600 2605 $ hg ci -Am a -d '0 0'
2601 2606 adding file
2602 2607
2603 2608 $ echo b >> file
2604 2609 $ hg ci -m b -d '1 0'
2605 2610
2606 2611 $ echo c >> head1
2607 2612 $ hg ci -Am h1c -d '2 0'
2608 2613 adding head1
2609 2614
2610 2615 $ hg update -q 1
2611 2616 $ echo d >> head2
2612 2617 $ hg ci -Am h2d -d '3 0'
2613 2618 adding head2
2614 2619 created new head
2615 2620
2616 2621 $ echo e >> head2
2617 2622 $ hg ci -m h2e -d '4 0'
2618 2623
2619 2624 $ hg merge -q
2620 2625 $ hg ci -m merge -d '5 -3600'
2621 2626
2622 2627 No tag set:
2623 2628
2624 2629 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2625 2630 5: null+5
2626 2631 4: null+4
2627 2632 3: null+3
2628 2633 2: null+3
2629 2634 1: null+2
2630 2635 0: null+1
2631 2636
2632 2637 One common tag: longest path wins:
2633 2638
2634 2639 $ hg tag -r 1 -m t1 -d '6 0' t1
2635 2640 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2636 2641 6: t1+4
2637 2642 5: t1+3
2638 2643 4: t1+2
2639 2644 3: t1+1
2640 2645 2: t1+1
2641 2646 1: t1+0
2642 2647 0: null+1
2643 2648
2644 2649 One ancestor tag: more recent wins:
2645 2650
2646 2651 $ hg tag -r 2 -m t2 -d '7 0' t2
2647 2652 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2648 2653 7: t2+3
2649 2654 6: t2+2
2650 2655 5: t2+1
2651 2656 4: t1+2
2652 2657 3: t1+1
2653 2658 2: t2+0
2654 2659 1: t1+0
2655 2660 0: null+1
2656 2661
2657 2662 Two branch tags: more recent wins:
2658 2663
2659 2664 $ hg tag -r 3 -m t3 -d '8 0' t3
2660 2665 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2661 2666 8: t3+5
2662 2667 7: t3+4
2663 2668 6: t3+3
2664 2669 5: t3+2
2665 2670 4: t3+1
2666 2671 3: t3+0
2667 2672 2: t2+0
2668 2673 1: t1+0
2669 2674 0: null+1
2670 2675
2671 2676 Merged tag overrides:
2672 2677
2673 2678 $ hg tag -r 5 -m t5 -d '9 0' t5
2674 2679 $ hg tag -r 3 -m at3 -d '10 0' at3
2675 2680 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2676 2681 10: t5+5
2677 2682 9: t5+4
2678 2683 8: t5+3
2679 2684 7: t5+2
2680 2685 6: t5+1
2681 2686 5: t5+0
2682 2687 4: at3:t3+1
2683 2688 3: at3:t3+0
2684 2689 2: t2+0
2685 2690 1: t1+0
2686 2691 0: null+1
2687 2692
2688 2693 $ cd ..
2689 2694
2690 2695
2691 2696 Style path expansion: issue1948 - ui.style option doesn't work on OSX
2692 2697 if it is a relative path
2693 2698
2694 2699 $ mkdir -p home/styles
2695 2700
2696 2701 $ cat > home/styles/teststyle <<EOF
2697 2702 > changeset = 'test {rev}:{node|short}\n'
2698 2703 > EOF
2699 2704
2700 2705 $ HOME=`pwd`/home; export HOME
2701 2706
2702 2707 $ cat > latesttag/.hg/hgrc <<EOF
2703 2708 > [ui]
2704 2709 > style = ~/styles/teststyle
2705 2710 > EOF
2706 2711
2707 2712 $ hg -R latesttag tip
2708 2713 test 10:9b4a630e5f5f
2709 2714
2710 2715 Test recursive showlist template (issue1989):
2711 2716
2712 2717 $ cat > style1989 <<EOF
2713 2718 > changeset = '{file_mods}{manifest}{extras}'
2714 2719 > file_mod = 'M|{author|person}\n'
2715 2720 > manifest = '{rev},{author}\n'
2716 2721 > extra = '{key}: {author}\n'
2717 2722 > EOF
2718 2723
2719 2724 $ hg -R latesttag log -r tip --style=style1989
2720 2725 M|test
2721 2726 10,test
2722 2727 branch: test
2723 2728
2724 2729 Test new-style inline templating:
2725 2730
2726 2731 $ hg log -R latesttag -r tip --template 'modified files: {file_mods % " {file}\n"}\n'
2727 2732 modified files: .hgtags
2728 2733
2729 2734 Test the sub function of templating for expansion:
2730 2735
2731 2736 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
2732 2737 xx
2733 2738
2734 2739 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
2735 2740 hg: parse error: sub got an invalid pattern: [
2736 2741 [255]
2737 2742 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
2738 2743 hg: parse error: sub got an invalid replacement: \1
2739 2744 [255]
2740 2745
2741 2746 Test the strip function with chars specified:
2742 2747
2743 2748 $ hg log -R latesttag --template '{desc}\n'
2744 2749 at3
2745 2750 t5
2746 2751 t3
2747 2752 t2
2748 2753 t1
2749 2754 merge
2750 2755 h2e
2751 2756 h2d
2752 2757 h1c
2753 2758 b
2754 2759 a
2755 2760
2756 2761 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
2757 2762 at3
2758 2763 5
2759 2764 3
2760 2765 2
2761 2766 1
2762 2767 merg
2763 2768 h2
2764 2769 h2d
2765 2770 h1c
2766 2771 b
2767 2772 a
2768 2773
2769 2774 Test date format:
2770 2775
2771 2776 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
2772 2777 date: 70 01 01 10 +0000
2773 2778 date: 70 01 01 09 +0000
2774 2779 date: 70 01 01 08 +0000
2775 2780 date: 70 01 01 07 +0000
2776 2781 date: 70 01 01 06 +0000
2777 2782 date: 70 01 01 05 +0100
2778 2783 date: 70 01 01 04 +0000
2779 2784 date: 70 01 01 03 +0000
2780 2785 date: 70 01 01 02 +0000
2781 2786 date: 70 01 01 01 +0000
2782 2787 date: 70 01 01 00 +0000
2783 2788
2784 2789 Test invalid date:
2785 2790
2786 2791 $ hg log -R latesttag -T '{date(rev)}\n'
2787 2792 hg: parse error: date expects a date information
2788 2793 [255]
2789 2794
2790 2795 Test integer literal:
2791 2796
2792 2797 $ hg log -Ra -r0 -T '{(0)}\n'
2793 2798 0
2794 2799 $ hg log -Ra -r0 -T '{(123)}\n'
2795 2800 123
2796 2801 $ hg log -Ra -r0 -T '{(-4)}\n'
2797 2802 -4
2798 2803 $ hg log -Ra -r0 -T '{(-)}\n'
2799 2804 hg: parse error at 2: integer literal without digits
2800 2805 [255]
2801 2806 $ hg log -Ra -r0 -T '{(-a)}\n'
2802 2807 hg: parse error at 2: integer literal without digits
2803 2808 [255]
2804 2809
2805 2810 top-level integer literal is interpreted as symbol (i.e. variable name):
2806 2811
2807 2812 $ hg log -Ra -r0 -T '{1}\n'
2808 2813
2809 2814 $ hg log -Ra -r0 -T '{if("t", "{1}")}\n'
2810 2815
2811 2816 $ hg log -Ra -r0 -T '{1|stringify}\n'
2812 2817
2813 2818
2814 2819 unless explicit symbol is expected:
2815 2820
2816 2821 $ hg log -Ra -r0 -T '{desc|1}\n'
2817 2822 hg: parse error: expected a symbol, got 'integer'
2818 2823 [255]
2819 2824 $ hg log -Ra -r0 -T '{1()}\n'
2820 2825 hg: parse error: expected a symbol, got 'integer'
2821 2826 [255]
2822 2827
2823 2828 Test string literal:
2824 2829
2825 2830 $ hg log -Ra -r0 -T '{"string with no template fragment"}\n'
2826 2831 string with no template fragment
2827 2832 $ hg log -Ra -r0 -T '{"template: {rev}"}\n'
2828 2833 template: 0
2829 2834 $ hg log -Ra -r0 -T '{r"rawstring: {rev}"}\n'
2830 2835 rawstring: {rev}
2831 2836
2832 2837 because map operation requires template, raw string can't be used
2833 2838
2834 2839 $ hg log -Ra -r0 -T '{files % r"rawstring"}\n'
2835 2840 hg: parse error: expected template specifier
2836 2841 [255]
2837 2842
2838 2843 Test string escaping:
2839 2844
2840 2845 $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2841 2846 >
2842 2847 <>\n<[>
2843 2848 <>\n<]>
2844 2849 <>\n<
2845 2850
2846 2851 $ hg log -R latesttag -r 0 \
2847 2852 > --config ui.logtemplate='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2848 2853 >
2849 2854 <>\n<[>
2850 2855 <>\n<]>
2851 2856 <>\n<
2852 2857
2853 2858 $ hg log -R latesttag -r 0 -T esc \
2854 2859 > --config templates.esc='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2855 2860 >
2856 2861 <>\n<[>
2857 2862 <>\n<]>
2858 2863 <>\n<
2859 2864
2860 2865 $ cat <<'EOF' > esctmpl
2861 2866 > changeset = '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
2862 2867 > EOF
2863 2868 $ hg log -R latesttag -r 0 --style ./esctmpl
2864 2869 >
2865 2870 <>\n<[>
2866 2871 <>\n<]>
2867 2872 <>\n<
2868 2873
2869 2874 Test string escaping of quotes:
2870 2875
2871 2876 $ hg log -Ra -r0 -T '{"\""}\n'
2872 2877 "
2873 2878 $ hg log -Ra -r0 -T '{"\\\""}\n'
2874 2879 \"
2875 2880 $ hg log -Ra -r0 -T '{r"\""}\n'
2876 2881 \"
2877 2882 $ hg log -Ra -r0 -T '{r"\\\""}\n'
2878 2883 \\\"
2879 2884
2880 2885
2881 2886 $ hg log -Ra -r0 -T '{"\""}\n'
2882 2887 "
2883 2888 $ hg log -Ra -r0 -T '{"\\\""}\n'
2884 2889 \"
2885 2890 $ hg log -Ra -r0 -T '{r"\""}\n'
2886 2891 \"
2887 2892 $ hg log -Ra -r0 -T '{r"\\\""}\n'
2888 2893 \\\"
2889 2894
2890 2895 Test exception in quoted template. single backslash before quotation mark is
2891 2896 stripped before parsing:
2892 2897
2893 2898 $ cat <<'EOF' > escquotetmpl
2894 2899 > changeset = "\" \\" \\\" \\\\" {files % \"{file}\"}\n"
2895 2900 > EOF
2896 2901 $ cd latesttag
2897 2902 $ hg log -r 2 --style ../escquotetmpl
2898 2903 " \" \" \\" head1
2899 2904
2900 2905 $ hg log -r 2 -T esc --config templates.esc='"{\"valid\"}\n"'
2901 2906 valid
2902 2907 $ hg log -r 2 -T esc --config templates.esc="'"'{\'"'"'valid\'"'"'}\n'"'"
2903 2908 valid
2904 2909
2905 2910 Test compatibility with 2.9.2-3.4 of escaped quoted strings in nested
2906 2911 _evalifliteral() templates (issue4733):
2907 2912
2908 2913 $ hg log -r 2 -T '{if(rev, "\"{rev}")}\n'
2909 2914 "2
2910 2915 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\"{rev}\")}")}\n'
2911 2916 "2
2912 2917 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\"{rev}\\\")}\")}")}\n'
2913 2918 "2
2914 2919
2915 2920 $ hg log -r 2 -T '{if(rev, "\\\"")}\n'
2916 2921 \"
2917 2922 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\\\\\"\")}")}\n'
2918 2923 \"
2919 2924 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
2920 2925 \"
2921 2926
2922 2927 $ hg log -r 2 -T '{if(rev, r"\\\"")}\n'
2923 2928 \\\"
2924 2929 $ hg log -r 2 -T '{if(rev, "{if(rev, r\"\\\\\\\"\")}")}\n'
2925 2930 \\\"
2926 2931 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, r\\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
2927 2932 \\\"
2928 2933
2929 2934 escaped single quotes and errors:
2930 2935
2931 2936 $ hg log -r 2 -T "{if(rev, '{if(rev, \'foo\')}')}"'\n'
2932 2937 foo
2933 2938 $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n'
2934 2939 foo
2935 2940 $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n'
2936 2941 hg: parse error at 21: unterminated string
2937 2942 [255]
2938 2943 $ hg log -r 2 -T '{if(rev, \"\\"")}\n'
2939 2944 hg: parse error: trailing \ in string
2940 2945 [255]
2941 2946 $ hg log -r 2 -T '{if(rev, r\"\\"")}\n'
2942 2947 hg: parse error: trailing \ in string
2943 2948 [255]
2944 2949
2945 2950 $ cd ..
2946 2951
2947 2952 Test leading backslashes:
2948 2953
2949 2954 $ cd latesttag
2950 2955 $ hg log -r 2 -T '\{rev} {files % "\{file}"}\n'
2951 2956 {rev} {file}
2952 2957 $ hg log -r 2 -T '\\{rev} {files % "\\{file}"}\n'
2953 2958 \2 \head1
2954 2959 $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"}\n'
2955 2960 \{rev} \{file}
2956 2961 $ cd ..
2957 2962
2958 2963 Test leading backslashes in "if" expression (issue4714):
2959 2964
2960 2965 $ cd latesttag
2961 2966 $ hg log -r 2 -T '{if("1", "\{rev}")} {if("1", r"\{rev}")}\n'
2962 2967 {rev} \{rev}
2963 2968 $ hg log -r 2 -T '{if("1", "\\{rev}")} {if("1", r"\\{rev}")}\n'
2964 2969 \2 \\{rev}
2965 2970 $ hg log -r 2 -T '{if("1", "\\\{rev}")} {if("1", r"\\\{rev}")}\n'
2966 2971 \{rev} \\\{rev}
2967 2972 $ cd ..
2968 2973
2969 2974 "string-escape"-ed "\x5c\x786e" becomes r"\x6e" (once) or r"n" (twice)
2970 2975
2971 2976 $ hg log -R a -r 0 --template '{if("1", "\x5c\x786e", "NG")}\n'
2972 2977 \x6e
2973 2978 $ hg log -R a -r 0 --template '{if("1", r"\x5c\x786e", "NG")}\n'
2974 2979 \x5c\x786e
2975 2980 $ hg log -R a -r 0 --template '{if("", "NG", "\x5c\x786e")}\n'
2976 2981 \x6e
2977 2982 $ hg log -R a -r 0 --template '{if("", "NG", r"\x5c\x786e")}\n'
2978 2983 \x5c\x786e
2979 2984
2980 2985 $ hg log -R a -r 2 --template '{ifeq("no perso\x6e", desc, "\x5c\x786e", "NG")}\n'
2981 2986 \x6e
2982 2987 $ hg log -R a -r 2 --template '{ifeq(r"no perso\x6e", desc, "NG", r"\x5c\x786e")}\n'
2983 2988 \x5c\x786e
2984 2989 $ hg log -R a -r 2 --template '{ifeq(desc, "no perso\x6e", "\x5c\x786e", "NG")}\n'
2985 2990 \x6e
2986 2991 $ hg log -R a -r 2 --template '{ifeq(desc, r"no perso\x6e", "NG", r"\x5c\x786e")}\n'
2987 2992 \x5c\x786e
2988 2993
2989 2994 $ hg log -R a -r 8 --template '{join(files, "\n")}\n'
2990 2995 fourth
2991 2996 second
2992 2997 third
2993 2998 $ hg log -R a -r 8 --template '{join(files, r"\n")}\n'
2994 2999 fourth\nsecond\nthird
2995 3000
2996 3001 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", "htm\x6c")}'
2997 3002 <p>
2998 3003 1st
2999 3004 </p>
3000 3005 <p>
3001 3006 2nd
3002 3007 </p>
3003 3008 $ hg log -R a -r 2 --template '{rstdoc(r"1st\n\n2nd", "html")}'
3004 3009 <p>
3005 3010 1st\n\n2nd
3006 3011 </p>
3007 3012 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", r"htm\x6c")}'
3008 3013 1st
3009 3014
3010 3015 2nd
3011 3016
3012 3017 $ hg log -R a -r 2 --template '{strip(desc, "\x6e")}\n'
3013 3018 o perso
3014 3019 $ hg log -R a -r 2 --template '{strip(desc, r"\x6e")}\n'
3015 3020 no person
3016 3021 $ hg log -R a -r 2 --template '{strip("no perso\x6e", "\x6e")}\n'
3017 3022 o perso
3018 3023 $ hg log -R a -r 2 --template '{strip(r"no perso\x6e", r"\x6e")}\n'
3019 3024 no perso
3020 3025
3021 3026 $ hg log -R a -r 2 --template '{sub("\\x6e", "\x2d", desc)}\n'
3022 3027 -o perso-
3023 3028 $ hg log -R a -r 2 --template '{sub(r"\\x6e", "-", desc)}\n'
3024 3029 no person
3025 3030 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", desc)}\n'
3026 3031 \x2do perso\x2d
3027 3032 $ hg log -R a -r 2 --template '{sub("n", "\x2d", "no perso\x6e")}\n'
3028 3033 -o perso-
3029 3034 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", r"no perso\x6e")}\n'
3030 3035 \x2do perso\x6e
3031 3036
3032 3037 $ hg log -R a -r 8 --template '{files % "{file}\n"}'
3033 3038 fourth
3034 3039 second
3035 3040 third
3036 3041
3037 3042 Test string escaping in nested expression:
3038 3043
3039 3044 $ hg log -R a -r 8 --template '{ifeq(r"\x6e", if("1", "\x5c\x786e"), join(files, "\x5c\x786e"))}\n'
3040 3045 fourth\x6esecond\x6ethird
3041 3046 $ hg log -R a -r 8 --template '{ifeq(if("1", r"\x6e"), "\x5c\x786e", join(files, "\x5c\x786e"))}\n'
3042 3047 fourth\x6esecond\x6ethird
3043 3048
3044 3049 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", "\x5c\x786e"))}\n'
3045 3050 fourth\x6esecond\x6ethird
3046 3051 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", r"\x5c\x786e"))}\n'
3047 3052 fourth\x5c\x786esecond\x5c\x786ethird
3048 3053
3049 3054 $ hg log -R a -r 3:4 --template '{rev}:{sub(if("1", "\x6e"), ifeq(branch, "foo", r"\x5c\x786e", "\x5c\x786e"), desc)}\n'
3050 3055 3:\x6eo user, \x6eo domai\x6e
3051 3056 4:\x5c\x786eew bra\x5c\x786ech
3052 3057
3053 3058 Test quotes in nested expression are evaluated just like a $(command)
3054 3059 substitution in POSIX shells:
3055 3060
3056 3061 $ hg log -R a -r 8 -T '{"{"{rev}:{node|short}"}"}\n'
3057 3062 8:95c24699272e
3058 3063 $ hg log -R a -r 8 -T '{"{"\{{rev}} \"{node|short}\""}"}\n'
3059 3064 {8} "95c24699272e"
3060 3065
3061 3066 Test recursive evaluation:
3062 3067
3063 3068 $ hg init r
3064 3069 $ cd r
3065 3070 $ echo a > a
3066 3071 $ hg ci -Am '{rev}'
3067 3072 adding a
3068 3073 $ hg log -r 0 --template '{if(rev, desc)}\n'
3069 3074 {rev}
3070 3075 $ hg log -r 0 --template '{if(rev, "{author} {rev}")}\n'
3071 3076 test 0
3072 3077
3073 3078 $ hg branch -q 'text.{rev}'
3074 3079 $ echo aa >> aa
3075 3080 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
3076 3081
3077 3082 $ hg log -l1 --template '{fill(desc, "20", author, branch)}'
3078 3083 {node|short}desc to
3079 3084 text.{rev}be wrapped
3080 3085 text.{rev}desc to be
3081 3086 text.{rev}wrapped (no-eol)
3082 3087 $ hg log -l1 --template '{fill(desc, "20", "{node|short}:", "text.{rev}:")}'
3083 3088 bcc7ff960b8e:desc to
3084 3089 text.1:be wrapped
3085 3090 text.1:desc to be
3086 3091 text.1:wrapped (no-eol)
3087 3092
3088 3093 $ hg log -l 1 --template '{sub(r"[0-9]", "-", author)}'
3089 3094 {node|short} (no-eol)
3090 3095 $ hg log -l 1 --template '{sub(r"[0-9]", "-", "{node|short}")}'
3091 3096 bcc-ff---b-e (no-eol)
3092 3097
3093 3098 $ cat >> .hg/hgrc <<EOF
3094 3099 > [extensions]
3095 3100 > color=
3096 3101 > [color]
3097 3102 > mode=ansi
3098 3103 > text.{rev} = red
3099 3104 > text.1 = green
3100 3105 > EOF
3101 3106 $ hg log --color=always -l 1 --template '{label(branch, "text\n")}'
3102 3107 \x1b[0;31mtext\x1b[0m (esc)
3103 3108 $ hg log --color=always -l 1 --template '{label("text.{rev}", "text\n")}'
3104 3109 \x1b[0;32mtext\x1b[0m (esc)
3105 3110
3106 3111 Test branches inside if statement:
3107 3112
3108 3113 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
3109 3114 no
3110 3115
3111 3116 Test get function:
3112 3117
3113 3118 $ hg log -r 0 --template '{get(extras, "branch")}\n'
3114 3119 default
3115 3120 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
3116 3121 hg: parse error: get() expects a dict as first argument
3117 3122 [255]
3118 3123
3119 3124 Test localdate(date, tz) function:
3120 3125
3121 3126 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
3122 3127 1970-01-01 09:00 +0900
3123 3128 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
3124 3129 1970-01-01 00:00 +0000
3125 3130 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
3126 3131 1970-01-01 02:00 +0200
3127 3132 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
3128 3133 1970-01-01 00:00 +0000
3129 3134 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
3130 3135 1970-01-01 00:00 +0000
3131 3136 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
3132 3137 hg: parse error: localdate expects a timezone
3133 3138 [255]
3134 3139 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
3135 3140 hg: parse error: localdate expects a timezone
3136 3141 [255]
3137 3142
3138 3143 Test shortest(node) function:
3139 3144
3140 3145 $ echo b > b
3141 3146 $ hg ci -qAm b
3142 3147 $ hg log --template '{shortest(node)}\n'
3143 3148 e777
3144 3149 bcc7
3145 3150 f776
3146 3151 $ hg log --template '{shortest(node, 10)}\n'
3147 3152 e777603221
3148 3153 bcc7ff960b
3149 3154 f7769ec2ab
3150 3155 $ hg log --template '{node|shortest}\n' -l1
3151 3156 e777
3152 3157
3153 3158 Test pad function
3154 3159
3155 3160 $ hg log --template '{pad(rev, 20)} {author|user}\n'
3156 3161 2 test
3157 3162 1 {node|short}
3158 3163 0 test
3159 3164
3160 3165 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
3161 3166 2 test
3162 3167 1 {node|short}
3163 3168 0 test
3164 3169
3165 3170 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
3166 3171 2------------------- test
3167 3172 1------------------- {node|short}
3168 3173 0------------------- test
3169 3174
3170 3175 Test template string in pad function
3171 3176
3172 3177 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
3173 3178 {0} test
3174 3179
3175 3180 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
3176 3181 \{rev} test
3177 3182
3178 3183 Test ifcontains function
3179 3184
3180 3185 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
3181 3186 2 is in the string
3182 3187 1 is not
3183 3188 0 is in the string
3184 3189
3185 3190 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
3186 3191 2 did not add a
3187 3192 1 did not add a
3188 3193 0 added a
3189 3194
3190 3195 Test revset function
3191 3196
3192 3197 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
3193 3198 2 current rev
3194 3199 1 not current rev
3195 3200 0 not current rev
3196 3201
3197 3202 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
3198 3203 2 match rev
3199 3204 1 match rev
3200 3205 0 not match rev
3201 3206
3202 3207 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
3203 3208 2 Parents: 1
3204 3209 1 Parents: 0
3205 3210 0 Parents:
3206 3211
3207 3212 $ cat >> .hg/hgrc <<EOF
3208 3213 > [revsetalias]
3209 3214 > myparents(\$1) = parents(\$1)
3210 3215 > EOF
3211 3216 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
3212 3217 2 Parents: 1
3213 3218 1 Parents: 0
3214 3219 0 Parents:
3215 3220
3216 3221 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
3217 3222 Rev: 2
3218 3223 Ancestor: 0
3219 3224 Ancestor: 1
3220 3225 Ancestor: 2
3221 3226
3222 3227 Rev: 1
3223 3228 Ancestor: 0
3224 3229 Ancestor: 1
3225 3230
3226 3231 Rev: 0
3227 3232 Ancestor: 0
3228 3233
3229 3234 $ hg log --template '{revset("TIP"|lower)}\n' -l1
3230 3235 2
3231 3236
3232 3237 Test active bookmark templating
3233 3238
3234 3239 $ hg book foo
3235 3240 $ hg book bar
3236 3241 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
3237 3242 2 bar* foo
3238 3243 1
3239 3244 0
3240 3245 $ hg log --template "{rev} {activebookmark}\n"
3241 3246 2 bar
3242 3247 1
3243 3248 0
3244 3249 $ hg bookmarks --inactive bar
3245 3250 $ hg log --template "{rev} {activebookmark}\n"
3246 3251 2
3247 3252 1
3248 3253 0
3249 3254 $ hg book -r1 baz
3250 3255 $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
3251 3256 2 bar foo
3252 3257 1 baz
3253 3258 0
3254 3259 $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
3255 3260 2 t
3256 3261 1 f
3257 3262 0 f
3258 3263
3259 3264 Test stringify on sub expressions
3260 3265
3261 3266 $ cd ..
3262 3267 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
3263 3268 fourth, second, third
3264 3269 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
3265 3270 abc
3266 3271
3267 3272 Test splitlines
3268 3273
3269 3274 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
3270 3275 @ foo Modify, add, remove, rename
3271 3276 |
3272 3277 o foo future
3273 3278 |
3274 3279 o foo third
3275 3280 |
3276 3281 o foo second
3277 3282
3278 3283 o foo merge
3279 3284 |\
3280 3285 | o foo new head
3281 3286 | |
3282 3287 o | foo new branch
3283 3288 |/
3284 3289 o foo no user, no domain
3285 3290 |
3286 3291 o foo no person
3287 3292 |
3288 3293 o foo other 1
3289 3294 | foo other 2
3290 3295 | foo
3291 3296 | foo other 3
3292 3297 o foo line 1
3293 3298 foo line 2
3294 3299
3295 3300 Test startswith
3296 3301 $ hg log -Gv -R a --template "{startswith(desc)}"
3297 3302 hg: parse error: startswith expects two arguments
3298 3303 [255]
3299 3304
3300 3305 $ hg log -Gv -R a --template "{startswith('line', desc)}"
3301 3306 @
3302 3307 |
3303 3308 o
3304 3309 |
3305 3310 o
3306 3311 |
3307 3312 o
3308 3313
3309 3314 o
3310 3315 |\
3311 3316 | o
3312 3317 | |
3313 3318 o |
3314 3319 |/
3315 3320 o
3316 3321 |
3317 3322 o
3318 3323 |
3319 3324 o
3320 3325 |
3321 3326 o line 1
3322 3327 line 2
3323 3328
3324 3329 Test bad template with better error message
3325 3330
3326 3331 $ hg log -Gv -R a --template '{desc|user()}'
3327 3332 hg: parse error: expected a symbol, got 'func'
3328 3333 [255]
3329 3334
3330 3335 Test word function (including index out of bounds graceful failure)
3331 3336
3332 3337 $ hg log -Gv -R a --template "{word('1', desc)}"
3333 3338 @ add,
3334 3339 |
3335 3340 o
3336 3341 |
3337 3342 o
3338 3343 |
3339 3344 o
3340 3345
3341 3346 o
3342 3347 |\
3343 3348 | o head
3344 3349 | |
3345 3350 o | branch
3346 3351 |/
3347 3352 o user,
3348 3353 |
3349 3354 o person
3350 3355 |
3351 3356 o 1
3352 3357 |
3353 3358 o 1
3354 3359
3355 3360
3356 3361 Test word third parameter used as splitter
3357 3362
3358 3363 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
3359 3364 @ M
3360 3365 |
3361 3366 o future
3362 3367 |
3363 3368 o third
3364 3369 |
3365 3370 o sec
3366 3371
3367 3372 o merge
3368 3373 |\
3369 3374 | o new head
3370 3375 | |
3371 3376 o | new branch
3372 3377 |/
3373 3378 o n
3374 3379 |
3375 3380 o n
3376 3381 |
3377 3382 o
3378 3383 |
3379 3384 o line 1
3380 3385 line 2
3381 3386
3382 3387 Test word error messages for not enough and too many arguments
3383 3388
3384 3389 $ hg log -Gv -R a --template "{word('0')}"
3385 3390 hg: parse error: word expects two or three arguments, got 1
3386 3391 [255]
3387 3392
3388 3393 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
3389 3394 hg: parse error: word expects two or three arguments, got 7
3390 3395 [255]
3391 3396
3392 3397 Test word for integer literal
3393 3398
3394 3399 $ hg log -R a --template "{word(2, desc)}\n" -r0
3395 3400 line
3396 3401
3397 3402 Test word for invalid numbers
3398 3403
3399 3404 $ hg log -Gv -R a --template "{word('a', desc)}"
3400 3405 hg: parse error: word expects an integer index
3401 3406 [255]
3402 3407
3403 3408 Test indent and not adding to empty lines
3404 3409
3405 3410 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
3406 3411 -----
3407 3412 > line 1
3408 3413 >> line 2
3409 3414 -----
3410 3415 > other 1
3411 3416 >> other 2
3412 3417
3413 3418 >> other 3
3414 3419
3415 3420 Test with non-strings like dates
3416 3421
3417 3422 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
3418 3423 1200000.00
3419 3424 1300000.00
3420 3425
3421 3426 Test broken string escapes:
3422 3427
3423 3428 $ hg log -T "bogus\\" -R a
3424 3429 hg: parse error: trailing \ in string
3425 3430 [255]
3426 3431 $ hg log -T "\\xy" -R a
3427 3432 hg: parse error: invalid \x escape
3428 3433 [255]
General Comments 0
You need to be logged in to leave comments. Login now