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.
--- 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