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]: |