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 and
regular file patterns to further restrict revisions. Usage of this option
requires --follow; revisions are shown in descending order and renames are
followed. Only the --graph option is currently not supported.
The UI is the result of a consensus from review feedback at:
https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-October/106749.html
The implementation spreads between commands.log() and cmdutil module.
In commands.log(), the main loop may now use a "hunksfilter" factory (similar
to "filematcher") that, for a given "rev", produces a filtering function for
The logic to build revisions from -L/--line-range options lives in
cmdutil.getloglinerangerevs() which produces "revs", "filematcher" and
"hunksfilter" information. Revisions obtained by following files' line range
are filtered if they do not match the revset specified by --rev option. If
regular FILE arguments are passed along with -L options, both filematchers are
combined into a new matcher.
.. feature::
Add an experimental -L/--line-range FILE,FROMLINE-TOLINE option to 'hg log'
command to follow the history of files by line range. In combination with
-p/--patch option, only diff hunks within specified line range will be
displayed. Feedback, especially on UX aspects, is welcome.
@@ -26,12 +26,14 @@ from . import (
changelog,
copies,
crecord as crecordmod,
+ dagop,
dirstateguard,
encoding,
error,
formatter,
graphmod,
match as matchmod,
+ mdiff,
obsolete,
patch,
pathutil,
@@ -2573,6 +2575,87 @@ def getlogrevs(repo, pats, opts):
return revs, expr, filematcher
+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('-'))
+ 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]
+
+ # 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
+
+ return filterfn
+
+ def filematcher(rev):
+ files = list(linerangesbyrev.get(rev, []))
+ return scmutil.matchfiles(repo, files)
+
+ revs = sorted(linerangesbyrev, reverse=True)
+
+ return revs, filematcher, hunksfilter
+
def _graphnodeformatter(ui, displayer):
spec = ui.config('ui', 'graphnodetemplate')
if not spec:
@@ -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.
+
.. container:: verbose
Some examples:
@@ -3336,6 +3351,15 @@ def log(ui, repo, *pats, **opts):
hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
+ - changesets touching lines 13 to 23 for file.c::
+
+ hg log -L file.c,13-23
+
+ - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
+ main.c with patch::
+
+ hg log -L file.c,13-23 -L main.c,2-6 -p
+
See :hg:`help dates` for a list of formats valid for -d/--date.
See :hg:`help revisions` for more about specifying and ordering
@@ -3350,14 +3374,38 @@ def log(ui, repo, *pats, **opts):
"""
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
+
limit = cmdutil.loglimit(opts)
count = 0
@@ -3385,7 +3433,12 @@ def log(ui, repo, *pats, **opts):
revmatchfn = filematcher(ctx.rev())
else:
revmatchfn = None
- displayer.show(ctx, copies=copies, matchfn=revmatchfn)
+ if hunksfilter:
+ revhunksfilter = hunksfilter(rev)
+ else:
+ revhunksfilter = None
+ displayer.show(ctx, copies=copies, matchfn=revmatchfn,
+ hunksfilterfn=revhunksfilter)
if displayer.flush(ctx):
count += 1
@@ -225,7 +225,7 @@ Show all commands + options
export: output, switch-parent, rev, text, git, binary, nodates
forget: include, exclude
init: ssh, remotecmd, insecure
- log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
+ log: follow, follow-first, date, copies, keyword, rev, line-range, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
merge: force, rev, preview, tool
pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
push: force, rev, bookmark, branch, new-branch, pushvars, ssh, remotecmd, insecure
new file mode 100644
@@ -0,0 +1,869 @@
+ $ cat >> $HGRCPATH << EOF
+ > [diff]
+ > git = true
+ > EOF
+
+ $ hg init
+ $ cat > foo << EOF
+ > 0
+ > 1
+ > 2
+ > 3
+ > 4
+ > EOF
+ $ hg ci -Am init
+ adding foo
+ $ cat > foo << EOF
+ > 0
+ > 0
+ > 0
+ > 0
+ > 1
+ > 2
+ > 3
+ > 4
+ > EOF
+ $ hg ci -m 'more 0'
+ $ sed 's/2/2+/' foo > foo.new
+ $ mv foo.new foo
+ $ cat > bar << EOF
+ > a
+ > b
+ > c
+ > d
+ > e
+ > EOF
+ $ hg add bar
+ $ hg ci -Am "2 -> 2+; added bar"
+ $ cat >> foo << EOF
+ > 5
+ > 6
+ > 7
+ > 8
+ > 9
+ > 10
+ > 11
+ > EOF
+ $ hg ci -m "to 11"
+
+Add some changes with two diff hunks
+
+ $ sed 's/^1$/ 1/' foo > foo.new
+ $ mv foo.new foo
+ $ sed 's/^11$/11+/' foo > foo.new
+ $ mv foo.new foo
+ $ hg ci -m '11 -> 11+; leading space before "1"'
+(make sure there are two hunks in "foo")
+ $ hg diff -c .
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -2,7 +2,7 @@
+ 0
+ 0
+ 0
+ -1
+ + 1
+ 2+
+ 3
+ 4
+ @@ -12,4 +12,4 @@
+ 8
+ 9
+ 10
+ -11
+ +11+
+ $ sed 's/3/3+/' foo > foo.new
+ $ mv foo.new foo
+ $ sed 's/^11+$/11-/' foo > foo.new
+ $ mv foo.new foo
+ $ sed 's/a/a+/' bar > bar.new
+ $ mv bar.new bar
+ $ hg ci -m 'foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+'
+(make sure there are two hunks in "foo")
+ $ hg diff -c . foo
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -4,7 +4,7 @@
+ 0
+ 1
+ 2+
+ -3
+ +3+
+ 4
+ 5
+ 6
+ @@ -12,4 +12,4 @@
+ 8
+ 9
+ 10
+ -11+
+ +11-
+
+ $ hg log -f -L foo,5-7 -p
+ changeset: 5:cfdf972b3971
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -4,7 +4,7 @@
+ 0
+ 1
+ 2+
+ -3
+ +3+
+ 4
+ 5
+ 6
+
+ changeset: 4:eaec41c1a0c9
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 11 -> 11+; leading space before "1"
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -2,7 +2,7 @@
+ 0
+ 0
+ 0
+ -1
+ + 1
+ 2+
+ 3
+ 4
+
+ changeset: 2:63a884426fd0
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 2 -> 2+; added bar
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -3,6 +3,6 @@
+ 0
+ 0
+ 1
+ -2
+ +2+
+ 3
+ 4
+
+ changeset: 0:5ae1f82b9a00
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: init
+
+ diff --git a/foo b/foo
+ new file mode 100644
+ --- /dev/null
+ +++ b/foo
+ @@ -0,0 +1,5 @@
+ +0
+ +1
+ +2
+ +3
+ +4
+
+
+With --template.
+
+ $ hg log -f -L foo,5-7 -T '{rev}:{node|short} {desc|firstline}\n'
+ 5:cfdf972b3971 foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+ 4:eaec41c1a0c9 11 -> 11+; leading space before "1"
+ 2:63a884426fd0 2 -> 2+; added bar
+ 0:5ae1f82b9a00 init
+ $ hg log -f -L foo,5-7 -T json
+ [
+ {
+ "rev": 5,
+ "node": "cfdf972b3971a2a59638bf9583c0debbffee5404",
+ "branch": "default",
+ "phase": "draft",
+ "user": "test",
+ "date": [0, 0],
+ "desc": "foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+",
+ "bookmarks": [],
+ "tags": ["tip"],
+ "parents": ["eaec41c1a0c9ad0a5e999611d0149d171beffb8c"]
+ },
+ {
+ "rev": 4,
+ "node": "eaec41c1a0c9ad0a5e999611d0149d171beffb8c",
+ "branch": "default",
+ "phase": "draft",
+ "user": "test",
+ "date": [0, 0],
+ "desc": "11 -> 11+; leading space before \"1\"",
+ "bookmarks": [],
+ "tags": [],
+ "parents": ["730a61fbaecf426c17c2c66bc42d195b5d5b0ba8"]
+ },
+ {
+ "rev": 2,
+ "node": "63a884426fd0b277fcd55895bbb2f230434576eb",
+ "branch": "default",
+ "phase": "draft",
+ "user": "test",
+ "date": [0, 0],
+ "desc": "2 -> 2+; added bar",
+ "bookmarks": [],
+ "tags": [],
+ "parents": ["29a1e7c6b80024f63f310a2d71de979e9d2996d7"]
+ },
+ {
+ "rev": 0,
+ "node": "5ae1f82b9a000ff1e0967d0dac1c58b9d796e1b4",
+ "branch": "default",
+ "phase": "draft",
+ "user": "test",
+ "date": [0, 0],
+ "desc": "init",
+ "bookmarks": [],
+ "tags": [],
+ "parents": ["0000000000000000000000000000000000000000"]
+ }
+ ]
+
+With some white-space diff option, respective revisions are skipped.
+
+ $ hg log -f -L foo,5-7 -p --config diff.ignorews=true
+ changeset: 5:cfdf972b3971
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -4,7 +4,7 @@
+ 0
+ 1
+ 2+
+ -3
+ +3+
+ 4
+ 5
+ 6
+
+ changeset: 2:63a884426fd0
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 2 -> 2+; added bar
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -3,6 +3,6 @@
+ 0
+ 0
+ 1
+ -2
+ +2+
+ 3
+ 4
+
+ changeset: 0:5ae1f82b9a00
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: init
+
+ diff --git a/foo b/foo
+ new file mode 100644
+ --- /dev/null
+ +++ b/foo
+ @@ -0,0 +1,5 @@
+ +0
+ +1
+ +2
+ +3
+ +4
+
+
+Regular file patterns are allowed with -L and their diff shows all lines.
+
+ $ hg log -f -L foo,5-7 -p bar
+ changeset: 5:cfdf972b3971
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+
+ diff --git a/bar b/bar
+ --- a/bar
+ +++ b/bar
+ @@ -1,4 +1,4 @@
+ -a
+ +a+
+ b
+ c
+ d
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -4,7 +4,7 @@
+ 0
+ 1
+ 2+
+ -3
+ +3+
+ 4
+ 5
+ 6
+
+ changeset: 2:63a884426fd0
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 2 -> 2+; added bar
+
+ diff --git a/bar b/bar
+ new file mode 100644
+ --- /dev/null
+ +++ b/bar
+ @@ -0,0 +1,5 @@
+ +a
+ +b
+ +c
+ +d
+ +e
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -3,6 +3,6 @@
+ 0
+ 0
+ 1
+ -2
+ +2+
+ 3
+ 4
+
+
+Option --rev acts as a restriction.
+
+ $ hg log -f -L foo,5-7 -p -r 'desc(2)'
+ changeset: 2:63a884426fd0
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 2 -> 2+; added bar
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -3,6 +3,6 @@
+ 0
+ 0
+ 1
+ -2
+ +2+
+ 3
+ 4
+
+ changeset: 0:5ae1f82b9a00
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: init
+
+ diff --git a/foo b/foo
+ new file mode 100644
+ --- /dev/null
+ +++ b/foo
+ @@ -0,0 +1,5 @@
+ +0
+ +1
+ +2
+ +3
+ +4
+
+
+With several -L patterns, changes touching any files in their respective line
+range are show.
+
+ $ hg log -f -L foo,5-7 -L bar,1-2 -p
+ changeset: 5:cfdf972b3971
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+
+ diff --git a/bar b/bar
+ --- a/bar
+ +++ b/bar
+ @@ -1,4 +1,4 @@
+ -a
+ +a+
+ b
+ c
+ d
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -4,7 +4,7 @@
+ 0
+ 1
+ 2+
+ -3
+ +3+
+ 4
+ 5
+ 6
+
+ changeset: 4:eaec41c1a0c9
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 11 -> 11+; leading space before "1"
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -2,7 +2,7 @@
+ 0
+ 0
+ 0
+ -1
+ + 1
+ 2+
+ 3
+ 4
+
+ changeset: 2:63a884426fd0
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 2 -> 2+; added bar
+
+ diff --git a/bar b/bar
+ new file mode 100644
+ --- /dev/null
+ +++ b/bar
+ @@ -0,0 +1,5 @@
+ +a
+ +b
+ +c
+ +d
+ +e
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -3,6 +3,6 @@
+ 0
+ 0
+ 1
+ -2
+ +2+
+ 3
+ 4
+
+ changeset: 0:5ae1f82b9a00
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: init
+
+ diff --git a/foo b/foo
+ new file mode 100644
+ --- /dev/null
+ +++ b/foo
+ @@ -0,0 +1,5 @@
+ +0
+ +1
+ +2
+ +3
+ +4
+
+
+Multiple -L options with the same file yields changes touching any of
+specified line ranges.
+
+ $ hg log -f -L foo,5-7 -L foo,14-15 -p
+ changeset: 5:cfdf972b3971
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -4,7 +4,7 @@
+ 0
+ 1
+ 2+
+ -3
+ +3+
+ 4
+ 5
+ 6
+ @@ -12,4 +12,4 @@
+ 8
+ 9
+ 10
+ -11+
+ +11-
+
+ changeset: 4:eaec41c1a0c9
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 11 -> 11+; leading space before "1"
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -2,7 +2,7 @@
+ 0
+ 0
+ 0
+ -1
+ + 1
+ 2+
+ 3
+ 4
+ @@ -12,4 +12,4 @@
+ 8
+ 9
+ 10
+ -11
+ +11+
+
+ changeset: 3:730a61fbaecf
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: to 11
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -6,3 +6,10 @@
+ 2+
+ 3
+ 4
+ +5
+ +6
+ +7
+ +8
+ +9
+ +10
+ +11
+
+ changeset: 2:63a884426fd0
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 2 -> 2+; added bar
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -3,6 +3,6 @@
+ 0
+ 0
+ 1
+ -2
+ +2+
+ 3
+ 4
+
+ changeset: 0:5ae1f82b9a00
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: init
+
+ diff --git a/foo b/foo
+ new file mode 100644
+ --- /dev/null
+ +++ b/foo
+ @@ -0,0 +1,5 @@
+ +0
+ +1
+ +2
+ +3
+ +4
+
+
+A file with a comma in its name.
+
+ $ cat > ba,z << EOF
+ > q
+ > w
+ > e
+ > r
+ > t
+ > y
+ > EOF
+ $ hg ci -Am 'querty'
+ adding ba,z
+ $ cat >> ba,z << EOF
+ > u
+ > i
+ > o
+ > p
+ > EOF
+ $ hg ci -m 'more keys'
+ $ cat > ba,z << EOF
+ > a
+ > z
+ > e
+ > r
+ > t
+ > y
+ > u
+ > i
+ > o
+ > p
+ > EOF
+ $ hg ci -m 'azerty'
+ $ hg log -f -L ba,z,1-2 -p
+ changeset: 8:52373265138b
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: azerty
+
+ diff --git a/ba,z b/ba,z
+ --- a/ba,z
+ +++ b/ba,z
+ @@ -1,5 +1,5 @@
+ -q
+ -w
+ +a
+ +z
+ e
+ r
+ t
+
+ changeset: 6:96ba8850f316
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: querty
+
+ diff --git a/ba,z b/ba,z
+ new file mode 100644
+ --- /dev/null
+ +++ b/ba,z
+ @@ -0,0 +1,6 @@
+ +q
+ +w
+ +e
+ +r
+ +t
+ +y
+
+
+Exact prefix kinds work in -L options.
+
+ $ mkdir dir
+ $ cd dir
+ $ hg log -f -L path:foo,5-7 -p
+ changeset: 5:cfdf972b3971
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -4,7 +4,7 @@
+ 0
+ 1
+ 2+
+ -3
+ +3+
+ 4
+ 5
+ 6
+
+ changeset: 4:eaec41c1a0c9
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 11 -> 11+; leading space before "1"
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -2,7 +2,7 @@
+ 0
+ 0
+ 0
+ -1
+ + 1
+ 2+
+ 3
+ 4
+
+ changeset: 2:63a884426fd0
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 2 -> 2+; added bar
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -3,6 +3,6 @@
+ 0
+ 0
+ 1
+ -2
+ +2+
+ 3
+ 4
+
+ changeset: 0:5ae1f82b9a00
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: init
+
+ diff --git a/foo b/foo
+ new file mode 100644
+ --- /dev/null
+ +++ b/foo
+ @@ -0,0 +1,5 @@
+ +0
+ +1
+ +2
+ +3
+ +4
+
+
+Renames are followed.
+
+ $ hg mv ../foo baz
+ $ sed 's/1/1+/' baz > baz.new
+ $ mv baz.new baz
+ $ hg ci -m 'foo -> dir/baz; 1-1+'
+ $ hg diff -c .
+ diff --git a/foo b/dir/baz
+ rename from foo
+ rename to dir/baz
+ --- a/foo
+ +++ b/dir/baz
+ @@ -2,7 +2,7 @@
+ 0
+ 0
+ 0
+ - 1
+ + 1+
+ 2+
+ 3+
+ 4
+ @@ -11,5 +11,5 @@
+ 7
+ 8
+ 9
+ -10
+ -11-
+ +1+0
+ +1+1-
+ $ hg log -f -L relpath:baz,5-7 -p
+ changeset: 9:6af29c3a778f
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: foo -> dir/baz; 1-1+
+
+ diff --git a/foo b/dir/baz
+ copy from foo
+ copy to dir/baz
+ --- a/foo
+ +++ b/dir/baz
+ @@ -2,7 +2,7 @@
+ 0
+ 0
+ 0
+ - 1
+ + 1+
+ 2+
+ 3+
+ 4
+
+ changeset: 5:cfdf972b3971
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -4,7 +4,7 @@
+ 0
+ 1
+ 2+
+ -3
+ +3+
+ 4
+ 5
+ 6
+
+ changeset: 4:eaec41c1a0c9
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 11 -> 11+; leading space before "1"
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -2,7 +2,7 @@
+ 0
+ 0
+ 0
+ -1
+ + 1
+ 2+
+ 3
+ 4
+
+ changeset: 2:63a884426fd0
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: 2 -> 2+; added bar
+
+ diff --git a/foo b/foo
+ --- a/foo
+ +++ b/foo
+ @@ -3,6 +3,6 @@
+ 0
+ 0
+ 1
+ -2
+ +2+
+ 3
+ 4
+
+ changeset: 0:5ae1f82b9a00
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: init
+
+ diff --git a/foo b/foo
+ new file mode 100644
+ --- /dev/null
+ +++ b/foo
+ @@ -0,0 +1,5 @@
+ +0
+ +1
+ +2
+ +3
+ +4
+
+Option --follow is required.
+
+ $ hg log -L foo,5-7
+ abort: --line-range requires --follow
+ [255]
+
+Non-exact pattern kinds are not allowed.
+
+ $ cd ..
+ $ hg log -f -L glob:*a*,1-2
+ hg: parse error: line range pattern 'glob:*a*' must match exactly one file
+ [255]
+
+Graph log does work yet.
+
+ $ hg log -f -L dir/baz,5-7 --graph
+ abort: graph not supported with line range patterns
+ [255]