annotate: support diff whitespace filtering flags (issue3030)
authorPatrick Mezard <pmezard@gmail.com>
Fri, 18 Nov 2011 12:04:31 +0100
changeset 15528 a84698badf0b
parent 15527 9926aab3d0b5
child 15529 b35cf47286a6
annotate: support diff whitespace filtering flags (issue3030) splitblock() was added to handle blocks returned by bdiff.blocks() which differ only by blank lines but are not made only of blank lines. I do not know exactly how it could happen but mdiff.blocks() threshold behaviour makes me think it can if those blocks are made of very popular lines mixed with popular blank lines. If it is proven to be wrong, the function can be dropped. The first implementation made annotate share diff configuration entries. But it looks like users will user -w/b for annotate but not for diff, on both the command line and hgweb. Since the latter cannot use command line entries, we introduce a new [annotate] section duplicating the diff whitespace options.
mercurial/commands.py
mercurial/context.py
mercurial/help/config.txt
mercurial/hgweb/webcommands.py
mercurial/mdiff.py
mercurial/patch.py
tests/test-annotate.t
tests/test-debugcomplete.t
--- a/mercurial/commands.py	Sun Nov 20 19:14:36 2011 +0100
+++ b/mercurial/commands.py	Fri Nov 18 12:04:31 2011 +0100
@@ -106,15 +106,19 @@
     ('', 'nodates', None, _('omit dates from diff headers'))
 ]
 
-diffopts2 = [
-    ('p', 'show-function', None, _('show which function each change is in')),
-    ('', 'reverse', None, _('produce a diff that undoes the changes')),
+diffwsopts = [
     ('w', 'ignore-all-space', None,
      _('ignore white space when comparing lines')),
     ('b', 'ignore-space-change', None,
      _('ignore changes in the amount of white space')),
     ('B', 'ignore-blank-lines', None,
      _('ignore changes whose lines are all blank')),
+    ]
+
+diffopts2 = [
+    ('p', 'show-function', None, _('show which function each change is in')),
+    ('', 'reverse', None, _('produce a diff that undoes the changes')),
+    ] + diffwsopts + [
     ('U', 'unified', '',
      _('number of lines of context to show'), _('NUM')),
     ('', 'stat', None, _('output diffstat-style summary of changes')),
@@ -215,7 +219,7 @@
     ('n', 'number', None, _('list the revision number (default)')),
     ('c', 'changeset', None, _('list the changeset')),
     ('l', 'line-number', None, _('show line number at the first appearance'))
-    ] + walkopts,
+    ] + diffwsopts + walkopts,
     _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
 def annotate(ui, repo, *pats, **opts):
     """show changeset information by line for each file
@@ -270,13 +274,15 @@
     m = scmutil.match(ctx, pats, opts)
     m.bad = bad
     follow = not opts.get('no_follow')
+    diffopts = patch.diffopts(ui, opts, section='annotate')
     for abs in ctx.walk(m):
         fctx = ctx[abs]
         if not opts.get('text') and util.binary(fctx.data()):
             ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
             continue
 
-        lines = fctx.annotate(follow=follow, linenumber=linenumber)
+        lines = fctx.annotate(follow=follow, linenumber=linenumber,
+                              diffopts=diffopts)
         pieces = []
 
         for f, sep in funcmap:
--- a/mercurial/context.py	Sun Nov 20 19:14:36 2011 +0100
+++ b/mercurial/context.py	Fri Nov 18 12:04:31 2011 +0100
@@ -7,7 +7,7 @@
 
 from node import nullid, nullrev, short, hex
 from i18n import _
-import ancestor, bdiff, error, util, scmutil, subrepo, patch, encoding
+import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding
 import match as matchmod
 import os, errno, stat
 
@@ -433,7 +433,7 @@
         return [filectx(self._repo, self._path, fileid=x,
                         filelog=self._filelog) for x in c]
 
-    def annotate(self, follow=False, linenumber=None):
+    def annotate(self, follow=False, linenumber=None, diffopts=None):
         '''returns a list of tuples of (ctx, line) for each line
         in the file, where ctx is the filectx of the node where
         that line was last changed.
@@ -460,8 +460,13 @@
                     without_linenumber)
 
         def pair(parent, child):
-            for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
-                child[0][b1:b2] = parent[0][a1:a2]
+            blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
+                                     refine=True)
+            for (a1, a2, b1, b2), t in blocks:
+                # Changed blocks ('!') or blocks made only of blank lines ('~')
+                # belong to the child.
+                if t == '=':
+                    child[0][b1:b2] = parent[0][a1:a2]
             return child
 
         getlog = util.lrucachefunc(lambda x: self._repo.file(x))
--- a/mercurial/help/config.txt	Sun Nov 20 19:14:36 2011 +0100
+++ b/mercurial/help/config.txt	Fri Nov 18 12:04:31 2011 +0100
@@ -227,6 +227,24 @@
    processed before shell aliases and will thus not be passed to
    aliases.
 
+
+``annotate``
+""""""""
+
+Settings used when displaying file annotations. All values are
+Booleans and default to False. See ``diff`` section for related
+options for the diff command.
+
+``ignorews``
+    Ignore white space when comparing lines.
+
+``ignorewsamount``
+    Ignore changes in the amount of white space.
+
+``ignoreblanklines``
+    Ignore changes whose lines are all blank.
+
+
 ``auth``
 """"""""
 
@@ -364,8 +382,9 @@
 ``diff``
 """"""""
 
-Settings used when displaying diffs. Everything except for ``unified`` is a
-Boolean and defaults to False.
+Settings used when displaying diffs. Everything except for ``unified``
+is a Boolean and defaults to False. See ``annotate`` section for
+related options for the annotate command.
 
 ``git``
     Use git extended diff format.
--- a/mercurial/hgweb/webcommands.py	Sun Nov 20 19:14:36 2011 +0100
+++ b/mercurial/hgweb/webcommands.py	Fri Nov 18 12:04:31 2011 +0100
@@ -12,7 +12,7 @@
 from mercurial.util import binary
 from common import paritygen, staticfile, get_contact, ErrorResponse
 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
-from mercurial import graphmod
+from mercurial import graphmod, patch
 from mercurial import help as helpmod
 from mercurial.i18n import _
 
@@ -576,6 +576,7 @@
     fctx = webutil.filectx(web.repo, req)
     f = fctx.path()
     parity = paritygen(web.stripecount)
+    diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
 
     def annotate(**map):
         last = None
@@ -585,7 +586,8 @@
             lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
                                 '(binary:%s)' % mt)])
         else:
-            lines = enumerate(fctx.annotate(follow=True, linenumber=True))
+            lines = enumerate(fctx.annotate(follow=True, linenumber=True,
+                                            diffopts=diffopts))
         for lineno, ((f, targetline), l) in lines:
             fnode = f.filenode()
 
--- a/mercurial/mdiff.py	Sun Nov 20 19:14:36 2011 +0100
+++ b/mercurial/mdiff.py	Fri Nov 18 12:04:31 2011 +0100
@@ -75,11 +75,38 @@
         text = re.sub('\n+', '\n', text).strip('\n')
     return text
 
-def allblocks(text1, text2, opts=None, lines1=None, lines2=None):
+def splitblock(base1, lines1, base2, lines2, opts):
+    # The input lines matches except for interwoven blank lines. We
+    # transform it into a sequence of matching blocks and blank blocks.
+    lines1 = [(wsclean(opts, l) and 1 or 0) for l in lines1]
+    lines2 = [(wsclean(opts, l) and 1 or 0) for l in lines2]
+    s1, e1 = 0, len(lines1)
+    s2, e2 = 0, len(lines2)
+    while s1 < e1 or s2 < e2:
+        i1, i2, btype = s1, s2, '='
+        if (i1 >= e1 or lines1[i1] == 0
+            or i2 >= e2 or lines2[i2] == 0):
+            # Consume the block of blank lines
+            btype = '~'
+            while i1 < e1 and lines1[i1] == 0:
+                i1 += 1
+            while i2 < e2 and lines2[i2] == 0:
+                i2 += 1
+        else:
+            # Consume the matching lines
+            while i1 < e1 and lines1[i1] == 1 and lines2[i2] == 1:
+                i1 += 1
+                i2 += 1
+        yield [base1 + s1, base1 + i1, base2 + s2, base2 + i2], btype
+        s1 = i1
+        s2 = i2
+
+def allblocks(text1, text2, opts=None, lines1=None, lines2=None, refine=False):
     """Return (block, type) tuples, where block is an mdiff.blocks
     line entry. type is '=' for blocks matching exactly one another
     (bdiff blocks), '!' for non-matching blocks and '~' for blocks
-    matching only after having filtered blank lines.
+    matching only after having filtered blank lines. If refine is True,
+    then '~' blocks are refined and are only made of blank lines.
     line1 and line2 are text1 and text2 split with splitnewlines() if
     they are already available.
     """
--- a/mercurial/patch.py	Sun Nov 20 19:14:36 2011 +0100
+++ b/mercurial/patch.py	Fri Nov 18 12:04:31 2011 +0100
@@ -1527,10 +1527,10 @@
 class GitDiffRequired(Exception):
     pass
 
-def diffopts(ui, opts=None, untrusted=False):
+def diffopts(ui, opts=None, untrusted=False, section='diff'):
     def get(key, name=None, getter=ui.configbool):
         return ((opts and opts.get(key)) or
-                getter('diff', name or key, None, untrusted=untrusted))
+                getter(section, name or key, None, untrusted=untrusted))
     return mdiff.diffopts(
         text=opts and opts.get('text'),
         git=get('git'),
--- a/tests/test-annotate.t	Sun Nov 20 19:14:36 2011 +0100
+++ b/tests/test-annotate.t	Fri Nov 18 12:04:31 2011 +0100
@@ -2,7 +2,8 @@
 
 init
 
-  $ hg init
+  $ hg init repo
+  $ cd repo
 
 commit
 
@@ -253,3 +254,56 @@
   $ hg ann nosuchfile
   abort: nosuchfile: no such file in rev e9e6b4fa872f
   [255]
+
+Test annotate with whitespace options
+
+  $ cd ..
+  $ hg init repo-ws
+  $ cd repo-ws
+  $ cat > a <<EOF
+  > aa
+  > 
+  > b b
+  > EOF
+  $ hg ci -Am "adda"
+  adding a
+  $ cat > a <<EOF
+  > a  a
+  > 
+  >  
+  > b  b
+  > EOF
+  $ hg ci -m "changea"
+
+Annotate with no option
+
+  $ hg annotate a
+  1: a  a
+  0: 
+  1:  
+  1: b  b
+
+Annotate with --ignore-space-change
+
+  $ hg annotate --ignore-space-change a
+  1: a  a
+  1: 
+  0:  
+  0: b  b
+
+Annotate with --ignore-all-space
+
+  $ hg annotate --ignore-all-space a
+  0: a  a
+  0: 
+  1:  
+  0: b  b
+
+Annotate with --ignore-blank-lines (similar to no options case)
+
+  $ hg annotate --ignore-blank-lines a
+  1: a  a
+  0: 
+  1:  
+  1: b  b
+
--- a/tests/test-debugcomplete.t	Sun Nov 20 19:14:36 2011 +0100
+++ b/tests/test-debugcomplete.t	Fri Nov 18 12:04:31 2011 +0100
@@ -189,7 +189,7 @@
 Show all commands + options
   $ hg debugcommands
   add: include, exclude, subrepos, dry-run
-  annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, include, exclude
+  annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude
   clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
   commit: addremove, close-branch, include, exclude, message, logfile, date, user, subrepos
   diff: rev, change, text, git, nodates, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude, subrepos