[PATCH 4 of 4] log: add -L/--line-range option to follow file history by line range

Denis Laxalde denis at laxalde.org
Fri Oct 6 10:38:23 EDT 2017


Yuya Nishihara a écrit :
> On Wed, 04 Oct 2017 17:04:02 +0200, Denis Laxalde wrote:
>> # HG changeset patch
>> # User Denis Laxalde <denis.laxalde at logilab.fr>
>> # Date 1507127733 -7200
>> #      Wed Oct 04 16:35:33 2017 +0200
>> # Node ID 0f2d8b304223a8d00163f917fdc18082a88bceae
>> # Parent  9274bdbba0f913672305ed3d91c7e05f38d1eab7
>> # Available At http://hg.logilab.org/users/dlaxalde/hg
>> #              hg pull http://hg.logilab.org/users/dlaxalde/hg -r 0f2d8b304223
>> # EXP-Topic followlines-cli
>> log: add -L/--line-range option to follow file history by line range
>>
>> We add a -L/--line-range option to 'hg log' taking file patterns along with a
>> line range using the (new) FILE,FROMLINE-TOLINE syntax where FILE may be a
>> pattern (matching exactly one file). The resulting history is similar to what
>> the "followlines" revset except that, if --patch is specified, only diff hunks
>> within specified line range are shown (it's also more convenient to type).
>>
>> Basically, this brings the CLI on par with what currently only exists in hgweb
>> through line selection in "file" and "annotate" views resulting in a file log
>> with filtered patch to only display followed line range.
>>
>> The option may be specified multiple times and can be combined with --rev or
>> regular file pattern in a meaningful way. It also respects --follow semantics.
>> Only the --graph option is currently not supported.
>>
>> Some debatable UI choices (I did not think too much about this for now).
>>
>> *   "," as a separator between the FILE and line range information; the idea
>>      is to avoid confusion with file pattern syntax which uses ":".
>>
>> *   "-" in the line range information may not be the best choice; in
>>      particular, we might want to add support for an offset +/- syntax.
> 
> Nit: we use ':' in revset.
> 
>> The implementation spreads between commands.log() and cmdutil module.
>> In commands.log(), the main loop now works with tuples of (rev, matchfn,
>> lineranges), where 'rev' and 'matchfn' are similar to before except that
>> 'matchfn' is now built out the loop. Without any -L option, "lineranges" is
>> None. With a -L option, 'matchfn' is a new matcher function built from files
>> specified in -L option(s) and any other regular FILE pattern.
>>
>> The logic to build revisions from -L/--line-range options lives in
>> cmdutil.getloglineranges() and the resulting information is combined with
>> information from --rev option and file patterns in commands.log(). Options
>> --rev and file patterns act as restrictions to the revisions obtained by
>> following line-range history of files specified in -L/--line-range patterns.
> 
> [...]
> 
>> +def getloglineranges(repo, opts):
>> +    """Return a generator of tuples (rev, fpath, [line range]) obtained by
>> +    processing "line-range" log options and walking block ancestors of each
>> +    specified file/line-range.
>> +    """
>> +    wctx = repo[None]
>> +    follow = bool(opts.get('follow'))
>> +    # Two-levels map of "rev -> file path -> [line range]".
>> +    linerangesbyrev = {}
>> +    for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
>> +        fctx = wctx.filectx(fname)
>> +        for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
>> +            fpath = fctx.path()
>> +            if not follow and fpath != fname:
>> +                continue
>> +            linerangesbyrev.setdefault(
>> +                fctx.rev(), {}).setdefault(
>> +                    fpath, []).append(linerange)
>> +    return sorted(linerangesbyrev.iteritems(), reverse=follow)
> 
> --follow isn't the option to flip the order. I think --follow should be
> implied (or required) given how line-range works.

Okay, will do.

> 
>> diff --git a/mercurial/commands.py b/mercurial/commands.py
>> --- a/mercurial/commands.py
>> +++ b/mercurial/commands.py
>> @@ -3230,6 +3230,8 @@ 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'),
>> +     _('FILE,RANGE')),
> 
> No idea if we like the short option -L.
> 
>> +    # Build an iterator of (rev, matcher, lineranges) tuples to be consumed
>> +    # later in the main loop.
>> +    if followlines:
>> +        # Filter revisions from --line-range patterns from those not in --rev
>> +        # option and combine any existing filematcher with the one built from
>> +        # "line range" patterns.
>> +        if filematcher is None:
>> +            def buildmatcher(files):
>> +                return scmutil.match(repo[None], files)
>> +        else:
>> +            def buildmatcher(files):
>> +                return scmutil.match(repo[None],
>> +                                     files + filematcher(None).files(),
>> +                                     default='path')
>> +        revsmatchandlineranges = (
>> +            (rev, buildmatcher(list(lrs)), lrs)
>> +            for rev, lrs in linerangesbyrev if rev in revs
>> +        )
> 
> I don't think it's intuitive that '-L PAT1' and 'PAT2' are AND-ed. (Well, I
> didn't carefully read the code, but probably PAT2 would be filtered by revs
> even though PATs are OR-ed.)
> 
> So I think -L PAT and plain PATs should be mutually exclusive. Alternatively,
> -L could be considered pseudo-pair of file pattern.
> 
>    $ hg log FILE1 -L RANGE1 FILE2 -L RANGE2
> 
> I prefer this over -L FILE,RANGE syntax.

I'm fine for making -L PAT and plain PATs mutually exclusive, it makes
sense.

I'm not sure I understand your proposal (how more specifically, how to
implement it). How should I make FILE1 and RANGE1 matching? Is there a
way to enforce a "-L RANGE" to be followed by a "FILE"? Or should I just
assuming that "opts['line_range']" and "pats" will have the same order
and length and zip on the two lists?

> 
>> -        displayer.show(ctx, copies=copies, matchfn=revmatchfn)
>> +        displayer.show(ctx, copies=copies, matchfn=matchfn,
>> +                       lineranges=lineranges)
> 
> Appears that passing matchfn adds extra '\n' in showpatch().
> 



More information about the Mercurial-devel mailing list