Skip to content

Commit e8b2dc2

Browse files
Ben Peartgitster
authored andcommitted
add status config and command line options for rename detection
After performing a merge that has conflicts git status will, by default, attempt to detect renames which causes many objects to be examined. In a virtualized repo, those objects do not exist locally so the rename logic triggers them to be fetched from the server. This results in the status call taking hours to complete on very large repos vs seconds with this patch. Add a new config status.renames setting to enable turning off rename detection during status and commit. This setting will default to the value of diff.renames. Add a new config status.renamelimit setting to to enable bounding the time spent finding out inexact renames during status and commit. This setting will default to the value of diff.renamelimit. Add --no-renames command line option to status that enables overriding the config setting from the command line. Add --find-renames[=<n>] command line option to status that enables detecting renames and optionally setting the similarity index. Reviewed-by: Elijah Newren <[email protected]> Original-Patch-by: Alejandro Pauly <[email protected]> Signed-off-by: Ben Peart <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent dc6b1d9 commit e8b2dc2

File tree

8 files changed

+194
-2
lines changed

8 files changed

+194
-2
lines changed

Documentation/config.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3119,6 +3119,18 @@ status.displayCommentPrefix::
31193119
behavior of linkgit:git-status[1] in Git 1.8.4 and previous.
31203120
Defaults to false.
31213121

3122+
status.renameLimit::
3123+
The number of files to consider when performing rename detection
3124+
in linkgit:git-status[1] and linkgit:git-commit[1]. Defaults to
3125+
the value of diff.renameLimit.
3126+
3127+
status.renames::
3128+
Whether and how Git detects renames in linkgit:git-status[1] and
3129+
linkgit:git-commit[1] . If set to "false", rename detection is
3130+
disabled. If set to "true", basic rename detection is enabled.
3131+
If set to "copies" or "copy", Git will detect copies, as well.
3132+
Defaults to the value of diff.renames.
3133+
31223134
status.showStash::
31233135
If set to true, linkgit:git-status[1] will display the number of
31243136
entries currently stashed away.

Documentation/git-status.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@ ignored, then the directory is not shown, but all contents are shown.
135135
Display or do not display detailed ahead/behind counts for the
136136
branch relative to its upstream branch. Defaults to true.
137137

138+
--renames::
139+
--no-renames::
140+
Turn on/off rename detection regardless of user configuration.
141+
See also linkgit:git-diff[1] `--no-renames`.
142+
143+
--find-renames[=<n>]::
144+
Turn on rename detection, optionally setting the similarity
145+
threshold.
146+
See also linkgit:git-diff[1] `--find-renames`.
147+
138148
<pathspec>...::
139149
See the 'pathspec' entry in linkgit:gitglossary[7].
140150

builtin/commit.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
143143
return 0;
144144
}
145145

146+
static int opt_parse_rename_score(const struct option *opt, const char *arg, int unset)
147+
{
148+
const char **value = opt->value;
149+
if (arg != NULL && *arg == '=')
150+
arg = arg + 1;
151+
152+
*value = arg;
153+
return 0;
154+
}
155+
146156
static void determine_whence(struct wt_status *s)
147157
{
148158
if (file_exists(git_path_merge_head()))
@@ -1259,11 +1269,31 @@ static int git_status_config(const char *k, const char *v, void *cb)
12591269
return error(_("Invalid untracked files mode '%s'"), v);
12601270
return 0;
12611271
}
1272+
if (!strcmp(k, "diff.renamelimit")) {
1273+
if (s->rename_limit == -1)
1274+
s->rename_limit = git_config_int(k, v);
1275+
return 0;
1276+
}
1277+
if (!strcmp(k, "status.renamelimit")) {
1278+
s->rename_limit = git_config_int(k, v);
1279+
return 0;
1280+
}
1281+
if (!strcmp(k, "diff.renames")) {
1282+
if (s->detect_rename == -1)
1283+
s->detect_rename = git_config_rename(k, v);
1284+
return 0;
1285+
}
1286+
if (!strcmp(k, "status.renames")) {
1287+
s->detect_rename = git_config_rename(k, v);
1288+
return 0;
1289+
}
12621290
return git_diff_ui_config(k, v, NULL);
12631291
}
12641292

12651293
int cmd_status(int argc, const char **argv, const char *prefix)
12661294
{
1295+
static int no_renames = -1;
1296+
static const char *rename_score_arg = (const char *)-1;
12671297
static struct wt_status s;
12681298
int fd;
12691299
struct object_id oid;
@@ -1297,6 +1327,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)
12971327
N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
12981328
PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
12991329
OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")),
1330+
OPT_BOOL(0, "no-renames", &no_renames, N_("do not detect renames")),
1331+
{ OPTION_CALLBACK, 'M', "find-renames", &rename_score_arg,
1332+
N_("n"), N_("detect renames, optionally set similarity index"),
1333+
PARSE_OPT_OPTARG, opt_parse_rename_score },
13001334
OPT_END(),
13011335
};
13021336

@@ -1336,6 +1370,14 @@ int cmd_status(int argc, const char **argv, const char *prefix)
13361370
s.ignore_submodule_arg = ignore_submodule_arg;
13371371
s.status_format = status_format;
13381372
s.verbose = verbose;
1373+
if (no_renames != -1)
1374+
s.detect_rename = !no_renames;
1375+
if ((intptr_t)rename_score_arg != -1) {
1376+
if (s.detect_rename < DIFF_DETECT_RENAME)
1377+
s.detect_rename = DIFF_DETECT_RENAME;
1378+
if (rename_score_arg)
1379+
s.rename_score = parse_rename_score(&rename_score_arg);
1380+
}
13391381

13401382
wt_status_collect(&s);
13411383

diff.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ static int parse_submodule_params(struct diff_options *options, const char *valu
177177
return 0;
178178
}
179179

180-
static int git_config_rename(const char *var, const char *value)
180+
int git_config_rename(const char *var, const char *value)
181181
{
182182
if (!value)
183183
return DIFF_DETECT_RENAME;

diff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ extern int git_diff_ui_config(const char *var, const char *value, void *cb);
324324
extern void diff_setup(struct diff_options *);
325325
extern int diff_opt_parse(struct diff_options *, const char **, int, const char *);
326326
extern void diff_setup_done(struct diff_options *);
327+
extern int git_config_rename(const char *var, const char *value);
327328

328329
#define DIFF_DETECT_RENAME 1
329330
#define DIFF_DETECT_COPY 2

t/t7525-status-rename.sh

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/bin/sh
2+
3+
test_description='git status rename detection options'
4+
5+
. ./test-lib.sh
6+
7+
test_expect_success 'setup' '
8+
echo 1 >original &&
9+
git add . &&
10+
git commit -m"Adding original file." &&
11+
mv original renamed &&
12+
echo 2 >> renamed &&
13+
git add . &&
14+
cat >.gitignore <<-\EOF
15+
.gitignore
16+
expect*
17+
actual*
18+
EOF
19+
'
20+
21+
test_expect_success 'status no-options' '
22+
git status >actual &&
23+
test_i18ngrep "renamed:" actual
24+
'
25+
26+
test_expect_success 'status --no-renames' '
27+
git status --no-renames >actual &&
28+
test_i18ngrep "deleted:" actual &&
29+
test_i18ngrep "new file:" actual
30+
'
31+
32+
test_expect_success 'status.renames inherits from diff.renames false' '
33+
git -c diff.renames=false status >actual &&
34+
test_i18ngrep "deleted:" actual &&
35+
test_i18ngrep "new file:" actual
36+
'
37+
38+
test_expect_success 'status.renames inherits from diff.renames true' '
39+
git -c diff.renames=true status >actual &&
40+
test_i18ngrep "renamed:" actual
41+
'
42+
43+
test_expect_success 'status.renames overrides diff.renames false' '
44+
git -c diff.renames=true -c status.renames=false status >actual &&
45+
test_i18ngrep "deleted:" actual &&
46+
test_i18ngrep "new file:" actual
47+
'
48+
49+
test_expect_success 'status.renames overrides from diff.renames true' '
50+
git -c diff.renames=false -c status.renames=true status >actual &&
51+
test_i18ngrep "renamed:" actual
52+
'
53+
54+
test_expect_success 'status status.renames=false' '
55+
git -c status.renames=false status >actual &&
56+
test_i18ngrep "deleted:" actual &&
57+
test_i18ngrep "new file:" actual
58+
'
59+
60+
test_expect_success 'status status.renames=true' '
61+
git -c status.renames=true status >actual &&
62+
test_i18ngrep "renamed:" actual
63+
'
64+
65+
test_expect_success 'commit honors status.renames=false' '
66+
git -c status.renames=false commit --dry-run >actual &&
67+
test_i18ngrep "deleted:" actual &&
68+
test_i18ngrep "new file:" actual
69+
'
70+
71+
test_expect_success 'commit honors status.renames=true' '
72+
git -c status.renames=true commit --dry-run >actual &&
73+
test_i18ngrep "renamed:" actual
74+
'
75+
76+
test_expect_success 'status config overridden' '
77+
git -c status.renames=true status --no-renames >actual &&
78+
test_i18ngrep "deleted:" actual &&
79+
test_i18ngrep "new file:" actual
80+
'
81+
82+
test_expect_success 'status score=100%' '
83+
git status -M=100% >actual &&
84+
test_i18ngrep "deleted:" actual &&
85+
test_i18ngrep "new file:" actual &&
86+
87+
git status --find-rename=100% >actual &&
88+
test_i18ngrep "deleted:" actual &&
89+
test_i18ngrep "new file:" actual
90+
'
91+
92+
test_expect_success 'status score=01%' '
93+
git status -M=01% >actual &&
94+
test_i18ngrep "renamed:" actual &&
95+
96+
git status --find-rename=01% >actual &&
97+
test_i18ngrep "renamed:" actual
98+
'
99+
100+
test_expect_success 'copies not overridden by find-rename' '
101+
cp renamed copy &&
102+
git add copy &&
103+
104+
git -c status.renames=copies status -M=01% >actual &&
105+
test_i18ngrep "copied:" actual &&
106+
test_i18ngrep "renamed:" actual &&
107+
108+
git -c status.renames=copies status --find-rename=01% >actual &&
109+
test_i18ngrep "copied:" actual &&
110+
test_i18ngrep "renamed:" actual
111+
'
112+
113+
test_done

wt-status.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ void wt_status_prepare(struct wt_status *s)
138138
s->show_stash = 0;
139139
s->ahead_behind_flags = AHEAD_BEHIND_UNSPECIFIED;
140140
s->display_comment_prefix = 0;
141+
s->detect_rename = -1;
142+
s->rename_score = -1;
143+
s->rename_limit = -1;
141144
}
142145

143146
static void wt_longstatus_print_unmerged_header(struct wt_status *s)
@@ -592,6 +595,9 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
592595
}
593596
rev.diffopt.format_callback = wt_status_collect_changed_cb;
594597
rev.diffopt.format_callback_data = s;
598+
rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
599+
rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
600+
rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
595601
copy_pathspec(&rev.prune_data, &s->pathspec);
596602
run_diff_files(&rev, 0);
597603
}
@@ -625,6 +631,9 @@ static void wt_status_collect_changes_index(struct wt_status *s)
625631
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
626632
rev.diffopt.format_callback = wt_status_collect_updated_cb;
627633
rev.diffopt.format_callback_data = s;
634+
rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
635+
rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
636+
rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
628637
copy_pathspec(&rev.prune_data, &s->pathspec);
629638
run_diff_index(&rev, 1);
630639
}
@@ -982,6 +991,9 @@ static void wt_longstatus_print_verbose(struct wt_status *s)
982991
setup_revisions(0, NULL, &rev, &opt);
983992

984993
rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
994+
rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
995+
rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
996+
rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
985997
rev.diffopt.file = s->fp;
986998
rev.diffopt.close_file = 0;
987999
/*

wt-status.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ struct wt_status {
8989
int show_stash;
9090
int hints;
9191
enum ahead_behind_flags ahead_behind_flags;
92-
92+
int detect_rename;
93+
int rename_score;
94+
int rename_limit;
9395
enum wt_status_format status_format;
9496
unsigned char sha1_commit[GIT_MAX_RAWSZ]; /* when not Initial */
9597

0 commit comments

Comments
 (0)