Mercurial > public > mercurial-scm > hg
comparison mercurial/commands.py @ 34857:84c6b9384d6a
log: add -L/--line-range option to follow file history by line range
We add an experimental -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.
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 diff hunks for a given file context object.
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.
author | Denis Laxalde <denis.laxalde@logilab.fr> |
---|---|
date | Tue, 17 Oct 2017 21:15:31 +0200 |
parents | 7e3001b74ab3 |
children | b1e3f609bf45 |
comparison
equal
deleted
inserted
replaced
34856:890afefa7296 | 34857:84c6b9384d6a |
---|---|
3232 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')), | 3232 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')), |
3233 ('C', 'copies', None, _('show copied files')), | 3233 ('C', 'copies', None, _('show copied files')), |
3234 ('k', 'keyword', [], | 3234 ('k', 'keyword', [], |
3235 _('do case-insensitive search for a given text'), _('TEXT')), | 3235 _('do case-insensitive search for a given text'), _('TEXT')), |
3236 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')), | 3236 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')), |
3237 ('L', 'line-range', [], | |
3238 _('follow line range of specified file (EXPERIMENTAL)'), | |
3239 _('FILE,RANGE')), | |
3237 ('', 'removed', None, _('include revisions where files were removed')), | 3240 ('', 'removed', None, _('include revisions where files were removed')), |
3238 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')), | 3241 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')), |
3239 ('u', 'user', [], _('revisions committed by user'), _('USER')), | 3242 ('u', 'user', [], _('revisions committed by user'), _('USER')), |
3240 ('', 'only-branch', [], | 3243 ('', 'only-branch', [], |
3241 _('show only changesets within the given named branch (DEPRECATED)'), | 3244 _('show only changesets within the given named branch (DEPRECATED)'), |
3273 and '+' represents a fork where the changeset from the lines below is a | 3276 and '+' represents a fork where the changeset from the lines below is a |
3274 parent of the 'o' merge on the same line. | 3277 parent of the 'o' merge on the same line. |
3275 Paths in the DAG are represented with '|', '/' and so forth. ':' in place | 3278 Paths in the DAG are represented with '|', '/' and so forth. ':' in place |
3276 of a '|' indicates one or more revisions in a path are omitted. | 3279 of a '|' indicates one or more revisions in a path are omitted. |
3277 | 3280 |
3281 .. container:: verbose | |
3282 | |
3283 Use -L/--line-range FILE,M-N options to follow the history of lines | |
3284 from M to N in FILE. With -p/--patch only diff hunks affecting | |
3285 specified line range will be shown. This option requires --follow; | |
3286 it can be specified multiple times. Currently, this option is not | |
3287 compatible with --graph. This option is experimental. | |
3288 | |
3278 .. note:: | 3289 .. note:: |
3279 | 3290 |
3280 :hg:`log --patch` may generate unexpected diff output for merge | 3291 :hg:`log --patch` may generate unexpected diff output for merge |
3281 changesets, as it will only compare the merge changeset against | 3292 changesets, as it will only compare the merge changeset against |
3282 its first parent. Also, only files different from BOTH parents | 3293 its first parent. Also, only files different from BOTH parents |
3288 made on branches and will not show removals or mode changes. To | 3299 made on branches and will not show removals or mode changes. To |
3289 see all such changes, use the --removed switch. | 3300 see all such changes, use the --removed switch. |
3290 | 3301 |
3291 .. container:: verbose | 3302 .. container:: verbose |
3292 | 3303 |
3304 .. note:: | |
3305 | |
3306 The history resulting from -L/--line-range options depends on diff | |
3307 options; for instance if white-spaces are ignored, respective changes | |
3308 with only white-spaces in specified line range will not be listed. | |
3309 | |
3310 .. container:: verbose | |
3311 | |
3293 Some examples: | 3312 Some examples: |
3294 | 3313 |
3295 - changesets with full descriptions and file lists:: | 3314 - changesets with full descriptions and file lists:: |
3296 | 3315 |
3297 hg log -v | 3316 hg log -v |
3333 hg log -k alice -d "may 2008 to jul 2008" | 3352 hg log -k alice -d "may 2008 to jul 2008" |
3334 | 3353 |
3335 - summary of all changesets after the last tag:: | 3354 - summary of all changesets after the last tag:: |
3336 | 3355 |
3337 hg log -r "last(tagged())::" --template "{desc|firstline}\\n" | 3356 hg log -r "last(tagged())::" --template "{desc|firstline}\\n" |
3357 | |
3358 - changesets touching lines 13 to 23 for file.c:: | |
3359 | |
3360 hg log -L file.c,13-23 | |
3361 | |
3362 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of | |
3363 main.c with patch:: | |
3364 | |
3365 hg log -L file.c,13-23 -L main.c,2-6 -p | |
3338 | 3366 |
3339 See :hg:`help dates` for a list of formats valid for -d/--date. | 3367 See :hg:`help dates` for a list of formats valid for -d/--date. |
3340 | 3368 |
3341 See :hg:`help revisions` for more about specifying and ordering | 3369 See :hg:`help revisions` for more about specifying and ordering |
3342 revisions. | 3370 revisions. |
3348 | 3376 |
3349 Returns 0 on success. | 3377 Returns 0 on success. |
3350 | 3378 |
3351 """ | 3379 """ |
3352 opts = pycompat.byteskwargs(opts) | 3380 opts = pycompat.byteskwargs(opts) |
3381 linerange = opts.get('line_range') | |
3382 | |
3383 if linerange and not opts.get('follow'): | |
3384 raise error.Abort(_('--line-range requires --follow')) | |
3385 | |
3353 if opts.get('follow') and opts.get('rev'): | 3386 if opts.get('follow') and opts.get('rev'): |
3354 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))] | 3387 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))] |
3355 del opts['follow'] | 3388 del opts['follow'] |
3356 | 3389 |
3357 if opts.get('graph'): | 3390 if opts.get('graph'): |
3391 if linerange: | |
3392 raise error.Abort(_('graph not supported with line range patterns')) | |
3358 return cmdutil.graphlog(ui, repo, pats, opts) | 3393 return cmdutil.graphlog(ui, repo, pats, opts) |
3359 | 3394 |
3360 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts) | 3395 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts) |
3396 hunksfilter = None | |
3397 | |
3398 if linerange: | |
3399 revs, lrfilematcher, hunksfilter = cmdutil.getloglinerangerevs( | |
3400 repo, revs, opts) | |
3401 | |
3402 if filematcher is not None and lrfilematcher is not None: | |
3403 basefilematcher = filematcher | |
3404 | |
3405 def filematcher(rev): | |
3406 files = (basefilematcher(rev).files() | |
3407 + lrfilematcher(rev).files()) | |
3408 return scmutil.matchfiles(repo, files) | |
3409 | |
3410 elif filematcher is None: | |
3411 filematcher = lrfilematcher | |
3412 | |
3361 limit = cmdutil.loglimit(opts) | 3413 limit = cmdutil.loglimit(opts) |
3362 count = 0 | 3414 count = 0 |
3363 | 3415 |
3364 getrenamed = None | 3416 getrenamed = None |
3365 if opts.get('copies'): | 3417 if opts.get('copies'): |
3383 copies.append((fn, rename[0])) | 3435 copies.append((fn, rename[0])) |
3384 if filematcher: | 3436 if filematcher: |
3385 revmatchfn = filematcher(ctx.rev()) | 3437 revmatchfn = filematcher(ctx.rev()) |
3386 else: | 3438 else: |
3387 revmatchfn = None | 3439 revmatchfn = None |
3388 displayer.show(ctx, copies=copies, matchfn=revmatchfn) | 3440 if hunksfilter: |
3441 revhunksfilter = hunksfilter(rev) | |
3442 else: | |
3443 revhunksfilter = None | |
3444 displayer.show(ctx, copies=copies, matchfn=revmatchfn, | |
3445 hunksfilterfn=revhunksfilter) | |
3389 if displayer.flush(ctx): | 3446 if displayer.flush(ctx): |
3390 count += 1 | 3447 count += 1 |
3391 | 3448 |
3392 displayer.close() | 3449 displayer.close() |
3393 | 3450 |