diff --git a/Documentation/config/diff.adoc b/Documentation/config/diff.adoc index 1135a62a0ad3de..c556470eba5a01 100644 --- a/Documentation/config/diff.adoc +++ b/Documentation/config/diff.adoc @@ -154,6 +154,13 @@ endif::git-diff[] `-l`. If not set, the default value is currently 1000. This setting has no effect if rename detection is turned off. +`diff.renameThreshold`:: + The minimum similarity threshold for rename detection; + equivalent to the `git diff` option `-M`. 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%. This setting + has no effect if rename detection is turned off. + `diff.renames`:: Whether and how Git detects renames. If set to `false`, rename detection is disabled. If set to `true`, basic rename diff --git a/Documentation/config/merge.adoc b/Documentation/config/merge.adoc index 15a4c14c38aade..a525d96fa3947a 100644 --- a/Documentation/config/merge.adoc +++ b/Documentation/config/merge.adoc @@ -47,6 +47,13 @@ include::fmt-merge-msg.adoc[] currently defaults to 7000. This setting has no effect if rename detection is turned off. +`merge.renameThreshold`:: + The minimum similarity threshold for rename detection during + a merge. If not specified, defaults to the value of + `diff.renameThreshold`. See `diff.renameThreshold` for the + accepted value format. This setting has no effect if rename + detection is turned off. + `merge.renames`:: Whether Git detects renames. If set to `false`, rename detection is disabled. If set to `true`, basic rename detection is enabled. diff --git a/Documentation/config/status.adoc b/Documentation/config/status.adoc index fa393e703eb01c..dce8bf418cc8a8 100644 --- a/Documentation/config/status.adoc +++ b/Documentation/config/status.adoc @@ -29,6 +29,12 @@ status.renameLimit:: in linkgit:git-status[1] and linkgit:git-commit[1]. Defaults to the value of diff.renameLimit. +status.renameThreshold:: + The minimum similarity threshold for rename detection in + linkgit:git-status[1] and linkgit:git-commit[1]. Defaults to + the value of diff.renameThreshold. See `diff.renameThreshold` + for the accepted value format. + status.renames:: Whether and how Git detects renames in linkgit:git-status[1] and linkgit:git-commit[1] . If set to "false", rename detection is diff --git a/builtin/commit.c b/builtin/commit.c index 82f5a66a9242c5..2b0ee37863da3f 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1672,6 +1672,22 @@ static int git_status_config(const char *k, const char *v, s->detect_rename = git_config_rename(k, v); return 0; } + if (!strcmp(k, "diff.renamethreshold")) { + if (s->rename_score == -1) { + const char *arg = v; + if (!v) + return config_error_nonbool(k); + s->rename_score = parse_rename_score(&arg); + } + return 0; + } + if (!strcmp(k, "status.renamethreshold")) { + const char *arg = v; + if (!v) + return config_error_nonbool(k); + s->rename_score = parse_rename_score(&arg); + return 0; + } return git_diff_ui_config(k, v, ctx, NULL); } diff --git a/diff.c b/diff.c index 05495e40172721..be5fc8cae27634 100644 --- a/diff.c +++ b/diff.c @@ -56,6 +56,7 @@ static int diff_detect_rename_default; static int diff_indent_heuristic = 1; static int diff_rename_limit_default = 1000; +static int diff_rename_score_default; static int diff_suppress_blank_empty; static enum git_colorbool diff_use_color_default = GIT_COLOR_UNKNOWN; static int diff_color_moved_default; @@ -398,6 +399,15 @@ 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; @@ -4856,6 +4866,7 @@ void repo_diff_setup(struct repository *r, struct diff_options *options) options->add_remove = diff_addremove; options->use_color = diff_use_color_default; options->detect_rename = diff_detect_rename_default; + options->rename_score = diff_rename_score_default; options->xdl_opts |= diff_algorithm; if (diff_indent_heuristic) DIFF_XDL_SET(options, INDENT_HEURISTIC); diff --git a/merge-ort.c b/merge-ort.c index 90166e54f9d19d..86bf9cc4fca2ec 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -5444,6 +5444,16 @@ static void merge_recursive_config(struct merge_options *opt, int ui) repo_config_get_int(the_repository, "merge.verbosity", &opt->verbosity); repo_config_get_int(the_repository, "diff.renamelimit", &opt->rename_limit); repo_config_get_int(the_repository, "merge.renamelimit", &opt->rename_limit); + if (!repo_config_get_string(the_repository, "diff.renamethreshold", &value)) { + const char *arg = value; + opt->rename_score = parse_rename_score(&arg); + free(value); + } + if (!repo_config_get_string(the_repository, "merge.renamethreshold", &value)) { + const char *arg = value; + opt->rename_score = parse_rename_score(&arg); + free(value); + } repo_config_get_bool(the_repository, "merge.renormalize", &renormalize); opt->renormalize = renormalize; if (!repo_config_get_string(the_repository, "diff.renames", &value)) { diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 4f520d600de495..eda98dee3e449c 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -126,6 +126,21 @@ test_expect_success 'test diff.renames unset' ' compare_diff_patch current expected ' +test_expect_success 'diff.renameThreshold=100% suppresses inexact rename in diff' ' + git -c diff.renameThreshold=100% diff --cached $tree >current && + compare_diff_patch current no-rename +' + +test_expect_success 'diff.renameThreshold=1% detects rename in diff' ' + git -c diff.renameThreshold=1% diff --cached $tree >current && + compare_diff_patch current expected +' + +test_expect_success '-M overrides diff.renameThreshold' ' + git -c diff.renameThreshold=100% diff -M --cached $tree >current && + compare_diff_patch current expected +' + test_expect_success 'favour same basenames over different ones' ' cp path1 another-path && git add another-path && @@ -155,6 +170,16 @@ test_expect_success 'favour same basenames even with minor differences' ' test_grep "renamed: .*path1 -> subdir/path1" out ' +test_expect_success 'diff.renameThreshold with modified rename in status' ' + git show HEAD:path1 | sed -e "s/Line 1/Changed 1/" \ + -e "s/Line 2/Changed 2/" -e "s/Line 3/Changed 3/" >subdir/path1 && + git add subdir/path1 && + git -c diff.renameThreshold=100% status >out && + test_grep ! "renamed:" out && + git -c diff.renameThreshold=1% status >out && + test_grep "renamed:" out +' + test_expect_success 'two files with same basename and same content' ' git reset --hard && mkdir -p dir/A dir/B && diff --git a/t/t6434-merge-recursive-rename-options.sh b/t/t6434-merge-recursive-rename-options.sh index 5a6f74839cb716..00ae0080c06041 100755 --- a/t/t6434-merge-recursive-rename-options.sh +++ b/t/t6434-merge-recursive-rename-options.sh @@ -332,4 +332,36 @@ test_expect_success 'merge.renames overrides diff.renames' ' $check_50 ' +test_expect_success 'diff.renameThreshold sets merge threshold' ' + git read-tree --reset -u HEAD && + test_must_fail git -c diff.renameThreshold=$th0 merge-recursive $tail && + check_threshold_0 +' + +test_expect_success 'diff.renameThreshold=100% limits to exact renames in merge' ' + git read-tree --reset -u HEAD && + test_must_fail git -c diff.renameThreshold=100% merge-recursive $tail && + check_exact_renames +' + +test_expect_success 'merge.renameThreshold overrides diff.renameThreshold' ' + git read-tree --reset -u HEAD && + test_must_fail git -c diff.renameThreshold=100% \ + -c merge.renameThreshold=$th0 merge-recursive $tail && + check_threshold_0 +' + +test_expect_success 'merge.renameThreshold defaults to diff.renameThreshold' ' + git read-tree --reset -u HEAD && + test_must_fail git -c diff.renameThreshold=$th2 merge-recursive $tail && + check_threshold_2 +' + +test_expect_success '--find-renames overrides merge.renameThreshold' ' + git read-tree --reset -u HEAD && + test_must_fail git -c merge.renameThreshold=100% \ + merge-recursive --find-renames=$th0 $tail && + check_threshold_0 +' + test_done diff --git a/t/t7525-status-rename.sh b/t/t7525-status-rename.sh index d409de1a33fd79..0e2634a5f7e5bb 100755 --- a/t/t7525-status-rename.sh +++ b/t/t7525-status-rename.sh @@ -97,6 +97,38 @@ test_expect_success 'status score=01%' ' test_grep "renamed:" actual ' +test_expect_success 'diff.renameThreshold sets default threshold' ' + git -c diff.renameThreshold=100% status >actual && + test_grep "deleted:" actual && + test_grep "new file:" actual +' + +test_expect_success 'status.renameThreshold overrides diff.renameThreshold' ' + git -c diff.renameThreshold=100% -c status.renameThreshold=01% status >actual && + test_grep "renamed:" actual +' + +test_expect_success 'diff.renameThreshold=01% detects rename in status' ' + git -c diff.renameThreshold=01% status >actual && + test_grep "renamed:" actual +' + +test_expect_success 'commit honors diff.renameThreshold' ' + git -c diff.renameThreshold=100% commit --dry-run >actual && + test_grep "deleted:" actual && + test_grep "new file:" actual +' + +test_expect_success 'commit honors status.renameThreshold' ' + git -c status.renameThreshold=01% commit --dry-run >actual && + test_grep "renamed:" actual +' + +test_expect_success '-M overrides status.renameThreshold' ' + git -c status.renameThreshold=100% status -M=01% >actual && + test_grep "renamed:" actual +' + test_expect_success 'copies not overridden by find-renames' ' cp renamed copy && git add copy &&