{"thread":{"id":"65598","subject":"[PATCH 0/4] diff: reject negative context values","startedAt":"2026-05-05T23:02:27Z","lastAt":"2026-05-13T01:16:54Z","messageCount":15,"participants":["Michael Montalbo via GitGitGadget","Junio C Hamano","Michael Montalbo"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"542791","messageId":"pull.2105.git.1778022144.gitgitgadget@gmail.com","threadId":"65598","inReplyTo":null,"subject":"[PATCH 0/4] diff: reject negative context values","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-05T23:02:20Z","receivedAt":"2026-05-05T23:02:27Z","isPatch":true,"body":"Negative values for -U and --inter-hunk-context are silently accepted\nand produce structurally invalid diff output.\n\nMalformed hunk headers:\n\n$ wc -l GIT-VERSION-GEN\n106\n$ git log -1 -p -U-500 -- GIT-VERSION-GEN | grep '^@@'                                                         \n@@ -503,999- +503,999- @@\n\n\nLine 503 of a 106-line file, count \"999-\" is not a valid integer.\n\nOverlapping hunks that cannot be applied:\n\n$ git log -1 -p -U3 --inter-hunk-context=100 791aeddfa2 \\                                                      \n    -- git-compat-util.h | git apply --check --reverse\n(success)                                                                                                      \n                                                                                                             \n$ git log -1 -p -U3 --inter-hunk-context=-100 791aeddfa2 \\                                                     \n    -- git-compat-util.h | git apply --check --reverse                                                       \nerror: patch failed: git-compat-util.h:118                                                                     \nerror: git-compat-util.h: patch does not apply                                                               \n\n\nBoth options were originally parsed via opt_arg() which gated on\nisdigit(), making negative values impossible. When they were converted\nto OPT_INTEGER_F / OPT_CALLBACK in d473e2e0e8 (diff.c: convert\n-U|--unified, 2019-01-27) and 16ed6c97cc (diff-parseopt: convert\n--inter-hunk-context, 2019-03-24), the implicit rejection was lost.\nPARSE_OPT_NONEG was added but only prevents the --no-* boolean form,\nnot negative numeric arguments.\n\nThis series restores the original invariant with stronger guarantees:\n\n1/4  diff: reject negative values for --inter-hunk-context                                                     \n     Change type to unsigned int, switch to OPT_UNSIGNED.                                                    \n                                                                                                               \n2/4  diff: reject negative values for -U/--unified                                                             \n     Change type to unsigned int, add range check in callback.                                                 \n                                                                                                               \n3/4  xdiff: guard against negative context lengths                                                           \n     BUG() in xdl_get_hunk() as defense in depth.\n                                                                                                               \n4/4  parse-options: clarify PARSE_OPT_NONEG does not reject                                                    \n     negative numbers                                                                                          \n     Documentation fix.                                                                                        \n\n\nThe config variables diff.context and diff.interHunkContext have\nalways rejected negative values. This series brings the CLI options in line.\n\nMichael Montalbo (4):\n  diff: reject negative values for --inter-hunk-context\n  diff: reject negative values for -U/--unified\n  xdiff: guard against negative context lengths\n  parse-options: clarify PARSE_OPT_NONEG does not reject negative\n    numbers\n\n diff.c                             | 25 ++++++++++++++-----------\n diff.h                             |  4 ++--\n parse-options.h                    |  5 ++++-\n t/t4032-diff-inter-hunk-context.sh |  6 ++++++\n t/t4055-diff-context.sh            |  5 +++++\n xdiff/xemit.c                      | 16 ++++++++++++----\n 6 files changed, 43 insertions(+), 18 deletions(-)\n\n\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2105%2Fmmontalbo%2Fmm%2Freject-negative-interhunk-context-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2105/mmontalbo/mm/reject-negative-interhunk-context-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/2105\n-- \ngitgitgadget\n"},{"id":"542792","messageId":"cca75eca0e81430bc966cad3506b0017652c2ab8.1778022144.git.gitgitgadget@gmail.com","threadId":"65598","inReplyTo":"pull.2105.git.1778022144.gitgitgadget@gmail.com","subject":"[PATCH 1/4] diff: reject negative values for --inter-hunk-context","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-05T23:02:21Z","receivedAt":"2026-05-05T23:02:28Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nNegative values for --inter-hunk-context produce structurally invalid\ndiff output with overlapping hunks:\n\n    $ git log -1 -p -U3 --inter-hunk-context=-100 791aeddfa2 \\\n        -- git-compat-util.h | grep '^@@'\n    @@ -110,6 +110,9 @@\n    @@ -115,6 +118,9 @@\n    @@ -116,6 +122,7 @@\n\nHunk 1 covers lines 110-115, hunk 2 starts at 115 (overlap), hunk 3\nstarts at 116 (overlaps both). The resulting patch cannot be applied.\n\nThe config variable diff.interHunkContext already rejects negative\nvalues, but the command line option does not. The option currently\nuses OPT_INTEGER_F with PARSE_OPT_NONEG, but PARSE_OPT_NONEG only\nprevents the \"--no-inter-hunk-context\" boolean negation form. It does\nnot reject negative numeric arguments like \"--inter-hunk-context=-1\".\n\nChange the type of diff_options.interhunkcontext and its static\ndefault from int to unsigned int, and switch the option parser from\nOPT_INTEGER_F to OPT_UNSIGNED. This rejects negative values at parse\ntime via git_parse_unsigned() and enforces the correct type at compile\ntime via BARF_UNLESS_UNSIGNED.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n diff.c                             | 13 ++++++-------\n diff.h                             |  2 +-\n t/t4032-diff-inter-hunk-context.sh |  6 ++++++\n 3 files changed, 13 insertions(+), 8 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 397e38b41c..5df28e49c5 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -61,7 +61,7 @@ static enum git_colorbool diff_use_color_default = GIT_COLOR_UNKNOWN;\n static int diff_color_moved_default;\n static int diff_color_moved_ws_default;\n static int diff_context_default = 3;\n-static int diff_interhunk_context_default;\n+static unsigned int diff_interhunk_context_default;\n static char *diff_word_regex_cfg;\n static struct external_diff external_diff_cfg;\n static char *diff_order_file_cfg;\n@@ -388,10 +388,10 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.interhunkcontext\")) {\n-\t\tdiff_interhunk_context_default = git_config_int(var, value,\n-\t\t\t\t\t\t\t\tctx->kvi);\n-\t\tif (diff_interhunk_context_default < 0)\n+\t\tint val = git_config_int(var, value, ctx->kvi);\n+\t\tif (val < 0)\n \t\t\treturn -1;\n+\t\tdiff_interhunk_context_default = val;\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.renames\")) {\n@@ -6111,9 +6111,8 @@ struct option *add_diff_options(const struct option *opts,\n \t\tOPT_CALLBACK_F(0, \"default-prefix\", options, NULL,\n \t\t\t       N_(\"use default prefixes a/ and b/\"),\n \t\t\t       PARSE_OPT_NONEG | PARSE_OPT_NOARG, diff_opt_default_prefix),\n-\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &options->interhunkcontext,\n-\t\t\t      N_(\"show context between diff hunks up to the specified number of lines\"),\n-\t\t\t      PARSE_OPT_NONEG),\n+\t\tOPT_UNSIGNED(0, \"inter-hunk-context\", &options->interhunkcontext,\n+\t\t\t     N_(\"show context between diff hunks up to the specified number of lines\")),\n \t\tOPT_CALLBACK_F(0, \"output-indicator-new\",\n \t\t\t       &options->output_indicators[OUTPUT_INDICATOR_NEW],\n \t\t\t       N_(\"<char>\"),\ndiff --git a/diff.h b/diff.h\nindex 7eb84aadf4..033d633db4 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -296,7 +296,7 @@ struct diff_options {\n \t/* Number of context lines to generate in patch output. */\n \tint context;\n \n-\tint interhunkcontext;\n+\tunsigned int interhunkcontext;\n \n \t/* Affects the way detection logic for complete rewrites, renames and\n \t * copies.\ndiff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh\nindex bada0cbd32..bec1676f8d 100755\n--- a/t/t4032-diff-inter-hunk-context.sh\n+++ b/t/t4032-diff-inter-hunk-context.sh\n@@ -114,4 +114,10 @@ test_expect_success 'diff.interHunkContext invalid' '\n \ttest_must_fail git diff\n '\n \n+test_expect_success '--inter-hunk-context rejects negative value' '\n+\ttest_unconfig diff.interHunkContext &&\n+\ttest_must_fail git diff --inter-hunk-context=-1 2>err &&\n+\ttest_grep \"expects a non-negative integer\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"542793","messageId":"f0478d434ce43e51fd92792c3e583df8a515bf05.1778022144.git.gitgitgadget@gmail.com","threadId":"65598","inReplyTo":"pull.2105.git.1778022144.gitgitgadget@gmail.com","subject":"[PATCH 2/4] diff: reject negative values for -U/--unified","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-05T23:02:22Z","receivedAt":"2026-05-05T23:02:30Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nPassing a negative value to -U is silently accepted and produces\ncorrupt unified diff output with malformed hunk headers:\n\n    $ git log -1 -p -U-500 -- GIT-VERSION-GEN | grep '^@@'\n    @@ -503,999- +503,999- @@\n\nLine 503 of a 106-line file, count \"999-\" is not a valid integer.\n\nThe config variable diff.context already rejects negative values, but\nthe command line callback diff_opt_unified() uses strtol() with no\nrange check.\n\nChange the type of diff_options.context and its static default from\nint to unsigned int, matching the change to interhunkcontext in the\nprevious commit. The type change requires reworking the callback and\nconfig parsing to validate in a local variable before assigning to\nthe now-unsigned field.\n\nUnlike --inter-hunk-context which could be converted to OPT_UNSIGNED,\n-U needs OPT_CALLBACK_F for PARSE_OPT_OPTARG (bare -U with no value\nenables patch output). Add a range check in the callback instead.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n diff.c                  | 12 ++++++++----\n diff.h                  |  2 +-\n t/t4055-diff-context.sh |  5 +++++\n 3 files changed, 14 insertions(+), 5 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 5df28e49c5..1771b2c444 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -60,7 +60,7 @@ static int diff_suppress_blank_empty;\n static enum git_colorbool diff_use_color_default = GIT_COLOR_UNKNOWN;\n static int diff_color_moved_default;\n static int diff_color_moved_ws_default;\n-static int diff_context_default = 3;\n+static unsigned int diff_context_default = 3;\n static unsigned int diff_interhunk_context_default;\n static char *diff_word_regex_cfg;\n static struct external_diff external_diff_cfg;\n@@ -382,9 +382,10 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.context\")) {\n-\t\tdiff_context_default = git_config_int(var, value, ctx->kvi);\n-\t\tif (diff_context_default < 0)\n+\t\tint val = git_config_int(var, value, ctx->kvi);\n+\t\tif (val < 0)\n \t\t\treturn -1;\n+\t\tdiff_context_default = val;\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.interhunkcontext\")) {\n@@ -5924,9 +5925,12 @@ static int diff_opt_unified(const struct option *opt,\n \tBUG_ON_OPT_NEG(unset);\n \n \tif (arg) {\n-\t\toptions->context = strtol(arg, &s, 10);\n+\t\tlong val = strtol(arg, &s, 10);\n \t\tif (*s)\n \t\t\treturn error(_(\"%s expects a numerical value\"), \"--unified\");\n+\t\tif (val < 0)\n+\t\t\treturn error(_(\"%s expects a non-negative integer\"), \"--unified\");\n+\t\toptions->context = val;\n \t}\n \tenable_patch_output(&options->output_format);\n \ndiff --git a/diff.h b/diff.h\nindex 033d633db4..bb5cddaf34 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -294,7 +294,7 @@ struct diff_options {\n \tenum git_colorbool use_color;\n \n \t/* Number of context lines to generate in patch output. */\n-\tint context;\n+\tunsigned int context;\n \n \tunsigned int interhunkcontext;\n \ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex 1384a81957..b26f6eea7c 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -82,6 +82,11 @@ test_expect_success 'negative integer config parsing' '\n \ttest_grep \"bad config variable\" output\n '\n \n+test_expect_success '-U-1 is rejected' '\n+\ttest_must_fail git diff -U-1 2>err &&\n+\ttest_grep \"expects a non-negative integer\" err\n+'\n+\n test_expect_success '-U0 is valid, so is diff.context=0' '\n \ttest_config diff.context 0 &&\n \tgit diff >output &&\n-- \ngitgitgadget\n\n"},{"id":"542794","messageId":"f9cfa0c55dde8f7e876b568f8ea7caf555ffff1c.1778022144.git.gitgitgadget@gmail.com","threadId":"65598","inReplyTo":"pull.2105.git.1778022144.gitgitgadget@gmail.com","subject":"[PATCH 3/4] xdiff: guard against negative context lengths","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-05T23:02:23Z","receivedAt":"2026-05-05T23:02:31Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nThe xdemitconf_t fields ctxlen and interhunkctxlen are typed as long\n(signed), but negative values are not meaningful for context line\ncounts. Unlike the diff_options fields changed in the previous two\ncommits, these cannot be converted to unsigned because the xdiff\narithmetic relies on signed subtraction:\n\n    s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);\n\nIf ctxlen were unsigned long, the signed operand would be implicitly\nconverted to unsigned, and the subtraction would wrap to a large\npositive value when i1 < ctxlen, defeating the XDL_MAX clamp. The\nsigned type is required for correct context-window calculations.\n\nThe previous two commits reject negative values at the parse layer\nfor --inter-hunk-context and -U/--unified, so negative values should\nno longer reach xdiff in normal use. Add BUG() guards at the top of\nxdl_get_hunk() as defense in depth to catch programming errors in\ncurrent or future callers that bypass option parsing.\n\nxdl_get_hunk() is called by both xdl_emit_diff() and\nxdl_call_hunk_func(), so a single guard covers all xdiff consumers.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n xdiff/xemit.c | 16 ++++++++++++----\n 1 file changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/xdiff/xemit.c b/xdiff/xemit.c\nindex 04f7e9193b..7cd9cf0a44 100644\n--- a/xdiff/xemit.c\n+++ b/xdiff/xemit.c\n@@ -46,12 +46,20 @@ static long saturating_add(long a, long b)\n xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)\n {\n \txdchange_t *xch, *xchp, *lxch;\n-\tlong max_common = saturating_add(saturating_add(xecfg->ctxlen,\n-\t\t\t\t\t\t\txecfg->ctxlen),\n-\t\t\t\t\t xecfg->interhunkctxlen);\n-\tlong max_ignorable = xecfg->ctxlen;\n+\tlong max_common;\n+\tlong max_ignorable;\n \tlong ignored = 0; /* number of ignored blank lines */\n \n+\tif (xecfg->ctxlen < 0)\n+\t\tBUG(\"negative context length: %ld\", xecfg->ctxlen);\n+\tif (xecfg->interhunkctxlen < 0)\n+\t\tBUG(\"negative inter-hunk context length: %ld\", xecfg->interhunkctxlen);\n+\n+\tmax_common = saturating_add(saturating_add(xecfg->ctxlen,\n+\t\t\t\t\t\t   xecfg->ctxlen),\n+\t\t\t\t    xecfg->interhunkctxlen);\n+\tmax_ignorable = xecfg->ctxlen;\n+\n \t/* remove ignorable changes that are too far before other changes */\n \tfor (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) {\n \t\txch = xchp->next;\n-- \ngitgitgadget\n\n"},{"id":"542795","messageId":"05ff821e6ffec02a3bfc5aef542592de6a7add76.1778022144.git.gitgitgadget@gmail.com","threadId":"65598","inReplyTo":"pull.2105.git.1778022144.gitgitgadget@gmail.com","subject":"[PATCH 4/4] parse-options: clarify PARSE_OPT_NONEG does not reject negative numbers","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-05T23:02:24Z","receivedAt":"2026-05-05T23:02:33Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nThe name \"NONEG\" can be misread as \"no negative [values]\" when it\nactually means \"no [boolean] negation\" (the --no-* form).\n\nWhen --inter-hunk-context and -U/--unified were converted from a\ncustom parser to OPT_INTEGER_F with PARSE_OPT_NONEG in d473e2e0e8\nand 16ed6c97cc, the implicit rejection of negative values (via\nisdigit() in the old opt_arg() parser) was silently lost. The\nprevious commits in this series fix the resulting bugs.\n\nAdd a clarifying note to the flag documentation.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n parse-options.h | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/parse-options.h b/parse-options.h\nindex 706de9729f..c0a3a3dcae 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -116,7 +116,10 @@ typedef int parse_opt_subcommand_fn(int argc, const char **argv,\n  *   mask of parse_opt_option_flags.\n  *   PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs)\n  *   PARSE_OPT_NOARG: says that this option does not take an argument\n- *   PARSE_OPT_NONEG: says that this option cannot be negated\n+ *   PARSE_OPT_NONEG: says that this option cannot be negated (i.e.\n+ *                   prevents --no-<option> boolean form). Does not reject\n+ *                   negative numeric values like --option=-1. Use\n+ *                   OPT_UNSIGNED for options that must be non-negative.\n  *   PARSE_OPT_HIDDEN: this option is skipped in the default usage, and\n  *                     shown only in the full usage.\n  *   PARSE_OPT_LASTARG_DEFAULT: says that this option will take the default\n-- \ngitgitgadget\n"},{"id":"542953","messageId":"xmqq8q9sb5uc.fsf@gitster.g","threadId":"65598","inReplyTo":"05ff821e6ffec02a3bfc5aef542592de6a7add76.1778022144.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 4/4] parse-options: clarify PARSE_OPT_NONEG does not reject negative numbers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-09T22:01:15Z","receivedAt":"2026-05-09T22:01:18Z","isPatch":true,"body":"\"Michael Montalbo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Michael Montalbo <mmontalbo@gmail.com>\n>\n> The name \"NONEG\" can be misread as \"no negative [values]\" when it\n> actually means \"no [boolean] negation\" (the --no-* form).\n>\n> When --inter-hunk-context and -U/--unified were converted from a\n> custom parser to OPT_INTEGER_F with PARSE_OPT_NONEG in d473e2e0e8\n> and 16ed6c97cc, the implicit rejection of negative values (via\n> isdigit() in the old opt_arg() parser) was silently lost. The\n> previous commits in this series fix the resulting bugs.\n\nI do not think _NONEG has anything to do with the bug.  It was\npurely to reject --no-unified and --no-inter-hunk-context.\n\nAnd there was no change to remove PARSE_OPT_NONEG from anywhere and\nuse OPT_UNSIGNED instead to fix any of the bugs fixed in this\nseries, ...\n\n>\n> Add a clarifying note to the flag documentation.\n>\n> Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>\n> ---\n>  parse-options.h | 5 ++++-\n>  1 file changed, 4 insertions(+), 1 deletion(-)\n>\n> diff --git a/parse-options.h b/parse-options.h\n> index 706de9729f..c0a3a3dcae 100644\n> --- a/parse-options.h\n> +++ b/parse-options.h\n> @@ -116,7 +116,10 @@ typedef int parse_opt_subcommand_fn(int argc, const char **argv,\n>   *   mask of parse_opt_option_flags.\n>   *   PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs)\n>   *   PARSE_OPT_NOARG: says that this option does not take an argument\n> - *   PARSE_OPT_NONEG: says that this option cannot be negated\n> + *   PARSE_OPT_NONEG: says that this option cannot be negated (i.e.\n> + *                   prevents --no-<option> boolean form). Does not reject\n> + *                   negative numeric values like --option=-1. Use\n> + *                   OPT_UNSIGNED for options that must be non-negative.\n\n... I do not think the two additional sentences are warranted.  Stop\nat clarifying what negated _means_ (i.e., rejects \"--no-<option>\"),\nwithout adding what negated does _not_ mean.\n\n\n>   *   PARSE_OPT_HIDDEN: this option is skipped in the default usage, and\n>   *                     shown only in the full usage.\n>   *   PARSE_OPT_LASTARG_DEFAULT: says that this option will take the default\n"},{"id":"542960","messageId":"xmqqv7cw9ixu.fsf@gitster.g","threadId":"65598","inReplyTo":"pull.2105.git.1778022144.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/4] diff: reject negative context values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-10T01:01:17Z","receivedAt":"2026-05-10T01:01:19Z","isPatch":true,"body":"\"Michael Montalbo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Negative values for -U and --inter-hunk-context are silently accepted\n> and produce structurally invalid diff output.\n>\n> Malformed hunk headers:\n>\n> $ wc -l GIT-VERSION-GEN\n> 106\n> $ git log -1 -p -U-500 -- GIT-VERSION-GEN | grep '^@@'                                                         \n> @@ -503,999- +503,999- @@\n\nIt may not matter in the cover letter, but why do you need ~60\nwhitespace characters at the end of the command line, and many other\nlines in the message?\n\n\n\n>\n>\n> Line 503 of a 106-line file, count \"999-\" is not a valid integer.\n>\n> Overlapping hunks that cannot be applied:\n>\n> $ git log -1 -p -U3 --inter-hunk-context=100 791aeddfa2 \\                                                      \n>     -- git-compat-util.h | git apply --check --reverse\n> (success)                                                                                                      \n>                                                                                                              \n> $ git log -1 -p -U3 --inter-hunk-context=-100 791aeddfa2 \\                                                     \n>     -- git-compat-util.h | git apply --check --reverse                                                       \n> error: patch failed: git-compat-util.h:118                                                                     \n> error: git-compat-util.h: patch does not apply                                                               \n>\n>\n> Both options were originally parsed via opt_arg() which gated on\n> isdigit(), making negative values impossible. When they were converted\n> to OPT_INTEGER_F / OPT_CALLBACK in d473e2e0e8 (diff.c: convert\n> -U|--unified, 2019-01-27) and 16ed6c97cc (diff-parseopt: convert\n> --inter-hunk-context, 2019-03-24), the implicit rejection was lost.\n> PARSE_OPT_NONEG was added but only prevents the --no-* boolean form,\n> not negative numeric arguments.\n>\n> This series restores the original invariant with stronger guarantees:\n>\n> 1/4  diff: reject negative values for --inter-hunk-context                                                     \n>      Change type to unsigned int, switch to OPT_UNSIGNED.                                                    \n>                                                                                                                \n> 2/4  diff: reject negative values for -U/--unified                                                             \n>      Change type to unsigned int, add range check in callback.                                                 \n>                                                                                                                \n> 3/4  xdiff: guard against negative context lengths                                                           \n>      BUG() in xdl_get_hunk() as defense in depth.\n>                                                                                                                \n> 4/4  parse-options: clarify PARSE_OPT_NONEG does not reject                                                    \n>      negative numbers                                                                                          \n>      Documentation fix.                                                                                        \n>\n>\n> The config variables diff.context and diff.interHunkContext have\n> always rejected negative values. This series brings the CLI options in line.\n>\n> Michael Montalbo (4):\n>   diff: reject negative values for --inter-hunk-context\n>   diff: reject negative values for -U/--unified\n>   xdiff: guard against negative context lengths\n>   parse-options: clarify PARSE_OPT_NONEG does not reject negative\n>     numbers\n>\n>  diff.c                             | 25 ++++++++++++++-----------\n>  diff.h                             |  4 ++--\n>  parse-options.h                    |  5 ++++-\n>  t/t4032-diff-inter-hunk-context.sh |  6 ++++++\n>  t/t4055-diff-context.sh            |  5 +++++\n>  xdiff/xemit.c                      | 16 ++++++++++++----\n>  6 files changed, 43 insertions(+), 18 deletions(-)\n>\n>\n> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2105%2Fmmontalbo%2Fmm%2Freject-negative-interhunk-context-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2105/mmontalbo/mm/reject-negative-interhunk-context-v1\n> Pull-Request: https://github.com/gitgitgadget/git/pull/2105\n"},{"id":"542964","messageId":"CAC2QwmLH19+LHNaP=13c9Ov8x1zXvyDmEhN69_RpbOY+OqEVZA@mail.gmail.com","threadId":"65598","inReplyTo":"xmqq8q9sb5uc.fsf@gitster.g","subject":"Re: [PATCH 4/4] parse-options: clarify PARSE_OPT_NONEG does not reject negative numbers","fromName":"Michael Montalbo","fromEmail":"mmontalbo@gmail.com","sentAt":"2026-05-10T02:41:25Z","receivedAt":"2026-05-10T02:41:38Z","isPatch":true,"body":"On Sat, May 9, 2026 at 3:01 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Michael Montalbo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: Michael Montalbo <mmontalbo@gmail.com>\n> >\n> > The name \"NONEG\" can be misread as \"no negative [values]\" when it\n> > actually means \"no [boolean] negation\" (the --no-* form).\n> >\n> > When --inter-hunk-context and -U/--unified were converted from a\n> > custom parser to OPT_INTEGER_F with PARSE_OPT_NONEG in d473e2e0e8\n> > and 16ed6c97cc, the implicit rejection of negative values (via\n> > isdigit() in the old opt_arg() parser) was silently lost. The\n> > previous commits in this series fix the resulting bugs.\n>\n> I do not think _NONEG has anything to do with the bug.  It was\n> purely to reject --no-unified and --no-inter-hunk-context.\n>\n\nYou are right this was a mistaken assumption on my part.\n\n> And there was no change to remove PARSE_OPT_NONEG from anywhere and\n> use OPT_UNSIGNED instead to fix any of the bugs fixed in this\n> series, ...\n>\n\nRight, PARSE_OPT_NONEG is still enabled implicitly by OPT_UNSIGNED so\nPARSE_OPT_NONEG really has no part in the story.\n\n> >\n> > Add a clarifying note to the flag documentation.\n> >\n> > Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>\n> > ---\n> >  parse-options.h | 5 ++++-\n> >  1 file changed, 4 insertions(+), 1 deletion(-)\n> >\n> > diff --git a/parse-options.h b/parse-options.h\n> > index 706de9729f..c0a3a3dcae 100644\n> > --- a/parse-options.h\n> > +++ b/parse-options.h\n> > @@ -116,7 +116,10 @@ typedef int parse_opt_subcommand_fn(int argc, const char **argv,\n> >   *   mask of parse_opt_option_flags.\n> >   *   PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs)\n> >   *   PARSE_OPT_NOARG: says that this option does not take an argument\n> > - *   PARSE_OPT_NONEG: says that this option cannot be negated\n> > + *   PARSE_OPT_NONEG: says that this option cannot be negated (i.e.\n> > + *                   prevents --no-<option> boolean form). Does not reject\n> > + *                   negative numeric values like --option=-1. Use\n> > + *                   OPT_UNSIGNED for options that must be non-negative.\n>\n> ... I do not think the two additional sentences are warranted.  Stop\n> at clarifying what negated _means_ (i.e., rejects \"--no-<option>\"),\n> without adding what negated does _not_ mean.\n>\n\nOk, will remove the latter portion of the change in a follow-up.\n\n>\n> >   *   PARSE_OPT_HIDDEN: this option is skipped in the default usage, and\n> >   *                     shown only in the full usage.\n> >   *   PARSE_OPT_LASTARG_DEFAULT: says that this option will take the default\n"},{"id":"542965","messageId":"CAC2Qwm+iXYC_L7aYTpcPc2QpDh4VQW79gNBsUOZhSHkNv-y+Ew@mail.gmail.com","threadId":"65598","inReplyTo":"xmqqv7cw9ixu.fsf@gitster.g","subject":"Re: [PATCH 0/4] diff: reject negative context values","fromName":"Michael Montalbo","fromEmail":"mmontalbo@gmail.com","sentAt":"2026-05-10T02:46:08Z","receivedAt":"2026-05-10T02:46:20Z","isPatch":true,"body":"On Sat, May 9, 2026 at 6:01 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Michael Montalbo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > Negative values for -U and --inter-hunk-context are silently accepted\n> > and produce structurally invalid diff output.\n> >\n> > Malformed hunk headers:\n> >\n> > $ wc -l GIT-VERSION-GEN\n> > 106\n> > $ git log -1 -p -U-500 -- GIT-VERSION-GEN | grep '^@@'\n> > @@ -503,999- +503,999- @@\n>\n> It may not matter in the cover letter, but why do you need ~60\n> whitespace characters at the end of the command line, and many other\n> lines in the message?\n>\n\nApologies, this is a mistake I made between formatting my cover letter\nand moving\nit to GitGitGadget. I will clean up the cover letter and be more\ncareful about that in\nthe future. Thank you for pointing this out.\n\n>\n>\n> >\n> >\n> > Line 503 of a 106-line file, count \"999-\" is not a valid integer.\n> >\n> > Overlapping hunks that cannot be applied:\n> >\n> > $ git log -1 -p -U3 --inter-hunk-context=100 791aeddfa2 \\\n> >     -- git-compat-util.h | git apply --check --reverse\n> > (success)\n> >\n> > $ git log -1 -p -U3 --inter-hunk-context=-100 791aeddfa2 \\\n> >     -- git-compat-util.h | git apply --check --reverse\n> > error: patch failed: git-compat-util.h:118\n> > error: git-compat-util.h: patch does not apply\n> >\n> >\n> > Both options were originally parsed via opt_arg() which gated on\n> > isdigit(), making negative values impossible. When they were converted\n> > to OPT_INTEGER_F / OPT_CALLBACK in d473e2e0e8 (diff.c: convert\n> > -U|--unified, 2019-01-27) and 16ed6c97cc (diff-parseopt: convert\n> > --inter-hunk-context, 2019-03-24), the implicit rejection was lost.\n> > PARSE_OPT_NONEG was added but only prevents the --no-* boolean form,\n> > not negative numeric arguments.\n> >\n> > This series restores the original invariant with stronger guarantees:\n> >\n> > 1/4  diff: reject negative values for --inter-hunk-context\n> >      Change type to unsigned int, switch to OPT_UNSIGNED.\n> >\n> > 2/4  diff: reject negative values for -U/--unified\n> >      Change type to unsigned int, add range check in callback.\n> >\n> > 3/4  xdiff: guard against negative context lengths\n> >      BUG() in xdl_get_hunk() as defense in depth.\n> >\n> > 4/4  parse-options: clarify PARSE_OPT_NONEG does not reject\n> >      negative numbers\n> >      Documentation fix.\n> >\n> >\n> > The config variables diff.context and diff.interHunkContext have\n> > always rejected negative values. This series brings the CLI options in line.\n> >\n> > Michael Montalbo (4):\n> >   diff: reject negative values for --inter-hunk-context\n> >   diff: reject negative values for -U/--unified\n> >   xdiff: guard against negative context lengths\n> >   parse-options: clarify PARSE_OPT_NONEG does not reject negative\n> >     numbers\n> >\n> >  diff.c                             | 25 ++++++++++++++-----------\n> >  diff.h                             |  4 ++--\n> >  parse-options.h                    |  5 ++++-\n> >  t/t4032-diff-inter-hunk-context.sh |  6 ++++++\n> >  t/t4055-diff-context.sh            |  5 +++++\n> >  xdiff/xemit.c                      | 16 ++++++++++++----\n> >  6 files changed, 43 insertions(+), 18 deletions(-)\n> >\n> >\n> > base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n> > Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2105%2Fmmontalbo%2Fmm%2Freject-negative-interhunk-context-v1\n> > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2105/mmontalbo/mm/reject-negative-interhunk-context-v1\n> > Pull-Request: https://github.com/gitgitgadget/git/pull/2105\n"},{"id":"543204","messageId":"pull.2105.v2.git.1778609423.gitgitgadget@gmail.com","threadId":"65598","inReplyTo":"pull.2105.git.1778022144.gitgitgadget@gmail.com","subject":"[PATCH v2 0/4] diff: reject negative context values","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-12T18:10:19Z","receivedAt":"2026-05-12T18:10:26Z","isPatch":true,"body":"Negative values for -U and --inter-hunk-context are silently accepted and\nproduce structurally invalid diff output.\n\nMalformed hunk headers:\n\n$ wc -l GIT-VERSION-GEN 106 $ git log -1 -p -U-500 -- GIT-VERSION-GEN | grep\n'^@@' @@ -503,999- +503,999- @@\n\nLine 503 of a 106-line file, count \"999-\" is not a valid integer.\n\nOverlapping hunks that cannot be applied:\n\n$ git log -1 -p -U3 --inter-hunk-context=100 791aeddfa2\n-- git-compat-util.h | git apply --check --reverse (success)\n\n$ git log -1 -p -U3 --inter-hunk-context=-100 791aeddfa2\n-- git-compat-util.h | git apply --check --reverse error: patch failed:\ngit-compat-util.h:118 error: git-compat-util.h: patch does not apply\n\nBoth options were originally parsed via opt_arg() which gated on isdigit(),\nmaking negative values impossible. When they were converted to OPT_INTEGER_F\n/ OPT_CALLBACK in d473e2e0e8 (diff.c: convert -U|--unified, 2019-01-27) and\n16ed6c97cc (diff-parseopt: convert --inter-hunk-context, 2019-03-24), the\nimplicit rejection was lost.\n\nThis series restores the original invariant with stronger guarantees:\n\n1/4 diff: reject negative values for --inter-hunk-context Change type to\nunsigned int, switch to OPT_UNSIGNED.\n\n2/4 diff: reject negative values for -U/--unified Change type to unsigned\nint, add range check in callback.\n\n3/4 xdiff: guard against negative context lengths BUG() in xdl_get_hunk() as\ndefense in depth.\n\n4/4 parse-options: clarify what \"negated\" means for PARSE_OPT_NONEG.\n\nThe config variables diff.context and diff.interHunkContext have always\nrejected negative values. This series brings the CLI options in line.\n\nChanges since v1:\n\nPatch 1 and 4: Rewrote commit message to not imply NONEG was related to the\nbug.\n\nPatch 4: Trimmed to just clarify what \"negated\" means, without documenting\nwhat PARSE_OPT_NONEG does not do.\n\nMichael Montalbo (4):\n  diff: reject negative values for --inter-hunk-context\n  diff: reject negative values for -U/--unified\n  xdiff: guard against negative context lengths\n  parse-options: clarify what \"negated\" means for PARSE_OPT_NONEG\n\n diff.c                             | 25 ++++++++++++++-----------\n diff.h                             |  4 ++--\n parse-options.h                    |  1 +\n t/t4032-diff-inter-hunk-context.sh |  6 ++++++\n t/t4055-diff-context.sh            |  5 +++++\n xdiff/xemit.c                      | 16 ++++++++++++----\n 6 files changed, 40 insertions(+), 17 deletions(-)\n\n\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2105%2Fmmontalbo%2Fmm%2Freject-negative-interhunk-context-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2105/mmontalbo/mm/reject-negative-interhunk-context-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/2105\n\nRange-diff vs v1:\n\n 1:  cca75eca0e ! 1:  f2ebb3a72b diff: reject negative values for --inter-hunk-context\n     @@ Commit message\n          starts at 116 (overlaps both). The resulting patch cannot be applied.\n      \n          The config variable diff.interHunkContext already rejects negative\n     -    values, but the command line option does not. The option currently\n     -    uses OPT_INTEGER_F with PARSE_OPT_NONEG, but PARSE_OPT_NONEG only\n     -    prevents the \"--no-inter-hunk-context\" boolean negation form. It does\n     -    not reject negative numeric arguments like \"--inter-hunk-context=-1\".\n     +    values, but the command line option does not.\n      \n          Change the type of diff_options.interhunkcontext and its static\n          default from int to unsigned int, and switch the option parser from\n 2:  f0478d434c = 2:  fc3d2bc31e diff: reject negative values for -U/--unified\n 3:  f9cfa0c55d = 3:  020ca774c0 xdiff: guard against negative context lengths\n 4:  05ff821e6f ! 4:  3a656f8c0f parse-options: clarify PARSE_OPT_NONEG does not reject negative numbers\n     @@ Metadata\n      Author: Michael Montalbo <mmontalbo@gmail.com>\n      \n       ## Commit message ##\n     -    parse-options: clarify PARSE_OPT_NONEG does not reject negative numbers\n     +    parse-options: clarify what \"negated\" means for PARSE_OPT_NONEG\n      \n     -    The name \"NONEG\" can be misread as \"no negative [values]\" when it\n     -    actually means \"no [boolean] negation\" (the --no-* form).\n     -\n     -    When --inter-hunk-context and -U/--unified were converted from a\n     -    custom parser to OPT_INTEGER_F with PARSE_OPT_NONEG in d473e2e0e8\n     -    and 16ed6c97cc, the implicit rejection of negative values (via\n     -    isdigit() in the old opt_arg() parser) was silently lost. The\n     -    previous commits in this series fix the resulting bugs.\n     -\n     -    Add a clarifying note to the flag documentation.\n     +    The documentation says the flag prevents an option from being\n     +    \"negated\" without specifying what that means. Add a parenthetical\n     +    to clarify that it rejects the \"--no-<option>\" form.\n      \n          Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>\n      \n       ## parse-options.h ##\n      @@ parse-options.h: typedef int parse_opt_subcommand_fn(int argc, const char **argv,\n     -  *   mask of parse_opt_option_flags.\n        *   PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs)\n        *   PARSE_OPT_NOARG: says that this option does not take an argument\n     -- *   PARSE_OPT_NONEG: says that this option cannot be negated\n     -+ *   PARSE_OPT_NONEG: says that this option cannot be negated (i.e.\n     -+ *                   prevents --no-<option> boolean form). Does not reject\n     -+ *                   negative numeric values like --option=-1. Use\n     -+ *                   OPT_UNSIGNED for options that must be non-negative.\n     +  *   PARSE_OPT_NONEG: says that this option cannot be negated\n     ++ *                   (i.e. rejects \"--no-<option>\")\n        *   PARSE_OPT_HIDDEN: this option is skipped in the default usage, and\n        *                     shown only in the full usage.\n        *   PARSE_OPT_LASTARG_DEFAULT: says that this option will take the default\n\n-- \ngitgitgadget\n"},{"id":"543205","messageId":"f2ebb3a72b0b69da3ec525184e79681d66125fdc.1778609423.git.gitgitgadget@gmail.com","threadId":"65598","inReplyTo":"pull.2105.v2.git.1778609423.gitgitgadget@gmail.com","subject":"[PATCH v2 1/4] diff: reject negative values for --inter-hunk-context","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-12T18:10:20Z","receivedAt":"2026-05-12T18:10:28Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nNegative values for --inter-hunk-context produce structurally invalid\ndiff output with overlapping hunks:\n\n    $ git log -1 -p -U3 --inter-hunk-context=-100 791aeddfa2 \\\n        -- git-compat-util.h | grep '^@@'\n    @@ -110,6 +110,9 @@\n    @@ -115,6 +118,9 @@\n    @@ -116,6 +122,7 @@\n\nHunk 1 covers lines 110-115, hunk 2 starts at 115 (overlap), hunk 3\nstarts at 116 (overlaps both). The resulting patch cannot be applied.\n\nThe config variable diff.interHunkContext already rejects negative\nvalues, but the command line option does not.\n\nChange the type of diff_options.interhunkcontext and its static\ndefault from int to unsigned int, and switch the option parser from\nOPT_INTEGER_F to OPT_UNSIGNED. This rejects negative values at parse\ntime via git_parse_unsigned() and enforces the correct type at compile\ntime via BARF_UNLESS_UNSIGNED.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n diff.c                             | 13 ++++++-------\n diff.h                             |  2 +-\n t/t4032-diff-inter-hunk-context.sh |  6 ++++++\n 3 files changed, 13 insertions(+), 8 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 397e38b41c..5df28e49c5 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -61,7 +61,7 @@ static enum git_colorbool diff_use_color_default = GIT_COLOR_UNKNOWN;\n static int diff_color_moved_default;\n static int diff_color_moved_ws_default;\n static int diff_context_default = 3;\n-static int diff_interhunk_context_default;\n+static unsigned int diff_interhunk_context_default;\n static char *diff_word_regex_cfg;\n static struct external_diff external_diff_cfg;\n static char *diff_order_file_cfg;\n@@ -388,10 +388,10 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.interhunkcontext\")) {\n-\t\tdiff_interhunk_context_default = git_config_int(var, value,\n-\t\t\t\t\t\t\t\tctx->kvi);\n-\t\tif (diff_interhunk_context_default < 0)\n+\t\tint val = git_config_int(var, value, ctx->kvi);\n+\t\tif (val < 0)\n \t\t\treturn -1;\n+\t\tdiff_interhunk_context_default = val;\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.renames\")) {\n@@ -6111,9 +6111,8 @@ struct option *add_diff_options(const struct option *opts,\n \t\tOPT_CALLBACK_F(0, \"default-prefix\", options, NULL,\n \t\t\t       N_(\"use default prefixes a/ and b/\"),\n \t\t\t       PARSE_OPT_NONEG | PARSE_OPT_NOARG, diff_opt_default_prefix),\n-\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &options->interhunkcontext,\n-\t\t\t      N_(\"show context between diff hunks up to the specified number of lines\"),\n-\t\t\t      PARSE_OPT_NONEG),\n+\t\tOPT_UNSIGNED(0, \"inter-hunk-context\", &options->interhunkcontext,\n+\t\t\t     N_(\"show context between diff hunks up to the specified number of lines\")),\n \t\tOPT_CALLBACK_F(0, \"output-indicator-new\",\n \t\t\t       &options->output_indicators[OUTPUT_INDICATOR_NEW],\n \t\t\t       N_(\"<char>\"),\ndiff --git a/diff.h b/diff.h\nindex 7eb84aadf4..033d633db4 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -296,7 +296,7 @@ struct diff_options {\n \t/* Number of context lines to generate in patch output. */\n \tint context;\n \n-\tint interhunkcontext;\n+\tunsigned int interhunkcontext;\n \n \t/* Affects the way detection logic for complete rewrites, renames and\n \t * copies.\ndiff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh\nindex bada0cbd32..bec1676f8d 100755\n--- a/t/t4032-diff-inter-hunk-context.sh\n+++ b/t/t4032-diff-inter-hunk-context.sh\n@@ -114,4 +114,10 @@ test_expect_success 'diff.interHunkContext invalid' '\n \ttest_must_fail git diff\n '\n \n+test_expect_success '--inter-hunk-context rejects negative value' '\n+\ttest_unconfig diff.interHunkContext &&\n+\ttest_must_fail git diff --inter-hunk-context=-1 2>err &&\n+\ttest_grep \"expects a non-negative integer\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"543206","messageId":"fc3d2bc31e648615296ec8bc6ef30692aab9ac11.1778609423.git.gitgitgadget@gmail.com","threadId":"65598","inReplyTo":"pull.2105.v2.git.1778609423.gitgitgadget@gmail.com","subject":"[PATCH v2 2/4] diff: reject negative values for -U/--unified","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-12T18:10:21Z","receivedAt":"2026-05-12T18:10:29Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nPassing a negative value to -U is silently accepted and produces\ncorrupt unified diff output with malformed hunk headers:\n\n    $ git log -1 -p -U-500 -- GIT-VERSION-GEN | grep '^@@'\n    @@ -503,999- +503,999- @@\n\nLine 503 of a 106-line file, count \"999-\" is not a valid integer.\n\nThe config variable diff.context already rejects negative values, but\nthe command line callback diff_opt_unified() uses strtol() with no\nrange check.\n\nChange the type of diff_options.context and its static default from\nint to unsigned int, matching the change to interhunkcontext in the\nprevious commit. The type change requires reworking the callback and\nconfig parsing to validate in a local variable before assigning to\nthe now-unsigned field.\n\nUnlike --inter-hunk-context which could be converted to OPT_UNSIGNED,\n-U needs OPT_CALLBACK_F for PARSE_OPT_OPTARG (bare -U with no value\nenables patch output). Add a range check in the callback instead.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n diff.c                  | 12 ++++++++----\n diff.h                  |  2 +-\n t/t4055-diff-context.sh |  5 +++++\n 3 files changed, 14 insertions(+), 5 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 5df28e49c5..1771b2c444 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -60,7 +60,7 @@ static int diff_suppress_blank_empty;\n static enum git_colorbool diff_use_color_default = GIT_COLOR_UNKNOWN;\n static int diff_color_moved_default;\n static int diff_color_moved_ws_default;\n-static int diff_context_default = 3;\n+static unsigned int diff_context_default = 3;\n static unsigned int diff_interhunk_context_default;\n static char *diff_word_regex_cfg;\n static struct external_diff external_diff_cfg;\n@@ -382,9 +382,10 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.context\")) {\n-\t\tdiff_context_default = git_config_int(var, value, ctx->kvi);\n-\t\tif (diff_context_default < 0)\n+\t\tint val = git_config_int(var, value, ctx->kvi);\n+\t\tif (val < 0)\n \t\t\treturn -1;\n+\t\tdiff_context_default = val;\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.interhunkcontext\")) {\n@@ -5924,9 +5925,12 @@ static int diff_opt_unified(const struct option *opt,\n \tBUG_ON_OPT_NEG(unset);\n \n \tif (arg) {\n-\t\toptions->context = strtol(arg, &s, 10);\n+\t\tlong val = strtol(arg, &s, 10);\n \t\tif (*s)\n \t\t\treturn error(_(\"%s expects a numerical value\"), \"--unified\");\n+\t\tif (val < 0)\n+\t\t\treturn error(_(\"%s expects a non-negative integer\"), \"--unified\");\n+\t\toptions->context = val;\n \t}\n \tenable_patch_output(&options->output_format);\n \ndiff --git a/diff.h b/diff.h\nindex 033d633db4..bb5cddaf34 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -294,7 +294,7 @@ struct diff_options {\n \tenum git_colorbool use_color;\n \n \t/* Number of context lines to generate in patch output. */\n-\tint context;\n+\tunsigned int context;\n \n \tunsigned int interhunkcontext;\n \ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex 1384a81957..b26f6eea7c 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -82,6 +82,11 @@ test_expect_success 'negative integer config parsing' '\n \ttest_grep \"bad config variable\" output\n '\n \n+test_expect_success '-U-1 is rejected' '\n+\ttest_must_fail git diff -U-1 2>err &&\n+\ttest_grep \"expects a non-negative integer\" err\n+'\n+\n test_expect_success '-U0 is valid, so is diff.context=0' '\n \ttest_config diff.context 0 &&\n \tgit diff >output &&\n-- \ngitgitgadget\n\n"},{"id":"543207","messageId":"020ca774c0ec3abadb5c987c93373f11d67d5880.1778609423.git.gitgitgadget@gmail.com","threadId":"65598","inReplyTo":"pull.2105.v2.git.1778609423.gitgitgadget@gmail.com","subject":"[PATCH v2 3/4] xdiff: guard against negative context lengths","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-12T18:10:22Z","receivedAt":"2026-05-12T18:10:30Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nThe xdemitconf_t fields ctxlen and interhunkctxlen are typed as long\n(signed), but negative values are not meaningful for context line\ncounts. Unlike the diff_options fields changed in the previous two\ncommits, these cannot be converted to unsigned because the xdiff\narithmetic relies on signed subtraction:\n\n    s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);\n\nIf ctxlen were unsigned long, the signed operand would be implicitly\nconverted to unsigned, and the subtraction would wrap to a large\npositive value when i1 < ctxlen, defeating the XDL_MAX clamp. The\nsigned type is required for correct context-window calculations.\n\nThe previous two commits reject negative values at the parse layer\nfor --inter-hunk-context and -U/--unified, so negative values should\nno longer reach xdiff in normal use. Add BUG() guards at the top of\nxdl_get_hunk() as defense in depth to catch programming errors in\ncurrent or future callers that bypass option parsing.\n\nxdl_get_hunk() is called by both xdl_emit_diff() and\nxdl_call_hunk_func(), so a single guard covers all xdiff consumers.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n xdiff/xemit.c | 16 ++++++++++++----\n 1 file changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/xdiff/xemit.c b/xdiff/xemit.c\nindex 04f7e9193b..7cd9cf0a44 100644\n--- a/xdiff/xemit.c\n+++ b/xdiff/xemit.c\n@@ -46,12 +46,20 @@ static long saturating_add(long a, long b)\n xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)\n {\n \txdchange_t *xch, *xchp, *lxch;\n-\tlong max_common = saturating_add(saturating_add(xecfg->ctxlen,\n-\t\t\t\t\t\t\txecfg->ctxlen),\n-\t\t\t\t\t xecfg->interhunkctxlen);\n-\tlong max_ignorable = xecfg->ctxlen;\n+\tlong max_common;\n+\tlong max_ignorable;\n \tlong ignored = 0; /* number of ignored blank lines */\n \n+\tif (xecfg->ctxlen < 0)\n+\t\tBUG(\"negative context length: %ld\", xecfg->ctxlen);\n+\tif (xecfg->interhunkctxlen < 0)\n+\t\tBUG(\"negative inter-hunk context length: %ld\", xecfg->interhunkctxlen);\n+\n+\tmax_common = saturating_add(saturating_add(xecfg->ctxlen,\n+\t\t\t\t\t\t   xecfg->ctxlen),\n+\t\t\t\t    xecfg->interhunkctxlen);\n+\tmax_ignorable = xecfg->ctxlen;\n+\n \t/* remove ignorable changes that are too far before other changes */\n \tfor (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) {\n \t\txch = xchp->next;\n-- \ngitgitgadget\n\n"},{"id":"543208","messageId":"3a656f8c0fb52a2949041dec55619acea9c117df.1778609423.git.gitgitgadget@gmail.com","threadId":"65598","inReplyTo":"pull.2105.v2.git.1778609423.gitgitgadget@gmail.com","subject":"[PATCH v2 4/4] parse-options: clarify what \"negated\" means for PARSE_OPT_NONEG","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-12T18:10:23Z","receivedAt":"2026-05-12T18:10:32Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nThe documentation says the flag prevents an option from being\n\"negated\" without specifying what that means. Add a parenthetical\nto clarify that it rejects the \"--no-<option>\" form.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n parse-options.h | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/parse-options.h b/parse-options.h\nindex 706de9729f..0d1f738f8d 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -117,6 +117,7 @@ typedef int parse_opt_subcommand_fn(int argc, const char **argv,\n  *   PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs)\n  *   PARSE_OPT_NOARG: says that this option does not take an argument\n  *   PARSE_OPT_NONEG: says that this option cannot be negated\n+ *                   (i.e. rejects \"--no-<option>\")\n  *   PARSE_OPT_HIDDEN: this option is skipped in the default usage, and\n  *                     shown only in the full usage.\n  *   PARSE_OPT_LASTARG_DEFAULT: says that this option will take the default\n-- \ngitgitgadget\n"},{"id":"543225","messageId":"xmqqik8sjegs.fsf@gitster.g","threadId":"65598","inReplyTo":"pull.2105.v2.git.1778609423.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/4] diff: reject negative context values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-13T01:16:51Z","receivedAt":"2026-05-13T01:16:54Z","isPatch":true,"body":"\"Michael Montalbo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Changes since v1:\n>\n> Patch 1 and 4: Rewrote commit message to not imply NONEG was related to the\n> bug.\n>\n> Patch 4: Trimmed to just clarify what \"negated\" means, without documenting\n> what PARSE_OPT_NONEG does not do.\n\nThanks.  Will queue.  I have nothing more to add, but I will hold\noff on marking it for 'next' to give others a chance to comment.\n"}]}