comparison mercurial/logcmdutil.py @ 36007:29b83c08afe0

log: pack filematcher and hunksfilter into changesetdiffer object This is just a way of getting rid of clumsy makefilematcher/makehunksfilter arguments. There might be a better abstraction, but I don't think this is bad. This makes filematcher and hunksfilter available by default, but that should be fine.
author Yuya Nishihara <yuya@tcha.org>
date Sun, 21 Jan 2018 15:54:18 +0900
parents dd77e36eabb6
children 006ff7268c5c
comparison
equal deleted inserted replaced
36006:f113ac0750f3 36007:29b83c08afe0
120 tempnode2 = None 120 tempnode2 = None
121 submatch = matchmod.subdirmatcher(subpath, match) 121 submatch = matchmod.subdirmatcher(subpath, match)
122 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes, 122 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
123 stat=stat, fp=fp, prefix=prefix) 123 stat=stat, fp=fp, prefix=prefix)
124 124
125 class changesetdiffer(object):
126 """Generate diff of changeset with pre-configured filtering functions"""
127
128 def _makefilematcher(self, ctx):
129 return scmutil.matchall(ctx.repo())
130
131 def _makehunksfilter(self, ctx):
132 return None
133
134 def showdiff(self, ui, ctx, diffopts, stat=False):
135 repo = ctx.repo()
136 node = ctx.node()
137 prev = ctx.p1().node()
138 diffordiffstat(ui, repo, diffopts, prev, node,
139 match=self._makefilematcher(ctx), stat=stat,
140 hunksfilterfn=self._makehunksfilter(ctx))
141
125 def changesetlabels(ctx): 142 def changesetlabels(ctx):
126 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()] 143 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
127 if ctx.obsolete(): 144 if ctx.obsolete():
128 labels.append('changeset.obsolete') 145 labels.append('changeset.obsolete')
129 if ctx.isunstable(): 146 if ctx.isunstable():
133 return ' '.join(labels) 150 return ' '.join(labels)
134 151
135 class changesetprinter(object): 152 class changesetprinter(object):
136 '''show changeset information when templating not requested.''' 153 '''show changeset information when templating not requested.'''
137 154
138 def __init__(self, ui, repo, makefilematcher=None, makehunksfilter=None, 155 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
139 diffopts=None, buffered=False):
140 self.ui = ui 156 self.ui = ui
141 self.repo = repo 157 self.repo = repo
142 self.buffered = buffered 158 self.buffered = buffered
143 self._makefilematcher = makefilematcher or (lambda ctx: None) 159 self._differ = differ or changesetdiffer()
144 self._makehunksfilter = makehunksfilter or (lambda ctx: None)
145 self.diffopts = diffopts or {} 160 self.diffopts = diffopts or {}
146 self.header = {} 161 self.header = {}
147 self.hunk = {} 162 self.hunk = {}
148 self.lastheader = None 163 self.lastheader = None
149 self.footer = None 164 self.footer = None
278 def _exthook(self, ctx): 293 def _exthook(self, ctx):
279 '''empty method used by extension as a hook point 294 '''empty method used by extension as a hook point
280 ''' 295 '''
281 296
282 def _showpatch(self, ctx): 297 def _showpatch(self, ctx):
283 matchfn = self._makefilematcher(ctx)
284 hunksfilterfn = self._makehunksfilter(ctx)
285 if not matchfn:
286 return
287 stat = self.diffopts.get('stat') 298 stat = self.diffopts.get('stat')
288 diff = self.diffopts.get('patch') 299 diff = self.diffopts.get('patch')
289 diffopts = patch.diffallopts(self.ui, self.diffopts) 300 diffopts = patch.diffallopts(self.ui, self.diffopts)
290 node = ctx.node()
291 prev = ctx.p1().node()
292 if stat: 301 if stat:
293 diffordiffstat(self.ui, self.repo, diffopts, prev, node, 302 self._differ.showdiff(self.ui, ctx, diffopts, stat=True)
294 match=matchfn, stat=True,
295 hunksfilterfn=hunksfilterfn)
296 if stat and diff: 303 if stat and diff:
297 self.ui.write("\n") 304 self.ui.write("\n")
298 if diff: 305 if diff:
299 diffordiffstat(self.ui, self.repo, diffopts, prev, node, 306 self._differ.showdiff(self.ui, ctx, diffopts, stat=False)
300 match=matchfn, stat=False,
301 hunksfilterfn=hunksfilterfn)
302 if stat or diff: 307 if stat or diff:
303 self.ui.write("\n") 308 self.ui.write("\n")
304 309
305 class jsonchangeset(changesetprinter): 310 class jsonchangeset(changesetprinter):
306 '''format changeset information.''' 311 '''format changeset information.'''
307 312
308 def __init__(self, ui, repo, makefilematcher=None, makehunksfilter=None, 313 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
309 diffopts=None, buffered=False): 314 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
310 changesetprinter.__init__(self, ui, repo, makefilematcher,
311 makehunksfilter, diffopts, buffered)
312 self.cache = {} 315 self.cache = {}
313 self._first = True 316 self._first = True
314 317
315 def close(self): 318 def close(self):
316 if not self._first: 319 if not self._first:
381 if copies: 384 if copies:
382 self.ui.write((',\n "copies": {%s}') % 385 self.ui.write((',\n "copies": {%s}') %
383 ", ".join('"%s": "%s"' % (j(k), j(v)) 386 ", ".join('"%s": "%s"' % (j(k), j(v))
384 for k, v in copies)) 387 for k, v in copies))
385 388
386 matchfn = self._makefilematcher(ctx)
387 stat = self.diffopts.get('stat') 389 stat = self.diffopts.get('stat')
388 diff = self.diffopts.get('patch') 390 diff = self.diffopts.get('patch')
389 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True) 391 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
390 node, prev = ctx.node(), ctx.p1().node() 392 if stat:
391 if matchfn and stat:
392 self.ui.pushbuffer() 393 self.ui.pushbuffer()
393 diffordiffstat(self.ui, self.repo, diffopts, prev, node, 394 self._differ.showdiff(self.ui, ctx, diffopts, stat=True)
394 match=matchfn, stat=True)
395 self.ui.write((',\n "diffstat": "%s"') 395 self.ui.write((',\n "diffstat": "%s"')
396 % j(self.ui.popbuffer())) 396 % j(self.ui.popbuffer()))
397 if matchfn and diff: 397 if diff:
398 self.ui.pushbuffer() 398 self.ui.pushbuffer()
399 diffordiffstat(self.ui, self.repo, diffopts, prev, node, 399 self._differ.showdiff(self.ui, ctx, diffopts, stat=False)
400 match=matchfn, stat=False)
401 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer())) 400 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
402 401
403 self.ui.write("\n }") 402 self.ui.write("\n }")
404 403
405 class changesettemplater(changesetprinter): 404 class changesettemplater(changesetprinter):
411 functions that use changesest_templater. 410 functions that use changesest_templater.
412 ''' 411 '''
413 412
414 # Arguments before "buffered" used to be positional. Consider not 413 # Arguments before "buffered" used to be positional. Consider not
415 # adding/removing arguments before "buffered" to not break callers. 414 # adding/removing arguments before "buffered" to not break callers.
416 def __init__(self, ui, repo, tmplspec, makefilematcher=None, 415 def __init__(self, ui, repo, tmplspec, differ=None, diffopts=None,
417 makehunksfilter=None, diffopts=None, buffered=False): 416 buffered=False):
418 changesetprinter.__init__(self, ui, repo, makefilematcher, 417 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
419 makehunksfilter, diffopts, buffered)
420 tres = formatter.templateresources(ui, repo) 418 tres = formatter.templateresources(ui, repo)
421 self.t = formatter.loadtemplater(ui, tmplspec, 419 self.t = formatter.loadtemplater(ui, tmplspec,
422 defaults=templatekw.keywords, 420 defaults=templatekw.keywords,
423 resources=tres, 421 resources=tres,
424 cache=templatekw.defaulttempl) 422 cache=templatekw.defaulttempl)
531 """Create a changesettemplater from a literal template 'tmpl' 529 """Create a changesettemplater from a literal template 'tmpl'
532 byte-string.""" 530 byte-string."""
533 spec = templatespec(tmpl, None) 531 spec = templatespec(tmpl, None)
534 return changesettemplater(ui, repo, spec, buffered=buffered) 532 return changesettemplater(ui, repo, spec, buffered=buffered)
535 533
536 def changesetdisplayer(ui, repo, opts, makefilematcher=None, 534 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
537 makehunksfilter=None, buffered=False):
538 """show one changeset using template or regular display. 535 """show one changeset using template or regular display.
539 536
540 Display format will be the first non-empty hit of: 537 Display format will be the first non-empty hit of:
541 1. option 'template' 538 1. option 'template'
542 2. option 'style' 539 2. option 'style'
543 3. [ui] setting 'logtemplate' 540 3. [ui] setting 'logtemplate'
544 4. [ui] setting 'style' 541 4. [ui] setting 'style'
545 If all of these values are either the unset or the empty string, 542 If all of these values are either the unset or the empty string,
546 regular display via changesetprinter() is done. 543 regular display via changesetprinter() is done.
547 """ 544 """
548 # options 545 postargs = (differ, opts, buffered)
549 if not makefilematcher and (opts.get('patch') or opts.get('stat')):
550 def makefilematcher(ctx):
551 return scmutil.matchall(repo)
552
553 postargs = (makefilematcher, makehunksfilter, opts, buffered)
554 if opts.get('template') == 'json': 546 if opts.get('template') == 'json':
555 return jsonchangeset(ui, repo, *postargs) 547 return jsonchangeset(ui, repo, *postargs)
556 548
557 spec = _lookuptemplate(ui, opts.get('template'), opts.get('style')) 549 spec = _lookuptemplate(ui, opts.get('template'), opts.get('style'))
558 550
724 revs = smartset.spanset(repo) 716 revs = smartset.spanset(repo)
725 revs.reverse() 717 revs.reverse()
726 return revs 718 return revs
727 719
728 def getrevs(repo, pats, opts): 720 def getrevs(repo, pats, opts):
729 """Return (revs, filematcher) where revs is a smartset 721 """Return (revs, differ) where revs is a smartset
730 722
731 filematcher is a callable taking a changectx and returning a match 723 differ is a changesetdiffer with pre-configured file matcher.
732 objects filtering the files to be detailed when displaying the revision.
733 """ 724 """
734 follow = opts.get('follow') or opts.get('follow_first') 725 follow = opts.get('follow') or opts.get('follow_first')
735 followfirst = opts.get('follow_first') 726 followfirst = opts.get('follow_first')
736 limit = getlimit(opts) 727 limit = getlimit(opts)
737 revs = _initialrevs(repo, opts) 728 revs = _initialrevs(repo, opts)
760 if expr: 751 if expr:
761 matcher = revset.match(None, expr) 752 matcher = revset.match(None, expr)
762 revs = matcher(repo, revs) 753 revs = matcher(repo, revs)
763 if limit is not None: 754 if limit is not None:
764 revs = revs.slice(0, limit) 755 revs = revs.slice(0, limit)
765 return revs, filematcher 756
757 differ = changesetdiffer()
758 differ._makefilematcher = filematcher
759 return revs, differ
766 760
767 def _parselinerangeopt(repo, opts): 761 def _parselinerangeopt(repo, opts):
768 """Parse --line-range log option and return a list of tuples (filename, 762 """Parse --line-range log option and return a list of tuples (filename,
769 (fromline, toline)). 763 (fromline, toline)).
770 """ 764 """
783 linerangebyfname.append( 777 linerangebyfname.append(
784 (fname, util.processlinerange(fromline, toline))) 778 (fname, util.processlinerange(fromline, toline)))
785 return linerangebyfname 779 return linerangebyfname
786 780
787 def getlinerangerevs(repo, userrevs, opts): 781 def getlinerangerevs(repo, userrevs, opts):
788 """Return (revs, filematcher, hunksfilter). 782 """Return (revs, differ).
789 783
790 "revs" are revisions obtained by processing "line-range" log options and 784 "revs" are revisions obtained by processing "line-range" log options and
791 walking block ancestors of each specified file/line-range. 785 walking block ancestors of each specified file/line-range.
792 786
793 "filematcher(ctx) -> match" is a factory function returning a match object 787 "differ" is a changesetdiffer with pre-configured file matcher and hunks
794 for a given revision for file patterns specified in --line-range option. 788 filter.
795
796 "hunksfilter(ctx) -> filterfn(fctx, hunks)" is a factory function
797 returning a hunks filtering function.
798 """ 789 """
799 wctx = repo[None] 790 wctx = repo[None]
800 791
801 # Two-levels map of "rev -> file ctx -> [line range]". 792 # Two-levels map of "rev -> file ctx -> [line range]".
802 linerangesbyrev = {} 793 linerangesbyrev = {}
841 files = list(linerangesbyrev.get(ctx.rev(), [])) 832 files = list(linerangesbyrev.get(ctx.rev(), []))
842 return scmutil.matchfiles(repo, files) 833 return scmutil.matchfiles(repo, files)
843 834
844 revs = sorted(linerangesbyrev, reverse=True) 835 revs = sorted(linerangesbyrev, reverse=True)
845 836
846 return revs, filematcher, hunksfilter 837 differ = changesetdiffer()
838 differ._makefilematcher = filematcher
839 differ._makehunksfilter = hunksfilter
840 return revs, differ
847 841
848 def _graphnodeformatter(ui, displayer): 842 def _graphnodeformatter(ui, displayer):
849 spec = ui.config('ui', 'graphnodetemplate') 843 spec = ui.config('ui', 'graphnodetemplate')
850 if not spec: 844 if not spec:
851 return templatekw.showgraphnode # fast path for "{graphnode}" 845 return templatekw.showgraphnode # fast path for "{graphnode}"
908 for type, char, width, coldata in itertools.chain([firstedge], edges): 902 for type, char, width, coldata in itertools.chain([firstedge], edges):
909 graphmod.ascii(ui, state, type, char, lines, coldata) 903 graphmod.ascii(ui, state, type, char, lines, coldata)
910 lines = [] 904 lines = []
911 displayer.close() 905 displayer.close()
912 906
913 def graphlog(ui, repo, revs, filematcher, opts): 907 def graphlog(ui, repo, revs, differ, opts):
914 # Parameters are identical to log command ones 908 # Parameters are identical to log command ones
915 revdag = graphmod.dagwalker(repo, revs) 909 revdag = graphmod.dagwalker(repo, revs)
916 910
917 getrenamed = None 911 getrenamed = None
918 if opts.get('copies'): 912 if opts.get('copies'):
920 if opts.get('rev'): 914 if opts.get('rev'):
921 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1 915 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
922 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev) 916 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
923 917
924 ui.pager('log') 918 ui.pager('log')
925 displayer = changesetdisplayer(ui, repo, opts, makefilematcher=filematcher, 919 displayer = changesetdisplayer(ui, repo, opts, differ, buffered=True)
926 buffered=True)
927 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed) 920 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
928 921
929 def checkunsupportedgraphflags(pats, opts): 922 def checkunsupportedgraphflags(pats, opts):
930 for op in ["newest_first"]: 923 for op in ["newest_first"]:
931 if op in opts and opts[op]: 924 if op in opts and opts[op]: