[Updated] [++- ] D9056: fix: include adjacent blank lines in ranges to be fixed

msuozzo (Matthew Suozzo) phabricator at mercurial-scm.org
Wed Sep 23 03:48:19 UTC 2020


msuozzo updated this revision to Diff 22764.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D9056?vs=22738&id=22764

BRANCH
  default

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D9056/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D9056

AFFECTED FILES
  hgext/fix.py
  tests/test-fix.t

CHANGE DETAILS

diff --git a/tests/test-fix.t b/tests/test-fix.t
--- a/tests/test-fix.t
+++ b/tests/test-fix.t
@@ -156,6 +156,11 @@
   If such a tool needs to operate on unchanged files, it should set the
   :skipclean suboption to false.
   
+  By default, adjacent blank lines will be excluded from the generated line
+  ranges. This can be suboptimal, especially when lines with adjacent blanks are
+  deleted and, thus, aren't included. Setting :adjacentblanks will include these
+  lines in diffs, even in the case of delete-only diffs.
+  
   The :pattern suboption determines which files will be passed through each
   configured tool. See 'hg help patterns' for possible values. However, all
   patterns are relative to the repo root, even if that text says they are
@@ -1453,6 +1458,51 @@
 
   $ cd ..
 
+Tools should be able to include adjacent blanks in generated line ranges when
+set :adjacentblanks is enabled. Notably, this generates line ranges for
+delete-only diffs which are normally excluded from line ranges.
+
+  $ hg init adjacentblanks
+  $ cd adjacentblanks
+
+  $ printf "a\nb\nc\n" > neither
+  $ printf "a\n\nb\n\nc\n" > both
+  $ printf "a\n\nb\nc\n" > above
+  $ printf "a\nb\n\nc\n" > below
+  $ hg commit -Aqm "base"
+
+  $ printf "a\nc\n" > neither
+  $ printf "a\n\n\nc\n" > both
+  $ printf "a\n\nc\n" > above
+  $ printf "a\n\nc\n" > below
+
+  $ cat >> print.py <<EOF
+  > import sys
+  > for a in sys.argv[1:]:
+  >    print(a)
+  > EOF
+
+  $ hg fix --working-dir neither both above below \
+  >        --config "fix.downwithwhitespace:command=\"$PYTHON\" print.py \"Line ranges:\"" \
+  >        --config 'fix.downwithwhitespace:linerange="{first} through {last}"' \
+  >        --config 'fix.downwithwhitespace:pattern=glob:**' \
+  >        --config 'fix.downwithwhitespace:skipclean=false' \
+  >        --config 'fix.downwithwhitespace:adjacentblanks=true'
+
+  $ cat neither
+  Line ranges:
+  $ cat both
+  Line ranges:
+  2 through 3
+  $ cat above
+  Line ranges:
+  2 through 2
+  $ cat below
+  Line ranges:
+  2 through 2
+
+  $ cd ..
+
 Test various cases around merges. We were previously dropping files if they were
 created on only the p2 side of the merge, so let's test permutations of:
 *   added, was fixed
diff --git a/hgext/fix.py b/hgext/fix.py
--- a/hgext/fix.py
+++ b/hgext/fix.py
@@ -45,6 +45,11 @@
 tool needs to operate on unchanged files, it should set the :skipclean suboption
 to false.
 
+By default, adjacent blank lines will be excluded from the generated line
+ranges. This can be suboptimal, especially when lines with adjacent blanks are
+deleted and, thus, aren't included. Setting :adjacentblanks will include these
+lines in diffs, even in the case of delete-only diffs.
+
 The :pattern suboption determines which files will be passed through each
 configured tool. See :hg:`help patterns` for possible values. However, all
 patterns are relative to the repo root, even if that text says they are relative
@@ -173,6 +178,7 @@
     b'priority': 0,
     b'metadata': False,
     b'skipclean': True,
+    b'adjacentblanks': False,
     b'enabled': True,
 }
 
@@ -479,7 +485,7 @@
     return files
 
 
-def lineranges(opts, path, basepaths, basectxs, fixctx, content2):
+def lineranges(opts, fixer, path, basepaths, basectxs, fixctx, content2):
     """Returns the set of line ranges that should be fixed in a file
 
     Of the form [(10, 20), (30, 40)].
@@ -494,7 +500,7 @@
     if opts.get(b'whole'):
         # Return a range containing all lines. Rely on the diff implementation's
         # idea of how many lines are in the file, instead of reimplementing it.
-        return difflineranges(b'', content2)
+        return difflineranges(fixer, b'', content2)
 
     rangeslist = []
     for basectx in basectxs:
@@ -504,7 +510,7 @@
             content1 = basectx[basepath].data()
         else:
             content1 = b''
-        rangeslist.extend(difflineranges(content1, content2))
+        rangeslist.extend(difflineranges(fixer, content1, content2))
     return unionranges(rangeslist)
 
 
@@ -558,7 +564,7 @@
     return unioned
 
 
-def difflineranges(content1, content2):
+def difflineranges(fixer, content1, content2):
     """Return list of line number ranges in content2 that differ from content1.
 
     Line numbers are 1-based. The numbers are the first and last line contained
@@ -569,7 +575,7 @@
 
     >>> from mercurial import pycompat
     >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
-    >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
+    >>> difflineranges2 = lambda a, b: difflineranges(..., lines(a), lines(b))
     >>> difflineranges2(b'', b'')
     []
     >>> difflineranges2(b'a', b'')
@@ -598,10 +604,22 @@
     [(2, 4)]
     """
     ranges = []
-    for lines, kind in mdiff.allblocks(content1, content2):
+    lines2 = mdiff.splitnewlines(content2)
+    adjacentblanks = fixer.shouldemitadjacentblanks()
+    for lines, kind in mdiff.allblocks(content1, content2, lines2=lines2):
         firstline, lastline = lines[2:4]
-        if kind == b'!' and firstline != lastline:
-            ranges.append((firstline + 1, lastline))
+        # Produce a line range whenever the two sources differ and EITHER there
+        # is an addition/modification OR :adjacentblanks is enabled and there is an
+        # adjacent blank line.
+        if kind == b'!' and (firstline != lastline or adjacentblanks and
+                             b'\n' in lines2[firstline - 1: lastline + 1]):
+            range_ = (firstline + 1, lastline)
+            if adjacentblanks:
+                range_ = (
+                    range_[0] - int(b'\n' in lines2[firstline - 1: firstline]),
+                    range_[1] + int(b'\n' in lines2[lastline: lastline + 1]),
+                )
+            ranges.append(range_)
     return ranges
 
 
@@ -675,7 +693,7 @@
     for fixername, fixer in pycompat.iteritems(fixers):
         if fixer.affects(opts, fixctx, path):
             ranges = lineranges(
-                opts, path, basepaths, basectxs, fixctx, newdata
+                opts, fixer, path, basepaths, basectxs, fixctx, newdata
             )
             command = fixer.command(ui, path, ranges)
             if command is None:
@@ -847,6 +865,7 @@
         priority = ui.configint(b'fix', name + b':priority')
         metadata = ui.configbool(b'fix', name + b':metadata')
         skipclean = ui.configbool(b'fix', name + b':skipclean')
+        adjacentblanks = ui.configbool(b'fix', name + b':adjacentblanks')
         # Don't use a fixer if it has no pattern configured. It would be
         # dangerous to let it affect all files. It would be pointless to let it
         # affect no files. There is no reasonable subset of files to use as the
@@ -863,7 +882,8 @@
             ui.debug(b'ignoring disabled fixer tool: %s\n' % (name,))
         else:
             fixers[name] = Fixer(
-                command, pattern, linerange, priority, metadata, skipclean
+                command, pattern, linerange, priority, metadata, skipclean,
+                adjacentblanks
             )
     return collections.OrderedDict(
         sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
@@ -883,7 +903,8 @@
     """Wraps the raw config values for a fixer with methods"""
 
     def __init__(
-        self, command, pattern, linerange, priority, metadata, skipclean
+        self, command, pattern, linerange, priority, metadata, skipclean,
+        adjacentblanks
     ):
         self._command = command
         self._pattern = pattern
@@ -891,6 +912,7 @@
         self._priority = priority
         self._metadata = metadata
         self._skipclean = skipclean
+        self._adjacentblanks = adjacentblanks
 
     def affects(self, opts, fixctx, path):
         """Should this fixer run on the file at the given path and context?"""
@@ -904,6 +926,10 @@
         """Should the stdout of this fixer start with JSON and a null byte?"""
         return self._metadata
 
+    def shouldemitadjacentblanks(self):
+        """Should this fixer receive line ranges of blanks adjacent to diffs?"""
+        return self._adjacentblanks
+
     def command(self, ui, path, ranges):
         """A shell command to use to invoke this fixer on the given file/lines
 



To: msuozzo, hooper, #hg-reviewers
Cc: mercurial-patches
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mercurial-scm.org/pipermail/mercurial-patches/attachments/20200923/39ecafad/attachment-0002.html>


More information about the Mercurial-patches mailing list