##// END OF EJS Templates
log: use revsetlang.formatspec() to concatenate list expression...
Yuya Nishihara -
r35662:7a0a90d6 default
parent child Browse files
Show More
@@ -1,3930 +1,3932 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 __future__ import absolute_import
9 9
10 10 import errno
11 11 import itertools
12 12 import os
13 13 import re
14 14 import tempfile
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 short,
22 22 )
23 23
24 24 from . import (
25 25 bookmarks,
26 26 changelog,
27 27 copies,
28 28 crecord as crecordmod,
29 29 dagop,
30 30 dirstateguard,
31 31 encoding,
32 32 error,
33 33 formatter,
34 34 graphmod,
35 35 match as matchmod,
36 36 mdiff,
37 37 obsolete,
38 38 patch,
39 39 pathutil,
40 40 pycompat,
41 41 registrar,
42 42 revlog,
43 43 revset,
44 revsetlang,
44 45 scmutil,
45 46 smartset,
46 47 templatekw,
47 48 templater,
48 49 util,
49 50 vfs as vfsmod,
50 51 )
51 52 stringio = util.stringio
52 53
53 54 # templates of common command options
54 55
55 56 dryrunopts = [
56 57 ('n', 'dry-run', None,
57 58 _('do not perform actions, just print output')),
58 59 ]
59 60
60 61 remoteopts = [
61 62 ('e', 'ssh', '',
62 63 _('specify ssh command to use'), _('CMD')),
63 64 ('', 'remotecmd', '',
64 65 _('specify hg command to run on the remote side'), _('CMD')),
65 66 ('', 'insecure', None,
66 67 _('do not verify server certificate (ignoring web.cacerts config)')),
67 68 ]
68 69
69 70 walkopts = [
70 71 ('I', 'include', [],
71 72 _('include names matching the given patterns'), _('PATTERN')),
72 73 ('X', 'exclude', [],
73 74 _('exclude names matching the given patterns'), _('PATTERN')),
74 75 ]
75 76
76 77 commitopts = [
77 78 ('m', 'message', '',
78 79 _('use text as commit message'), _('TEXT')),
79 80 ('l', 'logfile', '',
80 81 _('read commit message from file'), _('FILE')),
81 82 ]
82 83
83 84 commitopts2 = [
84 85 ('d', 'date', '',
85 86 _('record the specified date as commit date'), _('DATE')),
86 87 ('u', 'user', '',
87 88 _('record the specified user as committer'), _('USER')),
88 89 ]
89 90
90 91 # hidden for now
91 92 formatteropts = [
92 93 ('T', 'template', '',
93 94 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
94 95 ]
95 96
96 97 templateopts = [
97 98 ('', 'style', '',
98 99 _('display using template map file (DEPRECATED)'), _('STYLE')),
99 100 ('T', 'template', '',
100 101 _('display with template'), _('TEMPLATE')),
101 102 ]
102 103
103 104 logopts = [
104 105 ('p', 'patch', None, _('show patch')),
105 106 ('g', 'git', None, _('use git extended diff format')),
106 107 ('l', 'limit', '',
107 108 _('limit number of changes displayed'), _('NUM')),
108 109 ('M', 'no-merges', None, _('do not show merges')),
109 110 ('', 'stat', None, _('output diffstat-style summary of changes')),
110 111 ('G', 'graph', None, _("show the revision DAG")),
111 112 ] + templateopts
112 113
113 114 diffopts = [
114 115 ('a', 'text', None, _('treat all files as text')),
115 116 ('g', 'git', None, _('use git extended diff format')),
116 117 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
117 118 ('', 'nodates', None, _('omit dates from diff headers'))
118 119 ]
119 120
120 121 diffwsopts = [
121 122 ('w', 'ignore-all-space', None,
122 123 _('ignore white space when comparing lines')),
123 124 ('b', 'ignore-space-change', None,
124 125 _('ignore changes in the amount of white space')),
125 126 ('B', 'ignore-blank-lines', None,
126 127 _('ignore changes whose lines are all blank')),
127 128 ('Z', 'ignore-space-at-eol', None,
128 129 _('ignore changes in whitespace at EOL')),
129 130 ]
130 131
131 132 diffopts2 = [
132 133 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
133 134 ('p', 'show-function', None, _('show which function each change is in')),
134 135 ('', 'reverse', None, _('produce a diff that undoes the changes')),
135 136 ] + diffwsopts + [
136 137 ('U', 'unified', '',
137 138 _('number of lines of context to show'), _('NUM')),
138 139 ('', 'stat', None, _('output diffstat-style summary of changes')),
139 140 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
140 141 ]
141 142
142 143 mergetoolopts = [
143 144 ('t', 'tool', '', _('specify merge tool')),
144 145 ]
145 146
146 147 similarityopts = [
147 148 ('s', 'similarity', '',
148 149 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
149 150 ]
150 151
151 152 subrepoopts = [
152 153 ('S', 'subrepos', None,
153 154 _('recurse into subrepositories'))
154 155 ]
155 156
156 157 debugrevlogopts = [
157 158 ('c', 'changelog', False, _('open changelog')),
158 159 ('m', 'manifest', False, _('open manifest')),
159 160 ('', 'dir', '', _('open directory manifest')),
160 161 ]
161 162
162 163 # special string such that everything below this line will be ingored in the
163 164 # editor text
164 165 _linebelow = "^HG: ------------------------ >8 ------------------------$"
165 166
166 167 def ishunk(x):
167 168 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
168 169 return isinstance(x, hunkclasses)
169 170
170 171 def newandmodified(chunks, originalchunks):
171 172 newlyaddedandmodifiedfiles = set()
172 173 for chunk in chunks:
173 174 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
174 175 originalchunks:
175 176 newlyaddedandmodifiedfiles.add(chunk.header.filename())
176 177 return newlyaddedandmodifiedfiles
177 178
178 179 def parsealiases(cmd):
179 180 return cmd.lstrip("^").split("|")
180 181
181 182 def setupwrapcolorwrite(ui):
182 183 # wrap ui.write so diff output can be labeled/colorized
183 184 def wrapwrite(orig, *args, **kw):
184 185 label = kw.pop(r'label', '')
185 186 for chunk, l in patch.difflabel(lambda: args):
186 187 orig(chunk, label=label + l)
187 188
188 189 oldwrite = ui.write
189 190 def wrap(*args, **kwargs):
190 191 return wrapwrite(oldwrite, *args, **kwargs)
191 192 setattr(ui, 'write', wrap)
192 193 return oldwrite
193 194
194 195 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
195 196 if usecurses:
196 197 if testfile:
197 198 recordfn = crecordmod.testdecorator(testfile,
198 199 crecordmod.testchunkselector)
199 200 else:
200 201 recordfn = crecordmod.chunkselector
201 202
202 203 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
203 204
204 205 else:
205 206 return patch.filterpatch(ui, originalhunks, operation)
206 207
207 208 def recordfilter(ui, originalhunks, operation=None):
208 209 """ Prompts the user to filter the originalhunks and return a list of
209 210 selected hunks.
210 211 *operation* is used for to build ui messages to indicate the user what
211 212 kind of filtering they are doing: reverting, committing, shelving, etc.
212 213 (see patch.filterpatch).
213 214 """
214 215 usecurses = crecordmod.checkcurses(ui)
215 216 testfile = ui.config('experimental', 'crecordtest')
216 217 oldwrite = setupwrapcolorwrite(ui)
217 218 try:
218 219 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
219 220 testfile, operation)
220 221 finally:
221 222 ui.write = oldwrite
222 223 return newchunks, newopts
223 224
224 225 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
225 226 filterfn, *pats, **opts):
226 227 from . import merge as mergemod
227 228 opts = pycompat.byteskwargs(opts)
228 229 if not ui.interactive():
229 230 if cmdsuggest:
230 231 msg = _('running non-interactively, use %s instead') % cmdsuggest
231 232 else:
232 233 msg = _('running non-interactively')
233 234 raise error.Abort(msg)
234 235
235 236 # make sure username is set before going interactive
236 237 if not opts.get('user'):
237 238 ui.username() # raise exception, username not provided
238 239
239 240 def recordfunc(ui, repo, message, match, opts):
240 241 """This is generic record driver.
241 242
242 243 Its job is to interactively filter local changes, and
243 244 accordingly prepare working directory into a state in which the
244 245 job can be delegated to a non-interactive commit command such as
245 246 'commit' or 'qrefresh'.
246 247
247 248 After the actual job is done by non-interactive command, the
248 249 working directory is restored to its original state.
249 250
250 251 In the end we'll record interesting changes, and everything else
251 252 will be left in place, so the user can continue working.
252 253 """
253 254
254 255 checkunfinished(repo, commit=True)
255 256 wctx = repo[None]
256 257 merge = len(wctx.parents()) > 1
257 258 if merge:
258 259 raise error.Abort(_('cannot partially commit a merge '
259 260 '(use "hg commit" instead)'))
260 261
261 262 def fail(f, msg):
262 263 raise error.Abort('%s: %s' % (f, msg))
263 264
264 265 force = opts.get('force')
265 266 if not force:
266 267 vdirs = []
267 268 match.explicitdir = vdirs.append
268 269 match.bad = fail
269 270
270 271 status = repo.status(match=match)
271 272 if not force:
272 273 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
273 274 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
274 275 diffopts.nodates = True
275 276 diffopts.git = True
276 277 diffopts.showfunc = True
277 278 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
278 279 originalchunks = patch.parsepatch(originaldiff)
279 280
280 281 # 1. filter patch, since we are intending to apply subset of it
281 282 try:
282 283 chunks, newopts = filterfn(ui, originalchunks)
283 284 except error.PatchError as err:
284 285 raise error.Abort(_('error parsing patch: %s') % err)
285 286 opts.update(newopts)
286 287
287 288 # We need to keep a backup of files that have been newly added and
288 289 # modified during the recording process because there is a previous
289 290 # version without the edit in the workdir
290 291 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
291 292 contenders = set()
292 293 for h in chunks:
293 294 try:
294 295 contenders.update(set(h.files()))
295 296 except AttributeError:
296 297 pass
297 298
298 299 changed = status.modified + status.added + status.removed
299 300 newfiles = [f for f in changed if f in contenders]
300 301 if not newfiles:
301 302 ui.status(_('no changes to record\n'))
302 303 return 0
303 304
304 305 modified = set(status.modified)
305 306
306 307 # 2. backup changed files, so we can restore them in the end
307 308
308 309 if backupall:
309 310 tobackup = changed
310 311 else:
311 312 tobackup = [f for f in newfiles if f in modified or f in \
312 313 newlyaddedandmodifiedfiles]
313 314 backups = {}
314 315 if tobackup:
315 316 backupdir = repo.vfs.join('record-backups')
316 317 try:
317 318 os.mkdir(backupdir)
318 319 except OSError as err:
319 320 if err.errno != errno.EEXIST:
320 321 raise
321 322 try:
322 323 # backup continues
323 324 for f in tobackup:
324 325 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
325 326 dir=backupdir)
326 327 os.close(fd)
327 328 ui.debug('backup %r as %r\n' % (f, tmpname))
328 329 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
329 330 backups[f] = tmpname
330 331
331 332 fp = stringio()
332 333 for c in chunks:
333 334 fname = c.filename()
334 335 if fname in backups:
335 336 c.write(fp)
336 337 dopatch = fp.tell()
337 338 fp.seek(0)
338 339
339 340 # 2.5 optionally review / modify patch in text editor
340 341 if opts.get('review', False):
341 342 patchtext = (crecordmod.diffhelptext
342 343 + crecordmod.patchhelptext
343 344 + fp.read())
344 345 reviewedpatch = ui.edit(patchtext, "",
345 346 action="diff",
346 347 repopath=repo.path)
347 348 fp.truncate(0)
348 349 fp.write(reviewedpatch)
349 350 fp.seek(0)
350 351
351 352 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
352 353 # 3a. apply filtered patch to clean repo (clean)
353 354 if backups:
354 355 # Equivalent to hg.revert
355 356 m = scmutil.matchfiles(repo, backups.keys())
356 357 mergemod.update(repo, repo.dirstate.p1(),
357 358 False, True, matcher=m)
358 359
359 360 # 3b. (apply)
360 361 if dopatch:
361 362 try:
362 363 ui.debug('applying patch\n')
363 364 ui.debug(fp.getvalue())
364 365 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
365 366 except error.PatchError as err:
366 367 raise error.Abort(str(err))
367 368 del fp
368 369
369 370 # 4. We prepared working directory according to filtered
370 371 # patch. Now is the time to delegate the job to
371 372 # commit/qrefresh or the like!
372 373
373 374 # Make all of the pathnames absolute.
374 375 newfiles = [repo.wjoin(nf) for nf in newfiles]
375 376 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
376 377 finally:
377 378 # 5. finally restore backed-up files
378 379 try:
379 380 dirstate = repo.dirstate
380 381 for realname, tmpname in backups.iteritems():
381 382 ui.debug('restoring %r to %r\n' % (tmpname, realname))
382 383
383 384 if dirstate[realname] == 'n':
384 385 # without normallookup, restoring timestamp
385 386 # may cause partially committed files
386 387 # to be treated as unmodified
387 388 dirstate.normallookup(realname)
388 389
389 390 # copystat=True here and above are a hack to trick any
390 391 # editors that have f open that we haven't modified them.
391 392 #
392 393 # Also note that this racy as an editor could notice the
393 394 # file's mtime before we've finished writing it.
394 395 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
395 396 os.unlink(tmpname)
396 397 if tobackup:
397 398 os.rmdir(backupdir)
398 399 except OSError:
399 400 pass
400 401
401 402 def recordinwlock(ui, repo, message, match, opts):
402 403 with repo.wlock():
403 404 return recordfunc(ui, repo, message, match, opts)
404 405
405 406 return commit(ui, repo, recordinwlock, pats, opts)
406 407
407 408 class dirnode(object):
408 409 """
409 410 Represent a directory in user working copy with information required for
410 411 the purpose of tersing its status.
411 412
412 413 path is the path to the directory
413 414
414 415 statuses is a set of statuses of all files in this directory (this includes
415 416 all the files in all the subdirectories too)
416 417
417 418 files is a list of files which are direct child of this directory
418 419
419 420 subdirs is a dictionary of sub-directory name as the key and it's own
420 421 dirnode object as the value
421 422 """
422 423
423 424 def __init__(self, dirpath):
424 425 self.path = dirpath
425 426 self.statuses = set([])
426 427 self.files = []
427 428 self.subdirs = {}
428 429
429 430 def _addfileindir(self, filename, status):
430 431 """Add a file in this directory as a direct child."""
431 432 self.files.append((filename, status))
432 433
433 434 def addfile(self, filename, status):
434 435 """
435 436 Add a file to this directory or to its direct parent directory.
436 437
437 438 If the file is not direct child of this directory, we traverse to the
438 439 directory of which this file is a direct child of and add the file
439 440 there.
440 441 """
441 442
442 443 # the filename contains a path separator, it means it's not the direct
443 444 # child of this directory
444 445 if '/' in filename:
445 446 subdir, filep = filename.split('/', 1)
446 447
447 448 # does the dirnode object for subdir exists
448 449 if subdir not in self.subdirs:
449 450 subdirpath = os.path.join(self.path, subdir)
450 451 self.subdirs[subdir] = dirnode(subdirpath)
451 452
452 453 # try adding the file in subdir
453 454 self.subdirs[subdir].addfile(filep, status)
454 455
455 456 else:
456 457 self._addfileindir(filename, status)
457 458
458 459 if status not in self.statuses:
459 460 self.statuses.add(status)
460 461
461 462 def iterfilepaths(self):
462 463 """Yield (status, path) for files directly under this directory."""
463 464 for f, st in self.files:
464 465 yield st, os.path.join(self.path, f)
465 466
466 467 def tersewalk(self, terseargs):
467 468 """
468 469 Yield (status, path) obtained by processing the status of this
469 470 dirnode.
470 471
471 472 terseargs is the string of arguments passed by the user with `--terse`
472 473 flag.
473 474
474 475 Following are the cases which can happen:
475 476
476 477 1) All the files in the directory (including all the files in its
477 478 subdirectories) share the same status and the user has asked us to terse
478 479 that status. -> yield (status, dirpath)
479 480
480 481 2) Otherwise, we do following:
481 482
482 483 a) Yield (status, filepath) for all the files which are in this
483 484 directory (only the ones in this directory, not the subdirs)
484 485
485 486 b) Recurse the function on all the subdirectories of this
486 487 directory
487 488 """
488 489
489 490 if len(self.statuses) == 1:
490 491 onlyst = self.statuses.pop()
491 492
492 493 # Making sure we terse only when the status abbreviation is
493 494 # passed as terse argument
494 495 if onlyst in terseargs:
495 496 yield onlyst, self.path + pycompat.ossep
496 497 return
497 498
498 499 # add the files to status list
499 500 for st, fpath in self.iterfilepaths():
500 501 yield st, fpath
501 502
502 503 #recurse on the subdirs
503 504 for dirobj in self.subdirs.values():
504 505 for st, fpath in dirobj.tersewalk(terseargs):
505 506 yield st, fpath
506 507
507 508 def tersedir(statuslist, terseargs):
508 509 """
509 510 Terse the status if all the files in a directory shares the same status.
510 511
511 512 statuslist is scmutil.status() object which contains a list of files for
512 513 each status.
513 514 terseargs is string which is passed by the user as the argument to `--terse`
514 515 flag.
515 516
516 517 The function makes a tree of objects of dirnode class, and at each node it
517 518 stores the information required to know whether we can terse a certain
518 519 directory or not.
519 520 """
520 521 # the order matters here as that is used to produce final list
521 522 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
522 523
523 524 # checking the argument validity
524 525 for s in pycompat.bytestr(terseargs):
525 526 if s not in allst:
526 527 raise error.Abort(_("'%s' not recognized") % s)
527 528
528 529 # creating a dirnode object for the root of the repo
529 530 rootobj = dirnode('')
530 531 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
531 532 'ignored', 'removed')
532 533
533 534 tersedict = {}
534 535 for attrname in pstatus:
535 536 statuschar = attrname[0:1]
536 537 for f in getattr(statuslist, attrname):
537 538 rootobj.addfile(f, statuschar)
538 539 tersedict[statuschar] = []
539 540
540 541 # we won't be tersing the root dir, so add files in it
541 542 for st, fpath in rootobj.iterfilepaths():
542 543 tersedict[st].append(fpath)
543 544
544 545 # process each sub-directory and build tersedict
545 546 for subdir in rootobj.subdirs.values():
546 547 for st, f in subdir.tersewalk(terseargs):
547 548 tersedict[st].append(f)
548 549
549 550 tersedlist = []
550 551 for st in allst:
551 552 tersedict[st].sort()
552 553 tersedlist.append(tersedict[st])
553 554
554 555 return tersedlist
555 556
556 557 def _commentlines(raw):
557 558 '''Surround lineswith a comment char and a new line'''
558 559 lines = raw.splitlines()
559 560 commentedlines = ['# %s' % line for line in lines]
560 561 return '\n'.join(commentedlines) + '\n'
561 562
562 563 def _conflictsmsg(repo):
563 564 # avoid merge cycle
564 565 from . import merge as mergemod
565 566 mergestate = mergemod.mergestate.read(repo)
566 567 if not mergestate.active():
567 568 return
568 569
569 570 m = scmutil.match(repo[None])
570 571 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
571 572 if unresolvedlist:
572 573 mergeliststr = '\n'.join(
573 574 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
574 575 for path in unresolvedlist])
575 576 msg = _('''Unresolved merge conflicts:
576 577
577 578 %s
578 579
579 580 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
580 581 else:
581 582 msg = _('No unresolved merge conflicts.')
582 583
583 584 return _commentlines(msg)
584 585
585 586 def _helpmessage(continuecmd, abortcmd):
586 587 msg = _('To continue: %s\n'
587 588 'To abort: %s') % (continuecmd, abortcmd)
588 589 return _commentlines(msg)
589 590
590 591 def _rebasemsg():
591 592 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
592 593
593 594 def _histeditmsg():
594 595 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
595 596
596 597 def _unshelvemsg():
597 598 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
598 599
599 600 def _updatecleanmsg(dest=None):
600 601 warning = _('warning: this will discard uncommitted changes')
601 602 return 'hg update --clean %s (%s)' % (dest or '.', warning)
602 603
603 604 def _graftmsg():
604 605 # tweakdefaults requires `update` to have a rev hence the `.`
605 606 return _helpmessage('hg graft --continue', _updatecleanmsg())
606 607
607 608 def _mergemsg():
608 609 # tweakdefaults requires `update` to have a rev hence the `.`
609 610 return _helpmessage('hg commit', _updatecleanmsg())
610 611
611 612 def _bisectmsg():
612 613 msg = _('To mark the changeset good: hg bisect --good\n'
613 614 'To mark the changeset bad: hg bisect --bad\n'
614 615 'To abort: hg bisect --reset\n')
615 616 return _commentlines(msg)
616 617
617 618 def fileexistspredicate(filename):
618 619 return lambda repo: repo.vfs.exists(filename)
619 620
620 621 def _mergepredicate(repo):
621 622 return len(repo[None].parents()) > 1
622 623
623 624 STATES = (
624 625 # (state, predicate to detect states, helpful message function)
625 626 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
626 627 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
627 628 ('graft', fileexistspredicate('graftstate'), _graftmsg),
628 629 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
629 630 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
630 631 # The merge state is part of a list that will be iterated over.
631 632 # They need to be last because some of the other unfinished states may also
632 633 # be in a merge or update state (eg. rebase, histedit, graft, etc).
633 634 # We want those to have priority.
634 635 ('merge', _mergepredicate, _mergemsg),
635 636 )
636 637
637 638 def _getrepostate(repo):
638 639 # experimental config: commands.status.skipstates
639 640 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
640 641 for state, statedetectionpredicate, msgfn in STATES:
641 642 if state in skip:
642 643 continue
643 644 if statedetectionpredicate(repo):
644 645 return (state, statedetectionpredicate, msgfn)
645 646
646 647 def morestatus(repo, fm):
647 648 statetuple = _getrepostate(repo)
648 649 label = 'status.morestatus'
649 650 if statetuple:
650 651 fm.startitem()
651 652 state, statedetectionpredicate, helpfulmsg = statetuple
652 653 statemsg = _('The repository is in an unfinished *%s* state.') % state
653 654 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
654 655 conmsg = _conflictsmsg(repo)
655 656 if conmsg:
656 657 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
657 658 if helpfulmsg:
658 659 helpmsg = helpfulmsg()
659 660 fm.write('helpmsg', '%s\n', helpmsg, label=label)
660 661
661 662 def findpossible(cmd, table, strict=False):
662 663 """
663 664 Return cmd -> (aliases, command table entry)
664 665 for each matching command.
665 666 Return debug commands (or their aliases) only if no normal command matches.
666 667 """
667 668 choice = {}
668 669 debugchoice = {}
669 670
670 671 if cmd in table:
671 672 # short-circuit exact matches, "log" alias beats "^log|history"
672 673 keys = [cmd]
673 674 else:
674 675 keys = table.keys()
675 676
676 677 allcmds = []
677 678 for e in keys:
678 679 aliases = parsealiases(e)
679 680 allcmds.extend(aliases)
680 681 found = None
681 682 if cmd in aliases:
682 683 found = cmd
683 684 elif not strict:
684 685 for a in aliases:
685 686 if a.startswith(cmd):
686 687 found = a
687 688 break
688 689 if found is not None:
689 690 if aliases[0].startswith("debug") or found.startswith("debug"):
690 691 debugchoice[found] = (aliases, table[e])
691 692 else:
692 693 choice[found] = (aliases, table[e])
693 694
694 695 if not choice and debugchoice:
695 696 choice = debugchoice
696 697
697 698 return choice, allcmds
698 699
699 700 def findcmd(cmd, table, strict=True):
700 701 """Return (aliases, command table entry) for command string."""
701 702 choice, allcmds = findpossible(cmd, table, strict)
702 703
703 704 if cmd in choice:
704 705 return choice[cmd]
705 706
706 707 if len(choice) > 1:
707 708 clist = sorted(choice)
708 709 raise error.AmbiguousCommand(cmd, clist)
709 710
710 711 if choice:
711 712 return list(choice.values())[0]
712 713
713 714 raise error.UnknownCommand(cmd, allcmds)
714 715
715 716 def findrepo(p):
716 717 while not os.path.isdir(os.path.join(p, ".hg")):
717 718 oldp, p = p, os.path.dirname(p)
718 719 if p == oldp:
719 720 return None
720 721
721 722 return p
722 723
723 724 def bailifchanged(repo, merge=True, hint=None):
724 725 """ enforce the precondition that working directory must be clean.
725 726
726 727 'merge' can be set to false if a pending uncommitted merge should be
727 728 ignored (such as when 'update --check' runs).
728 729
729 730 'hint' is the usual hint given to Abort exception.
730 731 """
731 732
732 733 if merge and repo.dirstate.p2() != nullid:
733 734 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
734 735 modified, added, removed, deleted = repo.status()[:4]
735 736 if modified or added or removed or deleted:
736 737 raise error.Abort(_('uncommitted changes'), hint=hint)
737 738 ctx = repo[None]
738 739 for s in sorted(ctx.substate):
739 740 ctx.sub(s).bailifchanged(hint=hint)
740 741
741 742 def logmessage(ui, opts):
742 743 """ get the log message according to -m and -l option """
743 744 message = opts.get('message')
744 745 logfile = opts.get('logfile')
745 746
746 747 if message and logfile:
747 748 raise error.Abort(_('options --message and --logfile are mutually '
748 749 'exclusive'))
749 750 if not message and logfile:
750 751 try:
751 752 if isstdiofilename(logfile):
752 753 message = ui.fin.read()
753 754 else:
754 755 message = '\n'.join(util.readfile(logfile).splitlines())
755 756 except IOError as inst:
756 757 raise error.Abort(_("can't read commit message '%s': %s") %
757 758 (logfile, encoding.strtolocal(inst.strerror)))
758 759 return message
759 760
760 761 def mergeeditform(ctxorbool, baseformname):
761 762 """return appropriate editform name (referencing a committemplate)
762 763
763 764 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
764 765 merging is committed.
765 766
766 767 This returns baseformname with '.merge' appended if it is a merge,
767 768 otherwise '.normal' is appended.
768 769 """
769 770 if isinstance(ctxorbool, bool):
770 771 if ctxorbool:
771 772 return baseformname + ".merge"
772 773 elif 1 < len(ctxorbool.parents()):
773 774 return baseformname + ".merge"
774 775
775 776 return baseformname + ".normal"
776 777
777 778 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
778 779 editform='', **opts):
779 780 """get appropriate commit message editor according to '--edit' option
780 781
781 782 'finishdesc' is a function to be called with edited commit message
782 783 (= 'description' of the new changeset) just after editing, but
783 784 before checking empty-ness. It should return actual text to be
784 785 stored into history. This allows to change description before
785 786 storing.
786 787
787 788 'extramsg' is a extra message to be shown in the editor instead of
788 789 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
789 790 is automatically added.
790 791
791 792 'editform' is a dot-separated list of names, to distinguish
792 793 the purpose of commit text editing.
793 794
794 795 'getcommiteditor' returns 'commitforceeditor' regardless of
795 796 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
796 797 they are specific for usage in MQ.
797 798 """
798 799 if edit or finishdesc or extramsg:
799 800 return lambda r, c, s: commitforceeditor(r, c, s,
800 801 finishdesc=finishdesc,
801 802 extramsg=extramsg,
802 803 editform=editform)
803 804 elif editform:
804 805 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
805 806 else:
806 807 return commiteditor
807 808
808 809 def loglimit(opts):
809 810 """get the log limit according to option -l/--limit"""
810 811 limit = opts.get('limit')
811 812 if limit:
812 813 try:
813 814 limit = int(limit)
814 815 except ValueError:
815 816 raise error.Abort(_('limit must be a positive integer'))
816 817 if limit <= 0:
817 818 raise error.Abort(_('limit must be positive'))
818 819 else:
819 820 limit = None
820 821 return limit
821 822
822 823 def makefilename(repo, pat, node, desc=None,
823 824 total=None, seqno=None, revwidth=None, pathname=None):
824 825 node_expander = {
825 826 'H': lambda: hex(node),
826 827 'R': lambda: '%d' % repo.changelog.rev(node),
827 828 'h': lambda: short(node),
828 829 'm': lambda: re.sub('[^\w]', '_', desc or '')
829 830 }
830 831 expander = {
831 832 '%': lambda: '%',
832 833 'b': lambda: os.path.basename(repo.root),
833 834 }
834 835
835 836 try:
836 837 if node:
837 838 expander.update(node_expander)
838 839 if node:
839 840 expander['r'] = (lambda:
840 841 ('%d' % repo.changelog.rev(node)).zfill(revwidth or 0))
841 842 if total is not None:
842 843 expander['N'] = lambda: '%d' % total
843 844 if seqno is not None:
844 845 expander['n'] = lambda: '%d' % seqno
845 846 if total is not None and seqno is not None:
846 847 expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total)))
847 848 if pathname is not None:
848 849 expander['s'] = lambda: os.path.basename(pathname)
849 850 expander['d'] = lambda: os.path.dirname(pathname) or '.'
850 851 expander['p'] = lambda: pathname
851 852
852 853 newname = []
853 854 patlen = len(pat)
854 855 i = 0
855 856 while i < patlen:
856 857 c = pat[i:i + 1]
857 858 if c == '%':
858 859 i += 1
859 860 c = pat[i:i + 1]
860 861 c = expander[c]()
861 862 newname.append(c)
862 863 i += 1
863 864 return ''.join(newname)
864 865 except KeyError as inst:
865 866 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
866 867 inst.args[0])
867 868
868 869 def isstdiofilename(pat):
869 870 """True if the given pat looks like a filename denoting stdin/stdout"""
870 871 return not pat or pat == '-'
871 872
872 873 class _unclosablefile(object):
873 874 def __init__(self, fp):
874 875 self._fp = fp
875 876
876 877 def close(self):
877 878 pass
878 879
879 880 def __iter__(self):
880 881 return iter(self._fp)
881 882
882 883 def __getattr__(self, attr):
883 884 return getattr(self._fp, attr)
884 885
885 886 def __enter__(self):
886 887 return self
887 888
888 889 def __exit__(self, exc_type, exc_value, exc_tb):
889 890 pass
890 891
891 892 def makefileobj(repo, pat, node=None, desc=None, total=None,
892 893 seqno=None, revwidth=None, mode='wb', modemap=None,
893 894 pathname=None):
894 895
895 896 writable = mode not in ('r', 'rb')
896 897
897 898 if isstdiofilename(pat):
898 899 if writable:
899 900 fp = repo.ui.fout
900 901 else:
901 902 fp = repo.ui.fin
902 903 return _unclosablefile(fp)
903 904 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
904 905 if modemap is not None:
905 906 mode = modemap.get(fn, mode)
906 907 if mode == 'wb':
907 908 modemap[fn] = 'ab'
908 909 return open(fn, mode)
909 910
910 911 def openrevlog(repo, cmd, file_, opts):
911 912 """opens the changelog, manifest, a filelog or a given revlog"""
912 913 cl = opts['changelog']
913 914 mf = opts['manifest']
914 915 dir = opts['dir']
915 916 msg = None
916 917 if cl and mf:
917 918 msg = _('cannot specify --changelog and --manifest at the same time')
918 919 elif cl and dir:
919 920 msg = _('cannot specify --changelog and --dir at the same time')
920 921 elif cl or mf or dir:
921 922 if file_:
922 923 msg = _('cannot specify filename with --changelog or --manifest')
923 924 elif not repo:
924 925 msg = _('cannot specify --changelog or --manifest or --dir '
925 926 'without a repository')
926 927 if msg:
927 928 raise error.Abort(msg)
928 929
929 930 r = None
930 931 if repo:
931 932 if cl:
932 933 r = repo.unfiltered().changelog
933 934 elif dir:
934 935 if 'treemanifest' not in repo.requirements:
935 936 raise error.Abort(_("--dir can only be used on repos with "
936 937 "treemanifest enabled"))
937 938 dirlog = repo.manifestlog._revlog.dirlog(dir)
938 939 if len(dirlog):
939 940 r = dirlog
940 941 elif mf:
941 942 r = repo.manifestlog._revlog
942 943 elif file_:
943 944 filelog = repo.file(file_)
944 945 if len(filelog):
945 946 r = filelog
946 947 if not r:
947 948 if not file_:
948 949 raise error.CommandError(cmd, _('invalid arguments'))
949 950 if not os.path.isfile(file_):
950 951 raise error.Abort(_("revlog '%s' not found") % file_)
951 952 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
952 953 file_[:-2] + ".i")
953 954 return r
954 955
955 956 def copy(ui, repo, pats, opts, rename=False):
956 957 # called with the repo lock held
957 958 #
958 959 # hgsep => pathname that uses "/" to separate directories
959 960 # ossep => pathname that uses os.sep to separate directories
960 961 cwd = repo.getcwd()
961 962 targets = {}
962 963 after = opts.get("after")
963 964 dryrun = opts.get("dry_run")
964 965 wctx = repo[None]
965 966
966 967 def walkpat(pat):
967 968 srcs = []
968 969 if after:
969 970 badstates = '?'
970 971 else:
971 972 badstates = '?r'
972 973 m = scmutil.match(wctx, [pat], opts, globbed=True)
973 974 for abs in wctx.walk(m):
974 975 state = repo.dirstate[abs]
975 976 rel = m.rel(abs)
976 977 exact = m.exact(abs)
977 978 if state in badstates:
978 979 if exact and state == '?':
979 980 ui.warn(_('%s: not copying - file is not managed\n') % rel)
980 981 if exact and state == 'r':
981 982 ui.warn(_('%s: not copying - file has been marked for'
982 983 ' remove\n') % rel)
983 984 continue
984 985 # abs: hgsep
985 986 # rel: ossep
986 987 srcs.append((abs, rel, exact))
987 988 return srcs
988 989
989 990 # abssrc: hgsep
990 991 # relsrc: ossep
991 992 # otarget: ossep
992 993 def copyfile(abssrc, relsrc, otarget, exact):
993 994 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
994 995 if '/' in abstarget:
995 996 # We cannot normalize abstarget itself, this would prevent
996 997 # case only renames, like a => A.
997 998 abspath, absname = abstarget.rsplit('/', 1)
998 999 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
999 1000 reltarget = repo.pathto(abstarget, cwd)
1000 1001 target = repo.wjoin(abstarget)
1001 1002 src = repo.wjoin(abssrc)
1002 1003 state = repo.dirstate[abstarget]
1003 1004
1004 1005 scmutil.checkportable(ui, abstarget)
1005 1006
1006 1007 # check for collisions
1007 1008 prevsrc = targets.get(abstarget)
1008 1009 if prevsrc is not None:
1009 1010 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1010 1011 (reltarget, repo.pathto(abssrc, cwd),
1011 1012 repo.pathto(prevsrc, cwd)))
1012 1013 return
1013 1014
1014 1015 # check for overwrites
1015 1016 exists = os.path.lexists(target)
1016 1017 samefile = False
1017 1018 if exists and abssrc != abstarget:
1018 1019 if (repo.dirstate.normalize(abssrc) ==
1019 1020 repo.dirstate.normalize(abstarget)):
1020 1021 if not rename:
1021 1022 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1022 1023 return
1023 1024 exists = False
1024 1025 samefile = True
1025 1026
1026 1027 if not after and exists or after and state in 'mn':
1027 1028 if not opts['force']:
1028 1029 if state in 'mn':
1029 1030 msg = _('%s: not overwriting - file already committed\n')
1030 1031 if after:
1031 1032 flags = '--after --force'
1032 1033 else:
1033 1034 flags = '--force'
1034 1035 if rename:
1035 1036 hint = _('(hg rename %s to replace the file by '
1036 1037 'recording a rename)\n') % flags
1037 1038 else:
1038 1039 hint = _('(hg copy %s to replace the file by '
1039 1040 'recording a copy)\n') % flags
1040 1041 else:
1041 1042 msg = _('%s: not overwriting - file exists\n')
1042 1043 if rename:
1043 1044 hint = _('(hg rename --after to record the rename)\n')
1044 1045 else:
1045 1046 hint = _('(hg copy --after to record the copy)\n')
1046 1047 ui.warn(msg % reltarget)
1047 1048 ui.warn(hint)
1048 1049 return
1049 1050
1050 1051 if after:
1051 1052 if not exists:
1052 1053 if rename:
1053 1054 ui.warn(_('%s: not recording move - %s does not exist\n') %
1054 1055 (relsrc, reltarget))
1055 1056 else:
1056 1057 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1057 1058 (relsrc, reltarget))
1058 1059 return
1059 1060 elif not dryrun:
1060 1061 try:
1061 1062 if exists:
1062 1063 os.unlink(target)
1063 1064 targetdir = os.path.dirname(target) or '.'
1064 1065 if not os.path.isdir(targetdir):
1065 1066 os.makedirs(targetdir)
1066 1067 if samefile:
1067 1068 tmp = target + "~hgrename"
1068 1069 os.rename(src, tmp)
1069 1070 os.rename(tmp, target)
1070 1071 else:
1071 1072 util.copyfile(src, target)
1072 1073 srcexists = True
1073 1074 except IOError as inst:
1074 1075 if inst.errno == errno.ENOENT:
1075 1076 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1076 1077 srcexists = False
1077 1078 else:
1078 1079 ui.warn(_('%s: cannot copy - %s\n') %
1079 1080 (relsrc, encoding.strtolocal(inst.strerror)))
1080 1081 return True # report a failure
1081 1082
1082 1083 if ui.verbose or not exact:
1083 1084 if rename:
1084 1085 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1085 1086 else:
1086 1087 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1087 1088
1088 1089 targets[abstarget] = abssrc
1089 1090
1090 1091 # fix up dirstate
1091 1092 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1092 1093 dryrun=dryrun, cwd=cwd)
1093 1094 if rename and not dryrun:
1094 1095 if not after and srcexists and not samefile:
1095 1096 repo.wvfs.unlinkpath(abssrc)
1096 1097 wctx.forget([abssrc])
1097 1098
1098 1099 # pat: ossep
1099 1100 # dest ossep
1100 1101 # srcs: list of (hgsep, hgsep, ossep, bool)
1101 1102 # return: function that takes hgsep and returns ossep
1102 1103 def targetpathfn(pat, dest, srcs):
1103 1104 if os.path.isdir(pat):
1104 1105 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1105 1106 abspfx = util.localpath(abspfx)
1106 1107 if destdirexists:
1107 1108 striplen = len(os.path.split(abspfx)[0])
1108 1109 else:
1109 1110 striplen = len(abspfx)
1110 1111 if striplen:
1111 1112 striplen += len(pycompat.ossep)
1112 1113 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1113 1114 elif destdirexists:
1114 1115 res = lambda p: os.path.join(dest,
1115 1116 os.path.basename(util.localpath(p)))
1116 1117 else:
1117 1118 res = lambda p: dest
1118 1119 return res
1119 1120
1120 1121 # pat: ossep
1121 1122 # dest ossep
1122 1123 # srcs: list of (hgsep, hgsep, ossep, bool)
1123 1124 # return: function that takes hgsep and returns ossep
1124 1125 def targetpathafterfn(pat, dest, srcs):
1125 1126 if matchmod.patkind(pat):
1126 1127 # a mercurial pattern
1127 1128 res = lambda p: os.path.join(dest,
1128 1129 os.path.basename(util.localpath(p)))
1129 1130 else:
1130 1131 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1131 1132 if len(abspfx) < len(srcs[0][0]):
1132 1133 # A directory. Either the target path contains the last
1133 1134 # component of the source path or it does not.
1134 1135 def evalpath(striplen):
1135 1136 score = 0
1136 1137 for s in srcs:
1137 1138 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1138 1139 if os.path.lexists(t):
1139 1140 score += 1
1140 1141 return score
1141 1142
1142 1143 abspfx = util.localpath(abspfx)
1143 1144 striplen = len(abspfx)
1144 1145 if striplen:
1145 1146 striplen += len(pycompat.ossep)
1146 1147 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1147 1148 score = evalpath(striplen)
1148 1149 striplen1 = len(os.path.split(abspfx)[0])
1149 1150 if striplen1:
1150 1151 striplen1 += len(pycompat.ossep)
1151 1152 if evalpath(striplen1) > score:
1152 1153 striplen = striplen1
1153 1154 res = lambda p: os.path.join(dest,
1154 1155 util.localpath(p)[striplen:])
1155 1156 else:
1156 1157 # a file
1157 1158 if destdirexists:
1158 1159 res = lambda p: os.path.join(dest,
1159 1160 os.path.basename(util.localpath(p)))
1160 1161 else:
1161 1162 res = lambda p: dest
1162 1163 return res
1163 1164
1164 1165 pats = scmutil.expandpats(pats)
1165 1166 if not pats:
1166 1167 raise error.Abort(_('no source or destination specified'))
1167 1168 if len(pats) == 1:
1168 1169 raise error.Abort(_('no destination specified'))
1169 1170 dest = pats.pop()
1170 1171 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1171 1172 if not destdirexists:
1172 1173 if len(pats) > 1 or matchmod.patkind(pats[0]):
1173 1174 raise error.Abort(_('with multiple sources, destination must be an '
1174 1175 'existing directory'))
1175 1176 if util.endswithsep(dest):
1176 1177 raise error.Abort(_('destination %s is not a directory') % dest)
1177 1178
1178 1179 tfn = targetpathfn
1179 1180 if after:
1180 1181 tfn = targetpathafterfn
1181 1182 copylist = []
1182 1183 for pat in pats:
1183 1184 srcs = walkpat(pat)
1184 1185 if not srcs:
1185 1186 continue
1186 1187 copylist.append((tfn(pat, dest, srcs), srcs))
1187 1188 if not copylist:
1188 1189 raise error.Abort(_('no files to copy'))
1189 1190
1190 1191 errors = 0
1191 1192 for targetpath, srcs in copylist:
1192 1193 for abssrc, relsrc, exact in srcs:
1193 1194 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1194 1195 errors += 1
1195 1196
1196 1197 if errors:
1197 1198 ui.warn(_('(consider using --after)\n'))
1198 1199
1199 1200 return errors != 0
1200 1201
1201 1202 ## facility to let extension process additional data into an import patch
1202 1203 # list of identifier to be executed in order
1203 1204 extrapreimport = [] # run before commit
1204 1205 extrapostimport = [] # run after commit
1205 1206 # mapping from identifier to actual import function
1206 1207 #
1207 1208 # 'preimport' are run before the commit is made and are provided the following
1208 1209 # arguments:
1209 1210 # - repo: the localrepository instance,
1210 1211 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1211 1212 # - extra: the future extra dictionary of the changeset, please mutate it,
1212 1213 # - opts: the import options.
1213 1214 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1214 1215 # mutation of in memory commit and more. Feel free to rework the code to get
1215 1216 # there.
1216 1217 extrapreimportmap = {}
1217 1218 # 'postimport' are run after the commit is made and are provided the following
1218 1219 # argument:
1219 1220 # - ctx: the changectx created by import.
1220 1221 extrapostimportmap = {}
1221 1222
1222 1223 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1223 1224 """Utility function used by commands.import to import a single patch
1224 1225
1225 1226 This function is explicitly defined here to help the evolve extension to
1226 1227 wrap this part of the import logic.
1227 1228
1228 1229 The API is currently a bit ugly because it a simple code translation from
1229 1230 the import command. Feel free to make it better.
1230 1231
1231 1232 :hunk: a patch (as a binary string)
1232 1233 :parents: nodes that will be parent of the created commit
1233 1234 :opts: the full dict of option passed to the import command
1234 1235 :msgs: list to save commit message to.
1235 1236 (used in case we need to save it when failing)
1236 1237 :updatefunc: a function that update a repo to a given node
1237 1238 updatefunc(<repo>, <node>)
1238 1239 """
1239 1240 # avoid cycle context -> subrepo -> cmdutil
1240 1241 from . import context
1241 1242 extractdata = patch.extract(ui, hunk)
1242 1243 tmpname = extractdata.get('filename')
1243 1244 message = extractdata.get('message')
1244 1245 user = opts.get('user') or extractdata.get('user')
1245 1246 date = opts.get('date') or extractdata.get('date')
1246 1247 branch = extractdata.get('branch')
1247 1248 nodeid = extractdata.get('nodeid')
1248 1249 p1 = extractdata.get('p1')
1249 1250 p2 = extractdata.get('p2')
1250 1251
1251 1252 nocommit = opts.get('no_commit')
1252 1253 importbranch = opts.get('import_branch')
1253 1254 update = not opts.get('bypass')
1254 1255 strip = opts["strip"]
1255 1256 prefix = opts["prefix"]
1256 1257 sim = float(opts.get('similarity') or 0)
1257 1258 if not tmpname:
1258 1259 return (None, None, False)
1259 1260
1260 1261 rejects = False
1261 1262
1262 1263 try:
1263 1264 cmdline_message = logmessage(ui, opts)
1264 1265 if cmdline_message:
1265 1266 # pickup the cmdline msg
1266 1267 message = cmdline_message
1267 1268 elif message:
1268 1269 # pickup the patch msg
1269 1270 message = message.strip()
1270 1271 else:
1271 1272 # launch the editor
1272 1273 message = None
1273 1274 ui.debug('message:\n%s\n' % message)
1274 1275
1275 1276 if len(parents) == 1:
1276 1277 parents.append(repo[nullid])
1277 1278 if opts.get('exact'):
1278 1279 if not nodeid or not p1:
1279 1280 raise error.Abort(_('not a Mercurial patch'))
1280 1281 p1 = repo[p1]
1281 1282 p2 = repo[p2 or nullid]
1282 1283 elif p2:
1283 1284 try:
1284 1285 p1 = repo[p1]
1285 1286 p2 = repo[p2]
1286 1287 # Without any options, consider p2 only if the
1287 1288 # patch is being applied on top of the recorded
1288 1289 # first parent.
1289 1290 if p1 != parents[0]:
1290 1291 p1 = parents[0]
1291 1292 p2 = repo[nullid]
1292 1293 except error.RepoError:
1293 1294 p1, p2 = parents
1294 1295 if p2.node() == nullid:
1295 1296 ui.warn(_("warning: import the patch as a normal revision\n"
1296 1297 "(use --exact to import the patch as a merge)\n"))
1297 1298 else:
1298 1299 p1, p2 = parents
1299 1300
1300 1301 n = None
1301 1302 if update:
1302 1303 if p1 != parents[0]:
1303 1304 updatefunc(repo, p1.node())
1304 1305 if p2 != parents[1]:
1305 1306 repo.setparents(p1.node(), p2.node())
1306 1307
1307 1308 if opts.get('exact') or importbranch:
1308 1309 repo.dirstate.setbranch(branch or 'default')
1309 1310
1310 1311 partial = opts.get('partial', False)
1311 1312 files = set()
1312 1313 try:
1313 1314 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1314 1315 files=files, eolmode=None, similarity=sim / 100.0)
1315 1316 except error.PatchError as e:
1316 1317 if not partial:
1317 1318 raise error.Abort(str(e))
1318 1319 if partial:
1319 1320 rejects = True
1320 1321
1321 1322 files = list(files)
1322 1323 if nocommit:
1323 1324 if message:
1324 1325 msgs.append(message)
1325 1326 else:
1326 1327 if opts.get('exact') or p2:
1327 1328 # If you got here, you either use --force and know what
1328 1329 # you are doing or used --exact or a merge patch while
1329 1330 # being updated to its first parent.
1330 1331 m = None
1331 1332 else:
1332 1333 m = scmutil.matchfiles(repo, files or [])
1333 1334 editform = mergeeditform(repo[None], 'import.normal')
1334 1335 if opts.get('exact'):
1335 1336 editor = None
1336 1337 else:
1337 1338 editor = getcommiteditor(editform=editform,
1338 1339 **pycompat.strkwargs(opts))
1339 1340 extra = {}
1340 1341 for idfunc in extrapreimport:
1341 1342 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1342 1343 overrides = {}
1343 1344 if partial:
1344 1345 overrides[('ui', 'allowemptycommit')] = True
1345 1346 with repo.ui.configoverride(overrides, 'import'):
1346 1347 n = repo.commit(message, user,
1347 1348 date, match=m,
1348 1349 editor=editor, extra=extra)
1349 1350 for idfunc in extrapostimport:
1350 1351 extrapostimportmap[idfunc](repo[n])
1351 1352 else:
1352 1353 if opts.get('exact') or importbranch:
1353 1354 branch = branch or 'default'
1354 1355 else:
1355 1356 branch = p1.branch()
1356 1357 store = patch.filestore()
1357 1358 try:
1358 1359 files = set()
1359 1360 try:
1360 1361 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1361 1362 files, eolmode=None)
1362 1363 except error.PatchError as e:
1363 1364 raise error.Abort(str(e))
1364 1365 if opts.get('exact'):
1365 1366 editor = None
1366 1367 else:
1367 1368 editor = getcommiteditor(editform='import.bypass')
1368 1369 memctx = context.memctx(repo, (p1.node(), p2.node()),
1369 1370 message,
1370 1371 files=files,
1371 1372 filectxfn=store,
1372 1373 user=user,
1373 1374 date=date,
1374 1375 branch=branch,
1375 1376 editor=editor)
1376 1377 n = memctx.commit()
1377 1378 finally:
1378 1379 store.close()
1379 1380 if opts.get('exact') and nocommit:
1380 1381 # --exact with --no-commit is still useful in that it does merge
1381 1382 # and branch bits
1382 1383 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1383 1384 elif opts.get('exact') and hex(n) != nodeid:
1384 1385 raise error.Abort(_('patch is damaged or loses information'))
1385 1386 msg = _('applied to working directory')
1386 1387 if n:
1387 1388 # i18n: refers to a short changeset id
1388 1389 msg = _('created %s') % short(n)
1389 1390 return (msg, n, rejects)
1390 1391 finally:
1391 1392 os.unlink(tmpname)
1392 1393
1393 1394 # facility to let extensions include additional data in an exported patch
1394 1395 # list of identifiers to be executed in order
1395 1396 extraexport = []
1396 1397 # mapping from identifier to actual export function
1397 1398 # function as to return a string to be added to the header or None
1398 1399 # it is given two arguments (sequencenumber, changectx)
1399 1400 extraexportmap = {}
1400 1401
1401 1402 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1402 1403 node = scmutil.binnode(ctx)
1403 1404 parents = [p.node() for p in ctx.parents() if p]
1404 1405 branch = ctx.branch()
1405 1406 if switch_parent:
1406 1407 parents.reverse()
1407 1408
1408 1409 if parents:
1409 1410 prev = parents[0]
1410 1411 else:
1411 1412 prev = nullid
1412 1413
1413 1414 write("# HG changeset patch\n")
1414 1415 write("# User %s\n" % ctx.user())
1415 1416 write("# Date %d %d\n" % ctx.date())
1416 1417 write("# %s\n" % util.datestr(ctx.date()))
1417 1418 if branch and branch != 'default':
1418 1419 write("# Branch %s\n" % branch)
1419 1420 write("# Node ID %s\n" % hex(node))
1420 1421 write("# Parent %s\n" % hex(prev))
1421 1422 if len(parents) > 1:
1422 1423 write("# Parent %s\n" % hex(parents[1]))
1423 1424
1424 1425 for headerid in extraexport:
1425 1426 header = extraexportmap[headerid](seqno, ctx)
1426 1427 if header is not None:
1427 1428 write('# %s\n' % header)
1428 1429 write(ctx.description().rstrip())
1429 1430 write("\n\n")
1430 1431
1431 1432 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1432 1433 write(chunk, label=label)
1433 1434
1434 1435 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1435 1436 opts=None, match=None):
1436 1437 '''export changesets as hg patches
1437 1438
1438 1439 Args:
1439 1440 repo: The repository from which we're exporting revisions.
1440 1441 revs: A list of revisions to export as revision numbers.
1441 1442 fntemplate: An optional string to use for generating patch file names.
1442 1443 fp: An optional file-like object to which patches should be written.
1443 1444 switch_parent: If True, show diffs against second parent when not nullid.
1444 1445 Default is false, which always shows diff against p1.
1445 1446 opts: diff options to use for generating the patch.
1446 1447 match: If specified, only export changes to files matching this matcher.
1447 1448
1448 1449 Returns:
1449 1450 Nothing.
1450 1451
1451 1452 Side Effect:
1452 1453 "HG Changeset Patch" data is emitted to one of the following
1453 1454 destinations:
1454 1455 fp is specified: All revs are written to the specified
1455 1456 file-like object.
1456 1457 fntemplate specified: Each rev is written to a unique file named using
1457 1458 the given template.
1458 1459 Neither fp nor template specified: All revs written to repo.ui.write()
1459 1460 '''
1460 1461
1461 1462 total = len(revs)
1462 1463 revwidth = max(len(str(rev)) for rev in revs)
1463 1464 filemode = {}
1464 1465
1465 1466 write = None
1466 1467 dest = '<unnamed>'
1467 1468 if fp:
1468 1469 dest = getattr(fp, 'name', dest)
1469 1470 def write(s, **kw):
1470 1471 fp.write(s)
1471 1472 elif not fntemplate:
1472 1473 write = repo.ui.write
1473 1474
1474 1475 for seqno, rev in enumerate(revs, 1):
1475 1476 ctx = repo[rev]
1476 1477 fo = None
1477 1478 if not fp and fntemplate:
1478 1479 desc_lines = ctx.description().rstrip().split('\n')
1479 1480 desc = desc_lines[0] #Commit always has a first line.
1480 1481 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1481 1482 total=total, seqno=seqno, revwidth=revwidth,
1482 1483 mode='wb', modemap=filemode)
1483 1484 dest = fo.name
1484 1485 def write(s, **kw):
1485 1486 fo.write(s)
1486 1487 if not dest.startswith('<'):
1487 1488 repo.ui.note("%s\n" % dest)
1488 1489 _exportsingle(
1489 1490 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1490 1491 if fo is not None:
1491 1492 fo.close()
1492 1493
1493 1494 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1494 1495 changes=None, stat=False, fp=None, prefix='',
1495 1496 root='', listsubrepos=False, hunksfilterfn=None):
1496 1497 '''show diff or diffstat.'''
1497 1498 if fp is None:
1498 1499 write = ui.write
1499 1500 else:
1500 1501 def write(s, **kw):
1501 1502 fp.write(s)
1502 1503
1503 1504 if root:
1504 1505 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1505 1506 else:
1506 1507 relroot = ''
1507 1508 if relroot != '':
1508 1509 # XXX relative roots currently don't work if the root is within a
1509 1510 # subrepo
1510 1511 uirelroot = match.uipath(relroot)
1511 1512 relroot += '/'
1512 1513 for matchroot in match.files():
1513 1514 if not matchroot.startswith(relroot):
1514 1515 ui.warn(_('warning: %s not inside relative root %s\n') % (
1515 1516 match.uipath(matchroot), uirelroot))
1516 1517
1517 1518 if stat:
1518 1519 diffopts = diffopts.copy(context=0, noprefix=False)
1519 1520 width = 80
1520 1521 if not ui.plain():
1521 1522 width = ui.termwidth()
1522 1523 chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts,
1523 1524 prefix=prefix, relroot=relroot,
1524 1525 hunksfilterfn=hunksfilterfn)
1525 1526 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1526 1527 width=width):
1527 1528 write(chunk, label=label)
1528 1529 else:
1529 1530 for chunk, label in patch.diffui(repo, node1, node2, match,
1530 1531 changes, opts=diffopts, prefix=prefix,
1531 1532 relroot=relroot,
1532 1533 hunksfilterfn=hunksfilterfn):
1533 1534 write(chunk, label=label)
1534 1535
1535 1536 if listsubrepos:
1536 1537 ctx1 = repo[node1]
1537 1538 ctx2 = repo[node2]
1538 1539 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1539 1540 tempnode2 = node2
1540 1541 try:
1541 1542 if node2 is not None:
1542 1543 tempnode2 = ctx2.substate[subpath][1]
1543 1544 except KeyError:
1544 1545 # A subrepo that existed in node1 was deleted between node1 and
1545 1546 # node2 (inclusive). Thus, ctx2's substate won't contain that
1546 1547 # subpath. The best we can do is to ignore it.
1547 1548 tempnode2 = None
1548 1549 submatch = matchmod.subdirmatcher(subpath, match)
1549 1550 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1550 1551 stat=stat, fp=fp, prefix=prefix)
1551 1552
1552 1553 def _changesetlabels(ctx):
1553 1554 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1554 1555 if ctx.obsolete():
1555 1556 labels.append('changeset.obsolete')
1556 1557 if ctx.isunstable():
1557 1558 labels.append('changeset.unstable')
1558 1559 for instability in ctx.instabilities():
1559 1560 labels.append('instability.%s' % instability)
1560 1561 return ' '.join(labels)
1561 1562
1562 1563 class changeset_printer(object):
1563 1564 '''show changeset information when templating not requested.'''
1564 1565
1565 1566 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1566 1567 self.ui = ui
1567 1568 self.repo = repo
1568 1569 self.buffered = buffered
1569 1570 self.matchfn = matchfn
1570 1571 self.diffopts = diffopts
1571 1572 self.header = {}
1572 1573 self.hunk = {}
1573 1574 self.lastheader = None
1574 1575 self.footer = None
1575 1576 self._columns = templatekw.getlogcolumns()
1576 1577
1577 1578 def flush(self, ctx):
1578 1579 rev = ctx.rev()
1579 1580 if rev in self.header:
1580 1581 h = self.header[rev]
1581 1582 if h != self.lastheader:
1582 1583 self.lastheader = h
1583 1584 self.ui.write(h)
1584 1585 del self.header[rev]
1585 1586 if rev in self.hunk:
1586 1587 self.ui.write(self.hunk[rev])
1587 1588 del self.hunk[rev]
1588 1589
1589 1590 def close(self):
1590 1591 if self.footer:
1591 1592 self.ui.write(self.footer)
1592 1593
1593 1594 def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
1594 1595 **props):
1595 1596 props = pycompat.byteskwargs(props)
1596 1597 if self.buffered:
1597 1598 self.ui.pushbuffer(labeled=True)
1598 1599 self._show(ctx, copies, matchfn, hunksfilterfn, props)
1599 1600 self.hunk[ctx.rev()] = self.ui.popbuffer()
1600 1601 else:
1601 1602 self._show(ctx, copies, matchfn, hunksfilterfn, props)
1602 1603
1603 1604 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1604 1605 '''show a single changeset or file revision'''
1605 1606 changenode = ctx.node()
1606 1607 rev = ctx.rev()
1607 1608
1608 1609 if self.ui.quiet:
1609 1610 self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
1610 1611 label='log.node')
1611 1612 return
1612 1613
1613 1614 columns = self._columns
1614 1615 self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx),
1615 1616 label=_changesetlabels(ctx))
1616 1617
1617 1618 # branches are shown first before any other names due to backwards
1618 1619 # compatibility
1619 1620 branch = ctx.branch()
1620 1621 # don't show the default branch name
1621 1622 if branch != 'default':
1622 1623 self.ui.write(columns['branch'] % branch, label='log.branch')
1623 1624
1624 1625 for nsname, ns in self.repo.names.iteritems():
1625 1626 # branches has special logic already handled above, so here we just
1626 1627 # skip it
1627 1628 if nsname == 'branches':
1628 1629 continue
1629 1630 # we will use the templatename as the color name since those two
1630 1631 # should be the same
1631 1632 for name in ns.names(self.repo, changenode):
1632 1633 self.ui.write(ns.logfmt % name,
1633 1634 label='log.%s' % ns.colorname)
1634 1635 if self.ui.debugflag:
1635 1636 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
1636 1637 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1637 1638 label = 'log.parent changeset.%s' % pctx.phasestr()
1638 1639 self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
1639 1640 label=label)
1640 1641
1641 1642 if self.ui.debugflag and rev is not None:
1642 1643 mnode = ctx.manifestnode()
1643 1644 mrev = self.repo.manifestlog._revlog.rev(mnode)
1644 1645 self.ui.write(columns['manifest']
1645 1646 % scmutil.formatrevnode(self.ui, mrev, mnode),
1646 1647 label='ui.debug log.manifest')
1647 1648 self.ui.write(columns['user'] % ctx.user(), label='log.user')
1648 1649 self.ui.write(columns['date'] % util.datestr(ctx.date()),
1649 1650 label='log.date')
1650 1651
1651 1652 if ctx.isunstable():
1652 1653 instabilities = ctx.instabilities()
1653 1654 self.ui.write(columns['instability'] % ', '.join(instabilities),
1654 1655 label='log.instability')
1655 1656
1656 1657 elif ctx.obsolete():
1657 1658 self._showobsfate(ctx)
1658 1659
1659 1660 self._exthook(ctx)
1660 1661
1661 1662 if self.ui.debugflag:
1662 1663 files = ctx.p1().status(ctx)[:3]
1663 1664 for key, value in zip(['files', 'files+', 'files-'], files):
1664 1665 if value:
1665 1666 self.ui.write(columns[key] % " ".join(value),
1666 1667 label='ui.debug log.files')
1667 1668 elif ctx.files() and self.ui.verbose:
1668 1669 self.ui.write(columns['files'] % " ".join(ctx.files()),
1669 1670 label='ui.note log.files')
1670 1671 if copies and self.ui.verbose:
1671 1672 copies = ['%s (%s)' % c for c in copies]
1672 1673 self.ui.write(columns['copies'] % ' '.join(copies),
1673 1674 label='ui.note log.copies')
1674 1675
1675 1676 extra = ctx.extra()
1676 1677 if extra and self.ui.debugflag:
1677 1678 for key, value in sorted(extra.items()):
1678 1679 self.ui.write(columns['extra'] % (key, util.escapestr(value)),
1679 1680 label='ui.debug log.extra')
1680 1681
1681 1682 description = ctx.description().strip()
1682 1683 if description:
1683 1684 if self.ui.verbose:
1684 1685 self.ui.write(_("description:\n"),
1685 1686 label='ui.note log.description')
1686 1687 self.ui.write(description,
1687 1688 label='ui.note log.description')
1688 1689 self.ui.write("\n\n")
1689 1690 else:
1690 1691 self.ui.write(columns['summary'] % description.splitlines()[0],
1691 1692 label='log.summary')
1692 1693 self.ui.write("\n")
1693 1694
1694 1695 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
1695 1696
1696 1697 def _showobsfate(self, ctx):
1697 1698 obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
1698 1699
1699 1700 if obsfate:
1700 1701 for obsfateline in obsfate:
1701 1702 self.ui.write(self._columns['obsolete'] % obsfateline,
1702 1703 label='log.obsfate')
1703 1704
1704 1705 def _exthook(self, ctx):
1705 1706 '''empty method used by extension as a hook point
1706 1707 '''
1707 1708
1708 1709 def showpatch(self, ctx, matchfn, hunksfilterfn=None):
1709 1710 if not matchfn:
1710 1711 matchfn = self.matchfn
1711 1712 if matchfn:
1712 1713 stat = self.diffopts.get('stat')
1713 1714 diff = self.diffopts.get('patch')
1714 1715 diffopts = patch.diffallopts(self.ui, self.diffopts)
1715 1716 node = ctx.node()
1716 1717 prev = ctx.p1().node()
1717 1718 if stat:
1718 1719 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1719 1720 match=matchfn, stat=True,
1720 1721 hunksfilterfn=hunksfilterfn)
1721 1722 if diff:
1722 1723 if stat:
1723 1724 self.ui.write("\n")
1724 1725 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1725 1726 match=matchfn, stat=False,
1726 1727 hunksfilterfn=hunksfilterfn)
1727 1728 self.ui.write("\n")
1728 1729
1729 1730 class jsonchangeset(changeset_printer):
1730 1731 '''format changeset information.'''
1731 1732
1732 1733 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1733 1734 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1734 1735 self.cache = {}
1735 1736 self._first = True
1736 1737
1737 1738 def close(self):
1738 1739 if not self._first:
1739 1740 self.ui.write("\n]\n")
1740 1741 else:
1741 1742 self.ui.write("[]\n")
1742 1743
1743 1744 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1744 1745 '''show a single changeset or file revision'''
1745 1746 rev = ctx.rev()
1746 1747 if rev is None:
1747 1748 jrev = jnode = 'null'
1748 1749 else:
1749 1750 jrev = '%d' % rev
1750 1751 jnode = '"%s"' % hex(ctx.node())
1751 1752 j = encoding.jsonescape
1752 1753
1753 1754 if self._first:
1754 1755 self.ui.write("[\n {")
1755 1756 self._first = False
1756 1757 else:
1757 1758 self.ui.write(",\n {")
1758 1759
1759 1760 if self.ui.quiet:
1760 1761 self.ui.write(('\n "rev": %s') % jrev)
1761 1762 self.ui.write((',\n "node": %s') % jnode)
1762 1763 self.ui.write('\n }')
1763 1764 return
1764 1765
1765 1766 self.ui.write(('\n "rev": %s') % jrev)
1766 1767 self.ui.write((',\n "node": %s') % jnode)
1767 1768 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1768 1769 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1769 1770 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1770 1771 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1771 1772 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1772 1773
1773 1774 self.ui.write((',\n "bookmarks": [%s]') %
1774 1775 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1775 1776 self.ui.write((',\n "tags": [%s]') %
1776 1777 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1777 1778 self.ui.write((',\n "parents": [%s]') %
1778 1779 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1779 1780
1780 1781 if self.ui.debugflag:
1781 1782 if rev is None:
1782 1783 jmanifestnode = 'null'
1783 1784 else:
1784 1785 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1785 1786 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1786 1787
1787 1788 self.ui.write((',\n "extra": {%s}') %
1788 1789 ", ".join('"%s": "%s"' % (j(k), j(v))
1789 1790 for k, v in ctx.extra().items()))
1790 1791
1791 1792 files = ctx.p1().status(ctx)
1792 1793 self.ui.write((',\n "modified": [%s]') %
1793 1794 ", ".join('"%s"' % j(f) for f in files[0]))
1794 1795 self.ui.write((',\n "added": [%s]') %
1795 1796 ", ".join('"%s"' % j(f) for f in files[1]))
1796 1797 self.ui.write((',\n "removed": [%s]') %
1797 1798 ", ".join('"%s"' % j(f) for f in files[2]))
1798 1799
1799 1800 elif self.ui.verbose:
1800 1801 self.ui.write((',\n "files": [%s]') %
1801 1802 ", ".join('"%s"' % j(f) for f in ctx.files()))
1802 1803
1803 1804 if copies:
1804 1805 self.ui.write((',\n "copies": {%s}') %
1805 1806 ", ".join('"%s": "%s"' % (j(k), j(v))
1806 1807 for k, v in copies))
1807 1808
1808 1809 matchfn = self.matchfn
1809 1810 if matchfn:
1810 1811 stat = self.diffopts.get('stat')
1811 1812 diff = self.diffopts.get('patch')
1812 1813 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1813 1814 node, prev = ctx.node(), ctx.p1().node()
1814 1815 if stat:
1815 1816 self.ui.pushbuffer()
1816 1817 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1817 1818 match=matchfn, stat=True)
1818 1819 self.ui.write((',\n "diffstat": "%s"')
1819 1820 % j(self.ui.popbuffer()))
1820 1821 if diff:
1821 1822 self.ui.pushbuffer()
1822 1823 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1823 1824 match=matchfn, stat=False)
1824 1825 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1825 1826
1826 1827 self.ui.write("\n }")
1827 1828
1828 1829 class changeset_templater(changeset_printer):
1829 1830 '''format changeset information.
1830 1831
1831 1832 Note: there are a variety of convenience functions to build a
1832 1833 changeset_templater for common cases. See functions such as:
1833 1834 makelogtemplater, show_changeset, buildcommittemplate, or other
1834 1835 functions that use changesest_templater.
1835 1836 '''
1836 1837
1837 1838 # Arguments before "buffered" used to be positional. Consider not
1838 1839 # adding/removing arguments before "buffered" to not break callers.
1839 1840 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1840 1841 buffered=False):
1841 1842 diffopts = diffopts or {}
1842 1843
1843 1844 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1844 1845 tres = formatter.templateresources(ui, repo)
1845 1846 self.t = formatter.loadtemplater(ui, tmplspec,
1846 1847 defaults=templatekw.keywords,
1847 1848 resources=tres,
1848 1849 cache=templatekw.defaulttempl)
1849 1850 self._counter = itertools.count()
1850 1851 self.cache = tres['cache'] # shared with _graphnodeformatter()
1851 1852
1852 1853 self._tref = tmplspec.ref
1853 1854 self._parts = {'header': '', 'footer': '',
1854 1855 tmplspec.ref: tmplspec.ref,
1855 1856 'docheader': '', 'docfooter': '',
1856 1857 'separator': ''}
1857 1858 if tmplspec.mapfile:
1858 1859 # find correct templates for current mode, for backward
1859 1860 # compatibility with 'log -v/-q/--debug' using a mapfile
1860 1861 tmplmodes = [
1861 1862 (True, ''),
1862 1863 (self.ui.verbose, '_verbose'),
1863 1864 (self.ui.quiet, '_quiet'),
1864 1865 (self.ui.debugflag, '_debug'),
1865 1866 ]
1866 1867 for mode, postfix in tmplmodes:
1867 1868 for t in self._parts:
1868 1869 cur = t + postfix
1869 1870 if mode and cur in self.t:
1870 1871 self._parts[t] = cur
1871 1872 else:
1872 1873 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1873 1874 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1874 1875 self._parts.update(m)
1875 1876
1876 1877 if self._parts['docheader']:
1877 1878 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1878 1879
1879 1880 def close(self):
1880 1881 if self._parts['docfooter']:
1881 1882 if not self.footer:
1882 1883 self.footer = ""
1883 1884 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1884 1885 return super(changeset_templater, self).close()
1885 1886
1886 1887 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1887 1888 '''show a single changeset or file revision'''
1888 1889 props = props.copy()
1889 1890 props['ctx'] = ctx
1890 1891 props['index'] = index = next(self._counter)
1891 1892 props['revcache'] = {'copies': copies}
1892 1893 props = pycompat.strkwargs(props)
1893 1894
1894 1895 # write separator, which wouldn't work well with the header part below
1895 1896 # since there's inherently a conflict between header (across items) and
1896 1897 # separator (per item)
1897 1898 if self._parts['separator'] and index > 0:
1898 1899 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1899 1900
1900 1901 # write header
1901 1902 if self._parts['header']:
1902 1903 h = templater.stringify(self.t(self._parts['header'], **props))
1903 1904 if self.buffered:
1904 1905 self.header[ctx.rev()] = h
1905 1906 else:
1906 1907 if self.lastheader != h:
1907 1908 self.lastheader = h
1908 1909 self.ui.write(h)
1909 1910
1910 1911 # write changeset metadata, then patch if requested
1911 1912 key = self._parts[self._tref]
1912 1913 self.ui.write(templater.stringify(self.t(key, **props)))
1913 1914 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
1914 1915
1915 1916 if self._parts['footer']:
1916 1917 if not self.footer:
1917 1918 self.footer = templater.stringify(
1918 1919 self.t(self._parts['footer'], **props))
1919 1920
1920 1921 def logtemplatespec(tmpl, mapfile):
1921 1922 if mapfile:
1922 1923 return formatter.templatespec('changeset', tmpl, mapfile)
1923 1924 else:
1924 1925 return formatter.templatespec('', tmpl, None)
1925 1926
1926 1927 def _lookuplogtemplate(ui, tmpl, style):
1927 1928 """Find the template matching the given template spec or style
1928 1929
1929 1930 See formatter.lookuptemplate() for details.
1930 1931 """
1931 1932
1932 1933 # ui settings
1933 1934 if not tmpl and not style: # template are stronger than style
1934 1935 tmpl = ui.config('ui', 'logtemplate')
1935 1936 if tmpl:
1936 1937 return logtemplatespec(templater.unquotestring(tmpl), None)
1937 1938 else:
1938 1939 style = util.expandpath(ui.config('ui', 'style'))
1939 1940
1940 1941 if not tmpl and style:
1941 1942 mapfile = style
1942 1943 if not os.path.split(mapfile)[0]:
1943 1944 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1944 1945 or templater.templatepath(mapfile))
1945 1946 if mapname:
1946 1947 mapfile = mapname
1947 1948 return logtemplatespec(None, mapfile)
1948 1949
1949 1950 if not tmpl:
1950 1951 return logtemplatespec(None, None)
1951 1952
1952 1953 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1953 1954
1954 1955 def makelogtemplater(ui, repo, tmpl, buffered=False):
1955 1956 """Create a changeset_templater from a literal template 'tmpl'
1956 1957 byte-string."""
1957 1958 spec = logtemplatespec(tmpl, None)
1958 1959 return changeset_templater(ui, repo, spec, buffered=buffered)
1959 1960
1960 1961 def show_changeset(ui, repo, opts, buffered=False):
1961 1962 """show one changeset using template or regular display.
1962 1963
1963 1964 Display format will be the first non-empty hit of:
1964 1965 1. option 'template'
1965 1966 2. option 'style'
1966 1967 3. [ui] setting 'logtemplate'
1967 1968 4. [ui] setting 'style'
1968 1969 If all of these values are either the unset or the empty string,
1969 1970 regular display via changeset_printer() is done.
1970 1971 """
1971 1972 # options
1972 1973 match = None
1973 1974 if opts.get('patch') or opts.get('stat'):
1974 1975 match = scmutil.matchall(repo)
1975 1976
1976 1977 if opts.get('template') == 'json':
1977 1978 return jsonchangeset(ui, repo, match, opts, buffered)
1978 1979
1979 1980 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
1980 1981
1981 1982 if not spec.ref and not spec.tmpl and not spec.mapfile:
1982 1983 return changeset_printer(ui, repo, match, opts, buffered)
1983 1984
1984 1985 return changeset_templater(ui, repo, spec, match, opts, buffered)
1985 1986
1986 1987 def showmarker(fm, marker, index=None):
1987 1988 """utility function to display obsolescence marker in a readable way
1988 1989
1989 1990 To be used by debug function."""
1990 1991 if index is not None:
1991 1992 fm.write('index', '%i ', index)
1992 1993 fm.write('prednode', '%s ', hex(marker.prednode()))
1993 1994 succs = marker.succnodes()
1994 1995 fm.condwrite(succs, 'succnodes', '%s ',
1995 1996 fm.formatlist(map(hex, succs), name='node'))
1996 1997 fm.write('flag', '%X ', marker.flags())
1997 1998 parents = marker.parentnodes()
1998 1999 if parents is not None:
1999 2000 fm.write('parentnodes', '{%s} ',
2000 2001 fm.formatlist(map(hex, parents), name='node', sep=', '))
2001 2002 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
2002 2003 meta = marker.metadata().copy()
2003 2004 meta.pop('date', None)
2004 2005 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
2005 2006 fm.plain('\n')
2006 2007
2007 2008 def finddate(ui, repo, date):
2008 2009 """Find the tipmost changeset that matches the given date spec"""
2009 2010
2010 2011 df = util.matchdate(date)
2011 2012 m = scmutil.matchall(repo)
2012 2013 results = {}
2013 2014
2014 2015 def prep(ctx, fns):
2015 2016 d = ctx.date()
2016 2017 if df(d[0]):
2017 2018 results[ctx.rev()] = d
2018 2019
2019 2020 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
2020 2021 rev = ctx.rev()
2021 2022 if rev in results:
2022 2023 ui.status(_("found revision %s from %s\n") %
2023 2024 (rev, util.datestr(results[rev])))
2024 2025 return '%d' % rev
2025 2026
2026 2027 raise error.Abort(_("revision matching date not found"))
2027 2028
2028 2029 def increasingwindows(windowsize=8, sizelimit=512):
2029 2030 while True:
2030 2031 yield windowsize
2031 2032 if windowsize < sizelimit:
2032 2033 windowsize *= 2
2033 2034
2034 2035 class FileWalkError(Exception):
2035 2036 pass
2036 2037
2037 2038 def walkfilerevs(repo, match, follow, revs, fncache):
2038 2039 '''Walks the file history for the matched files.
2039 2040
2040 2041 Returns the changeset revs that are involved in the file history.
2041 2042
2042 2043 Throws FileWalkError if the file history can't be walked using
2043 2044 filelogs alone.
2044 2045 '''
2045 2046 wanted = set()
2046 2047 copies = []
2047 2048 minrev, maxrev = min(revs), max(revs)
2048 2049 def filerevgen(filelog, last):
2049 2050 """
2050 2051 Only files, no patterns. Check the history of each file.
2051 2052
2052 2053 Examines filelog entries within minrev, maxrev linkrev range
2053 2054 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2054 2055 tuples in backwards order
2055 2056 """
2056 2057 cl_count = len(repo)
2057 2058 revs = []
2058 2059 for j in xrange(0, last + 1):
2059 2060 linkrev = filelog.linkrev(j)
2060 2061 if linkrev < minrev:
2061 2062 continue
2062 2063 # only yield rev for which we have the changelog, it can
2063 2064 # happen while doing "hg log" during a pull or commit
2064 2065 if linkrev >= cl_count:
2065 2066 break
2066 2067
2067 2068 parentlinkrevs = []
2068 2069 for p in filelog.parentrevs(j):
2069 2070 if p != nullrev:
2070 2071 parentlinkrevs.append(filelog.linkrev(p))
2071 2072 n = filelog.node(j)
2072 2073 revs.append((linkrev, parentlinkrevs,
2073 2074 follow and filelog.renamed(n)))
2074 2075
2075 2076 return reversed(revs)
2076 2077 def iterfiles():
2077 2078 pctx = repo['.']
2078 2079 for filename in match.files():
2079 2080 if follow:
2080 2081 if filename not in pctx:
2081 2082 raise error.Abort(_('cannot follow file not in parent '
2082 2083 'revision: "%s"') % filename)
2083 2084 yield filename, pctx[filename].filenode()
2084 2085 else:
2085 2086 yield filename, None
2086 2087 for filename_node in copies:
2087 2088 yield filename_node
2088 2089
2089 2090 for file_, node in iterfiles():
2090 2091 filelog = repo.file(file_)
2091 2092 if not len(filelog):
2092 2093 if node is None:
2093 2094 # A zero count may be a directory or deleted file, so
2094 2095 # try to find matching entries on the slow path.
2095 2096 if follow:
2096 2097 raise error.Abort(
2097 2098 _('cannot follow nonexistent file: "%s"') % file_)
2098 2099 raise FileWalkError("Cannot walk via filelog")
2099 2100 else:
2100 2101 continue
2101 2102
2102 2103 if node is None:
2103 2104 last = len(filelog) - 1
2104 2105 else:
2105 2106 last = filelog.rev(node)
2106 2107
2107 2108 # keep track of all ancestors of the file
2108 2109 ancestors = {filelog.linkrev(last)}
2109 2110
2110 2111 # iterate from latest to oldest revision
2111 2112 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2112 2113 if not follow:
2113 2114 if rev > maxrev:
2114 2115 continue
2115 2116 else:
2116 2117 # Note that last might not be the first interesting
2117 2118 # rev to us:
2118 2119 # if the file has been changed after maxrev, we'll
2119 2120 # have linkrev(last) > maxrev, and we still need
2120 2121 # to explore the file graph
2121 2122 if rev not in ancestors:
2122 2123 continue
2123 2124 # XXX insert 1327 fix here
2124 2125 if flparentlinkrevs:
2125 2126 ancestors.update(flparentlinkrevs)
2126 2127
2127 2128 fncache.setdefault(rev, []).append(file_)
2128 2129 wanted.add(rev)
2129 2130 if copied:
2130 2131 copies.append(copied)
2131 2132
2132 2133 return wanted
2133 2134
2134 2135 class _followfilter(object):
2135 2136 def __init__(self, repo, onlyfirst=False):
2136 2137 self.repo = repo
2137 2138 self.startrev = nullrev
2138 2139 self.roots = set()
2139 2140 self.onlyfirst = onlyfirst
2140 2141
2141 2142 def match(self, rev):
2142 2143 def realparents(rev):
2143 2144 if self.onlyfirst:
2144 2145 return self.repo.changelog.parentrevs(rev)[0:1]
2145 2146 else:
2146 2147 return filter(lambda x: x != nullrev,
2147 2148 self.repo.changelog.parentrevs(rev))
2148 2149
2149 2150 if self.startrev == nullrev:
2150 2151 self.startrev = rev
2151 2152 return True
2152 2153
2153 2154 if rev > self.startrev:
2154 2155 # forward: all descendants
2155 2156 if not self.roots:
2156 2157 self.roots.add(self.startrev)
2157 2158 for parent in realparents(rev):
2158 2159 if parent in self.roots:
2159 2160 self.roots.add(rev)
2160 2161 return True
2161 2162 else:
2162 2163 # backwards: all parents
2163 2164 if not self.roots:
2164 2165 self.roots.update(realparents(self.startrev))
2165 2166 if rev in self.roots:
2166 2167 self.roots.remove(rev)
2167 2168 self.roots.update(realparents(rev))
2168 2169 return True
2169 2170
2170 2171 return False
2171 2172
2172 2173 def walkchangerevs(repo, match, opts, prepare):
2173 2174 '''Iterate over files and the revs in which they changed.
2174 2175
2175 2176 Callers most commonly need to iterate backwards over the history
2176 2177 in which they are interested. Doing so has awful (quadratic-looking)
2177 2178 performance, so we use iterators in a "windowed" way.
2178 2179
2179 2180 We walk a window of revisions in the desired order. Within the
2180 2181 window, we first walk forwards to gather data, then in the desired
2181 2182 order (usually backwards) to display it.
2182 2183
2183 2184 This function returns an iterator yielding contexts. Before
2184 2185 yielding each context, the iterator will first call the prepare
2185 2186 function on each context in the window in forward order.'''
2186 2187
2187 2188 follow = opts.get('follow') or opts.get('follow_first')
2188 2189 revs = _logrevs(repo, opts)
2189 2190 if not revs:
2190 2191 return []
2191 2192 wanted = set()
2192 2193 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
2193 2194 fncache = {}
2194 2195 change = repo.changectx
2195 2196
2196 2197 # First step is to fill wanted, the set of revisions that we want to yield.
2197 2198 # When it does not induce extra cost, we also fill fncache for revisions in
2198 2199 # wanted: a cache of filenames that were changed (ctx.files()) and that
2199 2200 # match the file filtering conditions.
2200 2201
2201 2202 if match.always():
2202 2203 # No files, no patterns. Display all revs.
2203 2204 wanted = revs
2204 2205 elif not slowpath:
2205 2206 # We only have to read through the filelog to find wanted revisions
2206 2207
2207 2208 try:
2208 2209 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2209 2210 except FileWalkError:
2210 2211 slowpath = True
2211 2212
2212 2213 # We decided to fall back to the slowpath because at least one
2213 2214 # of the paths was not a file. Check to see if at least one of them
2214 2215 # existed in history, otherwise simply return
2215 2216 for path in match.files():
2216 2217 if path == '.' or path in repo.store:
2217 2218 break
2218 2219 else:
2219 2220 return []
2220 2221
2221 2222 if slowpath:
2222 2223 # We have to read the changelog to match filenames against
2223 2224 # changed files
2224 2225
2225 2226 if follow:
2226 2227 raise error.Abort(_('can only follow copies/renames for explicit '
2227 2228 'filenames'))
2228 2229
2229 2230 # The slow path checks files modified in every changeset.
2230 2231 # This is really slow on large repos, so compute the set lazily.
2231 2232 class lazywantedset(object):
2232 2233 def __init__(self):
2233 2234 self.set = set()
2234 2235 self.revs = set(revs)
2235 2236
2236 2237 # No need to worry about locality here because it will be accessed
2237 2238 # in the same order as the increasing window below.
2238 2239 def __contains__(self, value):
2239 2240 if value in self.set:
2240 2241 return True
2241 2242 elif not value in self.revs:
2242 2243 return False
2243 2244 else:
2244 2245 self.revs.discard(value)
2245 2246 ctx = change(value)
2246 2247 matches = filter(match, ctx.files())
2247 2248 if matches:
2248 2249 fncache[value] = matches
2249 2250 self.set.add(value)
2250 2251 return True
2251 2252 return False
2252 2253
2253 2254 def discard(self, value):
2254 2255 self.revs.discard(value)
2255 2256 self.set.discard(value)
2256 2257
2257 2258 wanted = lazywantedset()
2258 2259
2259 2260 # it might be worthwhile to do this in the iterator if the rev range
2260 2261 # is descending and the prune args are all within that range
2261 2262 for rev in opts.get('prune', ()):
2262 2263 rev = repo[rev].rev()
2263 2264 ff = _followfilter(repo)
2264 2265 stop = min(revs[0], revs[-1])
2265 2266 for x in xrange(rev, stop - 1, -1):
2266 2267 if ff.match(x):
2267 2268 wanted = wanted - [x]
2268 2269
2269 2270 # Now that wanted is correctly initialized, we can iterate over the
2270 2271 # revision range, yielding only revisions in wanted.
2271 2272 def iterate():
2272 2273 if follow and match.always():
2273 2274 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2274 2275 def want(rev):
2275 2276 return ff.match(rev) and rev in wanted
2276 2277 else:
2277 2278 def want(rev):
2278 2279 return rev in wanted
2279 2280
2280 2281 it = iter(revs)
2281 2282 stopiteration = False
2282 2283 for windowsize in increasingwindows():
2283 2284 nrevs = []
2284 2285 for i in xrange(windowsize):
2285 2286 rev = next(it, None)
2286 2287 if rev is None:
2287 2288 stopiteration = True
2288 2289 break
2289 2290 elif want(rev):
2290 2291 nrevs.append(rev)
2291 2292 for rev in sorted(nrevs):
2292 2293 fns = fncache.get(rev)
2293 2294 ctx = change(rev)
2294 2295 if not fns:
2295 2296 def fns_generator():
2296 2297 for f in ctx.files():
2297 2298 if match(f):
2298 2299 yield f
2299 2300 fns = fns_generator()
2300 2301 prepare(ctx, fns)
2301 2302 for rev in nrevs:
2302 2303 yield change(rev)
2303 2304
2304 2305 if stopiteration:
2305 2306 break
2306 2307
2307 2308 return iterate()
2308 2309
2309 2310 def _makefollowlogfilematcher(repo, files, followfirst):
2310 2311 # When displaying a revision with --patch --follow FILE, we have
2311 2312 # to know which file of the revision must be diffed. With
2312 2313 # --follow, we want the names of the ancestors of FILE in the
2313 2314 # revision, stored in "fcache". "fcache" is populated by
2314 2315 # reproducing the graph traversal already done by --follow revset
2315 2316 # and relating revs to file names (which is not "correct" but
2316 2317 # good enough).
2317 2318 fcache = {}
2318 2319 fcacheready = [False]
2319 2320 pctx = repo['.']
2320 2321
2321 2322 def populate():
2322 2323 for fn in files:
2323 2324 fctx = pctx[fn]
2324 2325 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2325 2326 for c in fctx.ancestors(followfirst=followfirst):
2326 2327 fcache.setdefault(c.rev(), set()).add(c.path())
2327 2328
2328 2329 def filematcher(rev):
2329 2330 if not fcacheready[0]:
2330 2331 # Lazy initialization
2331 2332 fcacheready[0] = True
2332 2333 populate()
2333 2334 return scmutil.matchfiles(repo, fcache.get(rev, []))
2334 2335
2335 2336 return filematcher
2336 2337
2337 2338 def _makenofollowlogfilematcher(repo, pats, opts):
2338 2339 '''hook for extensions to override the filematcher for non-follow cases'''
2339 2340 return None
2340 2341
2341 2342 _opt2logrevset = {
2342 2343 'no_merges': ('not merge()', None),
2343 2344 'only_merges': ('merge()', None),
2344 2345 '_ancestors': ('ancestors(%(val)s)', None),
2345 2346 '_fancestors': ('_firstancestors(%(val)s)', None),
2346 2347 '_descendants': ('descendants(%(val)s)', None),
2347 2348 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2348 2349 '_matchfiles': ('_matchfiles(%(val)s)', None),
2349 2350 'date': ('date(%(val)r)', None),
2350 'branch': ('branch(%(val)r)', ' or '),
2351 '_patslog': ('filelog(%(val)r)', ' or '),
2352 '_patsfollow': ('follow(%(val)r)', ' or '),
2353 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2354 'keyword': ('keyword(%(val)r)', ' or '),
2355 'prune': ('not ancestors(%(val)r)', ' and '),
2356 'user': ('user(%(val)r)', ' or '),
2351 'branch': ('branch(%(val)r)', '%lr'),
2352 '_patslog': ('filelog(%(val)r)', '%lr'),
2353 '_patsfollow': ('follow(%(val)r)', '%lr'),
2354 '_patsfollowfirst': ('_followfirst(%(val)r)', '%lr'),
2355 'keyword': ('keyword(%(val)r)', '%lr'),
2356 'prune': ('ancestors(%(val)r)', 'not %lr'),
2357 'user': ('user(%(val)r)', '%lr'),
2357 2358 }
2358 2359
2359 2360 def _makelogrevset(repo, pats, opts, revs):
2360 2361 """Return (expr, filematcher) where expr is a revset string built
2361 2362 from log options and file patterns or None. If --stat or --patch
2362 2363 are not passed filematcher is None. Otherwise it is a callable
2363 2364 taking a revision number and returning a match objects filtering
2364 2365 the files to be detailed when displaying the revision.
2365 2366 """
2366 2367 opts = dict(opts)
2367 2368 # follow or not follow?
2368 2369 follow = opts.get('follow') or opts.get('follow_first')
2369 2370 if opts.get('follow_first'):
2370 2371 followfirst = 1
2371 2372 else:
2372 2373 followfirst = 0
2373 2374 # --follow with FILE behavior depends on revs...
2374 2375 it = iter(revs)
2375 2376 startrev = next(it)
2376 2377 followdescendants = startrev < next(it, startrev)
2377 2378
2378 2379 # branch and only_branch are really aliases and must be handled at
2379 2380 # the same time
2380 2381 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2381 2382 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2382 2383 # pats/include/exclude are passed to match.match() directly in
2383 2384 # _matchfiles() revset but walkchangerevs() builds its matcher with
2384 2385 # scmutil.match(). The difference is input pats are globbed on
2385 2386 # platforms without shell expansion (windows).
2386 2387 wctx = repo[None]
2387 2388 match, pats = scmutil.matchandpats(wctx, pats, opts)
2388 2389 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
2389 2390 if not slowpath:
2390 2391 for f in match.files():
2391 2392 if follow and f not in wctx:
2392 2393 # If the file exists, it may be a directory, so let it
2393 2394 # take the slow path.
2394 2395 if os.path.exists(repo.wjoin(f)):
2395 2396 slowpath = True
2396 2397 continue
2397 2398 else:
2398 2399 raise error.Abort(_('cannot follow file not in parent '
2399 2400 'revision: "%s"') % f)
2400 2401 filelog = repo.file(f)
2401 2402 if not filelog:
2402 2403 # A zero count may be a directory or deleted file, so
2403 2404 # try to find matching entries on the slow path.
2404 2405 if follow:
2405 2406 raise error.Abort(
2406 2407 _('cannot follow nonexistent file: "%s"') % f)
2407 2408 slowpath = True
2408 2409
2409 2410 # We decided to fall back to the slowpath because at least one
2410 2411 # of the paths was not a file. Check to see if at least one of them
2411 2412 # existed in history - in that case, we'll continue down the
2412 2413 # slowpath; otherwise, we can turn off the slowpath
2413 2414 if slowpath:
2414 2415 for path in match.files():
2415 2416 if path == '.' or path in repo.store:
2416 2417 break
2417 2418 else:
2418 2419 slowpath = False
2419 2420
2420 2421 fpats = ('_patsfollow', '_patsfollowfirst')
2421 2422 fnopats = (('_ancestors', '_fancestors'),
2422 2423 ('_descendants', '_fdescendants'))
2423 2424 if slowpath:
2424 2425 # See walkchangerevs() slow path.
2425 2426 #
2426 2427 # pats/include/exclude cannot be represented as separate
2427 2428 # revset expressions as their filtering logic applies at file
2428 2429 # level. For instance "-I a -X a" matches a revision touching
2429 2430 # "a" and "b" while "file(a) and not file(b)" does
2430 2431 # not. Besides, filesets are evaluated against the working
2431 2432 # directory.
2432 2433 matchargs = ['r:', 'd:relpath']
2433 2434 for p in pats:
2434 2435 matchargs.append('p:' + p)
2435 2436 for p in opts.get('include', []):
2436 2437 matchargs.append('i:' + p)
2437 2438 for p in opts.get('exclude', []):
2438 2439 matchargs.append('x:' + p)
2439 2440 matchargs = ','.join(('%r' % p) for p in matchargs)
2440 2441 opts['_matchfiles'] = matchargs
2441 2442 if follow:
2442 2443 opts[fnopats[0][followfirst]] = '.'
2443 2444 else:
2444 2445 if follow:
2445 2446 if pats:
2446 2447 # follow() revset interprets its file argument as a
2447 2448 # manifest entry, so use match.files(), not pats.
2448 2449 opts[fpats[followfirst]] = list(match.files())
2449 2450 else:
2450 2451 op = fnopats[followdescendants][followfirst]
2451 2452 opts[op] = 'rev(%d)' % startrev
2452 2453 else:
2453 2454 opts['_patslog'] = list(pats)
2454 2455
2455 2456 filematcher = None
2456 2457 if opts.get('patch') or opts.get('stat'):
2457 2458 # When following files, track renames via a special matcher.
2458 2459 # If we're forced to take the slowpath it means we're following
2459 2460 # at least one pattern/directory, so don't bother with rename tracking.
2460 2461 if follow and not match.always() and not slowpath:
2461 2462 # _makefollowlogfilematcher expects its files argument to be
2462 2463 # relative to the repo root, so use match.files(), not pats.
2463 2464 filematcher = _makefollowlogfilematcher(repo, match.files(),
2464 2465 followfirst)
2465 2466 else:
2466 2467 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2467 2468 if filematcher is None:
2468 2469 filematcher = lambda rev: match
2469 2470
2470 2471 expr = []
2471 2472 for op, val in sorted(opts.iteritems()):
2472 2473 if not val:
2473 2474 continue
2474 2475 if op not in _opt2logrevset:
2475 2476 continue
2476 revop, andor = _opt2logrevset[op]
2477 revop, listop = _opt2logrevset[op]
2477 2478 if '%(val)' not in revop:
2478 2479 expr.append(revop)
2479 2480 else:
2480 if not isinstance(val, list):
2481 if not listop:
2481 2482 e = revop % {'val': val}
2482 2483 else:
2483 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2484 e = [revop % {'val': v} for v in val]
2485 e = revsetlang.formatspec(listop, e)
2484 2486 expr.append(e)
2485 2487
2486 2488 if expr:
2487 2489 expr = '(' + ' and '.join(expr) + ')'
2488 2490 else:
2489 2491 expr = None
2490 2492 return expr, filematcher
2491 2493
2492 2494 def _logrevs(repo, opts):
2493 2495 # Default --rev value depends on --follow but --follow behavior
2494 2496 # depends on revisions resolved from --rev...
2495 2497 follow = opts.get('follow') or opts.get('follow_first')
2496 2498 if opts.get('rev'):
2497 2499 revs = scmutil.revrange(repo, opts['rev'])
2498 2500 elif follow and repo.dirstate.p1() == nullid:
2499 2501 revs = smartset.baseset()
2500 2502 elif follow:
2501 2503 revs = repo.revs('reverse(:.)')
2502 2504 else:
2503 2505 revs = smartset.spanset(repo)
2504 2506 revs.reverse()
2505 2507 return revs
2506 2508
2507 2509 def getlogrevs(repo, pats, opts):
2508 2510 """Return (revs, filematcher) where revs is a smartset
2509 2511
2510 2512 If --stat or --patch is not passed, filematcher is None. Otherwise it
2511 2513 is a callable taking a revision number and returning a match objects
2512 2514 filtering the files to be detailed when displaying the revision.
2513 2515 """
2514 2516 limit = loglimit(opts)
2515 2517 revs = _logrevs(repo, opts)
2516 2518 if not revs:
2517 2519 return smartset.baseset(), None
2518 2520 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2519 2521 if opts.get('graph') and opts.get('rev'):
2520 2522 # User-specified revs might be unsorted, but don't sort before
2521 2523 # _makelogrevset because it might depend on the order of revs
2522 2524 if not (revs.isdescending() or revs.istopo()):
2523 2525 revs.sort(reverse=True)
2524 2526 if expr:
2525 2527 matcher = revset.match(None, expr)
2526 2528 revs = matcher(repo, revs)
2527 2529 if limit is not None:
2528 2530 revs = revs.slice(0, limit)
2529 2531 return revs, filematcher
2530 2532
2531 2533 def _parselinerangelogopt(repo, opts):
2532 2534 """Parse --line-range log option and return a list of tuples (filename,
2533 2535 (fromline, toline)).
2534 2536 """
2535 2537 linerangebyfname = []
2536 2538 for pat in opts.get('line_range', []):
2537 2539 try:
2538 2540 pat, linerange = pat.rsplit(',', 1)
2539 2541 except ValueError:
2540 2542 raise error.Abort(_('malformatted line-range pattern %s') % pat)
2541 2543 try:
2542 2544 fromline, toline = map(int, linerange.split(':'))
2543 2545 except ValueError:
2544 2546 raise error.Abort(_("invalid line range for %s") % pat)
2545 2547 msg = _("line range pattern '%s' must match exactly one file") % pat
2546 2548 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
2547 2549 linerangebyfname.append(
2548 2550 (fname, util.processlinerange(fromline, toline)))
2549 2551 return linerangebyfname
2550 2552
2551 2553 def getloglinerangerevs(repo, userrevs, opts):
2552 2554 """Return (revs, filematcher, hunksfilter).
2553 2555
2554 2556 "revs" are revisions obtained by processing "line-range" log options and
2555 2557 walking block ancestors of each specified file/line-range.
2556 2558
2557 2559 "filematcher(rev) -> match" is a factory function returning a match object
2558 2560 for a given revision for file patterns specified in --line-range option.
2559 2561 If neither --stat nor --patch options are passed, "filematcher" is None.
2560 2562
2561 2563 "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
2562 2564 returning a hunks filtering function.
2563 2565 If neither --stat nor --patch options are passed, "filterhunks" is None.
2564 2566 """
2565 2567 wctx = repo[None]
2566 2568
2567 2569 # Two-levels map of "rev -> file ctx -> [line range]".
2568 2570 linerangesbyrev = {}
2569 2571 for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
2570 2572 if fname not in wctx:
2571 2573 raise error.Abort(_('cannot follow file not in parent '
2572 2574 'revision: "%s"') % fname)
2573 2575 fctx = wctx.filectx(fname)
2574 2576 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
2575 2577 rev = fctx.introrev()
2576 2578 if rev not in userrevs:
2577 2579 continue
2578 2580 linerangesbyrev.setdefault(
2579 2581 rev, {}).setdefault(
2580 2582 fctx.path(), []).append(linerange)
2581 2583
2582 2584 filematcher = None
2583 2585 hunksfilter = None
2584 2586 if opts.get('patch') or opts.get('stat'):
2585 2587
2586 2588 def nofilterhunksfn(fctx, hunks):
2587 2589 return hunks
2588 2590
2589 2591 def hunksfilter(rev):
2590 2592 fctxlineranges = linerangesbyrev.get(rev)
2591 2593 if fctxlineranges is None:
2592 2594 return nofilterhunksfn
2593 2595
2594 2596 def filterfn(fctx, hunks):
2595 2597 lineranges = fctxlineranges.get(fctx.path())
2596 2598 if lineranges is not None:
2597 2599 for hr, lines in hunks:
2598 2600 if hr is None: # binary
2599 2601 yield hr, lines
2600 2602 continue
2601 2603 if any(mdiff.hunkinrange(hr[2:], lr)
2602 2604 for lr in lineranges):
2603 2605 yield hr, lines
2604 2606 else:
2605 2607 for hunk in hunks:
2606 2608 yield hunk
2607 2609
2608 2610 return filterfn
2609 2611
2610 2612 def filematcher(rev):
2611 2613 files = list(linerangesbyrev.get(rev, []))
2612 2614 return scmutil.matchfiles(repo, files)
2613 2615
2614 2616 revs = sorted(linerangesbyrev, reverse=True)
2615 2617
2616 2618 return revs, filematcher, hunksfilter
2617 2619
2618 2620 def _graphnodeformatter(ui, displayer):
2619 2621 spec = ui.config('ui', 'graphnodetemplate')
2620 2622 if not spec:
2621 2623 return templatekw.showgraphnode # fast path for "{graphnode}"
2622 2624
2623 2625 spec = templater.unquotestring(spec)
2624 2626 tres = formatter.templateresources(ui)
2625 2627 if isinstance(displayer, changeset_templater):
2626 2628 tres['cache'] = displayer.cache # reuse cache of slow templates
2627 2629 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
2628 2630 resources=tres)
2629 2631 def formatnode(repo, ctx):
2630 2632 props = {'ctx': ctx, 'repo': repo, 'revcache': {}}
2631 2633 return templ.render(props)
2632 2634 return formatnode
2633 2635
2634 2636 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2635 2637 filematcher=None, props=None):
2636 2638 props = props or {}
2637 2639 formatnode = _graphnodeformatter(ui, displayer)
2638 2640 state = graphmod.asciistate()
2639 2641 styles = state['styles']
2640 2642
2641 2643 # only set graph styling if HGPLAIN is not set.
2642 2644 if ui.plain('graph'):
2643 2645 # set all edge styles to |, the default pre-3.8 behaviour
2644 2646 styles.update(dict.fromkeys(styles, '|'))
2645 2647 else:
2646 2648 edgetypes = {
2647 2649 'parent': graphmod.PARENT,
2648 2650 'grandparent': graphmod.GRANDPARENT,
2649 2651 'missing': graphmod.MISSINGPARENT
2650 2652 }
2651 2653 for name, key in edgetypes.items():
2652 2654 # experimental config: experimental.graphstyle.*
2653 2655 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2654 2656 styles[key])
2655 2657 if not styles[key]:
2656 2658 styles[key] = None
2657 2659
2658 2660 # experimental config: experimental.graphshorten
2659 2661 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2660 2662
2661 2663 for rev, type, ctx, parents in dag:
2662 2664 char = formatnode(repo, ctx)
2663 2665 copies = None
2664 2666 if getrenamed and ctx.rev():
2665 2667 copies = []
2666 2668 for fn in ctx.files():
2667 2669 rename = getrenamed(fn, ctx.rev())
2668 2670 if rename:
2669 2671 copies.append((fn, rename[0]))
2670 2672 revmatchfn = None
2671 2673 if filematcher is not None:
2672 2674 revmatchfn = filematcher(ctx.rev())
2673 2675 edges = edgefn(type, char, state, rev, parents)
2674 2676 firstedge = next(edges)
2675 2677 width = firstedge[2]
2676 2678 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
2677 2679 _graphwidth=width, **pycompat.strkwargs(props))
2678 2680 lines = displayer.hunk.pop(rev).split('\n')
2679 2681 if not lines[-1]:
2680 2682 del lines[-1]
2681 2683 displayer.flush(ctx)
2682 2684 for type, char, width, coldata in itertools.chain([firstedge], edges):
2683 2685 graphmod.ascii(ui, state, type, char, lines, coldata)
2684 2686 lines = []
2685 2687 displayer.close()
2686 2688
2687 2689 def graphlog(ui, repo, revs, filematcher, opts):
2688 2690 # Parameters are identical to log command ones
2689 2691 revdag = graphmod.dagwalker(repo, revs)
2690 2692
2691 2693 getrenamed = None
2692 2694 if opts.get('copies'):
2693 2695 endrev = None
2694 2696 if opts.get('rev'):
2695 2697 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2696 2698 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2697 2699
2698 2700 ui.pager('log')
2699 2701 displayer = show_changeset(ui, repo, opts, buffered=True)
2700 2702 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2701 2703 filematcher)
2702 2704
2703 2705 def checkunsupportedgraphflags(pats, opts):
2704 2706 for op in ["newest_first"]:
2705 2707 if op in opts and opts[op]:
2706 2708 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2707 2709 % op.replace("_", "-"))
2708 2710
2709 2711 def graphrevs(repo, nodes, opts):
2710 2712 limit = loglimit(opts)
2711 2713 nodes.reverse()
2712 2714 if limit is not None:
2713 2715 nodes = nodes[:limit]
2714 2716 return graphmod.nodes(repo, nodes)
2715 2717
2716 2718 def add(ui, repo, match, prefix, explicitonly, **opts):
2717 2719 join = lambda f: os.path.join(prefix, f)
2718 2720 bad = []
2719 2721
2720 2722 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2721 2723 names = []
2722 2724 wctx = repo[None]
2723 2725 cca = None
2724 2726 abort, warn = scmutil.checkportabilityalert(ui)
2725 2727 if abort or warn:
2726 2728 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2727 2729
2728 2730 badmatch = matchmod.badmatch(match, badfn)
2729 2731 dirstate = repo.dirstate
2730 2732 # We don't want to just call wctx.walk here, since it would return a lot of
2731 2733 # clean files, which we aren't interested in and takes time.
2732 2734 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2733 2735 unknown=True, ignored=False, full=False)):
2734 2736 exact = match.exact(f)
2735 2737 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2736 2738 if cca:
2737 2739 cca(f)
2738 2740 names.append(f)
2739 2741 if ui.verbose or not exact:
2740 2742 ui.status(_('adding %s\n') % match.rel(f))
2741 2743
2742 2744 for subpath in sorted(wctx.substate):
2743 2745 sub = wctx.sub(subpath)
2744 2746 try:
2745 2747 submatch = matchmod.subdirmatcher(subpath, match)
2746 2748 if opts.get(r'subrepos'):
2747 2749 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2748 2750 else:
2749 2751 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2750 2752 except error.LookupError:
2751 2753 ui.status(_("skipping missing subrepository: %s\n")
2752 2754 % join(subpath))
2753 2755
2754 2756 if not opts.get(r'dry_run'):
2755 2757 rejected = wctx.add(names, prefix)
2756 2758 bad.extend(f for f in rejected if f in match.files())
2757 2759 return bad
2758 2760
2759 2761 def addwebdirpath(repo, serverpath, webconf):
2760 2762 webconf[serverpath] = repo.root
2761 2763 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2762 2764
2763 2765 for r in repo.revs('filelog("path:.hgsub")'):
2764 2766 ctx = repo[r]
2765 2767 for subpath in ctx.substate:
2766 2768 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2767 2769
2768 2770 def forget(ui, repo, match, prefix, explicitonly):
2769 2771 join = lambda f: os.path.join(prefix, f)
2770 2772 bad = []
2771 2773 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2772 2774 wctx = repo[None]
2773 2775 forgot = []
2774 2776
2775 2777 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2776 2778 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2777 2779 if explicitonly:
2778 2780 forget = [f for f in forget if match.exact(f)]
2779 2781
2780 2782 for subpath in sorted(wctx.substate):
2781 2783 sub = wctx.sub(subpath)
2782 2784 try:
2783 2785 submatch = matchmod.subdirmatcher(subpath, match)
2784 2786 subbad, subforgot = sub.forget(submatch, prefix)
2785 2787 bad.extend([subpath + '/' + f for f in subbad])
2786 2788 forgot.extend([subpath + '/' + f for f in subforgot])
2787 2789 except error.LookupError:
2788 2790 ui.status(_("skipping missing subrepository: %s\n")
2789 2791 % join(subpath))
2790 2792
2791 2793 if not explicitonly:
2792 2794 for f in match.files():
2793 2795 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2794 2796 if f not in forgot:
2795 2797 if repo.wvfs.exists(f):
2796 2798 # Don't complain if the exact case match wasn't given.
2797 2799 # But don't do this until after checking 'forgot', so
2798 2800 # that subrepo files aren't normalized, and this op is
2799 2801 # purely from data cached by the status walk above.
2800 2802 if repo.dirstate.normalize(f) in repo.dirstate:
2801 2803 continue
2802 2804 ui.warn(_('not removing %s: '
2803 2805 'file is already untracked\n')
2804 2806 % match.rel(f))
2805 2807 bad.append(f)
2806 2808
2807 2809 for f in forget:
2808 2810 if ui.verbose or not match.exact(f):
2809 2811 ui.status(_('removing %s\n') % match.rel(f))
2810 2812
2811 2813 rejected = wctx.forget(forget, prefix)
2812 2814 bad.extend(f for f in rejected if f in match.files())
2813 2815 forgot.extend(f for f in forget if f not in rejected)
2814 2816 return bad, forgot
2815 2817
2816 2818 def files(ui, ctx, m, fm, fmt, subrepos):
2817 2819 rev = ctx.rev()
2818 2820 ret = 1
2819 2821 ds = ctx.repo().dirstate
2820 2822
2821 2823 for f in ctx.matches(m):
2822 2824 if rev is None and ds[f] == 'r':
2823 2825 continue
2824 2826 fm.startitem()
2825 2827 if ui.verbose:
2826 2828 fc = ctx[f]
2827 2829 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2828 2830 fm.data(abspath=f)
2829 2831 fm.write('path', fmt, m.rel(f))
2830 2832 ret = 0
2831 2833
2832 2834 for subpath in sorted(ctx.substate):
2833 2835 submatch = matchmod.subdirmatcher(subpath, m)
2834 2836 if (subrepos or m.exact(subpath) or any(submatch.files())):
2835 2837 sub = ctx.sub(subpath)
2836 2838 try:
2837 2839 recurse = m.exact(subpath) or subrepos
2838 2840 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2839 2841 ret = 0
2840 2842 except error.LookupError:
2841 2843 ui.status(_("skipping missing subrepository: %s\n")
2842 2844 % m.abs(subpath))
2843 2845
2844 2846 return ret
2845 2847
2846 2848 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2847 2849 join = lambda f: os.path.join(prefix, f)
2848 2850 ret = 0
2849 2851 s = repo.status(match=m, clean=True)
2850 2852 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2851 2853
2852 2854 wctx = repo[None]
2853 2855
2854 2856 if warnings is None:
2855 2857 warnings = []
2856 2858 warn = True
2857 2859 else:
2858 2860 warn = False
2859 2861
2860 2862 subs = sorted(wctx.substate)
2861 2863 total = len(subs)
2862 2864 count = 0
2863 2865 for subpath in subs:
2864 2866 count += 1
2865 2867 submatch = matchmod.subdirmatcher(subpath, m)
2866 2868 if subrepos or m.exact(subpath) or any(submatch.files()):
2867 2869 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2868 2870 sub = wctx.sub(subpath)
2869 2871 try:
2870 2872 if sub.removefiles(submatch, prefix, after, force, subrepos,
2871 2873 warnings):
2872 2874 ret = 1
2873 2875 except error.LookupError:
2874 2876 warnings.append(_("skipping missing subrepository: %s\n")
2875 2877 % join(subpath))
2876 2878 ui.progress(_('searching'), None)
2877 2879
2878 2880 # warn about failure to delete explicit files/dirs
2879 2881 deleteddirs = util.dirs(deleted)
2880 2882 files = m.files()
2881 2883 total = len(files)
2882 2884 count = 0
2883 2885 for f in files:
2884 2886 def insubrepo():
2885 2887 for subpath in wctx.substate:
2886 2888 if f.startswith(subpath + '/'):
2887 2889 return True
2888 2890 return False
2889 2891
2890 2892 count += 1
2891 2893 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2892 2894 isdir = f in deleteddirs or wctx.hasdir(f)
2893 2895 if (f in repo.dirstate or isdir or f == '.'
2894 2896 or insubrepo() or f in subs):
2895 2897 continue
2896 2898
2897 2899 if repo.wvfs.exists(f):
2898 2900 if repo.wvfs.isdir(f):
2899 2901 warnings.append(_('not removing %s: no tracked files\n')
2900 2902 % m.rel(f))
2901 2903 else:
2902 2904 warnings.append(_('not removing %s: file is untracked\n')
2903 2905 % m.rel(f))
2904 2906 # missing files will generate a warning elsewhere
2905 2907 ret = 1
2906 2908 ui.progress(_('deleting'), None)
2907 2909
2908 2910 if force:
2909 2911 list = modified + deleted + clean + added
2910 2912 elif after:
2911 2913 list = deleted
2912 2914 remaining = modified + added + clean
2913 2915 total = len(remaining)
2914 2916 count = 0
2915 2917 for f in remaining:
2916 2918 count += 1
2917 2919 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2918 2920 if ui.verbose or (f in files):
2919 2921 warnings.append(_('not removing %s: file still exists\n')
2920 2922 % m.rel(f))
2921 2923 ret = 1
2922 2924 ui.progress(_('skipping'), None)
2923 2925 else:
2924 2926 list = deleted + clean
2925 2927 total = len(modified) + len(added)
2926 2928 count = 0
2927 2929 for f in modified:
2928 2930 count += 1
2929 2931 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2930 2932 warnings.append(_('not removing %s: file is modified (use -f'
2931 2933 ' to force removal)\n') % m.rel(f))
2932 2934 ret = 1
2933 2935 for f in added:
2934 2936 count += 1
2935 2937 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2936 2938 warnings.append(_("not removing %s: file has been marked for add"
2937 2939 " (use 'hg forget' to undo add)\n") % m.rel(f))
2938 2940 ret = 1
2939 2941 ui.progress(_('skipping'), None)
2940 2942
2941 2943 list = sorted(list)
2942 2944 total = len(list)
2943 2945 count = 0
2944 2946 for f in list:
2945 2947 count += 1
2946 2948 if ui.verbose or not m.exact(f):
2947 2949 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2948 2950 ui.status(_('removing %s\n') % m.rel(f))
2949 2951 ui.progress(_('deleting'), None)
2950 2952
2951 2953 with repo.wlock():
2952 2954 if not after:
2953 2955 for f in list:
2954 2956 if f in added:
2955 2957 continue # we never unlink added files on remove
2956 2958 repo.wvfs.unlinkpath(f, ignoremissing=True)
2957 2959 repo[None].forget(list)
2958 2960
2959 2961 if warn:
2960 2962 for warning in warnings:
2961 2963 ui.warn(warning)
2962 2964
2963 2965 return ret
2964 2966
2965 2967 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2966 2968 err = 1
2967 2969 opts = pycompat.byteskwargs(opts)
2968 2970
2969 2971 def write(path):
2970 2972 filename = None
2971 2973 if fntemplate:
2972 2974 filename = makefilename(repo, fntemplate, ctx.node(),
2973 2975 pathname=os.path.join(prefix, path))
2974 2976 # attempt to create the directory if it does not already exist
2975 2977 try:
2976 2978 os.makedirs(os.path.dirname(filename))
2977 2979 except OSError:
2978 2980 pass
2979 2981 with formatter.maybereopen(basefm, filename, opts) as fm:
2980 2982 data = ctx[path].data()
2981 2983 if opts.get('decode'):
2982 2984 data = repo.wwritedata(path, data)
2983 2985 fm.startitem()
2984 2986 fm.write('data', '%s', data)
2985 2987 fm.data(abspath=path, path=matcher.rel(path))
2986 2988
2987 2989 # Automation often uses hg cat on single files, so special case it
2988 2990 # for performance to avoid the cost of parsing the manifest.
2989 2991 if len(matcher.files()) == 1 and not matcher.anypats():
2990 2992 file = matcher.files()[0]
2991 2993 mfl = repo.manifestlog
2992 2994 mfnode = ctx.manifestnode()
2993 2995 try:
2994 2996 if mfnode and mfl[mfnode].find(file)[0]:
2995 2997 write(file)
2996 2998 return 0
2997 2999 except KeyError:
2998 3000 pass
2999 3001
3000 3002 for abs in ctx.walk(matcher):
3001 3003 write(abs)
3002 3004 err = 0
3003 3005
3004 3006 for subpath in sorted(ctx.substate):
3005 3007 sub = ctx.sub(subpath)
3006 3008 try:
3007 3009 submatch = matchmod.subdirmatcher(subpath, matcher)
3008 3010
3009 3011 if not sub.cat(submatch, basefm, fntemplate,
3010 3012 os.path.join(prefix, sub._path),
3011 3013 **pycompat.strkwargs(opts)):
3012 3014 err = 0
3013 3015 except error.RepoLookupError:
3014 3016 ui.status(_("skipping missing subrepository: %s\n")
3015 3017 % os.path.join(prefix, subpath))
3016 3018
3017 3019 return err
3018 3020
3019 3021 def commit(ui, repo, commitfunc, pats, opts):
3020 3022 '''commit the specified files or all outstanding changes'''
3021 3023 date = opts.get('date')
3022 3024 if date:
3023 3025 opts['date'] = util.parsedate(date)
3024 3026 message = logmessage(ui, opts)
3025 3027 matcher = scmutil.match(repo[None], pats, opts)
3026 3028
3027 3029 dsguard = None
3028 3030 # extract addremove carefully -- this function can be called from a command
3029 3031 # that doesn't support addremove
3030 3032 if opts.get('addremove'):
3031 3033 dsguard = dirstateguard.dirstateguard(repo, 'commit')
3032 3034 with dsguard or util.nullcontextmanager():
3033 3035 if dsguard:
3034 3036 if scmutil.addremove(repo, matcher, "", opts) != 0:
3035 3037 raise error.Abort(
3036 3038 _("failed to mark all new/missing files as added/removed"))
3037 3039
3038 3040 return commitfunc(ui, repo, message, matcher, opts)
3039 3041
3040 3042 def samefile(f, ctx1, ctx2):
3041 3043 if f in ctx1.manifest():
3042 3044 a = ctx1.filectx(f)
3043 3045 if f in ctx2.manifest():
3044 3046 b = ctx2.filectx(f)
3045 3047 return (not a.cmp(b)
3046 3048 and a.flags() == b.flags())
3047 3049 else:
3048 3050 return False
3049 3051 else:
3050 3052 return f not in ctx2.manifest()
3051 3053
3052 3054 def amend(ui, repo, old, extra, pats, opts):
3053 3055 # avoid cycle context -> subrepo -> cmdutil
3054 3056 from . import context
3055 3057
3056 3058 # amend will reuse the existing user if not specified, but the obsolete
3057 3059 # marker creation requires that the current user's name is specified.
3058 3060 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3059 3061 ui.username() # raise exception if username not set
3060 3062
3061 3063 ui.note(_('amending changeset %s\n') % old)
3062 3064 base = old.p1()
3063 3065
3064 3066 with repo.wlock(), repo.lock(), repo.transaction('amend'):
3065 3067 # Participating changesets:
3066 3068 #
3067 3069 # wctx o - workingctx that contains changes from working copy
3068 3070 # | to go into amending commit
3069 3071 # |
3070 3072 # old o - changeset to amend
3071 3073 # |
3072 3074 # base o - first parent of the changeset to amend
3073 3075 wctx = repo[None]
3074 3076
3075 3077 # Copy to avoid mutating input
3076 3078 extra = extra.copy()
3077 3079 # Update extra dict from amended commit (e.g. to preserve graft
3078 3080 # source)
3079 3081 extra.update(old.extra())
3080 3082
3081 3083 # Also update it from the from the wctx
3082 3084 extra.update(wctx.extra())
3083 3085
3084 3086 user = opts.get('user') or old.user()
3085 3087 date = opts.get('date') or old.date()
3086 3088
3087 3089 # Parse the date to allow comparison between date and old.date()
3088 3090 date = util.parsedate(date)
3089 3091
3090 3092 if len(old.parents()) > 1:
3091 3093 # ctx.files() isn't reliable for merges, so fall back to the
3092 3094 # slower repo.status() method
3093 3095 files = set([fn for st in repo.status(base, old)[:3]
3094 3096 for fn in st])
3095 3097 else:
3096 3098 files = set(old.files())
3097 3099
3098 3100 # add/remove the files to the working copy if the "addremove" option
3099 3101 # was specified.
3100 3102 matcher = scmutil.match(wctx, pats, opts)
3101 3103 if (opts.get('addremove')
3102 3104 and scmutil.addremove(repo, matcher, "", opts)):
3103 3105 raise error.Abort(
3104 3106 _("failed to mark all new/missing files as added/removed"))
3105 3107
3106 3108 # Check subrepos. This depends on in-place wctx._status update in
3107 3109 # subrepo.precommit(). To minimize the risk of this hack, we do
3108 3110 # nothing if .hgsub does not exist.
3109 3111 if '.hgsub' in wctx or '.hgsub' in old:
3110 3112 from . import subrepo # avoid cycle: cmdutil -> subrepo -> cmdutil
3111 3113 subs, commitsubs, newsubstate = subrepo.precommit(
3112 3114 ui, wctx, wctx._status, matcher)
3113 3115 # amend should abort if commitsubrepos is enabled
3114 3116 assert not commitsubs
3115 3117 if subs:
3116 3118 subrepo.writestate(repo, newsubstate)
3117 3119
3118 3120 filestoamend = set(f for f in wctx.files() if matcher(f))
3119 3121
3120 3122 changes = (len(filestoamend) > 0)
3121 3123 if changes:
3122 3124 # Recompute copies (avoid recording a -> b -> a)
3123 3125 copied = copies.pathcopies(base, wctx, matcher)
3124 3126 if old.p2:
3125 3127 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3126 3128
3127 3129 # Prune files which were reverted by the updates: if old
3128 3130 # introduced file X and the file was renamed in the working
3129 3131 # copy, then those two files are the same and
3130 3132 # we can discard X from our list of files. Likewise if X
3131 3133 # was removed, it's no longer relevant. If X is missing (aka
3132 3134 # deleted), old X must be preserved.
3133 3135 files.update(filestoamend)
3134 3136 files = [f for f in files if (not samefile(f, wctx, base)
3135 3137 or f in wctx.deleted())]
3136 3138
3137 3139 def filectxfn(repo, ctx_, path):
3138 3140 try:
3139 3141 # If the file being considered is not amongst the files
3140 3142 # to be amended, we should return the file context from the
3141 3143 # old changeset. This avoids issues when only some files in
3142 3144 # the working copy are being amended but there are also
3143 3145 # changes to other files from the old changeset.
3144 3146 if path not in filestoamend:
3145 3147 return old.filectx(path)
3146 3148
3147 3149 # Return None for removed files.
3148 3150 if path in wctx.removed():
3149 3151 return None
3150 3152
3151 3153 fctx = wctx[path]
3152 3154 flags = fctx.flags()
3153 3155 mctx = context.memfilectx(repo, ctx_,
3154 3156 fctx.path(), fctx.data(),
3155 3157 islink='l' in flags,
3156 3158 isexec='x' in flags,
3157 3159 copied=copied.get(path))
3158 3160 return mctx
3159 3161 except KeyError:
3160 3162 return None
3161 3163 else:
3162 3164 ui.note(_('copying changeset %s to %s\n') % (old, base))
3163 3165
3164 3166 # Use version of files as in the old cset
3165 3167 def filectxfn(repo, ctx_, path):
3166 3168 try:
3167 3169 return old.filectx(path)
3168 3170 except KeyError:
3169 3171 return None
3170 3172
3171 3173 # See if we got a message from -m or -l, if not, open the editor with
3172 3174 # the message of the changeset to amend.
3173 3175 message = logmessage(ui, opts)
3174 3176
3175 3177 editform = mergeeditform(old, 'commit.amend')
3176 3178 editor = getcommiteditor(editform=editform,
3177 3179 **pycompat.strkwargs(opts))
3178 3180
3179 3181 if not message:
3180 3182 editor = getcommiteditor(edit=True, editform=editform)
3181 3183 message = old.description()
3182 3184
3183 3185 pureextra = extra.copy()
3184 3186 extra['amend_source'] = old.hex()
3185 3187
3186 3188 new = context.memctx(repo,
3187 3189 parents=[base.node(), old.p2().node()],
3188 3190 text=message,
3189 3191 files=files,
3190 3192 filectxfn=filectxfn,
3191 3193 user=user,
3192 3194 date=date,
3193 3195 extra=extra,
3194 3196 editor=editor)
3195 3197
3196 3198 newdesc = changelog.stripdesc(new.description())
3197 3199 if ((not changes)
3198 3200 and newdesc == old.description()
3199 3201 and user == old.user()
3200 3202 and date == old.date()
3201 3203 and pureextra == old.extra()):
3202 3204 # nothing changed. continuing here would create a new node
3203 3205 # anyway because of the amend_source noise.
3204 3206 #
3205 3207 # This not what we expect from amend.
3206 3208 return old.node()
3207 3209
3208 3210 if opts.get('secret'):
3209 3211 commitphase = 'secret'
3210 3212 else:
3211 3213 commitphase = old.phase()
3212 3214 overrides = {('phases', 'new-commit'): commitphase}
3213 3215 with ui.configoverride(overrides, 'amend'):
3214 3216 newid = repo.commitctx(new)
3215 3217
3216 3218 # Reroute the working copy parent to the new changeset
3217 3219 repo.setparents(newid, nullid)
3218 3220 mapping = {old.node(): (newid,)}
3219 3221 obsmetadata = None
3220 3222 if opts.get('note'):
3221 3223 obsmetadata = {'note': opts['note']}
3222 3224 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
3223 3225
3224 3226 # Fixing the dirstate because localrepo.commitctx does not update
3225 3227 # it. This is rather convenient because we did not need to update
3226 3228 # the dirstate for all the files in the new commit which commitctx
3227 3229 # could have done if it updated the dirstate. Now, we can
3228 3230 # selectively update the dirstate only for the amended files.
3229 3231 dirstate = repo.dirstate
3230 3232
3231 3233 # Update the state of the files which were added and
3232 3234 # and modified in the amend to "normal" in the dirstate.
3233 3235 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3234 3236 for f in normalfiles:
3235 3237 dirstate.normal(f)
3236 3238
3237 3239 # Update the state of files which were removed in the amend
3238 3240 # to "removed" in the dirstate.
3239 3241 removedfiles = set(wctx.removed()) & filestoamend
3240 3242 for f in removedfiles:
3241 3243 dirstate.drop(f)
3242 3244
3243 3245 return newid
3244 3246
3245 3247 def commiteditor(repo, ctx, subs, editform=''):
3246 3248 if ctx.description():
3247 3249 return ctx.description()
3248 3250 return commitforceeditor(repo, ctx, subs, editform=editform,
3249 3251 unchangedmessagedetection=True)
3250 3252
3251 3253 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3252 3254 editform='', unchangedmessagedetection=False):
3253 3255 if not extramsg:
3254 3256 extramsg = _("Leave message empty to abort commit.")
3255 3257
3256 3258 forms = [e for e in editform.split('.') if e]
3257 3259 forms.insert(0, 'changeset')
3258 3260 templatetext = None
3259 3261 while forms:
3260 3262 ref = '.'.join(forms)
3261 3263 if repo.ui.config('committemplate', ref):
3262 3264 templatetext = committext = buildcommittemplate(
3263 3265 repo, ctx, subs, extramsg, ref)
3264 3266 break
3265 3267 forms.pop()
3266 3268 else:
3267 3269 committext = buildcommittext(repo, ctx, subs, extramsg)
3268 3270
3269 3271 # run editor in the repository root
3270 3272 olddir = pycompat.getcwd()
3271 3273 os.chdir(repo.root)
3272 3274
3273 3275 # make in-memory changes visible to external process
3274 3276 tr = repo.currenttransaction()
3275 3277 repo.dirstate.write(tr)
3276 3278 pending = tr and tr.writepending() and repo.root
3277 3279
3278 3280 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3279 3281 editform=editform, pending=pending,
3280 3282 repopath=repo.path, action='commit')
3281 3283 text = editortext
3282 3284
3283 3285 # strip away anything below this special string (used for editors that want
3284 3286 # to display the diff)
3285 3287 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3286 3288 if stripbelow:
3287 3289 text = text[:stripbelow.start()]
3288 3290
3289 3291 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3290 3292 os.chdir(olddir)
3291 3293
3292 3294 if finishdesc:
3293 3295 text = finishdesc(text)
3294 3296 if not text.strip():
3295 3297 raise error.Abort(_("empty commit message"))
3296 3298 if unchangedmessagedetection and editortext == templatetext:
3297 3299 raise error.Abort(_("commit message unchanged"))
3298 3300
3299 3301 return text
3300 3302
3301 3303 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3302 3304 ui = repo.ui
3303 3305 spec = formatter.templatespec(ref, None, None)
3304 3306 t = changeset_templater(ui, repo, spec, None, {}, False)
3305 3307 t.t.cache.update((k, templater.unquotestring(v))
3306 3308 for k, v in repo.ui.configitems('committemplate'))
3307 3309
3308 3310 if not extramsg:
3309 3311 extramsg = '' # ensure that extramsg is string
3310 3312
3311 3313 ui.pushbuffer()
3312 3314 t.show(ctx, extramsg=extramsg)
3313 3315 return ui.popbuffer()
3314 3316
3315 3317 def hgprefix(msg):
3316 3318 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3317 3319
3318 3320 def buildcommittext(repo, ctx, subs, extramsg):
3319 3321 edittext = []
3320 3322 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3321 3323 if ctx.description():
3322 3324 edittext.append(ctx.description())
3323 3325 edittext.append("")
3324 3326 edittext.append("") # Empty line between message and comments.
3325 3327 edittext.append(hgprefix(_("Enter commit message."
3326 3328 " Lines beginning with 'HG:' are removed.")))
3327 3329 edittext.append(hgprefix(extramsg))
3328 3330 edittext.append("HG: --")
3329 3331 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3330 3332 if ctx.p2():
3331 3333 edittext.append(hgprefix(_("branch merge")))
3332 3334 if ctx.branch():
3333 3335 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3334 3336 if bookmarks.isactivewdirparent(repo):
3335 3337 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3336 3338 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3337 3339 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3338 3340 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3339 3341 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3340 3342 if not added and not modified and not removed:
3341 3343 edittext.append(hgprefix(_("no files changed")))
3342 3344 edittext.append("")
3343 3345
3344 3346 return "\n".join(edittext)
3345 3347
3346 3348 def commitstatus(repo, node, branch, bheads=None, opts=None):
3347 3349 if opts is None:
3348 3350 opts = {}
3349 3351 ctx = repo[node]
3350 3352 parents = ctx.parents()
3351 3353
3352 3354 if (not opts.get('amend') and bheads and node not in bheads and not
3353 3355 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3354 3356 repo.ui.status(_('created new head\n'))
3355 3357 # The message is not printed for initial roots. For the other
3356 3358 # changesets, it is printed in the following situations:
3357 3359 #
3358 3360 # Par column: for the 2 parents with ...
3359 3361 # N: null or no parent
3360 3362 # B: parent is on another named branch
3361 3363 # C: parent is a regular non head changeset
3362 3364 # H: parent was a branch head of the current branch
3363 3365 # Msg column: whether we print "created new head" message
3364 3366 # In the following, it is assumed that there already exists some
3365 3367 # initial branch heads of the current branch, otherwise nothing is
3366 3368 # printed anyway.
3367 3369 #
3368 3370 # Par Msg Comment
3369 3371 # N N y additional topo root
3370 3372 #
3371 3373 # B N y additional branch root
3372 3374 # C N y additional topo head
3373 3375 # H N n usual case
3374 3376 #
3375 3377 # B B y weird additional branch root
3376 3378 # C B y branch merge
3377 3379 # H B n merge with named branch
3378 3380 #
3379 3381 # C C y additional head from merge
3380 3382 # C H n merge with a head
3381 3383 #
3382 3384 # H H n head merge: head count decreases
3383 3385
3384 3386 if not opts.get('close_branch'):
3385 3387 for r in parents:
3386 3388 if r.closesbranch() and r.branch() == branch:
3387 3389 repo.ui.status(_('reopening closed branch head %d\n') % r)
3388 3390
3389 3391 if repo.ui.debugflag:
3390 3392 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3391 3393 elif repo.ui.verbose:
3392 3394 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3393 3395
3394 3396 def postcommitstatus(repo, pats, opts):
3395 3397 return repo.status(match=scmutil.match(repo[None], pats, opts))
3396 3398
3397 3399 def revert(ui, repo, ctx, parents, *pats, **opts):
3398 3400 opts = pycompat.byteskwargs(opts)
3399 3401 parent, p2 = parents
3400 3402 node = ctx.node()
3401 3403
3402 3404 mf = ctx.manifest()
3403 3405 if node == p2:
3404 3406 parent = p2
3405 3407
3406 3408 # need all matching names in dirstate and manifest of target rev,
3407 3409 # so have to walk both. do not print errors if files exist in one
3408 3410 # but not other. in both cases, filesets should be evaluated against
3409 3411 # workingctx to get consistent result (issue4497). this means 'set:**'
3410 3412 # cannot be used to select missing files from target rev.
3411 3413
3412 3414 # `names` is a mapping for all elements in working copy and target revision
3413 3415 # The mapping is in the form:
3414 3416 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3415 3417 names = {}
3416 3418
3417 3419 with repo.wlock():
3418 3420 ## filling of the `names` mapping
3419 3421 # walk dirstate to fill `names`
3420 3422
3421 3423 interactive = opts.get('interactive', False)
3422 3424 wctx = repo[None]
3423 3425 m = scmutil.match(wctx, pats, opts)
3424 3426
3425 3427 # we'll need this later
3426 3428 targetsubs = sorted(s for s in wctx.substate if m(s))
3427 3429
3428 3430 if not m.always():
3429 3431 matcher = matchmod.badmatch(m, lambda x, y: False)
3430 3432 for abs in wctx.walk(matcher):
3431 3433 names[abs] = m.rel(abs), m.exact(abs)
3432 3434
3433 3435 # walk target manifest to fill `names`
3434 3436
3435 3437 def badfn(path, msg):
3436 3438 if path in names:
3437 3439 return
3438 3440 if path in ctx.substate:
3439 3441 return
3440 3442 path_ = path + '/'
3441 3443 for f in names:
3442 3444 if f.startswith(path_):
3443 3445 return
3444 3446 ui.warn("%s: %s\n" % (m.rel(path), msg))
3445 3447
3446 3448 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3447 3449 if abs not in names:
3448 3450 names[abs] = m.rel(abs), m.exact(abs)
3449 3451
3450 3452 # Find status of all file in `names`.
3451 3453 m = scmutil.matchfiles(repo, names)
3452 3454
3453 3455 changes = repo.status(node1=node, match=m,
3454 3456 unknown=True, ignored=True, clean=True)
3455 3457 else:
3456 3458 changes = repo.status(node1=node, match=m)
3457 3459 for kind in changes:
3458 3460 for abs in kind:
3459 3461 names[abs] = m.rel(abs), m.exact(abs)
3460 3462
3461 3463 m = scmutil.matchfiles(repo, names)
3462 3464
3463 3465 modified = set(changes.modified)
3464 3466 added = set(changes.added)
3465 3467 removed = set(changes.removed)
3466 3468 _deleted = set(changes.deleted)
3467 3469 unknown = set(changes.unknown)
3468 3470 unknown.update(changes.ignored)
3469 3471 clean = set(changes.clean)
3470 3472 modadded = set()
3471 3473
3472 3474 # We need to account for the state of the file in the dirstate,
3473 3475 # even when we revert against something else than parent. This will
3474 3476 # slightly alter the behavior of revert (doing back up or not, delete
3475 3477 # or just forget etc).
3476 3478 if parent == node:
3477 3479 dsmodified = modified
3478 3480 dsadded = added
3479 3481 dsremoved = removed
3480 3482 # store all local modifications, useful later for rename detection
3481 3483 localchanges = dsmodified | dsadded
3482 3484 modified, added, removed = set(), set(), set()
3483 3485 else:
3484 3486 changes = repo.status(node1=parent, match=m)
3485 3487 dsmodified = set(changes.modified)
3486 3488 dsadded = set(changes.added)
3487 3489 dsremoved = set(changes.removed)
3488 3490 # store all local modifications, useful later for rename detection
3489 3491 localchanges = dsmodified | dsadded
3490 3492
3491 3493 # only take into account for removes between wc and target
3492 3494 clean |= dsremoved - removed
3493 3495 dsremoved &= removed
3494 3496 # distinct between dirstate remove and other
3495 3497 removed -= dsremoved
3496 3498
3497 3499 modadded = added & dsmodified
3498 3500 added -= modadded
3499 3501
3500 3502 # tell newly modified apart.
3501 3503 dsmodified &= modified
3502 3504 dsmodified |= modified & dsadded # dirstate added may need backup
3503 3505 modified -= dsmodified
3504 3506
3505 3507 # We need to wait for some post-processing to update this set
3506 3508 # before making the distinction. The dirstate will be used for
3507 3509 # that purpose.
3508 3510 dsadded = added
3509 3511
3510 3512 # in case of merge, files that are actually added can be reported as
3511 3513 # modified, we need to post process the result
3512 3514 if p2 != nullid:
3513 3515 mergeadd = set(dsmodified)
3514 3516 for path in dsmodified:
3515 3517 if path in mf:
3516 3518 mergeadd.remove(path)
3517 3519 dsadded |= mergeadd
3518 3520 dsmodified -= mergeadd
3519 3521
3520 3522 # if f is a rename, update `names` to also revert the source
3521 3523 cwd = repo.getcwd()
3522 3524 for f in localchanges:
3523 3525 src = repo.dirstate.copied(f)
3524 3526 # XXX should we check for rename down to target node?
3525 3527 if src and src not in names and repo.dirstate[src] == 'r':
3526 3528 dsremoved.add(src)
3527 3529 names[src] = (repo.pathto(src, cwd), True)
3528 3530
3529 3531 # determine the exact nature of the deleted changesets
3530 3532 deladded = set(_deleted)
3531 3533 for path in _deleted:
3532 3534 if path in mf:
3533 3535 deladded.remove(path)
3534 3536 deleted = _deleted - deladded
3535 3537
3536 3538 # distinguish between file to forget and the other
3537 3539 added = set()
3538 3540 for abs in dsadded:
3539 3541 if repo.dirstate[abs] != 'a':
3540 3542 added.add(abs)
3541 3543 dsadded -= added
3542 3544
3543 3545 for abs in deladded:
3544 3546 if repo.dirstate[abs] == 'a':
3545 3547 dsadded.add(abs)
3546 3548 deladded -= dsadded
3547 3549
3548 3550 # For files marked as removed, we check if an unknown file is present at
3549 3551 # the same path. If a such file exists it may need to be backed up.
3550 3552 # Making the distinction at this stage helps have simpler backup
3551 3553 # logic.
3552 3554 removunk = set()
3553 3555 for abs in removed:
3554 3556 target = repo.wjoin(abs)
3555 3557 if os.path.lexists(target):
3556 3558 removunk.add(abs)
3557 3559 removed -= removunk
3558 3560
3559 3561 dsremovunk = set()
3560 3562 for abs in dsremoved:
3561 3563 target = repo.wjoin(abs)
3562 3564 if os.path.lexists(target):
3563 3565 dsremovunk.add(abs)
3564 3566 dsremoved -= dsremovunk
3565 3567
3566 3568 # action to be actually performed by revert
3567 3569 # (<list of file>, message>) tuple
3568 3570 actions = {'revert': ([], _('reverting %s\n')),
3569 3571 'add': ([], _('adding %s\n')),
3570 3572 'remove': ([], _('removing %s\n')),
3571 3573 'drop': ([], _('removing %s\n')),
3572 3574 'forget': ([], _('forgetting %s\n')),
3573 3575 'undelete': ([], _('undeleting %s\n')),
3574 3576 'noop': (None, _('no changes needed to %s\n')),
3575 3577 'unknown': (None, _('file not managed: %s\n')),
3576 3578 }
3577 3579
3578 3580 # "constant" that convey the backup strategy.
3579 3581 # All set to `discard` if `no-backup` is set do avoid checking
3580 3582 # no_backup lower in the code.
3581 3583 # These values are ordered for comparison purposes
3582 3584 backupinteractive = 3 # do backup if interactively modified
3583 3585 backup = 2 # unconditionally do backup
3584 3586 check = 1 # check if the existing file differs from target
3585 3587 discard = 0 # never do backup
3586 3588 if opts.get('no_backup'):
3587 3589 backupinteractive = backup = check = discard
3588 3590 if interactive:
3589 3591 dsmodifiedbackup = backupinteractive
3590 3592 else:
3591 3593 dsmodifiedbackup = backup
3592 3594 tobackup = set()
3593 3595
3594 3596 backupanddel = actions['remove']
3595 3597 if not opts.get('no_backup'):
3596 3598 backupanddel = actions['drop']
3597 3599
3598 3600 disptable = (
3599 3601 # dispatch table:
3600 3602 # file state
3601 3603 # action
3602 3604 # make backup
3603 3605
3604 3606 ## Sets that results that will change file on disk
3605 3607 # Modified compared to target, no local change
3606 3608 (modified, actions['revert'], discard),
3607 3609 # Modified compared to target, but local file is deleted
3608 3610 (deleted, actions['revert'], discard),
3609 3611 # Modified compared to target, local change
3610 3612 (dsmodified, actions['revert'], dsmodifiedbackup),
3611 3613 # Added since target
3612 3614 (added, actions['remove'], discard),
3613 3615 # Added in working directory
3614 3616 (dsadded, actions['forget'], discard),
3615 3617 # Added since target, have local modification
3616 3618 (modadded, backupanddel, backup),
3617 3619 # Added since target but file is missing in working directory
3618 3620 (deladded, actions['drop'], discard),
3619 3621 # Removed since target, before working copy parent
3620 3622 (removed, actions['add'], discard),
3621 3623 # Same as `removed` but an unknown file exists at the same path
3622 3624 (removunk, actions['add'], check),
3623 3625 # Removed since targe, marked as such in working copy parent
3624 3626 (dsremoved, actions['undelete'], discard),
3625 3627 # Same as `dsremoved` but an unknown file exists at the same path
3626 3628 (dsremovunk, actions['undelete'], check),
3627 3629 ## the following sets does not result in any file changes
3628 3630 # File with no modification
3629 3631 (clean, actions['noop'], discard),
3630 3632 # Existing file, not tracked anywhere
3631 3633 (unknown, actions['unknown'], discard),
3632 3634 )
3633 3635
3634 3636 for abs, (rel, exact) in sorted(names.items()):
3635 3637 # target file to be touch on disk (relative to cwd)
3636 3638 target = repo.wjoin(abs)
3637 3639 # search the entry in the dispatch table.
3638 3640 # if the file is in any of these sets, it was touched in the working
3639 3641 # directory parent and we are sure it needs to be reverted.
3640 3642 for table, (xlist, msg), dobackup in disptable:
3641 3643 if abs not in table:
3642 3644 continue
3643 3645 if xlist is not None:
3644 3646 xlist.append(abs)
3645 3647 if dobackup:
3646 3648 # If in interactive mode, don't automatically create
3647 3649 # .orig files (issue4793)
3648 3650 if dobackup == backupinteractive:
3649 3651 tobackup.add(abs)
3650 3652 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3651 3653 bakname = scmutil.origpath(ui, repo, rel)
3652 3654 ui.note(_('saving current version of %s as %s\n') %
3653 3655 (rel, bakname))
3654 3656 if not opts.get('dry_run'):
3655 3657 if interactive:
3656 3658 util.copyfile(target, bakname)
3657 3659 else:
3658 3660 util.rename(target, bakname)
3659 3661 if ui.verbose or not exact:
3660 3662 if not isinstance(msg, bytes):
3661 3663 msg = msg(abs)
3662 3664 ui.status(msg % rel)
3663 3665 elif exact:
3664 3666 ui.warn(msg % rel)
3665 3667 break
3666 3668
3667 3669 if not opts.get('dry_run'):
3668 3670 needdata = ('revert', 'add', 'undelete')
3669 3671 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3670 3672 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3671 3673
3672 3674 if targetsubs:
3673 3675 # Revert the subrepos on the revert list
3674 3676 for sub in targetsubs:
3675 3677 try:
3676 3678 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3677 3679 **pycompat.strkwargs(opts))
3678 3680 except KeyError:
3679 3681 raise error.Abort("subrepository '%s' does not exist in %s!"
3680 3682 % (sub, short(ctx.node())))
3681 3683
3682 3684 def _revertprefetch(repo, ctx, *files):
3683 3685 """Let extension changing the storage layer prefetch content"""
3684 3686
3685 3687 def _performrevert(repo, parents, ctx, actions, interactive=False,
3686 3688 tobackup=None):
3687 3689 """function that actually perform all the actions computed for revert
3688 3690
3689 3691 This is an independent function to let extension to plug in and react to
3690 3692 the imminent revert.
3691 3693
3692 3694 Make sure you have the working directory locked when calling this function.
3693 3695 """
3694 3696 parent, p2 = parents
3695 3697 node = ctx.node()
3696 3698 excluded_files = []
3697 3699 matcher_opts = {"exclude": excluded_files}
3698 3700
3699 3701 def checkout(f):
3700 3702 fc = ctx[f]
3701 3703 repo.wwrite(f, fc.data(), fc.flags())
3702 3704
3703 3705 def doremove(f):
3704 3706 try:
3705 3707 repo.wvfs.unlinkpath(f)
3706 3708 except OSError:
3707 3709 pass
3708 3710 repo.dirstate.remove(f)
3709 3711
3710 3712 audit_path = pathutil.pathauditor(repo.root, cached=True)
3711 3713 for f in actions['forget'][0]:
3712 3714 if interactive:
3713 3715 choice = repo.ui.promptchoice(
3714 3716 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3715 3717 if choice == 0:
3716 3718 repo.dirstate.drop(f)
3717 3719 else:
3718 3720 excluded_files.append(repo.wjoin(f))
3719 3721 else:
3720 3722 repo.dirstate.drop(f)
3721 3723 for f in actions['remove'][0]:
3722 3724 audit_path(f)
3723 3725 if interactive:
3724 3726 choice = repo.ui.promptchoice(
3725 3727 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3726 3728 if choice == 0:
3727 3729 doremove(f)
3728 3730 else:
3729 3731 excluded_files.append(repo.wjoin(f))
3730 3732 else:
3731 3733 doremove(f)
3732 3734 for f in actions['drop'][0]:
3733 3735 audit_path(f)
3734 3736 repo.dirstate.remove(f)
3735 3737
3736 3738 normal = None
3737 3739 if node == parent:
3738 3740 # We're reverting to our parent. If possible, we'd like status
3739 3741 # to report the file as clean. We have to use normallookup for
3740 3742 # merges to avoid losing information about merged/dirty files.
3741 3743 if p2 != nullid:
3742 3744 normal = repo.dirstate.normallookup
3743 3745 else:
3744 3746 normal = repo.dirstate.normal
3745 3747
3746 3748 newlyaddedandmodifiedfiles = set()
3747 3749 if interactive:
3748 3750 # Prompt the user for changes to revert
3749 3751 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3750 3752 m = scmutil.match(ctx, torevert, matcher_opts)
3751 3753 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3752 3754 diffopts.nodates = True
3753 3755 diffopts.git = True
3754 3756 operation = 'discard'
3755 3757 reversehunks = True
3756 3758 if node != parent:
3757 3759 operation = 'apply'
3758 3760 reversehunks = False
3759 3761 if reversehunks:
3760 3762 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3761 3763 else:
3762 3764 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3763 3765 originalchunks = patch.parsepatch(diff)
3764 3766
3765 3767 try:
3766 3768
3767 3769 chunks, opts = recordfilter(repo.ui, originalchunks,
3768 3770 operation=operation)
3769 3771 if reversehunks:
3770 3772 chunks = patch.reversehunks(chunks)
3771 3773
3772 3774 except error.PatchError as err:
3773 3775 raise error.Abort(_('error parsing patch: %s') % err)
3774 3776
3775 3777 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3776 3778 if tobackup is None:
3777 3779 tobackup = set()
3778 3780 # Apply changes
3779 3781 fp = stringio()
3780 3782 for c in chunks:
3781 3783 # Create a backup file only if this hunk should be backed up
3782 3784 if ishunk(c) and c.header.filename() in tobackup:
3783 3785 abs = c.header.filename()
3784 3786 target = repo.wjoin(abs)
3785 3787 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3786 3788 util.copyfile(target, bakname)
3787 3789 tobackup.remove(abs)
3788 3790 c.write(fp)
3789 3791 dopatch = fp.tell()
3790 3792 fp.seek(0)
3791 3793 if dopatch:
3792 3794 try:
3793 3795 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3794 3796 except error.PatchError as err:
3795 3797 raise error.Abort(str(err))
3796 3798 del fp
3797 3799 else:
3798 3800 for f in actions['revert'][0]:
3799 3801 checkout(f)
3800 3802 if normal:
3801 3803 normal(f)
3802 3804
3803 3805 for f in actions['add'][0]:
3804 3806 # Don't checkout modified files, they are already created by the diff
3805 3807 if f not in newlyaddedandmodifiedfiles:
3806 3808 checkout(f)
3807 3809 repo.dirstate.add(f)
3808 3810
3809 3811 normal = repo.dirstate.normallookup
3810 3812 if node == parent and p2 == nullid:
3811 3813 normal = repo.dirstate.normal
3812 3814 for f in actions['undelete'][0]:
3813 3815 checkout(f)
3814 3816 normal(f)
3815 3817
3816 3818 copied = copies.pathcopies(repo[parent], ctx)
3817 3819
3818 3820 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3819 3821 if f in copied:
3820 3822 repo.dirstate.copy(copied[f], f)
3821 3823
3822 3824 class command(registrar.command):
3823 3825 """deprecated: used registrar.command instead"""
3824 3826 def _doregister(self, func, name, *args, **kwargs):
3825 3827 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3826 3828 return super(command, self)._doregister(func, name, *args, **kwargs)
3827 3829
3828 3830 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3829 3831 # commands.outgoing. "missing" is "missing" of the result of
3830 3832 # "findcommonoutgoing()"
3831 3833 outgoinghooks = util.hooks()
3832 3834
3833 3835 # a list of (ui, repo) functions called by commands.summary
3834 3836 summaryhooks = util.hooks()
3835 3837
3836 3838 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3837 3839 #
3838 3840 # functions should return tuple of booleans below, if 'changes' is None:
3839 3841 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3840 3842 #
3841 3843 # otherwise, 'changes' is a tuple of tuples below:
3842 3844 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3843 3845 # - (desturl, destbranch, destpeer, outgoing)
3844 3846 summaryremotehooks = util.hooks()
3845 3847
3846 3848 # A list of state files kept by multistep operations like graft.
3847 3849 # Since graft cannot be aborted, it is considered 'clearable' by update.
3848 3850 # note: bisect is intentionally excluded
3849 3851 # (state file, clearable, allowcommit, error, hint)
3850 3852 unfinishedstates = [
3851 3853 ('graftstate', True, False, _('graft in progress'),
3852 3854 _("use 'hg graft --continue' or 'hg update' to abort")),
3853 3855 ('updatestate', True, False, _('last update was interrupted'),
3854 3856 _("use 'hg update' to get a consistent checkout"))
3855 3857 ]
3856 3858
3857 3859 def checkunfinished(repo, commit=False):
3858 3860 '''Look for an unfinished multistep operation, like graft, and abort
3859 3861 if found. It's probably good to check this right before
3860 3862 bailifchanged().
3861 3863 '''
3862 3864 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3863 3865 if commit and allowcommit:
3864 3866 continue
3865 3867 if repo.vfs.exists(f):
3866 3868 raise error.Abort(msg, hint=hint)
3867 3869
3868 3870 def clearunfinished(repo):
3869 3871 '''Check for unfinished operations (as above), and clear the ones
3870 3872 that are clearable.
3871 3873 '''
3872 3874 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3873 3875 if not clearable and repo.vfs.exists(f):
3874 3876 raise error.Abort(msg, hint=hint)
3875 3877 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3876 3878 if clearable and repo.vfs.exists(f):
3877 3879 util.unlink(repo.vfs.join(f))
3878 3880
3879 3881 afterresolvedstates = [
3880 3882 ('graftstate',
3881 3883 _('hg graft --continue')),
3882 3884 ]
3883 3885
3884 3886 def howtocontinue(repo):
3885 3887 '''Check for an unfinished operation and return the command to finish
3886 3888 it.
3887 3889
3888 3890 afterresolvedstates tuples define a .hg/{file} and the corresponding
3889 3891 command needed to finish it.
3890 3892
3891 3893 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3892 3894 a boolean.
3893 3895 '''
3894 3896 contmsg = _("continue: %s")
3895 3897 for f, msg in afterresolvedstates:
3896 3898 if repo.vfs.exists(f):
3897 3899 return contmsg % msg, True
3898 3900 if repo[None].dirty(missing=True, merge=False, branch=False):
3899 3901 return contmsg % _("hg commit"), False
3900 3902 return None, None
3901 3903
3902 3904 def checkafterresolved(repo):
3903 3905 '''Inform the user about the next action after completing hg resolve
3904 3906
3905 3907 If there's a matching afterresolvedstates, howtocontinue will yield
3906 3908 repo.ui.warn as the reporter.
3907 3909
3908 3910 Otherwise, it will yield repo.ui.note.
3909 3911 '''
3910 3912 msg, warning = howtocontinue(repo)
3911 3913 if msg is not None:
3912 3914 if warning:
3913 3915 repo.ui.warn("%s\n" % msg)
3914 3916 else:
3915 3917 repo.ui.note("%s\n" % msg)
3916 3918
3917 3919 def wrongtooltocontinue(repo, task):
3918 3920 '''Raise an abort suggesting how to properly continue if there is an
3919 3921 active task.
3920 3922
3921 3923 Uses howtocontinue() to find the active task.
3922 3924
3923 3925 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3924 3926 a hint.
3925 3927 '''
3926 3928 after = howtocontinue(repo)
3927 3929 hint = None
3928 3930 if after[1]:
3929 3931 hint = after[0]
3930 3932 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,3560 +1,3561 b''
1 1 @ (34) head
2 2 |
3 3 | o (33) head
4 4 | |
5 5 o | (32) expand
6 6 |\ \
7 7 | o \ (31) expand
8 8 | |\ \
9 9 | | o \ (30) expand
10 10 | | |\ \
11 11 | | | o | (29) regular commit
12 12 | | | | |
13 13 | | o | | (28) merge zero known
14 14 | | |\ \ \
15 15 o | | | | | (27) collapse
16 16 |/ / / / /
17 17 | | o---+ (26) merge one known; far right
18 18 | | | | |
19 19 +---o | | (25) merge one known; far left
20 20 | | | | |
21 21 | | o | | (24) merge one known; immediate right
22 22 | | |\| |
23 23 | | o | | (23) merge one known; immediate left
24 24 | |/| | |
25 25 +---o---+ (22) merge two known; one far left, one far right
26 26 | | / /
27 27 o | | | (21) expand
28 28 |\ \ \ \
29 29 | o---+-+ (20) merge two known; two far right
30 30 | / / /
31 31 o | | | (19) expand
32 32 |\ \ \ \
33 33 +---+---o (18) merge two known; two far left
34 34 | | | |
35 35 | o | | (17) expand
36 36 | |\ \ \
37 37 | | o---+ (16) merge two known; one immediate right, one near right
38 38 | | |/ /
39 39 o | | | (15) expand
40 40 |\ \ \ \
41 41 | o-----+ (14) merge two known; one immediate right, one far right
42 42 | |/ / /
43 43 o | | | (13) expand
44 44 |\ \ \ \
45 45 +---o | | (12) merge two known; one immediate right, one far left
46 46 | | |/ /
47 47 | o | | (11) expand
48 48 | |\ \ \
49 49 | | o---+ (10) merge two known; one immediate left, one near right
50 50 | |/ / /
51 51 o | | | (9) expand
52 52 |\ \ \ \
53 53 | o-----+ (8) merge two known; one immediate left, one far right
54 54 |/ / / /
55 55 o | | | (7) expand
56 56 |\ \ \ \
57 57 +---o | | (6) merge two known; one immediate left, one far left
58 58 | |/ / /
59 59 | o | | (5) expand
60 60 | |\ \ \
61 61 | | o | | (4) merge two known; one immediate left, one immediate right
62 62 | |/|/ /
63 63 | o / / (3) collapse
64 64 |/ / /
65 65 o / / (2) collapse
66 66 |/ /
67 67 o / (1) collapse
68 68 |/
69 69 o (0) root
70 70
71 71
72 72 $ commit()
73 73 > {
74 74 > rev=$1
75 75 > msg=$2
76 76 > shift 2
77 77 > if [ "$#" -gt 0 ]; then
78 78 > hg debugsetparents "$@"
79 79 > fi
80 80 > echo $rev > a
81 81 > hg commit -Aqd "$rev 0" -m "($rev) $msg"
82 82 > }
83 83
84 84 $ cat > printrevset.py <<EOF
85 85 > from __future__ import absolute_import
86 86 > from mercurial import (
87 87 > cmdutil,
88 88 > commands,
89 89 > extensions,
90 90 > revsetlang,
91 91 > smartset,
92 92 > )
93 93 >
94 94 > def logrevset(repo, pats, opts):
95 95 > revs = cmdutil._logrevs(repo, opts)
96 96 > if not revs:
97 97 > return None
98 98 > return cmdutil._makelogrevset(repo, pats, opts, revs)[0]
99 99 >
100 100 > def uisetup(ui):
101 101 > def printrevset(orig, repo, pats, opts):
102 102 > revs, filematcher = orig(repo, pats, opts)
103 103 > if opts.get('print_revset'):
104 104 > expr = logrevset(repo, pats, opts)
105 105 > if expr:
106 106 > tree = revsetlang.parse(expr)
107 107 > tree = revsetlang.analyze(tree)
108 108 > else:
109 109 > tree = []
110 110 > ui = repo.ui
111 111 > ui.write('%r\n' % (opts.get('rev', []),))
112 112 > ui.write(revsetlang.prettyformat(tree) + '\n')
113 113 > ui.write(smartset.prettyformat(revs) + '\n')
114 114 > revs = smartset.baseset() # display no revisions
115 115 > return revs, filematcher
116 116 > extensions.wrapfunction(cmdutil, 'getlogrevs', printrevset)
117 117 > aliases, entry = cmdutil.findcmd('log', commands.table)
118 118 > entry[1].append(('', 'print-revset', False,
119 119 > 'print generated revset and exit (DEPRECATED)'))
120 120 > EOF
121 121
122 122 $ echo "[extensions]" >> $HGRCPATH
123 123 $ echo "printrevset=`pwd`/printrevset.py" >> $HGRCPATH
124 124
125 125 $ hg init repo
126 126 $ cd repo
127 127
128 128 Empty repo:
129 129
130 130 $ hg log -G
131 131
132 132
133 133 Building DAG:
134 134
135 135 $ commit 0 "root"
136 136 $ commit 1 "collapse" 0
137 137 $ commit 2 "collapse" 1
138 138 $ commit 3 "collapse" 2
139 139 $ commit 4 "merge two known; one immediate left, one immediate right" 1 3
140 140 $ commit 5 "expand" 3 4
141 141 $ commit 6 "merge two known; one immediate left, one far left" 2 5
142 142 $ commit 7 "expand" 2 5
143 143 $ commit 8 "merge two known; one immediate left, one far right" 0 7
144 144 $ commit 9 "expand" 7 8
145 145 $ commit 10 "merge two known; one immediate left, one near right" 0 6
146 146 $ commit 11 "expand" 6 10
147 147 $ commit 12 "merge two known; one immediate right, one far left" 1 9
148 148 $ commit 13 "expand" 9 11
149 149 $ commit 14 "merge two known; one immediate right, one far right" 0 12
150 150 $ commit 15 "expand" 13 14
151 151 $ commit 16 "merge two known; one immediate right, one near right" 0 1
152 152 $ commit 17 "expand" 12 16
153 153 $ commit 18 "merge two known; two far left" 1 15
154 154 $ commit 19 "expand" 15 17
155 155 $ commit 20 "merge two known; two far right" 0 18
156 156 $ commit 21 "expand" 19 20
157 157 $ commit 22 "merge two known; one far left, one far right" 18 21
158 158 $ commit 23 "merge one known; immediate left" 1 22
159 159 $ commit 24 "merge one known; immediate right" 0 23
160 160 $ commit 25 "merge one known; far left" 21 24
161 161 $ commit 26 "merge one known; far right" 18 25
162 162 $ commit 27 "collapse" 21
163 163 $ commit 28 "merge zero known" 1 26
164 164 $ commit 29 "regular commit" 0
165 165 $ commit 30 "expand" 28 29
166 166 $ commit 31 "expand" 21 30
167 167 $ commit 32 "expand" 27 31
168 168 $ commit 33 "head" 18
169 169 $ commit 34 "head" 32
170 170
171 171
172 172 $ hg log -G -q
173 173 @ 34:fea3ac5810e0
174 174 |
175 175 | o 33:68608f5145f9
176 176 | |
177 177 o | 32:d06dffa21a31
178 178 |\ \
179 179 | o \ 31:621d83e11f67
180 180 | |\ \
181 181 | | o \ 30:6e11cd4b648f
182 182 | | |\ \
183 183 | | | o | 29:cd9bb2be7593
184 184 | | | | |
185 185 | | o | | 28:44ecd0b9ae99
186 186 | | |\ \ \
187 187 o | | | | | 27:886ed638191b
188 188 |/ / / / /
189 189 | | o---+ 26:7f25b6c2f0b9
190 190 | | | | |
191 191 +---o | | 25:91da8ed57247
192 192 | | | | |
193 193 | | o | | 24:a9c19a3d96b7
194 194 | | |\| |
195 195 | | o | | 23:a01cddf0766d
196 196 | |/| | |
197 197 +---o---+ 22:e0d9cccacb5d
198 198 | | / /
199 199 o | | | 21:d42a756af44d
200 200 |\ \ \ \
201 201 | o---+-+ 20:d30ed6450e32
202 202 | / / /
203 203 o | | | 19:31ddc2c1573b
204 204 |\ \ \ \
205 205 +---+---o 18:1aa84d96232a
206 206 | | | |
207 207 | o | | 17:44765d7c06e0
208 208 | |\ \ \
209 209 | | o---+ 16:3677d192927d
210 210 | | |/ /
211 211 o | | | 15:1dda3f72782d
212 212 |\ \ \ \
213 213 | o-----+ 14:8eac370358ef
214 214 | |/ / /
215 215 o | | | 13:22d8966a97e3
216 216 |\ \ \ \
217 217 +---o | | 12:86b91144a6e9
218 218 | | |/ /
219 219 | o | | 11:832d76e6bdf2
220 220 | |\ \ \
221 221 | | o---+ 10:74c64d036d72
222 222 | |/ / /
223 223 o | | | 9:7010c0af0a35
224 224 |\ \ \ \
225 225 | o-----+ 8:7a0b11f71937
226 226 |/ / / /
227 227 o | | | 7:b632bb1b1224
228 228 |\ \ \ \
229 229 +---o | | 6:b105a072e251
230 230 | |/ / /
231 231 | o | | 5:4409d547b708
232 232 | |\ \ \
233 233 | | o | | 4:26a8bac39d9f
234 234 | |/|/ /
235 235 | o / / 3:27eef8ed80b4
236 236 |/ / /
237 237 o / / 2:3d9a33b8d1e1
238 238 |/ /
239 239 o / 1:6db2ef61d156
240 240 |/
241 241 o 0:e6eb3150255d
242 242
243 243
244 244 $ hg log -G
245 245 @ changeset: 34:fea3ac5810e0
246 246 | tag: tip
247 247 | parent: 32:d06dffa21a31
248 248 | user: test
249 249 | date: Thu Jan 01 00:00:34 1970 +0000
250 250 | summary: (34) head
251 251 |
252 252 | o changeset: 33:68608f5145f9
253 253 | | parent: 18:1aa84d96232a
254 254 | | user: test
255 255 | | date: Thu Jan 01 00:00:33 1970 +0000
256 256 | | summary: (33) head
257 257 | |
258 258 o | changeset: 32:d06dffa21a31
259 259 |\ \ parent: 27:886ed638191b
260 260 | | | parent: 31:621d83e11f67
261 261 | | | user: test
262 262 | | | date: Thu Jan 01 00:00:32 1970 +0000
263 263 | | | summary: (32) expand
264 264 | | |
265 265 | o | changeset: 31:621d83e11f67
266 266 | |\ \ parent: 21:d42a756af44d
267 267 | | | | parent: 30:6e11cd4b648f
268 268 | | | | user: test
269 269 | | | | date: Thu Jan 01 00:00:31 1970 +0000
270 270 | | | | summary: (31) expand
271 271 | | | |
272 272 | | o | changeset: 30:6e11cd4b648f
273 273 | | |\ \ parent: 28:44ecd0b9ae99
274 274 | | | | | parent: 29:cd9bb2be7593
275 275 | | | | | user: test
276 276 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
277 277 | | | | | summary: (30) expand
278 278 | | | | |
279 279 | | | o | changeset: 29:cd9bb2be7593
280 280 | | | | | parent: 0:e6eb3150255d
281 281 | | | | | user: test
282 282 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
283 283 | | | | | summary: (29) regular commit
284 284 | | | | |
285 285 | | o | | changeset: 28:44ecd0b9ae99
286 286 | | |\ \ \ parent: 1:6db2ef61d156
287 287 | | | | | | parent: 26:7f25b6c2f0b9
288 288 | | | | | | user: test
289 289 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
290 290 | | | | | | summary: (28) merge zero known
291 291 | | | | | |
292 292 o | | | | | changeset: 27:886ed638191b
293 293 |/ / / / / parent: 21:d42a756af44d
294 294 | | | | | user: test
295 295 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
296 296 | | | | | summary: (27) collapse
297 297 | | | | |
298 298 | | o---+ changeset: 26:7f25b6c2f0b9
299 299 | | | | | parent: 18:1aa84d96232a
300 300 | | | | | parent: 25:91da8ed57247
301 301 | | | | | user: test
302 302 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
303 303 | | | | | summary: (26) merge one known; far right
304 304 | | | | |
305 305 +---o | | changeset: 25:91da8ed57247
306 306 | | | | | parent: 21:d42a756af44d
307 307 | | | | | parent: 24:a9c19a3d96b7
308 308 | | | | | user: test
309 309 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
310 310 | | | | | summary: (25) merge one known; far left
311 311 | | | | |
312 312 | | o | | changeset: 24:a9c19a3d96b7
313 313 | | |\| | parent: 0:e6eb3150255d
314 314 | | | | | parent: 23:a01cddf0766d
315 315 | | | | | user: test
316 316 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
317 317 | | | | | summary: (24) merge one known; immediate right
318 318 | | | | |
319 319 | | o | | changeset: 23:a01cddf0766d
320 320 | |/| | | parent: 1:6db2ef61d156
321 321 | | | | | parent: 22:e0d9cccacb5d
322 322 | | | | | user: test
323 323 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
324 324 | | | | | summary: (23) merge one known; immediate left
325 325 | | | | |
326 326 +---o---+ changeset: 22:e0d9cccacb5d
327 327 | | | | parent: 18:1aa84d96232a
328 328 | | / / parent: 21:d42a756af44d
329 329 | | | | user: test
330 330 | | | | date: Thu Jan 01 00:00:22 1970 +0000
331 331 | | | | summary: (22) merge two known; one far left, one far right
332 332 | | | |
333 333 o | | | changeset: 21:d42a756af44d
334 334 |\ \ \ \ parent: 19:31ddc2c1573b
335 335 | | | | | parent: 20:d30ed6450e32
336 336 | | | | | user: test
337 337 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
338 338 | | | | | summary: (21) expand
339 339 | | | | |
340 340 | o---+-+ changeset: 20:d30ed6450e32
341 341 | | | | parent: 0:e6eb3150255d
342 342 | / / / parent: 18:1aa84d96232a
343 343 | | | | user: test
344 344 | | | | date: Thu Jan 01 00:00:20 1970 +0000
345 345 | | | | summary: (20) merge two known; two far right
346 346 | | | |
347 347 o | | | changeset: 19:31ddc2c1573b
348 348 |\ \ \ \ parent: 15:1dda3f72782d
349 349 | | | | | parent: 17:44765d7c06e0
350 350 | | | | | user: test
351 351 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
352 352 | | | | | summary: (19) expand
353 353 | | | | |
354 354 +---+---o changeset: 18:1aa84d96232a
355 355 | | | | parent: 1:6db2ef61d156
356 356 | | | | parent: 15:1dda3f72782d
357 357 | | | | user: test
358 358 | | | | date: Thu Jan 01 00:00:18 1970 +0000
359 359 | | | | summary: (18) merge two known; two far left
360 360 | | | |
361 361 | o | | changeset: 17:44765d7c06e0
362 362 | |\ \ \ parent: 12:86b91144a6e9
363 363 | | | | | parent: 16:3677d192927d
364 364 | | | | | user: test
365 365 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
366 366 | | | | | summary: (17) expand
367 367 | | | | |
368 368 | | o---+ changeset: 16:3677d192927d
369 369 | | | | | parent: 0:e6eb3150255d
370 370 | | |/ / parent: 1:6db2ef61d156
371 371 | | | | user: test
372 372 | | | | date: Thu Jan 01 00:00:16 1970 +0000
373 373 | | | | summary: (16) merge two known; one immediate right, one near right
374 374 | | | |
375 375 o | | | changeset: 15:1dda3f72782d
376 376 |\ \ \ \ parent: 13:22d8966a97e3
377 377 | | | | | parent: 14:8eac370358ef
378 378 | | | | | user: test
379 379 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
380 380 | | | | | summary: (15) expand
381 381 | | | | |
382 382 | o-----+ changeset: 14:8eac370358ef
383 383 | | | | | parent: 0:e6eb3150255d
384 384 | |/ / / parent: 12:86b91144a6e9
385 385 | | | | user: test
386 386 | | | | date: Thu Jan 01 00:00:14 1970 +0000
387 387 | | | | summary: (14) merge two known; one immediate right, one far right
388 388 | | | |
389 389 o | | | changeset: 13:22d8966a97e3
390 390 |\ \ \ \ parent: 9:7010c0af0a35
391 391 | | | | | parent: 11:832d76e6bdf2
392 392 | | | | | user: test
393 393 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
394 394 | | | | | summary: (13) expand
395 395 | | | | |
396 396 +---o | | changeset: 12:86b91144a6e9
397 397 | | |/ / parent: 1:6db2ef61d156
398 398 | | | | parent: 9:7010c0af0a35
399 399 | | | | user: test
400 400 | | | | date: Thu Jan 01 00:00:12 1970 +0000
401 401 | | | | summary: (12) merge two known; one immediate right, one far left
402 402 | | | |
403 403 | o | | changeset: 11:832d76e6bdf2
404 404 | |\ \ \ parent: 6:b105a072e251
405 405 | | | | | parent: 10:74c64d036d72
406 406 | | | | | user: test
407 407 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
408 408 | | | | | summary: (11) expand
409 409 | | | | |
410 410 | | o---+ changeset: 10:74c64d036d72
411 411 | | | | | parent: 0:e6eb3150255d
412 412 | |/ / / parent: 6:b105a072e251
413 413 | | | | user: test
414 414 | | | | date: Thu Jan 01 00:00:10 1970 +0000
415 415 | | | | summary: (10) merge two known; one immediate left, one near right
416 416 | | | |
417 417 o | | | changeset: 9:7010c0af0a35
418 418 |\ \ \ \ parent: 7:b632bb1b1224
419 419 | | | | | parent: 8:7a0b11f71937
420 420 | | | | | user: test
421 421 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
422 422 | | | | | summary: (9) expand
423 423 | | | | |
424 424 | o-----+ changeset: 8:7a0b11f71937
425 425 | | | | | parent: 0:e6eb3150255d
426 426 |/ / / / parent: 7:b632bb1b1224
427 427 | | | | user: test
428 428 | | | | date: Thu Jan 01 00:00:08 1970 +0000
429 429 | | | | summary: (8) merge two known; one immediate left, one far right
430 430 | | | |
431 431 o | | | changeset: 7:b632bb1b1224
432 432 |\ \ \ \ parent: 2:3d9a33b8d1e1
433 433 | | | | | parent: 5:4409d547b708
434 434 | | | | | user: test
435 435 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
436 436 | | | | | summary: (7) expand
437 437 | | | | |
438 438 +---o | | changeset: 6:b105a072e251
439 439 | |/ / / parent: 2:3d9a33b8d1e1
440 440 | | | | parent: 5:4409d547b708
441 441 | | | | user: test
442 442 | | | | date: Thu Jan 01 00:00:06 1970 +0000
443 443 | | | | summary: (6) merge two known; one immediate left, one far left
444 444 | | | |
445 445 | o | | changeset: 5:4409d547b708
446 446 | |\ \ \ parent: 3:27eef8ed80b4
447 447 | | | | | parent: 4:26a8bac39d9f
448 448 | | | | | user: test
449 449 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
450 450 | | | | | summary: (5) expand
451 451 | | | | |
452 452 | | o | | changeset: 4:26a8bac39d9f
453 453 | |/|/ / parent: 1:6db2ef61d156
454 454 | | | | parent: 3:27eef8ed80b4
455 455 | | | | user: test
456 456 | | | | date: Thu Jan 01 00:00:04 1970 +0000
457 457 | | | | summary: (4) merge two known; one immediate left, one immediate right
458 458 | | | |
459 459 | o | | changeset: 3:27eef8ed80b4
460 460 |/ / / user: test
461 461 | | | date: Thu Jan 01 00:00:03 1970 +0000
462 462 | | | summary: (3) collapse
463 463 | | |
464 464 o | | changeset: 2:3d9a33b8d1e1
465 465 |/ / user: test
466 466 | | date: Thu Jan 01 00:00:02 1970 +0000
467 467 | | summary: (2) collapse
468 468 | |
469 469 o | changeset: 1:6db2ef61d156
470 470 |/ user: test
471 471 | date: Thu Jan 01 00:00:01 1970 +0000
472 472 | summary: (1) collapse
473 473 |
474 474 o changeset: 0:e6eb3150255d
475 475 user: test
476 476 date: Thu Jan 01 00:00:00 1970 +0000
477 477 summary: (0) root
478 478
479 479
480 480 File glog:
481 481 $ hg log -G a
482 482 @ changeset: 34:fea3ac5810e0
483 483 | tag: tip
484 484 | parent: 32:d06dffa21a31
485 485 | user: test
486 486 | date: Thu Jan 01 00:00:34 1970 +0000
487 487 | summary: (34) head
488 488 |
489 489 | o changeset: 33:68608f5145f9
490 490 | | parent: 18:1aa84d96232a
491 491 | | user: test
492 492 | | date: Thu Jan 01 00:00:33 1970 +0000
493 493 | | summary: (33) head
494 494 | |
495 495 o | changeset: 32:d06dffa21a31
496 496 |\ \ parent: 27:886ed638191b
497 497 | | | parent: 31:621d83e11f67
498 498 | | | user: test
499 499 | | | date: Thu Jan 01 00:00:32 1970 +0000
500 500 | | | summary: (32) expand
501 501 | | |
502 502 | o | changeset: 31:621d83e11f67
503 503 | |\ \ parent: 21:d42a756af44d
504 504 | | | | parent: 30:6e11cd4b648f
505 505 | | | | user: test
506 506 | | | | date: Thu Jan 01 00:00:31 1970 +0000
507 507 | | | | summary: (31) expand
508 508 | | | |
509 509 | | o | changeset: 30:6e11cd4b648f
510 510 | | |\ \ parent: 28:44ecd0b9ae99
511 511 | | | | | parent: 29:cd9bb2be7593
512 512 | | | | | user: test
513 513 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
514 514 | | | | | summary: (30) expand
515 515 | | | | |
516 516 | | | o | changeset: 29:cd9bb2be7593
517 517 | | | | | parent: 0:e6eb3150255d
518 518 | | | | | user: test
519 519 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
520 520 | | | | | summary: (29) regular commit
521 521 | | | | |
522 522 | | o | | changeset: 28:44ecd0b9ae99
523 523 | | |\ \ \ parent: 1:6db2ef61d156
524 524 | | | | | | parent: 26:7f25b6c2f0b9
525 525 | | | | | | user: test
526 526 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
527 527 | | | | | | summary: (28) merge zero known
528 528 | | | | | |
529 529 o | | | | | changeset: 27:886ed638191b
530 530 |/ / / / / parent: 21:d42a756af44d
531 531 | | | | | user: test
532 532 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
533 533 | | | | | summary: (27) collapse
534 534 | | | | |
535 535 | | o---+ changeset: 26:7f25b6c2f0b9
536 536 | | | | | parent: 18:1aa84d96232a
537 537 | | | | | parent: 25:91da8ed57247
538 538 | | | | | user: test
539 539 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
540 540 | | | | | summary: (26) merge one known; far right
541 541 | | | | |
542 542 +---o | | changeset: 25:91da8ed57247
543 543 | | | | | parent: 21:d42a756af44d
544 544 | | | | | parent: 24:a9c19a3d96b7
545 545 | | | | | user: test
546 546 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
547 547 | | | | | summary: (25) merge one known; far left
548 548 | | | | |
549 549 | | o | | changeset: 24:a9c19a3d96b7
550 550 | | |\| | parent: 0:e6eb3150255d
551 551 | | | | | parent: 23:a01cddf0766d
552 552 | | | | | user: test
553 553 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
554 554 | | | | | summary: (24) merge one known; immediate right
555 555 | | | | |
556 556 | | o | | changeset: 23:a01cddf0766d
557 557 | |/| | | parent: 1:6db2ef61d156
558 558 | | | | | parent: 22:e0d9cccacb5d
559 559 | | | | | user: test
560 560 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
561 561 | | | | | summary: (23) merge one known; immediate left
562 562 | | | | |
563 563 +---o---+ changeset: 22:e0d9cccacb5d
564 564 | | | | parent: 18:1aa84d96232a
565 565 | | / / parent: 21:d42a756af44d
566 566 | | | | user: test
567 567 | | | | date: Thu Jan 01 00:00:22 1970 +0000
568 568 | | | | summary: (22) merge two known; one far left, one far right
569 569 | | | |
570 570 o | | | changeset: 21:d42a756af44d
571 571 |\ \ \ \ parent: 19:31ddc2c1573b
572 572 | | | | | parent: 20:d30ed6450e32
573 573 | | | | | user: test
574 574 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
575 575 | | | | | summary: (21) expand
576 576 | | | | |
577 577 | o---+-+ changeset: 20:d30ed6450e32
578 578 | | | | parent: 0:e6eb3150255d
579 579 | / / / parent: 18:1aa84d96232a
580 580 | | | | user: test
581 581 | | | | date: Thu Jan 01 00:00:20 1970 +0000
582 582 | | | | summary: (20) merge two known; two far right
583 583 | | | |
584 584 o | | | changeset: 19:31ddc2c1573b
585 585 |\ \ \ \ parent: 15:1dda3f72782d
586 586 | | | | | parent: 17:44765d7c06e0
587 587 | | | | | user: test
588 588 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
589 589 | | | | | summary: (19) expand
590 590 | | | | |
591 591 +---+---o changeset: 18:1aa84d96232a
592 592 | | | | parent: 1:6db2ef61d156
593 593 | | | | parent: 15:1dda3f72782d
594 594 | | | | user: test
595 595 | | | | date: Thu Jan 01 00:00:18 1970 +0000
596 596 | | | | summary: (18) merge two known; two far left
597 597 | | | |
598 598 | o | | changeset: 17:44765d7c06e0
599 599 | |\ \ \ parent: 12:86b91144a6e9
600 600 | | | | | parent: 16:3677d192927d
601 601 | | | | | user: test
602 602 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
603 603 | | | | | summary: (17) expand
604 604 | | | | |
605 605 | | o---+ changeset: 16:3677d192927d
606 606 | | | | | parent: 0:e6eb3150255d
607 607 | | |/ / parent: 1:6db2ef61d156
608 608 | | | | user: test
609 609 | | | | date: Thu Jan 01 00:00:16 1970 +0000
610 610 | | | | summary: (16) merge two known; one immediate right, one near right
611 611 | | | |
612 612 o | | | changeset: 15:1dda3f72782d
613 613 |\ \ \ \ parent: 13:22d8966a97e3
614 614 | | | | | parent: 14:8eac370358ef
615 615 | | | | | user: test
616 616 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
617 617 | | | | | summary: (15) expand
618 618 | | | | |
619 619 | o-----+ changeset: 14:8eac370358ef
620 620 | | | | | parent: 0:e6eb3150255d
621 621 | |/ / / parent: 12:86b91144a6e9
622 622 | | | | user: test
623 623 | | | | date: Thu Jan 01 00:00:14 1970 +0000
624 624 | | | | summary: (14) merge two known; one immediate right, one far right
625 625 | | | |
626 626 o | | | changeset: 13:22d8966a97e3
627 627 |\ \ \ \ parent: 9:7010c0af0a35
628 628 | | | | | parent: 11:832d76e6bdf2
629 629 | | | | | user: test
630 630 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
631 631 | | | | | summary: (13) expand
632 632 | | | | |
633 633 +---o | | changeset: 12:86b91144a6e9
634 634 | | |/ / parent: 1:6db2ef61d156
635 635 | | | | parent: 9:7010c0af0a35
636 636 | | | | user: test
637 637 | | | | date: Thu Jan 01 00:00:12 1970 +0000
638 638 | | | | summary: (12) merge two known; one immediate right, one far left
639 639 | | | |
640 640 | o | | changeset: 11:832d76e6bdf2
641 641 | |\ \ \ parent: 6:b105a072e251
642 642 | | | | | parent: 10:74c64d036d72
643 643 | | | | | user: test
644 644 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
645 645 | | | | | summary: (11) expand
646 646 | | | | |
647 647 | | o---+ changeset: 10:74c64d036d72
648 648 | | | | | parent: 0:e6eb3150255d
649 649 | |/ / / parent: 6:b105a072e251
650 650 | | | | user: test
651 651 | | | | date: Thu Jan 01 00:00:10 1970 +0000
652 652 | | | | summary: (10) merge two known; one immediate left, one near right
653 653 | | | |
654 654 o | | | changeset: 9:7010c0af0a35
655 655 |\ \ \ \ parent: 7:b632bb1b1224
656 656 | | | | | parent: 8:7a0b11f71937
657 657 | | | | | user: test
658 658 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
659 659 | | | | | summary: (9) expand
660 660 | | | | |
661 661 | o-----+ changeset: 8:7a0b11f71937
662 662 | | | | | parent: 0:e6eb3150255d
663 663 |/ / / / parent: 7:b632bb1b1224
664 664 | | | | user: test
665 665 | | | | date: Thu Jan 01 00:00:08 1970 +0000
666 666 | | | | summary: (8) merge two known; one immediate left, one far right
667 667 | | | |
668 668 o | | | changeset: 7:b632bb1b1224
669 669 |\ \ \ \ parent: 2:3d9a33b8d1e1
670 670 | | | | | parent: 5:4409d547b708
671 671 | | | | | user: test
672 672 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
673 673 | | | | | summary: (7) expand
674 674 | | | | |
675 675 +---o | | changeset: 6:b105a072e251
676 676 | |/ / / parent: 2:3d9a33b8d1e1
677 677 | | | | parent: 5:4409d547b708
678 678 | | | | user: test
679 679 | | | | date: Thu Jan 01 00:00:06 1970 +0000
680 680 | | | | summary: (6) merge two known; one immediate left, one far left
681 681 | | | |
682 682 | o | | changeset: 5:4409d547b708
683 683 | |\ \ \ parent: 3:27eef8ed80b4
684 684 | | | | | parent: 4:26a8bac39d9f
685 685 | | | | | user: test
686 686 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
687 687 | | | | | summary: (5) expand
688 688 | | | | |
689 689 | | o | | changeset: 4:26a8bac39d9f
690 690 | |/|/ / parent: 1:6db2ef61d156
691 691 | | | | parent: 3:27eef8ed80b4
692 692 | | | | user: test
693 693 | | | | date: Thu Jan 01 00:00:04 1970 +0000
694 694 | | | | summary: (4) merge two known; one immediate left, one immediate right
695 695 | | | |
696 696 | o | | changeset: 3:27eef8ed80b4
697 697 |/ / / user: test
698 698 | | | date: Thu Jan 01 00:00:03 1970 +0000
699 699 | | | summary: (3) collapse
700 700 | | |
701 701 o | | changeset: 2:3d9a33b8d1e1
702 702 |/ / user: test
703 703 | | date: Thu Jan 01 00:00:02 1970 +0000
704 704 | | summary: (2) collapse
705 705 | |
706 706 o | changeset: 1:6db2ef61d156
707 707 |/ user: test
708 708 | date: Thu Jan 01 00:00:01 1970 +0000
709 709 | summary: (1) collapse
710 710 |
711 711 o changeset: 0:e6eb3150255d
712 712 user: test
713 713 date: Thu Jan 01 00:00:00 1970 +0000
714 714 summary: (0) root
715 715
716 716
717 717 File glog per revset:
718 718
719 719 $ hg log -G -r 'file("a")'
720 720 @ changeset: 34:fea3ac5810e0
721 721 | tag: tip
722 722 | parent: 32:d06dffa21a31
723 723 | user: test
724 724 | date: Thu Jan 01 00:00:34 1970 +0000
725 725 | summary: (34) head
726 726 |
727 727 | o changeset: 33:68608f5145f9
728 728 | | parent: 18:1aa84d96232a
729 729 | | user: test
730 730 | | date: Thu Jan 01 00:00:33 1970 +0000
731 731 | | summary: (33) head
732 732 | |
733 733 o | changeset: 32:d06dffa21a31
734 734 |\ \ parent: 27:886ed638191b
735 735 | | | parent: 31:621d83e11f67
736 736 | | | user: test
737 737 | | | date: Thu Jan 01 00:00:32 1970 +0000
738 738 | | | summary: (32) expand
739 739 | | |
740 740 | o | changeset: 31:621d83e11f67
741 741 | |\ \ parent: 21:d42a756af44d
742 742 | | | | parent: 30:6e11cd4b648f
743 743 | | | | user: test
744 744 | | | | date: Thu Jan 01 00:00:31 1970 +0000
745 745 | | | | summary: (31) expand
746 746 | | | |
747 747 | | o | changeset: 30:6e11cd4b648f
748 748 | | |\ \ parent: 28:44ecd0b9ae99
749 749 | | | | | parent: 29:cd9bb2be7593
750 750 | | | | | user: test
751 751 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
752 752 | | | | | summary: (30) expand
753 753 | | | | |
754 754 | | | o | changeset: 29:cd9bb2be7593
755 755 | | | | | parent: 0:e6eb3150255d
756 756 | | | | | user: test
757 757 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
758 758 | | | | | summary: (29) regular commit
759 759 | | | | |
760 760 | | o | | changeset: 28:44ecd0b9ae99
761 761 | | |\ \ \ parent: 1:6db2ef61d156
762 762 | | | | | | parent: 26:7f25b6c2f0b9
763 763 | | | | | | user: test
764 764 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
765 765 | | | | | | summary: (28) merge zero known
766 766 | | | | | |
767 767 o | | | | | changeset: 27:886ed638191b
768 768 |/ / / / / parent: 21:d42a756af44d
769 769 | | | | | user: test
770 770 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
771 771 | | | | | summary: (27) collapse
772 772 | | | | |
773 773 | | o---+ changeset: 26:7f25b6c2f0b9
774 774 | | | | | parent: 18:1aa84d96232a
775 775 | | | | | parent: 25:91da8ed57247
776 776 | | | | | user: test
777 777 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
778 778 | | | | | summary: (26) merge one known; far right
779 779 | | | | |
780 780 +---o | | changeset: 25:91da8ed57247
781 781 | | | | | parent: 21:d42a756af44d
782 782 | | | | | parent: 24:a9c19a3d96b7
783 783 | | | | | user: test
784 784 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
785 785 | | | | | summary: (25) merge one known; far left
786 786 | | | | |
787 787 | | o | | changeset: 24:a9c19a3d96b7
788 788 | | |\| | parent: 0:e6eb3150255d
789 789 | | | | | parent: 23:a01cddf0766d
790 790 | | | | | user: test
791 791 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
792 792 | | | | | summary: (24) merge one known; immediate right
793 793 | | | | |
794 794 | | o | | changeset: 23:a01cddf0766d
795 795 | |/| | | parent: 1:6db2ef61d156
796 796 | | | | | parent: 22:e0d9cccacb5d
797 797 | | | | | user: test
798 798 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
799 799 | | | | | summary: (23) merge one known; immediate left
800 800 | | | | |
801 801 +---o---+ changeset: 22:e0d9cccacb5d
802 802 | | | | parent: 18:1aa84d96232a
803 803 | | / / parent: 21:d42a756af44d
804 804 | | | | user: test
805 805 | | | | date: Thu Jan 01 00:00:22 1970 +0000
806 806 | | | | summary: (22) merge two known; one far left, one far right
807 807 | | | |
808 808 o | | | changeset: 21:d42a756af44d
809 809 |\ \ \ \ parent: 19:31ddc2c1573b
810 810 | | | | | parent: 20:d30ed6450e32
811 811 | | | | | user: test
812 812 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
813 813 | | | | | summary: (21) expand
814 814 | | | | |
815 815 | o---+-+ changeset: 20:d30ed6450e32
816 816 | | | | parent: 0:e6eb3150255d
817 817 | / / / parent: 18:1aa84d96232a
818 818 | | | | user: test
819 819 | | | | date: Thu Jan 01 00:00:20 1970 +0000
820 820 | | | | summary: (20) merge two known; two far right
821 821 | | | |
822 822 o | | | changeset: 19:31ddc2c1573b
823 823 |\ \ \ \ parent: 15:1dda3f72782d
824 824 | | | | | parent: 17:44765d7c06e0
825 825 | | | | | user: test
826 826 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
827 827 | | | | | summary: (19) expand
828 828 | | | | |
829 829 +---+---o changeset: 18:1aa84d96232a
830 830 | | | | parent: 1:6db2ef61d156
831 831 | | | | parent: 15:1dda3f72782d
832 832 | | | | user: test
833 833 | | | | date: Thu Jan 01 00:00:18 1970 +0000
834 834 | | | | summary: (18) merge two known; two far left
835 835 | | | |
836 836 | o | | changeset: 17:44765d7c06e0
837 837 | |\ \ \ parent: 12:86b91144a6e9
838 838 | | | | | parent: 16:3677d192927d
839 839 | | | | | user: test
840 840 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
841 841 | | | | | summary: (17) expand
842 842 | | | | |
843 843 | | o---+ changeset: 16:3677d192927d
844 844 | | | | | parent: 0:e6eb3150255d
845 845 | | |/ / parent: 1:6db2ef61d156
846 846 | | | | user: test
847 847 | | | | date: Thu Jan 01 00:00:16 1970 +0000
848 848 | | | | summary: (16) merge two known; one immediate right, one near right
849 849 | | | |
850 850 o | | | changeset: 15:1dda3f72782d
851 851 |\ \ \ \ parent: 13:22d8966a97e3
852 852 | | | | | parent: 14:8eac370358ef
853 853 | | | | | user: test
854 854 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
855 855 | | | | | summary: (15) expand
856 856 | | | | |
857 857 | o-----+ changeset: 14:8eac370358ef
858 858 | | | | | parent: 0:e6eb3150255d
859 859 | |/ / / parent: 12:86b91144a6e9
860 860 | | | | user: test
861 861 | | | | date: Thu Jan 01 00:00:14 1970 +0000
862 862 | | | | summary: (14) merge two known; one immediate right, one far right
863 863 | | | |
864 864 o | | | changeset: 13:22d8966a97e3
865 865 |\ \ \ \ parent: 9:7010c0af0a35
866 866 | | | | | parent: 11:832d76e6bdf2
867 867 | | | | | user: test
868 868 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
869 869 | | | | | summary: (13) expand
870 870 | | | | |
871 871 +---o | | changeset: 12:86b91144a6e9
872 872 | | |/ / parent: 1:6db2ef61d156
873 873 | | | | parent: 9:7010c0af0a35
874 874 | | | | user: test
875 875 | | | | date: Thu Jan 01 00:00:12 1970 +0000
876 876 | | | | summary: (12) merge two known; one immediate right, one far left
877 877 | | | |
878 878 | o | | changeset: 11:832d76e6bdf2
879 879 | |\ \ \ parent: 6:b105a072e251
880 880 | | | | | parent: 10:74c64d036d72
881 881 | | | | | user: test
882 882 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
883 883 | | | | | summary: (11) expand
884 884 | | | | |
885 885 | | o---+ changeset: 10:74c64d036d72
886 886 | | | | | parent: 0:e6eb3150255d
887 887 | |/ / / parent: 6:b105a072e251
888 888 | | | | user: test
889 889 | | | | date: Thu Jan 01 00:00:10 1970 +0000
890 890 | | | | summary: (10) merge two known; one immediate left, one near right
891 891 | | | |
892 892 o | | | changeset: 9:7010c0af0a35
893 893 |\ \ \ \ parent: 7:b632bb1b1224
894 894 | | | | | parent: 8:7a0b11f71937
895 895 | | | | | user: test
896 896 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
897 897 | | | | | summary: (9) expand
898 898 | | | | |
899 899 | o-----+ changeset: 8:7a0b11f71937
900 900 | | | | | parent: 0:e6eb3150255d
901 901 |/ / / / parent: 7:b632bb1b1224
902 902 | | | | user: test
903 903 | | | | date: Thu Jan 01 00:00:08 1970 +0000
904 904 | | | | summary: (8) merge two known; one immediate left, one far right
905 905 | | | |
906 906 o | | | changeset: 7:b632bb1b1224
907 907 |\ \ \ \ parent: 2:3d9a33b8d1e1
908 908 | | | | | parent: 5:4409d547b708
909 909 | | | | | user: test
910 910 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
911 911 | | | | | summary: (7) expand
912 912 | | | | |
913 913 +---o | | changeset: 6:b105a072e251
914 914 | |/ / / parent: 2:3d9a33b8d1e1
915 915 | | | | parent: 5:4409d547b708
916 916 | | | | user: test
917 917 | | | | date: Thu Jan 01 00:00:06 1970 +0000
918 918 | | | | summary: (6) merge two known; one immediate left, one far left
919 919 | | | |
920 920 | o | | changeset: 5:4409d547b708
921 921 | |\ \ \ parent: 3:27eef8ed80b4
922 922 | | | | | parent: 4:26a8bac39d9f
923 923 | | | | | user: test
924 924 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
925 925 | | | | | summary: (5) expand
926 926 | | | | |
927 927 | | o | | changeset: 4:26a8bac39d9f
928 928 | |/|/ / parent: 1:6db2ef61d156
929 929 | | | | parent: 3:27eef8ed80b4
930 930 | | | | user: test
931 931 | | | | date: Thu Jan 01 00:00:04 1970 +0000
932 932 | | | | summary: (4) merge two known; one immediate left, one immediate right
933 933 | | | |
934 934 | o | | changeset: 3:27eef8ed80b4
935 935 |/ / / user: test
936 936 | | | date: Thu Jan 01 00:00:03 1970 +0000
937 937 | | | summary: (3) collapse
938 938 | | |
939 939 o | | changeset: 2:3d9a33b8d1e1
940 940 |/ / user: test
941 941 | | date: Thu Jan 01 00:00:02 1970 +0000
942 942 | | summary: (2) collapse
943 943 | |
944 944 o | changeset: 1:6db2ef61d156
945 945 |/ user: test
946 946 | date: Thu Jan 01 00:00:01 1970 +0000
947 947 | summary: (1) collapse
948 948 |
949 949 o changeset: 0:e6eb3150255d
950 950 user: test
951 951 date: Thu Jan 01 00:00:00 1970 +0000
952 952 summary: (0) root
953 953
954 954
955 955
956 956 File glog per revset (only merges):
957 957
958 958 $ hg log -G -r 'file("a")' -m
959 959 o changeset: 32:d06dffa21a31
960 960 |\ parent: 27:886ed638191b
961 961 | : parent: 31:621d83e11f67
962 962 | : user: test
963 963 | : date: Thu Jan 01 00:00:32 1970 +0000
964 964 | : summary: (32) expand
965 965 | :
966 966 o : changeset: 31:621d83e11f67
967 967 |\: parent: 21:d42a756af44d
968 968 | : parent: 30:6e11cd4b648f
969 969 | : user: test
970 970 | : date: Thu Jan 01 00:00:31 1970 +0000
971 971 | : summary: (31) expand
972 972 | :
973 973 o : changeset: 30:6e11cd4b648f
974 974 |\ \ parent: 28:44ecd0b9ae99
975 975 | ~ : parent: 29:cd9bb2be7593
976 976 | : user: test
977 977 | : date: Thu Jan 01 00:00:30 1970 +0000
978 978 | : summary: (30) expand
979 979 | /
980 980 o : changeset: 28:44ecd0b9ae99
981 981 |\ \ parent: 1:6db2ef61d156
982 982 | ~ : parent: 26:7f25b6c2f0b9
983 983 | : user: test
984 984 | : date: Thu Jan 01 00:00:28 1970 +0000
985 985 | : summary: (28) merge zero known
986 986 | /
987 987 o : changeset: 26:7f25b6c2f0b9
988 988 |\ \ parent: 18:1aa84d96232a
989 989 | | : parent: 25:91da8ed57247
990 990 | | : user: test
991 991 | | : date: Thu Jan 01 00:00:26 1970 +0000
992 992 | | : summary: (26) merge one known; far right
993 993 | | :
994 994 | o : changeset: 25:91da8ed57247
995 995 | |\: parent: 21:d42a756af44d
996 996 | | : parent: 24:a9c19a3d96b7
997 997 | | : user: test
998 998 | | : date: Thu Jan 01 00:00:25 1970 +0000
999 999 | | : summary: (25) merge one known; far left
1000 1000 | | :
1001 1001 | o : changeset: 24:a9c19a3d96b7
1002 1002 | |\ \ parent: 0:e6eb3150255d
1003 1003 | | ~ : parent: 23:a01cddf0766d
1004 1004 | | : user: test
1005 1005 | | : date: Thu Jan 01 00:00:24 1970 +0000
1006 1006 | | : summary: (24) merge one known; immediate right
1007 1007 | | /
1008 1008 | o : changeset: 23:a01cddf0766d
1009 1009 | |\ \ parent: 1:6db2ef61d156
1010 1010 | | ~ : parent: 22:e0d9cccacb5d
1011 1011 | | : user: test
1012 1012 | | : date: Thu Jan 01 00:00:23 1970 +0000
1013 1013 | | : summary: (23) merge one known; immediate left
1014 1014 | | /
1015 1015 | o : changeset: 22:e0d9cccacb5d
1016 1016 |/:/ parent: 18:1aa84d96232a
1017 1017 | : parent: 21:d42a756af44d
1018 1018 | : user: test
1019 1019 | : date: Thu Jan 01 00:00:22 1970 +0000
1020 1020 | : summary: (22) merge two known; one far left, one far right
1021 1021 | :
1022 1022 | o changeset: 21:d42a756af44d
1023 1023 | |\ parent: 19:31ddc2c1573b
1024 1024 | | | parent: 20:d30ed6450e32
1025 1025 | | | user: test
1026 1026 | | | date: Thu Jan 01 00:00:21 1970 +0000
1027 1027 | | | summary: (21) expand
1028 1028 | | |
1029 1029 +---o changeset: 20:d30ed6450e32
1030 1030 | | | parent: 0:e6eb3150255d
1031 1031 | | ~ parent: 18:1aa84d96232a
1032 1032 | | user: test
1033 1033 | | date: Thu Jan 01 00:00:20 1970 +0000
1034 1034 | | summary: (20) merge two known; two far right
1035 1035 | |
1036 1036 | o changeset: 19:31ddc2c1573b
1037 1037 | |\ parent: 15:1dda3f72782d
1038 1038 | | | parent: 17:44765d7c06e0
1039 1039 | | | user: test
1040 1040 | | | date: Thu Jan 01 00:00:19 1970 +0000
1041 1041 | | | summary: (19) expand
1042 1042 | | |
1043 1043 o | | changeset: 18:1aa84d96232a
1044 1044 |\| | parent: 1:6db2ef61d156
1045 1045 ~ | | parent: 15:1dda3f72782d
1046 1046 | | user: test
1047 1047 | | date: Thu Jan 01 00:00:18 1970 +0000
1048 1048 | | summary: (18) merge two known; two far left
1049 1049 / /
1050 1050 | o changeset: 17:44765d7c06e0
1051 1051 | |\ parent: 12:86b91144a6e9
1052 1052 | | | parent: 16:3677d192927d
1053 1053 | | | user: test
1054 1054 | | | date: Thu Jan 01 00:00:17 1970 +0000
1055 1055 | | | summary: (17) expand
1056 1056 | | |
1057 1057 | | o changeset: 16:3677d192927d
1058 1058 | | |\ parent: 0:e6eb3150255d
1059 1059 | | ~ ~ parent: 1:6db2ef61d156
1060 1060 | | user: test
1061 1061 | | date: Thu Jan 01 00:00:16 1970 +0000
1062 1062 | | summary: (16) merge two known; one immediate right, one near right
1063 1063 | |
1064 1064 o | changeset: 15:1dda3f72782d
1065 1065 |\ \ parent: 13:22d8966a97e3
1066 1066 | | | parent: 14:8eac370358ef
1067 1067 | | | user: test
1068 1068 | | | date: Thu Jan 01 00:00:15 1970 +0000
1069 1069 | | | summary: (15) expand
1070 1070 | | |
1071 1071 | o | changeset: 14:8eac370358ef
1072 1072 | |\| parent: 0:e6eb3150255d
1073 1073 | ~ | parent: 12:86b91144a6e9
1074 1074 | | user: test
1075 1075 | | date: Thu Jan 01 00:00:14 1970 +0000
1076 1076 | | summary: (14) merge two known; one immediate right, one far right
1077 1077 | /
1078 1078 o | changeset: 13:22d8966a97e3
1079 1079 |\ \ parent: 9:7010c0af0a35
1080 1080 | | | parent: 11:832d76e6bdf2
1081 1081 | | | user: test
1082 1082 | | | date: Thu Jan 01 00:00:13 1970 +0000
1083 1083 | | | summary: (13) expand
1084 1084 | | |
1085 1085 +---o changeset: 12:86b91144a6e9
1086 1086 | | | parent: 1:6db2ef61d156
1087 1087 | | ~ parent: 9:7010c0af0a35
1088 1088 | | user: test
1089 1089 | | date: Thu Jan 01 00:00:12 1970 +0000
1090 1090 | | summary: (12) merge two known; one immediate right, one far left
1091 1091 | |
1092 1092 | o changeset: 11:832d76e6bdf2
1093 1093 | |\ parent: 6:b105a072e251
1094 1094 | | | parent: 10:74c64d036d72
1095 1095 | | | user: test
1096 1096 | | | date: Thu Jan 01 00:00:11 1970 +0000
1097 1097 | | | summary: (11) expand
1098 1098 | | |
1099 1099 | | o changeset: 10:74c64d036d72
1100 1100 | |/| parent: 0:e6eb3150255d
1101 1101 | | ~ parent: 6:b105a072e251
1102 1102 | | user: test
1103 1103 | | date: Thu Jan 01 00:00:10 1970 +0000
1104 1104 | | summary: (10) merge two known; one immediate left, one near right
1105 1105 | |
1106 1106 o | changeset: 9:7010c0af0a35
1107 1107 |\ \ parent: 7:b632bb1b1224
1108 1108 | | | parent: 8:7a0b11f71937
1109 1109 | | | user: test
1110 1110 | | | date: Thu Jan 01 00:00:09 1970 +0000
1111 1111 | | | summary: (9) expand
1112 1112 | | |
1113 1113 | o | changeset: 8:7a0b11f71937
1114 1114 |/| | parent: 0:e6eb3150255d
1115 1115 | ~ | parent: 7:b632bb1b1224
1116 1116 | | user: test
1117 1117 | | date: Thu Jan 01 00:00:08 1970 +0000
1118 1118 | | summary: (8) merge two known; one immediate left, one far right
1119 1119 | /
1120 1120 o | changeset: 7:b632bb1b1224
1121 1121 |\ \ parent: 2:3d9a33b8d1e1
1122 1122 | ~ | parent: 5:4409d547b708
1123 1123 | | user: test
1124 1124 | | date: Thu Jan 01 00:00:07 1970 +0000
1125 1125 | | summary: (7) expand
1126 1126 | /
1127 1127 | o changeset: 6:b105a072e251
1128 1128 |/| parent: 2:3d9a33b8d1e1
1129 1129 | ~ parent: 5:4409d547b708
1130 1130 | user: test
1131 1131 | date: Thu Jan 01 00:00:06 1970 +0000
1132 1132 | summary: (6) merge two known; one immediate left, one far left
1133 1133 |
1134 1134 o changeset: 5:4409d547b708
1135 1135 |\ parent: 3:27eef8ed80b4
1136 1136 | ~ parent: 4:26a8bac39d9f
1137 1137 | user: test
1138 1138 | date: Thu Jan 01 00:00:05 1970 +0000
1139 1139 | summary: (5) expand
1140 1140 |
1141 1141 o changeset: 4:26a8bac39d9f
1142 1142 |\ parent: 1:6db2ef61d156
1143 1143 ~ ~ parent: 3:27eef8ed80b4
1144 1144 user: test
1145 1145 date: Thu Jan 01 00:00:04 1970 +0000
1146 1146 summary: (4) merge two known; one immediate left, one immediate right
1147 1147
1148 1148
1149 1149
1150 1150 Empty revision range - display nothing:
1151 1151 $ hg log -G -r 1..0
1152 1152
1153 1153 $ cd ..
1154 1154
1155 1155 #if no-outer-repo
1156 1156
1157 1157 From outer space:
1158 1158 $ hg log -G -l1 repo
1159 1159 @ changeset: 34:fea3ac5810e0
1160 1160 | tag: tip
1161 1161 ~ parent: 32:d06dffa21a31
1162 1162 user: test
1163 1163 date: Thu Jan 01 00:00:34 1970 +0000
1164 1164 summary: (34) head
1165 1165
1166 1166 $ hg log -G -l1 repo/a
1167 1167 @ changeset: 34:fea3ac5810e0
1168 1168 | tag: tip
1169 1169 ~ parent: 32:d06dffa21a31
1170 1170 user: test
1171 1171 date: Thu Jan 01 00:00:34 1970 +0000
1172 1172 summary: (34) head
1173 1173
1174 1174 $ hg log -G -l1 repo/missing
1175 1175
1176 1176 #endif
1177 1177
1178 1178 File log with revs != cset revs:
1179 1179 $ hg init flog
1180 1180 $ cd flog
1181 1181 $ echo one >one
1182 1182 $ hg add one
1183 1183 $ hg commit -mone
1184 1184 $ echo two >two
1185 1185 $ hg add two
1186 1186 $ hg commit -mtwo
1187 1187 $ echo more >two
1188 1188 $ hg commit -mmore
1189 1189 $ hg log -G two
1190 1190 @ changeset: 2:12c28321755b
1191 1191 | tag: tip
1192 1192 | user: test
1193 1193 | date: Thu Jan 01 00:00:00 1970 +0000
1194 1194 | summary: more
1195 1195 |
1196 1196 o changeset: 1:5ac72c0599bf
1197 1197 | user: test
1198 1198 ~ date: Thu Jan 01 00:00:00 1970 +0000
1199 1199 summary: two
1200 1200
1201 1201
1202 1202 Issue1896: File log with explicit style
1203 1203 $ hg log -G --style=default one
1204 1204 o changeset: 0:3d578b4a1f53
1205 1205 user: test
1206 1206 date: Thu Jan 01 00:00:00 1970 +0000
1207 1207 summary: one
1208 1208
1209 1209 Issue2395: glog --style header and footer
1210 1210 $ hg log -G --style=xml one
1211 1211 <?xml version="1.0"?>
1212 1212 <log>
1213 1213 o <logentry revision="0" node="3d578b4a1f537d5fcf7301bfa9c0b97adfaa6fb1">
1214 1214 <author email="test">test</author>
1215 1215 <date>1970-01-01T00:00:00+00:00</date>
1216 1216 <msg xml:space="preserve">one</msg>
1217 1217 </logentry>
1218 1218 </log>
1219 1219
1220 1220 $ cd ..
1221 1221
1222 1222 Incoming and outgoing:
1223 1223
1224 1224 $ hg clone -U -r31 repo repo2
1225 1225 adding changesets
1226 1226 adding manifests
1227 1227 adding file changes
1228 1228 added 31 changesets with 31 changes to 1 files
1229 1229 new changesets e6eb3150255d:621d83e11f67
1230 1230 $ cd repo2
1231 1231
1232 1232 $ hg incoming --graph ../repo
1233 1233 comparing with ../repo
1234 1234 searching for changes
1235 1235 o changeset: 34:fea3ac5810e0
1236 1236 | tag: tip
1237 1237 | parent: 32:d06dffa21a31
1238 1238 | user: test
1239 1239 | date: Thu Jan 01 00:00:34 1970 +0000
1240 1240 | summary: (34) head
1241 1241 |
1242 1242 | o changeset: 33:68608f5145f9
1243 1243 | parent: 18:1aa84d96232a
1244 1244 | user: test
1245 1245 | date: Thu Jan 01 00:00:33 1970 +0000
1246 1246 | summary: (33) head
1247 1247 |
1248 1248 o changeset: 32:d06dffa21a31
1249 1249 | parent: 27:886ed638191b
1250 1250 | parent: 31:621d83e11f67
1251 1251 | user: test
1252 1252 | date: Thu Jan 01 00:00:32 1970 +0000
1253 1253 | summary: (32) expand
1254 1254 |
1255 1255 o changeset: 27:886ed638191b
1256 1256 parent: 21:d42a756af44d
1257 1257 user: test
1258 1258 date: Thu Jan 01 00:00:27 1970 +0000
1259 1259 summary: (27) collapse
1260 1260
1261 1261 $ cd ..
1262 1262
1263 1263 $ hg -R repo outgoing --graph repo2
1264 1264 comparing with repo2
1265 1265 searching for changes
1266 1266 @ changeset: 34:fea3ac5810e0
1267 1267 | tag: tip
1268 1268 | parent: 32:d06dffa21a31
1269 1269 | user: test
1270 1270 | date: Thu Jan 01 00:00:34 1970 +0000
1271 1271 | summary: (34) head
1272 1272 |
1273 1273 | o changeset: 33:68608f5145f9
1274 1274 | parent: 18:1aa84d96232a
1275 1275 | user: test
1276 1276 | date: Thu Jan 01 00:00:33 1970 +0000
1277 1277 | summary: (33) head
1278 1278 |
1279 1279 o changeset: 32:d06dffa21a31
1280 1280 | parent: 27:886ed638191b
1281 1281 | parent: 31:621d83e11f67
1282 1282 | user: test
1283 1283 | date: Thu Jan 01 00:00:32 1970 +0000
1284 1284 | summary: (32) expand
1285 1285 |
1286 1286 o changeset: 27:886ed638191b
1287 1287 parent: 21:d42a756af44d
1288 1288 user: test
1289 1289 date: Thu Jan 01 00:00:27 1970 +0000
1290 1290 summary: (27) collapse
1291 1291
1292 1292
1293 1293 File + limit with revs != cset revs:
1294 1294 $ cd repo
1295 1295 $ touch b
1296 1296 $ hg ci -Aqm0
1297 1297 $ hg log -G -l2 a
1298 1298 o changeset: 34:fea3ac5810e0
1299 1299 | parent: 32:d06dffa21a31
1300 1300 ~ user: test
1301 1301 date: Thu Jan 01 00:00:34 1970 +0000
1302 1302 summary: (34) head
1303 1303
1304 1304 o changeset: 33:68608f5145f9
1305 1305 | parent: 18:1aa84d96232a
1306 1306 ~ user: test
1307 1307 date: Thu Jan 01 00:00:33 1970 +0000
1308 1308 summary: (33) head
1309 1309
1310 1310
1311 1311 File + limit + -ra:b, (b - a) < limit:
1312 1312 $ hg log -G -l3000 -r32:tip a
1313 1313 o changeset: 34:fea3ac5810e0
1314 1314 | parent: 32:d06dffa21a31
1315 1315 | user: test
1316 1316 | date: Thu Jan 01 00:00:34 1970 +0000
1317 1317 | summary: (34) head
1318 1318 |
1319 1319 | o changeset: 33:68608f5145f9
1320 1320 | | parent: 18:1aa84d96232a
1321 1321 | ~ user: test
1322 1322 | date: Thu Jan 01 00:00:33 1970 +0000
1323 1323 | summary: (33) head
1324 1324 |
1325 1325 o changeset: 32:d06dffa21a31
1326 1326 |\ parent: 27:886ed638191b
1327 1327 ~ ~ parent: 31:621d83e11f67
1328 1328 user: test
1329 1329 date: Thu Jan 01 00:00:32 1970 +0000
1330 1330 summary: (32) expand
1331 1331
1332 1332
1333 1333 Point out a common and an uncommon unshown parent
1334 1334
1335 1335 $ hg log -G -r 'rev(8) or rev(9)'
1336 1336 o changeset: 9:7010c0af0a35
1337 1337 |\ parent: 7:b632bb1b1224
1338 1338 | ~ parent: 8:7a0b11f71937
1339 1339 | user: test
1340 1340 | date: Thu Jan 01 00:00:09 1970 +0000
1341 1341 | summary: (9) expand
1342 1342 |
1343 1343 o changeset: 8:7a0b11f71937
1344 1344 |\ parent: 0:e6eb3150255d
1345 1345 ~ ~ parent: 7:b632bb1b1224
1346 1346 user: test
1347 1347 date: Thu Jan 01 00:00:08 1970 +0000
1348 1348 summary: (8) merge two known; one immediate left, one far right
1349 1349
1350 1350
1351 1351 File + limit + -ra:b, b < tip:
1352 1352
1353 1353 $ hg log -G -l1 -r32:34 a
1354 1354 o changeset: 34:fea3ac5810e0
1355 1355 | parent: 32:d06dffa21a31
1356 1356 ~ user: test
1357 1357 date: Thu Jan 01 00:00:34 1970 +0000
1358 1358 summary: (34) head
1359 1359
1360 1360
1361 1361 file(File) + limit + -ra:b, b < tip:
1362 1362
1363 1363 $ hg log -G -l1 -r32:34 -r 'file("a")'
1364 1364 o changeset: 34:fea3ac5810e0
1365 1365 | parent: 32:d06dffa21a31
1366 1366 ~ user: test
1367 1367 date: Thu Jan 01 00:00:34 1970 +0000
1368 1368 summary: (34) head
1369 1369
1370 1370
1371 1371 limit(file(File) and a::b), b < tip:
1372 1372
1373 1373 $ hg log -G -r 'limit(file("a") and 32::34, 1)'
1374 1374 o changeset: 32:d06dffa21a31
1375 1375 |\ parent: 27:886ed638191b
1376 1376 ~ ~ parent: 31:621d83e11f67
1377 1377 user: test
1378 1378 date: Thu Jan 01 00:00:32 1970 +0000
1379 1379 summary: (32) expand
1380 1380
1381 1381
1382 1382 File + limit + -ra:b, b < tip:
1383 1383
1384 1384 $ hg log -G -r 'limit(file("a") and 34::32, 1)'
1385 1385
1386 1386 File + limit + -ra:b, b < tip, (b - a) < limit:
1387 1387
1388 1388 $ hg log -G -l10 -r33:34 a
1389 1389 o changeset: 34:fea3ac5810e0
1390 1390 | parent: 32:d06dffa21a31
1391 1391 ~ user: test
1392 1392 date: Thu Jan 01 00:00:34 1970 +0000
1393 1393 summary: (34) head
1394 1394
1395 1395 o changeset: 33:68608f5145f9
1396 1396 | parent: 18:1aa84d96232a
1397 1397 ~ user: test
1398 1398 date: Thu Jan 01 00:00:33 1970 +0000
1399 1399 summary: (33) head
1400 1400
1401 1401
1402 1402 Do not crash or produce strange graphs if history is buggy
1403 1403
1404 1404 $ hg branch branch
1405 1405 marked working directory as branch branch
1406 1406 (branches are permanent and global, did you want a bookmark?)
1407 1407 $ commit 36 "buggy merge: identical parents" 35 35
1408 1408 $ hg log -G -l5
1409 1409 @ changeset: 36:08a19a744424
1410 1410 | branch: branch
1411 1411 | tag: tip
1412 1412 | parent: 35:9159c3644c5e
1413 1413 | parent: 35:9159c3644c5e
1414 1414 | user: test
1415 1415 | date: Thu Jan 01 00:00:36 1970 +0000
1416 1416 | summary: (36) buggy merge: identical parents
1417 1417 |
1418 1418 o changeset: 35:9159c3644c5e
1419 1419 | user: test
1420 1420 | date: Thu Jan 01 00:00:00 1970 +0000
1421 1421 | summary: 0
1422 1422 |
1423 1423 o changeset: 34:fea3ac5810e0
1424 1424 | parent: 32:d06dffa21a31
1425 1425 | user: test
1426 1426 | date: Thu Jan 01 00:00:34 1970 +0000
1427 1427 | summary: (34) head
1428 1428 |
1429 1429 | o changeset: 33:68608f5145f9
1430 1430 | | parent: 18:1aa84d96232a
1431 1431 | ~ user: test
1432 1432 | date: Thu Jan 01 00:00:33 1970 +0000
1433 1433 | summary: (33) head
1434 1434 |
1435 1435 o changeset: 32:d06dffa21a31
1436 1436 |\ parent: 27:886ed638191b
1437 1437 ~ ~ parent: 31:621d83e11f67
1438 1438 user: test
1439 1439 date: Thu Jan 01 00:00:32 1970 +0000
1440 1440 summary: (32) expand
1441 1441
1442 1442
1443 1443 Test log -G options
1444 1444
1445 1445 $ testlog() {
1446 1446 > hg log -G --print-revset "$@"
1447 1447 > hg log --template 'nodetag {rev}\n' "$@" | grep nodetag \
1448 1448 > | sed 's/.*nodetag/nodetag/' > log.nodes
1449 1449 > hg log -G --template 'nodetag {rev}\n' "$@" | grep nodetag \
1450 1450 > | sed 's/.*nodetag/nodetag/' > glog.nodes
1451 1451 > (cmp log.nodes glog.nodes || diff -u log.nodes glog.nodes) \
1452 1452 > | grep '^[-+@ ]' || :
1453 1453 > }
1454 1454
1455 1455 glog always reorders nodes which explains the difference with log
1456 1456
1457 1457 $ testlog -r 27 -r 25 -r 21 -r 34 -r 32 -r 31
1458 1458 ['27', '25', '21', '34', '32', '31']
1459 1459 []
1460 1460 <baseset- [21, 25, 27, 31, 32, 34]>
1461 1461 --- log.nodes * (glob)
1462 1462 +++ glog.nodes * (glob)
1463 1463 @@ -1,6 +1,6 @@
1464 1464 -nodetag 27
1465 1465 -nodetag 25
1466 1466 -nodetag 21
1467 1467 nodetag 34
1468 1468 nodetag 32
1469 1469 nodetag 31
1470 1470 +nodetag 27
1471 1471 +nodetag 25
1472 1472 +nodetag 21
1473 1473 $ testlog -u test -u not-a-user
1474 1474 []
1475 1475 (or
1476 1476 (list
1477 1477 (func
1478 1478 (symbol 'user')
1479 1479 (string 'test'))
1480 1480 (func
1481 1481 (symbol 'user')
1482 1482 (string 'not-a-user'))))
1483 1483 <filteredset
1484 1484 <spanset- 0:37>,
1485 1485 <addset
1486 1486 <filteredset
1487 1487 <fullreposet+ 0:37>,
1488 1488 <user 'test'>>,
1489 1489 <filteredset
1490 1490 <fullreposet+ 0:37>,
1491 1491 <user 'not-a-user'>>>>
1492 1492 $ testlog -b not-a-branch
1493 1493 abort: unknown revision 'not-a-branch'!
1494 1494 abort: unknown revision 'not-a-branch'!
1495 1495 abort: unknown revision 'not-a-branch'!
1496 1496 $ testlog -b 35 -b 36 --only-branch branch
1497 1497 []
1498 1498 (or
1499 1499 (list
1500 1500 (func
1501 1501 (symbol 'branch')
1502 1502 (string 'default'))
1503 (func
1504 (symbol 'branch')
1505 (string 'branch'))
1506 (func
1507 (symbol 'branch')
1508 (string 'branch'))))
1503 (or
1504 (list
1505 (func
1506 (symbol 'branch')
1507 (string 'branch'))
1508 (func
1509 (symbol 'branch')
1510 (string 'branch'))))))
1509 1511 <filteredset
1510 1512 <spanset- 0:37>,
1511 1513 <addset
1512 1514 <filteredset
1513 1515 <fullreposet+ 0:37>,
1514 1516 <branch 'default'>>,
1515 1517 <addset
1516 1518 <filteredset
1517 1519 <fullreposet+ 0:37>,
1518 1520 <branch 'branch'>>,
1519 1521 <filteredset
1520 1522 <fullreposet+ 0:37>,
1521 1523 <branch 'branch'>>>>>
1522 1524 $ testlog -k expand -k merge
1523 1525 []
1524 1526 (or
1525 1527 (list
1526 1528 (func
1527 1529 (symbol 'keyword')
1528 1530 (string 'expand'))
1529 1531 (func
1530 1532 (symbol 'keyword')
1531 1533 (string 'merge'))))
1532 1534 <filteredset
1533 1535 <spanset- 0:37>,
1534 1536 <addset
1535 1537 <filteredset
1536 1538 <fullreposet+ 0:37>,
1537 1539 <keyword 'expand'>>,
1538 1540 <filteredset
1539 1541 <fullreposet+ 0:37>,
1540 1542 <keyword 'merge'>>>>
1541 1543 $ testlog --only-merges
1542 1544 []
1543 1545 (func
1544 1546 (symbol 'merge')
1545 1547 None)
1546 1548 <filteredset
1547 1549 <spanset- 0:37>,
1548 1550 <merge>>
1549 1551 $ testlog --no-merges
1550 1552 []
1551 1553 (not
1552 1554 (func
1553 1555 (symbol 'merge')
1554 1556 None))
1555 1557 <filteredset
1556 1558 <spanset- 0:37>,
1557 1559 <not
1558 1560 <filteredset
1559 1561 <spanset- 0:37>,
1560 1562 <merge>>>>
1561 1563 $ testlog --date '2 0 to 4 0'
1562 1564 []
1563 1565 (func
1564 1566 (symbol 'date')
1565 1567 (string '2 0 to 4 0'))
1566 1568 <filteredset
1567 1569 <spanset- 0:37>,
1568 1570 <date '2 0 to 4 0'>>
1569 1571 $ hg log -G -d 'brace ) in a date'
1570 1572 hg: parse error: invalid date: 'brace ) in a date'
1571 1573 [255]
1572 1574 $ testlog --prune 31 --prune 32
1573 1575 []
1574 (and
1575 (not
1576 (func
1577 (symbol 'ancestors')
1578 (string '31')))
1579 (not
1580 (func
1581 (symbol 'ancestors')
1582 (string '32'))))
1576 (not
1577 (or
1578 (list
1579 (func
1580 (symbol 'ancestors')
1581 (string '31'))
1582 (func
1583 (symbol 'ancestors')
1584 (string '32')))))
1583 1585 <filteredset
1584 <filteredset
1585 <spanset- 0:37>,
1586 <not
1586 <spanset- 0:37>,
1587 <not
1588 <addset
1587 1589 <filteredset
1588 1590 <spanset- 0:37>,
1589 <generatorsetdesc+>>>>,
1590 <not
1591 <filteredset
1592 <spanset- 0:37>,
1593 <generatorsetdesc+>>>>
1591 <generatorsetdesc+>>,
1592 <filteredset
1593 <spanset- 0:37>,
1594 <generatorsetdesc+>>>>>
1594 1595
1595 1596 Dedicated repo for --follow and paths filtering. The g is crafted to
1596 1597 have 2 filelog topological heads in a linear changeset graph.
1597 1598
1598 1599 $ cd ..
1599 1600 $ hg init follow
1600 1601 $ cd follow
1601 1602 $ testlog --follow
1602 1603 []
1603 1604 []
1604 1605 <baseset []>
1605 1606 $ testlog -rnull
1606 1607 ['null']
1607 1608 []
1608 1609 <baseset [-1]>
1609 1610 $ echo a > a
1610 1611 $ echo aa > aa
1611 1612 $ echo f > f
1612 1613 $ hg ci -Am "add a" a aa f
1613 1614 $ hg cp a b
1614 1615 $ hg cp f g
1615 1616 $ hg ci -m "copy a b"
1616 1617 $ mkdir dir
1617 1618 $ hg mv b dir
1618 1619 $ echo g >> g
1619 1620 $ echo f >> f
1620 1621 $ hg ci -m "mv b dir/b"
1621 1622 $ hg mv a b
1622 1623 $ hg cp -f f g
1623 1624 $ echo a > d
1624 1625 $ hg add d
1625 1626 $ hg ci -m "mv a b; add d"
1626 1627 $ hg mv dir/b e
1627 1628 $ hg ci -m "mv dir/b e"
1628 1629 $ hg log -G --template '({rev}) {desc|firstline}\n'
1629 1630 @ (4) mv dir/b e
1630 1631 |
1631 1632 o (3) mv a b; add d
1632 1633 |
1633 1634 o (2) mv b dir/b
1634 1635 |
1635 1636 o (1) copy a b
1636 1637 |
1637 1638 o (0) add a
1638 1639
1639 1640
1640 1641 $ testlog a
1641 1642 []
1642 1643 (func
1643 1644 (symbol 'filelog')
1644 1645 (string 'a'))
1645 1646 <filteredset
1646 1647 <spanset- 0:5>, set([0])>
1647 1648 $ testlog a b
1648 1649 []
1649 1650 (or
1650 1651 (list
1651 1652 (func
1652 1653 (symbol 'filelog')
1653 1654 (string 'a'))
1654 1655 (func
1655 1656 (symbol 'filelog')
1656 1657 (string 'b'))))
1657 1658 <filteredset
1658 1659 <spanset- 0:5>,
1659 1660 <addset
1660 1661 <baseset+ [0]>,
1661 1662 <baseset+ [1]>>>
1662 1663
1663 1664 Test falling back to slow path for non-existing files
1664 1665
1665 1666 $ testlog a c
1666 1667 []
1667 1668 (func
1668 1669 (symbol '_matchfiles')
1669 1670 (list
1670 1671 (string 'r:')
1671 1672 (string 'd:relpath')
1672 1673 (string 'p:a')
1673 1674 (string 'p:c')))
1674 1675 <filteredset
1675 1676 <spanset- 0:5>,
1676 1677 <matchfiles patterns=['a', 'c'], include=[] exclude=[], default='relpath', rev=None>>
1677 1678
1678 1679 Test multiple --include/--exclude/paths
1679 1680
1680 1681 $ testlog --include a --include e --exclude b --exclude e a e
1681 1682 []
1682 1683 (func
1683 1684 (symbol '_matchfiles')
1684 1685 (list
1685 1686 (string 'r:')
1686 1687 (string 'd:relpath')
1687 1688 (string 'p:a')
1688 1689 (string 'p:e')
1689 1690 (string 'i:a')
1690 1691 (string 'i:e')
1691 1692 (string 'x:b')
1692 1693 (string 'x:e')))
1693 1694 <filteredset
1694 1695 <spanset- 0:5>,
1695 1696 <matchfiles patterns=['a', 'e'], include=['a', 'e'] exclude=['b', 'e'], default='relpath', rev=None>>
1696 1697
1697 1698 Test glob expansion of pats
1698 1699
1699 1700 $ expandglobs=`$PYTHON -c "import mercurial.util; \
1700 1701 > print(mercurial.util.expandglobs and 'true' or 'false')"`
1701 1702 $ if [ $expandglobs = "true" ]; then
1702 1703 > testlog 'a*';
1703 1704 > else
1704 1705 > testlog a*;
1705 1706 > fi;
1706 1707 []
1707 1708 (func
1708 1709 (symbol 'filelog')
1709 1710 (string 'aa'))
1710 1711 <filteredset
1711 1712 <spanset- 0:5>, set([0])>
1712 1713
1713 1714 Test --follow on a non-existent directory
1714 1715
1715 1716 $ testlog -f dir
1716 1717 abort: cannot follow file not in parent revision: "dir"
1717 1718 abort: cannot follow file not in parent revision: "dir"
1718 1719 abort: cannot follow file not in parent revision: "dir"
1719 1720
1720 1721 Test --follow on a directory
1721 1722
1722 1723 $ hg up -q '.^'
1723 1724 $ testlog -f dir
1724 1725 []
1725 1726 (and
1726 1727 (func
1727 1728 (symbol 'ancestors')
1728 1729 (symbol '.'))
1729 1730 (func
1730 1731 (symbol '_matchfiles')
1731 1732 (list
1732 1733 (string 'r:')
1733 1734 (string 'd:relpath')
1734 1735 (string 'p:dir'))))
1735 1736 <filteredset
1736 1737 <filteredset
1737 1738 <spanset- 0:4>,
1738 1739 <generatorsetdesc+>>,
1739 1740 <matchfiles patterns=['dir'], include=[] exclude=[], default='relpath', rev=None>>
1740 1741 $ hg up -q tip
1741 1742
1742 1743 Test --follow on file not in parent revision
1743 1744
1744 1745 $ testlog -f a
1745 1746 abort: cannot follow file not in parent revision: "a"
1746 1747 abort: cannot follow file not in parent revision: "a"
1747 1748 abort: cannot follow file not in parent revision: "a"
1748 1749
1749 1750 Test --follow and patterns
1750 1751
1751 1752 $ testlog -f 'glob:*'
1752 1753 []
1753 1754 (and
1754 1755 (func
1755 1756 (symbol 'ancestors')
1756 1757 (symbol '.'))
1757 1758 (func
1758 1759 (symbol '_matchfiles')
1759 1760 (list
1760 1761 (string 'r:')
1761 1762 (string 'd:relpath')
1762 1763 (string 'p:glob:*'))))
1763 1764 <filteredset
1764 1765 <filteredset
1765 1766 <spanset- 0:5>,
1766 1767 <generatorsetdesc+>>,
1767 1768 <matchfiles patterns=['glob:*'], include=[] exclude=[], default='relpath', rev=None>>
1768 1769
1769 1770 Test --follow on a single rename
1770 1771
1771 1772 $ hg up -q 2
1772 1773 $ testlog -f a
1773 1774 []
1774 1775 (func
1775 1776 (symbol 'follow')
1776 1777 (string 'a'))
1777 1778 <filteredset
1778 1779 <spanset- 0:3>,
1779 1780 <generatorsetdesc+>>
1780 1781
1781 1782 Test --follow and multiple renames
1782 1783
1783 1784 $ hg up -q tip
1784 1785 $ testlog -f e
1785 1786 []
1786 1787 (func
1787 1788 (symbol 'follow')
1788 1789 (string 'e'))
1789 1790 <filteredset
1790 1791 <spanset- 0:5>,
1791 1792 <generatorsetdesc+>>
1792 1793
1793 1794 Test --follow and multiple filelog heads
1794 1795
1795 1796 $ hg up -q 2
1796 1797 $ testlog -f g
1797 1798 []
1798 1799 (func
1799 1800 (symbol 'follow')
1800 1801 (string 'g'))
1801 1802 <filteredset
1802 1803 <spanset- 0:3>,
1803 1804 <generatorsetdesc+>>
1804 1805 $ cat log.nodes
1805 1806 nodetag 2
1806 1807 nodetag 1
1807 1808 nodetag 0
1808 1809 $ hg up -q tip
1809 1810 $ testlog -f g
1810 1811 []
1811 1812 (func
1812 1813 (symbol 'follow')
1813 1814 (string 'g'))
1814 1815 <filteredset
1815 1816 <spanset- 0:5>,
1816 1817 <generatorsetdesc+>>
1817 1818 $ cat log.nodes
1818 1819 nodetag 3
1819 1820 nodetag 2
1820 1821 nodetag 0
1821 1822
1822 1823 Test --follow and multiple files
1823 1824
1824 1825 $ testlog -f g e
1825 1826 []
1826 1827 (or
1827 1828 (list
1828 1829 (func
1829 1830 (symbol 'follow')
1830 1831 (string 'g'))
1831 1832 (func
1832 1833 (symbol 'follow')
1833 1834 (string 'e'))))
1834 1835 <filteredset
1835 1836 <spanset- 0:5>,
1836 1837 <addset
1837 1838 <generatorsetdesc+>,
1838 1839 <generatorsetdesc+>>>
1839 1840 $ cat log.nodes
1840 1841 nodetag 4
1841 1842 nodetag 3
1842 1843 nodetag 2
1843 1844 nodetag 1
1844 1845 nodetag 0
1845 1846
1846 1847 Test --follow null parent
1847 1848
1848 1849 $ hg up -q null
1849 1850 $ testlog -f
1850 1851 []
1851 1852 []
1852 1853 <baseset []>
1853 1854
1854 1855 Test --follow-first
1855 1856
1856 1857 $ hg up -q 3
1857 1858 $ echo ee > e
1858 1859 $ hg ci -Am "add another e" e
1859 1860 created new head
1860 1861 $ hg merge --tool internal:other 4
1861 1862 0 files updated, 1 files merged, 1 files removed, 0 files unresolved
1862 1863 (branch merge, don't forget to commit)
1863 1864 $ echo merge > e
1864 1865 $ hg ci -m "merge 5 and 4"
1865 1866 $ testlog --follow-first
1866 1867 []
1867 1868 (func
1868 1869 (symbol '_firstancestors')
1869 1870 (func
1870 1871 (symbol 'rev')
1871 1872 (symbol '6')))
1872 1873 <filteredset
1873 1874 <spanset- 0:7>,
1874 1875 <generatorsetdesc+>>
1875 1876
1876 1877 Cannot compare with log --follow-first FILE as it never worked
1877 1878
1878 1879 $ hg log -G --print-revset --follow-first e
1879 1880 []
1880 1881 (func
1881 1882 (symbol '_followfirst')
1882 1883 (string 'e'))
1883 1884 <filteredset
1884 1885 <spanset- 0:7>,
1885 1886 <generatorsetdesc+>>
1886 1887 $ hg log -G --follow-first e --template '{rev} {desc|firstline}\n'
1887 1888 @ 6 merge 5 and 4
1888 1889 |\
1889 1890 | ~
1890 1891 o 5 add another e
1891 1892 |
1892 1893 ~
1893 1894
1894 1895 Test --copies
1895 1896
1896 1897 $ hg log -G --copies --template "{rev} {desc|firstline} \
1897 1898 > copies: {file_copies_switch}\n"
1898 1899 @ 6 merge 5 and 4 copies:
1899 1900 |\
1900 1901 | o 5 add another e copies:
1901 1902 | |
1902 1903 o | 4 mv dir/b e copies: e (dir/b)
1903 1904 |/
1904 1905 o 3 mv a b; add d copies: b (a)g (f)
1905 1906 |
1906 1907 o 2 mv b dir/b copies: dir/b (b)
1907 1908 |
1908 1909 o 1 copy a b copies: b (a)g (f)
1909 1910 |
1910 1911 o 0 add a copies:
1911 1912
1912 1913 Test "set:..." and parent revision
1913 1914
1914 1915 $ hg up -q 4
1915 1916 $ testlog "set:copied()"
1916 1917 []
1917 1918 (func
1918 1919 (symbol '_matchfiles')
1919 1920 (list
1920 1921 (string 'r:')
1921 1922 (string 'd:relpath')
1922 1923 (string 'p:set:copied()')))
1923 1924 <filteredset
1924 1925 <spanset- 0:7>,
1925 1926 <matchfiles patterns=['set:copied()'], include=[] exclude=[], default='relpath', rev=None>>
1926 1927 $ testlog --include "set:copied()"
1927 1928 []
1928 1929 (func
1929 1930 (symbol '_matchfiles')
1930 1931 (list
1931 1932 (string 'r:')
1932 1933 (string 'd:relpath')
1933 1934 (string 'i:set:copied()')))
1934 1935 <filteredset
1935 1936 <spanset- 0:7>,
1936 1937 <matchfiles patterns=[], include=['set:copied()'] exclude=[], default='relpath', rev=None>>
1937 1938 $ testlog -r "sort(file('set:copied()'), -rev)"
1938 1939 ["sort(file('set:copied()'), -rev)"]
1939 1940 []
1940 1941 <baseset []>
1941 1942
1942 1943 Test --removed
1943 1944
1944 1945 $ testlog --removed
1945 1946 []
1946 1947 []
1947 1948 <spanset- 0:7>
1948 1949 $ testlog --removed a
1949 1950 []
1950 1951 (func
1951 1952 (symbol '_matchfiles')
1952 1953 (list
1953 1954 (string 'r:')
1954 1955 (string 'd:relpath')
1955 1956 (string 'p:a')))
1956 1957 <filteredset
1957 1958 <spanset- 0:7>,
1958 1959 <matchfiles patterns=['a'], include=[] exclude=[], default='relpath', rev=None>>
1959 1960 $ testlog --removed --follow a
1960 1961 []
1961 1962 (and
1962 1963 (func
1963 1964 (symbol 'ancestors')
1964 1965 (symbol '.'))
1965 1966 (func
1966 1967 (symbol '_matchfiles')
1967 1968 (list
1968 1969 (string 'r:')
1969 1970 (string 'd:relpath')
1970 1971 (string 'p:a'))))
1971 1972 <filteredset
1972 1973 <filteredset
1973 1974 <spanset- 0:5>,
1974 1975 <generatorsetdesc+>>,
1975 1976 <matchfiles patterns=['a'], include=[] exclude=[], default='relpath', rev=None>>
1976 1977
1977 1978 Test --patch and --stat with --follow and --follow-first
1978 1979
1979 1980 $ hg up -q 3
1980 1981 $ hg log -G --git --patch b
1981 1982 o changeset: 1:216d4c92cf98
1982 1983 | user: test
1983 1984 ~ date: Thu Jan 01 00:00:00 1970 +0000
1984 1985 summary: copy a b
1985 1986
1986 1987 diff --git a/a b/b
1987 1988 copy from a
1988 1989 copy to b
1989 1990
1990 1991
1991 1992 $ hg log -G --git --stat b
1992 1993 o changeset: 1:216d4c92cf98
1993 1994 | user: test
1994 1995 ~ date: Thu Jan 01 00:00:00 1970 +0000
1995 1996 summary: copy a b
1996 1997
1997 1998 b | 0
1998 1999 1 files changed, 0 insertions(+), 0 deletions(-)
1999 2000
2000 2001
2001 2002 $ hg log -G --git --patch --follow b
2002 2003 o changeset: 1:216d4c92cf98
2003 2004 | user: test
2004 2005 | date: Thu Jan 01 00:00:00 1970 +0000
2005 2006 | summary: copy a b
2006 2007 |
2007 2008 | diff --git a/a b/b
2008 2009 | copy from a
2009 2010 | copy to b
2010 2011 |
2011 2012 o changeset: 0:f8035bb17114
2012 2013 user: test
2013 2014 date: Thu Jan 01 00:00:00 1970 +0000
2014 2015 summary: add a
2015 2016
2016 2017 diff --git a/a b/a
2017 2018 new file mode 100644
2018 2019 --- /dev/null
2019 2020 +++ b/a
2020 2021 @@ -0,0 +1,1 @@
2021 2022 +a
2022 2023
2023 2024
2024 2025 $ hg log -G --git --stat --follow b
2025 2026 o changeset: 1:216d4c92cf98
2026 2027 | user: test
2027 2028 | date: Thu Jan 01 00:00:00 1970 +0000
2028 2029 | summary: copy a b
2029 2030 |
2030 2031 | b | 0
2031 2032 | 1 files changed, 0 insertions(+), 0 deletions(-)
2032 2033 |
2033 2034 o changeset: 0:f8035bb17114
2034 2035 user: test
2035 2036 date: Thu Jan 01 00:00:00 1970 +0000
2036 2037 summary: add a
2037 2038
2038 2039 a | 1 +
2039 2040 1 files changed, 1 insertions(+), 0 deletions(-)
2040 2041
2041 2042
2042 2043 $ hg up -q 6
2043 2044 $ hg log -G --git --patch --follow-first e
2044 2045 @ changeset: 6:fc281d8ff18d
2045 2046 |\ tag: tip
2046 2047 | ~ parent: 5:99b31f1c2782
2047 2048 | parent: 4:17d952250a9d
2048 2049 | user: test
2049 2050 | date: Thu Jan 01 00:00:00 1970 +0000
2050 2051 | summary: merge 5 and 4
2051 2052 |
2052 2053 | diff --git a/e b/e
2053 2054 | --- a/e
2054 2055 | +++ b/e
2055 2056 | @@ -1,1 +1,1 @@
2056 2057 | -ee
2057 2058 | +merge
2058 2059 |
2059 2060 o changeset: 5:99b31f1c2782
2060 2061 | parent: 3:5918b8d165d1
2061 2062 ~ user: test
2062 2063 date: Thu Jan 01 00:00:00 1970 +0000
2063 2064 summary: add another e
2064 2065
2065 2066 diff --git a/e b/e
2066 2067 new file mode 100644
2067 2068 --- /dev/null
2068 2069 +++ b/e
2069 2070 @@ -0,0 +1,1 @@
2070 2071 +ee
2071 2072
2072 2073
2073 2074 Test old-style --rev
2074 2075
2075 2076 $ hg tag 'foo-bar'
2076 2077 $ testlog -r 'foo-bar'
2077 2078 ['foo-bar']
2078 2079 []
2079 2080 <baseset [6]>
2080 2081
2081 2082 Test --follow and forward --rev
2082 2083
2083 2084 $ hg up -q 6
2084 2085 $ echo g > g
2085 2086 $ hg ci -Am 'add g' g
2086 2087 created new head
2087 2088 $ hg up -q 2
2088 2089 $ hg log -G --template "{rev} {desc|firstline}\n"
2089 2090 o 8 add g
2090 2091 |
2091 2092 | o 7 Added tag foo-bar for changeset fc281d8ff18d
2092 2093 |/
2093 2094 o 6 merge 5 and 4
2094 2095 |\
2095 2096 | o 5 add another e
2096 2097 | |
2097 2098 o | 4 mv dir/b e
2098 2099 |/
2099 2100 o 3 mv a b; add d
2100 2101 |
2101 2102 @ 2 mv b dir/b
2102 2103 |
2103 2104 o 1 copy a b
2104 2105 |
2105 2106 o 0 add a
2106 2107
2107 2108 $ hg archive -r 7 archive
2108 2109 $ grep changessincelatesttag archive/.hg_archival.txt
2109 2110 changessincelatesttag: 1
2110 2111 $ rm -r archive
2111 2112
2112 2113 changessincelatesttag with no prior tag
2113 2114 $ hg archive -r 4 archive
2114 2115 $ grep changessincelatesttag archive/.hg_archival.txt
2115 2116 changessincelatesttag: 5
2116 2117
2117 2118 $ hg export 'all()'
2118 2119 # HG changeset patch
2119 2120 # User test
2120 2121 # Date 0 0
2121 2122 # Thu Jan 01 00:00:00 1970 +0000
2122 2123 # Node ID f8035bb17114da16215af3436ec5222428ace8ee
2123 2124 # Parent 0000000000000000000000000000000000000000
2124 2125 add a
2125 2126
2126 2127 diff -r 000000000000 -r f8035bb17114 a
2127 2128 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2128 2129 +++ b/a Thu Jan 01 00:00:00 1970 +0000
2129 2130 @@ -0,0 +1,1 @@
2130 2131 +a
2131 2132 diff -r 000000000000 -r f8035bb17114 aa
2132 2133 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2133 2134 +++ b/aa Thu Jan 01 00:00:00 1970 +0000
2134 2135 @@ -0,0 +1,1 @@
2135 2136 +aa
2136 2137 diff -r 000000000000 -r f8035bb17114 f
2137 2138 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2138 2139 +++ b/f Thu Jan 01 00:00:00 1970 +0000
2139 2140 @@ -0,0 +1,1 @@
2140 2141 +f
2141 2142 # HG changeset patch
2142 2143 # User test
2143 2144 # Date 0 0
2144 2145 # Thu Jan 01 00:00:00 1970 +0000
2145 2146 # Node ID 216d4c92cf98ff2b4641d508b76b529f3d424c92
2146 2147 # Parent f8035bb17114da16215af3436ec5222428ace8ee
2147 2148 copy a b
2148 2149
2149 2150 diff -r f8035bb17114 -r 216d4c92cf98 b
2150 2151 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2151 2152 +++ b/b Thu Jan 01 00:00:00 1970 +0000
2152 2153 @@ -0,0 +1,1 @@
2153 2154 +a
2154 2155 diff -r f8035bb17114 -r 216d4c92cf98 g
2155 2156 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2156 2157 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2157 2158 @@ -0,0 +1,1 @@
2158 2159 +f
2159 2160 # HG changeset patch
2160 2161 # User test
2161 2162 # Date 0 0
2162 2163 # Thu Jan 01 00:00:00 1970 +0000
2163 2164 # Node ID bb573313a9e8349099b6ea2b2fb1fc7f424446f3
2164 2165 # Parent 216d4c92cf98ff2b4641d508b76b529f3d424c92
2165 2166 mv b dir/b
2166 2167
2167 2168 diff -r 216d4c92cf98 -r bb573313a9e8 b
2168 2169 --- a/b Thu Jan 01 00:00:00 1970 +0000
2169 2170 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2170 2171 @@ -1,1 +0,0 @@
2171 2172 -a
2172 2173 diff -r 216d4c92cf98 -r bb573313a9e8 dir/b
2173 2174 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2174 2175 +++ b/dir/b Thu Jan 01 00:00:00 1970 +0000
2175 2176 @@ -0,0 +1,1 @@
2176 2177 +a
2177 2178 diff -r 216d4c92cf98 -r bb573313a9e8 f
2178 2179 --- a/f Thu Jan 01 00:00:00 1970 +0000
2179 2180 +++ b/f Thu Jan 01 00:00:00 1970 +0000
2180 2181 @@ -1,1 +1,2 @@
2181 2182 f
2182 2183 +f
2183 2184 diff -r 216d4c92cf98 -r bb573313a9e8 g
2184 2185 --- a/g Thu Jan 01 00:00:00 1970 +0000
2185 2186 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2186 2187 @@ -1,1 +1,2 @@
2187 2188 f
2188 2189 +g
2189 2190 # HG changeset patch
2190 2191 # User test
2191 2192 # Date 0 0
2192 2193 # Thu Jan 01 00:00:00 1970 +0000
2193 2194 # Node ID 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2194 2195 # Parent bb573313a9e8349099b6ea2b2fb1fc7f424446f3
2195 2196 mv a b; add d
2196 2197
2197 2198 diff -r bb573313a9e8 -r 5918b8d165d1 a
2198 2199 --- a/a Thu Jan 01 00:00:00 1970 +0000
2199 2200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2200 2201 @@ -1,1 +0,0 @@
2201 2202 -a
2202 2203 diff -r bb573313a9e8 -r 5918b8d165d1 b
2203 2204 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2204 2205 +++ b/b Thu Jan 01 00:00:00 1970 +0000
2205 2206 @@ -0,0 +1,1 @@
2206 2207 +a
2207 2208 diff -r bb573313a9e8 -r 5918b8d165d1 d
2208 2209 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2209 2210 +++ b/d Thu Jan 01 00:00:00 1970 +0000
2210 2211 @@ -0,0 +1,1 @@
2211 2212 +a
2212 2213 diff -r bb573313a9e8 -r 5918b8d165d1 g
2213 2214 --- a/g Thu Jan 01 00:00:00 1970 +0000
2214 2215 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2215 2216 @@ -1,2 +1,2 @@
2216 2217 f
2217 2218 -g
2218 2219 +f
2219 2220 # HG changeset patch
2220 2221 # User test
2221 2222 # Date 0 0
2222 2223 # Thu Jan 01 00:00:00 1970 +0000
2223 2224 # Node ID 17d952250a9d03cc3dc77b199ab60e959b9b0260
2224 2225 # Parent 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2225 2226 mv dir/b e
2226 2227
2227 2228 diff -r 5918b8d165d1 -r 17d952250a9d dir/b
2228 2229 --- a/dir/b Thu Jan 01 00:00:00 1970 +0000
2229 2230 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2230 2231 @@ -1,1 +0,0 @@
2231 2232 -a
2232 2233 diff -r 5918b8d165d1 -r 17d952250a9d e
2233 2234 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2234 2235 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2235 2236 @@ -0,0 +1,1 @@
2236 2237 +a
2237 2238 # HG changeset patch
2238 2239 # User test
2239 2240 # Date 0 0
2240 2241 # Thu Jan 01 00:00:00 1970 +0000
2241 2242 # Node ID 99b31f1c2782e2deb1723cef08930f70fc84b37b
2242 2243 # Parent 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2243 2244 add another e
2244 2245
2245 2246 diff -r 5918b8d165d1 -r 99b31f1c2782 e
2246 2247 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2247 2248 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2248 2249 @@ -0,0 +1,1 @@
2249 2250 +ee
2250 2251 # HG changeset patch
2251 2252 # User test
2252 2253 # Date 0 0
2253 2254 # Thu Jan 01 00:00:00 1970 +0000
2254 2255 # Node ID fc281d8ff18d999ad6497b3d27390bcd695dcc73
2255 2256 # Parent 99b31f1c2782e2deb1723cef08930f70fc84b37b
2256 2257 # Parent 17d952250a9d03cc3dc77b199ab60e959b9b0260
2257 2258 merge 5 and 4
2258 2259
2259 2260 diff -r 99b31f1c2782 -r fc281d8ff18d dir/b
2260 2261 --- a/dir/b Thu Jan 01 00:00:00 1970 +0000
2261 2262 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2262 2263 @@ -1,1 +0,0 @@
2263 2264 -a
2264 2265 diff -r 99b31f1c2782 -r fc281d8ff18d e
2265 2266 --- a/e Thu Jan 01 00:00:00 1970 +0000
2266 2267 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2267 2268 @@ -1,1 +1,1 @@
2268 2269 -ee
2269 2270 +merge
2270 2271 # HG changeset patch
2271 2272 # User test
2272 2273 # Date 0 0
2273 2274 # Thu Jan 01 00:00:00 1970 +0000
2274 2275 # Node ID 02dbb8e276b8ab7abfd07cab50c901647e75c2dd
2275 2276 # Parent fc281d8ff18d999ad6497b3d27390bcd695dcc73
2276 2277 Added tag foo-bar for changeset fc281d8ff18d
2277 2278
2278 2279 diff -r fc281d8ff18d -r 02dbb8e276b8 .hgtags
2279 2280 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2280 2281 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
2281 2282 @@ -0,0 +1,1 @@
2282 2283 +fc281d8ff18d999ad6497b3d27390bcd695dcc73 foo-bar
2283 2284 # HG changeset patch
2284 2285 # User test
2285 2286 # Date 0 0
2286 2287 # Thu Jan 01 00:00:00 1970 +0000
2287 2288 # Node ID 24c2e826ddebf80f9dcd60b856bdb8e6715c5449
2288 2289 # Parent fc281d8ff18d999ad6497b3d27390bcd695dcc73
2289 2290 add g
2290 2291
2291 2292 diff -r fc281d8ff18d -r 24c2e826ddeb g
2292 2293 --- a/g Thu Jan 01 00:00:00 1970 +0000
2293 2294 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2294 2295 @@ -1,2 +1,1 @@
2295 2296 -f
2296 2297 -f
2297 2298 +g
2298 2299 $ testlog --follow -r6 -r8 -r5 -r7 -r4
2299 2300 ['reverse(::(((6) or (8)) or ((5) or ((7) or (4)))))']
2300 2301 []
2301 2302 <generatorsetdesc->
2302 2303
2303 2304 Test --follow-first and forward --rev
2304 2305
2305 2306 $ testlog --follow-first -r6 -r8 -r5 -r7 -r4
2306 2307 ['6', '8', '5', '7', '4']
2307 2308 (func
2308 2309 (symbol '_firstdescendants')
2309 2310 (func
2310 2311 (symbol 'rev')
2311 2312 (symbol '6')))
2312 2313 <filteredset
2313 2314 <baseset- [4, 5, 6, 7, 8]>,
2314 2315 <generatorsetasc+>>
2315 2316 --- log.nodes * (glob)
2316 2317 +++ glog.nodes * (glob)
2317 2318 @@ -1,3 +1,3 @@
2318 2319 -nodetag 6
2319 2320 nodetag 8
2320 2321 nodetag 7
2321 2322 +nodetag 6
2322 2323
2323 2324 Test --follow and backward --rev
2324 2325
2325 2326 $ testlog --follow -r6 -r5 -r7 -r8 -r4
2326 2327 ['reverse(::(((6) or (5)) or ((7) or ((8) or (4)))))']
2327 2328 []
2328 2329 <generatorsetdesc->
2329 2330
2330 2331 Test --follow-first and backward --rev
2331 2332
2332 2333 $ testlog --follow-first -r6 -r5 -r7 -r8 -r4
2333 2334 ['6', '5', '7', '8', '4']
2334 2335 (func
2335 2336 (symbol '_firstancestors')
2336 2337 (func
2337 2338 (symbol 'rev')
2338 2339 (symbol '6')))
2339 2340 <filteredset
2340 2341 <baseset- [4, 5, 6, 7, 8]>,
2341 2342 <generatorsetdesc+>>
2342 2343
2343 2344 Test --follow with --rev of graphlog extension
2344 2345
2345 2346 $ hg --config extensions.graphlog= glog -qfr1
2346 2347 o 1:216d4c92cf98
2347 2348 |
2348 2349 o 0:f8035bb17114
2349 2350
2350 2351
2351 2352 Test subdir
2352 2353
2353 2354 $ hg up -q 3
2354 2355 $ cd dir
2355 2356 $ testlog .
2356 2357 []
2357 2358 (func
2358 2359 (symbol '_matchfiles')
2359 2360 (list
2360 2361 (string 'r:')
2361 2362 (string 'd:relpath')
2362 2363 (string 'p:.')))
2363 2364 <filteredset
2364 2365 <spanset- 0:9>,
2365 2366 <matchfiles patterns=['.'], include=[] exclude=[], default='relpath', rev=None>>
2366 2367 $ testlog ../b
2367 2368 []
2368 2369 (func
2369 2370 (symbol 'filelog')
2370 2371 (string '../b'))
2371 2372 <filteredset
2372 2373 <spanset- 0:9>, set([1])>
2373 2374 $ testlog -f ../b
2374 2375 []
2375 2376 (func
2376 2377 (symbol 'follow')
2377 2378 (string 'b'))
2378 2379 <filteredset
2379 2380 <spanset- 0:4>,
2380 2381 <generatorsetdesc+>>
2381 2382 $ cd ..
2382 2383
2383 2384 Test --hidden
2384 2385 (enable obsolete)
2385 2386
2386 2387 $ cat >> $HGRCPATH << EOF
2387 2388 > [experimental]
2388 2389 > evolution.createmarkers=True
2389 2390 > EOF
2390 2391
2391 2392 $ hg debugobsolete `hg id --debug -i -r 8`
2392 2393 obsoleted 1 changesets
2393 2394 $ testlog
2394 2395 []
2395 2396 []
2396 2397 <spanset- 0:9>
2397 2398 $ testlog --hidden
2398 2399 []
2399 2400 []
2400 2401 <spanset- 0:9>
2401 2402 $ hg log -G --template '{rev} {desc}\n'
2402 2403 o 7 Added tag foo-bar for changeset fc281d8ff18d
2403 2404 |
2404 2405 o 6 merge 5 and 4
2405 2406 |\
2406 2407 | o 5 add another e
2407 2408 | |
2408 2409 o | 4 mv dir/b e
2409 2410 |/
2410 2411 @ 3 mv a b; add d
2411 2412 |
2412 2413 o 2 mv b dir/b
2413 2414 |
2414 2415 o 1 copy a b
2415 2416 |
2416 2417 o 0 add a
2417 2418
2418 2419
2419 2420 A template without trailing newline should do something sane
2420 2421
2421 2422 $ hg log -G -r ::2 --template '{rev} {desc}'
2422 2423 o 2 mv b dir/b
2423 2424 |
2424 2425 o 1 copy a b
2425 2426 |
2426 2427 o 0 add a
2427 2428
2428 2429
2429 2430 Extra newlines must be preserved
2430 2431
2431 2432 $ hg log -G -r ::2 --template '\n{rev} {desc}\n\n'
2432 2433 o
2433 2434 | 2 mv b dir/b
2434 2435 |
2435 2436 o
2436 2437 | 1 copy a b
2437 2438 |
2438 2439 o
2439 2440 0 add a
2440 2441
2441 2442
2442 2443 The almost-empty template should do something sane too ...
2443 2444
2444 2445 $ hg log -G -r ::2 --template '\n'
2445 2446 o
2446 2447 |
2447 2448 o
2448 2449 |
2449 2450 o
2450 2451
2451 2452
2452 2453 issue3772
2453 2454
2454 2455 $ hg log -G -r :null
2455 2456 o changeset: 0:f8035bb17114
2456 2457 | user: test
2457 2458 | date: Thu Jan 01 00:00:00 1970 +0000
2458 2459 | summary: add a
2459 2460 |
2460 2461 o changeset: -1:000000000000
2461 2462 user:
2462 2463 date: Thu Jan 01 00:00:00 1970 +0000
2463 2464
2464 2465 $ hg log -G -r null:null
2465 2466 o changeset: -1:000000000000
2466 2467 user:
2467 2468 date: Thu Jan 01 00:00:00 1970 +0000
2468 2469
2469 2470
2470 2471 should not draw line down to null due to the magic of fullreposet
2471 2472
2472 2473 $ hg log -G -r 'all()' | tail -6
2473 2474 |
2474 2475 o changeset: 0:f8035bb17114
2475 2476 user: test
2476 2477 date: Thu Jan 01 00:00:00 1970 +0000
2477 2478 summary: add a
2478 2479
2479 2480
2480 2481 $ hg log -G -r 'branch(default)' | tail -6
2481 2482 |
2482 2483 o changeset: 0:f8035bb17114
2483 2484 user: test
2484 2485 date: Thu Jan 01 00:00:00 1970 +0000
2485 2486 summary: add a
2486 2487
2487 2488
2488 2489 working-directory revision
2489 2490
2490 2491 $ hg log -G -qr '. + wdir()'
2491 2492 o 2147483647:ffffffffffff
2492 2493 |
2493 2494 @ 3:5918b8d165d1
2494 2495 |
2495 2496 ~
2496 2497
2497 2498 node template with changeset_printer:
2498 2499
2499 2500 $ hg log -Gqr 5:7 --config ui.graphnodetemplate='"{rev}"'
2500 2501 7 7:02dbb8e276b8
2501 2502 |
2502 2503 6 6:fc281d8ff18d
2503 2504 |\
2504 2505 | ~
2505 2506 5 5:99b31f1c2782
2506 2507 |
2507 2508 ~
2508 2509
2509 2510 node template with changeset_templater (shared cache variable):
2510 2511
2511 2512 $ hg log -Gr 5:7 -T '{latesttag % "{rev} {tag}+{distance}"}\n' \
2512 2513 > --config ui.graphnodetemplate='{ifeq(latesttagdistance, 0, "#", graphnode)}'
2513 2514 o 7 foo-bar+1
2514 2515 |
2515 2516 # 6 foo-bar+0
2516 2517 |\
2517 2518 | ~
2518 2519 o 5 null+5
2519 2520 |
2520 2521 ~
2521 2522
2522 2523 label() should just work in node template:
2523 2524
2524 2525 $ hg log -Gqr 7 --config extensions.color= --color=debug \
2525 2526 > --config ui.graphnodetemplate='{label("branch.{branch}", rev)}'
2526 2527 [branch.default|7] [log.node|7:02dbb8e276b8]
2527 2528 |
2528 2529 ~
2529 2530
2530 2531 $ cd ..
2531 2532
2532 2533 change graph edge styling
2533 2534
2534 2535 $ cd repo
2535 2536 $ cat << EOF >> $HGRCPATH
2536 2537 > [experimental]
2537 2538 > graphstyle.parent = |
2538 2539 > graphstyle.grandparent = :
2539 2540 > graphstyle.missing =
2540 2541 > EOF
2541 2542 $ hg log -G -r 'file("a")' -m
2542 2543 @ changeset: 36:08a19a744424
2543 2544 : branch: branch
2544 2545 : tag: tip
2545 2546 : parent: 35:9159c3644c5e
2546 2547 : parent: 35:9159c3644c5e
2547 2548 : user: test
2548 2549 : date: Thu Jan 01 00:00:36 1970 +0000
2549 2550 : summary: (36) buggy merge: identical parents
2550 2551 :
2551 2552 o changeset: 32:d06dffa21a31
2552 2553 |\ parent: 27:886ed638191b
2553 2554 | : parent: 31:621d83e11f67
2554 2555 | : user: test
2555 2556 | : date: Thu Jan 01 00:00:32 1970 +0000
2556 2557 | : summary: (32) expand
2557 2558 | :
2558 2559 o : changeset: 31:621d83e11f67
2559 2560 |\: parent: 21:d42a756af44d
2560 2561 | : parent: 30:6e11cd4b648f
2561 2562 | : user: test
2562 2563 | : date: Thu Jan 01 00:00:31 1970 +0000
2563 2564 | : summary: (31) expand
2564 2565 | :
2565 2566 o : changeset: 30:6e11cd4b648f
2566 2567 |\ \ parent: 28:44ecd0b9ae99
2567 2568 | ~ : parent: 29:cd9bb2be7593
2568 2569 | : user: test
2569 2570 | : date: Thu Jan 01 00:00:30 1970 +0000
2570 2571 | : summary: (30) expand
2571 2572 | /
2572 2573 o : changeset: 28:44ecd0b9ae99
2573 2574 |\ \ parent: 1:6db2ef61d156
2574 2575 | ~ : parent: 26:7f25b6c2f0b9
2575 2576 | : user: test
2576 2577 | : date: Thu Jan 01 00:00:28 1970 +0000
2577 2578 | : summary: (28) merge zero known
2578 2579 | /
2579 2580 o : changeset: 26:7f25b6c2f0b9
2580 2581 |\ \ parent: 18:1aa84d96232a
2581 2582 | | : parent: 25:91da8ed57247
2582 2583 | | : user: test
2583 2584 | | : date: Thu Jan 01 00:00:26 1970 +0000
2584 2585 | | : summary: (26) merge one known; far right
2585 2586 | | :
2586 2587 | o : changeset: 25:91da8ed57247
2587 2588 | |\: parent: 21:d42a756af44d
2588 2589 | | : parent: 24:a9c19a3d96b7
2589 2590 | | : user: test
2590 2591 | | : date: Thu Jan 01 00:00:25 1970 +0000
2591 2592 | | : summary: (25) merge one known; far left
2592 2593 | | :
2593 2594 | o : changeset: 24:a9c19a3d96b7
2594 2595 | |\ \ parent: 0:e6eb3150255d
2595 2596 | | ~ : parent: 23:a01cddf0766d
2596 2597 | | : user: test
2597 2598 | | : date: Thu Jan 01 00:00:24 1970 +0000
2598 2599 | | : summary: (24) merge one known; immediate right
2599 2600 | | /
2600 2601 | o : changeset: 23:a01cddf0766d
2601 2602 | |\ \ parent: 1:6db2ef61d156
2602 2603 | | ~ : parent: 22:e0d9cccacb5d
2603 2604 | | : user: test
2604 2605 | | : date: Thu Jan 01 00:00:23 1970 +0000
2605 2606 | | : summary: (23) merge one known; immediate left
2606 2607 | | /
2607 2608 | o : changeset: 22:e0d9cccacb5d
2608 2609 |/:/ parent: 18:1aa84d96232a
2609 2610 | : parent: 21:d42a756af44d
2610 2611 | : user: test
2611 2612 | : date: Thu Jan 01 00:00:22 1970 +0000
2612 2613 | : summary: (22) merge two known; one far left, one far right
2613 2614 | :
2614 2615 | o changeset: 21:d42a756af44d
2615 2616 | |\ parent: 19:31ddc2c1573b
2616 2617 | | | parent: 20:d30ed6450e32
2617 2618 | | | user: test
2618 2619 | | | date: Thu Jan 01 00:00:21 1970 +0000
2619 2620 | | | summary: (21) expand
2620 2621 | | |
2621 2622 +---o changeset: 20:d30ed6450e32
2622 2623 | | | parent: 0:e6eb3150255d
2623 2624 | | ~ parent: 18:1aa84d96232a
2624 2625 | | user: test
2625 2626 | | date: Thu Jan 01 00:00:20 1970 +0000
2626 2627 | | summary: (20) merge two known; two far right
2627 2628 | |
2628 2629 | o changeset: 19:31ddc2c1573b
2629 2630 | |\ parent: 15:1dda3f72782d
2630 2631 | | | parent: 17:44765d7c06e0
2631 2632 | | | user: test
2632 2633 | | | date: Thu Jan 01 00:00:19 1970 +0000
2633 2634 | | | summary: (19) expand
2634 2635 | | |
2635 2636 o | | changeset: 18:1aa84d96232a
2636 2637 |\| | parent: 1:6db2ef61d156
2637 2638 ~ | | parent: 15:1dda3f72782d
2638 2639 | | user: test
2639 2640 | | date: Thu Jan 01 00:00:18 1970 +0000
2640 2641 | | summary: (18) merge two known; two far left
2641 2642 / /
2642 2643 | o changeset: 17:44765d7c06e0
2643 2644 | |\ parent: 12:86b91144a6e9
2644 2645 | | | parent: 16:3677d192927d
2645 2646 | | | user: test
2646 2647 | | | date: Thu Jan 01 00:00:17 1970 +0000
2647 2648 | | | summary: (17) expand
2648 2649 | | |
2649 2650 | | o changeset: 16:3677d192927d
2650 2651 | | |\ parent: 0:e6eb3150255d
2651 2652 | | ~ ~ parent: 1:6db2ef61d156
2652 2653 | | user: test
2653 2654 | | date: Thu Jan 01 00:00:16 1970 +0000
2654 2655 | | summary: (16) merge two known; one immediate right, one near right
2655 2656 | |
2656 2657 o | changeset: 15:1dda3f72782d
2657 2658 |\ \ parent: 13:22d8966a97e3
2658 2659 | | | parent: 14:8eac370358ef
2659 2660 | | | user: test
2660 2661 | | | date: Thu Jan 01 00:00:15 1970 +0000
2661 2662 | | | summary: (15) expand
2662 2663 | | |
2663 2664 | o | changeset: 14:8eac370358ef
2664 2665 | |\| parent: 0:e6eb3150255d
2665 2666 | ~ | parent: 12:86b91144a6e9
2666 2667 | | user: test
2667 2668 | | date: Thu Jan 01 00:00:14 1970 +0000
2668 2669 | | summary: (14) merge two known; one immediate right, one far right
2669 2670 | /
2670 2671 o | changeset: 13:22d8966a97e3
2671 2672 |\ \ parent: 9:7010c0af0a35
2672 2673 | | | parent: 11:832d76e6bdf2
2673 2674 | | | user: test
2674 2675 | | | date: Thu Jan 01 00:00:13 1970 +0000
2675 2676 | | | summary: (13) expand
2676 2677 | | |
2677 2678 +---o changeset: 12:86b91144a6e9
2678 2679 | | | parent: 1:6db2ef61d156
2679 2680 | | ~ parent: 9:7010c0af0a35
2680 2681 | | user: test
2681 2682 | | date: Thu Jan 01 00:00:12 1970 +0000
2682 2683 | | summary: (12) merge two known; one immediate right, one far left
2683 2684 | |
2684 2685 | o changeset: 11:832d76e6bdf2
2685 2686 | |\ parent: 6:b105a072e251
2686 2687 | | | parent: 10:74c64d036d72
2687 2688 | | | user: test
2688 2689 | | | date: Thu Jan 01 00:00:11 1970 +0000
2689 2690 | | | summary: (11) expand
2690 2691 | | |
2691 2692 | | o changeset: 10:74c64d036d72
2692 2693 | |/| parent: 0:e6eb3150255d
2693 2694 | | ~ parent: 6:b105a072e251
2694 2695 | | user: test
2695 2696 | | date: Thu Jan 01 00:00:10 1970 +0000
2696 2697 | | summary: (10) merge two known; one immediate left, one near right
2697 2698 | |
2698 2699 o | changeset: 9:7010c0af0a35
2699 2700 |\ \ parent: 7:b632bb1b1224
2700 2701 | | | parent: 8:7a0b11f71937
2701 2702 | | | user: test
2702 2703 | | | date: Thu Jan 01 00:00:09 1970 +0000
2703 2704 | | | summary: (9) expand
2704 2705 | | |
2705 2706 | o | changeset: 8:7a0b11f71937
2706 2707 |/| | parent: 0:e6eb3150255d
2707 2708 | ~ | parent: 7:b632bb1b1224
2708 2709 | | user: test
2709 2710 | | date: Thu Jan 01 00:00:08 1970 +0000
2710 2711 | | summary: (8) merge two known; one immediate left, one far right
2711 2712 | /
2712 2713 o | changeset: 7:b632bb1b1224
2713 2714 |\ \ parent: 2:3d9a33b8d1e1
2714 2715 | ~ | parent: 5:4409d547b708
2715 2716 | | user: test
2716 2717 | | date: Thu Jan 01 00:00:07 1970 +0000
2717 2718 | | summary: (7) expand
2718 2719 | /
2719 2720 | o changeset: 6:b105a072e251
2720 2721 |/| parent: 2:3d9a33b8d1e1
2721 2722 | ~ parent: 5:4409d547b708
2722 2723 | user: test
2723 2724 | date: Thu Jan 01 00:00:06 1970 +0000
2724 2725 | summary: (6) merge two known; one immediate left, one far left
2725 2726 |
2726 2727 o changeset: 5:4409d547b708
2727 2728 |\ parent: 3:27eef8ed80b4
2728 2729 | ~ parent: 4:26a8bac39d9f
2729 2730 | user: test
2730 2731 | date: Thu Jan 01 00:00:05 1970 +0000
2731 2732 | summary: (5) expand
2732 2733 |
2733 2734 o changeset: 4:26a8bac39d9f
2734 2735 |\ parent: 1:6db2ef61d156
2735 2736 ~ ~ parent: 3:27eef8ed80b4
2736 2737 user: test
2737 2738 date: Thu Jan 01 00:00:04 1970 +0000
2738 2739 summary: (4) merge two known; one immediate left, one immediate right
2739 2740
2740 2741
2741 2742 Setting HGPLAIN ignores graphmod styling:
2742 2743
2743 2744 $ HGPLAIN=1 hg log -G -r 'file("a")' -m
2744 2745 @ changeset: 36:08a19a744424
2745 2746 | branch: branch
2746 2747 | tag: tip
2747 2748 | parent: 35:9159c3644c5e
2748 2749 | parent: 35:9159c3644c5e
2749 2750 | user: test
2750 2751 | date: Thu Jan 01 00:00:36 1970 +0000
2751 2752 | summary: (36) buggy merge: identical parents
2752 2753 |
2753 2754 o changeset: 32:d06dffa21a31
2754 2755 |\ parent: 27:886ed638191b
2755 2756 | | parent: 31:621d83e11f67
2756 2757 | | user: test
2757 2758 | | date: Thu Jan 01 00:00:32 1970 +0000
2758 2759 | | summary: (32) expand
2759 2760 | |
2760 2761 o | changeset: 31:621d83e11f67
2761 2762 |\| parent: 21:d42a756af44d
2762 2763 | | parent: 30:6e11cd4b648f
2763 2764 | | user: test
2764 2765 | | date: Thu Jan 01 00:00:31 1970 +0000
2765 2766 | | summary: (31) expand
2766 2767 | |
2767 2768 o | changeset: 30:6e11cd4b648f
2768 2769 |\ \ parent: 28:44ecd0b9ae99
2769 2770 | | | parent: 29:cd9bb2be7593
2770 2771 | | | user: test
2771 2772 | | | date: Thu Jan 01 00:00:30 1970 +0000
2772 2773 | | | summary: (30) expand
2773 2774 | | |
2774 2775 o | | changeset: 28:44ecd0b9ae99
2775 2776 |\ \ \ parent: 1:6db2ef61d156
2776 2777 | | | | parent: 26:7f25b6c2f0b9
2777 2778 | | | | user: test
2778 2779 | | | | date: Thu Jan 01 00:00:28 1970 +0000
2779 2780 | | | | summary: (28) merge zero known
2780 2781 | | | |
2781 2782 o | | | changeset: 26:7f25b6c2f0b9
2782 2783 |\ \ \ \ parent: 18:1aa84d96232a
2783 2784 | | | | | parent: 25:91da8ed57247
2784 2785 | | | | | user: test
2785 2786 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
2786 2787 | | | | | summary: (26) merge one known; far right
2787 2788 | | | | |
2788 2789 | o-----+ changeset: 25:91da8ed57247
2789 2790 | | | | | parent: 21:d42a756af44d
2790 2791 | | | | | parent: 24:a9c19a3d96b7
2791 2792 | | | | | user: test
2792 2793 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
2793 2794 | | | | | summary: (25) merge one known; far left
2794 2795 | | | | |
2795 2796 | o | | | changeset: 24:a9c19a3d96b7
2796 2797 | |\ \ \ \ parent: 0:e6eb3150255d
2797 2798 | | | | | | parent: 23:a01cddf0766d
2798 2799 | | | | | | user: test
2799 2800 | | | | | | date: Thu Jan 01 00:00:24 1970 +0000
2800 2801 | | | | | | summary: (24) merge one known; immediate right
2801 2802 | | | | | |
2802 2803 | o---+ | | changeset: 23:a01cddf0766d
2803 2804 | | | | | | parent: 1:6db2ef61d156
2804 2805 | | | | | | parent: 22:e0d9cccacb5d
2805 2806 | | | | | | user: test
2806 2807 | | | | | | date: Thu Jan 01 00:00:23 1970 +0000
2807 2808 | | | | | | summary: (23) merge one known; immediate left
2808 2809 | | | | | |
2809 2810 | o-------+ changeset: 22:e0d9cccacb5d
2810 2811 | | | | | | parent: 18:1aa84d96232a
2811 2812 |/ / / / / parent: 21:d42a756af44d
2812 2813 | | | | | user: test
2813 2814 | | | | | date: Thu Jan 01 00:00:22 1970 +0000
2814 2815 | | | | | summary: (22) merge two known; one far left, one far right
2815 2816 | | | | |
2816 2817 | | | | o changeset: 21:d42a756af44d
2817 2818 | | | | |\ parent: 19:31ddc2c1573b
2818 2819 | | | | | | parent: 20:d30ed6450e32
2819 2820 | | | | | | user: test
2820 2821 | | | | | | date: Thu Jan 01 00:00:21 1970 +0000
2821 2822 | | | | | | summary: (21) expand
2822 2823 | | | | | |
2823 2824 +-+-------o changeset: 20:d30ed6450e32
2824 2825 | | | | | parent: 0:e6eb3150255d
2825 2826 | | | | | parent: 18:1aa84d96232a
2826 2827 | | | | | user: test
2827 2828 | | | | | date: Thu Jan 01 00:00:20 1970 +0000
2828 2829 | | | | | summary: (20) merge two known; two far right
2829 2830 | | | | |
2830 2831 | | | | o changeset: 19:31ddc2c1573b
2831 2832 | | | | |\ parent: 15:1dda3f72782d
2832 2833 | | | | | | parent: 17:44765d7c06e0
2833 2834 | | | | | | user: test
2834 2835 | | | | | | date: Thu Jan 01 00:00:19 1970 +0000
2835 2836 | | | | | | summary: (19) expand
2836 2837 | | | | | |
2837 2838 o---+---+ | changeset: 18:1aa84d96232a
2838 2839 | | | | | parent: 1:6db2ef61d156
2839 2840 / / / / / parent: 15:1dda3f72782d
2840 2841 | | | | | user: test
2841 2842 | | | | | date: Thu Jan 01 00:00:18 1970 +0000
2842 2843 | | | | | summary: (18) merge two known; two far left
2843 2844 | | | | |
2844 2845 | | | | o changeset: 17:44765d7c06e0
2845 2846 | | | | |\ parent: 12:86b91144a6e9
2846 2847 | | | | | | parent: 16:3677d192927d
2847 2848 | | | | | | user: test
2848 2849 | | | | | | date: Thu Jan 01 00:00:17 1970 +0000
2849 2850 | | | | | | summary: (17) expand
2850 2851 | | | | | |
2851 2852 +-+-------o changeset: 16:3677d192927d
2852 2853 | | | | | parent: 0:e6eb3150255d
2853 2854 | | | | | parent: 1:6db2ef61d156
2854 2855 | | | | | user: test
2855 2856 | | | | | date: Thu Jan 01 00:00:16 1970 +0000
2856 2857 | | | | | summary: (16) merge two known; one immediate right, one near right
2857 2858 | | | | |
2858 2859 | | | o | changeset: 15:1dda3f72782d
2859 2860 | | | |\ \ parent: 13:22d8966a97e3
2860 2861 | | | | | | parent: 14:8eac370358ef
2861 2862 | | | | | | user: test
2862 2863 | | | | | | date: Thu Jan 01 00:00:15 1970 +0000
2863 2864 | | | | | | summary: (15) expand
2864 2865 | | | | | |
2865 2866 +-------o | changeset: 14:8eac370358ef
2866 2867 | | | | |/ parent: 0:e6eb3150255d
2867 2868 | | | | | parent: 12:86b91144a6e9
2868 2869 | | | | | user: test
2869 2870 | | | | | date: Thu Jan 01 00:00:14 1970 +0000
2870 2871 | | | | | summary: (14) merge two known; one immediate right, one far right
2871 2872 | | | | |
2872 2873 | | | o | changeset: 13:22d8966a97e3
2873 2874 | | | |\ \ parent: 9:7010c0af0a35
2874 2875 | | | | | | parent: 11:832d76e6bdf2
2875 2876 | | | | | | user: test
2876 2877 | | | | | | date: Thu Jan 01 00:00:13 1970 +0000
2877 2878 | | | | | | summary: (13) expand
2878 2879 | | | | | |
2879 2880 | +---+---o changeset: 12:86b91144a6e9
2880 2881 | | | | | parent: 1:6db2ef61d156
2881 2882 | | | | | parent: 9:7010c0af0a35
2882 2883 | | | | | user: test
2883 2884 | | | | | date: Thu Jan 01 00:00:12 1970 +0000
2884 2885 | | | | | summary: (12) merge two known; one immediate right, one far left
2885 2886 | | | | |
2886 2887 | | | | o changeset: 11:832d76e6bdf2
2887 2888 | | | | |\ parent: 6:b105a072e251
2888 2889 | | | | | | parent: 10:74c64d036d72
2889 2890 | | | | | | user: test
2890 2891 | | | | | | date: Thu Jan 01 00:00:11 1970 +0000
2891 2892 | | | | | | summary: (11) expand
2892 2893 | | | | | |
2893 2894 +---------o changeset: 10:74c64d036d72
2894 2895 | | | | |/ parent: 0:e6eb3150255d
2895 2896 | | | | | parent: 6:b105a072e251
2896 2897 | | | | | user: test
2897 2898 | | | | | date: Thu Jan 01 00:00:10 1970 +0000
2898 2899 | | | | | summary: (10) merge two known; one immediate left, one near right
2899 2900 | | | | |
2900 2901 | | | o | changeset: 9:7010c0af0a35
2901 2902 | | | |\ \ parent: 7:b632bb1b1224
2902 2903 | | | | | | parent: 8:7a0b11f71937
2903 2904 | | | | | | user: test
2904 2905 | | | | | | date: Thu Jan 01 00:00:09 1970 +0000
2905 2906 | | | | | | summary: (9) expand
2906 2907 | | | | | |
2907 2908 +-------o | changeset: 8:7a0b11f71937
2908 2909 | | | |/ / parent: 0:e6eb3150255d
2909 2910 | | | | | parent: 7:b632bb1b1224
2910 2911 | | | | | user: test
2911 2912 | | | | | date: Thu Jan 01 00:00:08 1970 +0000
2912 2913 | | | | | summary: (8) merge two known; one immediate left, one far right
2913 2914 | | | | |
2914 2915 | | | o | changeset: 7:b632bb1b1224
2915 2916 | | | |\ \ parent: 2:3d9a33b8d1e1
2916 2917 | | | | | | parent: 5:4409d547b708
2917 2918 | | | | | | user: test
2918 2919 | | | | | | date: Thu Jan 01 00:00:07 1970 +0000
2919 2920 | | | | | | summary: (7) expand
2920 2921 | | | | | |
2921 2922 | | | +---o changeset: 6:b105a072e251
2922 2923 | | | | |/ parent: 2:3d9a33b8d1e1
2923 2924 | | | | | parent: 5:4409d547b708
2924 2925 | | | | | user: test
2925 2926 | | | | | date: Thu Jan 01 00:00:06 1970 +0000
2926 2927 | | | | | summary: (6) merge two known; one immediate left, one far left
2927 2928 | | | | |
2928 2929 | | | o | changeset: 5:4409d547b708
2929 2930 | | | |\ \ parent: 3:27eef8ed80b4
2930 2931 | | | | | | parent: 4:26a8bac39d9f
2931 2932 | | | | | | user: test
2932 2933 | | | | | | date: Thu Jan 01 00:00:05 1970 +0000
2933 2934 | | | | | | summary: (5) expand
2934 2935 | | | | | |
2935 2936 | +---o | | changeset: 4:26a8bac39d9f
2936 2937 | | | |/ / parent: 1:6db2ef61d156
2937 2938 | | | | | parent: 3:27eef8ed80b4
2938 2939 | | | | | user: test
2939 2940 | | | | | date: Thu Jan 01 00:00:04 1970 +0000
2940 2941 | | | | | summary: (4) merge two known; one immediate left, one immediate right
2941 2942 | | | | |
2942 2943
2943 2944 .. unless HGPLAINEXCEPT=graph is set:
2944 2945
2945 2946 $ HGPLAIN=1 HGPLAINEXCEPT=graph hg log -G -r 'file("a")' -m
2946 2947 @ changeset: 36:08a19a744424
2947 2948 : branch: branch
2948 2949 : tag: tip
2949 2950 : parent: 35:9159c3644c5e
2950 2951 : parent: 35:9159c3644c5e
2951 2952 : user: test
2952 2953 : date: Thu Jan 01 00:00:36 1970 +0000
2953 2954 : summary: (36) buggy merge: identical parents
2954 2955 :
2955 2956 o changeset: 32:d06dffa21a31
2956 2957 |\ parent: 27:886ed638191b
2957 2958 | : parent: 31:621d83e11f67
2958 2959 | : user: test
2959 2960 | : date: Thu Jan 01 00:00:32 1970 +0000
2960 2961 | : summary: (32) expand
2961 2962 | :
2962 2963 o : changeset: 31:621d83e11f67
2963 2964 |\: parent: 21:d42a756af44d
2964 2965 | : parent: 30:6e11cd4b648f
2965 2966 | : user: test
2966 2967 | : date: Thu Jan 01 00:00:31 1970 +0000
2967 2968 | : summary: (31) expand
2968 2969 | :
2969 2970 o : changeset: 30:6e11cd4b648f
2970 2971 |\ \ parent: 28:44ecd0b9ae99
2971 2972 | ~ : parent: 29:cd9bb2be7593
2972 2973 | : user: test
2973 2974 | : date: Thu Jan 01 00:00:30 1970 +0000
2974 2975 | : summary: (30) expand
2975 2976 | /
2976 2977 o : changeset: 28:44ecd0b9ae99
2977 2978 |\ \ parent: 1:6db2ef61d156
2978 2979 | ~ : parent: 26:7f25b6c2f0b9
2979 2980 | : user: test
2980 2981 | : date: Thu Jan 01 00:00:28 1970 +0000
2981 2982 | : summary: (28) merge zero known
2982 2983 | /
2983 2984 o : changeset: 26:7f25b6c2f0b9
2984 2985 |\ \ parent: 18:1aa84d96232a
2985 2986 | | : parent: 25:91da8ed57247
2986 2987 | | : user: test
2987 2988 | | : date: Thu Jan 01 00:00:26 1970 +0000
2988 2989 | | : summary: (26) merge one known; far right
2989 2990 | | :
2990 2991 | o : changeset: 25:91da8ed57247
2991 2992 | |\: parent: 21:d42a756af44d
2992 2993 | | : parent: 24:a9c19a3d96b7
2993 2994 | | : user: test
2994 2995 | | : date: Thu Jan 01 00:00:25 1970 +0000
2995 2996 | | : summary: (25) merge one known; far left
2996 2997 | | :
2997 2998 | o : changeset: 24:a9c19a3d96b7
2998 2999 | |\ \ parent: 0:e6eb3150255d
2999 3000 | | ~ : parent: 23:a01cddf0766d
3000 3001 | | : user: test
3001 3002 | | : date: Thu Jan 01 00:00:24 1970 +0000
3002 3003 | | : summary: (24) merge one known; immediate right
3003 3004 | | /
3004 3005 | o : changeset: 23:a01cddf0766d
3005 3006 | |\ \ parent: 1:6db2ef61d156
3006 3007 | | ~ : parent: 22:e0d9cccacb5d
3007 3008 | | : user: test
3008 3009 | | : date: Thu Jan 01 00:00:23 1970 +0000
3009 3010 | | : summary: (23) merge one known; immediate left
3010 3011 | | /
3011 3012 | o : changeset: 22:e0d9cccacb5d
3012 3013 |/:/ parent: 18:1aa84d96232a
3013 3014 | : parent: 21:d42a756af44d
3014 3015 | : user: test
3015 3016 | : date: Thu Jan 01 00:00:22 1970 +0000
3016 3017 | : summary: (22) merge two known; one far left, one far right
3017 3018 | :
3018 3019 | o changeset: 21:d42a756af44d
3019 3020 | |\ parent: 19:31ddc2c1573b
3020 3021 | | | parent: 20:d30ed6450e32
3021 3022 | | | user: test
3022 3023 | | | date: Thu Jan 01 00:00:21 1970 +0000
3023 3024 | | | summary: (21) expand
3024 3025 | | |
3025 3026 +---o changeset: 20:d30ed6450e32
3026 3027 | | | parent: 0:e6eb3150255d
3027 3028 | | ~ parent: 18:1aa84d96232a
3028 3029 | | user: test
3029 3030 | | date: Thu Jan 01 00:00:20 1970 +0000
3030 3031 | | summary: (20) merge two known; two far right
3031 3032 | |
3032 3033 | o changeset: 19:31ddc2c1573b
3033 3034 | |\ parent: 15:1dda3f72782d
3034 3035 | | | parent: 17:44765d7c06e0
3035 3036 | | | user: test
3036 3037 | | | date: Thu Jan 01 00:00:19 1970 +0000
3037 3038 | | | summary: (19) expand
3038 3039 | | |
3039 3040 o | | changeset: 18:1aa84d96232a
3040 3041 |\| | parent: 1:6db2ef61d156
3041 3042 ~ | | parent: 15:1dda3f72782d
3042 3043 | | user: test
3043 3044 | | date: Thu Jan 01 00:00:18 1970 +0000
3044 3045 | | summary: (18) merge two known; two far left
3045 3046 / /
3046 3047 | o changeset: 17:44765d7c06e0
3047 3048 | |\ parent: 12:86b91144a6e9
3048 3049 | | | parent: 16:3677d192927d
3049 3050 | | | user: test
3050 3051 | | | date: Thu Jan 01 00:00:17 1970 +0000
3051 3052 | | | summary: (17) expand
3052 3053 | | |
3053 3054 | | o changeset: 16:3677d192927d
3054 3055 | | |\ parent: 0:e6eb3150255d
3055 3056 | | ~ ~ parent: 1:6db2ef61d156
3056 3057 | | user: test
3057 3058 | | date: Thu Jan 01 00:00:16 1970 +0000
3058 3059 | | summary: (16) merge two known; one immediate right, one near right
3059 3060 | |
3060 3061 o | changeset: 15:1dda3f72782d
3061 3062 |\ \ parent: 13:22d8966a97e3
3062 3063 | | | parent: 14:8eac370358ef
3063 3064 | | | user: test
3064 3065 | | | date: Thu Jan 01 00:00:15 1970 +0000
3065 3066 | | | summary: (15) expand
3066 3067 | | |
3067 3068 | o | changeset: 14:8eac370358ef
3068 3069 | |\| parent: 0:e6eb3150255d
3069 3070 | ~ | parent: 12:86b91144a6e9
3070 3071 | | user: test
3071 3072 | | date: Thu Jan 01 00:00:14 1970 +0000
3072 3073 | | summary: (14) merge two known; one immediate right, one far right
3073 3074 | /
3074 3075 o | changeset: 13:22d8966a97e3
3075 3076 |\ \ parent: 9:7010c0af0a35
3076 3077 | | | parent: 11:832d76e6bdf2
3077 3078 | | | user: test
3078 3079 | | | date: Thu Jan 01 00:00:13 1970 +0000
3079 3080 | | | summary: (13) expand
3080 3081 | | |
3081 3082 +---o changeset: 12:86b91144a6e9
3082 3083 | | | parent: 1:6db2ef61d156
3083 3084 | | ~ parent: 9:7010c0af0a35
3084 3085 | | user: test
3085 3086 | | date: Thu Jan 01 00:00:12 1970 +0000
3086 3087 | | summary: (12) merge two known; one immediate right, one far left
3087 3088 | |
3088 3089 | o changeset: 11:832d76e6bdf2
3089 3090 | |\ parent: 6:b105a072e251
3090 3091 | | | parent: 10:74c64d036d72
3091 3092 | | | user: test
3092 3093 | | | date: Thu Jan 01 00:00:11 1970 +0000
3093 3094 | | | summary: (11) expand
3094 3095 | | |
3095 3096 | | o changeset: 10:74c64d036d72
3096 3097 | |/| parent: 0:e6eb3150255d
3097 3098 | | ~ parent: 6:b105a072e251
3098 3099 | | user: test
3099 3100 | | date: Thu Jan 01 00:00:10 1970 +0000
3100 3101 | | summary: (10) merge two known; one immediate left, one near right
3101 3102 | |
3102 3103 o | changeset: 9:7010c0af0a35
3103 3104 |\ \ parent: 7:b632bb1b1224
3104 3105 | | | parent: 8:7a0b11f71937
3105 3106 | | | user: test
3106 3107 | | | date: Thu Jan 01 00:00:09 1970 +0000
3107 3108 | | | summary: (9) expand
3108 3109 | | |
3109 3110 | o | changeset: 8:7a0b11f71937
3110 3111 |/| | parent: 0:e6eb3150255d
3111 3112 | ~ | parent: 7:b632bb1b1224
3112 3113 | | user: test
3113 3114 | | date: Thu Jan 01 00:00:08 1970 +0000
3114 3115 | | summary: (8) merge two known; one immediate left, one far right
3115 3116 | /
3116 3117 o | changeset: 7:b632bb1b1224
3117 3118 |\ \ parent: 2:3d9a33b8d1e1
3118 3119 | ~ | parent: 5:4409d547b708
3119 3120 | | user: test
3120 3121 | | date: Thu Jan 01 00:00:07 1970 +0000
3121 3122 | | summary: (7) expand
3122 3123 | /
3123 3124 | o changeset: 6:b105a072e251
3124 3125 |/| parent: 2:3d9a33b8d1e1
3125 3126 | ~ parent: 5:4409d547b708
3126 3127 | user: test
3127 3128 | date: Thu Jan 01 00:00:06 1970 +0000
3128 3129 | summary: (6) merge two known; one immediate left, one far left
3129 3130 |
3130 3131 o changeset: 5:4409d547b708
3131 3132 |\ parent: 3:27eef8ed80b4
3132 3133 | ~ parent: 4:26a8bac39d9f
3133 3134 | user: test
3134 3135 | date: Thu Jan 01 00:00:05 1970 +0000
3135 3136 | summary: (5) expand
3136 3137 |
3137 3138 o changeset: 4:26a8bac39d9f
3138 3139 |\ parent: 1:6db2ef61d156
3139 3140 ~ ~ parent: 3:27eef8ed80b4
3140 3141 user: test
3141 3142 date: Thu Jan 01 00:00:04 1970 +0000
3142 3143 summary: (4) merge two known; one immediate left, one immediate right
3143 3144
3144 3145 Draw only part of a grandparent line differently with "<N><char>"; only the
3145 3146 last N lines (for positive N) or everything but the first N lines (for
3146 3147 negative N) along the current node use the style, the rest of the edge uses
3147 3148 the parent edge styling.
3148 3149
3149 3150 Last 3 lines:
3150 3151
3151 3152 $ cat << EOF >> $HGRCPATH
3152 3153 > [experimental]
3153 3154 > graphstyle.parent = !
3154 3155 > graphstyle.grandparent = 3.
3155 3156 > graphstyle.missing =
3156 3157 > EOF
3157 3158 $ hg log -G -r '36:18 & file("a")' -m
3158 3159 @ changeset: 36:08a19a744424
3159 3160 ! branch: branch
3160 3161 ! tag: tip
3161 3162 ! parent: 35:9159c3644c5e
3162 3163 ! parent: 35:9159c3644c5e
3163 3164 ! user: test
3164 3165 . date: Thu Jan 01 00:00:36 1970 +0000
3165 3166 . summary: (36) buggy merge: identical parents
3166 3167 .
3167 3168 o changeset: 32:d06dffa21a31
3168 3169 !\ parent: 27:886ed638191b
3169 3170 ! ! parent: 31:621d83e11f67
3170 3171 ! ! user: test
3171 3172 ! . date: Thu Jan 01 00:00:32 1970 +0000
3172 3173 ! . summary: (32) expand
3173 3174 ! .
3174 3175 o ! changeset: 31:621d83e11f67
3175 3176 !\! parent: 21:d42a756af44d
3176 3177 ! ! parent: 30:6e11cd4b648f
3177 3178 ! ! user: test
3178 3179 ! ! date: Thu Jan 01 00:00:31 1970 +0000
3179 3180 ! ! summary: (31) expand
3180 3181 ! !
3181 3182 o ! changeset: 30:6e11cd4b648f
3182 3183 !\ \ parent: 28:44ecd0b9ae99
3183 3184 ! ~ ! parent: 29:cd9bb2be7593
3184 3185 ! ! user: test
3185 3186 ! ! date: Thu Jan 01 00:00:30 1970 +0000
3186 3187 ! ! summary: (30) expand
3187 3188 ! /
3188 3189 o ! changeset: 28:44ecd0b9ae99
3189 3190 !\ \ parent: 1:6db2ef61d156
3190 3191 ! ~ ! parent: 26:7f25b6c2f0b9
3191 3192 ! ! user: test
3192 3193 ! ! date: Thu Jan 01 00:00:28 1970 +0000
3193 3194 ! ! summary: (28) merge zero known
3194 3195 ! /
3195 3196 o ! changeset: 26:7f25b6c2f0b9
3196 3197 !\ \ parent: 18:1aa84d96232a
3197 3198 ! ! ! parent: 25:91da8ed57247
3198 3199 ! ! ! user: test
3199 3200 ! ! ! date: Thu Jan 01 00:00:26 1970 +0000
3200 3201 ! ! ! summary: (26) merge one known; far right
3201 3202 ! ! !
3202 3203 ! o ! changeset: 25:91da8ed57247
3203 3204 ! !\! parent: 21:d42a756af44d
3204 3205 ! ! ! parent: 24:a9c19a3d96b7
3205 3206 ! ! ! user: test
3206 3207 ! ! ! date: Thu Jan 01 00:00:25 1970 +0000
3207 3208 ! ! ! summary: (25) merge one known; far left
3208 3209 ! ! !
3209 3210 ! o ! changeset: 24:a9c19a3d96b7
3210 3211 ! !\ \ parent: 0:e6eb3150255d
3211 3212 ! ! ~ ! parent: 23:a01cddf0766d
3212 3213 ! ! ! user: test
3213 3214 ! ! ! date: Thu Jan 01 00:00:24 1970 +0000
3214 3215 ! ! ! summary: (24) merge one known; immediate right
3215 3216 ! ! /
3216 3217 ! o ! changeset: 23:a01cddf0766d
3217 3218 ! !\ \ parent: 1:6db2ef61d156
3218 3219 ! ! ~ ! parent: 22:e0d9cccacb5d
3219 3220 ! ! ! user: test
3220 3221 ! ! ! date: Thu Jan 01 00:00:23 1970 +0000
3221 3222 ! ! ! summary: (23) merge one known; immediate left
3222 3223 ! ! /
3223 3224 ! o ! changeset: 22:e0d9cccacb5d
3224 3225 !/!/ parent: 18:1aa84d96232a
3225 3226 ! ! parent: 21:d42a756af44d
3226 3227 ! ! user: test
3227 3228 ! ! date: Thu Jan 01 00:00:22 1970 +0000
3228 3229 ! ! summary: (22) merge two known; one far left, one far right
3229 3230 ! !
3230 3231 ! o changeset: 21:d42a756af44d
3231 3232 ! !\ parent: 19:31ddc2c1573b
3232 3233 ! ! ! parent: 20:d30ed6450e32
3233 3234 ! ! ! user: test
3234 3235 ! ! ! date: Thu Jan 01 00:00:21 1970 +0000
3235 3236 ! ! ! summary: (21) expand
3236 3237 ! ! !
3237 3238 +---o changeset: 20:d30ed6450e32
3238 3239 ! ! | parent: 0:e6eb3150255d
3239 3240 ! ! ~ parent: 18:1aa84d96232a
3240 3241 ! ! user: test
3241 3242 ! ! date: Thu Jan 01 00:00:20 1970 +0000
3242 3243 ! ! summary: (20) merge two known; two far right
3243 3244 ! !
3244 3245 ! o changeset: 19:31ddc2c1573b
3245 3246 ! |\ parent: 15:1dda3f72782d
3246 3247 ! ~ ~ parent: 17:44765d7c06e0
3247 3248 ! user: test
3248 3249 ! date: Thu Jan 01 00:00:19 1970 +0000
3249 3250 ! summary: (19) expand
3250 3251 !
3251 3252 o changeset: 18:1aa84d96232a
3252 3253 |\ parent: 1:6db2ef61d156
3253 3254 ~ ~ parent: 15:1dda3f72782d
3254 3255 user: test
3255 3256 date: Thu Jan 01 00:00:18 1970 +0000
3256 3257 summary: (18) merge two known; two far left
3257 3258
3258 3259 All but the first 3 lines:
3259 3260
3260 3261 $ cat << EOF >> $HGRCPATH
3261 3262 > [experimental]
3262 3263 > graphstyle.parent = !
3263 3264 > graphstyle.grandparent = -3.
3264 3265 > graphstyle.missing =
3265 3266 > EOF
3266 3267 $ hg log -G -r '36:18 & file("a")' -m
3267 3268 @ changeset: 36:08a19a744424
3268 3269 ! branch: branch
3269 3270 ! tag: tip
3270 3271 . parent: 35:9159c3644c5e
3271 3272 . parent: 35:9159c3644c5e
3272 3273 . user: test
3273 3274 . date: Thu Jan 01 00:00:36 1970 +0000
3274 3275 . summary: (36) buggy merge: identical parents
3275 3276 .
3276 3277 o changeset: 32:d06dffa21a31
3277 3278 !\ parent: 27:886ed638191b
3278 3279 ! ! parent: 31:621d83e11f67
3279 3280 ! . user: test
3280 3281 ! . date: Thu Jan 01 00:00:32 1970 +0000
3281 3282 ! . summary: (32) expand
3282 3283 ! .
3283 3284 o ! changeset: 31:621d83e11f67
3284 3285 !\! parent: 21:d42a756af44d
3285 3286 ! ! parent: 30:6e11cd4b648f
3286 3287 ! ! user: test
3287 3288 ! ! date: Thu Jan 01 00:00:31 1970 +0000
3288 3289 ! ! summary: (31) expand
3289 3290 ! !
3290 3291 o ! changeset: 30:6e11cd4b648f
3291 3292 !\ \ parent: 28:44ecd0b9ae99
3292 3293 ! ~ ! parent: 29:cd9bb2be7593
3293 3294 ! ! user: test
3294 3295 ! ! date: Thu Jan 01 00:00:30 1970 +0000
3295 3296 ! ! summary: (30) expand
3296 3297 ! /
3297 3298 o ! changeset: 28:44ecd0b9ae99
3298 3299 !\ \ parent: 1:6db2ef61d156
3299 3300 ! ~ ! parent: 26:7f25b6c2f0b9
3300 3301 ! ! user: test
3301 3302 ! ! date: Thu Jan 01 00:00:28 1970 +0000
3302 3303 ! ! summary: (28) merge zero known
3303 3304 ! /
3304 3305 o ! changeset: 26:7f25b6c2f0b9
3305 3306 !\ \ parent: 18:1aa84d96232a
3306 3307 ! ! ! parent: 25:91da8ed57247
3307 3308 ! ! ! user: test
3308 3309 ! ! ! date: Thu Jan 01 00:00:26 1970 +0000
3309 3310 ! ! ! summary: (26) merge one known; far right
3310 3311 ! ! !
3311 3312 ! o ! changeset: 25:91da8ed57247
3312 3313 ! !\! parent: 21:d42a756af44d
3313 3314 ! ! ! parent: 24:a9c19a3d96b7
3314 3315 ! ! ! user: test
3315 3316 ! ! ! date: Thu Jan 01 00:00:25 1970 +0000
3316 3317 ! ! ! summary: (25) merge one known; far left
3317 3318 ! ! !
3318 3319 ! o ! changeset: 24:a9c19a3d96b7
3319 3320 ! !\ \ parent: 0:e6eb3150255d
3320 3321 ! ! ~ ! parent: 23:a01cddf0766d
3321 3322 ! ! ! user: test
3322 3323 ! ! ! date: Thu Jan 01 00:00:24 1970 +0000
3323 3324 ! ! ! summary: (24) merge one known; immediate right
3324 3325 ! ! /
3325 3326 ! o ! changeset: 23:a01cddf0766d
3326 3327 ! !\ \ parent: 1:6db2ef61d156
3327 3328 ! ! ~ ! parent: 22:e0d9cccacb5d
3328 3329 ! ! ! user: test
3329 3330 ! ! ! date: Thu Jan 01 00:00:23 1970 +0000
3330 3331 ! ! ! summary: (23) merge one known; immediate left
3331 3332 ! ! /
3332 3333 ! o ! changeset: 22:e0d9cccacb5d
3333 3334 !/!/ parent: 18:1aa84d96232a
3334 3335 ! ! parent: 21:d42a756af44d
3335 3336 ! ! user: test
3336 3337 ! ! date: Thu Jan 01 00:00:22 1970 +0000
3337 3338 ! ! summary: (22) merge two known; one far left, one far right
3338 3339 ! !
3339 3340 ! o changeset: 21:d42a756af44d
3340 3341 ! !\ parent: 19:31ddc2c1573b
3341 3342 ! ! ! parent: 20:d30ed6450e32
3342 3343 ! ! ! user: test
3343 3344 ! ! ! date: Thu Jan 01 00:00:21 1970 +0000
3344 3345 ! ! ! summary: (21) expand
3345 3346 ! ! !
3346 3347 +---o changeset: 20:d30ed6450e32
3347 3348 ! ! | parent: 0:e6eb3150255d
3348 3349 ! ! ~ parent: 18:1aa84d96232a
3349 3350 ! ! user: test
3350 3351 ! ! date: Thu Jan 01 00:00:20 1970 +0000
3351 3352 ! ! summary: (20) merge two known; two far right
3352 3353 ! !
3353 3354 ! o changeset: 19:31ddc2c1573b
3354 3355 ! |\ parent: 15:1dda3f72782d
3355 3356 ! ~ ~ parent: 17:44765d7c06e0
3356 3357 ! user: test
3357 3358 ! date: Thu Jan 01 00:00:19 1970 +0000
3358 3359 ! summary: (19) expand
3359 3360 !
3360 3361 o changeset: 18:1aa84d96232a
3361 3362 |\ parent: 1:6db2ef61d156
3362 3363 ~ ~ parent: 15:1dda3f72782d
3363 3364 user: test
3364 3365 date: Thu Jan 01 00:00:18 1970 +0000
3365 3366 summary: (18) merge two known; two far left
3366 3367
3367 3368 $ cd ..
3368 3369
3369 3370 Change graph shorten, test better with graphstyle.missing not none
3370 3371
3371 3372 $ cd repo
3372 3373 $ cat << EOF >> $HGRCPATH
3373 3374 > [experimental]
3374 3375 > graphstyle.parent = |
3375 3376 > graphstyle.grandparent = :
3376 3377 > graphstyle.missing = '
3377 3378 > graphshorten = true
3378 3379 > EOF
3379 3380 $ hg log -G -r 'file("a")' -m -T '{rev} {desc}'
3380 3381 @ 36 (36) buggy merge: identical parents
3381 3382 o 32 (32) expand
3382 3383 |\
3383 3384 o : 31 (31) expand
3384 3385 |\:
3385 3386 o : 30 (30) expand
3386 3387 |\ \
3387 3388 o \ \ 28 (28) merge zero known
3388 3389 |\ \ \
3389 3390 o \ \ \ 26 (26) merge one known; far right
3390 3391 |\ \ \ \
3391 3392 | o-----+ 25 (25) merge one known; far left
3392 3393 | o ' ' : 24 (24) merge one known; immediate right
3393 3394 | |\ \ \ \
3394 3395 | o---+ ' : 23 (23) merge one known; immediate left
3395 3396 | o-------+ 22 (22) merge two known; one far left, one far right
3396 3397 |/ / / / /
3397 3398 | ' ' ' o 21 (21) expand
3398 3399 | ' ' ' |\
3399 3400 +-+-------o 20 (20) merge two known; two far right
3400 3401 | ' ' ' o 19 (19) expand
3401 3402 | ' ' ' |\
3402 3403 o---+---+ | 18 (18) merge two known; two far left
3403 3404 / / / / /
3404 3405 ' ' ' | o 17 (17) expand
3405 3406 ' ' ' | |\
3406 3407 +-+-------o 16 (16) merge two known; one immediate right, one near right
3407 3408 ' ' ' o | 15 (15) expand
3408 3409 ' ' ' |\ \
3409 3410 +-------o | 14 (14) merge two known; one immediate right, one far right
3410 3411 ' ' ' | |/
3411 3412 ' ' ' o | 13 (13) expand
3412 3413 ' ' ' |\ \
3413 3414 ' +---+---o 12 (12) merge two known; one immediate right, one far left
3414 3415 ' ' ' | o 11 (11) expand
3415 3416 ' ' ' | |\
3416 3417 +---------o 10 (10) merge two known; one immediate left, one near right
3417 3418 ' ' ' | |/
3418 3419 ' ' ' o | 9 (9) expand
3419 3420 ' ' ' |\ \
3420 3421 +-------o | 8 (8) merge two known; one immediate left, one far right
3421 3422 ' ' ' |/ /
3422 3423 ' ' ' o | 7 (7) expand
3423 3424 ' ' ' |\ \
3424 3425 ' ' ' +---o 6 (6) merge two known; one immediate left, one far left
3425 3426 ' ' ' | '/
3426 3427 ' ' ' o ' 5 (5) expand
3427 3428 ' ' ' |\ \
3428 3429 ' +---o ' ' 4 (4) merge two known; one immediate left, one immediate right
3429 3430 ' ' ' '/ /
3430 3431
3431 3432 behavior with newlines
3432 3433
3433 3434 $ hg log -G -r ::2 -T '{rev} {desc}'
3434 3435 o 2 (2) collapse
3435 3436 o 1 (1) collapse
3436 3437 o 0 (0) root
3437 3438
3438 3439 $ hg log -G -r ::2 -T '{rev} {desc}\n'
3439 3440 o 2 (2) collapse
3440 3441 o 1 (1) collapse
3441 3442 o 0 (0) root
3442 3443
3443 3444 $ hg log -G -r ::2 -T '{rev} {desc}\n\n'
3444 3445 o 2 (2) collapse
3445 3446 |
3446 3447 o 1 (1) collapse
3447 3448 |
3448 3449 o 0 (0) root
3449 3450
3450 3451
3451 3452 $ hg log -G -r ::2 -T '\n{rev} {desc}'
3452 3453 o
3453 3454 | 2 (2) collapse
3454 3455 o
3455 3456 | 1 (1) collapse
3456 3457 o
3457 3458 0 (0) root
3458 3459
3459 3460 $ hg log -G -r ::2 -T '{rev} {desc}\n\n\n'
3460 3461 o 2 (2) collapse
3461 3462 |
3462 3463 |
3463 3464 o 1 (1) collapse
3464 3465 |
3465 3466 |
3466 3467 o 0 (0) root
3467 3468
3468 3469
3469 3470 $ cd ..
3470 3471
3471 3472 When inserting extra line nodes to handle more than 2 parents, ensure that
3472 3473 the right node styles are used (issue5174):
3473 3474
3474 3475 $ hg init repo-issue5174
3475 3476 $ cd repo-issue5174
3476 3477 $ echo a > f0
3477 3478 $ hg ci -Aqm 0
3478 3479 $ echo a > f1
3479 3480 $ hg ci -Aqm 1
3480 3481 $ echo a > f2
3481 3482 $ hg ci -Aqm 2
3482 3483 $ hg co ".^"
3483 3484 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
3484 3485 $ echo a > f3
3485 3486 $ hg ci -Aqm 3
3486 3487 $ hg co ".^^"
3487 3488 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
3488 3489 $ echo a > f4
3489 3490 $ hg ci -Aqm 4
3490 3491 $ hg merge -r 2
3491 3492 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
3492 3493 (branch merge, don't forget to commit)
3493 3494 $ hg ci -qm 5
3494 3495 $ hg merge -r 3
3495 3496 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
3496 3497 (branch merge, don't forget to commit)
3497 3498 $ hg ci -qm 6
3498 3499 $ hg log -G -r '0 | 1 | 2 | 6'
3499 3500 @ changeset: 6:851fe89689ad
3500 3501 :\ tag: tip
3501 3502 : : parent: 5:4f1e3cf15f5d
3502 3503 : : parent: 3:b74ba7084d2d
3503 3504 : : user: test
3504 3505 : : date: Thu Jan 01 00:00:00 1970 +0000
3505 3506 : : summary: 6
3506 3507 : :
3507 3508 : \
3508 3509 : :\
3509 3510 : o : changeset: 2:3e6599df4cce
3510 3511 : :/ user: test
3511 3512 : : date: Thu Jan 01 00:00:00 1970 +0000
3512 3513 : : summary: 2
3513 3514 : :
3514 3515 : o changeset: 1:bd9a55143933
3515 3516 :/ user: test
3516 3517 : date: Thu Jan 01 00:00:00 1970 +0000
3517 3518 : summary: 1
3518 3519 :
3519 3520 o changeset: 0:870a5edc339c
3520 3521 user: test
3521 3522 date: Thu Jan 01 00:00:00 1970 +0000
3522 3523 summary: 0
3523 3524
3524 3525
3525 3526 $ cd ..
3526 3527
3527 3528 Multiple roots (issue5440):
3528 3529
3529 3530 $ hg init multiroots
3530 3531 $ cd multiroots
3531 3532 $ cat <<EOF > .hg/hgrc
3532 3533 > [ui]
3533 3534 > logtemplate = '{rev} {desc}\n\n'
3534 3535 > EOF
3535 3536
3536 3537 $ touch foo
3537 3538 $ hg ci -Aqm foo
3538 3539 $ hg co -q null
3539 3540 $ touch bar
3540 3541 $ hg ci -Aqm bar
3541 3542
3542 3543 $ hg log -Gr null:
3543 3544 @ 1 bar
3544 3545 |
3545 3546 | o 0 foo
3546 3547 |/
3547 3548 o -1
3548 3549
3549 3550 $ hg log -Gr null+0
3550 3551 o 0 foo
3551 3552 |
3552 3553 o -1
3553 3554
3554 3555 $ hg log -Gr null+1
3555 3556 @ 1 bar
3556 3557 |
3557 3558 o -1
3558 3559
3559 3560
3560 3561 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now