* [GSoC PATCH] apply: report the location of corrupt patches
[not found] <20260315231538.68586-1-jerrywang183.ref@yahoo.com>
@ 2026-03-15 23:15 ` Jialong Wang
2026-03-16 11:14 ` Karthik Nayak
` (4 more replies)
0 siblings, 5 replies; 14+ messages in thread
From: Jialong Wang @ 2026-03-15 23:15 UTC (permalink / raw)
To: git; +Cc: Jialong Wang
When parsing a corrupt patch, git apply reports only the line number.
That does not tell the user which input the line number refers to.
Include the patch input path in the error message so the reported
location is easier to use.
Add tests for both file input and standard input.
Signed-off-by: Jialong Wang <jerrywang183@yahoo.com>
---
apply.c | 3 ++-
t/t4100-apply-stat.sh | 16 +++++++++++++++-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/apply.c b/apply.c
index b6dd1066a0..904d1c3e55 100644
--- a/apply.c
+++ b/apply.c
@@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,
len = parse_fragment(state, line, size, patch, fragment);
if (len <= 0) {
free(fragment);
- return error(_("corrupt patch at line %d"), state->linenr);
+ return error(_("corrupt patch at %s:%d"),
+ state->patch_input_file, state->linenr);
}
fragment->patch = line;
fragment->size = len;
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
index a5664f3eb3..f99e439688 100755
--- a/t/t4100-apply-stat.sh
+++ b/t/t4100-apply-stat.sh
@@ -48,7 +48,21 @@ test_expect_success 'applying a hunk header which overflows fails' '
+b
EOF
test_must_fail git apply patch 2>err &&
- echo "error: corrupt patch at line 4" >expect &&
+ echo "error: corrupt patch at patch:4" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying a hunk header which overflows from stdin fails' '
+ cat >patch <<-\EOF &&
+ diff -u a/file b/file
+ --- a/file
+ +++ b/file
+ @@ -98765432109876543210 +98765432109876543210 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply <patch 2>err &&
+ echo "error: corrupt patch at <stdin>:4" >expect &&
test_cmp expect err
'
test_done
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [GSoC PATCH] apply: report the location of corrupt patches
2026-03-15 23:15 ` [GSoC PATCH] apply: report the location of corrupt patches Jialong Wang
@ 2026-03-16 11:14 ` Karthik Nayak
2026-03-16 11:34 ` Jialong Wang
` (3 subsequent siblings)
4 siblings, 0 replies; 14+ messages in thread
From: Karthik Nayak @ 2026-03-16 11:14 UTC (permalink / raw)
To: Jialong Wang, git
[-- Attachment #1: Type: text/plain, Size: 2224 bytes --]
Jialong Wang <jerrywang183@yahoo.com> writes:
> When parsing a corrupt patch, git apply reports only the line number.
> That does not tell the user which input the line number refers to.
>
> Include the patch input path in the error message so the reported
> location is easier to use.
>
Definitely a welcome change.
> Add tests for both file input and standard input.
>
> Signed-off-by: Jialong Wang <jerrywang183@yahoo.com>
> ---
> apply.c | 3 ++-
> t/t4100-apply-stat.sh | 16 +++++++++++++++-
> 2 files changed, 17 insertions(+), 2 deletions(-)
>
> diff --git a/apply.c b/apply.c
> index b6dd1066a0..904d1c3e55 100644
> --- a/apply.c
> +++ b/apply.c
> @@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,
> len = parse_fragment(state, line, size, patch, fragment);
> if (len <= 0) {
> free(fragment);
> - return error(_("corrupt patch at line %d"), state->linenr);
> + return error(_("corrupt patch at %s:%d"),
> + state->patch_input_file, state->linenr);
Okay so `prase_single_patch()` is called by `parse_chunk()` which is
called by `apply_patch()`, which unconditionally sets
`state->patch_input_file`. So this looks good.
> }
> fragment->patch = line;
> fragment->size = len;
> diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
> index a5664f3eb3..f99e439688 100755
> --- a/t/t4100-apply-stat.sh
> +++ b/t/t4100-apply-stat.sh
> @@ -48,7 +48,21 @@ test_expect_success 'applying a hunk header which overflows fails' '
> +b
> EOF
> test_must_fail git apply patch 2>err &&
> - echo "error: corrupt patch at line 4" >expect &&
> + echo "error: corrupt patch at patch:4" >expect &&
> + test_cmp expect err
> +'
> +
> +test_expect_success 'applying a hunk header which overflows from stdin fails' '
> + cat >patch <<-\EOF &&
> + diff -u a/file b/file
> + --- a/file
> + +++ b/file
> + @@ -98765432109876543210 +98765432109876543210 @@
> + -a
> + +b
> + EOF
> + test_must_fail git apply <patch 2>err &&
> + echo "error: corrupt patch at <stdin>:4" >expect &&
> test_cmp expect err
> '
> test_done
> --
> 2.51.0
Nit: It would also be nice to see that this does work with multiple
patch inputs, where one of them is corrupted.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [GSoC PATCH] apply: report the location of corrupt patches
2026-03-15 23:15 ` [GSoC PATCH] apply: report the location of corrupt patches Jialong Wang
2026-03-16 11:14 ` Karthik Nayak
@ 2026-03-16 11:34 ` Jialong Wang
2026-03-16 11:34 ` [GSoC PATCH v2] " Jialong Wang
` (2 subsequent siblings)
4 siblings, 0 replies; 14+ messages in thread
From: Jialong Wang @ 2026-03-16 11:34 UTC (permalink / raw)
To: git; +Cc: karthik.188
Thanks for the review.
I added a test to cover multiple patch inputs where one of them is
corrupted, and sent a v2.
Thanks,
Jialong
^ permalink raw reply [flat|nested] 14+ messages in thread
* [GSoC PATCH v2] apply: report the location of corrupt patches
2026-03-15 23:15 ` [GSoC PATCH] apply: report the location of corrupt patches Jialong Wang
2026-03-16 11:14 ` Karthik Nayak
2026-03-16 11:34 ` Jialong Wang
@ 2026-03-16 11:34 ` Jialong Wang
2026-03-16 18:19 ` Junio C Hamano
2026-03-16 16:21 ` [GSoC PATCH v3] " Jialong Wang
[not found] ` <xmqq8qq6y4ql.fsf@gitster.g>
4 siblings, 1 reply; 14+ messages in thread
From: Jialong Wang @ 2026-03-16 11:34 UTC (permalink / raw)
To: git; +Cc: karthik.188, Jialong Wang
When parsing a corrupt patch, git apply reports only the line number.
That does not tell the user which input the line number refers to.
Include the patch input path in the error message, and reset the line
number for each patch input so the reported location remains useful
when multiple patch files are provided.
Add tests for file input, standard input, and multiple patch inputs.
Signed-off-by: Jialong Wang <jerrywang183@yahoo.com>
---
Changes since v1:
- reset the line number for each patch input
- add a test for multiple patch inputs where one input is corrupted
apply.c | 4 +++-
t/t4100-apply-stat.sh | 38 +++++++++++++++++++++++++++++++++++++-
2 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/apply.c b/apply.c
index b6dd1066a0..b7b0a201b3 100644
--- a/apply.c
+++ b/apply.c
@@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,
len = parse_fragment(state, line, size, patch, fragment);
if (len <= 0) {
free(fragment);
- return error(_("corrupt patch at line %d"), state->linenr);
+ return error(_("corrupt patch at %s:%d"),
+ state->patch_input_file, state->linenr);
}
fragment->patch = line;
fragment->size = len;
@@ -4825,6 +4826,7 @@ static int apply_patch(struct apply_state *state,
int flush_attributes = 0;
state->patch_input_file = filename;
+ state->linenr = 1;
if (read_patch_file(&buf, fd) < 0)
return -128;
offset = 0;
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
index a5664f3eb3..b19fc9fe50 100755
--- a/t/t4100-apply-stat.sh
+++ b/t/t4100-apply-stat.sh
@@ -48,7 +48,43 @@ test_expect_success 'applying a hunk header which overflows fails' '
+b
EOF
test_must_fail git apply patch 2>err &&
- echo "error: corrupt patch at line 4" >expect &&
+ echo "error: corrupt patch at patch:4" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying a hunk header which overflows from stdin fails' '
+ cat >patch <<-\EOF &&
+ diff -u a/file b/file
+ --- a/file
+ +++ b/file
+ @@ -98765432109876543210 +98765432109876543210 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply <patch 2>err &&
+ echo "error: corrupt patch at <stdin>:4" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying multiple patches reports the corrupted input' '
+ cat >good.patch <<-\EOF &&
+ diff -u a/file b/file
+ --- a/file
+ +++ b/file
+ @@ -1 +1 @@
+ -a
+ +b
+ EOF
+ cat >bad.patch <<-\EOF &&
+ diff -u a/file b/file
+ --- a/file
+ +++ b/file
+ @@ -98765432109876543210 +98765432109876543210 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply --stat --summary good.patch bad.patch 2>err &&
+ echo "error: corrupt patch at bad.patch:4" >expect &&
test_cmp expect err
'
test_done
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [GSoC PATCH v3] apply: report the location of corrupt patches
2026-03-15 23:15 ` [GSoC PATCH] apply: report the location of corrupt patches Jialong Wang
` (2 preceding siblings ...)
2026-03-16 11:34 ` [GSoC PATCH v2] " Jialong Wang
@ 2026-03-16 16:21 ` Jialong Wang
2026-03-17 16:23 ` [PATCH v4 0/3] apply: report input file for more parse errors Jialong Wang
[not found] ` <xmqq8qq6y4ql.fsf@gitster.g>
4 siblings, 1 reply; 14+ messages in thread
From: Jialong Wang @ 2026-03-16 16:21 UTC (permalink / raw)
To: git; +Cc: jerrywang183, karthik.188
When parsing a corrupt patch, git apply reports only the line number.
That does not tell the user which input the line number refers to.
Include the patch input path in the error message so the reported
location is easier to use.
Reset the line number for each patch input so the reported location stays
correct when multiple input files are provided.
Add tests for file input, standard input, multiple patch inputs, and
existing binary-diff corrupt patch cases.
Signed-off-by: Jialong Wang <jerrywang183@yahoo.com>
---
Changes since v2:
- update t4012-diff-binary.sh for the new corrupt patch location format
apply.c | 4 +++-
t/t4012-diff-binary.sh | 4 ++--
t/t4100-apply-stat.sh | 38 +++++++++++++++++++++++++++++++++++++-
3 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/apply.c b/apply.c
index b6dd1066a0..b7b0a201b3 100644
--- a/apply.c
+++ b/apply.c
@@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,
len = parse_fragment(state, line, size, patch, fragment);
if (len <= 0) {
free(fragment);
- return error(_("corrupt patch at line %d"), state->linenr);
+ return error(_("corrupt patch at %s:%d"),
+ state->patch_input_file, state->linenr);
}
fragment->patch = line;
fragment->size = len;
@@ -4825,6 +4826,7 @@ static int apply_patch(struct apply_state *state,
int flush_attributes = 0;
state->patch_input_file = filename;
+ state->linenr = 1;
if (read_patch_file(&buf, fd) < 0)
return -128;
offset = 0;
diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh
index d1d30ac2a9..97b5ac0407 100755
--- a/t/t4012-diff-binary.sh
+++ b/t/t4012-diff-binary.sh
@@ -68,7 +68,7 @@ test_expect_success 'apply detecting corrupt patch correctly' '
sed -e "s/-CIT/xCIT/" <output >broken &&
test_must_fail git apply --stat --summary broken 2>detected &&
detected=$(cat detected) &&
- detected=$(expr "$detected" : "error.*at line \\([0-9]*\\)\$") &&
+ detected=$(expr "$detected" : "error.*broken:\\([0-9]*\\)\$") &&
detected=$(sed -ne "${detected}p" broken) &&
test "$detected" = xCIT
'
@@ -77,7 +77,7 @@ test_expect_success 'apply detecting corrupt patch correctly' '
git diff --binary | sed -e "s/-CIT/xCIT/" >broken &&
test_must_fail git apply --stat --summary broken 2>detected &&
detected=$(cat detected) &&
- detected=$(expr "$detected" : "error.*at line \\([0-9]*\\)\$") &&
+ detected=$(expr "$detected" : "error.*broken:\\([0-9]*\\)\$") &&
detected=$(sed -ne "${detected}p" broken) &&
test "$detected" = xCIT
'
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
index a5664f3eb3..b19fc9fe50 100755
--- a/t/t4100-apply-stat.sh
+++ b/t/t4100-apply-stat.sh
@@ -48,7 +48,43 @@ test_expect_success 'applying a hunk header which overflows fails' '
+b
EOF
test_must_fail git apply patch 2>err &&
- echo "error: corrupt patch at line 4" >expect &&
+ echo "error: corrupt patch at patch:4" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying a hunk header which overflows from stdin fails' '
+ cat >patch <<-\EOF &&
+ diff -u a/file b/file
+ --- a/file
+ +++ b/file
+ @@ -98765432109876543210 +98765432109876543210 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply <patch 2>err &&
+ echo "error: corrupt patch at <stdin>:4" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying multiple patches reports the corrupted input' '
+ cat >good.patch <<-\EOF &&
+ diff -u a/file b/file
+ --- a/file
+ +++ b/file
+ @@ -1 +1 @@
+ -a
+ +b
+ EOF
+ cat >bad.patch <<-\EOF &&
+ diff -u a/file b/file
+ --- a/file
+ +++ b/file
+ @@ -98765432109876543210 +98765432109876543210 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply --stat --summary good.patch bad.patch 2>err &&
+ echo "error: corrupt patch at bad.patch:4" >expect &&
test_cmp expect err
'
test_done
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [GSoC PATCH v2] apply: report the location of corrupt patches
2026-03-16 11:34 ` [GSoC PATCH v2] " Jialong Wang
@ 2026-03-16 18:19 ` Junio C Hamano
0 siblings, 0 replies; 14+ messages in thread
From: Junio C Hamano @ 2026-03-16 18:19 UTC (permalink / raw)
To: Jialong Wang; +Cc: git, karthik.188
Jialong Wang <jerrywang183@yahoo.com> writes:
> When parsing a corrupt patch, git apply reports only the line number.
> That does not tell the user which input the line number refers to.
>
> Include the patch input path in the error message, and reset the line
> number for each patch input so the reported location remains useful
> when multiple patch files are provided.
>
> Add tests for file input, standard input, and multiple patch inputs.
>
> Signed-off-by: Jialong Wang <jerrywang183@yahoo.com>
> ---
> Changes since v1:
> - reset the line number for each patch input
> - add a test for multiple patch inputs where one input is corrupted
>
> apply.c | 4 +++-
> t/t4100-apply-stat.sh | 38 +++++++++++++++++++++++++++++++++++++-
> 2 files changed, 40 insertions(+), 2 deletions(-)
>
> diff --git a/apply.c b/apply.c
> index b6dd1066a0..b7b0a201b3 100644
> --- a/apply.c
> +++ b/apply.c
> @@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,
> len = parse_fragment(state, line, size, patch, fragment);
> if (len <= 0) {
> free(fragment);
> - return error(_("corrupt patch at line %d"), state->linenr);
> + return error(_("corrupt patch at %s:%d"),
> + state->patch_input_file, state->linenr);
> }
> fragment->patch = line;
> fragment->size = len;
> @@ -4825,6 +4826,7 @@ static int apply_patch(struct apply_state *state,
> int flush_attributes = 0;
>
> state->patch_input_file = filename;
> + state->linenr = 1;
> if (read_patch_file(&buf, fd) < 0)
> return -128;
> offset = 0;
This change is expecially interesting; it shows that practically
nobody feeds more than one patch to a single invocation of the
command ("git am" certainly does not) that this has gone undetected
ever since it was written by Linus 46979f56 (git-apply: improve
error detection and messages, 2005-05-23) ;-)
And all the changes contained in this patch look correctly done.
Having said that, there are places in apply.c that still report
errors only the line number, which we may want to address with a
follow-up patch, or in an updated version of this patch.
find_header() is one, parse_git_diff_header() is another. There
might be more.
Thanks, will queue.
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [GSoC PATCH v2] apply: report the location of corrupt patches
[not found] ` <xmqq8qq6y4ql.fsf@gitster.g>
@ 2026-03-16 18:31 ` Jialong Wang
2026-03-16 20:12 ` Junio C Hamano
2026-03-16 19:58 ` [GSoC PATCH] apply: report input location in header parsing errors Jialong Wang
2026-03-16 19:58 ` Jialong Wang
2 siblings, 1 reply; 14+ messages in thread
From: Jialong Wang @ 2026-03-16 18:31 UTC (permalink / raw)
To: git; +Cc: karthik.188, jerrywang183
Thanks for the review.
I sent a v3 after CI exposed two existing tests that still expected the
old error format; v3 only updates those tests and does not change the
main logic further.
The other line-number-only error sites you pointed out, such as
find_header() and parse_git_diff_header(), make sense to address in a
follow-up patch.
Thanks,
Jialong
^ permalink raw reply [flat|nested] 14+ messages in thread
* [GSoC PATCH] apply: report input location in header parsing errors
[not found] ` <xmqq8qq6y4ql.fsf@gitster.g>
2026-03-16 18:31 ` [GSoC PATCH v2] apply: report the location of corrupt patches Jialong Wang
@ 2026-03-16 19:58 ` Jialong Wang
2026-03-16 19:58 ` Jialong Wang
2 siblings, 0 replies; 14+ messages in thread
From: Jialong Wang @ 2026-03-16 19:58 UTC (permalink / raw)
To: git; +Cc: gitster, karthik.188, jerrywang183
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>
---
apply.c | 86 ++++++++++++++++++++++++++++++++-----------
apply.h | 1 +
range-diff.c | 2 +-
t/t4100-apply-stat.sh | 38 +++++++++++++++++++
t/t4254-am-corrupt.sh | 3 +-
5 files changed, 106 insertions(+), 24 deletions(-)
diff --git a/apply.c b/apply.c
index b7b0a201b3..700809f3e6 100644
--- a/apply.c
+++ b/apply.c
@@ -42,6 +42,7 @@
struct gitdiff_data {
struct strbuf *root;
+ const char *patch_input_file;
int linenr;
int p_value;
};
@@ -900,7 +901,8 @@ static int parse_traditional_patch(struct apply_state *state,
}
}
if (!name)
- return error(_("unable to find filename in patch at line %d"), state->linenr);
+ return error(_("unable to find filename in patch at %s:%d"),
+ state->patch_input_file, state->linenr);
return 0;
}
@@ -937,20 +939,35 @@ static int gitdiff_verify_name(struct gitdiff_data *state,
if (*name) {
char *another;
- if (isnull)
+ if (isnull) {
+ if (state->patch_input_file)
+ return error(_("git apply: bad git-diff - expected /dev/null, got %s at %s:%d"),
+ *name, state->patch_input_file, state->linenr);
return error(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"),
*name, state->linenr);
+ }
another = find_name(state->root, line, NULL, state->p_value, TERM_TAB);
if (!another || strcmp(another, *name)) {
free(another);
+ if (state->patch_input_file)
+ return error((side == DIFF_NEW_NAME) ?
+ _("git apply: bad git-diff - inconsistent new filename at %s:%d") :
+ _("git apply: bad git-diff - inconsistent old filename at %s:%d"),
+ state->patch_input_file, state->linenr);
return error((side == DIFF_NEW_NAME) ?
- _("git apply: bad git-diff - inconsistent new filename on line %d") :
- _("git apply: bad git-diff - inconsistent old filename on line %d"), state->linenr);
+ _("git apply: bad git-diff - inconsistent new filename on line %d") :
+ _("git apply: bad git-diff - inconsistent old filename on line %d"),
+ state->linenr);
}
free(another);
} else {
- if (!is_dev_null(line))
- return error(_("git apply: bad git-diff - expected /dev/null on line %d"), state->linenr);
+ if (!is_dev_null(line)) {
+ if (state->patch_input_file)
+ return error(_("git apply: bad git-diff - expected /dev/null at %s:%d"),
+ state->patch_input_file, state->linenr);
+ return error(_("git apply: bad git-diff - expected /dev/null on line %d"),
+ state->linenr);
+ }
}
return 0;
@@ -974,12 +991,19 @@ static int gitdiff_newname(struct gitdiff_data *state,
DIFF_NEW_NAME);
}
-static int parse_mode_line(const char *line, int linenr, unsigned int *mode)
+static int parse_mode_line(const char *line,
+ const char *patch_input_file,
+ int linenr,
+ unsigned int *mode)
{
char *end;
*mode = strtoul(line, &end, 8);
- if (end == line || !isspace(*end))
+ if (end == line || !isspace(*end)) {
+ if (patch_input_file)
+ return error(_("invalid mode at %s:%d: %s"),
+ patch_input_file, linenr, line);
return error(_("invalid mode on line %d: %s"), linenr, line);
+ }
*mode = canon_mode(*mode);
return 0;
}
@@ -988,14 +1012,16 @@ static int gitdiff_oldmode(struct gitdiff_data *state,
const char *line,
struct patch *patch)
{
- return parse_mode_line(line, state->linenr, &patch->old_mode);
+ return parse_mode_line(line, state->patch_input_file, state->linenr,
+ &patch->old_mode);
}
static int gitdiff_newmode(struct gitdiff_data *state,
const char *line,
struct patch *patch)
{
- return parse_mode_line(line, state->linenr, &patch->new_mode);
+ return parse_mode_line(line, state->patch_input_file, state->linenr,
+ &patch->new_mode);
}
static int gitdiff_delete(struct gitdiff_data *state,
@@ -1314,6 +1340,7 @@ static int check_header_line(int linenr, struct patch *patch)
}
int parse_git_diff_header(struct strbuf *root,
+ const char *patch_input_file,
int *linenr,
int p_value,
const char *line,
@@ -1345,6 +1372,7 @@ int parse_git_diff_header(struct strbuf *root,
size -= len;
(*linenr)++;
parse_hdr_state.root = root;
+ parse_hdr_state.patch_input_file = patch_input_file;
parse_hdr_state.linenr = *linenr;
parse_hdr_state.p_value = p_value;
@@ -1382,6 +1410,7 @@ int parse_git_diff_header(struct strbuf *root,
int res;
if (len < oplen || memcmp(p->str, line, oplen))
continue;
+ parse_hdr_state.linenr = *linenr;
res = p->fn(&parse_hdr_state, line + oplen, patch);
if (res < 0)
return -1;
@@ -1396,12 +1425,20 @@ int parse_git_diff_header(struct strbuf *root,
done:
if (!patch->old_name && !patch->new_name) {
if (!patch->def_name) {
- error(Q_("git diff header lacks filename information when removing "
- "%d leading pathname component (line %d)",
- "git diff header lacks filename information when removing "
- "%d leading pathname components (line %d)",
- parse_hdr_state.p_value),
- parse_hdr_state.p_value, *linenr);
+ if (patch_input_file)
+ error(Q_("git diff header lacks filename information when removing "
+ "%d leading pathname component at %s:%d",
+ "git diff header lacks filename information when removing "
+ "%d leading pathname components at %s:%d",
+ parse_hdr_state.p_value),
+ parse_hdr_state.p_value, patch_input_file, *linenr);
+ else
+ error(Q_("git diff header lacks filename information when removing "
+ "%d leading pathname component (line %d)",
+ "git diff header lacks filename information when removing "
+ "%d leading pathname components (line %d)",
+ parse_hdr_state.p_value),
+ parse_hdr_state.p_value, *linenr);
return -128;
}
patch->old_name = xstrdup(patch->def_name);
@@ -1409,8 +1446,12 @@ int parse_git_diff_header(struct strbuf *root,
}
if ((!patch->new_name && !patch->is_delete) ||
(!patch->old_name && !patch->is_new)) {
- error(_("git diff header lacks filename information "
- "(line %d)"), *linenr);
+ if (patch_input_file)
+ error(_("git diff header lacks filename information at %s:%d"),
+ patch_input_file, *linenr);
+ else
+ error(_("git diff header lacks filename information (line %d)"),
+ *linenr);
return -128;
}
patch->is_toplevel_relative = 1;
@@ -1577,8 +1618,9 @@ static int find_header(struct apply_state *state,
struct fragment dummy;
if (parse_fragment_header(line, len, &dummy) < 0)
continue;
- error(_("patch fragment without header at line %d: %.*s"),
- state->linenr, (int)len-1, line);
+ error(_("patch fragment without header at %s:%d: %.*s"),
+ state->patch_input_file, state->linenr,
+ (int)len-1, line);
return -128;
}
@@ -1590,7 +1632,9 @@ static int find_header(struct apply_state *state,
* or mode change, so we handle that specially
*/
if (!memcmp("diff --git ", line, 11)) {
- int git_hdr_len = parse_git_diff_header(&state->root, &state->linenr,
+ int git_hdr_len = parse_git_diff_header(&state->root,
+ state->patch_input_file,
+ &state->linenr,
state->p_value, line, len,
size, patch);
if (git_hdr_len < 0)
diff --git a/apply.h b/apply.h
index 90e887ec0e..5f2f03d3ed 100644
--- a/apply.h
+++ b/apply.h
@@ -167,6 +167,7 @@ int check_apply_state(struct apply_state *state, int force_apply);
* Returns -1 on failure, the length of the parsed header otherwise.
*/
int parse_git_diff_header(struct strbuf *root,
+ const char *patch_input_file,
int *linenr,
int p_value,
const char *line,
diff --git a/range-diff.c b/range-diff.c
index 57edff40a8..2712a9a107 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -140,7 +140,7 @@ static int read_patches(const char *range, struct string_list *list,
if (eol)
*eol = '\n';
orig_len = len;
- len = parse_git_diff_header(&root, &linenr, 0, line,
+ len = parse_git_diff_header(&root, NULL, &linenr, 0, line,
len, size, &patch);
if (len < 0) {
error(_("could not parse git header '%.*s'"),
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
index b19fc9fe50..b3d93d8ed6 100755
--- a/t/t4100-apply-stat.sh
+++ b/t/t4100-apply-stat.sh
@@ -87,4 +87,42 @@ test_expect_success 'applying multiple patches reports the corrupted input' '
echo "error: corrupt patch at bad.patch:4" >expect &&
test_cmp expect err
'
+
+test_expect_success 'applying a patch without a header reports the input' '
+ cat >fragment.patch <<-\EOF &&
+ @@ -1 +1 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply fragment.patch 2>err &&
+ echo "error: patch fragment without header at fragment.patch:1: @@ -1 +1 @@" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying a patch with a missing filename reports the input' '
+ cat >missing.patch <<-\EOF &&
+ diff --git a/f b/f
+ index 7898192..6178079 100644
+ --- a/f
+ @@ -1 +1 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply missing.patch 2>err &&
+ echo "error: git diff header lacks filename information at missing.patch:4" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying a patch with an invalid mode reports the input' '
+ cat >mode.patch <<-\EOF &&
+ diff --git a/f b/f
+ old mode 10x644
+ EOF
+ test_must_fail git apply mode.patch 2>err &&
+ cat >expect <<-\EOF &&
+ error: invalid mode at mode.patch:2: 10x644
+
+ EOF
+ test_cmp expect err
+'
test_done
diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh
index ae0a56cf5e..96ddf3c53a 100755
--- a/t/t4254-am-corrupt.sh
+++ b/t/t4254-am-corrupt.sh
@@ -65,9 +65,8 @@ test_expect_success setup '
test_expect_success 'try to apply corrupted patch' '
test_when_finished "git am --abort" &&
test_must_fail git -c advice.amWorkDir=false -c advice.mergeConflict=false am bad-patch.diff 2>actual &&
- echo "error: git diff header lacks filename information (line 4)" >expected &&
test_path_is_file f &&
- test_cmp expected actual
+ test_grep "error: git diff header lacks filename information at .*rebase-apply/patch:4" actual
'
test_expect_success "NUL in commit message's body" '
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [GSoC PATCH] apply: report input location in header parsing errors
[not found] ` <xmqq8qq6y4ql.fsf@gitster.g>
2026-03-16 18:31 ` [GSoC PATCH v2] apply: report the location of corrupt patches Jialong Wang
2026-03-16 19:58 ` [GSoC PATCH] apply: report input location in header parsing errors Jialong Wang
@ 2026-03-16 19:58 ` Jialong Wang
2 siblings, 0 replies; 14+ messages in thread
From: Jialong Wang @ 2026-03-16 19:58 UTC (permalink / raw)
To: git; +Cc: gitster, karthik.188, jerrywang183
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>
---
apply.c | 86 ++++++++++++++++++++++++++++++++-----------
apply.h | 1 +
range-diff.c | 2 +-
t/t4100-apply-stat.sh | 38 +++++++++++++++++++
t/t4254-am-corrupt.sh | 3 +-
5 files changed, 106 insertions(+), 24 deletions(-)
diff --git a/apply.c b/apply.c
index b7b0a201b3..700809f3e6 100644
--- a/apply.c
+++ b/apply.c
@@ -42,6 +42,7 @@
struct gitdiff_data {
struct strbuf *root;
+ const char *patch_input_file;
int linenr;
int p_value;
};
@@ -900,7 +901,8 @@ static int parse_traditional_patch(struct apply_state *state,
}
}
if (!name)
- return error(_("unable to find filename in patch at line %d"), state->linenr);
+ return error(_("unable to find filename in patch at %s:%d"),
+ state->patch_input_file, state->linenr);
return 0;
}
@@ -937,20 +939,35 @@ static int gitdiff_verify_name(struct gitdiff_data *state,
if (*name) {
char *another;
- if (isnull)
+ if (isnull) {
+ if (state->patch_input_file)
+ return error(_("git apply: bad git-diff - expected /dev/null, got %s at %s:%d"),
+ *name, state->patch_input_file, state->linenr);
return error(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"),
*name, state->linenr);
+ }
another = find_name(state->root, line, NULL, state->p_value, TERM_TAB);
if (!another || strcmp(another, *name)) {
free(another);
+ if (state->patch_input_file)
+ return error((side == DIFF_NEW_NAME) ?
+ _("git apply: bad git-diff - inconsistent new filename at %s:%d") :
+ _("git apply: bad git-diff - inconsistent old filename at %s:%d"),
+ state->patch_input_file, state->linenr);
return error((side == DIFF_NEW_NAME) ?
- _("git apply: bad git-diff - inconsistent new filename on line %d") :
- _("git apply: bad git-diff - inconsistent old filename on line %d"), state->linenr);
+ _("git apply: bad git-diff - inconsistent new filename on line %d") :
+ _("git apply: bad git-diff - inconsistent old filename on line %d"),
+ state->linenr);
}
free(another);
} else {
- if (!is_dev_null(line))
- return error(_("git apply: bad git-diff - expected /dev/null on line %d"), state->linenr);
+ if (!is_dev_null(line)) {
+ if (state->patch_input_file)
+ return error(_("git apply: bad git-diff - expected /dev/null at %s:%d"),
+ state->patch_input_file, state->linenr);
+ return error(_("git apply: bad git-diff - expected /dev/null on line %d"),
+ state->linenr);
+ }
}
return 0;
@@ -974,12 +991,19 @@ static int gitdiff_newname(struct gitdiff_data *state,
DIFF_NEW_NAME);
}
-static int parse_mode_line(const char *line, int linenr, unsigned int *mode)
+static int parse_mode_line(const char *line,
+ const char *patch_input_file,
+ int linenr,
+ unsigned int *mode)
{
char *end;
*mode = strtoul(line, &end, 8);
- if (end == line || !isspace(*end))
+ if (end == line || !isspace(*end)) {
+ if (patch_input_file)
+ return error(_("invalid mode at %s:%d: %s"),
+ patch_input_file, linenr, line);
return error(_("invalid mode on line %d: %s"), linenr, line);
+ }
*mode = canon_mode(*mode);
return 0;
}
@@ -988,14 +1012,16 @@ static int gitdiff_oldmode(struct gitdiff_data *state,
const char *line,
struct patch *patch)
{
- return parse_mode_line(line, state->linenr, &patch->old_mode);
+ return parse_mode_line(line, state->patch_input_file, state->linenr,
+ &patch->old_mode);
}
static int gitdiff_newmode(struct gitdiff_data *state,
const char *line,
struct patch *patch)
{
- return parse_mode_line(line, state->linenr, &patch->new_mode);
+ return parse_mode_line(line, state->patch_input_file, state->linenr,
+ &patch->new_mode);
}
static int gitdiff_delete(struct gitdiff_data *state,
@@ -1314,6 +1340,7 @@ static int check_header_line(int linenr, struct patch *patch)
}
int parse_git_diff_header(struct strbuf *root,
+ const char *patch_input_file,
int *linenr,
int p_value,
const char *line,
@@ -1345,6 +1372,7 @@ int parse_git_diff_header(struct strbuf *root,
size -= len;
(*linenr)++;
parse_hdr_state.root = root;
+ parse_hdr_state.patch_input_file = patch_input_file;
parse_hdr_state.linenr = *linenr;
parse_hdr_state.p_value = p_value;
@@ -1382,6 +1410,7 @@ int parse_git_diff_header(struct strbuf *root,
int res;
if (len < oplen || memcmp(p->str, line, oplen))
continue;
+ parse_hdr_state.linenr = *linenr;
res = p->fn(&parse_hdr_state, line + oplen, patch);
if (res < 0)
return -1;
@@ -1396,12 +1425,20 @@ int parse_git_diff_header(struct strbuf *root,
done:
if (!patch->old_name && !patch->new_name) {
if (!patch->def_name) {
- error(Q_("git diff header lacks filename information when removing "
- "%d leading pathname component (line %d)",
- "git diff header lacks filename information when removing "
- "%d leading pathname components (line %d)",
- parse_hdr_state.p_value),
- parse_hdr_state.p_value, *linenr);
+ if (patch_input_file)
+ error(Q_("git diff header lacks filename information when removing "
+ "%d leading pathname component at %s:%d",
+ "git diff header lacks filename information when removing "
+ "%d leading pathname components at %s:%d",
+ parse_hdr_state.p_value),
+ parse_hdr_state.p_value, patch_input_file, *linenr);
+ else
+ error(Q_("git diff header lacks filename information when removing "
+ "%d leading pathname component (line %d)",
+ "git diff header lacks filename information when removing "
+ "%d leading pathname components (line %d)",
+ parse_hdr_state.p_value),
+ parse_hdr_state.p_value, *linenr);
return -128;
}
patch->old_name = xstrdup(patch->def_name);
@@ -1409,8 +1446,12 @@ int parse_git_diff_header(struct strbuf *root,
}
if ((!patch->new_name && !patch->is_delete) ||
(!patch->old_name && !patch->is_new)) {
- error(_("git diff header lacks filename information "
- "(line %d)"), *linenr);
+ if (patch_input_file)
+ error(_("git diff header lacks filename information at %s:%d"),
+ patch_input_file, *linenr);
+ else
+ error(_("git diff header lacks filename information (line %d)"),
+ *linenr);
return -128;
}
patch->is_toplevel_relative = 1;
@@ -1577,8 +1618,9 @@ static int find_header(struct apply_state *state,
struct fragment dummy;
if (parse_fragment_header(line, len, &dummy) < 0)
continue;
- error(_("patch fragment without header at line %d: %.*s"),
- state->linenr, (int)len-1, line);
+ error(_("patch fragment without header at %s:%d: %.*s"),
+ state->patch_input_file, state->linenr,
+ (int)len-1, line);
return -128;
}
@@ -1590,7 +1632,9 @@ static int find_header(struct apply_state *state,
* or mode change, so we handle that specially
*/
if (!memcmp("diff --git ", line, 11)) {
- int git_hdr_len = parse_git_diff_header(&state->root, &state->linenr,
+ int git_hdr_len = parse_git_diff_header(&state->root,
+ state->patch_input_file,
+ &state->linenr,
state->p_value, line, len,
size, patch);
if (git_hdr_len < 0)
diff --git a/apply.h b/apply.h
index 90e887ec0e..5f2f03d3ed 100644
--- a/apply.h
+++ b/apply.h
@@ -167,6 +167,7 @@ int check_apply_state(struct apply_state *state, int force_apply);
* Returns -1 on failure, the length of the parsed header otherwise.
*/
int parse_git_diff_header(struct strbuf *root,
+ const char *patch_input_file,
int *linenr,
int p_value,
const char *line,
diff --git a/range-diff.c b/range-diff.c
index 57edff40a8..2712a9a107 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -140,7 +140,7 @@ static int read_patches(const char *range, struct string_list *list,
if (eol)
*eol = '\n';
orig_len = len;
- len = parse_git_diff_header(&root, &linenr, 0, line,
+ len = parse_git_diff_header(&root, NULL, &linenr, 0, line,
len, size, &patch);
if (len < 0) {
error(_("could not parse git header '%.*s'"),
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
index b19fc9fe50..b3d93d8ed6 100755
--- a/t/t4100-apply-stat.sh
+++ b/t/t4100-apply-stat.sh
@@ -87,4 +87,42 @@ test_expect_success 'applying multiple patches reports the corrupted input' '
echo "error: corrupt patch at bad.patch:4" >expect &&
test_cmp expect err
'
+
+test_expect_success 'applying a patch without a header reports the input' '
+ cat >fragment.patch <<-\EOF &&
+ @@ -1 +1 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply fragment.patch 2>err &&
+ echo "error: patch fragment without header at fragment.patch:1: @@ -1 +1 @@" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying a patch with a missing filename reports the input' '
+ cat >missing.patch <<-\EOF &&
+ diff --git a/f b/f
+ index 7898192..6178079 100644
+ --- a/f
+ @@ -1 +1 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply missing.patch 2>err &&
+ echo "error: git diff header lacks filename information at missing.patch:4" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying a patch with an invalid mode reports the input' '
+ cat >mode.patch <<-\EOF &&
+ diff --git a/f b/f
+ old mode 10x644
+ EOF
+ test_must_fail git apply mode.patch 2>err &&
+ cat >expect <<-\EOF &&
+ error: invalid mode at mode.patch:2: 10x644
+
+ EOF
+ test_cmp expect err
+'
test_done
diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh
index ae0a56cf5e..96ddf3c53a 100755
--- a/t/t4254-am-corrupt.sh
+++ b/t/t4254-am-corrupt.sh
@@ -65,9 +65,8 @@ test_expect_success setup '
test_expect_success 'try to apply corrupted patch' '
test_when_finished "git am --abort" &&
test_must_fail git -c advice.amWorkDir=false -c advice.mergeConflict=false am bad-patch.diff 2>actual &&
- echo "error: git diff header lacks filename information (line 4)" >expected &&
test_path_is_file f &&
- test_cmp expected actual
+ test_grep "error: git diff header lacks filename information at .*rebase-apply/patch:4" actual
'
test_expect_success "NUL in commit message's body" '
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [GSoC PATCH v2] apply: report the location of corrupt patches
2026-03-16 18:31 ` [GSoC PATCH v2] apply: report the location of corrupt patches Jialong Wang
@ 2026-03-16 20:12 ` Junio C Hamano
0 siblings, 0 replies; 14+ messages in thread
From: Junio C Hamano @ 2026-03-16 20:12 UTC (permalink / raw)
To: Jialong Wang; +Cc: git, karthik.188
Jialong Wang <jerrywang183@yahoo.com> writes:
> Thanks for the review.
>
> I sent a v3 after CI exposed two existing tests that still expected the
> old error format; v3 only updates those tests and does not change the
> main logic further.
>
> The other line-number-only error sites you pointed out, such as
> find_header() and parse_git_diff_header(), make sense to address in a
> follow-up patch.
I do not mind marking these as #leftoverbits; v3 looked great.
Thanks.
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 0/3] apply: report input file for more parse errors
2026-03-16 16:21 ` [GSoC PATCH v3] " Jialong Wang
@ 2026-03-17 16:23 ` Jialong Wang
2026-03-17 16:23 ` [PATCH v4 1/3] apply: report the location of corrupt patches Jialong Wang
` (2 more replies)
0 siblings, 3 replies; 14+ messages in thread
From: Jialong Wang @ 2026-03-17 16:23 UTC (permalink / raw)
To: git; +Cc: gitster, karthik.188, Jialong Wang
Rerolling these changes as a single series, since the follow-up patches
are all part of the same input-location reporting topic in apply.c.
The first patch updates the existing "corrupt patch at line N"
diagnostic to include the input file name.
The second patch extends the same treatment to header parsing related
errors, and the third patch does the same for binary patch and garbage
patch errors.
Jialong Wang (3):
apply: report the location of corrupt patches
apply: report input location in header parsing errors
apply: report input location in binary and garbage patch errors
apply.c | 100 +++++++++++++++++++++++++++++-----------
apply.h | 1 +
range-diff.c | 2 +-
t/t4012-diff-binary.sh | 4 +-
t/t4100-apply-stat.sh | 88 ++++++++++++++++++++++++++++++++++-
t/t4103-apply-binary.sh | 20 +++++++-
t/t4254-am-corrupt.sh | 3 +-
7 files changed, 185 insertions(+), 33 deletions(-)
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 1/3] apply: report the location of corrupt patches
2026-03-17 16:23 ` [PATCH v4 0/3] apply: report input file for more parse errors Jialong Wang
@ 2026-03-17 16:23 ` Jialong Wang
2026-03-17 16:23 ` [PATCH v4 2/3] apply: report input location in header parsing errors Jialong Wang
2026-03-17 16:23 ` [PATCH v4 3/3] apply: report input location in binary and garbage patch errors Jialong Wang
2 siblings, 0 replies; 14+ messages in thread
From: Jialong Wang @ 2026-03-17 16:23 UTC (permalink / raw)
To: git; +Cc: gitster, karthik.188, Jialong Wang
When parsing a corrupt patch, git apply reports only the line number.
That does not tell the user which input the line number refers to.
Include the patch input path in the error message so the reported
location is easier to use.
Reset the line number for each patch input so the reported location stays
correct when multiple input files are provided.
Add tests for file input, standard input, multiple patch inputs, and
existing binary-diff corrupt patch cases.
Signed-off-by: Jialong Wang <jerrywang183@yahoo.com>
---
apply.c | 4 +++-
t/t4012-diff-binary.sh | 4 ++--
t/t4100-apply-stat.sh | 38 +++++++++++++++++++++++++++++++++++++-
3 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/apply.c b/apply.c
index b6dd1066a0..b7b0a201b3 100644
--- a/apply.c
+++ b/apply.c
@@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,
len = parse_fragment(state, line, size, patch, fragment);
if (len <= 0) {
free(fragment);
- return error(_("corrupt patch at line %d"), state->linenr);
+ return error(_("corrupt patch at %s:%d"),
+ state->patch_input_file, state->linenr);
}
fragment->patch = line;
fragment->size = len;
@@ -4825,6 +4826,7 @@ static int apply_patch(struct apply_state *state,
int flush_attributes = 0;
state->patch_input_file = filename;
+ state->linenr = 1;
if (read_patch_file(&buf, fd) < 0)
return -128;
offset = 0;
diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh
index d1d30ac2a9..97b5ac0407 100755
--- a/t/t4012-diff-binary.sh
+++ b/t/t4012-diff-binary.sh
@@ -68,7 +68,7 @@ test_expect_success 'apply detecting corrupt patch correctly' '
sed -e "s/-CIT/xCIT/" <output >broken &&
test_must_fail git apply --stat --summary broken 2>detected &&
detected=$(cat detected) &&
- detected=$(expr "$detected" : "error.*at line \\([0-9]*\\)\$") &&
+ detected=$(expr "$detected" : "error.*broken:\\([0-9]*\\)\$") &&
detected=$(sed -ne "${detected}p" broken) &&
test "$detected" = xCIT
'
@@ -77,7 +77,7 @@ test_expect_success 'apply detecting corrupt patch correctly' '
git diff --binary | sed -e "s/-CIT/xCIT/" >broken &&
test_must_fail git apply --stat --summary broken 2>detected &&
detected=$(cat detected) &&
- detected=$(expr "$detected" : "error.*at line \\([0-9]*\\)\$") &&
+ detected=$(expr "$detected" : "error.*broken:\\([0-9]*\\)\$") &&
detected=$(sed -ne "${detected}p" broken) &&
test "$detected" = xCIT
'
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
index a5664f3eb3..b19fc9fe50 100755
--- a/t/t4100-apply-stat.sh
+++ b/t/t4100-apply-stat.sh
@@ -48,7 +48,43 @@ test_expect_success 'applying a hunk header which overflows fails' '
+b
EOF
test_must_fail git apply patch 2>err &&
- echo "error: corrupt patch at line 4" >expect &&
+ echo "error: corrupt patch at patch:4" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying a hunk header which overflows from stdin fails' '
+ cat >patch <<-\EOF &&
+ diff -u a/file b/file
+ --- a/file
+ +++ b/file
+ @@ -98765432109876543210 +98765432109876543210 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply <patch 2>err &&
+ echo "error: corrupt patch at <stdin>:4" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying multiple patches reports the corrupted input' '
+ cat >good.patch <<-\EOF &&
+ diff -u a/file b/file
+ --- a/file
+ +++ b/file
+ @@ -1 +1 @@
+ -a
+ +b
+ EOF
+ cat >bad.patch <<-\EOF &&
+ diff -u a/file b/file
+ --- a/file
+ +++ b/file
+ @@ -98765432109876543210 +98765432109876543210 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply --stat --summary good.patch bad.patch 2>err &&
+ echo "error: corrupt patch at bad.patch:4" >expect &&
test_cmp expect err
'
test_done
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v4 2/3] apply: report input location in header parsing errors
2026-03-17 16:23 ` [PATCH v4 0/3] apply: report input file for more parse errors Jialong Wang
2026-03-17 16:23 ` [PATCH v4 1/3] apply: report the location of corrupt patches Jialong Wang
@ 2026-03-17 16:23 ` Jialong Wang
2026-03-17 16:23 ` [PATCH v4 3/3] apply: report input location in binary and garbage patch errors Jialong Wang
2 siblings, 0 replies; 14+ messages in thread
From: Jialong Wang @ 2026-03-17 16:23 UTC (permalink / raw)
To: git; +Cc: gitster, karthik.188, Jialong Wang
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>
---
apply.c | 86 ++++++++++++++++++++++++++++++++-----------
apply.h | 1 +
range-diff.c | 2 +-
t/t4100-apply-stat.sh | 38 +++++++++++++++++++
t/t4254-am-corrupt.sh | 3 +-
5 files changed, 106 insertions(+), 24 deletions(-)
diff --git a/apply.c b/apply.c
index b7b0a201b3..700809f3e6 100644
--- a/apply.c
+++ b/apply.c
@@ -42,6 +42,7 @@
struct gitdiff_data {
struct strbuf *root;
+ const char *patch_input_file;
int linenr;
int p_value;
};
@@ -900,7 +901,8 @@ static int parse_traditional_patch(struct apply_state *state,
}
}
if (!name)
- return error(_("unable to find filename in patch at line %d"), state->linenr);
+ return error(_("unable to find filename in patch at %s:%d"),
+ state->patch_input_file, state->linenr);
return 0;
}
@@ -937,20 +939,35 @@ static int gitdiff_verify_name(struct gitdiff_data *state,
if (*name) {
char *another;
- if (isnull)
+ if (isnull) {
+ if (state->patch_input_file)
+ return error(_("git apply: bad git-diff - expected /dev/null, got %s at %s:%d"),
+ *name, state->patch_input_file, state->linenr);
return error(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"),
*name, state->linenr);
+ }
another = find_name(state->root, line, NULL, state->p_value, TERM_TAB);
if (!another || strcmp(another, *name)) {
free(another);
+ if (state->patch_input_file)
+ return error((side == DIFF_NEW_NAME) ?
+ _("git apply: bad git-diff - inconsistent new filename at %s:%d") :
+ _("git apply: bad git-diff - inconsistent old filename at %s:%d"),
+ state->patch_input_file, state->linenr);
return error((side == DIFF_NEW_NAME) ?
- _("git apply: bad git-diff - inconsistent new filename on line %d") :
- _("git apply: bad git-diff - inconsistent old filename on line %d"), state->linenr);
+ _("git apply: bad git-diff - inconsistent new filename on line %d") :
+ _("git apply: bad git-diff - inconsistent old filename on line %d"),
+ state->linenr);
}
free(another);
} else {
- if (!is_dev_null(line))
- return error(_("git apply: bad git-diff - expected /dev/null on line %d"), state->linenr);
+ if (!is_dev_null(line)) {
+ if (state->patch_input_file)
+ return error(_("git apply: bad git-diff - expected /dev/null at %s:%d"),
+ state->patch_input_file, state->linenr);
+ return error(_("git apply: bad git-diff - expected /dev/null on line %d"),
+ state->linenr);
+ }
}
return 0;
@@ -974,12 +991,19 @@ static int gitdiff_newname(struct gitdiff_data *state,
DIFF_NEW_NAME);
}
-static int parse_mode_line(const char *line, int linenr, unsigned int *mode)
+static int parse_mode_line(const char *line,
+ const char *patch_input_file,
+ int linenr,
+ unsigned int *mode)
{
char *end;
*mode = strtoul(line, &end, 8);
- if (end == line || !isspace(*end))
+ if (end == line || !isspace(*end)) {
+ if (patch_input_file)
+ return error(_("invalid mode at %s:%d: %s"),
+ patch_input_file, linenr, line);
return error(_("invalid mode on line %d: %s"), linenr, line);
+ }
*mode = canon_mode(*mode);
return 0;
}
@@ -988,14 +1012,16 @@ static int gitdiff_oldmode(struct gitdiff_data *state,
const char *line,
struct patch *patch)
{
- return parse_mode_line(line, state->linenr, &patch->old_mode);
+ return parse_mode_line(line, state->patch_input_file, state->linenr,
+ &patch->old_mode);
}
static int gitdiff_newmode(struct gitdiff_data *state,
const char *line,
struct patch *patch)
{
- return parse_mode_line(line, state->linenr, &patch->new_mode);
+ return parse_mode_line(line, state->patch_input_file, state->linenr,
+ &patch->new_mode);
}
static int gitdiff_delete(struct gitdiff_data *state,
@@ -1314,6 +1340,7 @@ static int check_header_line(int linenr, struct patch *patch)
}
int parse_git_diff_header(struct strbuf *root,
+ const char *patch_input_file,
int *linenr,
int p_value,
const char *line,
@@ -1345,6 +1372,7 @@ int parse_git_diff_header(struct strbuf *root,
size -= len;
(*linenr)++;
parse_hdr_state.root = root;
+ parse_hdr_state.patch_input_file = patch_input_file;
parse_hdr_state.linenr = *linenr;
parse_hdr_state.p_value = p_value;
@@ -1382,6 +1410,7 @@ int parse_git_diff_header(struct strbuf *root,
int res;
if (len < oplen || memcmp(p->str, line, oplen))
continue;
+ parse_hdr_state.linenr = *linenr;
res = p->fn(&parse_hdr_state, line + oplen, patch);
if (res < 0)
return -1;
@@ -1396,12 +1425,20 @@ int parse_git_diff_header(struct strbuf *root,
done:
if (!patch->old_name && !patch->new_name) {
if (!patch->def_name) {
- error(Q_("git diff header lacks filename information when removing "
- "%d leading pathname component (line %d)",
- "git diff header lacks filename information when removing "
- "%d leading pathname components (line %d)",
- parse_hdr_state.p_value),
- parse_hdr_state.p_value, *linenr);
+ if (patch_input_file)
+ error(Q_("git diff header lacks filename information when removing "
+ "%d leading pathname component at %s:%d",
+ "git diff header lacks filename information when removing "
+ "%d leading pathname components at %s:%d",
+ parse_hdr_state.p_value),
+ parse_hdr_state.p_value, patch_input_file, *linenr);
+ else
+ error(Q_("git diff header lacks filename information when removing "
+ "%d leading pathname component (line %d)",
+ "git diff header lacks filename information when removing "
+ "%d leading pathname components (line %d)",
+ parse_hdr_state.p_value),
+ parse_hdr_state.p_value, *linenr);
return -128;
}
patch->old_name = xstrdup(patch->def_name);
@@ -1409,8 +1446,12 @@ int parse_git_diff_header(struct strbuf *root,
}
if ((!patch->new_name && !patch->is_delete) ||
(!patch->old_name && !patch->is_new)) {
- error(_("git diff header lacks filename information "
- "(line %d)"), *linenr);
+ if (patch_input_file)
+ error(_("git diff header lacks filename information at %s:%d"),
+ patch_input_file, *linenr);
+ else
+ error(_("git diff header lacks filename information (line %d)"),
+ *linenr);
return -128;
}
patch->is_toplevel_relative = 1;
@@ -1577,8 +1618,9 @@ static int find_header(struct apply_state *state,
struct fragment dummy;
if (parse_fragment_header(line, len, &dummy) < 0)
continue;
- error(_("patch fragment without header at line %d: %.*s"),
- state->linenr, (int)len-1, line);
+ error(_("patch fragment without header at %s:%d: %.*s"),
+ state->patch_input_file, state->linenr,
+ (int)len-1, line);
return -128;
}
@@ -1590,7 +1632,9 @@ static int find_header(struct apply_state *state,
* or mode change, so we handle that specially
*/
if (!memcmp("diff --git ", line, 11)) {
- int git_hdr_len = parse_git_diff_header(&state->root, &state->linenr,
+ int git_hdr_len = parse_git_diff_header(&state->root,
+ state->patch_input_file,
+ &state->linenr,
state->p_value, line, len,
size, patch);
if (git_hdr_len < 0)
diff --git a/apply.h b/apply.h
index 90e887ec0e..5f2f03d3ed 100644
--- a/apply.h
+++ b/apply.h
@@ -167,6 +167,7 @@ int check_apply_state(struct apply_state *state, int force_apply);
* Returns -1 on failure, the length of the parsed header otherwise.
*/
int parse_git_diff_header(struct strbuf *root,
+ const char *patch_input_file,
int *linenr,
int p_value,
const char *line,
diff --git a/range-diff.c b/range-diff.c
index 57edff40a8..2712a9a107 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -140,7 +140,7 @@ static int read_patches(const char *range, struct string_list *list,
if (eol)
*eol = '\n';
orig_len = len;
- len = parse_git_diff_header(&root, &linenr, 0, line,
+ len = parse_git_diff_header(&root, NULL, &linenr, 0, line,
len, size, &patch);
if (len < 0) {
error(_("could not parse git header '%.*s'"),
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
index b19fc9fe50..b3d93d8ed6 100755
--- a/t/t4100-apply-stat.sh
+++ b/t/t4100-apply-stat.sh
@@ -87,4 +87,42 @@ test_expect_success 'applying multiple patches reports the corrupted input' '
echo "error: corrupt patch at bad.patch:4" >expect &&
test_cmp expect err
'
+
+test_expect_success 'applying a patch without a header reports the input' '
+ cat >fragment.patch <<-\EOF &&
+ @@ -1 +1 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply fragment.patch 2>err &&
+ echo "error: patch fragment without header at fragment.patch:1: @@ -1 +1 @@" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying a patch with a missing filename reports the input' '
+ cat >missing.patch <<-\EOF &&
+ diff --git a/f b/f
+ index 7898192..6178079 100644
+ --- a/f
+ @@ -1 +1 @@
+ -a
+ +b
+ EOF
+ test_must_fail git apply missing.patch 2>err &&
+ echo "error: git diff header lacks filename information at missing.patch:4" >expect &&
+ test_cmp expect err
+'
+
+test_expect_success 'applying a patch with an invalid mode reports the input' '
+ cat >mode.patch <<-\EOF &&
+ diff --git a/f b/f
+ old mode 10x644
+ EOF
+ test_must_fail git apply mode.patch 2>err &&
+ cat >expect <<-\EOF &&
+ error: invalid mode at mode.patch:2: 10x644
+
+ EOF
+ test_cmp expect err
+'
test_done
diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh
index ae0a56cf5e..96ddf3c53a 100755
--- a/t/t4254-am-corrupt.sh
+++ b/t/t4254-am-corrupt.sh
@@ -65,9 +65,8 @@ test_expect_success setup '
test_expect_success 'try to apply corrupted patch' '
test_when_finished "git am --abort" &&
test_must_fail git -c advice.amWorkDir=false -c advice.mergeConflict=false am bad-patch.diff 2>actual &&
- echo "error: git diff header lacks filename information (line 4)" >expected &&
test_path_is_file f &&
- test_cmp expected actual
+ test_grep "error: git diff header lacks filename information at .*rebase-apply/patch:4" actual
'
test_expect_success "NUL in commit message's body" '
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v4 3/3] apply: report input location in binary and garbage patch errors
2026-03-17 16:23 ` [PATCH v4 0/3] apply: report input file for more parse errors Jialong Wang
2026-03-17 16:23 ` [PATCH v4 1/3] apply: report the location of corrupt patches Jialong Wang
2026-03-17 16:23 ` [PATCH v4 2/3] apply: report input location in header parsing errors Jialong Wang
@ 2026-03-17 16:23 ` Jialong Wang
2 siblings, 0 replies; 14+ messages in thread
From: Jialong Wang @ 2026-03-17 16:23 UTC (permalink / raw)
To: git; +Cc: gitster, karthik.188, Jialong Wang
Several binary parsing paths in apply.c still report only line
numbers. When more than one patch input is fed to a single
invocation, that does not tell the user which input the line belongs
to.
Report the patch input location for corrupt and unrecognized binary
patches, as well as the "patch with only garbage" case, and update
the related tests.
Signed-off-by: Jialong Wang <jerrywang183@yahoo.com>
---
apply.c | 10 ++++++----
t/t4100-apply-stat.sh | 12 ++++++++++++
t/t4103-apply-binary.sh | 20 +++++++++++++++++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/apply.c b/apply.c
index 700809f3e6..84b4a569c5 100644
--- a/apply.c
+++ b/apply.c
@@ -2110,8 +2110,8 @@ static struct fragment *parse_binary_hunk(struct apply_state *state,
corrupt:
free(data);
*status_p = -1;
- error(_("corrupt binary patch at line %d: %.*s"),
- state->linenr-1, llen-1, buffer);
+ error(_("corrupt binary patch at %s:%d: %.*s"),
+ state->patch_input_file, state->linenr-1, llen-1, buffer);
return NULL;
}
@@ -2147,7 +2147,8 @@ static int parse_binary(struct apply_state *state,
forward = parse_binary_hunk(state, &buffer, &size, &status, &used);
if (!forward && !status)
/* there has to be one hunk (forward hunk) */
- return error(_("unrecognized binary patch at line %d"), state->linenr-1);
+ return error(_("unrecognized binary patch at %s:%d"),
+ state->patch_input_file, state->linenr-1);
if (status)
/* otherwise we already gave an error message */
return status;
@@ -2309,7 +2310,8 @@ static int parse_chunk(struct apply_state *state, char *buffer, unsigned long si
*/
if ((state->apply || state->check) &&
(!patch->is_binary && !metadata_changes(patch))) {
- error(_("patch with only garbage at line %d"), state->linenr);
+ error(_("patch with only garbage at %s:%d"),
+ state->patch_input_file, state->linenr);
return -128;
}
}
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
index b3d93d8ed6..8393076469 100755
--- a/t/t4100-apply-stat.sh
+++ b/t/t4100-apply-stat.sh
@@ -125,4 +125,16 @@ test_expect_success 'applying a patch with an invalid mode reports the input' '
EOF
test_cmp expect err
'
+
+test_expect_success 'applying a patch with only garbage reports the input' '
+ cat >garbage.patch <<-\EOF &&
+ diff --git a/f b/f
+ --- a/f
+ +++ b/f
+ this is garbage
+ EOF
+ test_must_fail git apply garbage.patch 2>err &&
+ echo "error: patch with only garbage at garbage.patch:4" >expect &&
+ test_cmp expect err
+'
test_done
diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh
index 8e302a5a57..f2d41e06bc 100755
--- a/t/t4103-apply-binary.sh
+++ b/t/t4103-apply-binary.sh
@@ -179,6 +179,24 @@ test_expect_success PERL_TEST_HELPERS 'reject truncated binary diff' '
" <patch >patch.trunc &&
do_reset &&
- test_must_fail git apply patch.trunc
+ test_must_fail git apply patch.trunc 2>err &&
+ line=$(awk "END { print NR + 1 }" patch.trunc) &&
+ grep "error: corrupt binary patch at patch.trunc:$line: " err
+'
+
+test_expect_success 'reject unrecognized binary diff' '
+ cat >patch.bad <<-\EOF &&
+ diff --git a/f b/f
+ new file mode 100644
+ index 0000000..7898192
+ GIT binary patch
+ bogus
+ EOF
+ test_must_fail git apply patch.bad 2>err &&
+ cat >expect <<-\EOF &&
+ error: unrecognized binary patch at patch.bad:4
+ error: No valid patches in input (allow with "--allow-empty")
+ EOF
+ test_cmp expect err
'
test_done
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-03-17 16:33 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260315231538.68586-1-jerrywang183.ref@yahoo.com>
2026-03-15 23:15 ` [GSoC PATCH] apply: report the location of corrupt patches Jialong Wang
2026-03-16 11:14 ` Karthik Nayak
2026-03-16 11:34 ` Jialong Wang
2026-03-16 11:34 ` [GSoC PATCH v2] " Jialong Wang
2026-03-16 18:19 ` Junio C Hamano
2026-03-16 16:21 ` [GSoC PATCH v3] " Jialong Wang
2026-03-17 16:23 ` [PATCH v4 0/3] apply: report input file for more parse errors Jialong Wang
2026-03-17 16:23 ` [PATCH v4 1/3] apply: report the location of corrupt patches Jialong Wang
2026-03-17 16:23 ` [PATCH v4 2/3] apply: report input location in header parsing errors Jialong Wang
2026-03-17 16:23 ` [PATCH v4 3/3] apply: report input location in binary and garbage patch errors Jialong Wang
[not found] ` <xmqq8qq6y4ql.fsf@gitster.g>
2026-03-16 18:31 ` [GSoC PATCH v2] apply: report the location of corrupt patches Jialong Wang
2026-03-16 20:12 ` Junio C Hamano
2026-03-16 19:58 ` [GSoC PATCH] apply: report input location in header parsing errors Jialong Wang
2026-03-16 19:58 ` Jialong Wang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox