{"thread":{"id":"65258","subject":"[GSoC PATCH] apply: report the location of corrupt patches","startedAt":"2026-03-16T00:36:39Z","lastAt":"2026-03-17T16:33:48Z","messageCount":14,"participants":["Jialong Wang","Karthik Nayak","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"539054","messageId":"20260315231538.68586-1-jerrywang183@yahoo.com","threadId":"65258","inReplyTo":"20260315231538.68586-1-jerrywang183.ref@yahoo.com","subject":"[GSoC PATCH] apply: report the location of corrupt patches","fromName":"Jialong Wang","fromEmail":"jerrywang183@yahoo.com","sentAt":"2026-03-15T23:15:38Z","receivedAt":"2026-03-16T00:36:39Z","isPatch":true,"sender":{"key":"jerrywang183@yahoo.com","avatar":null},"body":"When parsing a corrupt patch, git apply reports only the line number.\nThat does not tell the user which input the line number refers to.\n\nInclude the patch input path in the error message so the reported\nlocation is easier to use.\n\nAdd tests for both file input and standard input.\n\nSigned-off-by: Jialong Wang <jerrywang183@yahoo.com>\n---\n apply.c               |  3 ++-\n t/t4100-apply-stat.sh | 16 +++++++++++++++-\n 2 files changed, 17 insertions(+), 2 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex b6dd1066a0..904d1c3e55 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,\n \t\tlen = parse_fragment(state, line, size, patch, fragment);\n \t\tif (len <= 0) {\n \t\t\tfree(fragment);\n-\t\t\treturn error(_(\"corrupt patch at line %d\"), state->linenr);\n+\t\t\treturn error(_(\"corrupt patch at %s:%d\"),\n+\t\t\t\t     state->patch_input_file, state->linenr);\n \t\t}\n \t\tfragment->patch = line;\n \t\tfragment->size = len;\ndiff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh\nindex a5664f3eb3..f99e439688 100755\n--- a/t/t4100-apply-stat.sh\n+++ b/t/t4100-apply-stat.sh\n@@ -48,7 +48,21 @@ test_expect_success 'applying a hunk header which overflows fails' '\n \t+b\n \tEOF\n \ttest_must_fail git apply patch 2>err &&\n-\techo \"error: corrupt patch at line 4\" >expect &&\n+\techo \"error: corrupt patch at patch:4\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying a hunk header which overflows from stdin fails' '\n+\tcat >patch <<-\\EOF &&\n+\tdiff -u a/file b/file\n+\t--- a/file\n+\t+++ b/file\n+\t@@ -98765432109876543210 +98765432109876543210 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply <patch 2>err &&\n+\techo \"error: corrupt patch at <stdin>:4\" >expect &&\n \ttest_cmp expect err\n '\n test_done\n-- \n2.51.0\n\n"},{"id":"539096","messageId":"CAOLa=ZTpfHUySnMgCFMnvo2JcRSv8zqFP-cLFSs+Ab5Cy2zsvg@mail.gmail.com","threadId":"65258","inReplyTo":"20260315231538.68586-1-jerrywang183@yahoo.com","subject":"Re: [GSoC PATCH] apply: report the location of corrupt patches","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-03-16T11:14:15Z","receivedAt":"2026-03-16T11:14:18Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Jialong Wang <jerrywang183@yahoo.com> writes:\n\n> When parsing a corrupt patch, git apply reports only the line number.\n> That does not tell the user which input the line number refers to.\n>\n> Include the patch input path in the error message so the reported\n> location is easier to use.\n>\n\nDefinitely a welcome change.\n\n> Add tests for both file input and standard input.\n>\n> Signed-off-by: Jialong Wang <jerrywang183@yahoo.com>\n> ---\n>  apply.c               |  3 ++-\n>  t/t4100-apply-stat.sh | 16 +++++++++++++++-\n>  2 files changed, 17 insertions(+), 2 deletions(-)\n>\n> diff --git a/apply.c b/apply.c\n> index b6dd1066a0..904d1c3e55 100644\n> --- a/apply.c\n> +++ b/apply.c\n> @@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,\n>  \t\tlen = parse_fragment(state, line, size, patch, fragment);\n>  \t\tif (len <= 0) {\n>  \t\t\tfree(fragment);\n> -\t\t\treturn error(_(\"corrupt patch at line %d\"), state->linenr);\n> +\t\t\treturn error(_(\"corrupt patch at %s:%d\"),\n> +\t\t\t\t     state->patch_input_file, state->linenr);\n\nOkay so `prase_single_patch()` is called by `parse_chunk()` which is\ncalled by `apply_patch()`, which unconditionally sets\n`state->patch_input_file`. So this looks good.\n\n>  \t\t}\n>  \t\tfragment->patch = line;\n>  \t\tfragment->size = len;\n> diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh\n> index a5664f3eb3..f99e439688 100755\n> --- a/t/t4100-apply-stat.sh\n> +++ b/t/t4100-apply-stat.sh\n> @@ -48,7 +48,21 @@ test_expect_success 'applying a hunk header which overflows fails' '\n>  \t+b\n>  \tEOF\n>  \ttest_must_fail git apply patch 2>err &&\n> -\techo \"error: corrupt patch at line 4\" >expect &&\n> +\techo \"error: corrupt patch at patch:4\" >expect &&\n> +\ttest_cmp expect err\n> +'\n> +\n> +test_expect_success 'applying a hunk header which overflows from stdin fails' '\n> +\tcat >patch <<-\\EOF &&\n> +\tdiff -u a/file b/file\n> +\t--- a/file\n> +\t+++ b/file\n> +\t@@ -98765432109876543210 +98765432109876543210 @@\n> +\t-a\n> +\t+b\n> +\tEOF\n> +\ttest_must_fail git apply <patch 2>err &&\n> +\techo \"error: corrupt patch at <stdin>:4\" >expect &&\n>  \ttest_cmp expect err\n>  '\n>  test_done\n> --\n> 2.51.0\n\nNit: It would also be nice to see that this does work with multiple\npatch inputs, where one of them is corrupted.\n"},{"id":"539099","messageId":"20260316113448.77756-1-jerrywang183@yahoo.com","threadId":"65258","inReplyTo":"20260315231538.68586-1-jerrywang183@yahoo.com","subject":"Re: [GSoC PATCH] apply: report the location of corrupt patches","fromName":"Jialong Wang","fromEmail":"jerrywang183@yahoo.com","sentAt":"2026-03-16T11:34:48Z","receivedAt":"2026-03-16T11:45:01Z","isPatch":true,"sender":{"key":"jerrywang183@yahoo.com","avatar":null},"body":"Thanks for the review.\n\nI added a test to cover multiple patch inputs where one of them is\ncorrupted, and sent a v2.\n\nThanks,\nJialong\n"},{"id":"539101","messageId":"20260316113459.77794-1-jerrywang183@yahoo.com","threadId":"65258","inReplyTo":"20260315231538.68586-1-jerrywang183@yahoo.com","subject":"[GSoC PATCH v2] apply: report the location of corrupt patches","fromName":"Jialong Wang","fromEmail":"jerrywang183@yahoo.com","sentAt":"2026-03-16T11:34:59Z","receivedAt":"2026-03-16T12:05:27Z","isPatch":true,"sender":{"key":"jerrywang183@yahoo.com","avatar":null},"body":"When parsing a corrupt patch, git apply reports only the line number.\nThat does not tell the user which input the line number refers to.\n\nInclude the patch input path in the error message, and reset the line\nnumber for each patch input so the reported location remains useful\nwhen multiple patch files are provided.\n\nAdd tests for file input, standard input, and multiple patch inputs.\n\nSigned-off-by: Jialong Wang <jerrywang183@yahoo.com>\n---\nChanges since v1:\n- reset the line number for each patch input\n- add a test for multiple patch inputs where one input is corrupted\n\n apply.c               |  4 +++-\n t/t4100-apply-stat.sh | 38 +++++++++++++++++++++++++++++++++++++-\n 2 files changed, 40 insertions(+), 2 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex b6dd1066a0..b7b0a201b3 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,\n \t\tlen = parse_fragment(state, line, size, patch, fragment);\n \t\tif (len <= 0) {\n \t\t\tfree(fragment);\n-\t\t\treturn error(_(\"corrupt patch at line %d\"), state->linenr);\n+\t\t\treturn error(_(\"corrupt patch at %s:%d\"),\n+\t\t\t\t     state->patch_input_file, state->linenr);\n \t\t}\n \t\tfragment->patch = line;\n \t\tfragment->size = len;\n@@ -4825,6 +4826,7 @@ static int apply_patch(struct apply_state *state,\n \tint flush_attributes = 0;\n \n \tstate->patch_input_file = filename;\n+\tstate->linenr = 1;\n \tif (read_patch_file(&buf, fd) < 0)\n \t\treturn -128;\n \toffset = 0;\ndiff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh\nindex a5664f3eb3..b19fc9fe50 100755\n--- a/t/t4100-apply-stat.sh\n+++ b/t/t4100-apply-stat.sh\n@@ -48,7 +48,43 @@ test_expect_success 'applying a hunk header which overflows fails' '\n \t+b\n \tEOF\n \ttest_must_fail git apply patch 2>err &&\n-\techo \"error: corrupt patch at line 4\" >expect &&\n+\techo \"error: corrupt patch at patch:4\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying a hunk header which overflows from stdin fails' '\n+\tcat >patch <<-\\EOF &&\n+\tdiff -u a/file b/file\n+\t--- a/file\n+\t+++ b/file\n+\t@@ -98765432109876543210 +98765432109876543210 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply <patch 2>err &&\n+\techo \"error: corrupt patch at <stdin>:4\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying multiple patches reports the corrupted input' '\n+\tcat >good.patch <<-\\EOF &&\n+\tdiff -u a/file b/file\n+\t--- a/file\n+\t+++ b/file\n+\t@@ -1 +1 @@\n+\t-a\n+\t+b\n+\tEOF\n+\tcat >bad.patch <<-\\EOF &&\n+\tdiff -u a/file b/file\n+\t--- a/file\n+\t+++ b/file\n+\t@@ -98765432109876543210 +98765432109876543210 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply --stat --summary good.patch bad.patch 2>err &&\n+\techo \"error: corrupt patch at bad.patch:4\" >expect &&\n \ttest_cmp expect err\n '\n test_done\n-- \n2.51.0\n"},{"id":"539137","messageId":"20260316162123.84532-1-jerrywang183@yahoo.com","threadId":"65258","inReplyTo":"20260315231538.68586-1-jerrywang183@yahoo.com","subject":"[GSoC PATCH v3] apply: report the location of corrupt patches","fromName":"Jialong Wang","fromEmail":"jerrywang183@yahoo.com","sentAt":"2026-03-16T16:21:23Z","receivedAt":"2026-03-16T17:12:02Z","isPatch":true,"sender":{"key":"jerrywang183@yahoo.com","avatar":null},"body":"When parsing a corrupt patch, git apply reports only the line number.\nThat does not tell the user which input the line number refers to.\n\nInclude the patch input path in the error message so the reported\nlocation is easier to use.\n\nReset the line number for each patch input so the reported location stays\ncorrect when multiple input files are provided.\n\nAdd tests for file input, standard input, multiple patch inputs, and\nexisting binary-diff corrupt patch cases.\n\nSigned-off-by: Jialong Wang <jerrywang183@yahoo.com>\n---\nChanges since v2:\n- update t4012-diff-binary.sh for the new corrupt patch location format\n\n apply.c                |  4 +++-\n t/t4012-diff-binary.sh |  4 ++--\n t/t4100-apply-stat.sh  | 38 +++++++++++++++++++++++++++++++++++++-\n 3 files changed, 42 insertions(+), 4 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex b6dd1066a0..b7b0a201b3 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,\n \t\tlen = parse_fragment(state, line, size, patch, fragment);\n \t\tif (len <= 0) {\n \t\t\tfree(fragment);\n-\t\t\treturn error(_(\"corrupt patch at line %d\"), state->linenr);\n+\t\t\treturn error(_(\"corrupt patch at %s:%d\"),\n+\t\t\t\t     state->patch_input_file, state->linenr);\n \t\t}\n \t\tfragment->patch = line;\n \t\tfragment->size = len;\n@@ -4825,6 +4826,7 @@ static int apply_patch(struct apply_state *state,\n \tint flush_attributes = 0;\n \n \tstate->patch_input_file = filename;\n+\tstate->linenr = 1;\n \tif (read_patch_file(&buf, fd) < 0)\n \t\treturn -128;\n \toffset = 0;\ndiff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh\nindex d1d30ac2a9..97b5ac0407 100755\n--- a/t/t4012-diff-binary.sh\n+++ b/t/t4012-diff-binary.sh\n@@ -68,7 +68,7 @@ test_expect_success 'apply detecting corrupt patch correctly' '\n \tsed -e \"s/-CIT/xCIT/\" <output >broken &&\n \ttest_must_fail git apply --stat --summary broken 2>detected &&\n \tdetected=$(cat detected) &&\n-\tdetected=$(expr \"$detected\" : \"error.*at line \\\\([0-9]*\\\\)\\$\") &&\n+\tdetected=$(expr \"$detected\" : \"error.*broken:\\\\([0-9]*\\\\)\\$\") &&\n \tdetected=$(sed -ne \"${detected}p\" broken) &&\n \ttest \"$detected\" = xCIT\n '\n@@ -77,7 +77,7 @@ test_expect_success 'apply detecting corrupt patch correctly' '\n \tgit diff --binary | sed -e \"s/-CIT/xCIT/\" >broken &&\n \ttest_must_fail git apply --stat --summary broken 2>detected &&\n \tdetected=$(cat detected) &&\n-\tdetected=$(expr \"$detected\" : \"error.*at line \\\\([0-9]*\\\\)\\$\") &&\n+\tdetected=$(expr \"$detected\" : \"error.*broken:\\\\([0-9]*\\\\)\\$\") &&\n \tdetected=$(sed -ne \"${detected}p\" broken) &&\n \ttest \"$detected\" = xCIT\n '\ndiff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh\nindex a5664f3eb3..b19fc9fe50 100755\n--- a/t/t4100-apply-stat.sh\n+++ b/t/t4100-apply-stat.sh\n@@ -48,7 +48,43 @@ test_expect_success 'applying a hunk header which overflows fails' '\n \t+b\n \tEOF\n \ttest_must_fail git apply patch 2>err &&\n-\techo \"error: corrupt patch at line 4\" >expect &&\n+\techo \"error: corrupt patch at patch:4\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying a hunk header which overflows from stdin fails' '\n+\tcat >patch <<-\\EOF &&\n+\tdiff -u a/file b/file\n+\t--- a/file\n+\t+++ b/file\n+\t@@ -98765432109876543210 +98765432109876543210 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply <patch 2>err &&\n+\techo \"error: corrupt patch at <stdin>:4\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying multiple patches reports the corrupted input' '\n+\tcat >good.patch <<-\\EOF &&\n+\tdiff -u a/file b/file\n+\t--- a/file\n+\t+++ b/file\n+\t@@ -1 +1 @@\n+\t-a\n+\t+b\n+\tEOF\n+\tcat >bad.patch <<-\\EOF &&\n+\tdiff -u a/file b/file\n+\t--- a/file\n+\t+++ b/file\n+\t@@ -98765432109876543210 +98765432109876543210 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply --stat --summary good.patch bad.patch 2>err &&\n+\techo \"error: corrupt patch at bad.patch:4\" >expect &&\n \ttest_cmp expect err\n '\n test_done\n-- \n2.51.0\n"},{"id":"539142","messageId":"xmqqbjgnprhc.fsf@gitster.g","threadId":"65258","inReplyTo":"20260316113459.77794-1-jerrywang183@yahoo.com","subject":"Re: [GSoC PATCH v2] apply: report the location of corrupt patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-16T18:19:11Z","receivedAt":"2026-03-16T18:19:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jialong Wang <jerrywang183@yahoo.com> writes:\n\n> When parsing a corrupt patch, git apply reports only the line number.\n> That does not tell the user which input the line number refers to.\n>\n> Include the patch input path in the error message, and reset the line\n> number for each patch input so the reported location remains useful\n> when multiple patch files are provided.\n>\n> Add tests for file input, standard input, and multiple patch inputs.\n>\n> Signed-off-by: Jialong Wang <jerrywang183@yahoo.com>\n> ---\n> Changes since v1:\n> - reset the line number for each patch input\n> - add a test for multiple patch inputs where one input is corrupted\n>\n>  apply.c               |  4 +++-\n>  t/t4100-apply-stat.sh | 38 +++++++++++++++++++++++++++++++++++++-\n>  2 files changed, 40 insertions(+), 2 deletions(-)\n>\n> diff --git a/apply.c b/apply.c\n> index b6dd1066a0..b7b0a201b3 100644\n> --- a/apply.c\n> +++ b/apply.c\n> @@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,\n>  \t\tlen = parse_fragment(state, line, size, patch, fragment);\n>  \t\tif (len <= 0) {\n>  \t\t\tfree(fragment);\n> -\t\t\treturn error(_(\"corrupt patch at line %d\"), state->linenr);\n> +\t\t\treturn error(_(\"corrupt patch at %s:%d\"),\n> +\t\t\t\t     state->patch_input_file, state->linenr);\n>  \t\t}\n>  \t\tfragment->patch = line;\n>  \t\tfragment->size = len;\n> @@ -4825,6 +4826,7 @@ static int apply_patch(struct apply_state *state,\n>  \tint flush_attributes = 0;\n>  \n>  \tstate->patch_input_file = filename;\n> +\tstate->linenr = 1;\n>  \tif (read_patch_file(&buf, fd) < 0)\n>  \t\treturn -128;\n>  \toffset = 0;\n\nThis change is expecially interesting; it shows that practically\nnobody feeds more than one patch to a single invocation of the\ncommand (\"git am\" certainly does not) that this has gone undetected\never since it was written by Linus 46979f56 (git-apply: improve\nerror detection and messages, 2005-05-23) ;-)\n\nAnd all the changes contained in this patch look correctly done.\n\nHaving said that, there are places in apply.c that still report\nerrors only the line number, which we may want to address with a\nfollow-up patch, or in an updated version of this patch.\nfind_header() is one, parse_git_diff_header() is another.  There\nmight be more.\n\nThanks, will queue.\n"},{"id":"539143","messageId":"177368589341.86550.13587211475575359717.git-reply-v2-queue@yahoo.com","threadId":"65258","inReplyTo":"xmqq8qq6y4ql.fsf@gitster.g","subject":"Re: [GSoC PATCH v2] apply: report the location of corrupt patches","fromName":"Jialong Wang","fromEmail":"jerrywang183@yahoo.com","sentAt":"2026-03-16T18:31:33Z","receivedAt":"2026-03-16T18:31:36Z","isPatch":true,"sender":{"key":"jerrywang183@yahoo.com","avatar":null},"body":"Thanks for the review.\n\nI sent a v3 after CI exposed two existing tests that still expected the\nold error format; v3 only updates those tests and does not change the\nmain logic further.\n\nThe other line-number-only error sites you pointed out, such as\nfind_header() and parse_git_diff_header(), make sense to address in a\nfollow-up patch.\n\nThanks,\nJialong\n"},{"id":"539155","messageId":"20260316195838.92367-1-jerrywang183@yahoo.com","threadId":"65258","inReplyTo":"xmqq8qq6y4ql.fsf@gitster.g","subject":"[GSoC PATCH] apply: report input location in header parsing errors","fromName":"Jialong Wang","fromEmail":"jerrywang183@yahoo.com","sentAt":"2026-03-16T19:58:38Z","receivedAt":"2026-03-16T20:08:50Z","isPatch":true,"sender":{"key":"jerrywang183@yahoo.com","avatar":null},"body":"Several header parsing errors in apply.c still report only line\nnumbers. When applying more than one input, that does not tell the\nuser which input the line belongs to.\n\nReport the patch input location for these header parsing errors, and\nupdate the related tests.\n\nWhile touching parse_git_diff_header(), update the helper state to use\nthe current header line when reporting these errors.\n\nSigned-off-by: Jialong Wang <jerrywang183@yahoo.com>\n---\n apply.c               | 86 ++++++++++++++++++++++++++++++++-----------\n apply.h               |  1 +\n range-diff.c          |  2 +-\n t/t4100-apply-stat.sh | 38 +++++++++++++++++++\n t/t4254-am-corrupt.sh |  3 +-\n 5 files changed, 106 insertions(+), 24 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex b7b0a201b3..700809f3e6 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -42,6 +42,7 @@\n \n struct gitdiff_data {\n \tstruct strbuf *root;\n+\tconst char *patch_input_file;\n \tint linenr;\n \tint p_value;\n };\n@@ -900,7 +901,8 @@ static int parse_traditional_patch(struct apply_state *state,\n \t\t}\n \t}\n \tif (!name)\n-\t\treturn error(_(\"unable to find filename in patch at line %d\"), state->linenr);\n+\t\treturn error(_(\"unable to find filename in patch at %s:%d\"),\n+\t\t\t     state->patch_input_file, state->linenr);\n \n \treturn 0;\n }\n@@ -937,20 +939,35 @@ static int gitdiff_verify_name(struct gitdiff_data *state,\n \n \tif (*name) {\n \t\tchar *another;\n-\t\tif (isnull)\n+\t\tif (isnull) {\n+\t\t\tif (state->patch_input_file)\n+\t\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null, got %s at %s:%d\"),\n+\t\t\t\t\t     *name, state->patch_input_file, state->linenr);\n \t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null, got %s on line %d\"),\n \t\t\t\t     *name, state->linenr);\n+\t\t}\n \t\tanother = find_name(state->root, line, NULL, state->p_value, TERM_TAB);\n \t\tif (!another || strcmp(another, *name)) {\n \t\t\tfree(another);\n+\t\t\tif (state->patch_input_file)\n+\t\t\t\treturn error((side == DIFF_NEW_NAME) ?\n+\t\t\t\t\t     _(\"git apply: bad git-diff - inconsistent new filename at %s:%d\") :\n+\t\t\t\t\t     _(\"git apply: bad git-diff - inconsistent old filename at %s:%d\"),\n+\t\t\t\t\t     state->patch_input_file, state->linenr);\n \t\t\treturn error((side == DIFF_NEW_NAME) ?\n-\t\t\t    _(\"git apply: bad git-diff - inconsistent new filename on line %d\") :\n-\t\t\t    _(\"git apply: bad git-diff - inconsistent old filename on line %d\"), state->linenr);\n+\t\t\t\t     _(\"git apply: bad git-diff - inconsistent new filename on line %d\") :\n+\t\t\t\t     _(\"git apply: bad git-diff - inconsistent old filename on line %d\"),\n+\t\t\t\t     state->linenr);\n \t\t}\n \t\tfree(another);\n \t} else {\n-\t\tif (!is_dev_null(line))\n-\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null on line %d\"), state->linenr);\n+\t\tif (!is_dev_null(line)) {\n+\t\t\tif (state->patch_input_file)\n+\t\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null at %s:%d\"),\n+\t\t\t\t\t     state->patch_input_file, state->linenr);\n+\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null on line %d\"),\n+\t\t\t\t     state->linenr);\n+\t\t}\n \t}\n \n \treturn 0;\n@@ -974,12 +991,19 @@ static int gitdiff_newname(struct gitdiff_data *state,\n \t\t\t\t   DIFF_NEW_NAME);\n }\n \n-static int parse_mode_line(const char *line, int linenr, unsigned int *mode)\n+static int parse_mode_line(const char *line,\n+\t\t\t   const char *patch_input_file,\n+\t\t\t   int linenr,\n+\t\t\t   unsigned int *mode)\n {\n \tchar *end;\n \t*mode = strtoul(line, &end, 8);\n-\tif (end == line || !isspace(*end))\n+\tif (end == line || !isspace(*end)) {\n+\t\tif (patch_input_file)\n+\t\t\treturn error(_(\"invalid mode at %s:%d: %s\"),\n+\t\t\t\t     patch_input_file, linenr, line);\n \t\treturn error(_(\"invalid mode on line %d: %s\"), linenr, line);\n+\t}\n \t*mode = canon_mode(*mode);\n \treturn 0;\n }\n@@ -988,14 +1012,16 @@ static int gitdiff_oldmode(struct gitdiff_data *state,\n \t\t\t   const char *line,\n \t\t\t   struct patch *patch)\n {\n-\treturn parse_mode_line(line, state->linenr, &patch->old_mode);\n+\treturn parse_mode_line(line, state->patch_input_file, state->linenr,\n+\t\t\t       &patch->old_mode);\n }\n \n static int gitdiff_newmode(struct gitdiff_data *state,\n \t\t\t   const char *line,\n \t\t\t   struct patch *patch)\n {\n-\treturn parse_mode_line(line, state->linenr, &patch->new_mode);\n+\treturn parse_mode_line(line, state->patch_input_file, state->linenr,\n+\t\t\t       &patch->new_mode);\n }\n \n static int gitdiff_delete(struct gitdiff_data *state,\n@@ -1314,6 +1340,7 @@ static int check_header_line(int linenr, struct patch *patch)\n }\n \n int parse_git_diff_header(struct strbuf *root,\n+\t\t\t  const char *patch_input_file,\n \t\t\t  int *linenr,\n \t\t\t  int p_value,\n \t\t\t  const char *line,\n@@ -1345,6 +1372,7 @@ int parse_git_diff_header(struct strbuf *root,\n \tsize -= len;\n \t(*linenr)++;\n \tparse_hdr_state.root = root;\n+\tparse_hdr_state.patch_input_file = patch_input_file;\n \tparse_hdr_state.linenr = *linenr;\n \tparse_hdr_state.p_value = p_value;\n \n@@ -1382,6 +1410,7 @@ int parse_git_diff_header(struct strbuf *root,\n \t\t\tint res;\n \t\t\tif (len < oplen || memcmp(p->str, line, oplen))\n \t\t\t\tcontinue;\n+\t\t\tparse_hdr_state.linenr = *linenr;\n \t\t\tres = p->fn(&parse_hdr_state, line + oplen, patch);\n \t\t\tif (res < 0)\n \t\t\t\treturn -1;\n@@ -1396,12 +1425,20 @@ int parse_git_diff_header(struct strbuf *root,\n done:\n \tif (!patch->old_name && !patch->new_name) {\n \t\tif (!patch->def_name) {\n-\t\t\terror(Q_(\"git diff header lacks filename information when removing \"\n-\t\t\t\t \"%d leading pathname component (line %d)\",\n-\t\t\t\t \"git diff header lacks filename information when removing \"\n-\t\t\t\t \"%d leading pathname components (line %d)\",\n-\t\t\t\t parse_hdr_state.p_value),\n-\t\t\t      parse_hdr_state.p_value, *linenr);\n+\t\t\tif (patch_input_file)\n+\t\t\t\terror(Q_(\"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname component at %s:%d\",\n+\t\t\t\t\t \"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname components at %s:%d\",\n+\t\t\t\t\t parse_hdr_state.p_value),\n+\t\t\t\t      parse_hdr_state.p_value, patch_input_file, *linenr);\n+\t\t\telse\n+\t\t\t\terror(Q_(\"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname component (line %d)\",\n+\t\t\t\t\t \"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname components (line %d)\",\n+\t\t\t\t\t parse_hdr_state.p_value),\n+\t\t\t\t      parse_hdr_state.p_value, *linenr);\n \t\t\treturn -128;\n \t\t}\n \t\tpatch->old_name = xstrdup(patch->def_name);\n@@ -1409,8 +1446,12 @@ int parse_git_diff_header(struct strbuf *root,\n \t}\n \tif ((!patch->new_name && !patch->is_delete) ||\n \t    (!patch->old_name && !patch->is_new)) {\n-\t\terror(_(\"git diff header lacks filename information \"\n-\t\t\t\"(line %d)\"), *linenr);\n+\t\tif (patch_input_file)\n+\t\t\terror(_(\"git diff header lacks filename information at %s:%d\"),\n+\t\t\t      patch_input_file, *linenr);\n+\t\telse\n+\t\t\terror(_(\"git diff header lacks filename information (line %d)\"),\n+\t\t\t      *linenr);\n \t\treturn -128;\n \t}\n \tpatch->is_toplevel_relative = 1;\n@@ -1577,8 +1618,9 @@ static int find_header(struct apply_state *state,\n \t\t\tstruct fragment dummy;\n \t\t\tif (parse_fragment_header(line, len, &dummy) < 0)\n \t\t\t\tcontinue;\n-\t\t\terror(_(\"patch fragment without header at line %d: %.*s\"),\n-\t\t\t\t     state->linenr, (int)len-1, line);\n+\t\t\terror(_(\"patch fragment without header at %s:%d: %.*s\"),\n+\t\t\t      state->patch_input_file, state->linenr,\n+\t\t\t      (int)len-1, line);\n \t\t\treturn -128;\n \t\t}\n \n@@ -1590,7 +1632,9 @@ static int find_header(struct apply_state *state,\n \t\t * or mode change, so we handle that specially\n \t\t */\n \t\tif (!memcmp(\"diff --git \", line, 11)) {\n-\t\t\tint git_hdr_len = parse_git_diff_header(&state->root, &state->linenr,\n+\t\t\tint git_hdr_len = parse_git_diff_header(&state->root,\n+\t\t\t\t\t\t\t\tstate->patch_input_file,\n+\t\t\t\t\t\t\t\t&state->linenr,\n \t\t\t\t\t\t\t\tstate->p_value, line, len,\n \t\t\t\t\t\t\t\tsize, patch);\n \t\t\tif (git_hdr_len < 0)\ndiff --git a/apply.h b/apply.h\nindex 90e887ec0e..5f2f03d3ed 100644\n--- a/apply.h\n+++ b/apply.h\n@@ -167,6 +167,7 @@ int check_apply_state(struct apply_state *state, int force_apply);\n  * Returns -1 on failure, the length of the parsed header otherwise.\n  */\n int parse_git_diff_header(struct strbuf *root,\n+\t\t\t  const char *patch_input_file,\n \t\t\t  int *linenr,\n \t\t\t  int p_value,\n \t\t\t  const char *line,\ndiff --git a/range-diff.c b/range-diff.c\nindex 57edff40a8..2712a9a107 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -140,7 +140,7 @@ static int read_patches(const char *range, struct string_list *list,\n \t\t\tif (eol)\n \t\t\t\t*eol = '\\n';\n \t\t\torig_len = len;\n-\t\t\tlen = parse_git_diff_header(&root, &linenr, 0, line,\n+\t\t\tlen = parse_git_diff_header(&root, NULL, &linenr, 0, line,\n \t\t\t\t\t\t    len, size, &patch);\n \t\t\tif (len < 0) {\n \t\t\t\terror(_(\"could not parse git header '%.*s'\"),\ndiff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh\nindex b19fc9fe50..b3d93d8ed6 100755\n--- a/t/t4100-apply-stat.sh\n+++ b/t/t4100-apply-stat.sh\n@@ -87,4 +87,42 @@ test_expect_success 'applying multiple patches reports the corrupted input' '\n \techo \"error: corrupt patch at bad.patch:4\" >expect &&\n \ttest_cmp expect err\n '\n+\n+test_expect_success 'applying a patch without a header reports the input' '\n+\tcat >fragment.patch <<-\\EOF &&\n+\t@@ -1 +1 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply fragment.patch 2>err &&\n+\techo \"error: patch fragment without header at fragment.patch:1: @@ -1 +1 @@\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying a patch with a missing filename reports the input' '\n+\tcat >missing.patch <<-\\EOF &&\n+\tdiff --git a/f b/f\n+\tindex 7898192..6178079 100644\n+\t--- a/f\n+\t@@ -1 +1 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply missing.patch 2>err &&\n+\techo \"error: git diff header lacks filename information at missing.patch:4\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying a patch with an invalid mode reports the input' '\n+\tcat >mode.patch <<-\\EOF &&\n+\tdiff --git a/f b/f\n+\told mode 10x644\n+\tEOF\n+\ttest_must_fail git apply mode.patch 2>err &&\n+\tcat >expect <<-\\EOF &&\n+\terror: invalid mode at mode.patch:2: 10x644\n+\n+\tEOF\n+\ttest_cmp expect err\n+'\n test_done\ndiff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh\nindex ae0a56cf5e..96ddf3c53a 100755\n--- a/t/t4254-am-corrupt.sh\n+++ b/t/t4254-am-corrupt.sh\n@@ -65,9 +65,8 @@ test_expect_success setup '\n test_expect_success 'try to apply corrupted patch' '\n \ttest_when_finished \"git am --abort\" &&\n \ttest_must_fail git -c advice.amWorkDir=false -c advice.mergeConflict=false am bad-patch.diff 2>actual &&\n-\techo \"error: git diff header lacks filename information (line 4)\" >expected &&\n \ttest_path_is_file f &&\n-\ttest_cmp expected actual\n+\ttest_grep \"error: git diff header lacks filename information at .*rebase-apply/patch:4\" actual\n '\n \n test_expect_success \"NUL in commit message's body\" '\n-- \n2.51.0\n\n"},{"id":"539156","messageId":"20260316195847.92386-1-jerrywang183@yahoo.com","threadId":"65258","inReplyTo":"xmqq8qq6y4ql.fsf@gitster.g","subject":"[GSoC PATCH] apply: report input location in header parsing errors","fromName":"Jialong Wang","fromEmail":"jerrywang183@yahoo.com","sentAt":"2026-03-16T19:58:47Z","receivedAt":"2026-03-16T20:09:00Z","isPatch":true,"sender":{"key":"jerrywang183@yahoo.com","avatar":null},"body":"Several header parsing errors in apply.c still report only line\nnumbers. When applying more than one input, that does not tell the\nuser which input the line belongs to.\n\nReport the patch input location for these header parsing errors, and\nupdate the related tests.\n\nWhile touching parse_git_diff_header(), update the helper state to use\nthe current header line when reporting these errors.\n\nSigned-off-by: Jialong Wang <jerrywang183@yahoo.com>\n---\n apply.c               | 86 ++++++++++++++++++++++++++++++++-----------\n apply.h               |  1 +\n range-diff.c          |  2 +-\n t/t4100-apply-stat.sh | 38 +++++++++++++++++++\n t/t4254-am-corrupt.sh |  3 +-\n 5 files changed, 106 insertions(+), 24 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex b7b0a201b3..700809f3e6 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -42,6 +42,7 @@\n \n struct gitdiff_data {\n \tstruct strbuf *root;\n+\tconst char *patch_input_file;\n \tint linenr;\n \tint p_value;\n };\n@@ -900,7 +901,8 @@ static int parse_traditional_patch(struct apply_state *state,\n \t\t}\n \t}\n \tif (!name)\n-\t\treturn error(_(\"unable to find filename in patch at line %d\"), state->linenr);\n+\t\treturn error(_(\"unable to find filename in patch at %s:%d\"),\n+\t\t\t     state->patch_input_file, state->linenr);\n \n \treturn 0;\n }\n@@ -937,20 +939,35 @@ static int gitdiff_verify_name(struct gitdiff_data *state,\n \n \tif (*name) {\n \t\tchar *another;\n-\t\tif (isnull)\n+\t\tif (isnull) {\n+\t\t\tif (state->patch_input_file)\n+\t\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null, got %s at %s:%d\"),\n+\t\t\t\t\t     *name, state->patch_input_file, state->linenr);\n \t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null, got %s on line %d\"),\n \t\t\t\t     *name, state->linenr);\n+\t\t}\n \t\tanother = find_name(state->root, line, NULL, state->p_value, TERM_TAB);\n \t\tif (!another || strcmp(another, *name)) {\n \t\t\tfree(another);\n+\t\t\tif (state->patch_input_file)\n+\t\t\t\treturn error((side == DIFF_NEW_NAME) ?\n+\t\t\t\t\t     _(\"git apply: bad git-diff - inconsistent new filename at %s:%d\") :\n+\t\t\t\t\t     _(\"git apply: bad git-diff - inconsistent old filename at %s:%d\"),\n+\t\t\t\t\t     state->patch_input_file, state->linenr);\n \t\t\treturn error((side == DIFF_NEW_NAME) ?\n-\t\t\t    _(\"git apply: bad git-diff - inconsistent new filename on line %d\") :\n-\t\t\t    _(\"git apply: bad git-diff - inconsistent old filename on line %d\"), state->linenr);\n+\t\t\t\t     _(\"git apply: bad git-diff - inconsistent new filename on line %d\") :\n+\t\t\t\t     _(\"git apply: bad git-diff - inconsistent old filename on line %d\"),\n+\t\t\t\t     state->linenr);\n \t\t}\n \t\tfree(another);\n \t} else {\n-\t\tif (!is_dev_null(line))\n-\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null on line %d\"), state->linenr);\n+\t\tif (!is_dev_null(line)) {\n+\t\t\tif (state->patch_input_file)\n+\t\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null at %s:%d\"),\n+\t\t\t\t\t     state->patch_input_file, state->linenr);\n+\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null on line %d\"),\n+\t\t\t\t     state->linenr);\n+\t\t}\n \t}\n \n \treturn 0;\n@@ -974,12 +991,19 @@ static int gitdiff_newname(struct gitdiff_data *state,\n \t\t\t\t   DIFF_NEW_NAME);\n }\n \n-static int parse_mode_line(const char *line, int linenr, unsigned int *mode)\n+static int parse_mode_line(const char *line,\n+\t\t\t   const char *patch_input_file,\n+\t\t\t   int linenr,\n+\t\t\t   unsigned int *mode)\n {\n \tchar *end;\n \t*mode = strtoul(line, &end, 8);\n-\tif (end == line || !isspace(*end))\n+\tif (end == line || !isspace(*end)) {\n+\t\tif (patch_input_file)\n+\t\t\treturn error(_(\"invalid mode at %s:%d: %s\"),\n+\t\t\t\t     patch_input_file, linenr, line);\n \t\treturn error(_(\"invalid mode on line %d: %s\"), linenr, line);\n+\t}\n \t*mode = canon_mode(*mode);\n \treturn 0;\n }\n@@ -988,14 +1012,16 @@ static int gitdiff_oldmode(struct gitdiff_data *state,\n \t\t\t   const char *line,\n \t\t\t   struct patch *patch)\n {\n-\treturn parse_mode_line(line, state->linenr, &patch->old_mode);\n+\treturn parse_mode_line(line, state->patch_input_file, state->linenr,\n+\t\t\t       &patch->old_mode);\n }\n \n static int gitdiff_newmode(struct gitdiff_data *state,\n \t\t\t   const char *line,\n \t\t\t   struct patch *patch)\n {\n-\treturn parse_mode_line(line, state->linenr, &patch->new_mode);\n+\treturn parse_mode_line(line, state->patch_input_file, state->linenr,\n+\t\t\t       &patch->new_mode);\n }\n \n static int gitdiff_delete(struct gitdiff_data *state,\n@@ -1314,6 +1340,7 @@ static int check_header_line(int linenr, struct patch *patch)\n }\n \n int parse_git_diff_header(struct strbuf *root,\n+\t\t\t  const char *patch_input_file,\n \t\t\t  int *linenr,\n \t\t\t  int p_value,\n \t\t\t  const char *line,\n@@ -1345,6 +1372,7 @@ int parse_git_diff_header(struct strbuf *root,\n \tsize -= len;\n \t(*linenr)++;\n \tparse_hdr_state.root = root;\n+\tparse_hdr_state.patch_input_file = patch_input_file;\n \tparse_hdr_state.linenr = *linenr;\n \tparse_hdr_state.p_value = p_value;\n \n@@ -1382,6 +1410,7 @@ int parse_git_diff_header(struct strbuf *root,\n \t\t\tint res;\n \t\t\tif (len < oplen || memcmp(p->str, line, oplen))\n \t\t\t\tcontinue;\n+\t\t\tparse_hdr_state.linenr = *linenr;\n \t\t\tres = p->fn(&parse_hdr_state, line + oplen, patch);\n \t\t\tif (res < 0)\n \t\t\t\treturn -1;\n@@ -1396,12 +1425,20 @@ int parse_git_diff_header(struct strbuf *root,\n done:\n \tif (!patch->old_name && !patch->new_name) {\n \t\tif (!patch->def_name) {\n-\t\t\terror(Q_(\"git diff header lacks filename information when removing \"\n-\t\t\t\t \"%d leading pathname component (line %d)\",\n-\t\t\t\t \"git diff header lacks filename information when removing \"\n-\t\t\t\t \"%d leading pathname components (line %d)\",\n-\t\t\t\t parse_hdr_state.p_value),\n-\t\t\t      parse_hdr_state.p_value, *linenr);\n+\t\t\tif (patch_input_file)\n+\t\t\t\terror(Q_(\"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname component at %s:%d\",\n+\t\t\t\t\t \"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname components at %s:%d\",\n+\t\t\t\t\t parse_hdr_state.p_value),\n+\t\t\t\t      parse_hdr_state.p_value, patch_input_file, *linenr);\n+\t\t\telse\n+\t\t\t\terror(Q_(\"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname component (line %d)\",\n+\t\t\t\t\t \"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname components (line %d)\",\n+\t\t\t\t\t parse_hdr_state.p_value),\n+\t\t\t\t      parse_hdr_state.p_value, *linenr);\n \t\t\treturn -128;\n \t\t}\n \t\tpatch->old_name = xstrdup(patch->def_name);\n@@ -1409,8 +1446,12 @@ int parse_git_diff_header(struct strbuf *root,\n \t}\n \tif ((!patch->new_name && !patch->is_delete) ||\n \t    (!patch->old_name && !patch->is_new)) {\n-\t\terror(_(\"git diff header lacks filename information \"\n-\t\t\t\"(line %d)\"), *linenr);\n+\t\tif (patch_input_file)\n+\t\t\terror(_(\"git diff header lacks filename information at %s:%d\"),\n+\t\t\t      patch_input_file, *linenr);\n+\t\telse\n+\t\t\terror(_(\"git diff header lacks filename information (line %d)\"),\n+\t\t\t      *linenr);\n \t\treturn -128;\n \t}\n \tpatch->is_toplevel_relative = 1;\n@@ -1577,8 +1618,9 @@ static int find_header(struct apply_state *state,\n \t\t\tstruct fragment dummy;\n \t\t\tif (parse_fragment_header(line, len, &dummy) < 0)\n \t\t\t\tcontinue;\n-\t\t\terror(_(\"patch fragment without header at line %d: %.*s\"),\n-\t\t\t\t     state->linenr, (int)len-1, line);\n+\t\t\terror(_(\"patch fragment without header at %s:%d: %.*s\"),\n+\t\t\t      state->patch_input_file, state->linenr,\n+\t\t\t      (int)len-1, line);\n \t\t\treturn -128;\n \t\t}\n \n@@ -1590,7 +1632,9 @@ static int find_header(struct apply_state *state,\n \t\t * or mode change, so we handle that specially\n \t\t */\n \t\tif (!memcmp(\"diff --git \", line, 11)) {\n-\t\t\tint git_hdr_len = parse_git_diff_header(&state->root, &state->linenr,\n+\t\t\tint git_hdr_len = parse_git_diff_header(&state->root,\n+\t\t\t\t\t\t\t\tstate->patch_input_file,\n+\t\t\t\t\t\t\t\t&state->linenr,\n \t\t\t\t\t\t\t\tstate->p_value, line, len,\n \t\t\t\t\t\t\t\tsize, patch);\n \t\t\tif (git_hdr_len < 0)\ndiff --git a/apply.h b/apply.h\nindex 90e887ec0e..5f2f03d3ed 100644\n--- a/apply.h\n+++ b/apply.h\n@@ -167,6 +167,7 @@ int check_apply_state(struct apply_state *state, int force_apply);\n  * Returns -1 on failure, the length of the parsed header otherwise.\n  */\n int parse_git_diff_header(struct strbuf *root,\n+\t\t\t  const char *patch_input_file,\n \t\t\t  int *linenr,\n \t\t\t  int p_value,\n \t\t\t  const char *line,\ndiff --git a/range-diff.c b/range-diff.c\nindex 57edff40a8..2712a9a107 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -140,7 +140,7 @@ static int read_patches(const char *range, struct string_list *list,\n \t\t\tif (eol)\n \t\t\t\t*eol = '\\n';\n \t\t\torig_len = len;\n-\t\t\tlen = parse_git_diff_header(&root, &linenr, 0, line,\n+\t\t\tlen = parse_git_diff_header(&root, NULL, &linenr, 0, line,\n \t\t\t\t\t\t    len, size, &patch);\n \t\t\tif (len < 0) {\n \t\t\t\terror(_(\"could not parse git header '%.*s'\"),\ndiff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh\nindex b19fc9fe50..b3d93d8ed6 100755\n--- a/t/t4100-apply-stat.sh\n+++ b/t/t4100-apply-stat.sh\n@@ -87,4 +87,42 @@ test_expect_success 'applying multiple patches reports the corrupted input' '\n \techo \"error: corrupt patch at bad.patch:4\" >expect &&\n \ttest_cmp expect err\n '\n+\n+test_expect_success 'applying a patch without a header reports the input' '\n+\tcat >fragment.patch <<-\\EOF &&\n+\t@@ -1 +1 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply fragment.patch 2>err &&\n+\techo \"error: patch fragment without header at fragment.patch:1: @@ -1 +1 @@\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying a patch with a missing filename reports the input' '\n+\tcat >missing.patch <<-\\EOF &&\n+\tdiff --git a/f b/f\n+\tindex 7898192..6178079 100644\n+\t--- a/f\n+\t@@ -1 +1 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply missing.patch 2>err &&\n+\techo \"error: git diff header lacks filename information at missing.patch:4\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying a patch with an invalid mode reports the input' '\n+\tcat >mode.patch <<-\\EOF &&\n+\tdiff --git a/f b/f\n+\told mode 10x644\n+\tEOF\n+\ttest_must_fail git apply mode.patch 2>err &&\n+\tcat >expect <<-\\EOF &&\n+\terror: invalid mode at mode.patch:2: 10x644\n+\n+\tEOF\n+\ttest_cmp expect err\n+'\n test_done\ndiff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh\nindex ae0a56cf5e..96ddf3c53a 100755\n--- a/t/t4254-am-corrupt.sh\n+++ b/t/t4254-am-corrupt.sh\n@@ -65,9 +65,8 @@ test_expect_success setup '\n test_expect_success 'try to apply corrupted patch' '\n \ttest_when_finished \"git am --abort\" &&\n \ttest_must_fail git -c advice.amWorkDir=false -c advice.mergeConflict=false am bad-patch.diff 2>actual &&\n-\techo \"error: git diff header lacks filename information (line 4)\" >expected &&\n \ttest_path_is_file f &&\n-\ttest_cmp expected actual\n+\ttest_grep \"error: git diff header lacks filename information at .*rebase-apply/patch:4\" actual\n '\n \n test_expect_success \"NUL in commit message's body\" '\n-- \n2.51.0\n\n"},{"id":"539157","messageId":"xmqq8qbro7om.fsf@gitster.g","threadId":"65258","inReplyTo":"177368589341.86550.13587211475575359717.git-reply-v2-queue@yahoo.com","subject":"Re: [GSoC PATCH v2] apply: report the location of corrupt patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-16T20:12:09Z","receivedAt":"2026-03-16T20:12:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jialong Wang <jerrywang183@yahoo.com> writes:\n\n> Thanks for the review.\n>\n> I sent a v3 after CI exposed two existing tests that still expected the\n> old error format; v3 only updates those tests and does not change the\n> main logic further.\n>\n> The other line-number-only error sites you pointed out, such as\n> find_header() and parse_git_diff_header(), make sense to address in a\n> follow-up patch.\n\nI do not mind marking these as #leftoverbits; v3 looked great.\nThanks.\n"},{"id":"539234","messageId":"20260317162321.71812-1-jerrywang183@yahoo.com","threadId":"65258","inReplyTo":"20260316162123.84532-1-jerrywang183@yahoo.com","subject":"[PATCH v4 0/3] apply: report input file for more parse errors","fromName":"Jialong Wang","fromEmail":"jerrywang183@yahoo.com","sentAt":"2026-03-17T16:23:18Z","receivedAt":"2026-03-17T16:23:34Z","isPatch":true,"sender":{"key":"jerrywang183@yahoo.com","avatar":null},"body":"Rerolling these changes as a single series, since the follow-up patches\nare all part of the same input-location reporting topic in apply.c.\n\nThe first patch updates the existing \"corrupt patch at line N\"\ndiagnostic to include the input file name.\n\nThe second patch extends the same treatment to header parsing related\nerrors, and the third patch does the same for binary patch and garbage\npatch errors.\n\nJialong Wang (3):\n  apply: report the location of corrupt patches\n  apply: report input location in header parsing errors\n  apply: report input location in binary and garbage patch errors\n\n apply.c                 | 100 +++++++++++++++++++++++++++++-----------\n apply.h                 |   1 +\n range-diff.c            |   2 +-\n t/t4012-diff-binary.sh  |   4 +-\n t/t4100-apply-stat.sh   |  88 ++++++++++++++++++++++++++++++++++-\n t/t4103-apply-binary.sh |  20 +++++++-\n t/t4254-am-corrupt.sh   |   3 +-\n 7 files changed, 185 insertions(+), 33 deletions(-)\n\n-- \n2.51.0\n"},{"id":"539235","messageId":"20260317162321.71812-3-jerrywang183@yahoo.com","threadId":"65258","inReplyTo":"20260317162321.71812-1-jerrywang183@yahoo.com","subject":"[PATCH v4 2/3] apply: report input location in header parsing errors","fromName":"Jialong Wang","fromEmail":"jerrywang183@yahoo.com","sentAt":"2026-03-17T16:23:20Z","receivedAt":"2026-03-17T16:23:34Z","isPatch":true,"sender":{"key":"jerrywang183@yahoo.com","avatar":null},"body":"Several header parsing errors in apply.c still report only line\nnumbers. When applying more than one input, that does not tell the\nuser which input the line belongs to.\n\nReport the patch input location for these header parsing errors, and\nupdate the related tests.\n\nWhile touching parse_git_diff_header(), update the helper state to use\nthe current header line when reporting these errors.\n\nSigned-off-by: Jialong Wang <jerrywang183@yahoo.com>\n---\n apply.c               | 86 ++++++++++++++++++++++++++++++++-----------\n apply.h               |  1 +\n range-diff.c          |  2 +-\n t/t4100-apply-stat.sh | 38 +++++++++++++++++++\n t/t4254-am-corrupt.sh |  3 +-\n 5 files changed, 106 insertions(+), 24 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex b7b0a201b3..700809f3e6 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -42,6 +42,7 @@\n \n struct gitdiff_data {\n \tstruct strbuf *root;\n+\tconst char *patch_input_file;\n \tint linenr;\n \tint p_value;\n };\n@@ -900,7 +901,8 @@ static int parse_traditional_patch(struct apply_state *state,\n \t\t}\n \t}\n \tif (!name)\n-\t\treturn error(_(\"unable to find filename in patch at line %d\"), state->linenr);\n+\t\treturn error(_(\"unable to find filename in patch at %s:%d\"),\n+\t\t\t     state->patch_input_file, state->linenr);\n \n \treturn 0;\n }\n@@ -937,20 +939,35 @@ static int gitdiff_verify_name(struct gitdiff_data *state,\n \n \tif (*name) {\n \t\tchar *another;\n-\t\tif (isnull)\n+\t\tif (isnull) {\n+\t\t\tif (state->patch_input_file)\n+\t\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null, got %s at %s:%d\"),\n+\t\t\t\t\t     *name, state->patch_input_file, state->linenr);\n \t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null, got %s on line %d\"),\n \t\t\t\t     *name, state->linenr);\n+\t\t}\n \t\tanother = find_name(state->root, line, NULL, state->p_value, TERM_TAB);\n \t\tif (!another || strcmp(another, *name)) {\n \t\t\tfree(another);\n+\t\t\tif (state->patch_input_file)\n+\t\t\t\treturn error((side == DIFF_NEW_NAME) ?\n+\t\t\t\t\t     _(\"git apply: bad git-diff - inconsistent new filename at %s:%d\") :\n+\t\t\t\t\t     _(\"git apply: bad git-diff - inconsistent old filename at %s:%d\"),\n+\t\t\t\t\t     state->patch_input_file, state->linenr);\n \t\t\treturn error((side == DIFF_NEW_NAME) ?\n-\t\t\t    _(\"git apply: bad git-diff - inconsistent new filename on line %d\") :\n-\t\t\t    _(\"git apply: bad git-diff - inconsistent old filename on line %d\"), state->linenr);\n+\t\t\t\t     _(\"git apply: bad git-diff - inconsistent new filename on line %d\") :\n+\t\t\t\t     _(\"git apply: bad git-diff - inconsistent old filename on line %d\"),\n+\t\t\t\t     state->linenr);\n \t\t}\n \t\tfree(another);\n \t} else {\n-\t\tif (!is_dev_null(line))\n-\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null on line %d\"), state->linenr);\n+\t\tif (!is_dev_null(line)) {\n+\t\t\tif (state->patch_input_file)\n+\t\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null at %s:%d\"),\n+\t\t\t\t\t     state->patch_input_file, state->linenr);\n+\t\t\treturn error(_(\"git apply: bad git-diff - expected /dev/null on line %d\"),\n+\t\t\t\t     state->linenr);\n+\t\t}\n \t}\n \n \treturn 0;\n@@ -974,12 +991,19 @@ static int gitdiff_newname(struct gitdiff_data *state,\n \t\t\t\t   DIFF_NEW_NAME);\n }\n \n-static int parse_mode_line(const char *line, int linenr, unsigned int *mode)\n+static int parse_mode_line(const char *line,\n+\t\t\t   const char *patch_input_file,\n+\t\t\t   int linenr,\n+\t\t\t   unsigned int *mode)\n {\n \tchar *end;\n \t*mode = strtoul(line, &end, 8);\n-\tif (end == line || !isspace(*end))\n+\tif (end == line || !isspace(*end)) {\n+\t\tif (patch_input_file)\n+\t\t\treturn error(_(\"invalid mode at %s:%d: %s\"),\n+\t\t\t\t     patch_input_file, linenr, line);\n \t\treturn error(_(\"invalid mode on line %d: %s\"), linenr, line);\n+\t}\n \t*mode = canon_mode(*mode);\n \treturn 0;\n }\n@@ -988,14 +1012,16 @@ static int gitdiff_oldmode(struct gitdiff_data *state,\n \t\t\t   const char *line,\n \t\t\t   struct patch *patch)\n {\n-\treturn parse_mode_line(line, state->linenr, &patch->old_mode);\n+\treturn parse_mode_line(line, state->patch_input_file, state->linenr,\n+\t\t\t       &patch->old_mode);\n }\n \n static int gitdiff_newmode(struct gitdiff_data *state,\n \t\t\t   const char *line,\n \t\t\t   struct patch *patch)\n {\n-\treturn parse_mode_line(line, state->linenr, &patch->new_mode);\n+\treturn parse_mode_line(line, state->patch_input_file, state->linenr,\n+\t\t\t       &patch->new_mode);\n }\n \n static int gitdiff_delete(struct gitdiff_data *state,\n@@ -1314,6 +1340,7 @@ static int check_header_line(int linenr, struct patch *patch)\n }\n \n int parse_git_diff_header(struct strbuf *root,\n+\t\t\t  const char *patch_input_file,\n \t\t\t  int *linenr,\n \t\t\t  int p_value,\n \t\t\t  const char *line,\n@@ -1345,6 +1372,7 @@ int parse_git_diff_header(struct strbuf *root,\n \tsize -= len;\n \t(*linenr)++;\n \tparse_hdr_state.root = root;\n+\tparse_hdr_state.patch_input_file = patch_input_file;\n \tparse_hdr_state.linenr = *linenr;\n \tparse_hdr_state.p_value = p_value;\n \n@@ -1382,6 +1410,7 @@ int parse_git_diff_header(struct strbuf *root,\n \t\t\tint res;\n \t\t\tif (len < oplen || memcmp(p->str, line, oplen))\n \t\t\t\tcontinue;\n+\t\t\tparse_hdr_state.linenr = *linenr;\n \t\t\tres = p->fn(&parse_hdr_state, line + oplen, patch);\n \t\t\tif (res < 0)\n \t\t\t\treturn -1;\n@@ -1396,12 +1425,20 @@ int parse_git_diff_header(struct strbuf *root,\n done:\n \tif (!patch->old_name && !patch->new_name) {\n \t\tif (!patch->def_name) {\n-\t\t\terror(Q_(\"git diff header lacks filename information when removing \"\n-\t\t\t\t \"%d leading pathname component (line %d)\",\n-\t\t\t\t \"git diff header lacks filename information when removing \"\n-\t\t\t\t \"%d leading pathname components (line %d)\",\n-\t\t\t\t parse_hdr_state.p_value),\n-\t\t\t      parse_hdr_state.p_value, *linenr);\n+\t\t\tif (patch_input_file)\n+\t\t\t\terror(Q_(\"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname component at %s:%d\",\n+\t\t\t\t\t \"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname components at %s:%d\",\n+\t\t\t\t\t parse_hdr_state.p_value),\n+\t\t\t\t      parse_hdr_state.p_value, patch_input_file, *linenr);\n+\t\t\telse\n+\t\t\t\terror(Q_(\"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname component (line %d)\",\n+\t\t\t\t\t \"git diff header lacks filename information when removing \"\n+\t\t\t\t\t \"%d leading pathname components (line %d)\",\n+\t\t\t\t\t parse_hdr_state.p_value),\n+\t\t\t\t      parse_hdr_state.p_value, *linenr);\n \t\t\treturn -128;\n \t\t}\n \t\tpatch->old_name = xstrdup(patch->def_name);\n@@ -1409,8 +1446,12 @@ int parse_git_diff_header(struct strbuf *root,\n \t}\n \tif ((!patch->new_name && !patch->is_delete) ||\n \t    (!patch->old_name && !patch->is_new)) {\n-\t\terror(_(\"git diff header lacks filename information \"\n-\t\t\t\"(line %d)\"), *linenr);\n+\t\tif (patch_input_file)\n+\t\t\terror(_(\"git diff header lacks filename information at %s:%d\"),\n+\t\t\t      patch_input_file, *linenr);\n+\t\telse\n+\t\t\terror(_(\"git diff header lacks filename information (line %d)\"),\n+\t\t\t      *linenr);\n \t\treturn -128;\n \t}\n \tpatch->is_toplevel_relative = 1;\n@@ -1577,8 +1618,9 @@ static int find_header(struct apply_state *state,\n \t\t\tstruct fragment dummy;\n \t\t\tif (parse_fragment_header(line, len, &dummy) < 0)\n \t\t\t\tcontinue;\n-\t\t\terror(_(\"patch fragment without header at line %d: %.*s\"),\n-\t\t\t\t     state->linenr, (int)len-1, line);\n+\t\t\terror(_(\"patch fragment without header at %s:%d: %.*s\"),\n+\t\t\t      state->patch_input_file, state->linenr,\n+\t\t\t      (int)len-1, line);\n \t\t\treturn -128;\n \t\t}\n \n@@ -1590,7 +1632,9 @@ static int find_header(struct apply_state *state,\n \t\t * or mode change, so we handle that specially\n \t\t */\n \t\tif (!memcmp(\"diff --git \", line, 11)) {\n-\t\t\tint git_hdr_len = parse_git_diff_header(&state->root, &state->linenr,\n+\t\t\tint git_hdr_len = parse_git_diff_header(&state->root,\n+\t\t\t\t\t\t\t\tstate->patch_input_file,\n+\t\t\t\t\t\t\t\t&state->linenr,\n \t\t\t\t\t\t\t\tstate->p_value, line, len,\n \t\t\t\t\t\t\t\tsize, patch);\n \t\t\tif (git_hdr_len < 0)\ndiff --git a/apply.h b/apply.h\nindex 90e887ec0e..5f2f03d3ed 100644\n--- a/apply.h\n+++ b/apply.h\n@@ -167,6 +167,7 @@ int check_apply_state(struct apply_state *state, int force_apply);\n  * Returns -1 on failure, the length of the parsed header otherwise.\n  */\n int parse_git_diff_header(struct strbuf *root,\n+\t\t\t  const char *patch_input_file,\n \t\t\t  int *linenr,\n \t\t\t  int p_value,\n \t\t\t  const char *line,\ndiff --git a/range-diff.c b/range-diff.c\nindex 57edff40a8..2712a9a107 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -140,7 +140,7 @@ static int read_patches(const char *range, struct string_list *list,\n \t\t\tif (eol)\n \t\t\t\t*eol = '\\n';\n \t\t\torig_len = len;\n-\t\t\tlen = parse_git_diff_header(&root, &linenr, 0, line,\n+\t\t\tlen = parse_git_diff_header(&root, NULL, &linenr, 0, line,\n \t\t\t\t\t\t    len, size, &patch);\n \t\t\tif (len < 0) {\n \t\t\t\terror(_(\"could not parse git header '%.*s'\"),\ndiff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh\nindex b19fc9fe50..b3d93d8ed6 100755\n--- a/t/t4100-apply-stat.sh\n+++ b/t/t4100-apply-stat.sh\n@@ -87,4 +87,42 @@ test_expect_success 'applying multiple patches reports the corrupted input' '\n \techo \"error: corrupt patch at bad.patch:4\" >expect &&\n \ttest_cmp expect err\n '\n+\n+test_expect_success 'applying a patch without a header reports the input' '\n+\tcat >fragment.patch <<-\\EOF &&\n+\t@@ -1 +1 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply fragment.patch 2>err &&\n+\techo \"error: patch fragment without header at fragment.patch:1: @@ -1 +1 @@\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying a patch with a missing filename reports the input' '\n+\tcat >missing.patch <<-\\EOF &&\n+\tdiff --git a/f b/f\n+\tindex 7898192..6178079 100644\n+\t--- a/f\n+\t@@ -1 +1 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply missing.patch 2>err &&\n+\techo \"error: git diff header lacks filename information at missing.patch:4\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying a patch with an invalid mode reports the input' '\n+\tcat >mode.patch <<-\\EOF &&\n+\tdiff --git a/f b/f\n+\told mode 10x644\n+\tEOF\n+\ttest_must_fail git apply mode.patch 2>err &&\n+\tcat >expect <<-\\EOF &&\n+\terror: invalid mode at mode.patch:2: 10x644\n+\n+\tEOF\n+\ttest_cmp expect err\n+'\n test_done\ndiff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh\nindex ae0a56cf5e..96ddf3c53a 100755\n--- a/t/t4254-am-corrupt.sh\n+++ b/t/t4254-am-corrupt.sh\n@@ -65,9 +65,8 @@ test_expect_success setup '\n test_expect_success 'try to apply corrupted patch' '\n \ttest_when_finished \"git am --abort\" &&\n \ttest_must_fail git -c advice.amWorkDir=false -c advice.mergeConflict=false am bad-patch.diff 2>actual &&\n-\techo \"error: git diff header lacks filename information (line 4)\" >expected &&\n \ttest_path_is_file f &&\n-\ttest_cmp expected actual\n+\ttest_grep \"error: git diff header lacks filename information at .*rebase-apply/patch:4\" actual\n '\n \n test_expect_success \"NUL in commit message's body\" '\n-- \n2.51.0\n\n"},{"id":"539238","messageId":"20260317162321.71812-4-jerrywang183@yahoo.com","threadId":"65258","inReplyTo":"20260317162321.71812-1-jerrywang183@yahoo.com","subject":"[PATCH v4 3/3] apply: report input location in binary and garbage patch errors","fromName":"Jialong Wang","fromEmail":"jerrywang183@yahoo.com","sentAt":"2026-03-17T16:23:21Z","receivedAt":"2026-03-17T16:33:46Z","isPatch":true,"sender":{"key":"jerrywang183@yahoo.com","avatar":null},"body":"Several binary parsing paths in apply.c still report only line\nnumbers. When more than one patch input is fed to a single\ninvocation, that does not tell the user which input the line belongs\nto.\n\nReport the patch input location for corrupt and unrecognized binary\npatches, as well as the \"patch with only garbage\" case, and update\nthe related tests.\n\nSigned-off-by: Jialong Wang <jerrywang183@yahoo.com>\n---\n apply.c                 | 10 ++++++----\n t/t4100-apply-stat.sh   | 12 ++++++++++++\n t/t4103-apply-binary.sh | 20 +++++++++++++++++++-\n 3 files changed, 37 insertions(+), 5 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 700809f3e6..84b4a569c5 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -2110,8 +2110,8 @@ static struct fragment *parse_binary_hunk(struct apply_state *state,\n  corrupt:\n \tfree(data);\n \t*status_p = -1;\n-\terror(_(\"corrupt binary patch at line %d: %.*s\"),\n-\t      state->linenr-1, llen-1, buffer);\n+\terror(_(\"corrupt binary patch at %s:%d: %.*s\"),\n+\t      state->patch_input_file, state->linenr-1, llen-1, buffer);\n \treturn NULL;\n }\n \n@@ -2147,7 +2147,8 @@ static int parse_binary(struct apply_state *state,\n \tforward = parse_binary_hunk(state, &buffer, &size, &status, &used);\n \tif (!forward && !status)\n \t\t/* there has to be one hunk (forward hunk) */\n-\t\treturn error(_(\"unrecognized binary patch at line %d\"), state->linenr-1);\n+\t\treturn error(_(\"unrecognized binary patch at %s:%d\"),\n+\t\t\t     state->patch_input_file, state->linenr-1);\n \tif (status)\n \t\t/* otherwise we already gave an error message */\n \t\treturn status;\n@@ -2309,7 +2310,8 @@ static int parse_chunk(struct apply_state *state, char *buffer, unsigned long si\n \t\t */\n \t\tif ((state->apply || state->check) &&\n \t\t    (!patch->is_binary && !metadata_changes(patch))) {\n-\t\t\terror(_(\"patch with only garbage at line %d\"), state->linenr);\n+\t\t\terror(_(\"patch with only garbage at %s:%d\"),\n+\t\t\t      state->patch_input_file, state->linenr);\n \t\t\treturn -128;\n \t\t}\n \t}\ndiff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh\nindex b3d93d8ed6..8393076469 100755\n--- a/t/t4100-apply-stat.sh\n+++ b/t/t4100-apply-stat.sh\n@@ -125,4 +125,16 @@ test_expect_success 'applying a patch with an invalid mode reports the input' '\n \tEOF\n \ttest_cmp expect err\n '\n+\n+test_expect_success 'applying a patch with only garbage reports the input' '\n+\tcat >garbage.patch <<-\\EOF &&\n+\tdiff --git a/f b/f\n+\t--- a/f\n+\t+++ b/f\n+\tthis is garbage\n+\tEOF\n+\ttest_must_fail git apply garbage.patch 2>err &&\n+\techo \"error: patch with only garbage at garbage.patch:4\" >expect &&\n+\ttest_cmp expect err\n+'\n test_done\ndiff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh\nindex 8e302a5a57..f2d41e06bc 100755\n--- a/t/t4103-apply-binary.sh\n+++ b/t/t4103-apply-binary.sh\n@@ -179,6 +179,24 @@ test_expect_success PERL_TEST_HELPERS 'reject truncated binary diff' '\n \t\" <patch >patch.trunc &&\n \n \tdo_reset &&\n-\ttest_must_fail git apply patch.trunc\n+\ttest_must_fail git apply patch.trunc 2>err &&\n+\tline=$(awk \"END { print NR + 1 }\" patch.trunc) &&\n+\tgrep \"error: corrupt binary patch at patch.trunc:$line: \" err\n+'\n+\n+test_expect_success 'reject unrecognized binary diff' '\n+\tcat >patch.bad <<-\\EOF &&\n+\tdiff --git a/f b/f\n+\tnew file mode 100644\n+\tindex 0000000..7898192\n+\tGIT binary patch\n+\tbogus\n+\tEOF\n+\ttest_must_fail git apply patch.bad 2>err &&\n+\tcat >expect <<-\\EOF &&\n+\terror: unrecognized binary patch at patch.bad:4\n+\terror: No valid patches in input (allow with \"--allow-empty\")\n+\tEOF\n+\ttest_cmp expect err\n '\n test_done\n-- \n2.51.0\n\n"},{"id":"539237","messageId":"20260317162321.71812-2-jerrywang183@yahoo.com","threadId":"65258","inReplyTo":"20260317162321.71812-1-jerrywang183@yahoo.com","subject":"[PATCH v4 1/3] apply: report the location of corrupt patches","fromName":"Jialong Wang","fromEmail":"jerrywang183@yahoo.com","sentAt":"2026-03-17T16:23:19Z","receivedAt":"2026-03-17T16:33:48Z","isPatch":true,"sender":{"key":"jerrywang183@yahoo.com","avatar":null},"body":"When parsing a corrupt patch, git apply reports only the line number.\nThat does not tell the user which input the line number refers to.\n\nInclude the patch input path in the error message so the reported\nlocation is easier to use.\n\nReset the line number for each patch input so the reported location stays\ncorrect when multiple input files are provided.\n\nAdd tests for file input, standard input, multiple patch inputs, and\nexisting binary-diff corrupt patch cases.\n\nSigned-off-by: Jialong Wang <jerrywang183@yahoo.com>\n---\n apply.c                |  4 +++-\n t/t4012-diff-binary.sh |  4 ++--\n t/t4100-apply-stat.sh  | 38 +++++++++++++++++++++++++++++++++++++-\n 3 files changed, 42 insertions(+), 4 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex b6dd1066a0..b7b0a201b3 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -1875,7 +1875,8 @@ static int parse_single_patch(struct apply_state *state,\n \t\tlen = parse_fragment(state, line, size, patch, fragment);\n \t\tif (len <= 0) {\n \t\t\tfree(fragment);\n-\t\t\treturn error(_(\"corrupt patch at line %d\"), state->linenr);\n+\t\t\treturn error(_(\"corrupt patch at %s:%d\"),\n+\t\t\t\t     state->patch_input_file, state->linenr);\n \t\t}\n \t\tfragment->patch = line;\n \t\tfragment->size = len;\n@@ -4825,6 +4826,7 @@ static int apply_patch(struct apply_state *state,\n \tint flush_attributes = 0;\n \n \tstate->patch_input_file = filename;\n+\tstate->linenr = 1;\n \tif (read_patch_file(&buf, fd) < 0)\n \t\treturn -128;\n \toffset = 0;\ndiff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh\nindex d1d30ac2a9..97b5ac0407 100755\n--- a/t/t4012-diff-binary.sh\n+++ b/t/t4012-diff-binary.sh\n@@ -68,7 +68,7 @@ test_expect_success 'apply detecting corrupt patch correctly' '\n \tsed -e \"s/-CIT/xCIT/\" <output >broken &&\n \ttest_must_fail git apply --stat --summary broken 2>detected &&\n \tdetected=$(cat detected) &&\n-\tdetected=$(expr \"$detected\" : \"error.*at line \\\\([0-9]*\\\\)\\$\") &&\n+\tdetected=$(expr \"$detected\" : \"error.*broken:\\\\([0-9]*\\\\)\\$\") &&\n \tdetected=$(sed -ne \"${detected}p\" broken) &&\n \ttest \"$detected\" = xCIT\n '\n@@ -77,7 +77,7 @@ test_expect_success 'apply detecting corrupt patch correctly' '\n \tgit diff --binary | sed -e \"s/-CIT/xCIT/\" >broken &&\n \ttest_must_fail git apply --stat --summary broken 2>detected &&\n \tdetected=$(cat detected) &&\n-\tdetected=$(expr \"$detected\" : \"error.*at line \\\\([0-9]*\\\\)\\$\") &&\n+\tdetected=$(expr \"$detected\" : \"error.*broken:\\\\([0-9]*\\\\)\\$\") &&\n \tdetected=$(sed -ne \"${detected}p\" broken) &&\n \ttest \"$detected\" = xCIT\n '\ndiff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh\nindex a5664f3eb3..b19fc9fe50 100755\n--- a/t/t4100-apply-stat.sh\n+++ b/t/t4100-apply-stat.sh\n@@ -48,7 +48,43 @@ test_expect_success 'applying a hunk header which overflows fails' '\n \t+b\n \tEOF\n \ttest_must_fail git apply patch 2>err &&\n-\techo \"error: corrupt patch at line 4\" >expect &&\n+\techo \"error: corrupt patch at patch:4\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying a hunk header which overflows from stdin fails' '\n+\tcat >patch <<-\\EOF &&\n+\tdiff -u a/file b/file\n+\t--- a/file\n+\t+++ b/file\n+\t@@ -98765432109876543210 +98765432109876543210 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply <patch 2>err &&\n+\techo \"error: corrupt patch at <stdin>:4\" >expect &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success 'applying multiple patches reports the corrupted input' '\n+\tcat >good.patch <<-\\EOF &&\n+\tdiff -u a/file b/file\n+\t--- a/file\n+\t+++ b/file\n+\t@@ -1 +1 @@\n+\t-a\n+\t+b\n+\tEOF\n+\tcat >bad.patch <<-\\EOF &&\n+\tdiff -u a/file b/file\n+\t--- a/file\n+\t+++ b/file\n+\t@@ -98765432109876543210 +98765432109876543210 @@\n+\t-a\n+\t+b\n+\tEOF\n+\ttest_must_fail git apply --stat --summary good.patch bad.patch 2>err &&\n+\techo \"error: corrupt patch at bad.patch:4\" >expect &&\n \ttest_cmp expect err\n '\n test_done\n-- \n2.51.0\n\n"}]}