Patchwork [2,of,4] diff: pass down line range information from changeset_printer to patch.diff()

login
register
mail settings
Submitter Denis Laxalde
Date Oct. 4, 2017, 3:04 p.m.
Message ID <f7b31d85dc0e7c7e60b8.1507129440@sh77.tls.logilab.fr>
Download mbox | patch
Permalink /patch/24499/
State Changes Requested
Headers show

Comments

Denis Laxalde - Oct. 4, 2017, 3:04 p.m.
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1507127862 -7200
#      Wed Oct 04 16:37:42 2017 +0200
# Node ID f7b31d85dc0e7c7e60b85d0a7fd728a32ba210ea
# Parent  3d8ed7dafee4676b49922a3dde0edf1b1fad63ec
# Available At http://hg.logilab.org/users/dlaxalde/hg
#              hg pull http://hg.logilab.org/users/dlaxalde/hg -r f7b31d85dc0e
# EXP-Topic followlines-cli
diff: pass down line range information from changeset_printer to patch.diff()

We add a 'lineranges' keyword argument in all functions of the call stack from
changeset_printer.show() to patch.diff(). This is a mapping from filename to
a list of line range tuples is used in patch.diff() to eliminate hunks not in
specified line range. This will be used in the "-L/--line-range" option of "hg
log" command introduced in the following changesets.
Yuya Nishihara - Oct. 5, 2017, 2:52 p.m.
On Wed, 04 Oct 2017 17:04:00 +0200, Denis Laxalde wrote:
> # HG changeset patch
> # User Denis Laxalde <denis.laxalde@logilab.fr>
> # Date 1507127862 -7200
> #      Wed Oct 04 16:37:42 2017 +0200
> # Node ID f7b31d85dc0e7c7e60b85d0a7fd728a32ba210ea
> # Parent  3d8ed7dafee4676b49922a3dde0edf1b1fad63ec
> # Available At http://hg.logilab.org/users/dlaxalde/hg
> #              hg pull http://hg.logilab.org/users/dlaxalde/hg -r f7b31d85dc0e
> # EXP-Topic followlines-cli
> diff: pass down line range information from changeset_printer to patch.diff()

> diff --git a/mercurial/patch.py b/mercurial/patch.py
> --- a/mercurial/patch.py
> +++ b/mercurial/patch.py
> @@ -2293,7 +2293,8 @@ def difffeatureopts(ui, opts=None, untru
>      return mdiff.diffopts(**pycompat.strkwargs(buildopts))
>  
>  def diff(repo, node1=None, node2=None, match=None, changes=None,
> -         opts=None, losedatafn=None, prefix='', relroot='', copy=None):
> +         opts=None, losedatafn=None, prefix='', relroot='', copy=None,
> +         lineranges=None):
>      '''yields diff of changes to files between two nodes, or node and
>      working directory.
>  
> @@ -2315,11 +2316,23 @@ def diff(repo, node1=None, node2=None, m
>      patterns that fall outside it will be ignored.
>  
>      copy, if not empty, should contain mappings {dst@y: src@x} of copy
> -    information.'''
> +    information.
> +
> +    lineranges, if not None, must be a mapping from filename to line range
> +    tuples and is used to filter diff hunks not in specified range.
> +    '''
>      for hdr, hunks in diffhunks(repo, node1=node1, node2=node2, match=match,
>                                  changes=changes, opts=opts,
>                                  losedatafn=losedatafn, prefix=prefix,
>                                  relroot=relroot, copy=copy):
> +        if lineranges is not None and hdr:
> +            fname = header(hdr).filename()

Parsing header line doesn't seem right. Can we get the original filename or
file ctxs instead?

> +            flineranges = lineranges.get(fname)
> +            if flineranges is not None:
> +                hunks = (
> +                    (hr, lines) for hr, lines in hunks
> +                    if any(mdiff.hunkinrange(hr[2:], lr) for lr in flineranges)
> +                )

Nested iterators seems hard to read. Maybe this could be a filter function,
which could optionally be an argument. i.e. diff(..., filter) instead of
diff(..., lineranges).

Patch

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1513,7 +1513,7 @@  def export(repo, revs, fntemplate='hg-%h
 
 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
                    changes=None, stat=False, fp=None, prefix='',
-                   root='', listsubrepos=False):
+                   root='', listsubrepos=False, lineranges=None):
     '''show diff or diffstat.'''
     if fp is None:
         write = ui.write
@@ -1541,14 +1541,16 @@  def diffordiffstat(ui, repo, diffopts, n
         if not ui.plain():
             width = ui.termwidth()
         chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
-                            prefix=prefix, relroot=relroot)
+                            prefix=prefix, relroot=relroot,
+                            lineranges=lineranges)
         for chunk, label in patch.diffstatui(util.iterlines(chunks),
                                              width=width):
             write(chunk, label=label)
     else:
         for chunk, label in patch.diffui(repo, node1, node2, match,
                                          changes, diffopts, prefix=prefix,
-                                         relroot=relroot):
+                                         relroot=relroot,
+                                         lineranges=lineranges):
             write(chunk, label=label)
 
     if listsubrepos:
@@ -1610,16 +1612,16 @@  class changeset_printer(object):
         if self.footer:
             self.ui.write(self.footer)
 
-    def show(self, ctx, copies=None, matchfn=None, **props):
+    def show(self, ctx, copies=None, matchfn=None, lineranges=None, **props):
         props = pycompat.byteskwargs(props)
         if self.buffered:
             self.ui.pushbuffer(labeled=True)
-            self._show(ctx, copies, matchfn, props)
+            self._show(ctx, copies, matchfn, props, lineranges)
             self.hunk[ctx.rev()] = self.ui.popbuffer()
         else:
-            self._show(ctx, copies, matchfn, props)
-
-    def _show(self, ctx, copies, matchfn, props):
+            self._show(ctx, copies, matchfn, props, lineranges)
+
+    def _show(self, ctx, copies, matchfn, props, lineranges):
         '''show a single changeset or file revision'''
         changenode = ctx.node()
         rev = ctx.rev()
@@ -1730,13 +1732,13 @@  class changeset_printer(object):
                               label='log.summary')
         self.ui.write("\n")
 
-        self.showpatch(ctx, matchfn)
+        self.showpatch(ctx, matchfn, lineranges=lineranges)
 
     def _exthook(self, ctx):
         '''empty method used by extension as a hook point
         '''
 
-    def showpatch(self, ctx, matchfn):
+    def showpatch(self, ctx, matchfn, lineranges=None):
         if not matchfn:
             matchfn = self.matchfn
         if matchfn:
@@ -1747,12 +1749,14 @@  class changeset_printer(object):
             prev = ctx.p1().node()
             if stat:
                 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=True)
+                               match=matchfn, stat=True,
+                               lineranges=lineranges)
             if diff:
                 if stat:
                     self.ui.write("\n")
                 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=False)
+                               match=matchfn, stat=False,
+                               lineranges=lineranges)
             self.ui.write("\n")
 
 class jsonchangeset(changeset_printer):
@@ -1769,7 +1773,7 @@  class jsonchangeset(changeset_printer):
         else:
             self.ui.write("[]\n")
 
-    def _show(self, ctx, copies, matchfn, props):
+    def _show(self, ctx, copies, matchfn, props, lineranges):
         '''show a single changeset or file revision'''
         rev = ctx.rev()
         if rev is None:
@@ -1843,13 +1847,15 @@  class jsonchangeset(changeset_printer):
             if stat:
                 self.ui.pushbuffer()
                 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=True)
+                               match=matchfn, stat=True,
+                               lineranges=lineranges)
                 self.ui.write((',\n  "diffstat": "%s"')
                               % j(self.ui.popbuffer()))
             if diff:
                 self.ui.pushbuffer()
                 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=False)
+                               match=matchfn, stat=False,
+                               lineranges=lineranges)
                 self.ui.write((',\n  "diff": "%s"') % j(self.ui.popbuffer()))
 
         self.ui.write("\n }")
@@ -1903,7 +1909,7 @@  class changeset_templater(changeset_prin
             self.footer += templater.stringify(self.t(self._parts['docfooter']))
         return super(changeset_templater, self).close()
 
-    def _show(self, ctx, copies, matchfn, props):
+    def _show(self, ctx, copies, matchfn, props, lineranges):
         '''show a single changeset or file revision'''
         props = props.copy()
         props.update(templatekw.keywords)
@@ -1935,7 +1941,7 @@  class changeset_templater(changeset_prin
         # write changeset metadata, then patch if requested
         key = self._parts[self._tref]
         self.ui.write(templater.stringify(self.t(key, **props)))
-        self.showpatch(ctx, matchfn)
+        self.showpatch(ctx, matchfn, lineranges=lineranges)
 
         if self._parts['footer']:
             if not self.footer:
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -2293,7 +2293,8 @@  def difffeatureopts(ui, opts=None, untru
     return mdiff.diffopts(**pycompat.strkwargs(buildopts))
 
 def diff(repo, node1=None, node2=None, match=None, changes=None,
-         opts=None, losedatafn=None, prefix='', relroot='', copy=None):
+         opts=None, losedatafn=None, prefix='', relroot='', copy=None,
+         lineranges=None):
     '''yields diff of changes to files between two nodes, or node and
     working directory.
 
@@ -2315,11 +2316,23 @@  def diff(repo, node1=None, node2=None, m
     patterns that fall outside it will be ignored.
 
     copy, if not empty, should contain mappings {dst@y: src@x} of copy
-    information.'''
+    information.
+
+    lineranges, if not None, must be a mapping from filename to line range
+    tuples and is used to filter diff hunks not in specified range.
+    '''
     for hdr, hunks in diffhunks(repo, node1=node1, node2=node2, match=match,
                                 changes=changes, opts=opts,
                                 losedatafn=losedatafn, prefix=prefix,
                                 relroot=relroot, copy=copy):
+        if lineranges is not None and hdr:
+            fname = header(hdr).filename()
+            flineranges = lineranges.get(fname)
+            if flineranges is not None:
+                hunks = (
+                    (hr, lines) for hr, lines in hunks
+                    if any(mdiff.hunkinrange(hr[2:], lr) for lr in flineranges)
+                )
         text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
         if hdr and (text or len(hdr) > 1):
             yield '\n'.join(hdr) + '\n'