##// END OF EJS Templates
log: add bookmark option to "hg log"...
Sebastien Boisvert -
r46512:0aa118f1 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,127 b''
1 Test 'hg log' with a bookmark
2
3
4 Create the repository
5
6 $ hg init Test-D8973
7 $ cd Test-D8973
8 $ echo "bar" > foo.txt
9 $ hg add foo.txt
10 $ hg commit -m "Add foo in 'default'"
11
12
13 Add a bookmark for topic X
14
15 $ hg branch -f sebhtml
16 marked working directory as branch sebhtml
17 (branches are permanent and global, did you want a bookmark?)
18
19 $ hg bookmark sebhtml/99991-topic-X
20 $ hg up sebhtml/99991-topic-X
21 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
22
23 $ echo "X" > x.txt
24 $ hg add x.txt
25 $ hg commit -m "Add x.txt in 'sebhtml/99991-topic-X'"
26
27 $ hg log -B sebhtml/99991-topic-X
28 changeset: 1:29f39dea9bf9
29 branch: sebhtml
30 bookmark: sebhtml/99991-topic-X
31 tag: tip
32 user: test
33 date: Thu Jan 01 00:00:00 1970 +0000
34 summary: Add x.txt in 'sebhtml/99991-topic-X'
35
36
37 Add a bookmark for topic Y
38
39 $ hg update default
40 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
41 (leaving bookmark sebhtml/99991-topic-X)
42
43 $ echo "Y" > y.txt
44 $ hg add y.txt
45 $ hg branch -f sebhtml
46 marked working directory as branch sebhtml
47 $ hg bookmark sebhtml/99992-topic-Y
48 $ hg up sebhtml/99992-topic-Y
49 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 $ hg commit -m "Add y.txt in 'sebhtml/99992-topic-Y'"
51 created new head
52
53 $ hg log -B sebhtml/99992-topic-Y
54 changeset: 2:11df7969cf8d
55 branch: sebhtml
56 bookmark: sebhtml/99992-topic-Y
57 tag: tip
58 parent: 0:eaea25376a59
59 user: test
60 date: Thu Jan 01 00:00:00 1970 +0000
61 summary: Add y.txt in 'sebhtml/99992-topic-Y'
62
63
64 The log of topic Y does not interfere with the log of topic X
65
66 $ hg log -B sebhtml/99991-topic-X
67 changeset: 1:29f39dea9bf9
68 branch: sebhtml
69 bookmark: sebhtml/99991-topic-X
70 user: test
71 date: Thu Jan 01 00:00:00 1970 +0000
72 summary: Add x.txt in 'sebhtml/99991-topic-X'
73
74
75 Merge topics Y and X in the default branch
76
77 $ hg update default
78 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
79 (leaving bookmark sebhtml/99992-topic-Y)
80
81 $ hg bookmark
82 sebhtml/99991-topic-X 1:29f39dea9bf9
83 sebhtml/99992-topic-Y 2:11df7969cf8d
84
85 $ hg merge sebhtml/99992-topic-Y
86 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
87 (branch merge, don't forget to commit)
88
89 $ hg commit -m "Merge branch 'sebhtml/99992-topic-Y' into 'default'"
90
91 $ hg update default
92 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
93
94 $ hg merge sebhtml/99991-topic-X
95 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
96 (branch merge, don't forget to commit)
97
98 $ hg commit -m "Merge branch 'sebhtml/99991-topic-X' into 'default'"
99
100
101 Check the log of topic X, topic Y, and default branch
102
103 $ hg log -B sebhtml/99992-topic-Y
104
105 $ hg log -B sebhtml/99991-topic-X
106
107 $ hg log -b default
108 changeset: 4:c26ba8c1e1cb
109 tag: tip
110 parent: 3:2189f3fb90d6
111 parent: 1:29f39dea9bf9
112 user: test
113 date: Thu Jan 01 00:00:00 1970 +0000
114 summary: Merge branch 'sebhtml/99991-topic-X' into 'default'
115
116 changeset: 3:2189f3fb90d6
117 parent: 0:eaea25376a59
118 parent: 2:11df7969cf8d
119 user: test
120 date: Thu Jan 01 00:00:00 1970 +0000
121 summary: Merge branch 'sebhtml/99992-topic-Y' into 'default'
122
123 changeset: 0:eaea25376a59
124 user: test
125 date: Thu Jan 01 00:00:00 1970 +0000
126 summary: Add foo in 'default'
127
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,1210 +1,1230 b''
1 1 # logcmdutil.py - utility for log-like commands
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 itertools
11 11 import os
12 12 import posixpath
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 nullid,
17 17 wdirid,
18 18 wdirrev,
19 19 )
20 20
21 21 from .thirdparty import attr
22 22
23 23 from . import (
24 24 dagop,
25 25 error,
26 26 formatter,
27 27 graphmod,
28 28 match as matchmod,
29 29 mdiff,
30 30 patch,
31 31 pathutil,
32 32 pycompat,
33 33 revset,
34 34 revsetlang,
35 35 scmutil,
36 36 smartset,
37 37 templatekw,
38 38 templater,
39 39 util,
40 40 )
41 41 from .utils import (
42 42 dateutil,
43 43 stringutil,
44 44 )
45 45
46 46
47 47 if pycompat.TYPE_CHECKING:
48 48 from typing import (
49 49 Any,
50 50 Callable,
51 51 Dict,
52 52 List,
53 53 Optional,
54 54 Tuple,
55 55 )
56 56
57 57 for t in (Any, Callable, Dict, List, Optional, Tuple):
58 58 assert t
59 59
60 60
61 61 def getlimit(opts):
62 62 """get the log limit according to option -l/--limit"""
63 63 limit = opts.get(b'limit')
64 64 if limit:
65 65 try:
66 66 limit = int(limit)
67 67 except ValueError:
68 68 raise error.Abort(_(b'limit must be a positive integer'))
69 69 if limit <= 0:
70 70 raise error.Abort(_(b'limit must be positive'))
71 71 else:
72 72 limit = None
73 73 return limit
74 74
75 75
76 76 def diffordiffstat(
77 77 ui,
78 78 repo,
79 79 diffopts,
80 80 ctx1,
81 81 ctx2,
82 82 match,
83 83 changes=None,
84 84 stat=False,
85 85 fp=None,
86 86 graphwidth=0,
87 87 prefix=b'',
88 88 root=b'',
89 89 listsubrepos=False,
90 90 hunksfilterfn=None,
91 91 ):
92 92 '''show diff or diffstat.'''
93 93 if root:
94 94 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
95 95 else:
96 96 relroot = b''
97 97 copysourcematch = None
98 98
99 99 def compose(f, g):
100 100 return lambda x: f(g(x))
101 101
102 102 def pathfn(f):
103 103 return posixpath.join(prefix, f)
104 104
105 105 if relroot != b'':
106 106 # XXX relative roots currently don't work if the root is within a
107 107 # subrepo
108 108 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
109 109 uirelroot = uipathfn(pathfn(relroot))
110 110 relroot += b'/'
111 111 for matchroot in match.files():
112 112 if not matchroot.startswith(relroot):
113 113 ui.warn(
114 114 _(b'warning: %s not inside relative root %s\n')
115 115 % (uipathfn(pathfn(matchroot)), uirelroot)
116 116 )
117 117
118 118 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
119 119 match = matchmod.intersectmatchers(match, relrootmatch)
120 120 copysourcematch = relrootmatch
121 121
122 122 checkroot = repo.ui.configbool(
123 123 b'devel', b'all-warnings'
124 124 ) or repo.ui.configbool(b'devel', b'check-relroot')
125 125
126 126 def relrootpathfn(f):
127 127 if checkroot and not f.startswith(relroot):
128 128 raise AssertionError(
129 129 b"file %s doesn't start with relroot %s" % (f, relroot)
130 130 )
131 131 return f[len(relroot) :]
132 132
133 133 pathfn = compose(relrootpathfn, pathfn)
134 134
135 135 if stat:
136 136 diffopts = diffopts.copy(context=0, noprefix=False)
137 137 width = 80
138 138 if not ui.plain():
139 139 width = ui.termwidth() - graphwidth
140 140 # If an explicit --root was given, don't respect ui.relative-paths
141 141 if not relroot:
142 142 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
143 143
144 144 chunks = ctx2.diff(
145 145 ctx1,
146 146 match,
147 147 changes,
148 148 opts=diffopts,
149 149 pathfn=pathfn,
150 150 copysourcematch=copysourcematch,
151 151 hunksfilterfn=hunksfilterfn,
152 152 )
153 153
154 154 if fp is not None or ui.canwritewithoutlabels():
155 155 out = fp or ui
156 156 if stat:
157 157 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
158 158 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
159 159 out.write(chunk)
160 160 else:
161 161 if stat:
162 162 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
163 163 else:
164 164 chunks = patch.difflabel(
165 165 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
166 166 )
167 167 if ui.canbatchlabeledwrites():
168 168
169 169 def gen():
170 170 for chunk, label in chunks:
171 171 yield ui.label(chunk, label=label)
172 172
173 173 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
174 174 ui.write(chunk)
175 175 else:
176 176 for chunk, label in chunks:
177 177 ui.write(chunk, label=label)
178 178
179 179 node2 = ctx2.node()
180 180 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
181 181 tempnode2 = node2
182 182 try:
183 183 if node2 is not None:
184 184 tempnode2 = ctx2.substate[subpath][1]
185 185 except KeyError:
186 186 # A subrepo that existed in node1 was deleted between node1 and
187 187 # node2 (inclusive). Thus, ctx2's substate won't contain that
188 188 # subpath. The best we can do is to ignore it.
189 189 tempnode2 = None
190 190 submatch = matchmod.subdirmatcher(subpath, match)
191 191 subprefix = repo.wvfs.reljoin(prefix, subpath)
192 192 if listsubrepos or match.exact(subpath) or any(submatch.files()):
193 193 sub.diff(
194 194 ui,
195 195 diffopts,
196 196 tempnode2,
197 197 submatch,
198 198 changes=changes,
199 199 stat=stat,
200 200 fp=fp,
201 201 prefix=subprefix,
202 202 )
203 203
204 204
205 205 class changesetdiffer(object):
206 206 """Generate diff of changeset with pre-configured filtering functions"""
207 207
208 208 def _makefilematcher(self, ctx):
209 209 return scmutil.matchall(ctx.repo())
210 210
211 211 def _makehunksfilter(self, ctx):
212 212 return None
213 213
214 214 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
215 215 diffordiffstat(
216 216 ui,
217 217 ctx.repo(),
218 218 diffopts,
219 219 ctx.p1(),
220 220 ctx,
221 221 match=self._makefilematcher(ctx),
222 222 stat=stat,
223 223 graphwidth=graphwidth,
224 224 hunksfilterfn=self._makehunksfilter(ctx),
225 225 )
226 226
227 227
228 228 def changesetlabels(ctx):
229 229 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
230 230 if ctx.obsolete():
231 231 labels.append(b'changeset.obsolete')
232 232 if ctx.isunstable():
233 233 labels.append(b'changeset.unstable')
234 234 for instability in ctx.instabilities():
235 235 labels.append(b'instability.%s' % instability)
236 236 return b' '.join(labels)
237 237
238 238
239 239 class changesetprinter(object):
240 240 '''show changeset information when templating not requested.'''
241 241
242 242 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
243 243 self.ui = ui
244 244 self.repo = repo
245 245 self.buffered = buffered
246 246 self._differ = differ or changesetdiffer()
247 247 self._diffopts = patch.diffallopts(ui, diffopts)
248 248 self._includestat = diffopts and diffopts.get(b'stat')
249 249 self._includediff = diffopts and diffopts.get(b'patch')
250 250 self.header = {}
251 251 self.hunk = {}
252 252 self.lastheader = None
253 253 self.footer = None
254 254 self._columns = templatekw.getlogcolumns()
255 255
256 256 def flush(self, ctx):
257 257 rev = ctx.rev()
258 258 if rev in self.header:
259 259 h = self.header[rev]
260 260 if h != self.lastheader:
261 261 self.lastheader = h
262 262 self.ui.write(h)
263 263 del self.header[rev]
264 264 if rev in self.hunk:
265 265 self.ui.write(self.hunk[rev])
266 266 del self.hunk[rev]
267 267
268 268 def close(self):
269 269 if self.footer:
270 270 self.ui.write(self.footer)
271 271
272 272 def show(self, ctx, copies=None, **props):
273 273 props = pycompat.byteskwargs(props)
274 274 if self.buffered:
275 275 self.ui.pushbuffer(labeled=True)
276 276 self._show(ctx, copies, props)
277 277 self.hunk[ctx.rev()] = self.ui.popbuffer()
278 278 else:
279 279 self._show(ctx, copies, props)
280 280
281 281 def _show(self, ctx, copies, props):
282 282 '''show a single changeset or file revision'''
283 283 changenode = ctx.node()
284 284 graphwidth = props.get(b'graphwidth', 0)
285 285
286 286 if self.ui.quiet:
287 287 self.ui.write(
288 288 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
289 289 )
290 290 return
291 291
292 292 columns = self._columns
293 293 self.ui.write(
294 294 columns[b'changeset'] % scmutil.formatchangeid(ctx),
295 295 label=changesetlabels(ctx),
296 296 )
297 297
298 298 # branches are shown first before any other names due to backwards
299 299 # compatibility
300 300 branch = ctx.branch()
301 301 # don't show the default branch name
302 302 if branch != b'default':
303 303 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
304 304
305 305 for nsname, ns in pycompat.iteritems(self.repo.names):
306 306 # branches has special logic already handled above, so here we just
307 307 # skip it
308 308 if nsname == b'branches':
309 309 continue
310 310 # we will use the templatename as the color name since those two
311 311 # should be the same
312 312 for name in ns.names(self.repo, changenode):
313 313 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
314 314 if self.ui.debugflag:
315 315 self.ui.write(
316 316 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
317 317 )
318 318 for pctx in scmutil.meaningfulparents(self.repo, ctx):
319 319 label = b'log.parent changeset.%s' % pctx.phasestr()
320 320 self.ui.write(
321 321 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
322 322 )
323 323
324 324 if self.ui.debugflag:
325 325 mnode = ctx.manifestnode()
326 326 if mnode is None:
327 327 mnode = wdirid
328 328 mrev = wdirrev
329 329 else:
330 330 mrev = self.repo.manifestlog.rev(mnode)
331 331 self.ui.write(
332 332 columns[b'manifest']
333 333 % scmutil.formatrevnode(self.ui, mrev, mnode),
334 334 label=b'ui.debug log.manifest',
335 335 )
336 336 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
337 337 self.ui.write(
338 338 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
339 339 )
340 340
341 341 if ctx.isunstable():
342 342 instabilities = ctx.instabilities()
343 343 self.ui.write(
344 344 columns[b'instability'] % b', '.join(instabilities),
345 345 label=b'log.instability',
346 346 )
347 347
348 348 elif ctx.obsolete():
349 349 self._showobsfate(ctx)
350 350
351 351 self._exthook(ctx)
352 352
353 353 if self.ui.debugflag:
354 354 files = ctx.p1().status(ctx)
355 355 for key, value in zip(
356 356 [b'files', b'files+', b'files-'],
357 357 [files.modified, files.added, files.removed],
358 358 ):
359 359 if value:
360 360 self.ui.write(
361 361 columns[key] % b" ".join(value),
362 362 label=b'ui.debug log.files',
363 363 )
364 364 elif ctx.files() and self.ui.verbose:
365 365 self.ui.write(
366 366 columns[b'files'] % b" ".join(ctx.files()),
367 367 label=b'ui.note log.files',
368 368 )
369 369 if copies and self.ui.verbose:
370 370 copies = [b'%s (%s)' % c for c in copies]
371 371 self.ui.write(
372 372 columns[b'copies'] % b' '.join(copies),
373 373 label=b'ui.note log.copies',
374 374 )
375 375
376 376 extra = ctx.extra()
377 377 if extra and self.ui.debugflag:
378 378 for key, value in sorted(extra.items()):
379 379 self.ui.write(
380 380 columns[b'extra'] % (key, stringutil.escapestr(value)),
381 381 label=b'ui.debug log.extra',
382 382 )
383 383
384 384 description = ctx.description().strip()
385 385 if description:
386 386 if self.ui.verbose:
387 387 self.ui.write(
388 388 _(b"description:\n"), label=b'ui.note log.description'
389 389 )
390 390 self.ui.write(description, label=b'ui.note log.description')
391 391 self.ui.write(b"\n\n")
392 392 else:
393 393 self.ui.write(
394 394 columns[b'summary'] % description.splitlines()[0],
395 395 label=b'log.summary',
396 396 )
397 397 self.ui.write(b"\n")
398 398
399 399 self._showpatch(ctx, graphwidth)
400 400
401 401 def _showobsfate(self, ctx):
402 402 # TODO: do not depend on templater
403 403 tres = formatter.templateresources(self.repo.ui, self.repo)
404 404 t = formatter.maketemplater(
405 405 self.repo.ui,
406 406 b'{join(obsfate, "\n")}',
407 407 defaults=templatekw.keywords,
408 408 resources=tres,
409 409 )
410 410 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
411 411
412 412 if obsfate:
413 413 for obsfateline in obsfate:
414 414 self.ui.write(
415 415 self._columns[b'obsolete'] % obsfateline,
416 416 label=b'log.obsfate',
417 417 )
418 418
419 419 def _exthook(self, ctx):
420 420 '''empty method used by extension as a hook point
421 421 '''
422 422
423 423 def _showpatch(self, ctx, graphwidth=0):
424 424 if self._includestat:
425 425 self._differ.showdiff(
426 426 self.ui, ctx, self._diffopts, graphwidth, stat=True
427 427 )
428 428 if self._includestat and self._includediff:
429 429 self.ui.write(b"\n")
430 430 if self._includediff:
431 431 self._differ.showdiff(
432 432 self.ui, ctx, self._diffopts, graphwidth, stat=False
433 433 )
434 434 if self._includestat or self._includediff:
435 435 self.ui.write(b"\n")
436 436
437 437
438 438 class changesetformatter(changesetprinter):
439 439 """Format changeset information by generic formatter"""
440 440
441 441 def __init__(
442 442 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
443 443 ):
444 444 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
445 445 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
446 446 self._fm = fm
447 447
448 448 def close(self):
449 449 self._fm.end()
450 450
451 451 def _show(self, ctx, copies, props):
452 452 '''show a single changeset or file revision'''
453 453 fm = self._fm
454 454 fm.startitem()
455 455 fm.context(ctx=ctx)
456 456 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
457 457
458 458 datahint = fm.datahint()
459 459 if self.ui.quiet and not datahint:
460 460 return
461 461
462 462 fm.data(
463 463 branch=ctx.branch(),
464 464 phase=ctx.phasestr(),
465 465 user=ctx.user(),
466 466 date=fm.formatdate(ctx.date()),
467 467 desc=ctx.description(),
468 468 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
469 469 tags=fm.formatlist(ctx.tags(), name=b'tag'),
470 470 parents=fm.formatlist(
471 471 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
472 472 ),
473 473 )
474 474
475 475 if self.ui.debugflag or b'manifest' in datahint:
476 476 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid))
477 477 if self.ui.debugflag or b'extra' in datahint:
478 478 fm.data(extra=fm.formatdict(ctx.extra()))
479 479
480 480 if (
481 481 self.ui.debugflag
482 482 or b'modified' in datahint
483 483 or b'added' in datahint
484 484 or b'removed' in datahint
485 485 ):
486 486 files = ctx.p1().status(ctx)
487 487 fm.data(
488 488 modified=fm.formatlist(files.modified, name=b'file'),
489 489 added=fm.formatlist(files.added, name=b'file'),
490 490 removed=fm.formatlist(files.removed, name=b'file'),
491 491 )
492 492
493 493 verbose = not self.ui.debugflag and self.ui.verbose
494 494 if verbose or b'files' in datahint:
495 495 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
496 496 if verbose and copies or b'copies' in datahint:
497 497 fm.data(
498 498 copies=fm.formatdict(copies or {}, key=b'name', value=b'source')
499 499 )
500 500
501 501 if self._includestat or b'diffstat' in datahint:
502 502 self.ui.pushbuffer()
503 503 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
504 504 fm.data(diffstat=self.ui.popbuffer())
505 505 if self._includediff or b'diff' in datahint:
506 506 self.ui.pushbuffer()
507 507 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
508 508 fm.data(diff=self.ui.popbuffer())
509 509
510 510
511 511 class changesettemplater(changesetprinter):
512 512 '''format changeset information.
513 513
514 514 Note: there are a variety of convenience functions to build a
515 515 changesettemplater for common cases. See functions such as:
516 516 maketemplater, changesetdisplayer, buildcommittemplate, or other
517 517 functions that use changesest_templater.
518 518 '''
519 519
520 520 # Arguments before "buffered" used to be positional. Consider not
521 521 # adding/removing arguments before "buffered" to not break callers.
522 522 def __init__(
523 523 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
524 524 ):
525 525 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
526 526 # tres is shared with _graphnodeformatter()
527 527 self._tresources = tres = formatter.templateresources(ui, repo)
528 528 self.t = formatter.loadtemplater(
529 529 ui,
530 530 tmplspec,
531 531 defaults=templatekw.keywords,
532 532 resources=tres,
533 533 cache=templatekw.defaulttempl,
534 534 )
535 535 self._counter = itertools.count()
536 536
537 537 self._tref = tmplspec.ref
538 538 self._parts = {
539 539 b'header': b'',
540 540 b'footer': b'',
541 541 tmplspec.ref: tmplspec.ref,
542 542 b'docheader': b'',
543 543 b'docfooter': b'',
544 544 b'separator': b'',
545 545 }
546 546 if tmplspec.mapfile:
547 547 # find correct templates for current mode, for backward
548 548 # compatibility with 'log -v/-q/--debug' using a mapfile
549 549 tmplmodes = [
550 550 (True, b''),
551 551 (self.ui.verbose, b'_verbose'),
552 552 (self.ui.quiet, b'_quiet'),
553 553 (self.ui.debugflag, b'_debug'),
554 554 ]
555 555 for mode, postfix in tmplmodes:
556 556 for t in self._parts:
557 557 cur = t + postfix
558 558 if mode and cur in self.t:
559 559 self._parts[t] = cur
560 560 else:
561 561 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
562 562 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
563 563 self._parts.update(m)
564 564
565 565 if self._parts[b'docheader']:
566 566 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
567 567
568 568 def close(self):
569 569 if self._parts[b'docfooter']:
570 570 if not self.footer:
571 571 self.footer = b""
572 572 self.footer += self.t.render(self._parts[b'docfooter'], {})
573 573 return super(changesettemplater, self).close()
574 574
575 575 def _show(self, ctx, copies, props):
576 576 '''show a single changeset or file revision'''
577 577 props = props.copy()
578 578 props[b'ctx'] = ctx
579 579 props[b'index'] = index = next(self._counter)
580 580 props[b'revcache'] = {b'copies': copies}
581 581 graphwidth = props.get(b'graphwidth', 0)
582 582
583 583 # write separator, which wouldn't work well with the header part below
584 584 # since there's inherently a conflict between header (across items) and
585 585 # separator (per item)
586 586 if self._parts[b'separator'] and index > 0:
587 587 self.ui.write(self.t.render(self._parts[b'separator'], {}))
588 588
589 589 # write header
590 590 if self._parts[b'header']:
591 591 h = self.t.render(self._parts[b'header'], props)
592 592 if self.buffered:
593 593 self.header[ctx.rev()] = h
594 594 else:
595 595 if self.lastheader != h:
596 596 self.lastheader = h
597 597 self.ui.write(h)
598 598
599 599 # write changeset metadata, then patch if requested
600 600 key = self._parts[self._tref]
601 601 self.ui.write(self.t.render(key, props))
602 602 self._exthook(ctx)
603 603 self._showpatch(ctx, graphwidth)
604 604
605 605 if self._parts[b'footer']:
606 606 if not self.footer:
607 607 self.footer = self.t.render(self._parts[b'footer'], props)
608 608
609 609
610 610 def templatespec(tmpl, mapfile):
611 611 assert not (tmpl and mapfile)
612 612 if mapfile:
613 613 return formatter.mapfile_templatespec(b'changeset', mapfile)
614 614 else:
615 615 return formatter.literal_templatespec(tmpl)
616 616
617 617
618 618 def _lookuptemplate(ui, tmpl, style):
619 619 """Find the template matching the given template spec or style
620 620
621 621 See formatter.lookuptemplate() for details.
622 622 """
623 623
624 624 # ui settings
625 625 if not tmpl and not style: # template are stronger than style
626 626 tmpl = ui.config(b'command-templates', b'log')
627 627 if tmpl:
628 628 return formatter.literal_templatespec(templater.unquotestring(tmpl))
629 629 else:
630 630 style = util.expandpath(ui.config(b'ui', b'style'))
631 631
632 632 if not tmpl and style:
633 633 mapfile = style
634 634 fp = None
635 635 if not os.path.split(mapfile)[0]:
636 636 (mapname, fp) = templater.try_open_template(
637 637 b'map-cmdline.' + mapfile
638 638 ) or templater.try_open_template(mapfile)
639 639 if mapname:
640 640 mapfile = mapname
641 641 return formatter.mapfile_templatespec(b'changeset', mapfile, fp)
642 642
643 643 return formatter.lookuptemplate(ui, b'changeset', tmpl)
644 644
645 645
646 646 def maketemplater(ui, repo, tmpl, buffered=False):
647 647 """Create a changesettemplater from a literal template 'tmpl'
648 648 byte-string."""
649 649 spec = formatter.literal_templatespec(tmpl)
650 650 return changesettemplater(ui, repo, spec, buffered=buffered)
651 651
652 652
653 653 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
654 654 """show one changeset using template or regular display.
655 655
656 656 Display format will be the first non-empty hit of:
657 657 1. option 'template'
658 658 2. option 'style'
659 659 3. [command-templates] setting 'log'
660 660 4. [ui] setting 'style'
661 661 If all of these values are either the unset or the empty string,
662 662 regular display via changesetprinter() is done.
663 663 """
664 664 postargs = (differ, opts, buffered)
665 665 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
666 666
667 667 # machine-readable formats have slightly different keyword set than
668 668 # plain templates, which are handled by changesetformatter.
669 669 # note that {b'pickle', b'debug'} can also be added to the list if needed.
670 670 if spec.ref in {b'cbor', b'json'}:
671 671 fm = ui.formatter(b'log', opts)
672 672 return changesetformatter(ui, repo, fm, *postargs)
673 673
674 674 if not spec.ref and not spec.tmpl and not spec.mapfile:
675 675 return changesetprinter(ui, repo, *postargs)
676 676
677 677 return changesettemplater(ui, repo, spec, *postargs)
678 678
679 679
680 680 @attr.s
681 681 class walkopts(object):
682 682 """Options to configure a set of revisions and file matcher factory
683 683 to scan revision/file history
684 684 """
685 685
686 686 # raw command-line parameters, which a matcher will be built from
687 687 pats = attr.ib() # type: List[bytes]
688 688 opts = attr.ib() # type: Dict[bytes, Any]
689 689
690 690 # a list of revset expressions to be traversed; if follow, it specifies
691 691 # the start revisions
692 692 revspec = attr.ib() # type: List[bytes]
693 693
694 694 # miscellaneous queries to filter revisions (see "hg help log" for details)
695 695 branches = attr.ib(default=attr.Factory(list)) # type: List[bytes]
696 696 date = attr.ib(default=None) # type: Optional[bytes]
697 697 keywords = attr.ib(default=attr.Factory(list)) # type: List[bytes]
698 698 no_merges = attr.ib(default=False) # type: bool
699 699 only_merges = attr.ib(default=False) # type: bool
700 700 prune_ancestors = attr.ib(default=attr.Factory(list)) # type: List[bytes]
701 701 users = attr.ib(default=attr.Factory(list)) # type: List[bytes]
702 702
703 703 # miscellaneous matcher arguments
704 704 include_pats = attr.ib(default=attr.Factory(list)) # type: List[bytes]
705 705 exclude_pats = attr.ib(default=attr.Factory(list)) # type: List[bytes]
706 706
707 707 # 0: no follow, 1: follow first, 2: follow both parents
708 708 follow = attr.ib(default=0) # type: int
709 709
710 710 # do not attempt filelog-based traversal, which may be fast but cannot
711 711 # include revisions where files were removed
712 712 force_changelog_traversal = attr.ib(default=False) # type: bool
713 713
714 714 # filter revisions by file patterns, which should be disabled only if
715 715 # you want to include revisions where files were unmodified
716 716 filter_revisions_by_pats = attr.ib(default=True) # type: bool
717 717
718 718 # sort revisions prior to traversal: 'desc', 'topo', or None
719 719 sort_revisions = attr.ib(default=None) # type: Optional[bytes]
720 720
721 721 # limit number of changes displayed; None means unlimited
722 722 limit = attr.ib(default=None) # type: Optional[int]
723 723
724 724
725 725 def parseopts(ui, pats, opts):
726 726 # type: (Any, List[bytes], Dict[bytes, Any]) -> walkopts
727 727 """Parse log command options into walkopts
728 728
729 729 The returned walkopts will be passed in to getrevs() or makewalker().
730 730 """
731 731 if opts.get(b'follow_first'):
732 732 follow = 1
733 733 elif opts.get(b'follow'):
734 734 follow = 2
735 735 else:
736 736 follow = 0
737 737
738 738 if opts.get(b'graph'):
739 739 if ui.configbool(b'experimental', b'log.topo'):
740 740 sort_revisions = b'topo'
741 741 else:
742 742 sort_revisions = b'desc'
743 743 else:
744 744 sort_revisions = None
745 745
746 746 return walkopts(
747 747 pats=pats,
748 748 opts=opts,
749 749 revspec=opts.get(b'rev', []),
750 750 # branch and only_branch are really aliases and must be handled at
751 751 # the same time
752 752 branches=opts.get(b'branch', []) + opts.get(b'only_branch', []),
753 753 date=opts.get(b'date'),
754 754 keywords=opts.get(b'keyword', []),
755 755 no_merges=bool(opts.get(b'no_merges')),
756 756 only_merges=bool(opts.get(b'only_merges')),
757 757 prune_ancestors=opts.get(b'prune', []),
758 758 users=opts.get(b'user', []),
759 759 include_pats=opts.get(b'include', []),
760 760 exclude_pats=opts.get(b'exclude', []),
761 761 follow=follow,
762 762 force_changelog_traversal=bool(opts.get(b'removed')),
763 763 sort_revisions=sort_revisions,
764 764 limit=getlimit(opts),
765 765 )
766 766
767 767
768 768 def _makematcher(repo, revs, wopts):
769 769 """Build matcher and expanded patterns from log options
770 770
771 771 If --follow, revs are the revisions to follow from.
772 772
773 773 Returns (match, pats, slowpath) where
774 774 - match: a matcher built from the given pats and -I/-X opts
775 775 - pats: patterns used (globs are expanded on Windows)
776 776 - slowpath: True if patterns aren't as simple as scanning filelogs
777 777 """
778 778 # pats/include/exclude are passed to match.match() directly in
779 779 # _matchfiles() revset, but a log-like command should build its matcher
780 780 # with scmutil.match(). The difference is input pats are globbed on
781 781 # platforms without shell expansion (windows).
782 782 wctx = repo[None]
783 783 match, pats = scmutil.matchandpats(wctx, wopts.pats, wopts.opts)
784 784 slowpath = match.anypats() or (
785 785 not match.always() and wopts.force_changelog_traversal
786 786 )
787 787 if not slowpath:
788 788 if wopts.follow and wopts.revspec:
789 789 # There may be the case that a path doesn't exist in some (but
790 790 # not all) of the specified start revisions, but let's consider
791 791 # the path is valid. Missing files will be warned by the matcher.
792 792 startctxs = [repo[r] for r in revs]
793 793 for f in match.files():
794 794 found = False
795 795 for c in startctxs:
796 796 if f in c:
797 797 found = True
798 798 elif c.hasdir(f):
799 799 # If a directory exists in any of the start revisions,
800 800 # take the slow path.
801 801 found = slowpath = True
802 802 if not found:
803 803 raise error.Abort(
804 804 _(
805 805 b'cannot follow file not in any of the specified '
806 806 b'revisions: "%s"'
807 807 )
808 808 % f
809 809 )
810 810 elif wopts.follow:
811 811 for f in match.files():
812 812 if f not in wctx:
813 813 # If the file exists, it may be a directory, so let it
814 814 # take the slow path.
815 815 if os.path.exists(repo.wjoin(f)):
816 816 slowpath = True
817 817 continue
818 818 else:
819 819 raise error.Abort(
820 820 _(
821 821 b'cannot follow file not in parent '
822 822 b'revision: "%s"'
823 823 )
824 824 % f
825 825 )
826 826 filelog = repo.file(f)
827 827 if not filelog:
828 828 # A file exists in wdir but not in history, which means
829 829 # the file isn't committed yet.
830 830 raise error.Abort(
831 831 _(b'cannot follow nonexistent file: "%s"') % f
832 832 )
833 833 else:
834 834 for f in match.files():
835 835 filelog = repo.file(f)
836 836 if not filelog:
837 837 # A zero count may be a directory or deleted file, so
838 838 # try to find matching entries on the slow path.
839 839 slowpath = True
840 840
841 841 # We decided to fall back to the slowpath because at least one
842 842 # of the paths was not a file. Check to see if at least one of them
843 843 # existed in history - in that case, we'll continue down the
844 844 # slowpath; otherwise, we can turn off the slowpath
845 845 if slowpath:
846 846 for path in match.files():
847 847 if path == b'.' or path in repo.store:
848 848 break
849 849 else:
850 850 slowpath = False
851 851
852 852 return match, pats, slowpath
853 853
854 854
855 855 def _fileancestors(repo, revs, match, followfirst):
856 856 fctxs = []
857 857 for r in revs:
858 858 ctx = repo[r]
859 859 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
860 860
861 861 # When displaying a revision with --patch --follow FILE, we have
862 862 # to know which file of the revision must be diffed. With
863 863 # --follow, we want the names of the ancestors of FILE in the
864 864 # revision, stored in "fcache". "fcache" is populated as a side effect
865 865 # of the graph traversal.
866 866 fcache = {}
867 867
868 868 def filematcher(ctx):
869 869 return scmutil.matchfiles(repo, fcache.get(scmutil.intrev(ctx), []))
870 870
871 871 def revgen():
872 872 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
873 873 fcache[rev] = [c.path() for c in cs]
874 874 yield rev
875 875
876 876 return smartset.generatorset(revgen(), iterasc=False), filematcher
877 877
878 878
879 879 def _makenofollowfilematcher(repo, pats, opts):
880 880 '''hook for extensions to override the filematcher for non-follow cases'''
881 881 return None
882 882
883 883
884 884 _opt2logrevset = {
885 885 b'no_merges': (b'not merge()', None),
886 886 b'only_merges': (b'merge()', None),
887 887 b'_matchfiles': (None, b'_matchfiles(%ps)'),
888 888 b'date': (b'date(%s)', None),
889 889 b'branch': (b'branch(%s)', b'%lr'),
890 890 b'_patslog': (b'filelog(%s)', b'%lr'),
891 891 b'keyword': (b'keyword(%s)', b'%lr'),
892 892 b'prune': (b'ancestors(%s)', b'not %lr'),
893 893 b'user': (b'user(%s)', b'%lr'),
894 894 }
895 895
896 896
897 897 def _makerevset(repo, wopts, slowpath):
898 898 """Return a revset string built from log options and file patterns"""
899 899 opts = {
900 900 b'branch': [repo.lookupbranch(b) for b in wopts.branches],
901 901 b'date': wopts.date,
902 902 b'keyword': wopts.keywords,
903 903 b'no_merges': wopts.no_merges,
904 904 b'only_merges': wopts.only_merges,
905 905 b'prune': wopts.prune_ancestors,
906 906 b'user': wopts.users,
907 907 }
908 908
909 909 if wopts.filter_revisions_by_pats and slowpath:
910 910 # pats/include/exclude cannot be represented as separate
911 911 # revset expressions as their filtering logic applies at file
912 912 # level. For instance "-I a -X b" matches a revision touching
913 913 # "a" and "b" while "file(a) and not file(b)" does
914 914 # not. Besides, filesets are evaluated against the working
915 915 # directory.
916 916 matchargs = [b'r:', b'd:relpath']
917 917 for p in wopts.pats:
918 918 matchargs.append(b'p:' + p)
919 919 for p in wopts.include_pats:
920 920 matchargs.append(b'i:' + p)
921 921 for p in wopts.exclude_pats:
922 922 matchargs.append(b'x:' + p)
923 923 opts[b'_matchfiles'] = matchargs
924 924 elif wopts.filter_revisions_by_pats and not wopts.follow:
925 925 opts[b'_patslog'] = list(wopts.pats)
926 926
927 927 expr = []
928 928 for op, val in sorted(pycompat.iteritems(opts)):
929 929 if not val:
930 930 continue
931 931 revop, listop = _opt2logrevset[op]
932 932 if revop and b'%' not in revop:
933 933 expr.append(revop)
934 934 elif not listop:
935 935 expr.append(revsetlang.formatspec(revop, val))
936 936 else:
937 937 if revop:
938 938 val = [revsetlang.formatspec(revop, v) for v in val]
939 939 expr.append(revsetlang.formatspec(listop, val))
940 940
941 941 if expr:
942 942 expr = b'(' + b' and '.join(expr) + b')'
943 943 else:
944 944 expr = None
945 945 return expr
946 946
947 947
948 948 def _initialrevs(repo, wopts):
949 949 """Return the initial set of revisions to be filtered or followed"""
950 950 if wopts.revspec:
951 951 revs = scmutil.revrange(repo, wopts.revspec)
952 952 elif wopts.follow and repo.dirstate.p1() == nullid:
953 953 revs = smartset.baseset()
954 954 elif wopts.follow:
955 955 revs = repo.revs(b'.')
956 956 else:
957 957 revs = smartset.spanset(repo)
958 958 revs.reverse()
959 959 return revs
960 960
961 961
962 962 def makewalker(repo, wopts):
963 963 # type: (Any, walkopts) -> Tuple[smartset.abstractsmartset, Optional[Callable[[Any], matchmod.basematcher]]]
964 964 """Build (revs, makefilematcher) to scan revision/file history
965 965
966 966 - revs is the smartset to be traversed.
967 967 - makefilematcher is a function to map ctx to a matcher for that revision
968 968 """
969 969 revs = _initialrevs(repo, wopts)
970 970 if not revs:
971 971 return smartset.baseset(), None
972 972 # TODO: might want to merge slowpath with wopts.force_changelog_traversal
973 973 match, pats, slowpath = _makematcher(repo, revs, wopts)
974 974 wopts = attr.evolve(wopts, pats=pats)
975 975
976 976 filematcher = None
977 977 if wopts.follow:
978 978 if slowpath or match.always():
979 979 revs = dagop.revancestors(repo, revs, followfirst=wopts.follow == 1)
980 980 else:
981 981 assert not wopts.force_changelog_traversal
982 982 revs, filematcher = _fileancestors(
983 983 repo, revs, match, followfirst=wopts.follow == 1
984 984 )
985 985 revs.reverse()
986 986 if filematcher is None:
987 987 filematcher = _makenofollowfilematcher(repo, wopts.pats, wopts.opts)
988 988 if filematcher is None:
989 989
990 990 def filematcher(ctx):
991 991 return match
992 992
993 993 expr = _makerevset(repo, wopts, slowpath)
994 994 if wopts.sort_revisions:
995 995 assert wopts.sort_revisions in {b'topo', b'desc'}
996 996 if wopts.sort_revisions == b'topo':
997 997 if not revs.istopo():
998 998 revs = dagop.toposort(revs, repo.changelog.parentrevs)
999 999 # TODO: try to iterate the set lazily
1000 1000 revs = revset.baseset(list(revs), istopo=True)
1001 1001 elif not (revs.isdescending() or revs.istopo()):
1002 1002 # User-specified revs might be unsorted
1003 1003 revs.sort(reverse=True)
1004 1004 if expr:
1005 1005 matcher = revset.match(None, expr)
1006 1006 revs = matcher(repo, revs)
1007 1007 if wopts.limit is not None:
1008 1008 revs = revs.slice(0, wopts.limit)
1009 1009
1010 1010 return revs, filematcher
1011 1011
1012 1012
1013 1013 def getrevs(repo, wopts):
1014 1014 # type: (Any, walkopts) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
1015 1015 """Return (revs, differ) where revs is a smartset
1016 1016
1017 1017 differ is a changesetdiffer with pre-configured file matcher.
1018 1018 """
1019 1019 revs, filematcher = makewalker(repo, wopts)
1020 1020 if not revs:
1021 1021 return revs, None
1022 1022 differ = changesetdiffer()
1023 1023 differ._makefilematcher = filematcher
1024 1024 return revs, differ
1025 1025
1026 1026
1027 def get_bookmark_revs(repo, bookmark, walk_opts):
1028 # type: (Any, bookmark, walk_opts) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
1029 """Return (revs, differ) where revs is a smartset
1030
1031 differ is a changesetdiffer with pre-configured file matcher.
1032 """
1033 revs, filematcher = makewalker(repo, walk_opts)
1034 if not revs:
1035 return revs, None
1036 differ = changesetdiffer()
1037 differ._makefilematcher = filematcher
1038
1039 if bookmark:
1040 if bookmark not in repo._bookmarks:
1041 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
1042 revs = scmutil.bookmarkrevs(repo, bookmark)
1043
1044 return revs, differ
1045
1046
1027 1047 def _parselinerangeopt(repo, opts):
1028 1048 """Parse --line-range log option and return a list of tuples (filename,
1029 1049 (fromline, toline)).
1030 1050 """
1031 1051 linerangebyfname = []
1032 1052 for pat in opts.get(b'line_range', []):
1033 1053 try:
1034 1054 pat, linerange = pat.rsplit(b',', 1)
1035 1055 except ValueError:
1036 1056 raise error.Abort(_(b'malformatted line-range pattern %s') % pat)
1037 1057 try:
1038 1058 fromline, toline = map(int, linerange.split(b':'))
1039 1059 except ValueError:
1040 1060 raise error.Abort(_(b"invalid line range for %s") % pat)
1041 1061 msg = _(b"line range pattern '%s' must match exactly one file") % pat
1042 1062 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
1043 1063 linerangebyfname.append(
1044 1064 (fname, util.processlinerange(fromline, toline))
1045 1065 )
1046 1066 return linerangebyfname
1047 1067
1048 1068
1049 1069 def getlinerangerevs(repo, userrevs, opts):
1050 1070 """Return (revs, differ).
1051 1071
1052 1072 "revs" are revisions obtained by processing "line-range" log options and
1053 1073 walking block ancestors of each specified file/line-range.
1054 1074
1055 1075 "differ" is a changesetdiffer with pre-configured file matcher and hunks
1056 1076 filter.
1057 1077 """
1058 1078 wctx = repo[None]
1059 1079
1060 1080 # Two-levels map of "rev -> file ctx -> [line range]".
1061 1081 linerangesbyrev = {}
1062 1082 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
1063 1083 if fname not in wctx:
1064 1084 raise error.Abort(
1065 1085 _(b'cannot follow file not in parent revision: "%s"') % fname
1066 1086 )
1067 1087 fctx = wctx.filectx(fname)
1068 1088 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
1069 1089 rev = fctx.introrev()
1070 1090 if rev is None:
1071 1091 rev = wdirrev
1072 1092 if rev not in userrevs:
1073 1093 continue
1074 1094 linerangesbyrev.setdefault(rev, {}).setdefault(
1075 1095 fctx.path(), []
1076 1096 ).append(linerange)
1077 1097
1078 1098 def nofilterhunksfn(fctx, hunks):
1079 1099 return hunks
1080 1100
1081 1101 def hunksfilter(ctx):
1082 1102 fctxlineranges = linerangesbyrev.get(scmutil.intrev(ctx))
1083 1103 if fctxlineranges is None:
1084 1104 return nofilterhunksfn
1085 1105
1086 1106 def filterfn(fctx, hunks):
1087 1107 lineranges = fctxlineranges.get(fctx.path())
1088 1108 if lineranges is not None:
1089 1109 for hr, lines in hunks:
1090 1110 if hr is None: # binary
1091 1111 yield hr, lines
1092 1112 continue
1093 1113 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
1094 1114 yield hr, lines
1095 1115 else:
1096 1116 for hunk in hunks:
1097 1117 yield hunk
1098 1118
1099 1119 return filterfn
1100 1120
1101 1121 def filematcher(ctx):
1102 1122 files = list(linerangesbyrev.get(scmutil.intrev(ctx), []))
1103 1123 return scmutil.matchfiles(repo, files)
1104 1124
1105 1125 revs = sorted(linerangesbyrev, reverse=True)
1106 1126
1107 1127 differ = changesetdiffer()
1108 1128 differ._makefilematcher = filematcher
1109 1129 differ._makehunksfilter = hunksfilter
1110 1130 return smartset.baseset(revs), differ
1111 1131
1112 1132
1113 1133 def _graphnodeformatter(ui, displayer):
1114 1134 spec = ui.config(b'command-templates', b'graphnode')
1115 1135 if not spec:
1116 1136 return templatekw.getgraphnode # fast path for "{graphnode}"
1117 1137
1118 1138 spec = templater.unquotestring(spec)
1119 1139 if isinstance(displayer, changesettemplater):
1120 1140 # reuse cache of slow templates
1121 1141 tres = displayer._tresources
1122 1142 else:
1123 1143 tres = formatter.templateresources(ui)
1124 1144 templ = formatter.maketemplater(
1125 1145 ui, spec, defaults=templatekw.keywords, resources=tres
1126 1146 )
1127 1147
1128 1148 def formatnode(repo, ctx, cache):
1129 1149 props = {b'ctx': ctx, b'repo': repo}
1130 1150 return templ.renderdefault(props)
1131 1151
1132 1152 return formatnode
1133 1153
1134 1154
1135 1155 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
1136 1156 props = props or {}
1137 1157 formatnode = _graphnodeformatter(ui, displayer)
1138 1158 state = graphmod.asciistate()
1139 1159 styles = state.styles
1140 1160
1141 1161 # only set graph styling if HGPLAIN is not set.
1142 1162 if ui.plain(b'graph'):
1143 1163 # set all edge styles to |, the default pre-3.8 behaviour
1144 1164 styles.update(dict.fromkeys(styles, b'|'))
1145 1165 else:
1146 1166 edgetypes = {
1147 1167 b'parent': graphmod.PARENT,
1148 1168 b'grandparent': graphmod.GRANDPARENT,
1149 1169 b'missing': graphmod.MISSINGPARENT,
1150 1170 }
1151 1171 for name, key in edgetypes.items():
1152 1172 # experimental config: experimental.graphstyle.*
1153 1173 styles[key] = ui.config(
1154 1174 b'experimental', b'graphstyle.%s' % name, styles[key]
1155 1175 )
1156 1176 if not styles[key]:
1157 1177 styles[key] = None
1158 1178
1159 1179 # experimental config: experimental.graphshorten
1160 1180 state.graphshorten = ui.configbool(b'experimental', b'graphshorten')
1161 1181
1162 1182 formatnode_cache = {}
1163 1183 for rev, type, ctx, parents in dag:
1164 1184 char = formatnode(repo, ctx, formatnode_cache)
1165 1185 copies = getcopies(ctx) if getcopies else None
1166 1186 edges = edgefn(type, char, state, rev, parents)
1167 1187 firstedge = next(edges)
1168 1188 width = firstedge[2]
1169 1189 displayer.show(
1170 1190 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1171 1191 )
1172 1192 lines = displayer.hunk.pop(rev).split(b'\n')
1173 1193 if not lines[-1]:
1174 1194 del lines[-1]
1175 1195 displayer.flush(ctx)
1176 1196 for type, char, width, coldata in itertools.chain([firstedge], edges):
1177 1197 graphmod.ascii(ui, state, type, char, lines, coldata)
1178 1198 lines = []
1179 1199 displayer.close()
1180 1200
1181 1201
1182 1202 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1183 1203 revdag = graphmod.dagwalker(repo, revs)
1184 1204 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1185 1205
1186 1206
1187 1207 def displayrevs(ui, repo, revs, displayer, getcopies):
1188 1208 for rev in revs:
1189 1209 ctx = repo[rev]
1190 1210 copies = getcopies(ctx) if getcopies else None
1191 1211 displayer.show(ctx, copies=copies)
1192 1212 displayer.flush(ctx)
1193 1213 displayer.close()
1194 1214
1195 1215
1196 1216 def checkunsupportedgraphflags(pats, opts):
1197 1217 for op in [b"newest_first"]:
1198 1218 if op in opts and opts[op]:
1199 1219 raise error.Abort(
1200 1220 _(b"-G/--graph option is incompatible with --%s")
1201 1221 % op.replace(b"_", b"-")
1202 1222 )
1203 1223
1204 1224
1205 1225 def graphrevs(repo, nodes, opts):
1206 1226 limit = getlimit(opts)
1207 1227 nodes.reverse()
1208 1228 if limit is not None:
1209 1229 nodes = nodes[:limit]
1210 1230 return graphmod.nodes(repo, nodes)
@@ -1,439 +1,439 b''
1 1 Show all commands except debug commands
2 2 $ hg debugcomplete
3 3 abort
4 4 add
5 5 addremove
6 6 annotate
7 7 archive
8 8 backout
9 9 bisect
10 10 bookmarks
11 11 branch
12 12 branches
13 13 bundle
14 14 cat
15 15 clone
16 16 commit
17 17 config
18 18 continue
19 19 copy
20 20 diff
21 21 export
22 22 files
23 23 forget
24 24 graft
25 25 grep
26 26 heads
27 27 help
28 28 identify
29 29 import
30 30 incoming
31 31 init
32 32 locate
33 33 log
34 34 manifest
35 35 merge
36 36 outgoing
37 37 parents
38 38 paths
39 39 phase
40 40 pull
41 41 push
42 42 recover
43 43 remove
44 44 rename
45 45 resolve
46 46 revert
47 47 rollback
48 48 root
49 49 serve
50 50 shelve
51 51 status
52 52 summary
53 53 tag
54 54 tags
55 55 tip
56 56 unbundle
57 57 unshelve
58 58 update
59 59 verify
60 60 version
61 61
62 62 Show all commands that start with "a"
63 63 $ hg debugcomplete a
64 64 abort
65 65 add
66 66 addremove
67 67 annotate
68 68 archive
69 69
70 70 Do not show debug commands if there are other candidates
71 71 $ hg debugcomplete d
72 72 diff
73 73
74 74 Show debug commands if there are no other candidates
75 75 $ hg debugcomplete debug
76 76 debugancestor
77 77 debugantivirusrunning
78 78 debugapplystreamclonebundle
79 79 debugbackupbundle
80 80 debugbuilddag
81 81 debugbundle
82 82 debugcapabilities
83 83 debugchangedfiles
84 84 debugcheckstate
85 85 debugcolor
86 86 debugcommands
87 87 debugcomplete
88 88 debugconfig
89 89 debugcreatestreamclonebundle
90 90 debugdag
91 91 debugdata
92 92 debugdate
93 93 debugdeltachain
94 94 debugdirstate
95 95 debugdiscovery
96 96 debugdownload
97 97 debugextensions
98 98 debugfileset
99 99 debugformat
100 100 debugfsinfo
101 101 debuggetbundle
102 102 debugignore
103 103 debugindex
104 104 debugindexdot
105 105 debugindexstats
106 106 debuginstall
107 107 debugknown
108 108 debuglabelcomplete
109 109 debuglocks
110 110 debugmanifestfulltextcache
111 111 debugmergestate
112 112 debugnamecomplete
113 113 debugnodemap
114 114 debugobsolete
115 115 debugp1copies
116 116 debugp2copies
117 117 debugpathcomplete
118 118 debugpathcopies
119 119 debugpeer
120 120 debugpickmergetool
121 121 debugpushkey
122 122 debugpvec
123 123 debugrebuilddirstate
124 124 debugrebuildfncache
125 125 debugrename
126 126 debugrequires
127 127 debugrevlog
128 128 debugrevlogindex
129 129 debugrevspec
130 130 debugserve
131 131 debugsetparents
132 132 debugsidedata
133 133 debugssl
134 134 debugstrip
135 135 debugsub
136 136 debugsuccessorssets
137 137 debugtagscache
138 138 debugtemplate
139 139 debuguigetpass
140 140 debuguiprompt
141 141 debugupdatecaches
142 142 debugupgraderepo
143 143 debugwalk
144 144 debugwhyunstable
145 145 debugwireargs
146 146 debugwireproto
147 147
148 148 Do not show the alias of a debug command if there are other candidates
149 149 (this should hide rawcommit)
150 150 $ hg debugcomplete r
151 151 recover
152 152 remove
153 153 rename
154 154 resolve
155 155 revert
156 156 rollback
157 157 root
158 158 Show the alias of a debug command if there are no other candidates
159 159 $ hg debugcomplete rawc
160 160
161 161
162 162 Show the global options
163 163 $ hg debugcomplete --options | sort
164 164 --color
165 165 --config
166 166 --cwd
167 167 --debug
168 168 --debugger
169 169 --encoding
170 170 --encodingmode
171 171 --help
172 172 --hidden
173 173 --noninteractive
174 174 --pager
175 175 --profile
176 176 --quiet
177 177 --repository
178 178 --time
179 179 --traceback
180 180 --verbose
181 181 --version
182 182 -R
183 183 -h
184 184 -q
185 185 -v
186 186 -y
187 187
188 188 Show the options for the "serve" command
189 189 $ hg debugcomplete --options serve | sort
190 190 --accesslog
191 191 --address
192 192 --certificate
193 193 --cmdserver
194 194 --color
195 195 --config
196 196 --cwd
197 197 --daemon
198 198 --daemon-postexec
199 199 --debug
200 200 --debugger
201 201 --encoding
202 202 --encodingmode
203 203 --errorlog
204 204 --help
205 205 --hidden
206 206 --ipv6
207 207 --name
208 208 --noninteractive
209 209 --pager
210 210 --pid-file
211 211 --port
212 212 --prefix
213 213 --print-url
214 214 --profile
215 215 --quiet
216 216 --repository
217 217 --stdio
218 218 --style
219 219 --subrepos
220 220 --templates
221 221 --time
222 222 --traceback
223 223 --verbose
224 224 --version
225 225 --web-conf
226 226 -6
227 227 -A
228 228 -E
229 229 -R
230 230 -S
231 231 -a
232 232 -d
233 233 -h
234 234 -n
235 235 -p
236 236 -q
237 237 -t
238 238 -v
239 239 -y
240 240
241 241 Show an error if we use --options with an ambiguous abbreviation
242 242 $ hg debugcomplete --options s
243 243 hg: command 's' is ambiguous:
244 244 serve shelve showconfig status summary
245 245 [255]
246 246
247 247 Show all commands + options
248 248 $ hg debugcommands
249 249 abort: dry-run
250 250 add: include, exclude, subrepos, dry-run
251 251 addremove: similarity, subrepos, include, exclude, dry-run
252 252 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, include, exclude, template
253 253 archive: no-decode, prefix, rev, type, subrepos, include, exclude
254 254 backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
255 255 bisect: reset, good, bad, skip, extend, command, noupdate
256 256 bookmarks: force, rev, delete, rename, inactive, list, template
257 257 branch: force, clean, rev
258 258 branches: active, closed, rev, template
259 259 bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
260 260 cat: output, rev, decode, include, exclude, template
261 261 clone: noupdate, updaterev, rev, branch, pull, uncompressed, stream, ssh, remotecmd, insecure
262 262 commit: addremove, close-branch, amend, secret, edit, force-close-branch, interactive, include, exclude, message, logfile, date, user, subrepos
263 263 config: untrusted, edit, local, shared, non-shared, global, template
264 264 continue: dry-run
265 265 copy: forget, after, at-rev, force, include, exclude, dry-run
266 266 debugancestor:
267 267 debugantivirusrunning:
268 268 debugapplystreamclonebundle:
269 269 debugbackupbundle: recover, patch, git, limit, no-merges, stat, graph, style, template
270 270 debugbuilddag: mergeable-file, overwritten-file, new-file
271 271 debugbundle: all, part-type, spec
272 272 debugcapabilities:
273 273 debugchangedfiles:
274 274 debugcheckstate:
275 275 debugcolor: style
276 276 debugcommands:
277 277 debugcomplete: options
278 278 debugcreatestreamclonebundle:
279 279 debugdag: tags, branches, dots, spaces
280 280 debugdata: changelog, manifest, dir
281 281 debugdate: extended
282 282 debugdeltachain: changelog, manifest, dir, template
283 283 debugdirstate: nodates, dates, datesort
284 284 debugdiscovery: old, nonheads, rev, seed, ssh, remotecmd, insecure
285 285 debugdownload: output
286 286 debugextensions: template
287 287 debugfileset: rev, all-files, show-matcher, show-stage
288 288 debugformat: template
289 289 debugfsinfo:
290 290 debuggetbundle: head, common, type
291 291 debugignore:
292 292 debugindex: changelog, manifest, dir, template
293 293 debugindexdot: changelog, manifest, dir
294 294 debugindexstats:
295 295 debuginstall: template
296 296 debugknown:
297 297 debuglabelcomplete:
298 298 debuglocks: force-lock, force-wlock, set-lock, set-wlock
299 299 debugmanifestfulltextcache: clear, add
300 300 debugmergestate: style, template
301 301 debugnamecomplete:
302 302 debugnodemap: dump-new, dump-disk, check, metadata
303 303 debugobsolete: flags, record-parents, rev, exclusive, index, delete, date, user, template
304 304 debugp1copies: rev
305 305 debugp2copies: rev
306 306 debugpathcomplete: full, normal, added, removed
307 307 debugpathcopies: include, exclude
308 308 debugpeer:
309 309 debugpickmergetool: rev, changedelete, include, exclude, tool
310 310 debugpushkey:
311 311 debugpvec:
312 312 debugrebuilddirstate: rev, minimal
313 313 debugrebuildfncache:
314 314 debugrename: rev
315 315 debugrequires:
316 316 debugrevlog: changelog, manifest, dir, dump
317 317 debugrevlogindex: changelog, manifest, dir, format
318 318 debugrevspec: optimize, show-revs, show-set, show-stage, no-optimized, verify-optimized
319 319 debugserve: sshstdio, logiofd, logiofile
320 320 debugsetparents:
321 321 debugsidedata: changelog, manifest, dir
322 322 debugssl:
323 323 debugstrip: rev, force, no-backup, nobackup, , keep, bookmark, soft
324 324 debugsub: rev
325 325 debugsuccessorssets: closest
326 326 debugtagscache:
327 327 debugtemplate: rev, define
328 328 debuguigetpass: prompt
329 329 debuguiprompt: prompt
330 330 debugupdatecaches:
331 331 debugupgraderepo: optimize, run, backup, changelog, manifest
332 332 debugwalk: include, exclude
333 333 debugwhyunstable:
334 334 debugwireargs: three, four, five, ssh, remotecmd, insecure
335 335 debugwireproto: localssh, peer, noreadstderr, nologhandshake, ssh, remotecmd, insecure
336 336 diff: rev, change, text, git, binary, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, unified, stat, root, include, exclude, subrepos
337 337 export: bookmark, output, switch-parent, rev, text, git, binary, nodates, template
338 338 files: rev, print0, include, exclude, template, subrepos
339 339 forget: interactive, include, exclude, dry-run
340 340 graft: rev, base, continue, stop, abort, edit, log, no-commit, force, currentdate, currentuser, date, user, tool, dry-run
341 341 grep: print0, all, diff, text, follow, ignore-case, files-with-matches, line-number, rev, all-files, user, date, template, include, exclude
342 342 heads: rev, topo, active, closed, style, template
343 343 help: extension, command, keyword, system
344 344 identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure, template
345 345 import: strip, base, secret, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity
346 346 incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
347 347 init: ssh, remotecmd, insecure
348 348 locate: rev, print0, fullpath, include, exclude
349 log: follow, follow-first, date, copies, keyword, rev, line-range, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
349 log: follow, follow-first, date, copies, keyword, rev, line-range, removed, only-merges, user, only-branch, branch, bookmark, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
350 350 manifest: rev, all, template
351 351 merge: force, rev, preview, abort, tool
352 352 outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
353 353 parents: rev, style, template
354 354 paths: template
355 355 phase: public, draft, secret, force, rev
356 356 pull: update, force, confirm, rev, bookmark, branch, ssh, remotecmd, insecure
357 357 push: force, rev, bookmark, all-bookmarks, branch, new-branch, pushvars, publish, ssh, remotecmd, insecure
358 358 recover: verify
359 359 remove: after, force, subrepos, include, exclude, dry-run
360 360 rename: after, at-rev, force, include, exclude, dry-run
361 361 resolve: all, list, mark, unmark, no-status, re-merge, tool, include, exclude, template
362 362 revert: all, date, rev, no-backup, interactive, include, exclude, dry-run
363 363 rollback: dry-run, force
364 364 root: template
365 365 serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, print-url, subrepos
366 366 shelve: addremove, unknown, cleanup, date, delete, edit, keep, list, message, name, patch, interactive, stat, include, exclude
367 367 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template
368 368 summary: remote
369 369 tag: force, local, rev, remove, edit, message, date, user
370 370 tags: template
371 371 tip: patch, git, style, template
372 372 unbundle: update
373 373 unshelve: abort, continue, interactive, keep, name, tool, date
374 374 update: clean, check, merge, date, rev, tool
375 375 verify: full
376 376 version: template
377 377
378 378 $ hg init a
379 379 $ cd a
380 380 $ echo fee > fee
381 381 $ hg ci -q -Amfee
382 382 $ hg tag fee
383 383 $ mkdir fie
384 384 $ echo dead > fie/dead
385 385 $ echo live > fie/live
386 386 $ hg bookmark fo
387 387 $ hg branch -q fie
388 388 $ hg ci -q -Amfie
389 389 $ echo fo > fo
390 390 $ hg branch -qf default
391 391 $ hg ci -q -Amfo
392 392 $ echo Fum > Fum
393 393 $ hg ci -q -AmFum
394 394 $ hg bookmark Fum
395 395
396 396 Test debugpathcomplete
397 397
398 398 $ hg debugpathcomplete f
399 399 fee
400 400 fie
401 401 fo
402 402 $ hg debugpathcomplete -f f
403 403 fee
404 404 fie/dead
405 405 fie/live
406 406 fo
407 407
408 408 $ hg rm Fum
409 409 $ hg debugpathcomplete -r F
410 410 Fum
411 411
412 412 Test debugnamecomplete
413 413
414 414 $ hg debugnamecomplete
415 415 Fum
416 416 default
417 417 fee
418 418 fie
419 419 fo
420 420 tip
421 421 $ hg debugnamecomplete f
422 422 fee
423 423 fie
424 424 fo
425 425
426 426 Test debuglabelcomplete, a deprecated name for debugnamecomplete that is still
427 427 used for completions in some shells.
428 428
429 429 $ hg debuglabelcomplete
430 430 Fum
431 431 default
432 432 fee
433 433 fie
434 434 fo
435 435 tip
436 436 $ hg debuglabelcomplete f
437 437 fee
438 438 fie
439 439 fo
General Comments 0
You need to be logged in to leave comments. Login now