comparison mercurial/logcmdutil.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents a68350a7fc55
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
39 from .utils import ( 39 from .utils import (
40 dateutil, 40 dateutil,
41 stringutil, 41 stringutil,
42 ) 42 )
43 43
44
44 def getlimit(opts): 45 def getlimit(opts):
45 """get the log limit according to option -l/--limit""" 46 """get the log limit according to option -l/--limit"""
46 limit = opts.get('limit') 47 limit = opts.get('limit')
47 if limit: 48 if limit:
48 try: 49 try:
53 raise error.Abort(_('limit must be positive')) 54 raise error.Abort(_('limit must be positive'))
54 else: 55 else:
55 limit = None 56 limit = None
56 return limit 57 return limit
57 58
58 def diffordiffstat(ui, repo, diffopts, node1, node2, match, 59
59 changes=None, stat=False, fp=None, graphwidth=0, 60 def diffordiffstat(
60 prefix='', root='', listsubrepos=False, hunksfilterfn=None): 61 ui,
62 repo,
63 diffopts,
64 node1,
65 node2,
66 match,
67 changes=None,
68 stat=False,
69 fp=None,
70 graphwidth=0,
71 prefix='',
72 root='',
73 listsubrepos=False,
74 hunksfilterfn=None,
75 ):
61 '''show diff or diffstat.''' 76 '''show diff or diffstat.'''
62 ctx1 = repo[node1] 77 ctx1 = repo[node1]
63 ctx2 = repo[node2] 78 ctx2 = repo[node2]
64 if root: 79 if root:
65 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root) 80 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
66 else: 81 else:
67 relroot = '' 82 relroot = ''
68 copysourcematch = None 83 copysourcematch = None
84
69 def compose(f, g): 85 def compose(f, g):
70 return lambda x: f(g(x)) 86 return lambda x: f(g(x))
87
71 def pathfn(f): 88 def pathfn(f):
72 return posixpath.join(prefix, f) 89 return posixpath.join(prefix, f)
90
73 if relroot != '': 91 if relroot != '':
74 # XXX relative roots currently don't work if the root is within a 92 # XXX relative roots currently don't work if the root is within a
75 # subrepo 93 # subrepo
76 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) 94 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
77 uirelroot = uipathfn(pathfn(relroot)) 95 uirelroot = uipathfn(pathfn(relroot))
78 relroot += '/' 96 relroot += '/'
79 for matchroot in match.files(): 97 for matchroot in match.files():
80 if not matchroot.startswith(relroot): 98 if not matchroot.startswith(relroot):
81 ui.warn(_('warning: %s not inside relative root %s\n') % 99 ui.warn(
82 (uipathfn(pathfn(matchroot)), uirelroot)) 100 _('warning: %s not inside relative root %s\n')
101 % (uipathfn(pathfn(matchroot)), uirelroot)
102 )
83 103
84 relrootmatch = scmutil.match(ctx2, pats=[relroot], default='path') 104 relrootmatch = scmutil.match(ctx2, pats=[relroot], default='path')
85 match = matchmod.intersectmatchers(match, relrootmatch) 105 match = matchmod.intersectmatchers(match, relrootmatch)
86 copysourcematch = relrootmatch 106 copysourcematch = relrootmatch
87 107
88 checkroot = (repo.ui.configbool('devel', 'all-warnings') or 108 checkroot = repo.ui.configbool(
89 repo.ui.configbool('devel', 'check-relroot')) 109 'devel', 'all-warnings'
110 ) or repo.ui.configbool('devel', 'check-relroot')
111
90 def relrootpathfn(f): 112 def relrootpathfn(f):
91 if checkroot and not f.startswith(relroot): 113 if checkroot and not f.startswith(relroot):
92 raise AssertionError( 114 raise AssertionError(
93 "file %s doesn't start with relroot %s" % (f, relroot)) 115 "file %s doesn't start with relroot %s" % (f, relroot)
94 return f[len(relroot):] 116 )
117 return f[len(relroot) :]
118
95 pathfn = compose(relrootpathfn, pathfn) 119 pathfn = compose(relrootpathfn, pathfn)
96 120
97 if stat: 121 if stat:
98 diffopts = diffopts.copy(context=0, noprefix=False) 122 diffopts = diffopts.copy(context=0, noprefix=False)
99 width = 80 123 width = 80
101 width = ui.termwidth() - graphwidth 125 width = ui.termwidth() - graphwidth
102 # If an explicit --root was given, don't respect ui.relative-paths 126 # If an explicit --root was given, don't respect ui.relative-paths
103 if not relroot: 127 if not relroot:
104 pathfn = compose(scmutil.getuipathfn(repo), pathfn) 128 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
105 129
106 chunks = ctx2.diff(ctx1, match, changes, opts=diffopts, pathfn=pathfn, 130 chunks = ctx2.diff(
107 copysourcematch=copysourcematch, 131 ctx1,
108 hunksfilterfn=hunksfilterfn) 132 match,
133 changes,
134 opts=diffopts,
135 pathfn=pathfn,
136 copysourcematch=copysourcematch,
137 hunksfilterfn=hunksfilterfn,
138 )
109 139
110 if fp is not None or ui.canwritewithoutlabels(): 140 if fp is not None or ui.canwritewithoutlabels():
111 out = fp or ui 141 out = fp or ui
112 if stat: 142 if stat:
113 chunks = [patch.diffstat(util.iterlines(chunks), width=width)] 143 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
115 out.write(chunk) 145 out.write(chunk)
116 else: 146 else:
117 if stat: 147 if stat:
118 chunks = patch.diffstatui(util.iterlines(chunks), width=width) 148 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
119 else: 149 else:
120 chunks = patch.difflabel(lambda chunks, **kwargs: chunks, chunks, 150 chunks = patch.difflabel(
121 opts=diffopts) 151 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
152 )
122 if ui.canbatchlabeledwrites(): 153 if ui.canbatchlabeledwrites():
154
123 def gen(): 155 def gen():
124 for chunk, label in chunks: 156 for chunk, label in chunks:
125 yield ui.label(chunk, label=label) 157 yield ui.label(chunk, label=label)
158
126 for chunk in util.filechunkiter(util.chunkbuffer(gen())): 159 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
127 ui.write(chunk) 160 ui.write(chunk)
128 else: 161 else:
129 for chunk, label in chunks: 162 for chunk, label in chunks:
130 ui.write(chunk, label=label) 163 ui.write(chunk, label=label)
140 # subpath. The best we can do is to ignore it. 173 # subpath. The best we can do is to ignore it.
141 tempnode2 = None 174 tempnode2 = None
142 submatch = matchmod.subdirmatcher(subpath, match) 175 submatch = matchmod.subdirmatcher(subpath, match)
143 subprefix = repo.wvfs.reljoin(prefix, subpath) 176 subprefix = repo.wvfs.reljoin(prefix, subpath)
144 if listsubrepos or match.exact(subpath) or any(submatch.files()): 177 if listsubrepos or match.exact(subpath) or any(submatch.files()):
145 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes, 178 sub.diff(
146 stat=stat, fp=fp, prefix=subprefix) 179 ui,
180 diffopts,
181 tempnode2,
182 submatch,
183 changes=changes,
184 stat=stat,
185 fp=fp,
186 prefix=subprefix,
187 )
188
147 189
148 class changesetdiffer(object): 190 class changesetdiffer(object):
149 """Generate diff of changeset with pre-configured filtering functions""" 191 """Generate diff of changeset with pre-configured filtering functions"""
150 192
151 def _makefilematcher(self, ctx): 193 def _makefilematcher(self, ctx):
156 198
157 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False): 199 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
158 repo = ctx.repo() 200 repo = ctx.repo()
159 node = ctx.node() 201 node = ctx.node()
160 prev = ctx.p1().node() 202 prev = ctx.p1().node()
161 diffordiffstat(ui, repo, diffopts, prev, node, 203 diffordiffstat(
162 match=self._makefilematcher(ctx), stat=stat, 204 ui,
163 graphwidth=graphwidth, 205 repo,
164 hunksfilterfn=self._makehunksfilter(ctx)) 206 diffopts,
207 prev,
208 node,
209 match=self._makefilematcher(ctx),
210 stat=stat,
211 graphwidth=graphwidth,
212 hunksfilterfn=self._makehunksfilter(ctx),
213 )
214
165 215
166 def changesetlabels(ctx): 216 def changesetlabels(ctx):
167 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()] 217 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
168 if ctx.obsolete(): 218 if ctx.obsolete():
169 labels.append('changeset.obsolete') 219 labels.append('changeset.obsolete')
170 if ctx.isunstable(): 220 if ctx.isunstable():
171 labels.append('changeset.unstable') 221 labels.append('changeset.unstable')
172 for instability in ctx.instabilities(): 222 for instability in ctx.instabilities():
173 labels.append('instability.%s' % instability) 223 labels.append('instability.%s' % instability)
174 return ' '.join(labels) 224 return ' '.join(labels)
225
175 226
176 class changesetprinter(object): 227 class changesetprinter(object):
177 '''show changeset information when templating not requested.''' 228 '''show changeset information when templating not requested.'''
178 229
179 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False): 230 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
219 '''show a single changeset or file revision''' 270 '''show a single changeset or file revision'''
220 changenode = ctx.node() 271 changenode = ctx.node()
221 graphwidth = props.get('graphwidth', 0) 272 graphwidth = props.get('graphwidth', 0)
222 273
223 if self.ui.quiet: 274 if self.ui.quiet:
224 self.ui.write("%s\n" % scmutil.formatchangeid(ctx), 275 self.ui.write(
225 label='log.node') 276 "%s\n" % scmutil.formatchangeid(ctx), label='log.node'
277 )
226 return 278 return
227 279
228 columns = self._columns 280 columns = self._columns
229 self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx), 281 self.ui.write(
230 label=changesetlabels(ctx)) 282 columns['changeset'] % scmutil.formatchangeid(ctx),
283 label=changesetlabels(ctx),
284 )
231 285
232 # branches are shown first before any other names due to backwards 286 # branches are shown first before any other names due to backwards
233 # compatibility 287 # compatibility
234 branch = ctx.branch() 288 branch = ctx.branch()
235 # don't show the default branch name 289 # don't show the default branch name
242 if nsname == 'branches': 296 if nsname == 'branches':
243 continue 297 continue
244 # we will use the templatename as the color name since those two 298 # we will use the templatename as the color name since those two
245 # should be the same 299 # should be the same
246 for name in ns.names(self.repo, changenode): 300 for name in ns.names(self.repo, changenode):
247 self.ui.write(ns.logfmt % name, 301 self.ui.write(ns.logfmt % name, label='log.%s' % ns.colorname)
248 label='log.%s' % ns.colorname)
249 if self.ui.debugflag: 302 if self.ui.debugflag:
250 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase') 303 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
251 for pctx in scmutil.meaningfulparents(self.repo, ctx): 304 for pctx in scmutil.meaningfulparents(self.repo, ctx):
252 label = 'log.parent changeset.%s' % pctx.phasestr() 305 label = 'log.parent changeset.%s' % pctx.phasestr()
253 self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx), 306 self.ui.write(
254 label=label) 307 columns['parent'] % scmutil.formatchangeid(pctx), label=label
308 )
255 309
256 if self.ui.debugflag: 310 if self.ui.debugflag:
257 mnode = ctx.manifestnode() 311 mnode = ctx.manifestnode()
258 if mnode is None: 312 if mnode is None:
259 mnode = wdirid 313 mnode = wdirid
260 mrev = wdirrev 314 mrev = wdirrev
261 else: 315 else:
262 mrev = self.repo.manifestlog.rev(mnode) 316 mrev = self.repo.manifestlog.rev(mnode)
263 self.ui.write(columns['manifest'] 317 self.ui.write(
264 % scmutil.formatrevnode(self.ui, mrev, mnode), 318 columns['manifest']
265 label='ui.debug log.manifest') 319 % scmutil.formatrevnode(self.ui, mrev, mnode),
320 label='ui.debug log.manifest',
321 )
266 self.ui.write(columns['user'] % ctx.user(), label='log.user') 322 self.ui.write(columns['user'] % ctx.user(), label='log.user')
267 self.ui.write(columns['date'] % dateutil.datestr(ctx.date()), 323 self.ui.write(
268 label='log.date') 324 columns['date'] % dateutil.datestr(ctx.date()), label='log.date'
325 )
269 326
270 if ctx.isunstable(): 327 if ctx.isunstable():
271 instabilities = ctx.instabilities() 328 instabilities = ctx.instabilities()
272 self.ui.write(columns['instability'] % ', '.join(instabilities), 329 self.ui.write(
273 label='log.instability') 330 columns['instability'] % ', '.join(instabilities),
331 label='log.instability',
332 )
274 333
275 elif ctx.obsolete(): 334 elif ctx.obsolete():
276 self._showobsfate(ctx) 335 self._showobsfate(ctx)
277 336
278 self._exthook(ctx) 337 self._exthook(ctx)
279 338
280 if self.ui.debugflag: 339 if self.ui.debugflag:
281 files = ctx.p1().status(ctx)[:3] 340 files = ctx.p1().status(ctx)[:3]
282 for key, value in zip(['files', 'files+', 'files-'], files): 341 for key, value in zip(['files', 'files+', 'files-'], files):
283 if value: 342 if value:
284 self.ui.write(columns[key] % " ".join(value), 343 self.ui.write(
285 label='ui.debug log.files') 344 columns[key] % " ".join(value),
345 label='ui.debug log.files',
346 )
286 elif ctx.files() and self.ui.verbose: 347 elif ctx.files() and self.ui.verbose:
287 self.ui.write(columns['files'] % " ".join(ctx.files()), 348 self.ui.write(
288 label='ui.note log.files') 349 columns['files'] % " ".join(ctx.files()),
350 label='ui.note log.files',
351 )
289 if copies and self.ui.verbose: 352 if copies and self.ui.verbose:
290 copies = ['%s (%s)' % c for c in copies] 353 copies = ['%s (%s)' % c for c in copies]
291 self.ui.write(columns['copies'] % ' '.join(copies), 354 self.ui.write(
292 label='ui.note log.copies') 355 columns['copies'] % ' '.join(copies), label='ui.note log.copies'
356 )
293 357
294 extra = ctx.extra() 358 extra = ctx.extra()
295 if extra and self.ui.debugflag: 359 if extra and self.ui.debugflag:
296 for key, value in sorted(extra.items()): 360 for key, value in sorted(extra.items()):
297 self.ui.write(columns['extra'] 361 self.ui.write(
298 % (key, stringutil.escapestr(value)), 362 columns['extra'] % (key, stringutil.escapestr(value)),
299 label='ui.debug log.extra') 363 label='ui.debug log.extra',
364 )
300 365
301 description = ctx.description().strip() 366 description = ctx.description().strip()
302 if description: 367 if description:
303 if self.ui.verbose: 368 if self.ui.verbose:
304 self.ui.write(_("description:\n"), 369 self.ui.write(
305 label='ui.note log.description') 370 _("description:\n"), label='ui.note log.description'
306 self.ui.write(description, 371 )
307 label='ui.note log.description') 372 self.ui.write(description, label='ui.note log.description')
308 self.ui.write("\n\n") 373 self.ui.write("\n\n")
309 else: 374 else:
310 self.ui.write(columns['summary'] % description.splitlines()[0], 375 self.ui.write(
311 label='log.summary') 376 columns['summary'] % description.splitlines()[0],
377 label='log.summary',
378 )
312 self.ui.write("\n") 379 self.ui.write("\n")
313 380
314 self._showpatch(ctx, graphwidth) 381 self._showpatch(ctx, graphwidth)
315 382
316 def _showobsfate(self, ctx): 383 def _showobsfate(self, ctx):
317 # TODO: do not depend on templater 384 # TODO: do not depend on templater
318 tres = formatter.templateresources(self.repo.ui, self.repo) 385 tres = formatter.templateresources(self.repo.ui, self.repo)
319 t = formatter.maketemplater(self.repo.ui, '{join(obsfate, "\n")}', 386 t = formatter.maketemplater(
320 defaults=templatekw.keywords, 387 self.repo.ui,
321 resources=tres) 388 '{join(obsfate, "\n")}',
389 defaults=templatekw.keywords,
390 resources=tres,
391 )
322 obsfate = t.renderdefault({'ctx': ctx}).splitlines() 392 obsfate = t.renderdefault({'ctx': ctx}).splitlines()
323 393
324 if obsfate: 394 if obsfate:
325 for obsfateline in obsfate: 395 for obsfateline in obsfate:
326 self.ui.write(self._columns['obsolete'] % obsfateline, 396 self.ui.write(
327 label='log.obsfate') 397 self._columns['obsolete'] % obsfateline, label='log.obsfate'
398 )
328 399
329 def _exthook(self, ctx): 400 def _exthook(self, ctx):
330 '''empty method used by extension as a hook point 401 '''empty method used by extension as a hook point
331 ''' 402 '''
332 403
333 def _showpatch(self, ctx, graphwidth=0): 404 def _showpatch(self, ctx, graphwidth=0):
334 if self._includestat: 405 if self._includestat:
335 self._differ.showdiff(self.ui, ctx, self._diffopts, 406 self._differ.showdiff(
336 graphwidth, stat=True) 407 self.ui, ctx, self._diffopts, graphwidth, stat=True
408 )
337 if self._includestat and self._includediff: 409 if self._includestat and self._includediff:
338 self.ui.write("\n") 410 self.ui.write("\n")
339 if self._includediff: 411 if self._includediff:
340 self._differ.showdiff(self.ui, ctx, self._diffopts, 412 self._differ.showdiff(
341 graphwidth, stat=False) 413 self.ui, ctx, self._diffopts, graphwidth, stat=False
414 )
342 if self._includestat or self._includediff: 415 if self._includestat or self._includediff:
343 self.ui.write("\n") 416 self.ui.write("\n")
344 417
418
345 class changesetformatter(changesetprinter): 419 class changesetformatter(changesetprinter):
346 """Format changeset information by generic formatter""" 420 """Format changeset information by generic formatter"""
347 421
348 def __init__(self, ui, repo, fm, differ=None, diffopts=None, 422 def __init__(
349 buffered=False): 423 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
424 ):
350 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered) 425 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
351 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True) 426 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
352 self._fm = fm 427 self._fm = fm
353 428
354 def close(self): 429 def close(self):
357 def _show(self, ctx, copies, props): 432 def _show(self, ctx, copies, props):
358 '''show a single changeset or file revision''' 433 '''show a single changeset or file revision'''
359 fm = self._fm 434 fm = self._fm
360 fm.startitem() 435 fm.startitem()
361 fm.context(ctx=ctx) 436 fm.context(ctx=ctx)
362 fm.data(rev=scmutil.intrev(ctx), 437 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
363 node=fm.hexfunc(scmutil.binnode(ctx)))
364 438
365 if self.ui.quiet: 439 if self.ui.quiet:
366 return 440 return
367 441
368 fm.data(branch=ctx.branch(), 442 fm.data(
369 phase=ctx.phasestr(), 443 branch=ctx.branch(),
370 user=ctx.user(), 444 phase=ctx.phasestr(),
371 date=fm.formatdate(ctx.date()), 445 user=ctx.user(),
372 desc=ctx.description(), 446 date=fm.formatdate(ctx.date()),
373 bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'), 447 desc=ctx.description(),
374 tags=fm.formatlist(ctx.tags(), name='tag'), 448 bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'),
375 parents=fm.formatlist([fm.hexfunc(c.node()) 449 tags=fm.formatlist(ctx.tags(), name='tag'),
376 for c in ctx.parents()], name='node')) 450 parents=fm.formatlist(
451 [fm.hexfunc(c.node()) for c in ctx.parents()], name='node'
452 ),
453 )
377 454
378 if self.ui.debugflag: 455 if self.ui.debugflag:
379 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid), 456 fm.data(
380 extra=fm.formatdict(ctx.extra())) 457 manifest=fm.hexfunc(ctx.manifestnode() or wdirid),
458 extra=fm.formatdict(ctx.extra()),
459 )
381 460
382 files = ctx.p1().status(ctx) 461 files = ctx.p1().status(ctx)
383 fm.data(modified=fm.formatlist(files[0], name='file'), 462 fm.data(
384 added=fm.formatlist(files[1], name='file'), 463 modified=fm.formatlist(files[0], name='file'),
385 removed=fm.formatlist(files[2], name='file')) 464 added=fm.formatlist(files[1], name='file'),
465 removed=fm.formatlist(files[2], name='file'),
466 )
386 467
387 elif self.ui.verbose: 468 elif self.ui.verbose:
388 fm.data(files=fm.formatlist(ctx.files(), name='file')) 469 fm.data(files=fm.formatlist(ctx.files(), name='file'))
389 if copies: 470 if copies:
390 fm.data(copies=fm.formatdict(copies, 471 fm.data(
391 key='name', value='source')) 472 copies=fm.formatdict(copies, key='name', value='source')
473 )
392 474
393 if self._includestat: 475 if self._includestat:
394 self.ui.pushbuffer() 476 self.ui.pushbuffer()
395 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True) 477 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
396 fm.data(diffstat=self.ui.popbuffer()) 478 fm.data(diffstat=self.ui.popbuffer())
397 if self._includediff: 479 if self._includediff:
398 self.ui.pushbuffer() 480 self.ui.pushbuffer()
399 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False) 481 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
400 fm.data(diff=self.ui.popbuffer()) 482 fm.data(diff=self.ui.popbuffer())
401 483
484
402 class changesettemplater(changesetprinter): 485 class changesettemplater(changesetprinter):
403 '''format changeset information. 486 '''format changeset information.
404 487
405 Note: there are a variety of convenience functions to build a 488 Note: there are a variety of convenience functions to build a
406 changesettemplater for common cases. See functions such as: 489 changesettemplater for common cases. See functions such as:
408 functions that use changesest_templater. 491 functions that use changesest_templater.
409 ''' 492 '''
410 493
411 # Arguments before "buffered" used to be positional. Consider not 494 # Arguments before "buffered" used to be positional. Consider not
412 # adding/removing arguments before "buffered" to not break callers. 495 # adding/removing arguments before "buffered" to not break callers.
413 def __init__(self, ui, repo, tmplspec, differ=None, diffopts=None, 496 def __init__(
414 buffered=False): 497 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
498 ):
415 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered) 499 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
416 # tres is shared with _graphnodeformatter() 500 # tres is shared with _graphnodeformatter()
417 self._tresources = tres = formatter.templateresources(ui, repo) 501 self._tresources = tres = formatter.templateresources(ui, repo)
418 self.t = formatter.loadtemplater(ui, tmplspec, 502 self.t = formatter.loadtemplater(
419 defaults=templatekw.keywords, 503 ui,
420 resources=tres, 504 tmplspec,
421 cache=templatekw.defaulttempl) 505 defaults=templatekw.keywords,
506 resources=tres,
507 cache=templatekw.defaulttempl,
508 )
422 self._counter = itertools.count() 509 self._counter = itertools.count()
423 510
424 self._tref = tmplspec.ref 511 self._tref = tmplspec.ref
425 self._parts = {'header': '', 'footer': '', 512 self._parts = {
426 tmplspec.ref: tmplspec.ref, 513 'header': '',
427 'docheader': '', 'docfooter': '', 514 'footer': '',
428 'separator': ''} 515 tmplspec.ref: tmplspec.ref,
516 'docheader': '',
517 'docfooter': '',
518 'separator': '',
519 }
429 if tmplspec.mapfile: 520 if tmplspec.mapfile:
430 # find correct templates for current mode, for backward 521 # find correct templates for current mode, for backward
431 # compatibility with 'log -v/-q/--debug' using a mapfile 522 # compatibility with 'log -v/-q/--debug' using a mapfile
432 tmplmodes = [ 523 tmplmodes = [
433 (True, ''), 524 (True, ''),
486 577
487 if self._parts['footer']: 578 if self._parts['footer']:
488 if not self.footer: 579 if not self.footer:
489 self.footer = self.t.render(self._parts['footer'], props) 580 self.footer = self.t.render(self._parts['footer'], props)
490 581
582
491 def templatespec(tmpl, mapfile): 583 def templatespec(tmpl, mapfile):
492 if pycompat.ispy3: 584 if pycompat.ispy3:
493 assert not isinstance(tmpl, str), 'tmpl must not be a str' 585 assert not isinstance(tmpl, str), 'tmpl must not be a str'
494 if mapfile: 586 if mapfile:
495 return formatter.templatespec('changeset', tmpl, mapfile) 587 return formatter.templatespec('changeset', tmpl, mapfile)
496 else: 588 else:
497 return formatter.templatespec('', tmpl, None) 589 return formatter.templatespec('', tmpl, None)
498 590
591
499 def _lookuptemplate(ui, tmpl, style): 592 def _lookuptemplate(ui, tmpl, style):
500 """Find the template matching the given template spec or style 593 """Find the template matching the given template spec or style
501 594
502 See formatter.lookuptemplate() for details. 595 See formatter.lookuptemplate() for details.
503 """ 596 """
504 597
505 # ui settings 598 # ui settings
506 if not tmpl and not style: # template are stronger than style 599 if not tmpl and not style: # template are stronger than style
507 tmpl = ui.config('ui', 'logtemplate') 600 tmpl = ui.config('ui', 'logtemplate')
508 if tmpl: 601 if tmpl:
509 return templatespec(templater.unquotestring(tmpl), None) 602 return templatespec(templater.unquotestring(tmpl), None)
510 else: 603 else:
511 style = util.expandpath(ui.config('ui', 'style')) 604 style = util.expandpath(ui.config('ui', 'style'))
512 605
513 if not tmpl and style: 606 if not tmpl and style:
514 mapfile = style 607 mapfile = style
515 if not os.path.split(mapfile)[0]: 608 if not os.path.split(mapfile)[0]:
516 mapname = (templater.templatepath('map-cmdline.' + mapfile) 609 mapname = templater.templatepath(
517 or templater.templatepath(mapfile)) 610 'map-cmdline.' + mapfile
611 ) or templater.templatepath(mapfile)
518 if mapname: 612 if mapname:
519 mapfile = mapname 613 mapfile = mapname
520 return templatespec(None, mapfile) 614 return templatespec(None, mapfile)
521 615
522 if not tmpl: 616 if not tmpl:
523 return templatespec(None, None) 617 return templatespec(None, None)
524 618
525 return formatter.lookuptemplate(ui, 'changeset', tmpl) 619 return formatter.lookuptemplate(ui, 'changeset', tmpl)
620
526 621
527 def maketemplater(ui, repo, tmpl, buffered=False): 622 def maketemplater(ui, repo, tmpl, buffered=False):
528 """Create a changesettemplater from a literal template 'tmpl' 623 """Create a changesettemplater from a literal template 'tmpl'
529 byte-string.""" 624 byte-string."""
530 spec = templatespec(tmpl, None) 625 spec = templatespec(tmpl, None)
531 return changesettemplater(ui, repo, spec, buffered=buffered) 626 return changesettemplater(ui, repo, spec, buffered=buffered)
627
532 628
533 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False): 629 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
534 """show one changeset using template or regular display. 630 """show one changeset using template or regular display.
535 631
536 Display format will be the first non-empty hit of: 632 Display format will be the first non-empty hit of:
550 646
551 if not spec.ref and not spec.tmpl and not spec.mapfile: 647 if not spec.ref and not spec.tmpl and not spec.mapfile:
552 return changesetprinter(ui, repo, *postargs) 648 return changesetprinter(ui, repo, *postargs)
553 649
554 return changesettemplater(ui, repo, spec, *postargs) 650 return changesettemplater(ui, repo, spec, *postargs)
651
555 652
556 def _makematcher(repo, revs, pats, opts): 653 def _makematcher(repo, revs, pats, opts):
557 """Build matcher and expanded patterns from log options 654 """Build matcher and expanded patterns from log options
558 655
559 If --follow, revs are the revisions to follow from. 656 If --follow, revs are the revisions to follow from.
587 # take the slow path. 684 # take the slow path.
588 if os.path.exists(repo.wjoin(f)): 685 if os.path.exists(repo.wjoin(f)):
589 slowpath = True 686 slowpath = True
590 continue 687 continue
591 else: 688 else:
592 raise error.Abort(_('cannot follow file not in parent ' 689 raise error.Abort(
593 'revision: "%s"') % f) 690 _('cannot follow file not in parent ' 'revision: "%s"')
691 % f
692 )
594 filelog = repo.file(f) 693 filelog = repo.file(f)
595 if not filelog: 694 if not filelog:
596 # A zero count may be a directory or deleted file, so 695 # A zero count may be a directory or deleted file, so
597 # try to find matching entries on the slow path. 696 # try to find matching entries on the slow path.
598 if follow: 697 if follow:
599 raise error.Abort( 698 raise error.Abort(
600 _('cannot follow nonexistent file: "%s"') % f) 699 _('cannot follow nonexistent file: "%s"') % f
700 )
601 slowpath = True 701 slowpath = True
602 702
603 # We decided to fall back to the slowpath because at least one 703 # We decided to fall back to the slowpath because at least one
604 # of the paths was not a file. Check to see if at least one of them 704 # of the paths was not a file. Check to see if at least one of them
605 # existed in history - in that case, we'll continue down the 705 # existed in history - in that case, we'll continue down the
611 else: 711 else:
612 slowpath = False 712 slowpath = False
613 713
614 return match, pats, slowpath 714 return match, pats, slowpath
615 715
716
616 def _fileancestors(repo, revs, match, followfirst): 717 def _fileancestors(repo, revs, match, followfirst):
617 fctxs = [] 718 fctxs = []
618 for r in revs: 719 for r in revs:
619 ctx = repo[r] 720 ctx = repo[r]
620 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match)) 721 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
623 # to know which file of the revision must be diffed. With 724 # to know which file of the revision must be diffed. With
624 # --follow, we want the names of the ancestors of FILE in the 725 # --follow, we want the names of the ancestors of FILE in the
625 # revision, stored in "fcache". "fcache" is populated as a side effect 726 # revision, stored in "fcache". "fcache" is populated as a side effect
626 # of the graph traversal. 727 # of the graph traversal.
627 fcache = {} 728 fcache = {}
729
628 def filematcher(ctx): 730 def filematcher(ctx):
629 return scmutil.matchfiles(repo, fcache.get(ctx.rev(), [])) 731 return scmutil.matchfiles(repo, fcache.get(ctx.rev(), []))
630 732
631 def revgen(): 733 def revgen():
632 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst): 734 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
633 fcache[rev] = [c.path() for c in cs] 735 fcache[rev] = [c.path() for c in cs]
634 yield rev 736 yield rev
737
635 return smartset.generatorset(revgen(), iterasc=False), filematcher 738 return smartset.generatorset(revgen(), iterasc=False), filematcher
739
636 740
637 def _makenofollowfilematcher(repo, pats, opts): 741 def _makenofollowfilematcher(repo, pats, opts):
638 '''hook for extensions to override the filematcher for non-follow cases''' 742 '''hook for extensions to override the filematcher for non-follow cases'''
639 return None 743 return None
640 744
745
641 _opt2logrevset = { 746 _opt2logrevset = {
642 'no_merges': ('not merge()', None), 747 'no_merges': ('not merge()', None),
643 'only_merges': ('merge()', None), 748 'only_merges': ('merge()', None),
644 '_matchfiles': (None, '_matchfiles(%ps)'), 749 '_matchfiles': (None, '_matchfiles(%ps)'),
645 'date': ('date(%s)', None), 750 'date': ('date(%s)', None),
646 'branch': ('branch(%s)', '%lr'), 751 'branch': ('branch(%s)', '%lr'),
647 '_patslog': ('filelog(%s)', '%lr'), 752 '_patslog': ('filelog(%s)', '%lr'),
648 'keyword': ('keyword(%s)', '%lr'), 753 'keyword': ('keyword(%s)', '%lr'),
649 'prune': ('ancestors(%s)', 'not %lr'), 754 'prune': ('ancestors(%s)', 'not %lr'),
650 'user': ('user(%s)', '%lr'), 755 'user': ('user(%s)', '%lr'),
651 } 756 }
757
652 758
653 def _makerevset(repo, match, pats, slowpath, opts): 759 def _makerevset(repo, match, pats, slowpath, opts):
654 """Return a revset string built from log options and file patterns""" 760 """Return a revset string built from log options and file patterns"""
655 opts = dict(opts) 761 opts = dict(opts)
656 # follow or not follow? 762 # follow or not follow?
701 expr = '(' + ' and '.join(expr) + ')' 807 expr = '(' + ' and '.join(expr) + ')'
702 else: 808 else:
703 expr = None 809 expr = None
704 return expr 810 return expr
705 811
812
706 def _initialrevs(repo, opts): 813 def _initialrevs(repo, opts):
707 """Return the initial set of revisions to be filtered or followed""" 814 """Return the initial set of revisions to be filtered or followed"""
708 follow = opts.get('follow') or opts.get('follow_first') 815 follow = opts.get('follow') or opts.get('follow_first')
709 if opts.get('rev'): 816 if opts.get('rev'):
710 revs = scmutil.revrange(repo, opts['rev']) 817 revs = scmutil.revrange(repo, opts['rev'])
714 revs = repo.revs('.') 821 revs = repo.revs('.')
715 else: 822 else:
716 revs = smartset.spanset(repo) 823 revs = smartset.spanset(repo)
717 revs.reverse() 824 revs.reverse()
718 return revs 825 return revs
826
719 827
720 def getrevs(repo, pats, opts): 828 def getrevs(repo, pats, opts):
721 """Return (revs, differ) where revs is a smartset 829 """Return (revs, differ) where revs is a smartset
722 830
723 differ is a changesetdiffer with pre-configured file matcher. 831 differ is a changesetdiffer with pre-configured file matcher.
737 revs, filematcher = _fileancestors(repo, revs, match, followfirst) 845 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
738 revs.reverse() 846 revs.reverse()
739 if filematcher is None: 847 if filematcher is None:
740 filematcher = _makenofollowfilematcher(repo, pats, opts) 848 filematcher = _makenofollowfilematcher(repo, pats, opts)
741 if filematcher is None: 849 if filematcher is None:
850
742 def filematcher(ctx): 851 def filematcher(ctx):
743 return match 852 return match
744 853
745 expr = _makerevset(repo, match, pats, slowpath, opts) 854 expr = _makerevset(repo, match, pats, slowpath, opts)
746 if opts.get('graph'): 855 if opts.get('graph'):
760 revs = revs.slice(0, limit) 869 revs = revs.slice(0, limit)
761 870
762 differ = changesetdiffer() 871 differ = changesetdiffer()
763 differ._makefilematcher = filematcher 872 differ._makefilematcher = filematcher
764 return revs, differ 873 return revs, differ
874
765 875
766 def _parselinerangeopt(repo, opts): 876 def _parselinerangeopt(repo, opts):
767 """Parse --line-range log option and return a list of tuples (filename, 877 """Parse --line-range log option and return a list of tuples (filename,
768 (fromline, toline)). 878 (fromline, toline)).
769 """ 879 """
778 except ValueError: 888 except ValueError:
779 raise error.Abort(_("invalid line range for %s") % pat) 889 raise error.Abort(_("invalid line range for %s") % pat)
780 msg = _("line range pattern '%s' must match exactly one file") % pat 890 msg = _("line range pattern '%s' must match exactly one file") % pat
781 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg) 891 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
782 linerangebyfname.append( 892 linerangebyfname.append(
783 (fname, util.processlinerange(fromline, toline))) 893 (fname, util.processlinerange(fromline, toline))
894 )
784 return linerangebyfname 895 return linerangebyfname
896
785 897
786 def getlinerangerevs(repo, userrevs, opts): 898 def getlinerangerevs(repo, userrevs, opts):
787 """Return (revs, differ). 899 """Return (revs, differ).
788 900
789 "revs" are revisions obtained by processing "line-range" log options and 901 "revs" are revisions obtained by processing "line-range" log options and
796 908
797 # Two-levels map of "rev -> file ctx -> [line range]". 909 # Two-levels map of "rev -> file ctx -> [line range]".
798 linerangesbyrev = {} 910 linerangesbyrev = {}
799 for fname, (fromline, toline) in _parselinerangeopt(repo, opts): 911 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
800 if fname not in wctx: 912 if fname not in wctx:
801 raise error.Abort(_('cannot follow file not in parent ' 913 raise error.Abort(
802 'revision: "%s"') % fname) 914 _('cannot follow file not in parent ' 'revision: "%s"') % fname
915 )
803 fctx = wctx.filectx(fname) 916 fctx = wctx.filectx(fname)
804 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline): 917 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
805 rev = fctx.introrev() 918 rev = fctx.introrev()
806 if rev not in userrevs: 919 if rev not in userrevs:
807 continue 920 continue
808 linerangesbyrev.setdefault( 921 linerangesbyrev.setdefault(rev, {}).setdefault(
809 rev, {}).setdefault( 922 fctx.path(), []
810 fctx.path(), []).append(linerange) 923 ).append(linerange)
811 924
812 def nofilterhunksfn(fctx, hunks): 925 def nofilterhunksfn(fctx, hunks):
813 return hunks 926 return hunks
814 927
815 def hunksfilter(ctx): 928 def hunksfilter(ctx):
819 932
820 def filterfn(fctx, hunks): 933 def filterfn(fctx, hunks):
821 lineranges = fctxlineranges.get(fctx.path()) 934 lineranges = fctxlineranges.get(fctx.path())
822 if lineranges is not None: 935 if lineranges is not None:
823 for hr, lines in hunks: 936 for hr, lines in hunks:
824 if hr is None: # binary 937 if hr is None: # binary
825 yield hr, lines 938 yield hr, lines
826 continue 939 continue
827 if any(mdiff.hunkinrange(hr[2:], lr) 940 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
828 for lr in lineranges):
829 yield hr, lines 941 yield hr, lines
830 else: 942 else:
831 for hunk in hunks: 943 for hunk in hunks:
832 yield hunk 944 yield hunk
833 945
841 953
842 differ = changesetdiffer() 954 differ = changesetdiffer()
843 differ._makefilematcher = filematcher 955 differ._makefilematcher = filematcher
844 differ._makehunksfilter = hunksfilter 956 differ._makehunksfilter = hunksfilter
845 return revs, differ 957 return revs, differ
958
846 959
847 def _graphnodeformatter(ui, displayer): 960 def _graphnodeformatter(ui, displayer):
848 spec = ui.config('ui', 'graphnodetemplate') 961 spec = ui.config('ui', 'graphnodetemplate')
849 if not spec: 962 if not spec:
850 return templatekw.getgraphnode # fast path for "{graphnode}" 963 return templatekw.getgraphnode # fast path for "{graphnode}"
853 if isinstance(displayer, changesettemplater): 966 if isinstance(displayer, changesettemplater):
854 # reuse cache of slow templates 967 # reuse cache of slow templates
855 tres = displayer._tresources 968 tres = displayer._tresources
856 else: 969 else:
857 tres = formatter.templateresources(ui) 970 tres = formatter.templateresources(ui)
858 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords, 971 templ = formatter.maketemplater(
859 resources=tres) 972 ui, spec, defaults=templatekw.keywords, resources=tres
973 )
974
860 def formatnode(repo, ctx): 975 def formatnode(repo, ctx):
861 props = {'ctx': ctx, 'repo': repo} 976 props = {'ctx': ctx, 'repo': repo}
862 return templ.renderdefault(props) 977 return templ.renderdefault(props)
978
863 return formatnode 979 return formatnode
980
864 981
865 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None): 982 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
866 props = props or {} 983 props = props or {}
867 formatnode = _graphnodeformatter(ui, displayer) 984 formatnode = _graphnodeformatter(ui, displayer)
868 state = graphmod.asciistate() 985 state = graphmod.asciistate()
874 styles.update(dict.fromkeys(styles, '|')) 991 styles.update(dict.fromkeys(styles, '|'))
875 else: 992 else:
876 edgetypes = { 993 edgetypes = {
877 'parent': graphmod.PARENT, 994 'parent': graphmod.PARENT,
878 'grandparent': graphmod.GRANDPARENT, 995 'grandparent': graphmod.GRANDPARENT,
879 'missing': graphmod.MISSINGPARENT 996 'missing': graphmod.MISSINGPARENT,
880 } 997 }
881 for name, key in edgetypes.items(): 998 for name, key in edgetypes.items():
882 # experimental config: experimental.graphstyle.* 999 # experimental config: experimental.graphstyle.*
883 styles[key] = ui.config('experimental', 'graphstyle.%s' % name, 1000 styles[key] = ui.config(
884 styles[key]) 1001 'experimental', 'graphstyle.%s' % name, styles[key]
1002 )
885 if not styles[key]: 1003 if not styles[key]:
886 styles[key] = None 1004 styles[key] = None
887 1005
888 # experimental config: experimental.graphshorten 1006 # experimental config: experimental.graphshorten
889 state['graphshorten'] = ui.configbool('experimental', 'graphshorten') 1007 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
892 char = formatnode(repo, ctx) 1010 char = formatnode(repo, ctx)
893 copies = getcopies(ctx) if getcopies else None 1011 copies = getcopies(ctx) if getcopies else None
894 edges = edgefn(type, char, state, rev, parents) 1012 edges = edgefn(type, char, state, rev, parents)
895 firstedge = next(edges) 1013 firstedge = next(edges)
896 width = firstedge[2] 1014 width = firstedge[2]
897 displayer.show(ctx, copies=copies, 1015 displayer.show(
898 graphwidth=width, **pycompat.strkwargs(props)) 1016 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1017 )
899 lines = displayer.hunk.pop(rev).split('\n') 1018 lines = displayer.hunk.pop(rev).split('\n')
900 if not lines[-1]: 1019 if not lines[-1]:
901 del lines[-1] 1020 del lines[-1]
902 displayer.flush(ctx) 1021 displayer.flush(ctx)
903 for type, char, width, coldata in itertools.chain([firstedge], edges): 1022 for type, char, width, coldata in itertools.chain([firstedge], edges):
904 graphmod.ascii(ui, state, type, char, lines, coldata) 1023 graphmod.ascii(ui, state, type, char, lines, coldata)
905 lines = [] 1024 lines = []
906 displayer.close() 1025 displayer.close()
907 1026
1027
908 def displaygraphrevs(ui, repo, revs, displayer, getrenamed): 1028 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
909 revdag = graphmod.dagwalker(repo, revs) 1029 revdag = graphmod.dagwalker(repo, revs)
910 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed) 1030 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1031
911 1032
912 def displayrevs(ui, repo, revs, displayer, getcopies): 1033 def displayrevs(ui, repo, revs, displayer, getcopies):
913 for rev in revs: 1034 for rev in revs:
914 ctx = repo[rev] 1035 ctx = repo[rev]
915 copies = getcopies(ctx) if getcopies else None 1036 copies = getcopies(ctx) if getcopies else None
916 displayer.show(ctx, copies=copies) 1037 displayer.show(ctx, copies=copies)
917 displayer.flush(ctx) 1038 displayer.flush(ctx)
918 displayer.close() 1039 displayer.close()
919 1040
1041
920 def checkunsupportedgraphflags(pats, opts): 1042 def checkunsupportedgraphflags(pats, opts):
921 for op in ["newest_first"]: 1043 for op in ["newest_first"]:
922 if op in opts and opts[op]: 1044 if op in opts and opts[op]:
923 raise error.Abort(_("-G/--graph option is incompatible with --%s") 1045 raise error.Abort(
924 % op.replace("_", "-")) 1046 _("-G/--graph option is incompatible with --%s")
1047 % op.replace("_", "-")
1048 )
1049
925 1050
926 def graphrevs(repo, nodes, opts): 1051 def graphrevs(repo, nodes, opts):
927 limit = getlimit(opts) 1052 limit = getlimit(opts)
928 nodes.reverse() 1053 nodes.reverse()
929 if limit is not None: 1054 if limit is not None: