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" '