[PATCH 4 of 4 v4] log: add -L/--line-range option to follow file history by line range
Yuya Nishihara
yuya at tcha.org
Wed Oct 18 11:00:40 EDT 2017
On Tue, 17 Oct 2017 21:37:00 +0200, Denis Laxalde wrote:
> # HG changeset patch
> # User Denis Laxalde <denis.laxalde at logilab.fr>
> # Date 1508267731 -7200
> # Tue Oct 17 21:15:31 2017 +0200
> # Node ID 601ab53301506bc35ab75adc8de9f611af9b3d80
> # Parent 909a69f31ef323ded6fef8dd56fb44dc97f4cd89
> # EXP-Topic followlines-cli
> log: add -L/--line-range option to follow file history by line range
I found a couple of UI bugs, but queued to start bikeshedding. Thanks.
> +def _parselinerangelogopt(repo, opts):
> + """Parse --line-range log option and return a list of tuples (filename,
> + (fromline, toline)).
> + """
> + linerangebyfname = []
> + for pat in opts.get('line_range', []):
> + try:
> + pat, linerange = pat.rsplit(',', 1)
> + except ValueError:
> + raise error.Abort(_('malformatted line-range pattern %s') % pat)
> + try:
> + fromline, toline = map(int, linerange.split('-'))
Nit: I prefer : than - for consistency.
> + except ValueError:
> + raise error.Abort(_("invalid line range for %s") % pat)
> + msg = _("line range pattern '%s' must match exactly one file") % pat
> + fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
> + linerangebyfname.append(
> + (fname, util.processlinerange(fromline, toline)))
> + return linerangebyfname
> +
> +def getloglinerangerevs(repo, userrevs, opts):
> + """Return (revs, filematcher, hunksfilter).
> +
> + "revs" are revisions obtained by processing "line-range" log options and
> + walking block ancestors of each specified file/line-range.
> +
> + "filematcher(rev) -> match" is a factory function returning a match object
> + for a given revision for file patterns specified in --line-range option.
> + If neither --stat nor --patch options are passed, "filematcher" is None.
> +
> + "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
> + returning a hunks filtering function.
> + If neither --stat nor --patch options are passed, "filterhunks" is None.
> + """
> + wctx = repo[None]
Perhaps, it should track history from the specified revision.
$ hg log -frREV -L FILE,RANGE
(will be identical to -r 'followlines(FILE, RANGE, startrev=REV)')
> + # Two-levels map of "rev -> file ctx -> [line range]".
> + linerangesbyrev = {}
> + for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
> + fctx = wctx.filectx(fname)
> + for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
> + rev = fctx.introrev()
> + if rev not in userrevs:
> + continue
> + linerangesbyrev.setdefault(
> + rev, {}).setdefault(
> + fctx.path(), []).append(linerange)
> +
> + filematcher = None
> + hunksfilter = None
> + if opts.get('patch') or opts.get('stat'):
> +
> + def nofilterhunksfn(fctx, hunks):
> + return hunks
> +
> + def hunksfilter(rev):
> + fctxlineranges = linerangesbyrev.get(rev)
> + if fctxlineranges is None:
> + return nofilterhunksfn
> +
> + def filterfn(fctx, hunks):
> + lineranges = fctxlineranges.get(fctx.path())
> + if lineranges is not None:
> + for hr, lines in hunks:
> + if any(mdiff.hunkinrange(hr[2:], lr)
> + for lr in lineranges):
> + yield hr, lines
> + else:
> + for hunk in hunks:
> + yield hunk
Got TypeError with a binary file. Can you investigate it?
> diff --git a/mercurial/commands.py b/mercurial/commands.py
> --- a/mercurial/commands.py
> +++ b/mercurial/commands.py
> @@ -3234,6 +3234,9 @@ def locate(ui, repo, *pats, **opts):
> ('k', 'keyword', [],
> _('do case-insensitive search for a given text'), _('TEXT')),
> ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
> + ('L', 'line-range', [],
> + _('follow line range of specified file (EXPERIMENTAL)'),
> + _('FILE,RANGE')),
> ('', 'removed', None, _('include revisions where files were removed')),
> ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
> ('u', 'user', [], _('revisions committed by user'), _('USER')),
> @@ -3275,6 +3278,12 @@ def log(ui, repo, *pats, **opts):
> Paths in the DAG are represented with '|', '/' and so forth. ':' in place
> of a '|' indicates one or more revisions in a path are omitted.
>
> + Use -L/--line-range FILE,M-N options to follow the history of lines from M
> + to N in FILE. With -p/--patch only diff hunks affecting specified line
> + range will be shown. This option requires --follow; it can be specified
> + multiple times. Currently, this option is not compatible with --graph.
> + This option is experimental.
> +
> .. note::
>
> :hg:`log --patch` may generate unexpected diff output for merge
> @@ -3288,6 +3297,12 @@ def log(ui, repo, *pats, **opts):
> made on branches and will not show removals or mode changes. To
> see all such changes, use the --removed switch.
>
> + .. note::
> +
> + The history resulting from -L/--line-range options depends on diff
> + options; for instance if white-spaces are ignored, respective changes
> + with only white-spaces in specified line range will not be listed.
Moved these paragraphs under verbose container since -L is still experimental.
> """
> opts = pycompat.byteskwargs(opts)
> + linerange = opts.get('line_range')
> +
> + if linerange and not opts.get('follow'):
> + raise error.Abort(_('--line-range requires --follow'))
> +
> if opts.get('follow') and opts.get('rev'):
> opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
> del opts['follow']
>
> if opts.get('graph'):
> + if linerange:
> + raise error.Abort(_('graph not supported with line range patterns'))
> return cmdutil.graphlog(ui, repo, pats, opts)
>
> revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
> + hunksfilter = None
> +
> + if linerange:
> + revs, lrfilematcher, hunksfilter = cmdutil.getloglinerangerevs(
> + repo, revs, opts)
> +
> + if filematcher is not None and lrfilematcher is not None:
> + basefilematcher = filematcher
> +
> + def filematcher(rev):
> + files = (basefilematcher(rev).files()
> + + lrfilematcher(rev).files())
> + return scmutil.matchfiles(repo, files)
> +
> + elif filematcher is None:
> + filematcher = lrfilematcher
So, --line-range appears to conflict with the bare file patterns.
$ hg log -f -L hg,1-2 hgweb.cgi
(should show followlines(hg, 1:2) + follow(hgweb.cgi))
I think bare file patterns should be rejected until it works as expected.
More information about the Mercurial-devel
mailing list