Skip to content

Commit 12accdc

Browse files
committed
Merge branch 'nd/ita-wt-renames-in-status' into maint
"git status" after moving a path in the working tree (hence making it appear "removed") and then adding with the -N option (hence making that appear "added") detected it as a rename, but did not report the old and new pathnames correctly. * nd/ita-wt-renames-in-status: wt-status.c: handle worktree renames wt-status.c: rename rename-related fields in wt_status_change_data wt-status.c: catch unhandled diff status codes wt-status.c: coding style fix Use DIFF_DETECT_RENAME for detect_rename assignments t2203: test status output with porcelain v2 format
2 parents ffa9524 + 176ea74 commit 12accdc

File tree

6 files changed

+143
-44
lines changed

6 files changed

+143
-44
lines changed

Documentation/git-status.txt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,15 @@ the status.relativePaths config option below.
149149
Short Format
150150
~~~~~~~~~~~~
151151

152-
In the short-format, the status of each path is shown as
152+
In the short-format, the status of each path is shown as one of these
153+
forms
153154

154-
XY PATH1 -> PATH2
155+
XY PATH
156+
XY ORIG_PATH -> PATH
155157

156-
where `PATH1` is the path in the `HEAD`, and the " `-> PATH2`" part is
157-
shown only when `PATH1` corresponds to a different path in the
158-
index/worktree (i.e. the file is renamed). The `XY` is a two-letter
159-
status code.
158+
where `ORIG_PATH` is where the renamed/copied contents came
159+
from. `ORIG_PATH` is only shown when the entry is renamed or
160+
copied. The `XY` is a two-letter status code.
160161

161162
The fields (including the `->`) are separated from each other by a
162163
single space. If a filename contains whitespace or other nonprintable
@@ -192,6 +193,8 @@ in which case `XY` are `!!`.
192193
[MARC] index and work tree matches
193194
[ MARC] M work tree changed since index
194195
[ MARC] D deleted in work tree
196+
[ D] R renamed in work tree
197+
[ D] C copied in work tree
195198
-------------------------------------------------
196199
D D unmerged, both deleted
197200
A U unmerged, added by us
@@ -309,13 +312,13 @@ Renamed or copied entries have the following format:
309312
of similarity between the source and target of the
310313
move or copy). For example "R100" or "C75".
311314
<path> The pathname. In a renamed/copied entry, this
312-
is the path in the index and in the working tree.
315+
is the target path.
313316
<sep> When the `-z` option is used, the 2 pathnames are separated
314317
with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09)
315318
byte separates them.
316-
<origPath> The pathname in the commit at HEAD. This is only
317-
present in a renamed/copied entry, and tells
318-
where the renamed/copied contents came from.
319+
<origPath> The pathname in the commit at HEAD or in the index.
320+
This is only present in a renamed/copied entry, and
321+
tells where the renamed/copied contents came from.
319322
--------------------------------------------------------
320323

321324
Unmerged entries have the following format; the first character is

builtin/commit.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1507,7 +1507,7 @@ static void print_summary(const char *prefix, const struct object_id *oid,
15071507
rev.show_root_diff = 1;
15081508
get_commit_format(format.buf, &rev);
15091509
rev.always_show_header = 0;
1510-
rev.diffopt.detect_rename = 1;
1510+
rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
15111511
rev.diffopt.break_opt = 0;
15121512
diff_setup_done(&rev.diffopt);
15131513

diff.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ static int parse_ws_error_highlight(const char *arg)
246246
*/
247247
void init_diff_ui_defaults(void)
248248
{
249-
diff_detect_rename_default = 1;
249+
diff_detect_rename_default = DIFF_DETECT_RENAME;
250250
}
251251

252252
int git_diff_heuristic_config(const char *var, const char *value, void *cb)

t/t2203-add-intent.sh

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ test_expect_success 'git status' '
2525
test_cmp expect actual
2626
'
2727

28+
test_expect_success 'git status with porcelain v2' '
29+
git status --porcelain=v2 | grep -v "^?" >actual &&
30+
nam1=d00491fd7e5bb6fa28c517a0bb32b8b506539d4d &&
31+
nam2=ce013625030ba8dba906f756967f9e9ca394464a &&
32+
cat >expect <<-EOF &&
33+
1 DA N... 100644 000000 100644 $nam1 $_z40 1.t
34+
1 A. N... 000000 100644 100644 $_z40 $nam2 elif
35+
1 .A N... 000000 000000 100644 $_z40 $_z40 file
36+
EOF
37+
test_cmp expect actual
38+
'
39+
2840
test_expect_success 'check result of "add -N"' '
2941
git ls-files -s file >actual &&
3042
empty=$(git hash-object --stdin </dev/null) &&
@@ -150,5 +162,65 @@ test_expect_success 'commit: ita entries ignored in empty commit check' '
150162
)
151163
'
152164

165+
test_expect_success 'rename detection finds the right names' '
166+
git init rename-detection &&
167+
(
168+
cd rename-detection &&
169+
echo contents >first &&
170+
git add first &&
171+
git commit -m first &&
172+
mv first third &&
173+
git add -N third &&
174+
175+
git status | grep -v "^?" >actual.1 &&
176+
test_i18ngrep "renamed: *first -> third" actual.1 &&
177+
178+
git status --porcelain | grep -v "^?" >actual.2 &&
179+
cat >expected.2 <<-\EOF &&
180+
R first -> third
181+
EOF
182+
test_cmp expected.2 actual.2 &&
183+
184+
hash=12f00e90b6ef79117ce6e650416b8cf517099b78 &&
185+
git status --porcelain=v2 | grep -v "^?" >actual.3 &&
186+
cat >expected.3 <<-EOF &&
187+
2 .R N... 100644 100644 100644 $hash $hash R100 third first
188+
EOF
189+
test_cmp expected.3 actual.3
190+
)
191+
'
192+
193+
test_expect_success 'double rename detection in status' '
194+
git init rename-detection-2 &&
195+
(
196+
cd rename-detection-2 &&
197+
echo contents >first &&
198+
git add first &&
199+
git commit -m first &&
200+
git mv first second &&
201+
mv second third &&
202+
git add -N third &&
203+
204+
git status | grep -v "^?" >actual.1 &&
205+
test_i18ngrep "renamed: *first -> second" actual.1 &&
206+
test_i18ngrep "renamed: *second -> third" actual.1 &&
207+
208+
git status --porcelain | grep -v "^?" >actual.2 &&
209+
cat >expected.2 <<-\EOF &&
210+
R first -> second
211+
R second -> third
212+
EOF
213+
test_cmp expected.2 actual.2 &&
214+
215+
hash=12f00e90b6ef79117ce6e650416b8cf517099b78 &&
216+
git status --porcelain=v2 | grep -v "^?" >actual.3 &&
217+
cat >expected.3 <<-EOF &&
218+
2 R. N... 100644 100644 100644 $hash $hash R100 second first
219+
2 .R N... 100644 100644 100644 $hash $hash R100 third second
220+
EOF
221+
test_cmp expected.3 actual.3
222+
)
223+
'
224+
153225
test_done
154226

wt-status.c

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,6 @@ static void wt_longstatus_print_change_data(struct wt_status *s,
360360
switch (change_type) {
361361
case WT_STATUS_UPDATED:
362362
status = d->index_status;
363-
if (d->head_path)
364-
one_name = d->head_path;
365363
break;
366364
case WT_STATUS_CHANGED:
367365
if (d->new_submodule_commits || d->dirty_submodule) {
@@ -382,6 +380,14 @@ static void wt_longstatus_print_change_data(struct wt_status *s,
382380
change_type);
383381
}
384382

383+
/*
384+
* Only pick up the rename it's relevant. If the rename is for
385+
* the changed section and we're printing the updated section,
386+
* ignore it.
387+
*/
388+
if (d->rename_status == status)
389+
one_name = d->rename_source;
390+
385391
one = quote_path(one_name, s->prefix, &onebuf);
386392
two = quote_path(two_name, s->prefix, &twobuf);
387393

@@ -391,7 +397,7 @@ static void wt_longstatus_print_change_data(struct wt_status *s,
391397
die("BUG: unhandled diff status %c", status);
392398
len = label_width - utf8_strwidth(what);
393399
assert(len >= 0);
394-
if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
400+
if (one_name != two_name)
395401
status_printf_more(s, c, "%s%.*s%s -> %s",
396402
what, len, padding, one, two);
397403
else
@@ -406,7 +412,8 @@ static void wt_longstatus_print_change_data(struct wt_status *s,
406412
strbuf_release(&twobuf);
407413
}
408414

409-
static char short_submodule_status(struct wt_status_change_data *d) {
415+
static char short_submodule_status(struct wt_status_change_data *d)
416+
{
410417
if (d->new_submodule_commits)
411418
return 'M';
412419
if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
@@ -432,7 +439,7 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
432439
struct wt_status_change_data *d;
433440

434441
p = q->queue[i];
435-
it = string_list_insert(&s->change, p->one->path);
442+
it = string_list_insert(&s->change, p->two->path);
436443
d = it->util;
437444
if (!d) {
438445
d = xcalloc(1, sizeof(*d));
@@ -459,6 +466,14 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
459466
/* mode_worktree is zero for a delete. */
460467
break;
461468

469+
case DIFF_STATUS_COPIED:
470+
case DIFF_STATUS_RENAMED:
471+
if (d->rename_status)
472+
die("BUG: multiple renames on the same target? how?");
473+
d->rename_source = xstrdup(p->one->path);
474+
d->rename_score = p->score * 100 / MAX_SCORE;
475+
d->rename_status = p->status;
476+
/* fallthru */
462477
case DIFF_STATUS_MODIFIED:
463478
case DIFF_STATUS_TYPE_CHANGED:
464479
case DIFF_STATUS_UNMERGED:
@@ -467,8 +482,8 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
467482
oidcpy(&d->oid_index, &p->one->oid);
468483
break;
469484

470-
case DIFF_STATUS_UNKNOWN:
471-
die("BUG: worktree status unknown???");
485+
default:
486+
die("BUG: unhandled diff-files status '%c'", p->status);
472487
break;
473488
}
474489

@@ -530,8 +545,11 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
530545

531546
case DIFF_STATUS_COPIED:
532547
case DIFF_STATUS_RENAMED:
533-
d->head_path = xstrdup(p->one->path);
534-
d->score = p->score * 100 / MAX_SCORE;
548+
if (d->rename_status)
549+
die("BUG: multiple renames on the same target? how?");
550+
d->rename_source = xstrdup(p->one->path);
551+
d->rename_score = p->score * 100 / MAX_SCORE;
552+
d->rename_status = p->status;
535553
/* fallthru */
536554
case DIFF_STATUS_MODIFIED:
537555
case DIFF_STATUS_TYPE_CHANGED:
@@ -548,6 +566,10 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
548566
* values in these fields.
549567
*/
550568
break;
569+
570+
default:
571+
die("BUG: unhandled diff-index status '%c'", p->status);
572+
break;
551573
}
552574
}
553575
}
@@ -602,7 +624,7 @@ static void wt_status_collect_changes_index(struct wt_status *s)
602624
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
603625
rev.diffopt.format_callback = wt_status_collect_updated_cb;
604626
rev.diffopt.format_callback_data = s;
605-
rev.diffopt.detect_rename = 1;
627+
rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
606628
rev.diffopt.rename_limit = 200;
607629
rev.diffopt.break_opt = 0;
608630
copy_pathspec(&rev.prune_data, &s->pathspec);
@@ -962,7 +984,7 @@ static void wt_longstatus_print_verbose(struct wt_status *s)
962984
setup_revisions(0, NULL, &rev, &opt);
963985

964986
rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
965-
rev.diffopt.detect_rename = 1;
987+
rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
966988
rev.diffopt.file = s->fp;
967989
rev.diffopt.close_file = 0;
968990
/*
@@ -1719,13 +1741,14 @@ static void wt_shortstatus_status(struct string_list_item *it,
17191741
putchar(' ');
17201742
if (s->null_termination) {
17211743
fprintf(stdout, "%s%c", it->string, 0);
1722-
if (d->head_path)
1723-
fprintf(stdout, "%s%c", d->head_path, 0);
1744+
if (d->rename_source)
1745+
fprintf(stdout, "%s%c", d->rename_source, 0);
17241746
} else {
17251747
struct strbuf onebuf = STRBUF_INIT;
17261748
const char *one;
1727-
if (d->head_path) {
1728-
one = quote_path(d->head_path, s->prefix, &onebuf);
1749+
1750+
if (d->rename_source) {
1751+
one = quote_path(d->rename_source, s->prefix, &onebuf);
17291752
if (*one != '"' && strchr(one, ' ') != NULL) {
17301753
putchar('"');
17311754
strbuf_addch(&onebuf, '"');
@@ -2030,10 +2053,10 @@ static void wt_porcelain_v2_print_changed_entry(
20302053
struct wt_status *s)
20312054
{
20322055
struct wt_status_change_data *d = it->util;
2033-
struct strbuf buf_index = STRBUF_INIT;
2034-
struct strbuf buf_head = STRBUF_INIT;
2035-
const char *path_index = NULL;
2036-
const char *path_head = NULL;
2056+
struct strbuf buf = STRBUF_INIT;
2057+
struct strbuf buf_from = STRBUF_INIT;
2058+
const char *path = NULL;
2059+
const char *path_from = NULL;
20372060
char key[3];
20382061
char submodule_token[5];
20392062
char sep_char, eol_char;
@@ -2052,8 +2075,8 @@ static void wt_porcelain_v2_print_changed_entry(
20522075
*/
20532076
sep_char = '\0';
20542077
eol_char = '\0';
2055-
path_index = it->string;
2056-
path_head = d->head_path;
2078+
path = it->string;
2079+
path_from = d->rename_source;
20572080
} else {
20582081
/*
20592082
* Path(s) are C-quoted if necessary. Current path is ALWAYS first.
@@ -2063,27 +2086,27 @@ static void wt_porcelain_v2_print_changed_entry(
20632086
*/
20642087
sep_char = '\t';
20652088
eol_char = '\n';
2066-
path_index = quote_path(it->string, s->prefix, &buf_index);
2067-
if (d->head_path)
2068-
path_head = quote_path(d->head_path, s->prefix, &buf_head);
2089+
path = quote_path(it->string, s->prefix, &buf);
2090+
if (d->rename_source)
2091+
path_from = quote_path(d->rename_source, s->prefix, &buf_from);
20692092
}
20702093

2071-
if (path_head)
2094+
if (path_from)
20722095
fprintf(s->fp, "2 %s %s %06o %06o %06o %s %s %c%d %s%c%s%c",
20732096
key, submodule_token,
20742097
d->mode_head, d->mode_index, d->mode_worktree,
20752098
oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
2076-
key[0], d->score,
2077-
path_index, sep_char, path_head, eol_char);
2099+
d->rename_status, d->rename_score,
2100+
path, sep_char, path_from, eol_char);
20782101
else
20792102
fprintf(s->fp, "1 %s %s %06o %06o %06o %s %s %s%c",
20802103
key, submodule_token,
20812104
d->mode_head, d->mode_index, d->mode_worktree,
20822105
oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
2083-
path_index, eol_char);
2106+
path, eol_char);
20842107

2085-
strbuf_release(&buf_index);
2086-
strbuf_release(&buf_head);
2108+
strbuf_release(&buf);
2109+
strbuf_release(&buf_from);
20872110
}
20882111

20892112
/*

wt-status.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ struct wt_status_change_data {
4444
int worktree_status;
4545
int index_status;
4646
int stagemask;
47-
int score;
4847
int mode_head, mode_index, mode_worktree;
4948
struct object_id oid_head, oid_index;
50-
char *head_path;
49+
int rename_status;
50+
int rename_score;
51+
char *rename_source;
5152
unsigned dirty_submodule : 2;
5253
unsigned new_submodule_commits : 1;
5354
};

0 commit comments

Comments
 (0)