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