apply: report input location in header parsing errors
Several header parsing errors in apply.c still report only line numbers. When applying more than one input, that does not tell the user which input the line belongs to. Report the patch input location for these header parsing errors, and update the related tests. While touching parse_git_diff_header(), update the helper state to use the current header line when reporting these errors. Signed-off-by: Jialong Wang <jerrywang183@yahoo.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jialong Wang committed
Mar 17, 2026 at 12:23 UTC
838c0ba5004b37009d8d2649ed1be7b77770c2f0
5 files changed
+106
-24
apply.c
+65
-21
@@ -42,6 +42,7 @@
42
43
struct gitdiff_data {
44
struct strbuf *root;
45
+ const char *patch_input_file;
46
int linenr;
47
int p_value;
48
};
@@ -900,7 +901,8 @@ static int parse_traditional_patch(struct apply_state *state,
901
}
902
}
903
if (!name)
903
- return error(_("unable to find filename in patch at line %d"), state->linenr);
904
+ return error(_("unable to find filename in patch at %s:%d"),
905
+ state->patch_input_file, state->linenr);
906
907
return 0;
908
}
@@ -937,20 +939,35 @@ static int gitdiff_verify_name(struct gitdiff_data *state,
939
940
if (*name) {
941
char *another;
940
- if (isnull)
942
+ if (isnull) {
943
+ if (state->patch_input_file)
944
+ return error(_("git apply: bad git-diff - expected /dev/null, got %s at %s:%d"),
945
+ *name, state->patch_input_file, state->linenr);
946
return error(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"),
947
*name, state->linenr);
948
+ }
949
another = find_name(state->root, line, NULL, state->p_value, TERM_TAB);
950
if (!another || strcmp(another, *name)) {
951
free(another);
952
+ if (state->patch_input_file)
953
+ return error((side == DIFF_NEW_NAME) ?
954
+ _("git apply: bad git-diff - inconsistent new filename at %s:%d") :
955
+ _("git apply: bad git-diff - inconsistent old filename at %s:%d"),
956
+ state->patch_input_file, state->linenr);
957
return error((side == DIFF_NEW_NAME) ?
947
- _("git apply: bad git-diff - inconsistent new filename on line %d") :
948
- _("git apply: bad git-diff - inconsistent old filename on line %d"), state->linenr);
958
+ _("git apply: bad git-diff - inconsistent new filename on line %d") :
959
+ _("git apply: bad git-diff - inconsistent old filename on line %d"),
960
+ state->linenr);
961
}
962
free(another);
963
} else {
952
- if (!is_dev_null(line))
953
- return error(_("git apply: bad git-diff - expected /dev/null on line %d"), state->linenr);
964
+ if (!is_dev_null(line)) {
965
+ if (state->patch_input_file)
966
+ return error(_("git apply: bad git-diff - expected /dev/null at %s:%d"),
967
+ state->patch_input_file, state->linenr);
968
+ return error(_("git apply: bad git-diff - expected /dev/null on line %d"),
969
+ state->linenr);
970
+ }
971
}
972
973
return 0;
@@ -974,12 +991,19 @@ static int gitdiff_newname(struct gitdiff_data *state,
991
DIFF_NEW_NAME);
992
}
993
977
-static int parse_mode_line(const char *line, int linenr, unsigned int *mode)
994
+static int parse_mode_line(const char *line,
995
+ const char *patch_input_file,
996
+ int linenr,
997
+ unsigned int *mode)
998
{
999
char *end;
1000
*mode = strtoul(line, &end, 8);
981
- if (end == line || !isspace(*end))
1001
+ if (end == line || !isspace(*end)) {
1002
+ if (patch_input_file)
1003
+ return error(_("invalid mode at %s:%d: %s"),
1004
+ patch_input_file, linenr, line);
1005
return error(_("invalid mode on line %d: %s"), linenr, line);
1006
+ }
1007
*mode = canon_mode(*mode);
1008
return 0;
1009
}
@@ -988,14 +1012,16 @@ static int gitdiff_oldmode(struct gitdiff_data *state,
1012
const char *line,
1013
struct patch *patch)
1014
{
991
- return parse_mode_line(line, state->linenr, &patch->old_mode);
1015
+ return parse_mode_line(line, state->patch_input_file, state->linenr,
1016
+ &patch->old_mode);
1017
}
1018
1019
static int gitdiff_newmode(struct gitdiff_data *state,
1020
const char *line,
1021
struct patch *patch)
1022
{
998
- return parse_mode_line(line, state->linenr, &patch->new_mode);
1023
+ return parse_mode_line(line, state->patch_input_file, state->linenr,
1024
+ &patch->new_mode);
1025
}
1026
1027
static int gitdiff_delete(struct gitdiff_data *state,
@@ -1314,6 +1340,7 @@ static int check_header_line(int linenr, struct patch *patch)
1340
}
1341
1342
int parse_git_diff_header(struct strbuf *root,
1343
+ const char *patch_input_file,
1344
int *linenr,
1345
int p_value,
1346
const char *line,
@@ -1345,6 +1372,7 @@ int parse_git_diff_header(struct strbuf *root,
1372
size -= len;
1373
(*linenr)++;
1374
parse_hdr_state.root = root;
1375
+ parse_hdr_state.patch_input_file = patch_input_file;
1376
parse_hdr_state.linenr = *linenr;
1377
parse_hdr_state.p_value = p_value;
1378
@@ -1382,6 +1410,7 @@ int parse_git_diff_header(struct strbuf *root,
1410
int res;
1411
if (len < oplen || memcmp(p->str, line, oplen))
1412
continue;
1413
+ parse_hdr_state.linenr = *linenr;
1414
res = p->fn(&parse_hdr_state, line + oplen, patch);
1415
if (res < 0)
1416
return -1;
@@ -1396,12 +1425,20 @@ int parse_git_diff_header(struct strbuf *root,
1425
done:
1426
if (!patch->old_name && !patch->new_name) {
1427
if (!patch->def_name) {
1399
- error(Q_("git diff header lacks filename information when removing "
1400
- "%d leading pathname component (line %d)",
1401
- "git diff header lacks filename information when removing "
1402
- "%d leading pathname components (line %d)",
1403
- parse_hdr_state.p_value),
1404
- parse_hdr_state.p_value, *linenr);
1428
+ if (patch_input_file)
1429
+ error(Q_("git diff header lacks filename information when removing "
1430
+ "%d leading pathname component at %s:%d",
1431
+ "git diff header lacks filename information when removing "
1432
+ "%d leading pathname components at %s:%d",
1433
+ parse_hdr_state.p_value),
1434
+ parse_hdr_state.p_value, patch_input_file, *linenr);
1435
+ else
1436
+ error(Q_("git diff header lacks filename information when removing "
1437
+ "%d leading pathname component (line %d)",
1438
+ "git diff header lacks filename information when removing "
1439
+ "%d leading pathname components (line %d)",
1440
+ parse_hdr_state.p_value),
1441
+ parse_hdr_state.p_value, *linenr);
1442
return -128;
1443
}
1444
patch->old_name = xstrdup(patch->def_name);
@@ -1409,8 +1446,12 @@ done:
1446
}
1447
if ((!patch->new_name && !patch->is_delete) ||
1448
(!patch->old_name && !patch->is_new)) {
1412
- error(_("git diff header lacks filename information "
1413
- "(line %d)"), *linenr);
1449
+ if (patch_input_file)
1450
+ error(_("git diff header lacks filename information at %s:%d"),
1451
+ patch_input_file, *linenr);
1452
+ else
1453
+ error(_("git diff header lacks filename information (line %d)"),
1454
+ *linenr);
1455
return -128;
1456
}
1457
patch->is_toplevel_relative = 1;
@@ -1577,8 +1618,9 @@ static int find_header(struct apply_state *state,
1618
struct fragment dummy;
1619
if (parse_fragment_header(line, len, &dummy) < 0)
1620
continue;
1580
- error(_("patch fragment without header at line %d: %.*s"),
1581
- state->linenr, (int)len-1, line);
1621
+ error(_("patch fragment without header at %s:%d: %.*s"),
1622
+ state->patch_input_file, state->linenr,
1623
+ (int)len-1, line);
1624
return -128;
1625
}
1626
@@ -1590,7 +1632,9 @@ static int find_header(struct apply_state *state,
1632
* or mode change, so we handle that specially
1633
*/
1634
if (!memcmp("diff --git ", line, 11)) {
1593
- int git_hdr_len = parse_git_diff_header(&state->root, &state->linenr,
1635
+ int git_hdr_len = parse_git_diff_header(&state->root,
1636
+ state->patch_input_file,
1637
+ &state->linenr,
1638
state->p_value, line, len,
1639
size, patch);
1640
if (git_hdr_len < 0)
apply.h
+1
@@ -167,6 +167,7 @@ int check_apply_state(struct apply_state *state, int force_apply);
167
* Returns -1 on failure, the length of the parsed header otherwise.
168
*/
169
int parse_git_diff_header(struct strbuf *root,
170
+ const char *patch_input_file,
171
int *linenr,
172
int p_value,
173
const char *line,
range-diff.c
+1
-1
@@ -140,7 +140,7 @@ static int read_patches(const char *range, struct string_list *list,
140
if (eol)
141
*eol = '\n';
142
orig_len = len;
143
- len = parse_git_diff_header(&root, &linenr, 0, line,
143
+ len = parse_git_diff_header(&root, NULL, &linenr, 0, line,
144
len, size, &patch);
145
if (len < 0) {
146
error(_("could not parse git header '%.*s'"),
t/t4100-apply-stat.sh
+38
@@ -87,4 +87,42 @@ test_expect_success 'applying multiple patches reports the corrupted input' '
87
echo "error: corrupt patch at bad.patch:4" >expect &&
88
test_cmp expect err
89
'
90
+
91
+test_expect_success 'applying a patch without a header reports the input' '
92
+ cat >fragment.patch <<-\EOF &&
93
+ @@ -1 +1 @@
94
+ -a
95
+ +b
96
+ EOF
97
+ test_must_fail git apply fragment.patch 2>err &&
98
+ echo "error: patch fragment without header at fragment.patch:1: @@ -1 +1 @@" >expect &&
99
+ test_cmp expect err
100
+'
101
+
102
+test_expect_success 'applying a patch with a missing filename reports the input' '
103
+ cat >missing.patch <<-\EOF &&
104
+ diff --git a/f b/f
105
+ index 7898192..6178079 100644
106
+ --- a/f
107
+ @@ -1 +1 @@
108
+ -a
109
+ +b
110
+ EOF
111
+ test_must_fail git apply missing.patch 2>err &&
112
+ echo "error: git diff header lacks filename information at missing.patch:4" >expect &&
113
+ test_cmp expect err
114
+'
115
+
116
+test_expect_success 'applying a patch with an invalid mode reports the input' '
117
+ cat >mode.patch <<-\EOF &&
118
+ diff --git a/f b/f
119
+ old mode 10x644
120
+ EOF
121
+ test_must_fail git apply mode.patch 2>err &&
122
+ cat >expect <<-\EOF &&
123
+ error: invalid mode at mode.patch:2: 10x644
124
+
125
+ EOF
126
+ test_cmp expect err
127
+'
128
test_done
t/t4254-am-corrupt.sh
+1
-2
@@ -65,9 +65,8 @@ test_expect_success setup '
65
test_expect_success 'try to apply corrupted patch' '
66
test_when_finished "git am --abort" &&
67
test_must_fail git -c advice.amWorkDir=false -c advice.mergeConflict=false am bad-patch.diff 2>actual &&
68
- echo "error: git diff header lacks filename information (line 4)" >expected &&
68
test_path_is_file f &&
70
- test_cmp expected actual
69
+ test_grep "error: git diff header lacks filename information at .*rebase-apply/patch:4" actual
70
'
71
72
test_expect_success "NUL in commit message's body" '