diff --git a/Documentation/config/blame.adoc b/Documentation/config/blame.adoc index 4d047c17908cd6..23c64dee69e772 100644 --- a/Documentation/config/blame.adoc +++ b/Documentation/config/blame.adoc @@ -35,3 +35,21 @@ blame.markUnblamableLines:: blame.markIgnoredLines:: Mark lines that were changed by an ignored revision that we attributed to another commit with a '?' in the output of linkgit:git-blame[1]. + +blame.renames:: + If set to `false`, disable rename following in + linkgit:git-blame[1]. This option defaults to `true`. + +blame.renameThreshold:: + The minimum similarity threshold for rename detection in + linkgit:git-blame[1]; equivalent to the `-M` option of + linkgit:git-diff[1]. The value is a percentage (e.g. `50%`), + or a fraction between 0 and 1 (e.g. `0.5`). If not set, the + default is 50%. To limit blame to only follow exact renames, + set `blame.renameThreshold = 100%`. + +blame.renameLimit:: + The number of files to consider when performing rename + detection in linkgit:git-blame[1]; equivalent to the `-l` + option of linkgit:git-diff[1]. If not set, the default + value is currently 1000. diff --git a/Documentation/config/diff.adoc b/Documentation/config/diff.adoc index 1a889ce2277645..c556470eba5a01 100644 --- a/Documentation/config/diff.adoc +++ b/Documentation/config/diff.adoc @@ -160,10 +160,6 @@ endif::git-diff[] percentage (e.g. `50%`), or a fraction between 0 and 1 (e.g. `0.5`). If not set, the default is 50%. This setting has no effect if rename detection is turned off. -+ -This config setting is also respected by linkgit:git-blame[1]; -to limit blame to only consider exact renames, for example, set -`diff.renameThreshold = 100%`. `diff.renames`:: Whether and how Git detects renames. If set to `false`, diff --git a/Documentation/git-blame.adoc b/Documentation/git-blame.adoc index 1ce4ae781251ad..465549df77149b 100644 --- a/Documentation/git-blame.adoc +++ b/Documentation/git-blame.adoc @@ -24,11 +24,11 @@ When specified one or more times, `-L` restricts annotation to the requested lines. The origin of lines is automatically followed across whole-file -renames (currently there is no option to turn the rename-following -off, but the minimum similarity threshold can be adjusted with -`diff.renameThreshold`; see linkgit:git-diff[1]). To follow lines -moved from one file to another, or to follow lines that were copied -and pasted from another file, etc., see the `-C` and `-M` options. +renames. This can be disabled with `blame.renames`, and the minimum +similarity threshold can be adjusted with `blame.renameThreshold` +(see linkgit:git-config[1]). To follow lines moved from one file to +another, or to follow lines that were copied and pasted from another +file, etc., see the `-C` and `-M` options. The report does not tell you anything about lines which have been deleted or replaced; you need to use a tool such as `git diff` or the "pickaxe" diff --git a/builtin/blame.c b/builtin/blame.c index 6044973462a173..4999774cdabc0a 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -65,6 +65,7 @@ static int incremental; static int xdl_opts; static int abbrev = -1; static int no_whole_file_rename; +static int blame_detect_rename = -1; static int show_progress; static char repeated_meta_color[COLOR_MAXLEN]; static int coloring_mode; @@ -780,6 +781,27 @@ static int git_blame_config(const char *var, const char *value, } } + if (!strcmp(var, "blame.renames")) { + blame_detect_rename = git_config_bool(var, value); + return 0; + } + + /* + * Blame does not use git_diff_basic_config in its config + * chain, so diff_rename_score_default is not normally loaded. + * Forward blame.renameThreshold as diff.renameThreshold to + * set the global that repo_diff_setup() copies into + * diff_options.rename_score. + */ + if (!strcmp(var, "blame.renamethreshold")) + return git_diff_basic_config("diff.renamethreshold", + value, ctx, cb); + + /* Same approach for blame.renameLimit; see above. */ + if (!strcmp(var, "blame.renamelimit")) + return git_diff_basic_config("diff.renamelimit", + value, ctx, cb); + if (!strcmp(var, "diff.algorithm")) { long diff_algorithm; if (!value) @@ -1028,7 +1050,10 @@ int cmd_blame(int argc, } parse_done: revision_opts_finish(&revs); - no_whole_file_rename = !revs.diffopt.flags.follow_renames; + if (blame_detect_rename >= 0) + no_whole_file_rename = !blame_detect_rename; + if (!revs.diffopt.flags.follow_renames) + no_whole_file_rename = 1; xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC; revs.diffopt.flags.follow_renames = 0; argc = parse_options_end(&ctx); diff --git a/diff.c b/diff.c index be5fc8cae27634..65d0facc486f7e 100644 --- a/diff.c +++ b/diff.c @@ -399,15 +399,6 @@ int git_diff_ui_config(const char *var, const char *value, diff_detect_rename_default = git_config_rename(var, value); return 0; } - if (!strcmp(var, "diff.renamethreshold")) { - const char *arg = value; - if (!value) - return config_error_nonbool(var); - diff_rename_score_default = parse_rename_score(&arg); - if (*arg) - return error(_("invalid value for '%s': '%s'"), var, value); - return 0; - } if (!strcmp(var, "diff.autorefreshindex")) { diff_auto_refresh_index = git_config_bool(var, value); return 0; @@ -494,6 +485,16 @@ int git_diff_basic_config(const char *var, const char *value, return 0; } + if (!strcmp(var, "diff.renamethreshold")) { + const char *arg = value; + if (!value) + return config_error_nonbool(var); + diff_rename_score_default = parse_rename_score(&arg); + if (*arg) + return error(_("invalid value for '%s': '%s'"), var, value); + return 0; + } + if (userdiff_config(var, value) < 0) return -1; diff --git a/t/meson.build b/t/meson.build index 6062ff76d953d7..b3be46e8616c29 100644 --- a/t/meson.build +++ b/t/meson.build @@ -982,6 +982,7 @@ integration_tests = [ 't8013-blame-ignore-revs.sh', 't8014-blame-ignore-fuzzy.sh', 't8015-blame-diff-algorithm.sh', + 't8016-blame-rename-config.sh', 't8020-last-modified.sh', 't8100-git-survey.sh', 't9001-send-email.sh', diff --git a/t/t8016-blame-rename-config.sh b/t/t8016-blame-rename-config.sh new file mode 100755 index 00000000000000..c6b34a11cf9bac --- /dev/null +++ b/t/t8016-blame-rename-config.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +test_description='git blame rename configuration options' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_write_lines line1 line2 line3 >v1-before-inexact.txt && + test_write_lines other1 other2 other3 >unrelated.txt && + git add v1-before-inexact.txt unrelated.txt && + GIT_AUTHOR_NAME=Original git commit -m "add files" && + + test_write_lines changed1 line2 line3 >v2-before-exact.txt && + git rm v1-before-inexact.txt && + git rm unrelated.txt && + git add v2-before-exact.txt && + GIT_AUTHOR_NAME=Inexact git commit -m "inexact rename with content change" && + + git mv v2-before-exact.txt v3.txt && + GIT_AUTHOR_NAME=Exact git commit -m "exact rename" +' + +test_expect_success 'blame follows renames by default' ' + git blame --porcelain v3.txt >output && + grep "^filename v1-before-inexact.txt" output +' + +test_expect_success 'blame.renames=false disables rename following' ' + git -c blame.renames=false blame --porcelain v3.txt >output && + ! grep "^filename v1-before-inexact.txt" output && + ! grep "^filename v2-before-exact.txt" output +' + +test_expect_success 'blame.renameThreshold=100% allows exact but skips inexact renames' ' + git -c blame.renameThreshold=100% blame --porcelain v3.txt >output && + grep "^filename v2-before-exact.txt" output && + ! grep "^filename v1-before-inexact.txt" output +' + +test_expect_success 'blame.renameLimit=1 skips when sources*destinations exceeds limit' ' + git -c blame.renameLimit=1 blame --porcelain v3.txt >output && + grep "^filename v2-before-exact.txt" output && + ! grep "^filename v1-before-inexact.txt" output +' + +test_expect_success 'blame.renameLimit=2 detects with two sources' ' + git -c blame.renameLimit=2 blame --porcelain v3.txt >output && + grep "^filename v1-before-inexact.txt" output +' + +test_done