{"thread":{"id":"63396","subject":"[PATCH 0/3] Better support for customising context lines in --patch commands","startedAt":"2025-05-05T09:18:42Z","lastAt":"2025-07-29T16:18:36Z","messageCount":77,"participants":["Leon Michalak via GitGitGadget","Kristoffer Haugsbakk","Leon Michalak","Eric Sunshine","Christian Couder","Phillip Wood","Junio C Hamano","phillip.wood123@gmail.com"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"517232","messageId":"pull.1915.git.1746436719.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":null,"subject":"[PATCH 0/3] Better support for customising context lines in --patch commands","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-05-05T09:18:36Z","receivedAt":"2025-05-05T09:18:42Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"This series of patches attempt to give --interactive/--patch compatible\nbuiltins (\"add\", \"commit\", \"checkout\", \"reset\", \"restore\" and \"stash\")\nbetter support and nicer experience for configuring how many context lines\nare shown in diffs through a variety of ways.\n\nPrior to these patches, the user could not choose how many context lines\nthey saw in --patch commands (apart from one workaround by using\nGIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a\npersistent solution). Additionally, the behaviour around reading from the\ndiff.context and diff.interHunkContext configs was also inconsistent with\nother diff generating commands such as \"log -p\".\n\nThe summarised changes below hopefully make this experience better and fix\nsome inconsistencies:\n\n * diff.context and diff.interHunkContext configs are now respected by\n   --patch compatible commands\n * --unified and --inter-hunk-context command line options have been added\n   to --patch compatible commands (which take prescendence over file\n   configs)\n * \"add\" and \"commit\" in --interactive mode now expose a new \"context\"\n   subcommand which configures the amount of context lines you wish to see\n   in subsequent diffs generated from other subcommands such as \"patch\" or\n   \"diff\"\n\nThe original discussion for this can be read at:\n\n * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/\n\nLeon Michalak (3):\n  add-patch: respect diff.context configuration\n  add-patch: add diff.context command line overrides\n  add-interactive: add new \"context\" subcommand\n\n Documentation/git-add.adoc      |  21 ++++++-\n Documentation/git-checkout.adoc |  11 ++++\n Documentation/git-commit.adoc   |  11 ++++\n Documentation/git-reset.adoc    |  11 ++++\n Documentation/git-restore.adoc  |  11 ++++\n Documentation/git-stash.adoc    |  11 ++++\n add-interactive.c               | 107 +++++++++++++++++++++++++++++---\n add-interactive.h               |  17 ++++-\n add-patch.c                     |  11 +++-\n builtin/add.c                   |  21 +++++--\n builtin/checkout.c              |  28 ++++++++-\n builtin/commit.c                |  15 ++++-\n builtin/reset.c                 |  16 ++++-\n builtin/stash.c                 |  54 ++++++++++++----\n commit.h                        |   3 +-\n t/t3701-add-interactive.sh      |  36 +++++++++--\n t/t4055-diff-context.sh         |  78 ++++++++++++++++++++++-\n t/t9902-completion.sh           |   2 +\n 18 files changed, 420 insertions(+), 44 deletions(-)\n\n\nbase-commit: f65182a99e545d2f2bc22e6c1c2da192133b16a3\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1915\n-- \ngitgitgadget\n"},{"id":"517233","messageId":"8b91eef8120b8f92db953ec983fddce8a442abcc.1746436719.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.git.1746436719.gitgitgadget@gmail.com","subject":"[PATCH 1/3] add-patch: respect diff.context configuration","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-05-05T09:18:37Z","receivedAt":"2025-05-05T09:18:43Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nThis aims to teach relevant builtins (that take in `--patch`) to respect\nthe user's diff.context and diff.interHunkContext file configurations.\n\nSince these are both UI options and `--patch` is designed for the end user,\nI believe this was previously just an inconsistency, which this patch hopes\nto address.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n add-interactive.c       | 16 ++++++++++----\n add-patch.c             |  6 ++++++\n t/t4055-diff-context.sh | 48 ++++++++++++++++++++++++++++++++++++++++-\n 3 files changed, 65 insertions(+), 5 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 97ff35b6f12a..ad12dc416598 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -41,6 +41,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \tconst char *value;\n \n \ts->r = r;\n+\ts->context = -1;\n+\ts->interhunkcontext = -1;\n \n \tif (repo_config_get_value(r, \"color.interactive\", &value))\n \t\ts->use_color = -1;\n@@ -78,6 +80,9 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \trepo_config_get_string(r, \"diff.algorithm\",\n \t\t\t       &s->interactive_diff_algorithm);\n \n+\trepo_config_get_int(r, \"diff.context\", &s->context);\n+\trepo_config_get_int(r, \"diff.interHunkContext\", &s->interhunkcontext);\n+\n \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n \tif (s->use_single_key)\n \t\tsetbuf(stdin, NULL);\n@@ -1014,10 +1019,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,\n \tif (count > 0) {\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n-\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\",\n-\t\t\t     oid_to_hex(!is_initial ? &oid :\n-\t\t\t\t\ts->r->hash_algo->empty_tree),\n-\t\t\t     \"--\", NULL);\n+\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\", NULL);\n+\t\tif (s->context != -1)\n+\t\t\tstrvec_pushf(&cmd.args, \"--unified=%i\", s->context);\n+\t\tif (s->interhunkcontext != -1)\n+\t\t\tstrvec_pushf(&cmd.args, \"--inter-hunk-context=%i\", s->interhunkcontext);\n+\t\tstrvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :\n+\t\t\t     s->r->hash_algo->empty_tree), \"--\", NULL);\n \t\tfor (i = 0; i < files->items.nr; i++)\n \t\t\tif (files->selected[i])\n \t\t\t\tstrvec_push(&cmd.args,\ndiff --git a/add-patch.c b/add-patch.c\nindex 95c67d8c80c4..b43ca1600738 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n {\n \tstruct strvec args = STRVEC_INIT;\n \tconst char *diff_algorithm = s->s.interactive_diff_algorithm;\n+\tint diff_context = s->s.context;\n+\tint diff_interhunkcontext = s->s.interhunkcontext;\n \tstruct strbuf *plain = &s->plain, *colored = NULL;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\\0';\n@@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \tint res;\n \n \tstrvec_pushv(&args, s->mode->diff_cmd);\n+\tif (diff_context != -1)\n+\t\tstrvec_pushf(&args, \"--unified=%i\", diff_context);\n+\tif (diff_interhunkcontext != -1)\n+\t\tstrvec_pushf(&args, \"--inter-hunk-context=%i\", diff_interhunkcontext);\n \tif (diff_algorithm)\n \t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", diff_algorithm);\n \tif (s->revision) {\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex ec2804eea67c..9c024200ade7 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -49,7 +49,53 @@ test_expect_success 'diff.context honored by \"log\"' '\n \t! grep firstline output &&\n \tgit config diff.context 8 &&\n \tgit log -1 -p >output &&\n-\tgrep \"^ firstline\" output\n+\tgrep \"^ firstline\" output &&\n+\tgit config --unset diff.context\n+'\n+\n+test_expect_success 'diff.context honored by \"add\"' '\n+\tgit add -p >output &&\n+\t! grep firstline output &&\n+\tgit config diff.context 8 &&\n+\tgit add -p >output &&\n+\tgrep \"^ firstline\" output &&\n+\tgit config --unset diff.context\n+'\n+\n+test_expect_success 'diff.context honored by \"commit\"' '\n+\t! git commit -p >output &&\n+\t! grep firstline output &&\n+\tgit config diff.context 8 &&\n+\t! git commit -p >output &&\n+\tgrep \"^ firstline\" output &&\n+\tgit config --unset diff.context\n+'\n+\n+test_expect_success 'diff.context honored by \"checkout\"' '\n+\tgit checkout -p >output &&\n+\t! grep firstline output &&\n+\tgit config diff.context 8 &&\n+\tgit checkout -p >output &&\n+\tgrep \"^ firstline\" output &&\n+\tgit config --unset diff.context\n+'\n+\n+test_expect_success 'diff.context honored by \"stash\"' '\n+\t! git stash -p >output &&\n+\t! grep firstline output &&\n+\tgit config diff.context 8 &&\n+\t! git stash -p >output &&\n+\tgrep \"^ firstline\" output &&\n+\tgit config --unset diff.context\n+'\n+\n+test_expect_success 'diff.context honored by \"restore\"' '\n+\tgit restore -p >output &&\n+\t! grep firstline output &&\n+\tgit config diff.context 8 &&\n+\tgit restore -p >output &&\n+\tgrep \"^ firstline\" output &&\n+\tgit config --unset diff.context\n '\n \n test_expect_success 'The -U option overrides diff.context' '\n-- \ngitgitgadget\n\n"},{"id":"517234","messageId":"b4b7854f330af7588b12e3361bed40723febddad.1746436719.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.git.1746436719.gitgitgadget@gmail.com","subject":"[PATCH 3/3] add-interactive: add new \"context\" subcommand","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-05-05T09:18:39Z","receivedAt":"2025-05-05T09:18:44Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nThis teaches `add/commit --interactive` a new \"context\" subcommand, which\nchanges the amount of context lines subsequent subcommands like \"patch\"\nor \"diff\" generate in their diffs.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n Documentation/git-add.adoc | 10 ++++--\n add-interactive.c          | 72 +++++++++++++++++++++++++++++++++++++-\n t/t3701-add-interactive.sh | 36 ++++++++++++++++---\n 3 files changed, 110 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\nindex e3a50706acd5..1ab98c9b876e 100644\n--- a/Documentation/git-add.adoc\n+++ b/Documentation/git-add.adoc\n@@ -265,14 +265,15 @@ and type return, like this:\n ------------\n     *** Commands ***\n       1: status       2: update       3: revert       4: add untracked\n-      5: patch        6: diff         7: quit         8: help\n+      5: patch        6: diff         7: context      8: quit\n+      9: help\n     What now> 1\n ------------\n \n You also could say `s` or `sta` or `status` above as long as the\n choice is unique.\n \n-The main command loop has 6 subcommands (plus help and quit).\n+The main command loop has 7 subcommands (plus help and quit).\n \n status::\n \n@@ -373,6 +374,11 @@ diff::\n   This lets you review what will be committed (i.e. between\n   `HEAD` and index).\n \n+context::\n+\n+  This lets you change the amount of context lines shown in diffs that\n+  the 'patch' and 'diff' subcommands generate.\n+\n \n EDITING PATCHES\n ---------------\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 1ea8eb711a60..42b1bb8c5b64 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -20,6 +20,8 @@\n #include \"prompt.h\"\n #include \"tree.h\"\n \n+static void choose_prompt_help_context(struct add_i_state *s);\n+\n static void init_color(struct repository *r, struct add_i_state *s,\n \t\t       const char *section_and_slot, char *dst,\n \t\t       const char *default_color)\n@@ -259,7 +261,8 @@ static void list(struct add_i_state *s, struct string_list *list, int *selected,\n \t\topts->print_item(i, selected ? selected[i] : 0, list->items + i,\n \t\t\t\t opts->print_item_data);\n \n-\t\tif ((opts->columns) && ((i + 1) % (opts->columns))) {\n+\t\tif (i < list->nr - 1 &&\n+\t\t    (opts->columns) && ((i + 1) % (opts->columns))) {\n \t\t\tputchar('\\t');\n \t\t\tlast_lf = 0;\n \t\t}\n@@ -1047,6 +1050,60 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,\n \treturn res;\n }\n \n+static int run_context(struct add_i_state *s, const struct pathspec *ps UNUSED,\n+\t\t       struct prefix_item_list *files UNUSED,\n+\t\t       struct list_and_choose_options *opts UNUSED)\n+{\n+\tstruct diff_options diffopts;\n+\tstruct strbuf input = STRBUF_INIT;\n+\tint res = 0;\n+\n+\trepo_diff_setup(s->r, &diffopts);\n+\n+\tfor (;;) {\n+\t\tint new_context;\n+\t\tchar *endp;\n+\n+\t\tstrbuf_reset(&input);\n+\n+\t\tcolor_fprintf(stdout, s->header_color, \"  %s:\", N_(\"Current\"));\n+\t\tfprintf(stdout, \" %i\\n\", s->context == -1 ?\n+\t\t\tdiffopts.context : s->context);\n+\n+\t\tcolor_fprintf(stdout, s->prompt_color, \"%s\", N_(\"Change context\"));\n+\t\tfputs(\"> \", stdout);\n+\t\tfflush(stdout);\n+\n+\t\tif (git_read_line_interactively(&input) == EOF) {\n+\t\t\tputchar('\\n');\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (!input.len)\n+\t\t\tbreak;\n+\n+\t\tif (!strcmp(input.buf, \"?\")) {\n+\t\t\tchoose_prompt_help_context(s);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tnew_context = strtol(input.buf, &endp, 10);\n+\t\tif (*endp) {\n+\t\t\tcolor_fprintf_ln(stderr, s->error_color,\n+\t\t\t\t_(\"Context must be a numerical value\"));\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\ts->context = new_context;\n+\n+\t\tbreak;\n+\t}\n+\n+\tstrbuf_release(&input);\n+\tputchar('\\n');\n+\treturn res;\n+}\n+\n static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED,\n \t\t    struct prefix_item_list *files UNUSED,\n \t\t    struct list_and_choose_options *opts UNUSED)\n@@ -1061,6 +1118,8 @@ static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED,\n \t\t\t _(\"pick hunks and update selectively\"));\n \tcolor_fprintf_ln(stdout, s->help_color, \"diff          - %s\",\n \t\t\t _(\"view diff between HEAD and index\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"context       - %s\",\n+\t\t\t _(\"change how many context lines diffs are generated with\"));\n \tcolor_fprintf_ln(stdout, s->help_color, \"add untracked - %s\",\n \t\t\t _(\"add contents of untracked files to the staged set of changes\"));\n \n@@ -1087,6 +1146,16 @@ static void choose_prompt_help(struct add_i_state *s)\n \t\t\t _(\"(empty) finish selecting\"));\n }\n \n+static void choose_prompt_help_context(struct add_i_state *s)\n+{\n+\tcolor_fprintf_ln(stdout, s->help_color, \"%s\",\n+\t\t\t _(\"Prompt help:\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"<n>        - %s\",\n+\t\t\t _(\"specify new context lines amount\"));\n+\tcolor_fprintf_ln(stdout, s->help_color, \"           - %s\",\n+\t\t\t _(\"(empty) finish selecting\"));\n+}\n+\n typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,\n \t\t\t struct prefix_item_list *files,\n \t\t\t struct list_and_choose_options *opts);\n@@ -1147,6 +1216,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps,\n \t\t{ \"add untracked\", run_add_untracked },\n \t\t{ \"patch\", run_patch },\n \t\t{ \"diff\", run_diff },\n+\t\t{ \"context\", run_context },\n \t\t{ \"quit\", NULL },\n \t\t{ \"help\", run_help },\n \t};\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex b8a05d95f3f1..9dcbc07d5876 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -758,16 +758,19 @@ test_expect_success 'colors can be overridden' '\n \n \t<RED>*** Commands ***<RESET>\n \t  1: <YELLOW>s<RESET>tatus\t  2: <YELLOW>u<RESET>pdate\t  3: <YELLOW>r<RESET>evert\t  4: <YELLOW>a<RESET>dd untracked\n-\t  5: <YELLOW>p<RESET>atch\t  6: <YELLOW>d<RESET>iff\t  7: <YELLOW>q<RESET>uit\t  8: <YELLOW>h<RESET>elp\n+\t  5: <YELLOW>p<RESET>atch\t  6: <YELLOW>d<RESET>iff\t  7: <YELLOW>c<RESET>ontext\t  8: <YELLOW>q<RESET>uit\n+\t  9: <YELLOW>h<RESET>elp\n \t<YELLOW>What now<RESET>> <GREEN>status        - show paths with changes<RESET>\n \t<GREEN>update        - add working tree state to the staged set of changes<RESET>\n \t<GREEN>revert        - revert staged set of changes back to the HEAD version<RESET>\n \t<GREEN>patch         - pick hunks and update selectively<RESET>\n \t<GREEN>diff          - view diff between HEAD and index<RESET>\n+\t<GREEN>context       - change how many context lines diffs are generated with<RESET>\n \t<GREEN>add untracked - add contents of untracked files to the staged set of changes<RESET>\n \t<RED>*** Commands ***<RESET>\n \t  1: <YELLOW>s<RESET>tatus\t  2: <YELLOW>u<RESET>pdate\t  3: <YELLOW>r<RESET>evert\t  4: <YELLOW>a<RESET>dd untracked\n-\t  5: <YELLOW>p<RESET>atch\t  6: <YELLOW>d<RESET>iff\t  7: <YELLOW>q<RESET>uit\t  8: <YELLOW>h<RESET>elp\n+\t  5: <YELLOW>p<RESET>atch\t  6: <YELLOW>d<RESET>iff\t  7: <YELLOW>c<RESET>ontext\t  8: <YELLOW>q<RESET>uit\n+\t  9: <YELLOW>h<RESET>elp\n \t<YELLOW>What now<RESET>> Bye.\n \tEOF\n \ttest_cmp expect actual &&\n@@ -831,7 +834,8 @@ test_expect_success 'brackets appear without color' '\n \t|\n \t|*** Commands ***\n \t|  1: [s]tatus\t  2: [u]pdate\t  3: [r]evert\t  4: [a]dd untracked\n-\t|  5: [p]atch\t  6: [d]iff\t  7: [q]uit\t  8: [h]elp\n+\t|  5: [p]atch\t  6: [d]iff\t  7: [c]ontext\t  8: [q]uit\n+\t|  9: [h]elp\n \t|What now> Bye.\n \tEOF\n \n@@ -1172,16 +1176,19 @@ test_expect_success 'show help from add--helper' '\n \n \t<BOLD>*** Commands ***<RESET>\n \t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n-\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>c<RESET>ontext\t  8: <BOLD;BLUE>q<RESET>uit\n+\t  9: <BOLD;BLUE>h<RESET>elp\n \t<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>\n \t<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>\n \t<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>\n \t<BOLD;RED>patch         - pick hunks and update selectively<RESET>\n \t<BOLD;RED>diff          - view diff between HEAD and index<RESET>\n+\t<BOLD;RED>context       - change how many context lines diffs are generated with<RESET>\n \t<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>\n \t<BOLD>*** Commands ***<RESET>\n \t  1: <BOLD;BLUE>s<RESET>tatus\t  2: <BOLD;BLUE>u<RESET>pdate\t  3: <BOLD;BLUE>r<RESET>evert\t  4: <BOLD;BLUE>a<RESET>dd untracked\n-\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>q<RESET>uit\t  8: <BOLD;BLUE>h<RESET>elp\n+\t  5: <BOLD;BLUE>p<RESET>atch\t  6: <BOLD;BLUE>d<RESET>iff\t  7: <BOLD;BLUE>c<RESET>ontext\t  8: <BOLD;BLUE>q<RESET>uit\n+\t  9: <BOLD;BLUE>h<RESET>elp\n \t<BOLD;BLUE>What now<RESET>>$SP\n \tBye.\n \tEOF\n@@ -1230,4 +1237,23 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'change context works' '\n+\tgit reset --hard &&\n+\tcat >template <<-\\EOF &&\n+\tfirstline\n+\tpreline\n+\tTARGET\n+\tpostline\n+\tlastline\n+\tEOF\n+\tsed \"/TARGET/d\" >x <template &&\n+\tgit update-index --add x &&\n+\tgit commit -m initial &&\n+\tsed \"s/TARGET/ADDED/\" >x <template &&\n+\ttest_write_lines p 1 | git add -i >output &&\n+\tgrep firstline output &&\n+\ttest_write_lines c 0 p 1 | git add -i >output &&\n+\t! grep firstline output\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"517235","messageId":"7700eb173e73bb240852dc1c7ce26f3d1f95d8ca.1746436719.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.git.1746436719.gitgitgadget@gmail.com","subject":"[PATCH 2/3] add-patch: add diff.context command line overrides","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-05-05T09:18:38Z","receivedAt":"2025-05-05T09:18:44Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nThis patch compliments `8b91eef812`, where builtins that accept\n`--patch` options now respect `diff.context` and `diff.interHunkContext`\nfile configurations.\n\nIn particular, this patch helps users who don't want to set persistent\ncontext configurations or just want a way to override them on a one-time\nbasis, by allowing the relevant builtins to accept corresponding command\nline options that override the file configurations.\n\nThis mimics `diff` which allows for both context file configuration and\ncommand line overrides.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n Documentation/git-add.adoc      | 11 +++++++\n Documentation/git-checkout.adoc | 11 +++++++\n Documentation/git-commit.adoc   | 11 +++++++\n Documentation/git-reset.adoc    | 11 +++++++\n Documentation/git-restore.adoc  | 11 +++++++\n Documentation/git-stash.adoc    | 11 +++++++\n add-interactive.c               | 19 +++++++++---\n add-interactive.h               | 17 +++++++++--\n add-patch.c                     |  5 +--\n builtin/add.c                   | 21 ++++++++++---\n builtin/checkout.c              | 28 +++++++++++++++--\n builtin/commit.c                | 15 ++++++++-\n builtin/reset.c                 | 16 ++++++++--\n builtin/stash.c                 | 54 ++++++++++++++++++++++++++-------\n commit.h                        |  3 +-\n t/t4055-diff-context.sh         | 30 ++++++++++++++++++\n t/t9902-completion.sh           |  2 ++\n 17 files changed, 245 insertions(+), 31 deletions(-)\n\ndiff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\nindex eba0b419ce50..e3a50706acd5 100644\n--- a/Documentation/git-add.adoc\n+++ b/Documentation/git-add.adoc\n@@ -104,6 +104,17 @@ This effectively runs `add --interactive`, but bypasses the\n initial command menu and directly jumps to the `patch` subcommand.\n See ``Interactive mode'' for details.\n \n+`-U<n>`::\n+`--unified=<n>`::\n+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n+\tor 3 if the config option is unset. Implies `--interactive/--patch`.\n+\n+`--inter-hunk-context=<n>`::\n+\tShow the context between diff hunks, up to the specified _<number>_\n+\tof lines, thereby fusing hunks that are close to each other.\n+\tDefaults to `diff.interHunkContext` or 0 if the config option\n+\tis unset. Implies `--interactive/--patch`.\n+\n `-e`::\n `--edit`::\n \tOpen the diff vs. the index in an editor and let the user\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex a66c53a5cd1e..46286a6c8c9e 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -289,6 +289,17 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.\n Note that this option uses the no overlay mode by default (see also\n `--overlay`), and currently doesn't support overlay mode.\n \n+`-U<n>`::\n+`--unified=<n>`::\n+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n+\tor 3 if the config option is unset. Implies `--patch`.\n+\n+`--inter-hunk-context=<n>`::\n+\tShow the context between diff hunks, up to the specified _<number>_\n+\tof lines, thereby fusing hunks that are close to each other.\n+\tDefaults to `diff.interHunkContext` or 0 if the config option\n+\tis unset. Implies `--patch`.\n+\n --ignore-other-worktrees::\n \t`git checkout` refuses when the wanted branch is already checked\n \tout or otherwise in use by another worktree. This option makes\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex dc219025f1eb..c0b206aee9c9 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -76,6 +76,17 @@ OPTIONS\n \twhich changes to commit. See linkgit:git-add[1] for\n \tdetails.\n \n+`-U<n>`::\n+`--unified=<n>`::\n+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n+\tor 3 if the config option is unset. Implies `--interactive/--patch`.\n+\n+`--inter-hunk-context=<n>`::\n+\tShow the context between diff hunks, up to the specified _<number>_\n+\tof lines, thereby fusing hunks that are close to each other.\n+\tDefaults to `diff.interHunkContext` or 0 if the config option\n+\tis unset. Implies `--interactive/--patch`.\n+\n `-C <commit>`::\n `--reuse-message=<commit>`::\n \tTake an existing _<commit>_ object, and reuse the log message\ndiff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc\nindex 53ab88c5451c..6fcc86a3b960 100644\n--- a/Documentation/git-reset.adoc\n+++ b/Documentation/git-reset.adoc\n@@ -125,6 +125,17 @@ OPTIONS\n \tseparated with _NUL_ character and all other characters are taken\n \tliterally (including newlines and quotes).\n \n+`-U<n>`::\n+`--unified=<n>`::\n+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n+\tor 3 if the config option is unset. Implies `--patch`.\n+\n+`--inter-hunk-context=<n>`::\n+\tShow the context between diff hunks, up to the specified _<number>_\n+\tof lines, thereby fusing hunks that are close to each other.\n+\tDefaults to `diff.interHunkContext` or 0 if the config option\n+\tis unset. Implies `--patch`.\n+\n `--`::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc\nindex 877b7772e667..49d7e6b5a5c8 100644\n--- a/Documentation/git-restore.adoc\n+++ b/Documentation/git-restore.adoc\n@@ -52,6 +52,17 @@ leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to\n \tMode\" section of linkgit:git-add[1] to learn how to operate\n \tthe `--patch` mode.\n \n+`-U<n>`::\n+`--unified=<n>`::\n+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n+\tor 3 if the config option is unset. Implies `--patch`.\n+\n+`--inter-hunk-context=<n>`::\n+\tShow the context between diff hunks, up to the specified _<number>_\n+\tof lines, thereby fusing hunks that are close to each other.\n+\tDefaults to `diff.interHunkContext` or 0 if the config option\n+\tis unset. Implies `--patch`.\n+\n `-W`::\n `--worktree`::\n `-S`::\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex 1a5177f4986c..24916fa0723e 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -208,6 +208,17 @@ to learn how to operate the `--patch` mode.\n The `--patch` option implies `--keep-index`.  You can use\n `--no-keep-index` to override this.\n \n+`-U<n>`::\n+`--unified=<n>`::\n+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n+\tor 3 if the config option is unset. Implies `--patch`.\n+\n+`--inter-hunk-context=<n>`::\n+\tShow the context between diff hunks, up to the specified _<number>_\n+\tof lines, thereby fusing hunks that are close to each other.\n+\tDefaults to `diff.interHunkContext` or 0 if the config option\n+\tis unset. Implies `--patch`.\n+\n -S::\n --staged::\n \tThis option is only valid for `push` and `save` commands.\ndiff --git a/add-interactive.c b/add-interactive.c\nindex ad12dc416598..1ea8eb711a60 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -36,7 +36,8 @@ static void init_color(struct repository *r, struct add_i_state *s,\n \tfree(key);\n }\n \n-void init_add_i_state(struct add_i_state *s, struct repository *r)\n+void init_add_i_state(struct add_i_state *s, struct repository *r,\n+\t\t      struct add_p_opt *add_p_opt)\n {\n \tconst char *value;\n \n@@ -86,6 +87,11 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n \tif (s->use_single_key)\n \t\tsetbuf(stdin, NULL);\n+\n+\tif (add_p_opt->context != -1)\n+\t\ts->context = add_p_opt->context;\n+\tif (add_p_opt->interhunkcontext != -1)\n+\t\ts->interhunkcontext = add_p_opt->interhunkcontext;\n }\n \n void clear_add_i_state(struct add_i_state *s)\n@@ -974,6 +980,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,\n \topts->prompt = N_(\"Patch update\");\n \tcount = list_and_choose(s, files, opts);\n \tif (count > 0) {\n+\t\tstruct add_p_opt add_p_opt = {\n+\t\t\t.context = s->context,\n+\t\t\t.interhunkcontext = s->interhunkcontext,\n+\t\t};\n \t\tstruct strvec args = STRVEC_INIT;\n \t\tstruct pathspec ps_selected = { 0 };\n \n@@ -984,7 +994,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,\n \t\tparse_pathspec(&ps_selected,\n \t\t\t       PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,\n \t\t\t       PATHSPEC_LITERAL_PATH, \"\", args.v);\n-\t\tres = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);\n+\t\tres = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected);\n \t\tstrvec_clear(&args);\n \t\tclear_pathspec(&ps_selected);\n \t}\n@@ -1118,7 +1128,8 @@ static void command_prompt_help(struct add_i_state *s)\n \t\t\t _(\"(empty) select nothing\"));\n }\n \n-int run_add_i(struct repository *r, const struct pathspec *ps)\n+int run_add_i(struct repository *r, const struct pathspec *ps,\n+\t      struct add_p_opt *add_p_opt)\n {\n \tstruct add_i_state s = { NULL };\n \tstruct print_command_item_data data = { \"[\", \"]\" };\n@@ -1161,7 +1172,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t\t->util = util;\n \t}\n \n-\tinit_add_i_state(&s, r);\n+\tinit_add_i_state(&s, r, add_p_opt);\n \n \t/*\n \t * When color was asked for, use the prompt color for\ndiff --git a/add-interactive.h b/add-interactive.h\nindex 693f125e8e4b..653f07a917b8 100644\n--- a/add-interactive.h\n+++ b/add-interactive.h\n@@ -3,6 +3,13 @@\n \n #include \"color.h\"\n \n+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }\n+\n+struct add_p_opt {\n+\tint context;\n+\tint interhunkcontext;\n+};\n+\n struct add_i_state {\n \tstruct repository *r;\n \tint use_color;\n@@ -18,14 +25,17 @@ struct add_i_state {\n \n \tint use_single_key;\n \tchar *interactive_diff_filter, *interactive_diff_algorithm;\n+\tint context, interhunkcontext;\n };\n \n-void init_add_i_state(struct add_i_state *s, struct repository *r);\n+void init_add_i_state(struct add_i_state *s, struct repository *r,\n+\t\t      struct add_p_opt *add_p_opt);\n void clear_add_i_state(struct add_i_state *s);\n \n struct repository;\n struct pathspec;\n-int run_add_i(struct repository *r, const struct pathspec *ps);\n+int run_add_i(struct repository *r, const struct pathspec *ps,\n+\t      struct add_p_opt *add_p_opt);\n \n enum add_p_mode {\n \tADD_P_ADD,\n@@ -36,6 +46,7 @@ enum add_p_mode {\n };\n \n int run_add_p(struct repository *r, enum add_p_mode mode,\n-\t      const char *revision, const struct pathspec *ps);\n+\t      struct add_p_opt *o, const char *revision,\n+\t      const struct pathspec *ps);\n \n #endif\ndiff --git a/add-patch.c b/add-patch.c\nindex b43ca1600738..c0d33820c558 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -1766,14 +1766,15 @@ soft_increment:\n }\n \n int run_add_p(struct repository *r, enum add_p_mode mode,\n-\t      const char *revision, const struct pathspec *ps)\n+\t      struct add_p_opt *o, const char *revision,\n+\t      const struct pathspec *ps)\n {\n \tstruct add_p_state s = {\n \t\t{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tsize_t i, binary_count = 0;\n \n-\tinit_add_i_state(&s.s, r);\n+\tinit_add_i_state(&s.s, r, o);\n \n \tif (mode == ADD_P_STASH)\n \t\ts.mode = &patch_mode_stash;\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 78dfb2657767..9b4e618b3fe2 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -29,6 +29,7 @@ static const char * const builtin_add_usage[] = {\n \tNULL\n };\n static int patch_interactive, add_interactive, edit_interactive;\n+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n static int take_worktree_changes;\n static int add_renormalize;\n static int pathspec_file_nul;\n@@ -157,7 +158,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec *\n int interactive_add(struct repository *repo,\n \t\t    const char **argv,\n \t\t    const char *prefix,\n-\t\t    int patch)\n+\t\t    int patch, struct add_p_opt *add_p_opt)\n {\n \tstruct pathspec pathspec;\n \tint ret;\n@@ -169,9 +170,10 @@ int interactive_add(struct repository *repo,\n \t\t       prefix, argv);\n \n \tif (patch)\n-\t\tret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);\n+\t\tret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL,\n+\t\t\t\t  &pathspec);\n \telse\n-\t\tret = !!run_add_i(repo, &pathspec);\n+\t\tret = !!run_add_i(repo, &pathspec, add_p_opt);\n \n \tclear_pathspec(&pathspec);\n \treturn ret;\n@@ -253,6 +255,12 @@ static struct option builtin_add_options[] = {\n \tOPT_GROUP(\"\"),\n \tOPT_BOOL('i', \"interactive\", &add_interactive, N_(\"interactive picking\")),\n \tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"select hunks interactively\")),\n+\tOPT_INTEGER_F('U', \"unified\", &add_p_opt.context,\n+\t\t      N_(\"generate diffs with <n> lines context, implies --interactive/--patch\"),\n+\t\t      PARSE_OPT_NONEG),\n+\tOPT_INTEGER_F(0, \"inter-hunk-context\", &add_p_opt.interhunkcontext,\n+\t\t      N_(\"show context between diff hunks up to the specified number of lines, implies --interactive/--patch\"),\n+\t\t      PARSE_OPT_NONEG),\n \tOPT_BOOL('e', \"edit\", &edit_interactive, N_(\"edit current diff and apply\")),\n \tOPT__FORCE(&ignored_too, N_(\"allow adding otherwise ignored files\"), 0),\n \tOPT_BOOL('u', \"update\", &take_worktree_changes, N_(\"update tracked files\")),\n@@ -398,7 +406,12 @@ int cmd_add(int argc,\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--dry-run\", \"--interactive/--patch\");\n \t\tif (pathspec_from_file)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--pathspec-from-file\", \"--interactive/--patch\");\n-\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive));\n+\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--interactive/--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--interactive/--patch\");\n \t}\n \n \tif (edit_interactive) {\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex d185982f3a63..22bd7b1d2b3b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -61,6 +61,8 @@ static const char * const restore_usage[] = {\n \n struct checkout_opts {\n \tint patch_mode;\n+\tint patch_context;\n+\tint patch_interhunk_context;\n \tint quiet;\n \tint merge;\n \tint force;\n@@ -104,7 +106,12 @@ struct checkout_opts {\n \tstruct tree *source_tree;\n };\n \n-#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 }\n+#define CHECKOUT_OPTS_INIT { \\\n+\t.conflict_style = -1, \\\n+\t.merge = -1, \\\n+\t.patch_context = -1, \\\n+\t.patch_interhunk_context = -1, \\\n+}\n \n struct branch_info {\n \tchar *name; /* The short name used */\n@@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts,\n \n \tif (opts->patch_mode) {\n \t\tenum add_p_mode patch_mode;\n+\t\tstruct add_p_opt add_p_opt = {\n+\t\t\t.context = opts->patch_context,\n+\t\t\t.interhunkcontext = opts->patch_interhunk_context,\n+\t\t};\n \t\tconst char *rev = new_branch_info->name;\n \t\tchar rev_oid[GIT_MAX_HEXSZ + 1];\n \n@@ -564,8 +575,13 @@ static int checkout_paths(const struct checkout_opts *opts,\n \t\telse\n \t\t\tBUG(\"either flag must have been set, worktree=%d, index=%d\",\n \t\t\t    opts->checkout_worktree, opts->checkout_index);\n-\t\treturn !!run_add_p(the_repository, patch_mode, rev,\n-\t\t\t\t   &opts->pathspec);\n+\t\treturn !!run_add_p(the_repository, patch_mode, &add_p_opt,\n+\t\t\t\t   rev, &opts->pathspec);\n+\t} else {\n+\t\tif (opts->patch_context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (opts->patch_interhunk_context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n \t}\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n@@ -1738,6 +1754,12 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,\n \t\t\t      N_(\"checkout their version for unmerged files\"),\n \t\t\t      3, PARSE_OPT_NONEG),\n \t\tOPT_BOOL('p', \"patch\", &opts->patch_mode, N_(\"select hunks interactively\")),\n+\t\tOPT_INTEGER_F('U', \"unified\", &opts->patch_context,\n+\t\t\tN_(\"generate diffs with <n> lines context, implies --patch\"),\n+\t\t\tPARSE_OPT_NONEG),\n+\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &opts->patch_interhunk_context,\n+\t\t\tN_(\"show context between diff hunks up to the specified number of lines, implies --patch\"),\n+\t\t\tPARSE_OPT_NONEG),\n \t\tOPT_BOOL(0, \"ignore-skip-worktree-bits\", &opts->ignore_skipworktree,\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n \t\tOPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 66bd91fd523d..5831fd019717 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -19,6 +19,7 @@\n #include \"environment.h\"\n #include \"diff.h\"\n #include \"commit.h\"\n+#include \"add-interactive.h\"\n #include \"gettext.h\"\n #include \"revision.h\"\n #include \"wt-status.h\"\n@@ -122,6 +123,7 @@ static const char *edit_message, *use_message;\n static char *fixup_message, *fixup_commit, *squash_message;\n static const char *fixup_prefix;\n static int all, also, interactive, patch_interactive, only, amend, signoff;\n+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n static int edit_flag = -1; /* unspecified */\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int config_commit_verbose = -1; /* unspecified */\n@@ -400,7 +402,7 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n \t\tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n \n-\t\tif (interactive_add(the_repository, argv, prefix, patch_interactive) != 0)\n+\t\tif (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0)\n \t\t\tdie(_(\"interactive add failed\"));\n \n \t\tthe_repository->index_file = old_repo_index_file;\n@@ -424,6 +426,11 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\tcommit_style = COMMIT_NORMAL;\n \t\tret = get_lock_file_path(&index_lock);\n \t\tgoto out;\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--interactive/--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--interactive/--patch\");\n \t}\n \n \t/*\n@@ -1722,6 +1729,12 @@ int cmd_commit(int argc,\n \t\tOPT_BOOL('i', \"include\", &also, N_(\"add specified files to index for commit\")),\n \t\tOPT_BOOL(0, \"interactive\", &interactive, N_(\"interactively add files\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"interactively add changes\")),\n+\t\tOPT_INTEGER_F('U', \"unified\", &add_p_opt.context,\n+\t\t\t      N_(\"generate diffs with <n> lines context, implies --interactive/--patch\"),\n+\t\t\t      PARSE_OPT_NONEG),\n+\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &add_p_opt.interhunkcontext,\n+\t\t\t      N_(\"show context between diff hunks up to the specified number of lines, implies --interactive/--patch\"),\n+\t\t\t      PARSE_OPT_NONEG),\n \t\tOPT_BOOL('o', \"only\", &only, N_(\"commit only specified files\")),\n \t\tOPT_BOOL('n', \"no-verify\", &no_verify, N_(\"bypass pre-commit and commit-msg hooks\")),\n \t\tOPT_BOOL(0, \"dry-run\", &dry_run, N_(\"show what would be committed\")),\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 73b4537a9a56..ee39d6277381 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -346,6 +346,7 @@ int cmd_reset(int argc,\n \tstruct object_id oid;\n \tstruct pathspec pathspec;\n \tint intent_to_add = 0;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tconst struct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"no-refresh\", &no_refresh,\n@@ -370,6 +371,12 @@ int cmd_reset(int argc,\n \t\t\t       PARSE_OPT_OPTARG,\n \t\t\t       option_parse_recurse_submodules_worktree_updater),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode, N_(\"select hunks interactively\")),\n+\t\tOPT_INTEGER_F('U', \"unified\", &add_p_opt.context,\n+\t\t\t       N_(\"generate diffs with <n> lines context, implies --patch\"),\n+\t\t\t       PARSE_OPT_NONEG),\n+\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &add_p_opt.interhunkcontext,\n+\t\t\t       N_(\"show context between diff hunks up to the specified number of lines, implies --patch\"),\n+\t\t\t       PARSE_OPT_NONEG),\n \t\tOPT_BOOL('N', \"intent-to-add\", &intent_to_add,\n \t\t\t\tN_(\"record only the fact that removed paths will be added later\")),\n \t\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n@@ -424,9 +431,14 @@ int cmd_reset(int argc,\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--patch\", \"--{hard,mixed,soft}\");\n \t\ttrace2_cmd_mode(\"patch-interactive\");\n-\t\tupdate_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev,\n-\t\t\t\t   &pathspec);\n+\t\tupdate_ref_status = !!run_add_p(the_repository, ADD_P_RESET,\n+\t\t\t\t\t\t&add_p_opt, rev, &pathspec);\n \t\tgoto cleanup;\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n \t}\n \n \t/* git reset tree [--] paths... can be used to\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex cfbd92852a65..dc82bc34b762 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1242,7 +1242,8 @@ done:\n }\n \n static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n-\t\t       struct strbuf *out_patch, int quiet)\n+\t\t       struct strbuf *out_patch, int quiet,\n+\t\t       struct add_p_opt *add_p_opt)\n {\n \tint ret = 0;\n \tstruct child_process cp_read_tree = CHILD_PROCESS_INIT;\n@@ -1267,7 +1268,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n \told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n \tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n \n-\tret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps);\n+\tret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps);\n \n \tthe_repository->index_file = old_repo_index_file;\n \tif (old_index_env && *old_index_env)\n@@ -1362,8 +1363,8 @@ done:\n }\n \n static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,\n-\t\t\t   int include_untracked, int patch_mode, int only_staged,\n-\t\t\t   struct stash_info *info, struct strbuf *patch,\n+\t\t\t   int include_untracked, int patch_mode, struct add_p_opt *add_p_opt,\n+\t\t\t   int only_staged, struct stash_info *info, struct strbuf *patch,\n \t\t\t   int quiet)\n {\n \tint ret = 0;\n@@ -1439,7 +1440,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\tuntracked_commit_option = 1;\n \t}\n \tif (patch_mode) {\n-\t\tret = stash_patch(info, ps, patch, quiet);\n+\t\tret = stash_patch(info, ps, patch, quiet, add_p_opt);\n \t\tif (ret < 0) {\n \t\t\tif (!quiet)\n \t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n@@ -1513,7 +1514,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n \tif (!check_changes_tracked_files(&ps))\n \t\treturn 0;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n+\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,\n \t\t\t      NULL, 0);\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n@@ -1524,7 +1525,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n }\n \n static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,\n-\t\t\t int keep_index, int patch_mode, int include_untracked, int only_staged)\n+\t\t\t int keep_index, int patch_mode, struct add_p_opt *add_p_opt,\n+\t\t\t int include_untracked, int only_staged)\n {\n \tint ret = 0;\n \tstruct stash_info info = STASH_INFO_INIT;\n@@ -1594,8 +1596,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \n \tif (stash_msg)\n \t\tstrbuf_addstr(&stash_msg_buf, stash_msg);\n-\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,\n-\t\t\t    &info, &patch, quiet)) {\n+\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,\n+\t\t\t    add_p_opt, only_staged, &info, &patch, quiet)) {\n \t\tret = -1;\n \t\tgoto done;\n \t}\n@@ -1768,6 +1770,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \tconst char *stash_msg = NULL;\n \tchar *pathspec_from_file = NULL;\n \tstruct pathspec ps;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n@@ -1775,6 +1778,12 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n+\t\tOPT_INTEGER_F('U', \"unified\", &add_p_opt.context,\n+\t\t\t      N_(\"generate diffs with <n> lines context, implies --patch\"),\n+\t\t\t      PARSE_OPT_NONEG),\n+\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &add_p_opt.interhunkcontext,\n+\t\t\t      N_(\"show context between diff hunks up to the specified number of lines, implies --patch\"),\n+\t\t\t      PARSE_OPT_NONEG),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n \t\t\t N_(\"include untracked files in stash\")),\n@@ -1826,8 +1835,15 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--pathspec-file-nul\", \"--pathspec-from-file\");\n \t}\n \n+\tif (!patch_mode) {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,\n-\t\t\t    include_untracked, only_staged);\n+\t\t\t    &add_p_opt, include_untracked, only_staged);\n \n \tclear_pathspec(&ps);\n \tfree(pathspec_from_file);\n@@ -1852,6 +1868,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \tconst char *stash_msg = NULL;\n \tstruct pathspec ps;\n \tstruct strbuf stash_msg_buf = STRBUF_INIT;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n@@ -1859,6 +1876,12 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n+\t\tOPT_INTEGER_F('U', \"unified\", &add_p_opt.context,\n+\t\t\t      N_(\"generate diffs with <n> lines context, implies --patch\"),\n+\t\t\t      PARSE_OPT_NONEG),\n+\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &add_p_opt.interhunkcontext,\n+\t\t\t      N_(\"show context between diff hunks up to the specified number of lines, implies --patch\"),\n+\t\t\t      PARSE_OPT_NONEG),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n \t\t\t N_(\"include untracked files in stash\")),\n@@ -1877,8 +1900,17 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \t\tstash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');\n \n \tmemset(&ps, 0, sizeof(ps));\n+\n+\tif (!patch_mode) {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index,\n-\t\t\t    patch_mode, include_untracked, only_staged);\n+\t\t\t    patch_mode, &add_p_opt, include_untracked,\n+\t\t\t    only_staged);\n \n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\ndiff --git a/commit.h b/commit.h\nindex 70c870dae4d4..7a7fedbc2f14 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -2,6 +2,7 @@\n #define COMMIT_H\n \n #include \"object.h\"\n+#include \"add-interactive.h\"\n \n struct signature_check;\n struct strbuf;\n@@ -257,7 +258,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *);\n int interactive_add(struct repository *repo,\n \t\t    const char **argv,\n \t\t    const char *prefix,\n-\t\t    int patch);\n+\t\t    int patch, struct add_p_opt *add_p_opt);\n \n struct commit_extra_header {\n \tstruct commit_extra_header *next;\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex 9c024200ade7..1c1e62b434ec 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -104,6 +104,36 @@ test_expect_success 'The -U option overrides diff.context' '\n \t! grep \"^ firstline\" output\n '\n \n+test_expect_success 'The -U option overrides diff.context for \"add\"' '\n+\tgit config diff.context 8 &&\n+\tgit add -U4 -p >output &&\n+\t! grep \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"commit\"' '\n+\tgit config diff.context 8 &&\n+\t! git commit -U4 -p >output &&\n+\t! grep \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"checkout\"' '\n+\tgit config diff.context 8 &&\n+\tgit checkout -U4 -p >output &&\n+\t! grep \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"stash\"' '\n+\tgit config diff.context 8 &&\n+\t! git stash -U4 -p >output &&\n+\t! grep \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"restore\"' '\n+\tgit config diff.context 8 &&\n+\tgit restore -U4 -p >output &&\n+\t! grep \"^ firstline\" output\n+'\n+\n test_expect_success 'diff.context honored by \"diff\"' '\n \tgit config diff.context 8 &&\n \tgit diff >output &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 343b8cd1912b..6650d33fba69 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2596,6 +2596,8 @@ test_expect_success 'double dash \"git checkout\"' '\n \t--merge Z\n \t--conflict=Z\n \t--patch Z\n+\t--unified=Z\n+\t--inter-hunk-context=Z\n \t--ignore-skip-worktree-bits Z\n \t--ignore-other-worktrees Z\n \t--recurse-submodules Z\n-- \ngitgitgadget\n\n"},{"id":"517238","messageId":"d2dedbd7-6797-4f1d-a0b2-243f9bef57f2@app.fastmail.com","threadId":"63396","inReplyTo":"7700eb173e73bb240852dc1c7ce26f3d1f95d8ca.1746436719.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/3] add-patch: add diff.context command line overrides","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2025-05-05T09:49:57Z","receivedAt":"2025-05-05T09:50:19Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"On Mon, May 5, 2025, at 11:18, Leon Michalak via GitGitGadget wrote:\n> From: Leon Michalak <leonmichalak6@gmail.com>\n>\n> This patch compliments `8b91eef812`, where builtins that accept\n> `--patch` options now respect `diff.context` and `diff.interHunkContext`\n> file configurations.\n\n8b91eef812 is patch 1.  This hash will change once the patches have been\nimported via git-am(1).  So it won’t make sense when these patches land\nas commits.\n\nI think the usual approach is to refer to a previous commit in the\nseries as “in a previous commit we...”.  Or maybe “in the previous\ncommit” for this patch and “two commits ago” for patch 3.\n\nFor commits that are in the stable history (like `master`) the\nconvention is to use:\n\n    git show -s --pretty=reference <commit>\n\nWithout backticks (`).  See SubmittingPatches, commit-reference.\n"},{"id":"517242","messageId":"CAP9jKjHQtC1oCvGEeXVBX4Qfoot9s8YdC5hdufy+WnYT0xcj-g@mail.gmail.com","threadId":"63396","inReplyTo":"CAP9jKjHj7WP91aKA9SE94zYj+naBGLUs99mF3G4BhTGcGjFDUQ@mail.gmail.com","subject":"Re: [PATCH 2/3] add-patch: add diff.context command line overrides","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-05-05T10:11:39Z","receivedAt":"2025-05-05T10:11:52Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"Thanks! Part of me did wonder if the reference would be updated when\nmerging, but I can see now the correct way to approach this.\n\nThis being my first ever contribution, I hope I am at least a little\nforgiven for any mistakes, which of course I'm more than happy to\ncorrect and learn from.\n\nOn Mon, 5 May 2025 at 11:08, Leon Michalak <leonmichalak6@gmail.com> wrote:\n>\n> Thanks! Part of me did wonder if the reference would be updated when merging, but I can see now the correct way to approach this.\n>\n> This being my first ever contribution, I hope I am at least a little forgiven for any mistakes, which of course I can correct :)\n>\n>\n> On Mon, 5 May 2025, 10:50 Kristoffer Haugsbakk, <kristofferhaugsbakk@fastmail.com> wrote:\n>>\n>> On Mon, May 5, 2025, at 11:18, Leon Michalak via GitGitGadget wrote:\n>> > From: Leon Michalak <leonmichalak6@gmail.com>\n>> >\n>> > This patch compliments `8b91eef812`, where builtins that accept\n>> > `--patch` options now respect `diff.context` and `diff.interHunkContext`\n>> > file configurations.\n>>\n>> 8b91eef812 is patch 1.  This hash will change once the patches have been\n>> imported via git-am(1).  So it won’t make sense when these patches land\n>> as commits.\n>>\n>> I think the usual approach is to refer to a previous commit in the\n>> series as “in a previous commit we...”.  Or maybe “in the previous\n>> commit” for this patch and “two commits ago” for patch 3.\n>>\n>> For commits that are in the stable history (like `master`) the\n>> convention is to use:\n>>\n>>     git show -s --pretty=reference <commit>\n>>\n>> Without backticks (`).  See SubmittingPatches, commit-reference.\n"},{"id":"517274","messageId":"CAPig+cTqqe-VmbGeMginenQY+bzuuVCxL1E2HGhgKSqzDwunCw@mail.gmail.com","threadId":"63396","inReplyTo":"8b91eef8120b8f92db953ec983fddce8a442abcc.1746436719.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/3] add-patch: respect diff.context configuration","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-05-05T20:29:54Z","receivedAt":"2025-05-05T20:30:07Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, May 5, 2025 at 5:18 AM Leon Michalak via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> This aims to teach relevant builtins (that take in `--patch`) to respect\n> the user's diff.context and diff.interHunkContext file configurations.\n>\n> Since these are both UI options and `--patch` is designed for the end user,\n> I believe this was previously just an inconsistency, which this patch hopes\n> to address.\n>\n> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n> ---\n> diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\n> @@ -49,7 +49,53 @@ test_expect_success 'diff.context honored by \"log\"' '\n>         ! grep firstline output &&\n>         git config diff.context 8 &&\n>         git log -1 -p >output &&\n> -       grep \"^ firstline\" output\n> +       grep \"^ firstline\" output &&\n> +       git config --unset diff.context\n> +'\n> +\n> +test_expect_success 'diff.context honored by \"add\"' '\n> +       git add -p >output &&\n> +       ! grep firstline output &&\n> +       git config diff.context 8 &&\n> +       git add -p >output &&\n> +       grep \"^ firstline\" output &&\n> +       git config --unset diff.context\n> +'\n\nBe aware that if any command in the &&-chain prior to `git config\n--unset` fails, then `git config --unset` itself will not be executed,\nhence the cleanup won't happen. The way to address this is to instead\nuse `test_config` to configure the value since it will ensure that the\nvalue gets \"unset\" regardless of whether the test succeeds or fails:\n\n   test_config diff.context 8 &&\n\nThe same comment applies to all the new tests.\n"},{"id":"517284","messageId":"CAPig+cQmnAiHo8su6UBaKnZ=UZwgwMbDFA6ewMAfvCRR0RFzbw@mail.gmail.com","threadId":"63396","inReplyTo":"b4b7854f330af7588b12e3361bed40723febddad.1746436719.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/3] add-interactive: add new \"context\" subcommand","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-05-06T00:02:09Z","receivedAt":"2025-05-06T00:08:26Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, May 5, 2025 at 5:19 AM Leon Michalak via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> This teaches `add/commit --interactive` a new \"context\" subcommand, which\n> changes the amount of context lines subsequent subcommands like \"patch\"\n> or \"diff\" generate in their diffs.\n>\n> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n> ---\n> diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\n> @@ -265,14 +265,15 @@ and type return, like this:\n>  ------------\n>      *** Commands ***\n>        1: status       2: update       3: revert       4: add untracked\n> -      5: patch        6: diff         7: quit         8: help\n> +      5: patch        6: diff         7: context      8: quit\n> +      9: help\n>      What now> 1\n\nI'm not a `git add/commit --interactive' user, but I can imagine that\ninserting \"context\" at 7 and bumping \"quit\" and \"help\" to 8 and 9,\nrespectively, is going to play havoc with muscle memory people have\nbuilt up over the years. To make this more friendly for existing\nusers, I'd suggest adding this new command at the end of the list\nwithout changing the existing command numbers.\n\nAlso, looking at this list, I can't help but think that \"context\"\nfeels out of place among the other action-oriented commands. Moreover,\nif --interactive mode grows more configuration/setting-like commands\nin the future, do we really want to keep extending this menu for them?\nSpecifically, I'm wondering if it would instead make sense to\nintroduce a new item \"9: settings\" which takes the user to a\n\"Settings\" submenu from which the number of context lines can be set.\n\n> -The main command loop has 6 subcommands (plus help and quit).\n> +The main command loop has 7 subcommands (plus help and quit).\n\nSince you're touching this anyhow, let's fix this maintenance burden\nonce and for all by writing more it generically, perhaps like this:\n\n   The main command loop has several subcommands (plus help and quit).\n\n> +context::\n> +\n> +  This lets you change the amount of context lines shown in diffs that\n> +  the 'patch' and 'diff' subcommands generate.\n\ns/amount/number/\n\n> diff --git a/add-interactive.c b/add-interactive.c\n> @@ -1061,6 +1118,8 @@ static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED,\n> +       color_fprintf_ln(stdout, s->help_color, \"context       - %s\",\n> +                        _(\"change how many context lines diffs are generated with\"));\n\nPerhaps:\n\n    _(\"change the number of diff context lines\"));\n\n> @@ -1087,6 +1146,16 @@ static void choose_prompt_help(struct add_i_state *s)\n> +static void choose_prompt_help_context(struct add_i_state *s)\n> +{\n> +       color_fprintf_ln(stdout, s->help_color, \"%s\",\n> +                        _(\"Prompt help:\"));\n> +       color_fprintf_ln(stdout, s->help_color, \"<n>        - %s\",\n> +                        _(\"specify new context lines amount\"));\n\nLikewise:\n\n    _(\"change number of diff context lines\"));\n\n> +       color_fprintf_ln(stdout, s->help_color, \"           - %s\",\n> +                        _(\"(empty) finish selecting\"));\n\n\"finish selecting\" looks like a copy/paste error from elsewhere in\nthis source file. Perhaps you meant something like:\n\n    _(\"(empty) don't change number of context lines\"));\n\n> diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\n> @@ -1230,4 +1237,23 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n> +test_expect_success 'change context works' '\n> +       git reset --hard &&\n> +       cat >template <<-\\EOF &&\n> +       firstline\n> +       preline\n> +       TARGET\n> +       postline\n> +       lastline\n> +       EOF\n> +       sed \"/TARGET/d\" >x <template &&\n> +       git update-index --add x &&\n> +       git commit -m initial &&\n> +       sed \"s/TARGET/ADDED/\" >x <template &&\n> +       test_write_lines p 1 | git add -i >output &&\n> +       grep firstline output &&\n> +       test_write_lines c 0 p 1 | git add -i >output &&\n> +       ! grep firstline output\n> +'\n\nThis script does have its share of bare `grep` invocations, but these\ndays we prefer `test_grep`, which also appears often in this script,\nso the following would be more appropriate:\n\n    test_grep firstline output &&\n    ...\n    test_grep ! firstline output\n\nNote the placement of \"!\" when used with `test_grep`.\n"},{"id":"517293","messageId":"CAP9jKjFH_2CmvVAQwfk4KKTjDyMne2Wq9rkVnn+XB0dD5BEOCQ@mail.gmail.com","threadId":"63396","inReplyTo":"CAPig+cQmnAiHo8su6UBaKnZ=UZwgwMbDFA6ewMAfvCRR0RFzbw@mail.gmail.com","subject":"Re: [PATCH 3/3] add-interactive: add new \"context\" subcommand","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-05-06T07:20:48Z","receivedAt":"2025-05-06T07:21:01Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"Valid points, I don't think I have any objections to anything listed.\n\nWould it be recommended to update to test_grep (and test_config from\nprevious message) in the same test files whilst I'm at it?\n\nThanks for the review :)\n\nOn Tue, 6 May 2025 at 01:02, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> On Mon, May 5, 2025 at 5:19 AM Leon Michalak via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > This teaches `add/commit --interactive` a new \"context\" subcommand, which\n> > changes the amount of context lines subsequent subcommands like \"patch\"\n> > or \"diff\" generate in their diffs.\n> >\n> > Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n> > ---\n> > diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\n> > @@ -265,14 +265,15 @@ and type return, like this:\n> >  ------------\n> >      *** Commands ***\n> >        1: status       2: update       3: revert       4: add untracked\n> > -      5: patch        6: diff         7: quit         8: help\n> > +      5: patch        6: diff         7: context      8: quit\n> > +      9: help\n> >      What now> 1\n>\n> I'm not a `git add/commit --interactive' user, but I can imagine that\n> inserting \"context\" at 7 and bumping \"quit\" and \"help\" to 8 and 9,\n> respectively, is going to play havoc with muscle memory people have\n> built up over the years. To make this more friendly for existing\n> users, I'd suggest adding this new command at the end of the list\n> without changing the existing command numbers.\n>\n> Also, looking at this list, I can't help but think that \"context\"\n> feels out of place among the other action-oriented commands. Moreover,\n> if --interactive mode grows more configuration/setting-like commands\n> in the future, do we really want to keep extending this menu for them?\n> Specifically, I'm wondering if it would instead make sense to\n> introduce a new item \"9: settings\" which takes the user to a\n> \"Settings\" submenu from which the number of context lines can be set.\n>\n> > -The main command loop has 6 subcommands (plus help and quit).\n> > +The main command loop has 7 subcommands (plus help and quit).\n>\n> Since you're touching this anyhow, let's fix this maintenance burden\n> once and for all by writing more it generically, perhaps like this:\n>\n>    The main command loop has several subcommands (plus help and quit).\n>\n> > +context::\n> > +\n> > +  This lets you change the amount of context lines shown in diffs that\n> > +  the 'patch' and 'diff' subcommands generate.\n>\n> s/amount/number/\n>\n> > diff --git a/add-interactive.c b/add-interactive.c\n> > @@ -1061,6 +1118,8 @@ static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED,\n> > +       color_fprintf_ln(stdout, s->help_color, \"context       - %s\",\n> > +                        _(\"change how many context lines diffs are generated with\"));\n>\n> Perhaps:\n>\n>     _(\"change the number of diff context lines\"));\n>\n> > @@ -1087,6 +1146,16 @@ static void choose_prompt_help(struct add_i_state *s)\n> > +static void choose_prompt_help_context(struct add_i_state *s)\n> > +{\n> > +       color_fprintf_ln(stdout, s->help_color, \"%s\",\n> > +                        _(\"Prompt help:\"));\n> > +       color_fprintf_ln(stdout, s->help_color, \"<n>        - %s\",\n> > +                        _(\"specify new context lines amount\"));\n>\n> Likewise:\n>\n>     _(\"change number of diff context lines\"));\n>\n> > +       color_fprintf_ln(stdout, s->help_color, \"           - %s\",\n> > +                        _(\"(empty) finish selecting\"));\n>\n> \"finish selecting\" looks like a copy/paste error from elsewhere in\n> this source file. Perhaps you meant something like:\n>\n>     _(\"(empty) don't change number of context lines\"));\n>\n> > diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\n> > @@ -1230,4 +1237,23 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n> > +test_expect_success 'change context works' '\n> > +       git reset --hard &&\n> > +       cat >template <<-\\EOF &&\n> > +       firstline\n> > +       preline\n> > +       TARGET\n> > +       postline\n> > +       lastline\n> > +       EOF\n> > +       sed \"/TARGET/d\" >x <template &&\n> > +       git update-index --add x &&\n> > +       git commit -m initial &&\n> > +       sed \"s/TARGET/ADDED/\" >x <template &&\n> > +       test_write_lines p 1 | git add -i >output &&\n> > +       grep firstline output &&\n> > +       test_write_lines c 0 p 1 | git add -i >output &&\n> > +       ! grep firstline output\n> > +'\n>\n> This script does have its share of bare `grep` invocations, but these\n> days we prefer `test_grep`, which also appears often in this script,\n> so the following would be more appropriate:\n>\n>     test_grep firstline output &&\n>     ...\n>     test_grep ! firstline output\n>\n> Note the placement of \"!\" when used with `test_grep`.\n"},{"id":"517299","messageId":"CAP8UFD00bzcrdLLXwjy5yUrvXpnjTDG6ohMTahgpziRD63udUw@mail.gmail.com","threadId":"63396","inReplyTo":"CAP9jKjFH_2CmvVAQwfk4KKTjDyMne2Wq9rkVnn+XB0dD5BEOCQ@mail.gmail.com","subject":"Re: [PATCH 3/3] add-interactive: add new \"context\" subcommand","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-05-06T08:28:25Z","receivedAt":"2025-05-06T08:28:38Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, May 6, 2025 at 9:37 AM Leon Michalak <leonmichalak6@gmail.com> wrote:\n>\n> Valid points, I don't think I have any objections to anything listed.\n\nPlease don't \"top post\" and reply inline instead (see\nhttps://en.wikipedia.org/wiki/Posting_style) as it's the standard on\nthis list.\n\n> Would it be recommended to update to test_grep (and test_config from\n> previous message) in the same test files whilst I'm at it?\n\nIf you want to replace pre existing \"grep\" commands with \"test_grep\"\nand pre existing `git config` with `test_config`, I would recommend\ndoing it in preparatory patches that go first in your patch series.\n\nThanks!\n"},{"id":"517303","messageId":"c5591cbb-7eb9-4b69-85a1-ae1678983b14@gmail.com","threadId":"63396","inReplyTo":"8b91eef8120b8f92db953ec983fddce8a442abcc.1746436719.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/3] add-patch: respect diff.context configuration","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-05-06T08:55:52Z","receivedAt":"2025-05-06T08:55:55Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Leon\n\nThanks for working on this. I've left a few comments below but for your \nfirst submission this is very good.\n\nOn 05/05/2025 10:18, Leon Michalak via GitGitGadget wrote:\n> From: Leon Michalak <leonmichalak6@gmail.com>\n> \n> This aims to teach relevant builtins (that take in `--patch`) to respect\n> the user's diff.context and diff.interHunkContext file configurations.\n\nThis is a good summary of the intent of the patch. I'd maybe drop \"This \naims\" and instead say that the various commands do not respect the \nconfig and this patch fixes that.\n\n> Since these are both UI options and `--patch` is designed for the end user,\n> I believe this was previously just an inconsistency, which this patch hopes\n> to address.\n> \n> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n> ---\n>   add-interactive.c       | 16 ++++++++++----\n>   add-patch.c             |  6 ++++++\n>   t/t4055-diff-context.sh | 48 ++++++++++++++++++++++++++++++++++++++++-\n>   3 files changed, 65 insertions(+), 5 deletions(-)\n> \n> diff --git a/add-interactive.c b/add-interactive.c\n> index 97ff35b6f12a..ad12dc416598 100644\n> --- a/add-interactive.c\n> +++ b/add-interactive.c\n> @@ -41,6 +41,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n>   \tconst char *value;\n>   \n>   \ts->r = r;\n> +\ts->context = -1;\n> +\ts->interhunkcontext = -1;\n>   \n>   \tif (repo_config_get_value(r, \"color.interactive\", &value))\n>   \t\ts->use_color = -1;\n> @@ -78,6 +80,9 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n>   \trepo_config_get_string(r, \"diff.algorithm\",\n>   \t\t\t       &s->interactive_diff_algorithm);\n>   \n> +\trepo_config_get_int(r, \"diff.context\", &s->context);\n> +\trepo_config_get_int(r, \"diff.interHunkContext\", &s->interhunkcontext);\n\nThis looks good\n\n> @@ -1014,10 +1019,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,\n>   \tif (count > 0) {\n>   \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n>   \n> -\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\",\n> -\t\t\t     oid_to_hex(!is_initial ? &oid :\n> -\t\t\t\t\ts->r->hash_algo->empty_tree),\n> -\t\t\t     \"--\", NULL);\n> +\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\", NULL);\n\nAs we're running \"git diff\" here nothing needs to be changed because it \nreads all of the relevant config variables itself. This is why the \nexisting code does not worry about adding \"--algorithm\".\n\n> diff --git a/add-patch.c b/add-patch.c\n> index 95c67d8c80c4..b43ca1600738 100644\n> --- a/add-patch.c\n> +++ b/add-patch.c\n> @@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n>   {\n>   \tstruct strvec args = STRVEC_INIT;\n>   \tconst char *diff_algorithm = s->s.interactive_diff_algorithm;\n> +\tint diff_context = s->s.context;\n> +\tint diff_interhunkcontext = s->s.interhunkcontext;\n>   \tstruct strbuf *plain = &s->plain, *colored = NULL;\n>   \tstruct child_process cp = CHILD_PROCESS_INIT;\n>   \tchar *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\\0';\n> @@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n>   \tint res;\n>   \n>   \tstrvec_pushv(&args, s->mode->diff_cmd);\n> +\tif (diff_context != -1)\n> +\t\tstrvec_pushf(&args, \"--unified=%i\", diff_context);\n> +\tif (diff_interhunkcontext != -1)\n> +\t\tstrvec_pushf(&args, \"--inter-hunk-context=%i\", diff_interhunkcontext);\n\nThis is good - if the user has set diff.config or diff.interhunkcontext \nthen we pass the appropriate values to \"git diff-index\" or \"git \ndiff-files\" which unlike \"git diff\" do not read those config values \nthemselves.\n\n>   \tif (diff_algorithm)\n>   \t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", diff_algorithm);\n>   \tif (s->revision) {\n> diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\n> index ec2804eea67c..9c024200ade7 100755\n> --- a/t/t4055-diff-context.sh\n> +++ b/t/t4055-diff-context.sh\n\nI'm not sure this is the best home for the new tests. Normally tests \nrelated to the code in add-patch.c go into t3701-add-interactive.sh\n\n> @@ -49,7 +49,53 @@ test_expect_success 'diff.context honored by \"log\"' '\n>   \t! grep firstline output &&\n>   \tgit config diff.context 8 &&\n>   \tgit log -1 -p >output &&\n> -\tgrep \"^ firstline\" output\n> +\tgrep \"^ firstline\" output &&\n> +\tgit config --unset diff.context\n> +'\n> +\n> +test_expect_success 'diff.context honored by \"add\"' '\n> +\tgit add -p >output &&\n> +\t! grep firstline output &&\n> +\tgit config diff.context 8 &&\n> +\tgit add -p >output &&\n> +\tgrep \"^ firstline\" output &&\n> +\tgit config --unset diff.context\n\nEric has already mentioned using \"test_config\", I would go one step \nfurther and say that as we're only running a single command we should \nuse \"git add -c diff.context=* add -p\" to avoid having to change the on \ndisk config at all.\n\nI would also recommend using \"test_grep ...\" rather than \"grep ...\" and \n\"test_grep ! ..\" instead of \"! grep ...\" as they provide useful \ndebugging information if the test fails.\n\nGiven the way the code is structured with the config being read in \nadd-patch.c I'm not sure how much value there is in testing all the \ndifferent \"-p\" commands. It would however be very useful to check that \n\"git -c diff.interhunkcontext=8 add -p\" works as expected.\n\nI'll leave it there for now, I'll try and look at the other patches \nlater today or maybe tomorrow.\n\nBest Wishes\n\nPhillip\n\n> +'\n> +\n> +test_expect_success 'diff.context honored by \"commit\"' '\n> +\t! git commit -p >output &&\n> +\t! grep firstline output &&\n> +\tgit config diff.context 8 &&\n> +\t! git commit -p >output &&\n> +\tgrep \"^ firstline\" output &&\n> +\tgit config --unset diff.context\n> +'\n> +\n> +test_expect_success 'diff.context honored by \"checkout\"' '\n> +\tgit checkout -p >output &&\n> +\t! grep firstline output &&\n> +\tgit config diff.context 8 &&\n> +\tgit checkout -p >output &&\n> +\tgrep \"^ firstline\" output &&\n> +\tgit config --unset diff.context\n> +'\n> +\n> +test_expect_success 'diff.context honored by \"stash\"' '\n> +\t! git stash -p >output &&\n> +\t! grep firstline output &&\n> +\tgit config diff.context 8 &&\n> +\t! git stash -p >output &&\n> +\tgrep \"^ firstline\" output &&\n> +\tgit config --unset diff.context\n> +'\n> +\n> +test_expect_success 'diff.context honored by \"restore\"' '\n> +\tgit restore -p >output &&\n> +\t! grep firstline output &&\n> +\tgit config diff.context 8 &&\n> +\tgit restore -p >output &&\n> +\tgrep \"^ firstline\" output &&\n> +\tgit config --unset diff.context\n>   '\n>   \n>   test_expect_success 'The -U option overrides diff.context' '\n\n"},{"id":"517357","messageId":"xmqqldr9lmpg.fsf@gitster.g","threadId":"63396","inReplyTo":"CAPig+cQmnAiHo8su6UBaKnZ=UZwgwMbDFA6ewMAfvCRR0RFzbw@mail.gmail.com","subject":"Re: [PATCH 3/3] add-interactive: add new \"context\" subcommand","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-06T16:37:31Z","receivedAt":"2025-05-06T16:37:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> On Mon, May 5, 2025 at 5:19 AM Leon Michalak via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>> This teaches `add/commit --interactive` a new \"context\" subcommand, which\n>> changes the amount of context lines subsequent subcommands like \"patch\"\n>> or \"diff\" generate in their diffs.\n>>\n>> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n>> ---\n>> diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\n>> @@ -265,14 +265,15 @@ and type return, like this:\n>>  ------------\n>>      *** Commands ***\n>>        1: status       2: update       3: revert       4: add untracked\n>> -      5: patch        6: diff         7: quit         8: help\n>> +      5: patch        6: diff         7: context      8: quit\n>> +      9: help\n>>      What now> 1\n>\n> I'm not a `git add/commit --interactive' user, but I can imagine that\n> inserting \"context\" at 7 and bumping \"quit\" and \"help\" to 8 and 9,\n> respectively, is going to play havoc with muscle memory people have\n> built up over the years. To make this more friendly for existing\n> users, I'd suggest adding this new command at the end of the list\n> without changing the existing command numbers.\n\nHmph, in the real UI, the first letters of these commands (which\nhave deliberately been chosen to be unique) are shown in different\ncolor to hint that the answer to the prompt can be like 's' (for\nstatus).  I would hate to see that quit/help, which are somewhat\nspecial, move from the very end position.\n\nPerhaps the example of the documentation page shown above should\nrespond with 's', not '1' to show more prominently that it is\npossible and encouraged?  I dunno.\n\n> Also, looking at this list, I can't help but think that \"context\"\n> feels out of place among the other action-oriented commands. Moreover,\n> if --interactive mode grows more configuration/setting-like commands\n> in the future, do we really want to keep extending this menu for them?\n\nThat is a valid concern.\n\n> Specifically, I'm wondering if it would instead make sense to\n> introduce a new item \"9: settings\" which takes the user to a\n> \"Settings\" submenu from which the number of context lines can be set.\n\n's' is taken for 'status', so it shouldn't be '9: settings' (it\nshould come before 'quit' instead), but I think I know where you are\ngoing.\n\nAn alternative that may work better is to allow subcommand specific\nparameters in the answer to the \"What now>\" prompt, e.g.\n\n\tWhat now> r -U7\n\nto choose an equivalent to \"git revert -p -U7\".\n\nThanks.\n"},{"id":"517364","messageId":"CAP9jKjE6RMY9Me0bZVHdz6S7VseRY37JBG3jqUKkzNhD0hLimg@mail.gmail.com","threadId":"63396","inReplyTo":"CAP8UFD00bzcrdLLXwjy5yUrvXpnjTDG6ohMTahgpziRD63udUw@mail.gmail.com","subject":"Re: [PATCH 3/3] add-interactive: add new \"context\" subcommand","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-05-06T17:07:25Z","receivedAt":"2025-05-06T17:07:38Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"On Tue, 6 May 2025 at 09:28, Christian Couder\n<christian.couder@gmail.com> wrote:\n> Please don't \"top post\" and reply inline instead (see\n> https://en.wikipedia.org/wiki/Posting_style) as it's the standard on\n> this list.\n\nThanks, I rarely use email like this so overlooked this aspect. I hope\nthis email is better? If not let me know.\n\n> If you want to replace pre existing \"grep\" commands with \"test_grep\"\n> and pre existing `git config` with `test_config`, I would recommend\n> doing it in preparatory patches that go first in your patch series.\n\nYes that makes sense :)\n"},{"id":"517368","messageId":"CAP9jKjF2y48Zo6wSFPBN9OZy+iOhwmWPThVEt0o+9A0CASHXHA@mail.gmail.com","threadId":"63396","inReplyTo":"xmqqldr9lmpg.fsf@gitster.g","subject":"Re: [PATCH 3/3] add-interactive: add new \"context\" subcommand","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-05-06T17:25:45Z","receivedAt":"2025-05-06T17:25:59Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"On Tue, 6 May 2025 at 17:37, Junio C Hamano <gitster@pobox.com> wrote:\n> Hmph, in the real UI, the first letters of these commands (which\n> have deliberately been chosen to be unique) are shown in different\n> color to hint that the answer to the prompt can be like 's' (for\n> status).  I would hate to see that quit/help, which are somewhat\n> special, move from the very end position.\n\nThis is the main reason I chose to put the new subcommand before as I\nalso feel quit/help are better suited to be last, although I do see\nthe argument against muscle memory and is valid.\n\n> Perhaps the example of the documentation page shown above should\n> respond with 's', not '1' to show more prominently that it is\n> possible and encouraged?  I dunno.\n\nWriting the first letters as opposed to the numbers is how I\npersonally use this, just to add another perspective.\n\nWould \"config\" as opposed to \"settings\" fix the unique lettering\nproblem? Or perhaps config is not the best description for what it\nwould do (I sometimes find myself using config/settings\ninterchangeably but the majority may not...)?\n\nOverall I feel the first two patches cover my use case the most so I\nam also not opposed to completely dropping the last patch about the\n`--interactive` context subcommand. Inherited diff context and command\nline overrides I feel are a great base to begin with either way. Just\nsome thoughts :)\n"},{"id":"517369","messageId":"xmqqzffpk5q9.fsf@gitster.g","threadId":"63396","inReplyTo":"8b91eef8120b8f92db953ec983fddce8a442abcc.1746436719.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/3] add-patch: respect diff.context configuration","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-06T17:29:34Z","receivedAt":"2025-05-06T17:29:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Leon Michalak <leonmichalak6@gmail.com>\n>\n> This aims to teach relevant builtins (that take in `--patch`) to respect\n> the user's diff.context and diff.interHunkContext file configurations.\n>\n> Since these are both UI options and `--patch` is designed for the end user,\n> I believe this was previously just an inconsistency, which this patch hopes\n> to address.\n\nThe usual way to compose a log message of this project is to\n\n - Give an observation on how the current system works in the present\n   tense (so no need to say \"Currently X is Y\", just \"X is Y\"), and\n   discuss what you perceive as a problem in it.\n\n - Propose a solution (optional---often, problem description\n   trivially leads to an obvious solution in reader's minds).\n\n - Give commands to the codebase to \"become like so\".\n\nin this order.  \"Various built-in commands that use add-patch\ninfrastructure do not honor diff.context and diff.interHunkContext\nconfiguration variables.\"  Would be the first sentence to explain\nthe current situation.  Follow that sentence with an explanation why\nit is a bad thing (i.e. it is possible that \"do not honor\" may be a\ndeliberate design decision---perhaps the implementation of \"add -p\"\nis impossible if it has to honor diff.context that is set to 0; show\nevidence that no such deliberate design decision was there when the\nfeature was introduced from the list archive, and say that not\nhonoring the configuration is an inconvenient inconsistency).\n\nSuch an explanation would be sufficient for readers to guess the\n\"solution\", so stopping there should be fine.\n\nThanks.\n\n> @@ -1014,10 +1019,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,\n>  \tif (count > 0) {\n>  \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n>  \n> -\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\",\n> -\t\t\t     oid_to_hex(!is_initial ? &oid :\n> -\t\t\t\t\ts->r->hash_algo->empty_tree),\n> -\t\t\t     \"--\", NULL);\n> +\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\", NULL);\n> +\t\tif (s->context != -1)\n> +\t\t\tstrvec_pushf(&cmd.args, \"--unified=%i\", s->context);\n> +\t\tif (s->interhunkcontext != -1)\n> +\t\t\tstrvec_pushf(&cmd.args, \"--inter-hunk-context=%i\", s->interhunkcontext);\n> +\t\tstrvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :\n> +\t\t\t     s->r->hash_algo->empty_tree), \"--\", NULL);\n\nOK.\n\n> +test_expect_success 'diff.context honored by \"add\"' '\n> +\tgit add -p >output &&\n> +\t! grep firstline output &&\n\n\ttest_grep ! firstline output\n\n?\n\n> +\tgit config diff.context 8 &&\n> +\tgit add -p >output &&\n> +\tgrep \"^ firstline\" output &&\n\nLikewise.  \n\n\ttest_grep \"^firstline\" output\n\n?\n\n> +\tgit config --unset diff.context\n\nNot like this.  Either use test_when_finished to arrange that this\nunset is run when you leave, or use test_config.\n\n\ttest_expect_success title '\n\t\ttest_config diff.context 8 &&\n\t\tdo your tests\n\t'\n\nwill reset diff.conftext when the above piece is done.\n\n\ttest_expect_success title '\n\t\ttest_when_finished \"git config unset diff.context\" &&\n\t\tgit config set diff.context 8 &&\n\t\tdo your tests\n\t'\n\nis an equivalent.\n"},{"id":"517468","messageId":"61fe7690-87af-4159-be87-cd39c09475fe@gmail.com","threadId":"63396","inReplyTo":"7700eb173e73bb240852dc1c7ce26f3d1f95d8ca.1746436719.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/3] add-patch: add diff.context command line overrides","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-05-07T09:51:13Z","receivedAt":"2025-05-07T09:51:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Leon\n\nOn 05/05/2025 10:18, Leon Michalak via GitGitGadget wrote:\n> From: Leon Michalak <leonmichalak6@gmail.com>\n> \n> This patch compliments `8b91eef812`, where builtins that accept\n> `--patch` options now respect `diff.context` and `diff.interHunkContext`\n> file configurations.\n> \n> In particular, this patch helps users who don't want to set persistent\n> context configurations or just want a way to override them on a one-time\n> basis, by allowing the relevant builtins to accept corresponding command\n> line options that override the file configurations.\n> \n> This mimics `diff` which allows for both context file configuration and\n> command line overrides.\n> \n> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n\nI'm somewhat torn about the name \"--unified\" as I don't think it is \nparticularly informative - it only makes sense if one knows the diff \noption and we only support a single diff format. Having said that it is \nprobably better to reuse the name from \"git diff\" for consistency.\n\n> +`-U<n>`::\n> +`--unified=<n>`::\n> +\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n> +\tor 3 if the config option is unset. Implies `--interactive/--patch`.\n> +\n> +`--inter-hunk-context=<n>`::\n> +\tShow the context between diff hunks, up to the specified _<number>_\n> +\tof lines, thereby fusing hunks that are close to each other.\n> +\tDefaults to `diff.interHunkContext` or 0 if the config option\n> +\tis unset. Implies `--interactive/--patch`.\n\nThis documentation is repeated for each command. I think it would be \nbetter to put this in separate file that is then included where it is \nneeded. That way if we need to update the documentation in the future we \nonly have one copy to worry about. The syntax to include a file called \ndiff-context-options.adoc is\n\ninclude::diff-context-options.adoc[]\n\n> --- a/add-interactive.c\n> +++ b/add-interactive.c\n> @@ -86,6 +87,11 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n>   \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n>   \tif (s->use_single_key)\n>   \t\tsetbuf(stdin, NULL);\n> +\n> +\tif (add_p_opt->context != -1)\n> +\t\ts->context = add_p_opt->context;\n> +\tif (add_p_opt->interhunkcontext != -1)\n> +\t\ts->interhunkcontext = add_p_opt->interhunkcontext;\n\nThis happens after we read the config so the command line option wins - \ngood.\n\n> index 693f125e8e4b..653f07a917b8 100644\n> --- a/add-interactive.h\n> +++ b/add-interactive.h\n> @@ -3,6 +3,13 @@\n>   \n>   #include \"color.h\"\n>   \n> +#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }\n\nI think we normally define initializer macros after the struct they \ninitialize so the reader can more easily check the fields are all \ninitialized correctly\n\n> +struct add_p_opt {\n> +\tint context;\n> +\tint interhunkcontext;\n> +};\n> +\n>   struct add_i_state {\n>   \tstruct repository *r;\n>   \tint use_color;\n> @@ -18,14 +25,17 @@ struct add_i_state {\n>   \n>   \tint use_single_key;\n>   \tchar *interactive_diff_filter, *interactive_diff_algorithm;\n> +\tint context, interhunkcontext;\n\nShould this be in the previous patch? It is a good idea to run\n\n     git rebase --keep-base --exec 'make && cd t && prove -j8 \ntests-that-you-think-might-fail :: --root=/dev/shm'\n\nbefore submitting a patch series so that you can be sure the patches all \ncompile and the tests pass. Running the whole test suite can be pretty \nslow so just run the tests that are relevant to your changes.\n\n> @@ -169,9 +170,10 @@ int interactive_add(struct repository *repo,\n>   \t\t       prefix, argv);\n>   \n>   \tif (patch)\n> -\t\tret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);\n> +\t\tret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL,\n> +\t\t\t\t  &pathspec);\n\nThis line could be unwraped and still fit within 80 columns I think.\n\n>   \telse\n> -\t\tret = !!run_add_i(repo, &pathspec);\n> +\t\tret = !!run_add_i(repo, &pathspec, add_p_opt);\n>   \n>   \tclear_pathspec(&pathspec);\n>   \treturn ret;\n> @@ -253,6 +255,12 @@ static struct option builtin_add_options[] = {\n>   \tOPT_GROUP(\"\"),\n>   \tOPT_BOOL('i', \"interactive\", &add_interactive, N_(\"interactive picking\")),\n>   \tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"select hunks interactively\")),\n> +\tOPT_INTEGER_F('U', \"unified\", &add_p_opt.context,\n> +\t\t      N_(\"generate diffs with <n> lines context, implies --interactive/--patch\"),\n\nIt cannot imply both --interactive and --patch as they are two different \noperations. I think it should imply --patch because that option is \nsupported by all the commands we're adding -U to.\n\n> +\t\t      PARSE_OPT_NONEG),\n> +\tOPT_INTEGER_F(0, \"inter-hunk-context\", &add_p_opt.interhunkcontext,\n> +\t\t      N_(\"show context between diff hunks up to the specified number of lines, implies --interactive/--patch\"),\n> +\t\t      PARSE_OPT_NONEG),\n\nAs these two options are duplicated in many commands we should define a \npreprocessor macro for them in parse-options.h. If you look at the end \nof that file there are a bunch of similar definitions.\n\n>   \tOPT_BOOL('e', \"edit\", &edit_interactive, N_(\"edit current diff and apply\")),\n>   \tOPT__FORCE(&ignored_too, N_(\"allow adding otherwise ignored files\"), 0),\n>   \tOPT_BOOL('u', \"update\", &take_worktree_changes, N_(\"update tracked files\")),\n> @@ -398,7 +406,12 @@ int cmd_add(int argc,\n>   \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--dry-run\", \"--interactive/--patch\");\n>   \t\tif (pathspec_from_file)\n>   \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--pathspec-from-file\", \"--interactive/--patch\");\n> -\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive));\n> +\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));\n> +\t} else {\n> +\t\tif (add_p_opt.context != -1)\n> +\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--interactive/--patch\");\n> +\t\tif (add_p_opt.interhunkcontext != -1)\n> +\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--interactive/--patch\");\n\nI don't understand this - I thought the help for --unified said it \nimplied --patch but here it is erroring out if --patch is not given.\n\nSomewhere we should check that the values given for the context and \ninterhunk context are non-negative. git_diff_ui_config() errors out if \nthe context is negative and we should do the same in add-patch.c \notherwise we'll get some wierd error about \"git diff-index\" failing. We \nshould also think about -U0 as \"git apply\" rejects hunks without any \ncontext unless by default and I dont think \"add -p\" passes the option to \nenable that and I'm not sure it should.\n\n> +test_expect_success 'The -U option overrides diff.context for \"add\"' '\n> +\tgit config diff.context 8 &&\n> +\tgit add -U4 -p >output &&\n> +\t! grep \"^ firstline\" output\n> +'\n\nIt is great that you've added tests and I do think we should test all \nthe commands here as unlike the previous patch there isn't a common code \npath. My other comments about the tests in the previous patch apply here \nthough I think.\n\nWe should have a test to check negative context values and possibly zero \nare rejected.\n\nThanks\n\nPhillip\n\n"},{"id":"517490","messageId":"a290bc6f-9198-4a00-8273-2970cfbd4a29@gmail.com","threadId":"63396","inReplyTo":"xmqqldr9lmpg.fsf@gitster.g","subject":"Re: [PATCH 3/3] add-interactive: add new \"context\" subcommand","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-05-07T13:30:42Z","receivedAt":"2025-05-07T13:30:45Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 06/05/2025 17:37, Junio C Hamano wrote:\n> \n> An alternative that may work better is to allow subcommand specific\n> parameters in the answer to the \"What now>\" prompt, e.g.\n> \n> \tWhat now> r -U7\n> \n> to choose an equivalent to \"git revert -p -U7\".\n\nI think the best solution would be to allow users to re-display the \ncurrent hunk with more context inside \"add -p\" rather than changing \"add \n-i\". That way a user who needs more context to make a decision on the \ncurrent hunk can get it without restarting the whole process of \nselecting hunks [1]. If the user knows up front that they want a certain \namount of context they can use \"git add -p -U <context>\" or \"git add -i \n-U <context>\" which was added in the preceding patch.\n\nBest Wishes\n\nPhillip\n\n[1] With \"git add -p\" they can quit and carry on from where they left \noff but \"git stash -p\" doesn't let you amend an existing stash and git \nrefuses to pop a stash if the working tree is dirty. This means if a \nstash is split in two because the user exited to change the context they \ncannot pop both stashes on top of each other.\n"},{"id":"517510","messageId":"xmqq8qn8cn0q.fsf@gitster.g","threadId":"63396","inReplyTo":"61fe7690-87af-4159-be87-cd39c09475fe@gmail.com","subject":"Re: [PATCH 2/3] add-patch: add diff.context command line overrides","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-07T18:07:49Z","receivedAt":"2025-05-07T18:07:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>\n>> +`-U<n>`::\n>> +`--unified=<n>`::\n>> +\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n>> +\tor 3 if the config option is unset. Implies `--interactive/--patch`.\n>> +\n>> +`--inter-hunk-context=<n>`::\n>> +\tShow the context between diff hunks, up to the specified _<number>_\n>> +\tof lines, thereby fusing hunks that are close to each other.\n>> +\tDefaults to `diff.interHunkContext` or 0 if the config option\n>> +\tis unset. Implies `--interactive/--patch`.\n>\n> This documentation is repeated for each command. I think it would be\n> better to put this in separate file that is then included where it is\n> needed. That way if we need to update the documentation in the future\n> we only have one copy to worry about. The syntax to include a file\n> called diff-context-options.adoc is\n>\n> include::diff-context-options.adoc[]\n\nExcellent suggestion.\n\nI however think \"-U implies -i/-p\" makes no sense at all.  What if\nthe user said \"git add -U6\"?  Do we run \"git add -i -U6\" or go\ndirectly to \"git add -p -U6\"?\n\nI think it is better to encourage the user to be more explicit to\nrequire them to say \"git revert -U6 -p\" or \"git add -U6 -i\".\n\nIf -U<n> is given and there is no \"-i\" (for \"add\" only) or \"-p\" (for\neverything), just error out *until* other modes of these commands\nfind a good use of context width information, at which point we can\nuse the context length information to drive these commands in their\nnon-\"patch\" mode.\n\nEven if we cannot think of a way to use the diff output (and with\nconfigurable context length) in modes that are not \"--patch\" for\nthese commands RIGHT NOW, that does not mean -U<n> would never\nbecome useful in these modes for the commands.  Let's not close the\ndoor prematurely from our future developers by making -U<n> imply\nanything, and make it an error to use -U<N> without -p/-i for now.\n"},{"id":"517511","messageId":"CAP9jKjGEDY78MSrZVS2FxAoikPZpGazqem2XHDVmXqMjcox6yg@mail.gmail.com","threadId":"63396","inReplyTo":"xmqq8qn8cn0q.fsf@gitster.g","subject":"Re: [PATCH 2/3] add-patch: add diff.context command line overrides","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-05-07T18:28:54Z","receivedAt":"2025-05-07T18:29:07Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"I think I may be misunderstanding, so I'll elaborate on what I\npersonally intended my documentation to mean.\n\nWhen I put in the documentation \"implies --interactive/--patch\" it\nreads to me as \"this assumes you are also using either --interactive\nor --patch and that if you don't specify one or the other it will do\nnothing or possibly error (which is what I chose in the end, based on\nthe initial discussion in a separate thread)\". I didn't think it would\nread as \"you must have both settings\" or \"if you don't specify these\nthe command will assume it as if you had and effectively act as if you\nhad\".\n\nI'm not sure if the wording was confusing or it generally has\ndifferent meanings to others so perhaps that might clarify at least\nwhat I intended :)\n"},{"id":"517513","messageId":"xmqqzffob5ji.fsf@gitster.g","threadId":"63396","inReplyTo":"a290bc6f-9198-4a00-8273-2970cfbd4a29@gmail.com","subject":"Re: [PATCH 3/3] add-interactive: add new \"context\" subcommand","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-07T19:10:41Z","receivedAt":"2025-05-07T19:10:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 06/05/2025 17:37, Junio C Hamano wrote:\n>> An alternative that may work better is to allow subcommand specific\n>> parameters in the answer to the \"What now>\" prompt, e.g.\n>> \tWhat now> r -U7\n>> to choose an equivalent to \"git revert -p -U7\".\n>\n> I think the best solution would be to allow users to re-display the\n> current hunk with more context inside \"add -p\" rather than changing\n> \"add -i\". That way a user who needs more context to make a decision on\n> the current hunk can get it without restarting the whole process of\n> selecting hunks [1]. If the user knows up front that they want a\n> certain amount of context they can use \"git add -p -U <context>\" or\n> \"git add -i -U <context>\" which was added in the preceding patch.\n\nYes, but notice that the above is about interactive mode \"add -i\"\nthat is setting the diff.context to 7 for subsequent \"patch\"\ncommand, not against \"Stage this hunk [y,n,q,...]?\" prompt inside\n\"add -p\".  Once you go into the \"--patch\" mode, especially after you\nlet the user to edit some hunks, \"redisplay this hunk with wider\ncontext\" is an impossible operation with the current code structure,\nI think, as the implementation does not keep track of information\nthat are needed to do so.\n\nSo, this is like \"restarting 'add -p' with -U7\", but done from the\n\"add -i\" session.\n"},{"id":"517517","messageId":"xmqqo6w4b223.fsf@gitster.g","threadId":"63396","inReplyTo":"CAP9jKjGEDY78MSrZVS2FxAoikPZpGazqem2XHDVmXqMjcox6yg@mail.gmail.com","subject":"Re: [PATCH 2/3] add-patch: add diff.context command line overrides","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-07T20:25:56Z","receivedAt":"2025-05-07T20:25:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Leon Michalak <leonmichalak6@gmail.com> writes:\n\n> I think I may be misunderstanding, so I'll elaborate on what I\n> personally intended my documentation to mean.\n>\n> When I put in the documentation \"implies --interactive/--patch\" it\n> reads to me as \"this assumes you are also using either --interactive\n> or --patch and that if you don't specify one or the other it will do\n> nothing or possibly error (which is what I chose in the end, based on\n> the initial discussion in a separate thread)\". I didn't think it would\n> read as \"you must have both settings\" or \"if you don't specify these\n> the command will assume it as if you had and effectively act as if you\n> had\".\n>\n> I'm not sure if the wording was confusing or it generally has\n> different meanings to others so perhaps that might clarify at least\n> what I intended :)\n\nWe use \"imply\" to mean quite a different thing from what you said in\nthe above explanation, and that is where my reaction came from.\n\nWe say \"option A implies option B\" only when the command behaves as\nif the user gave option B when only option A is given (and without\ngiving B).  For example, \"git commit --help\" has\n\n        `--short`::\n                When doing a dry-run, give the output in the short-format. See\n                linkgit:git-status[1] for details. Implies `--dry-run`.\n\nThe `--short` option does not make any sense when you are actually\ncreating a commit; if you say \"git commit --short\", the command\nbehaves as if you said \"git commit --dry-run --short\".\n\nIf something works only under certain condition, it seems we tend to\nsay \"this only works when/with ...\", so in this case, we would have\nsaid \"The -U<n> option is only effective for --interactive or --patch\nmode of this command\" to express what you meant to convey, I think.\n\nThanks.\n\n"},{"id":"517735","messageId":"4f92a1b4c24a6942fc55d305865bb55833d13c45.1746884789.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v2.git.1746884789.gitgitgadget@gmail.com","subject":"[PATCH v2 1/4] test: refactor to use \"test_grep\"","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-05-10T13:46:26Z","receivedAt":"2025-05-10T13:46:34Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nRefactor to use the modern \"test_grep\" test utility instead of regular\n\"grep\" which provides better debug information if tests fail.\n\nThis is a prerequisite to the commits that follow which add to both test\nfiles.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n t/t3701-add-interactive.sh | 48 +++++++++++++++++++-------------------\n t/t4055-diff-context.sh    | 28 +++++++++++-----------\n 2 files changed, 38 insertions(+), 38 deletions(-)\n\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex b8a05d95f3f1..b088ee141ff4 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -63,7 +63,7 @@ test_expect_success 'setup (initial)' '\n '\n test_expect_success 'status works (initial)' '\n \tgit add -i </dev/null >output &&\n-\tgrep \"+1/-0 *+2/-0 file\" output\n+\ttest_grep \"+1/-0 *+2/-0 file\" output\n '\n \n test_expect_success 'setup expected' '\n@@ -86,7 +86,7 @@ test_expect_success 'revert works (initial)' '\n \tgit add file &&\n \ttest_write_lines r 1 | git add -i &&\n \tgit ls-files >output &&\n-\t! grep . output\n+\ttest_grep ! . output\n '\n \n test_expect_success 'add untracked (multiple)' '\n@@ -109,7 +109,7 @@ test_expect_success 'setup (commit)' '\n '\n test_expect_success 'status works (commit)' '\n \tgit add -i </dev/null >output &&\n-\tgrep \"+1/-0 *+2/-0 file\" output\n+\ttest_grep \"+1/-0 *+2/-0 file\" output\n '\n \n test_expect_success 'update can stage deletions' '\n@@ -141,7 +141,7 @@ test_expect_success 'revert works (commit)' '\n \tgit add file &&\n \ttest_write_lines r 1 | git add -i &&\n \tgit add -i </dev/null >output &&\n-\tgrep \"unchanged *+3/-0 file\" output\n+\ttest_grep \"unchanged *+3/-0 file\" output\n '\n \n test_expect_success 'reject multi-key input' '\n@@ -185,7 +185,7 @@ test_expect_success 'setup fake editor' '\n test_expect_success 'bad edit rejected' '\n \tgit reset &&\n \ttest_write_lines e n d | git add -p >output &&\n-\tgrep \"hunk does not apply\" output\n+\ttest_grep \"hunk does not apply\" output\n '\n \n test_expect_success 'setup patch' '\n@@ -198,7 +198,7 @@ test_expect_success 'setup patch' '\n test_expect_success 'garbage edit rejected' '\n \tgit reset &&\n \ttest_write_lines e n d | git add -p >output &&\n-\tgrep \"hunk does not apply\" output\n+\ttest_grep \"hunk does not apply\" output\n '\n \n test_expect_success 'setup patch' '\n@@ -313,8 +313,8 @@ test_expect_success FILEMODE 'stage mode and hunk' '\n \tchmod +x file &&\n \tprintf \"y\\\\ny\\\\n\" | git add -p &&\n \tgit diff --cached file >out &&\n-\tgrep \"new mode\" out &&\n-\tgrep \"+content\" out &&\n+\ttest_grep \"new mode\" out &&\n+\ttest_grep \"+content\" out &&\n \tgit diff file >out &&\n \ttest_must_be_empty out\n '\n@@ -636,7 +636,7 @@ test_expect_success 'split hunk \"add -p (edit)\"' '\n \tprintf \"%s\\n\" s e     q n q q |\n \tEDITOR=: git add -p &&\n \tgit diff >actual &&\n-\t! grep \"^+15\" actual\n+\ttest_grep ! \"^+15\" actual\n '\n \n test_expect_success 'split hunk \"add -p (no, yes, edit)\"' '\n@@ -648,7 +648,7 @@ test_expect_success 'split hunk \"add -p (no, yes, edit)\"' '\n \tEDITOR=: git add -p 2>error &&\n \ttest_must_be_empty error &&\n \tgit diff >actual &&\n-\t! grep \"^+31\" actual\n+\ttest_grep ! \"^+31\" actual\n '\n \n test_expect_success 'split hunk with incomplete line at end' '\n@@ -682,7 +682,7 @@ test_expect_success 'edit, adding lines to the first hunk' '\n \tEDITOR=./fake_editor.sh git add -p 2>error &&\n \ttest_must_be_empty error &&\n \tgit diff --cached >actual &&\n-\tgrep \"^+22\" actual\n+\ttest_grep \"^+22\" actual\n '\n \n test_expect_success 'patch mode ignores unmerged entries' '\n@@ -696,7 +696,7 @@ test_expect_success 'patch mode ignores unmerged entries' '\n \ttest_must_fail git merge side &&\n \techo changed >non-conflict.t &&\n \techo y | git add -p >output &&\n-\t! grep a/conflict.t output &&\n+\ttest_grep ! a/conflict.t output &&\n \tcat >expected <<-\\EOF &&\n \t* Unmerged path conflict.t\n \tdiff --git a/non-conflict.t b/non-conflict.t\n@@ -728,7 +728,7 @@ test_expect_success 'diffs can be colorized' '\n \n \t# We do not want to depend on the exact coloring scheme\n \t# git uses for diffs, so just check that we saw some kind of color.\n-\tgrep \"$(printf \"\\\\033\")\" output\n+\ttest_grep \"$(printf \"\\\\033\")\" output\n '\n \n test_expect_success 'colors can be overridden' '\n@@ -743,7 +743,7 @@ test_expect_success 'colors can be overridden' '\n \t\t-c color.interactive.error=blue \\\n \t\tadd -i 2>err.raw <input &&\n \ttest_decode_color <err.raw >err &&\n-\tgrep \"<BLUE>Huh (trigger)?<RESET>\" err &&\n+\ttest_grep \"<BLUE>Huh (trigger)?<RESET>\" err &&\n \n \ttest_write_lines help quit >input &&\n \tforce_color git \\\n@@ -863,7 +863,7 @@ test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '\n \tprintf y >y &&\n \tforce_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&\n \ttest_decode_color <output.raw >output &&\n-\tgrep \"old<\" output\n+\ttest_grep \"old<\" output\n '\n \n test_expect_success 'diffFilter filters diff' '\n@@ -876,7 +876,7 @@ test_expect_success 'diffFilter filters diff' '\n \n \t# avoid depending on the exact coloring or content of the prompts,\n \t# and just make sure we saw our diff prefixed\n-\tgrep foo:.*content output\n+\ttest_grep foo:.*content output\n '\n \n test_expect_success 'detect bogus diffFilter output' '\n@@ -886,7 +886,7 @@ test_expect_success 'detect bogus diffFilter output' '\n \ttest_config interactive.diffFilter \"sed 6d\" &&\n \tprintf y >y &&\n \tforce_color test_must_fail git add -p <y >output 2>&1 &&\n-\tgrep \"mismatched output\" output\n+\ttest_grep \"mismatched output\" output\n '\n \n test_expect_success 'handle iffy colored hunk headers' '\n@@ -896,7 +896,7 @@ test_expect_success 'handle iffy colored hunk headers' '\n \tprintf n >n &&\n \tforce_color git -c interactive.diffFilter=\"sed s/.*@@.*/XX/\" \\\n \t\tadd -p >output 2>&1 <n &&\n-\tgrep \"^XX$\" output\n+\ttest_grep \"^XX$\" output\n '\n \n test_expect_success 'handle very large filtered diff' '\n@@ -1002,7 +1002,7 @@ test_expect_success 'add -p does not expand argument lists' '\n \t# update it, but we want to be sure that our \".\" pathspec\n \t# was not expanded into the argument list of any command.\n \t# So look only for \"not-changed\".\n-\t! grep -E \"^trace: (built-in|exec|run_command): .*not-changed\" trace.out\n+\ttest_grep ! -E \"^trace: (built-in|exec|run_command): .*not-changed\" trace.out\n '\n \n test_expect_success 'hunk-editing handles custom comment char' '\n@@ -1072,21 +1072,21 @@ test_expect_success 'setup different kinds of dirty submodules' '\n \n test_expect_success 'status ignores dirty submodules (except HEAD)' '\n \tgit -C for-submodules add -i </dev/null >output &&\n-\tgrep dirty-head output &&\n-\tgrep dirty-both-ways output &&\n-\t! grep dirty-otherwise output\n+\ttest_grep dirty-head output &&\n+\ttest_grep dirty-both-ways output &&\n+\ttest_grep ! dirty-otherwise output\n '\n \n test_expect_success 'handle submodules' '\n \techo 123 >>for-submodules/dirty-otherwise/initial.t &&\n \n \tforce_color git -C for-submodules add -p dirty-otherwise >output 2>&1 &&\n-\tgrep \"No changes\" output &&\n+\ttest_grep \"No changes\" output &&\n \n \tforce_color git -C for-submodules add -p dirty-head >output 2>&1 <y &&\n \tgit -C for-submodules ls-files --stage dirty-head >actual &&\n \trev=\"$(git -C for-submodules/dirty-head rev-parse HEAD)\" &&\n-\tgrep \"$rev\" actual\n+\ttest_grep \"$rev\" actual\n '\n \n test_expect_success 'set up pathological context' '\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex ec2804eea67c..c66f966a3ab3 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -38,36 +38,36 @@ test_expect_success 'setup' '\n \n test_expect_success 'the default number of context lines is 3' '\n \tgit diff >output &&\n-\t! grep \"^ d\" output &&\n-\tgrep \"^ e\" output &&\n-\tgrep \"^ j\" output &&\n-\t! grep \"^ k\" output\n+\ttest_grep ! \"^ d\" output &&\n+\ttest_grep \"^ e\" output &&\n+\ttest_grep \"^ j\" output &&\n+\ttest_grep ! \"^ k\" output\n '\n \n test_expect_success 'diff.context honored by \"log\"' '\n \tgit log -1 -p >output &&\n-\t! grep firstline output &&\n+\ttest_grep ! firstline output &&\n \tgit config diff.context 8 &&\n \tgit log -1 -p >output &&\n-\tgrep \"^ firstline\" output\n+\ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'The -U option overrides diff.context' '\n \tgit config diff.context 8 &&\n \tgit log -U4 -1 >output &&\n-\t! grep \"^ firstline\" output\n+\ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'diff.context honored by \"diff\"' '\n \tgit config diff.context 8 &&\n \tgit diff >output &&\n-\tgrep \"^ firstline\" output\n+\ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'plumbing not affected' '\n \tgit config diff.context 8 &&\n \tgit diff-files -p >output &&\n-\t! grep \"^ firstline\" output\n+\ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'non-integer config parsing' '\n@@ -85,8 +85,8 @@ test_expect_success 'negative integer config parsing' '\n test_expect_success '-U0 is valid, so is diff.context=0' '\n \tgit config diff.context 0 &&\n \tgit diff >output &&\n-\tgrep \"^-ADDED\" output &&\n-\tgrep \"^+MODIFIED\" output\n+\ttest_grep \"^-ADDED\" output &&\n+\ttest_grep \"^+MODIFIED\" output\n '\n \n test_expect_success '-U2147483647 works' '\n@@ -94,9 +94,9 @@ test_expect_success '-U2147483647 works' '\n \ttest_line_count = 16 x &&\n \tgit diff -U2147483647 >output &&\n \ttest_line_count = 22 output &&\n-\tgrep \"^-ADDED\" output &&\n-\tgrep \"^+MODIFIED\" output &&\n-\tgrep \"^+APPENDED\" output\n+\ttest_grep \"^-ADDED\" output &&\n+\ttest_grep \"^+MODIFIED\" output &&\n+\ttest_grep \"^+APPENDED\" output\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"517736","messageId":"pull.1915.v2.git.1746884789.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.git.1746436719.gitgitgadget@gmail.com","subject":"[PATCH v2 0/4] Better support for customising context lines in --patch commands","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-05-10T13:46:25Z","receivedAt":"2025-05-10T13:46:34Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"This series of patches attempt to give --interactive/--patch compatible\nbuiltins (\"add\", \"commit\", \"checkout\", \"reset\", \"restore\" and \"stash\")\nbetter support and nicer experience for configuring how many context lines\nare shown in diffs through a variety of ways.\n\nPrior to these patches, the user could not choose how many context lines\nthey saw in --patch commands (apart from one workaround by using\nGIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a\npersistent solution). Additionally, the behaviour around reading from the\ndiff.context and diff.interHunkContext configs was also inconsistent with\nother diff generating commands such as \"log -p\".\n\nThe summarised changes below hopefully make this experience better and fix\nsome inconsistencies:\n\n * diff.context and diff.interHunkContext configs are now respected by\n   --patch compatible commands\n * --unified and --inter-hunk-context command line options have been added\n   to --patch compatible commands (which take prescendence over file\n   configs)\n * \"add\" and \"commit\" in --interactive mode now expose a new \"context\"\n   subcommand which configures the amount of context lines you wish to see\n   in subsequent diffs generated from other subcommands such as \"patch\" or\n   \"diff\"\n\nThe original discussion for this can be read at:\n\n * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/\n\nChanges since v1:\n\n * Update commit descriptions\n * Update tests to use the more modern and robust test_grep and test_config\n   utils\n * Reword some documentation / user messages\n * Ensure each commit is atomic and builds/passes tests on it's own\n * Make new command line options DRY\n * Add tests for interhunk context interaction\n * Error if context config/command line options are negative\n * Drop previous last commit to do with new subcommand for --interactive\n   add/commit. My motivations behind this patch series originally where\n   quite simple, just for add-patch commands to respect context configs.\n   This subcommand, after the discussion in v1, will require more thought\n   and a larger implementation that what I had anticipated. I would prefer\n   to leave this for another time as it's the least impactful but the most\n   time intensive and complicated idea.\n\nLeon Michalak (4):\n  test: refactor to use \"test_grep\"\n  test: refactor to use \"test_config\"\n  add-patch: respect diff.context configuration\n  add-patch: add diff.context command line overrides\n\n Documentation/diff-context-options.adoc |  10 ++\n Documentation/git-add.adoc              |   2 +\n Documentation/git-checkout.adoc         |   2 +\n Documentation/git-commit.adoc           |   2 +\n Documentation/git-reset.adoc            |   2 +\n Documentation/git-restore.adoc          |   2 +\n Documentation/git-stash.adoc            |   2 +\n add-interactive.c                       |  53 +++++++--\n add-interactive.h                       |  17 ++-\n add-patch.c                             |  11 +-\n builtin/add.c                           |  16 ++-\n builtin/checkout.c                      |  24 +++-\n builtin/commit.c                        |  11 +-\n builtin/reset.c                         |  12 +-\n builtin/stash.c                         |  46 ++++++--\n commit.h                                |   3 +-\n parse-options.h                         |   2 +\n t/t3701-add-interactive.sh              |  48 ++++----\n t/t4032-diff-inter-hunk-context.sh      |  61 ++++++++++\n t/t4055-diff-context.sh                 | 142 ++++++++++++++++++++----\n t/t9902-completion.sh                   |   2 +\n 21 files changed, 390 insertions(+), 80 deletions(-)\n create mode 100644 Documentation/diff-context-options.adoc\n\n\nbase-commit: f65182a99e545d2f2bc22e6c1c2da192133b16a3\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1915\n\nRange-diff vs v1:\n\n -:  ----------- > 1:  4f92a1b4c24 test: refactor to use \"test_grep\"\n -:  ----------- > 2:  75424cb8e1c test: refactor to use \"test_config\"\n 1:  8b91eef8120 ! 3:  f16d3de8611 add-patch: respect diff.context configuration\n     @@ Metadata\n       ## Commit message ##\n          add-patch: respect diff.context configuration\n      \n     -    This aims to teach relevant builtins (that take in `--patch`) to respect\n     +    Various builtins that use add-patch infrastructure do not respect\n          the user's diff.context and diff.interHunkContext file configurations.\n     -\n     -    Since these are both UI options and `--patch` is designed for the end user,\n     -    I believe this was previously just an inconsistency, which this patch hopes\n     -    to address.\n     +    This patch fixes this inconsistency.\n      \n          Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n      \n       ## add-interactive.c ##\n     -@@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)\n     +@@ add-interactive.c: static void init_color(struct repository *r, struct add_i_state *s,\n     + void init_add_i_state(struct add_i_state *s, struct repository *r)\n     + {\n       \tconst char *value;\n     ++\tint context;\n     ++\tint interhunkcontext;\n       \n       \ts->r = r;\n      +\ts->context = -1;\n     @@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repositor\n       \trepo_config_get_string(r, \"diff.algorithm\",\n       \t\t\t       &s->interactive_diff_algorithm);\n       \n     -+\trepo_config_get_int(r, \"diff.context\", &s->context);\n     -+\trepo_config_get_int(r, \"diff.interHunkContext\", &s->interhunkcontext);\n     ++\tif (!repo_config_get_int(r, \"diff.context\", &context)) {\n     ++\t\tif (context < 0)\n     ++\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n     ++\t\telse\n     ++\t\t\ts->context = context;\n     ++\t};\n     ++\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &interhunkcontext)) {\n     ++\t\tif (interhunkcontext < 0)\n     ++\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n     ++\t\telse\n     ++\t\t\ts->interhunkcontext = interhunkcontext;\n     ++\t};\n      +\n       \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n       \tif (s->use_single_key)\n       \t\tsetbuf(stdin, NULL);\n     -@@ add-interactive.c: static int run_diff(struct add_i_state *s, const struct pathspec *ps,\n     - \tif (count > 0) {\n     - \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n     +\n     + ## add-interactive.h ##\n     +@@ add-interactive.h: struct add_i_state {\n     + \n     + \tint use_single_key;\n     + \tchar *interactive_diff_filter, *interactive_diff_algorithm;\n     ++\tint context, interhunkcontext;\n     + };\n       \n     --\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\",\n     --\t\t\t     oid_to_hex(!is_initial ? &oid :\n     --\t\t\t\t\ts->r->hash_algo->empty_tree),\n     --\t\t\t     \"--\", NULL);\n     -+\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\", NULL);\n     -+\t\tif (s->context != -1)\n     -+\t\t\tstrvec_pushf(&cmd.args, \"--unified=%i\", s->context);\n     -+\t\tif (s->interhunkcontext != -1)\n     -+\t\t\tstrvec_pushf(&cmd.args, \"--inter-hunk-context=%i\", s->interhunkcontext);\n     -+\t\tstrvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :\n     -+\t\t\t     s->r->hash_algo->empty_tree), \"--\", NULL);\n     - \t\tfor (i = 0; i < files->items.nr; i++)\n     - \t\t\tif (files->selected[i])\n     - \t\t\t\tstrvec_push(&cmd.args,\n     + void init_add_i_state(struct add_i_state *s, struct repository *r);\n      \n       ## add-patch.c ##\n      @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n     @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec\n      \n       ## t/t4055-diff-context.sh ##\n      @@ t/t4055-diff-context.sh: test_expect_success 'diff.context honored by \"log\"' '\n     - \t! grep firstline output &&\n     - \tgit config diff.context 8 &&\n     - \tgit log -1 -p >output &&\n     --\tgrep \"^ firstline\" output\n     -+\tgrep \"^ firstline\" output &&\n     -+\tgit config --unset diff.context\n     -+'\n     -+\n     + \ttest_grep \"^ firstline\" output\n     + '\n     + \n      +test_expect_success 'diff.context honored by \"add\"' '\n      +\tgit add -p >output &&\n     -+\t! grep firstline output &&\n     -+\tgit config diff.context 8 &&\n     -+\tgit add -p >output &&\n     -+\tgrep \"^ firstline\" output &&\n     -+\tgit config --unset diff.context\n     ++\ttest_grep ! firstline output &&\n     ++\ttest_config diff.context 8 &&\n     ++\tgit log -1 -p >output &&\n     ++\ttest_grep \"^ firstline\" output\n      +'\n      +\n      +test_expect_success 'diff.context honored by \"commit\"' '\n      +\t! git commit -p >output &&\n     -+\t! grep firstline output &&\n     -+\tgit config diff.context 8 &&\n     ++\ttest_grep ! firstline output &&\n     ++\ttest_config diff.context 8 &&\n      +\t! git commit -p >output &&\n     -+\tgrep \"^ firstline\" output &&\n     -+\tgit config --unset diff.context\n     ++\ttest_grep \"^ firstline\" output\n      +'\n      +\n      +test_expect_success 'diff.context honored by \"checkout\"' '\n      +\tgit checkout -p >output &&\n     -+\t! grep firstline output &&\n     -+\tgit config diff.context 8 &&\n     ++\ttest_grep ! firstline output &&\n     ++\ttest_config diff.context 8 &&\n      +\tgit checkout -p >output &&\n     -+\tgrep \"^ firstline\" output &&\n     -+\tgit config --unset diff.context\n     ++\ttest_grep \"^ firstline\" output\n      +'\n      +\n      +test_expect_success 'diff.context honored by \"stash\"' '\n      +\t! git stash -p >output &&\n     -+\t! grep firstline output &&\n     -+\tgit config diff.context 8 &&\n     ++\ttest_grep ! firstline output &&\n     ++\ttest_config diff.context 8 &&\n      +\t! git stash -p >output &&\n     -+\tgrep \"^ firstline\" output &&\n     -+\tgit config --unset diff.context\n     ++\ttest_grep \"^ firstline\" output\n      +'\n      +\n      +test_expect_success 'diff.context honored by \"restore\"' '\n      +\tgit restore -p >output &&\n     -+\t! grep firstline output &&\n     -+\tgit config diff.context 8 &&\n     ++\ttest_grep ! firstline output &&\n     ++\ttest_config diff.context 8 &&\n      +\tgit restore -p >output &&\n     -+\tgrep \"^ firstline\" output &&\n     -+\tgit config --unset diff.context\n     ++\ttest_grep \"^ firstline\" output\n     ++'\n     ++\n     + test_expect_success 'The -U option overrides diff.context' '\n     + \ttest_config diff.context 8 &&\n     + \tgit log -U4 -1 >output &&\n     +@@ t/t4055-diff-context.sh: test_expect_success 'negative integer config parsing' '\n     + \ttest_grep \"bad config variable\" output\n       '\n       \n     - test_expect_success 'The -U option overrides diff.context' '\n     ++test_expect_success 'negative integer config parsing by \"add\"' '\n     ++\ttest_config diff.context -1 &&\n     ++\ttest_must_fail git add -p 2>output &&\n     ++\ttest_grep \"diff.context cannot be negative\" output\n     ++'\n     ++\n     ++test_expect_success 'negative integer config parsing by \"commit\"' '\n     ++\ttest_config diff.context -1 &&\n     ++\ttest_must_fail git commit -p 2>output &&\n     ++\ttest_grep \"bad config variable\" output\n     ++'\n     ++\n     ++test_expect_success 'negative integer config parsing by \"checkout\"' '\n     ++\ttest_config diff.context -1 &&\n     ++\ttest_must_fail git checkout -p 2>output &&\n     ++\ttest_grep \"diff.context cannot be negative\" output\n     ++'\n     ++\n     ++test_expect_success 'negative integer config parsing by \"stash\"' '\n     ++\ttest_config diff.context -1 &&\n     ++\ttest_must_fail git stash -p 2>output &&\n     ++\ttest_grep \"diff.context cannot be negative\" output\n     ++'\n     ++\n     ++test_expect_success 'negative integer config parsing by \"restore\"' '\n     ++\ttest_config diff.context -1 &&\n     ++\ttest_must_fail git restore -p 2>output &&\n     ++\ttest_grep \"diff.context cannot be negative\" output\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 2:  7700eb173e7 ! 4:  973dfadd1b3 add-patch: add diff.context command line overrides\n     @@ Metadata\n       ## Commit message ##\n          add-patch: add diff.context command line overrides\n      \n     -    This patch compliments `8b91eef812`, where builtins that accept\n     -    `--patch` options now respect `diff.context` and `diff.interHunkContext`\n     -    file configurations.\n     +    This patch compliments the previous commit, where builtins that use\n     +    add-patch infrastructure now respect diff.context and\n     +    diff.interHunkContext file configurations.\n      \n          In particular, this patch helps users who don't want to set persistent\n          context configurations or just want a way to override them on a one-time\n          basis, by allowing the relevant builtins to accept corresponding command\n          line options that override the file configurations.\n      \n     -    This mimics `diff` which allows for both context file configuration and\n     -    command line overrides.\n     +    This mimics commands such as diff and log, which allow for both context\n     +    file configuration and command line overrides.\n      \n          Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n      \n     - ## Documentation/git-add.adoc ##\n     -@@ Documentation/git-add.adoc: This effectively runs `add --interactive`, but bypasses the\n     - initial command menu and directly jumps to the `patch` subcommand.\n     - See ``Interactive mode'' for details.\n     - \n     + ## Documentation/diff-context-options.adoc (new) ##\n     +@@\n      +`-U<n>`::\n      +`--unified=<n>`::\n      +\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n     -+\tor 3 if the config option is unset. Implies `--interactive/--patch`.\n     ++\tor 3 if the config option is unset.\n      +\n      +`--inter-hunk-context=<n>`::\n      +\tShow the context between diff hunks, up to the specified _<number>_\n      +\tof lines, thereby fusing hunks that are close to each other.\n      +\tDefaults to `diff.interHunkContext` or 0 if the config option\n     -+\tis unset. Implies `--interactive/--patch`.\n     ++\tis unset.\n     +\n     + ## Documentation/git-add.adoc ##\n     +@@ Documentation/git-add.adoc: This effectively runs `add --interactive`, but bypasses the\n     + initial command menu and directly jumps to the `patch` subcommand.\n     + See ``Interactive mode'' for details.\n     + \n     ++include::diff-context-options.adoc[]\n      +\n       `-e`::\n       `--edit`::\n     @@ Documentation/git-checkout.adoc: section of linkgit:git-add[1] to learn how to o\n       Note that this option uses the no overlay mode by default (see also\n       `--overlay`), and currently doesn't support overlay mode.\n       \n     -+`-U<n>`::\n     -+`--unified=<n>`::\n     -+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n     -+\tor 3 if the config option is unset. Implies `--patch`.\n     -+\n     -+`--inter-hunk-context=<n>`::\n     -+\tShow the context between diff hunks, up to the specified _<number>_\n     -+\tof lines, thereby fusing hunks that are close to each other.\n     -+\tDefaults to `diff.interHunkContext` or 0 if the config option\n     -+\tis unset. Implies `--patch`.\n     ++include::diff-context-options.adoc[]\n      +\n       --ignore-other-worktrees::\n       \t`git checkout` refuses when the wanted branch is already checked\n     @@ Documentation/git-commit.adoc: OPTIONS\n       \twhich changes to commit. See linkgit:git-add[1] for\n       \tdetails.\n       \n     -+`-U<n>`::\n     -+`--unified=<n>`::\n     -+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n     -+\tor 3 if the config option is unset. Implies `--interactive/--patch`.\n     -+\n     -+`--inter-hunk-context=<n>`::\n     -+\tShow the context between diff hunks, up to the specified _<number>_\n     -+\tof lines, thereby fusing hunks that are close to each other.\n     -+\tDefaults to `diff.interHunkContext` or 0 if the config option\n     -+\tis unset. Implies `--interactive/--patch`.\n     ++include::diff-context-options.adoc[]\n      +\n       `-C <commit>`::\n       `--reuse-message=<commit>`::\n     @@ Documentation/git-reset.adoc: OPTIONS\n       \tseparated with _NUL_ character and all other characters are taken\n       \tliterally (including newlines and quotes).\n       \n     -+`-U<n>`::\n     -+`--unified=<n>`::\n     -+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n     -+\tor 3 if the config option is unset. Implies `--patch`.\n     -+\n     -+`--inter-hunk-context=<n>`::\n     -+\tShow the context between diff hunks, up to the specified _<number>_\n     -+\tof lines, thereby fusing hunks that are close to each other.\n     -+\tDefaults to `diff.interHunkContext` or 0 if the config option\n     -+\tis unset. Implies `--patch`.\n     ++include::diff-context-options.adoc[]\n      +\n       `--`::\n       \tDo not interpret any more arguments as options.\n     @@ Documentation/git-restore.adoc: leave out at most one of _<rev-A>__ and _<rev-B>\n       \tMode\" section of linkgit:git-add[1] to learn how to operate\n       \tthe `--patch` mode.\n       \n     -+`-U<n>`::\n     -+`--unified=<n>`::\n     -+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n     -+\tor 3 if the config option is unset. Implies `--patch`.\n     -+\n     -+`--inter-hunk-context=<n>`::\n     -+\tShow the context between diff hunks, up to the specified _<number>_\n     -+\tof lines, thereby fusing hunks that are close to each other.\n     -+\tDefaults to `diff.interHunkContext` or 0 if the config option\n     -+\tis unset. Implies `--patch`.\n     ++include::diff-context-options.adoc[]\n      +\n       `-W`::\n       `--worktree`::\n     @@ Documentation/git-stash.adoc: to learn how to operate the `--patch` mode.\n       The `--patch` option implies `--keep-index`.  You can use\n       `--no-keep-index` to override this.\n       \n     -+`-U<n>`::\n     -+`--unified=<n>`::\n     -+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n     -+\tor 3 if the config option is unset. Implies `--patch`.\n     -+\n     -+`--inter-hunk-context=<n>`::\n     -+\tShow the context between diff hunks, up to the specified _<number>_\n     -+\tof lines, thereby fusing hunks that are close to each other.\n     -+\tDefaults to `diff.interHunkContext` or 0 if the config option\n     -+\tis unset. Implies `--patch`.\n     ++include::diff-context-options.adoc[]\n      +\n       -S::\n       --staged::\n     @@ add-interactive.c: static void init_color(struct repository *r, struct add_i_sta\n      +\t\t      struct add_p_opt *add_p_opt)\n       {\n       \tconst char *value;\n     - \n     + \tint context;\n      @@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)\n       \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n       \tif (s->use_single_key)\n       \t\tsetbuf(stdin, NULL);\n      +\n     -+\tif (add_p_opt->context != -1)\n     ++\tif (add_p_opt->context != -1) {\n     ++\t\tif (add_p_opt->context < 0)\n     ++\t\t\tdie(_(\"%s cannot be negative\"), \"--unified\");\n      +\t\ts->context = add_p_opt->context;\n     -+\tif (add_p_opt->interhunkcontext != -1)\n     ++\t}\n     ++\tif (add_p_opt->interhunkcontext != -1) {\n     ++\t\tif (add_p_opt->interhunkcontext < 0)\n     ++\t\t\tdie(_(\"%s cannot be negative\"), \"--inter-hunk-context\");\n      +\t\ts->interhunkcontext = add_p_opt->interhunkcontext;\n     ++\t}\n       }\n       \n       void clear_add_i_state(struct add_i_state *s)\n     @@ add-interactive.c: static int run_patch(struct add_i_state *s, const struct path\n       \t\tstrvec_clear(&args);\n       \t\tclear_pathspec(&ps_selected);\n       \t}\n     +@@ add-interactive.c: static int run_diff(struct add_i_state *s, const struct pathspec *ps,\n     + \tif (count > 0) {\n     + \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n     + \n     +-\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\",\n     +-\t\t\t     oid_to_hex(!is_initial ? &oid :\n     +-\t\t\t\t\ts->r->hash_algo->empty_tree),\n     +-\t\t\t     \"--\", NULL);\n     ++\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\", NULL);\n     ++\t\tif (s->context != -1)\n     ++\t\t\tstrvec_pushf(&cmd.args, \"--unified=%i\", s->context);\n     ++\t\tif (s->interhunkcontext != -1)\n     ++\t\t\tstrvec_pushf(&cmd.args, \"--inter-hunk-context=%i\", s->interhunkcontext);\n     ++\t\tstrvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :\n     ++\t\t\t     s->r->hash_algo->empty_tree), \"--\", NULL);\n     + \t\tfor (i = 0; i < files->items.nr; i++)\n     + \t\t\tif (files->selected[i])\n     + \t\t\t\tstrvec_push(&cmd.args,\n      @@ add-interactive.c: static void command_prompt_help(struct add_i_state *s)\n       \t\t\t _(\"(empty) select nothing\"));\n       }\n     @@ add-interactive.h\n       \n       #include \"color.h\"\n       \n     -+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }\n     -+\n      +struct add_p_opt {\n      +\tint context;\n      +\tint interhunkcontext;\n      +};\n     ++\n     ++#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }\n      +\n       struct add_i_state {\n       \tstruct repository *r;\n       \tint use_color;\n      @@ add-interactive.h: struct add_i_state {\n     - \n     - \tint use_single_key;\n     - \tchar *interactive_diff_filter, *interactive_diff_algorithm;\n     -+\tint context, interhunkcontext;\n     + \tint context, interhunkcontext;\n       };\n       \n      -void init_add_i_state(struct add_i_state *s, struct repository *r);\n     @@ builtin/add.c: int interactive_add(struct repository *repo,\n       \n       \tif (patch)\n      -\t\tret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);\n     -+\t\tret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL,\n     -+\t\t\t\t  &pathspec);\n     ++\t\tret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec);\n       \telse\n      -\t\tret = !!run_add_i(repo, &pathspec);\n      +\t\tret = !!run_add_i(repo, &pathspec, add_p_opt);\n     @@ builtin/add.c: static struct option builtin_add_options[] = {\n       \tOPT_GROUP(\"\"),\n       \tOPT_BOOL('i', \"interactive\", &add_interactive, N_(\"interactive picking\")),\n       \tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"select hunks interactively\")),\n     -+\tOPT_INTEGER_F('U', \"unified\", &add_p_opt.context,\n     -+\t\t      N_(\"generate diffs with <n> lines context, implies --interactive/--patch\"),\n     -+\t\t      PARSE_OPT_NONEG),\n     -+\tOPT_INTEGER_F(0, \"inter-hunk-context\", &add_p_opt.interhunkcontext,\n     -+\t\t      N_(\"show context between diff hunks up to the specified number of lines, implies --interactive/--patch\"),\n     -+\t\t      PARSE_OPT_NONEG),\n     ++\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n     ++\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n       \tOPT_BOOL('e', \"edit\", &edit_interactive, N_(\"edit current diff and apply\")),\n       \tOPT__FORCE(&ignored_too, N_(\"allow adding otherwise ignored files\"), 0),\n       \tOPT_BOOL('u', \"update\", &take_worktree_changes, N_(\"update tracked files\")),\n     @@ builtin/checkout.c: static struct option *add_checkout_path_options(struct check\n       \t\t\t      N_(\"checkout their version for unmerged files\"),\n       \t\t\t      3, PARSE_OPT_NONEG),\n       \t\tOPT_BOOL('p', \"patch\", &opts->patch_mode, N_(\"select hunks interactively\")),\n     -+\t\tOPT_INTEGER_F('U', \"unified\", &opts->patch_context,\n     -+\t\t\tN_(\"generate diffs with <n> lines context, implies --patch\"),\n     -+\t\t\tPARSE_OPT_NONEG),\n     -+\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &opts->patch_interhunk_context,\n     -+\t\t\tN_(\"show context between diff hunks up to the specified number of lines, implies --patch\"),\n     -+\t\t\tPARSE_OPT_NONEG),\n     ++\t\tOPT_DIFF_UNIFIED(&opts->patch_context),\n     ++\t\tOPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context),\n       \t\tOPT_BOOL(0, \"ignore-skip-worktree-bits\", &opts->ignore_skipworktree,\n       \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n       \t\tOPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),\n     @@ builtin/commit.c: int cmd_commit(int argc,\n       \t\tOPT_BOOL('i', \"include\", &also, N_(\"add specified files to index for commit\")),\n       \t\tOPT_BOOL(0, \"interactive\", &interactive, N_(\"interactively add files\")),\n       \t\tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"interactively add changes\")),\n     -+\t\tOPT_INTEGER_F('U', \"unified\", &add_p_opt.context,\n     -+\t\t\t      N_(\"generate diffs with <n> lines context, implies --interactive/--patch\"),\n     -+\t\t\t      PARSE_OPT_NONEG),\n     -+\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &add_p_opt.interhunkcontext,\n     -+\t\t\t      N_(\"show context between diff hunks up to the specified number of lines, implies --interactive/--patch\"),\n     -+\t\t\t      PARSE_OPT_NONEG),\n     ++\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n     ++\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n       \t\tOPT_BOOL('o', \"only\", &only, N_(\"commit only specified files\")),\n       \t\tOPT_BOOL('n', \"no-verify\", &no_verify, N_(\"bypass pre-commit and commit-msg hooks\")),\n       \t\tOPT_BOOL(0, \"dry-run\", &dry_run, N_(\"show what would be committed\")),\n     @@ builtin/reset.c: int cmd_reset(int argc,\n       \t\t\t       PARSE_OPT_OPTARG,\n       \t\t\t       option_parse_recurse_submodules_worktree_updater),\n       \t\tOPT_BOOL('p', \"patch\", &patch_mode, N_(\"select hunks interactively\")),\n     -+\t\tOPT_INTEGER_F('U', \"unified\", &add_p_opt.context,\n     -+\t\t\t       N_(\"generate diffs with <n> lines context, implies --patch\"),\n     -+\t\t\t       PARSE_OPT_NONEG),\n     -+\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &add_p_opt.interhunkcontext,\n     -+\t\t\t       N_(\"show context between diff hunks up to the specified number of lines, implies --patch\"),\n     -+\t\t\t       PARSE_OPT_NONEG),\n     ++\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n     ++\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n       \t\tOPT_BOOL('N', \"intent-to-add\", &intent_to_add,\n       \t\t\t\tN_(\"record only the fact that removed paths will be added later\")),\n       \t\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n     @@ builtin/stash.c: static int push_stash(int argc, const char **argv, const char *\n       \t\t\t N_(\"stash staged changes only\")),\n       \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n       \t\t\t N_(\"stash in patch mode\")),\n     -+\t\tOPT_INTEGER_F('U', \"unified\", &add_p_opt.context,\n     -+\t\t\t      N_(\"generate diffs with <n> lines context, implies --patch\"),\n     -+\t\t\t      PARSE_OPT_NONEG),\n     -+\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &add_p_opt.interhunkcontext,\n     -+\t\t\t      N_(\"show context between diff hunks up to the specified number of lines, implies --patch\"),\n     -+\t\t\t      PARSE_OPT_NONEG),\n     ++\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n     ++\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n       \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n       \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n       \t\t\t N_(\"include untracked files in stash\")),\n     @@ builtin/stash.c: static int save_stash(int argc, const char **argv, const char *\n       \t\t\t N_(\"stash staged changes only\")),\n       \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n       \t\t\t N_(\"stash in patch mode\")),\n     -+\t\tOPT_INTEGER_F('U', \"unified\", &add_p_opt.context,\n     -+\t\t\t      N_(\"generate diffs with <n> lines context, implies --patch\"),\n     -+\t\t\t      PARSE_OPT_NONEG),\n     -+\t\tOPT_INTEGER_F(0, \"inter-hunk-context\", &add_p_opt.interhunkcontext,\n     -+\t\t\t      N_(\"show context between diff hunks up to the specified number of lines, implies --patch\"),\n     -+\t\t\t      PARSE_OPT_NONEG),\n     ++\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n     ++\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n       \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n       \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n       \t\t\t N_(\"include untracked files in stash\")),\n     @@ commit.h: int for_each_commit_graft(each_commit_graft_fn, void *);\n       struct commit_extra_header {\n       \tstruct commit_extra_header *next;\n      \n     + ## parse-options.h ##\n     +@@ parse-options.h: int parse_opt_tracking_mode(const struct option *, const char *, int);\n     + #define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, \"pathspec-from-file\", v, N_(\"read pathspec from file\"))\n     + #define OPT_PATHSPEC_FILE_NUL(v)  OPT_BOOL(0, \"pathspec-file-nul\", v, N_(\"with --pathspec-from-file, pathspec elements are separated with NUL character\"))\n     + #define OPT_AUTOSTASH(v) OPT_BOOL(0, \"autostash\", v, N_(\"automatically stash/stash pop before and after\"))\n     ++#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', \"unified\", v, N_(\"generate diffs with <n> lines context\"), PARSE_OPT_NONEG)\n     ++#define OPT_DIFF_INTERHUNK_CONTEXT(v) OPT_INTEGER_F(0, \"inter-hunk-context\", v, N_(\"show context between diff hunks up to the specified number of lines\"), PARSE_OPT_NONEG)\n     + \n     + #define OPT_IPVERSION(v) \\\n     + \tOPT_SET_INT_F('4', \"ipv4\", (v), N_(\"use IPv4 addresses only\"), \\\n     +\n     + ## t/t4032-diff-inter-hunk-context.sh ##\n     +@@ t/t4032-diff-inter-hunk-context.sh: t() {\n     + \t\"\n     + }\n     + \n     ++t_patch() {\n     ++\tuse_config=\n     ++\tgit config --unset diff.interHunkContext\n     ++\n     ++\tcase $# in\n     ++\t4) hunks=$4; cmd=\"add -p -U$3\";;\n     ++\t5) hunks=$5; cmd=\"add -p -U$3 --inter-hunk-context=$4\";;\n     ++\t6) hunks=$5; cmd=\"add -p -U$3\"; git config diff.interHunkContext $4; use_config=\"(diff.interHunkContext=$4) \";;\n     ++\tesac\n     ++\tlabel=\"$use_config$cmd, $1 common $2\"\n     ++\tfile=f$1\n     ++\n     ++\tif ! test -f $file\n     ++\tthen\n     ++\t\tf A $1 B >$file\n     ++\t\tgit add $file\n     ++\t\tgit commit -q -m. $file\n     ++\t\tf X $1 Y >$file\n     ++\tfi\n     ++\n     ++\ttest_expect_success \"$label: count hunks ($hunks)\" \"\n     ++\t\ttest $(test_write_lines q | git $cmd $file | sed -n 's/^([0-9]*\\/\\([0-9]*\\)) Stage this hunk.*/\\1/p') = $hunks\n     ++\t\"\n     ++}\n     ++\n     + cat <<EOF >expected.f1.0.1 || exit 1\n     + diff --git a/f1 b/f1\n     + --- a/f1\n     +@@ t/t4032-diff-inter-hunk-context.sh: t 3 lines\t1\t2\t1\tconfig\n     + t 9 lines\t3\t2\t2\tconfig\n     + t 9 lines\t3\t3\t1\tconfig\n     + \n     ++# common lines\tctx\tintrctx\thunks\n     ++t_patch 1 line\t0\t\t2\n     ++t_patch 1 line\t0\t0\t2\n     ++t_patch 1 line\t0\t1\t1\n     ++t_patch 1 line\t0\t2\t1\n     ++t_patch 1 line\t1\t\t1\n     ++\n     ++t_patch 2 lines\t0\t\t2\n     ++t_patch 2 lines\t0\t0\t2\n     ++t_patch 2 lines\t0\t1\t2\n     ++t_patch 2 lines\t0\t2\t1\n     ++t_patch 2 lines\t1\t\t1\n     ++\n     ++t_patch 3 lines\t1\t\t2\n     ++t_patch 3 lines\t1\t0\t2\n     ++t_patch 3 lines\t1\t1\t1\n     ++t_patch 3 lines\t1\t2\t1\n     ++\n     ++t_patch 9 lines\t3\t\t2\n     ++t_patch 9 lines\t3\t2\t2\n     ++t_patch 9 lines\t3\t3\t1\n     ++\n     ++#\t\t\t\t\tuse diff.interHunkContext?\n     ++t_patch 1 line\t0\t0\t2\tconfig\n     ++t_patch 1 line\t0\t1\t1\tconfig\n     ++t_patch 1 line\t0\t2\t1\tconfig\n     ++t_patch 9 lines\t3\t3\t1\tconfig\n     ++t_patch 2 lines\t0\t0\t2\tconfig\n     ++t_patch 2 lines\t0\t1\t2\tconfig\n     ++t_patch 2 lines\t0\t2\t1\tconfig\n     ++t_patch 3 lines\t1\t0\t2\tconfig\n     ++t_patch 3 lines\t1\t1\t1\tconfig\n     ++t_patch 3 lines\t1\t2\t1\tconfig\n     ++t_patch 9 lines\t3\t2\t2\tconfig\n     ++t_patch 9 lines\t3\t3\t1\tconfig\n     ++\n     + test_expect_success 'diff.interHunkContext invalid' '\n     + \tgit config diff.interHunkContext asdf &&\n     + \ttest_must_fail git diff &&\n     +\n       ## t/t4055-diff-context.sh ##\n      @@ t/t4055-diff-context.sh: test_expect_success 'The -U option overrides diff.context' '\n     - \t! grep \"^ firstline\" output\n     + \ttest_grep ! \"^ firstline\" output\n       '\n       \n      +test_expect_success 'The -U option overrides diff.context for \"add\"' '\n     -+\tgit config diff.context 8 &&\n     ++\ttest_config diff.context 8 &&\n      +\tgit add -U4 -p >output &&\n     -+\t! grep \"^ firstline\" output\n     ++\ttest_grep ! \"^ firstline\" output\n      +'\n      +\n      +test_expect_success 'The -U option overrides diff.context for \"commit\"' '\n     -+\tgit config diff.context 8 &&\n     ++\ttest_config diff.context 8 &&\n      +\t! git commit -U4 -p >output &&\n     -+\t! grep \"^ firstline\" output\n     ++\ttest_grep ! \"^ firstline\" output\n      +'\n      +\n      +test_expect_success 'The -U option overrides diff.context for \"checkout\"' '\n     -+\tgit config diff.context 8 &&\n     ++\ttest_config diff.context 8 &&\n      +\tgit checkout -U4 -p >output &&\n     -+\t! grep \"^ firstline\" output\n     ++\ttest_grep ! \"^ firstline\" output\n      +'\n      +\n      +test_expect_success 'The -U option overrides diff.context for \"stash\"' '\n     -+\tgit config diff.context 8 &&\n     ++\ttest_config diff.context 8 &&\n      +\t! git stash -U4 -p >output &&\n     -+\t! grep \"^ firstline\" output\n     ++\ttest_grep ! \"^ firstline\" output\n      +'\n      +\n      +test_expect_success 'The -U option overrides diff.context for \"restore\"' '\n     -+\tgit config diff.context 8 &&\n     ++\ttest_config diff.context 8 &&\n      +\tgit restore -U4 -p >output &&\n     -+\t! grep \"^ firstline\" output\n     ++\ttest_grep ! \"^ firstline\" output\n      +'\n      +\n       test_expect_success 'diff.context honored by \"diff\"' '\n     - \tgit config diff.context 8 &&\n     + \ttest_config diff.context 8 &&\n       \tgit diff >output &&\n      \n       ## t/t9902-completion.sh ##\n 3:  b4b7854f330 < -:  ----------- add-interactive: add new \"context\" subcommand\n\n-- \ngitgitgadget\n"},{"id":"517737","messageId":"75424cb8e1ccae4f2ce8c4f796b8217552895d09.1746884789.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v2.git.1746884789.gitgitgadget@gmail.com","subject":"[PATCH v2 2/4] test: refactor to use \"test_config\"","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-05-10T13:46:27Z","receivedAt":"2025-05-10T13:46:36Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nRefactor to use the modern \"test_config\" test utility instead of manual\n\"git config\" as the former provides clean up on test completion.\n\nThis is a prerequisite to the commits that follow which add to this test\nfile.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n t/t4055-diff-context.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex c66f966a3ab3..1384a8195705 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -47,43 +47,43 @@ test_expect_success 'the default number of context lines is 3' '\n test_expect_success 'diff.context honored by \"log\"' '\n \tgit log -1 -p >output &&\n \ttest_grep ! firstline output &&\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit log -1 -p >output &&\n \ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'The -U option overrides diff.context' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit log -U4 -1 >output &&\n \ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'diff.context honored by \"diff\"' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit diff >output &&\n \ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'plumbing not affected' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit diff-files -p >output &&\n \ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'non-integer config parsing' '\n-\tgit config diff.context no &&\n+\ttest_config diff.context no &&\n \ttest_must_fail git diff 2>output &&\n \ttest_grep \"bad numeric config value\" output\n '\n \n test_expect_success 'negative integer config parsing' '\n-\tgit config diff.context -1 &&\n+\ttest_config diff.context -1 &&\n \ttest_must_fail git diff 2>output &&\n \ttest_grep \"bad config variable\" output\n '\n \n test_expect_success '-U0 is valid, so is diff.context=0' '\n-\tgit config diff.context 0 &&\n+\ttest_config diff.context 0 &&\n \tgit diff >output &&\n \ttest_grep \"^-ADDED\" output &&\n \ttest_grep \"^+MODIFIED\" output\n-- \ngitgitgadget\n\n"},{"id":"517738","messageId":"f16d3de86110ee61599459a25764248b52883b52.1746884789.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v2.git.1746884789.gitgitgadget@gmail.com","subject":"[PATCH v2 3/4] add-patch: respect diff.context configuration","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-05-10T13:46:28Z","receivedAt":"2025-05-10T13:46:36Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nVarious builtins that use add-patch infrastructure do not respect\nthe user's diff.context and diff.interHunkContext file configurations.\nThis patch fixes this inconsistency.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n add-interactive.c       | 17 ++++++++++\n add-interactive.h       |  1 +\n add-patch.c             |  6 ++++\n t/t4055-diff-context.sh | 70 +++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 94 insertions(+)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 97ff35b6f12a..cac036441caf 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -39,8 +39,12 @@ static void init_color(struct repository *r, struct add_i_state *s,\n void init_add_i_state(struct add_i_state *s, struct repository *r)\n {\n \tconst char *value;\n+\tint context;\n+\tint interhunkcontext;\n \n \ts->r = r;\n+\ts->context = -1;\n+\ts->interhunkcontext = -1;\n \n \tif (repo_config_get_value(r, \"color.interactive\", &value))\n \t\ts->use_color = -1;\n@@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \trepo_config_get_string(r, \"diff.algorithm\",\n \t\t\t       &s->interactive_diff_algorithm);\n \n+\tif (!repo_config_get_int(r, \"diff.context\", &context)) {\n+\t\tif (context < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n+\t\telse\n+\t\t\ts->context = context;\n+\t};\n+\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &interhunkcontext)) {\n+\t\tif (interhunkcontext < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n+\t\telse\n+\t\t\ts->interhunkcontext = interhunkcontext;\n+\t};\n+\n \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n \tif (s->use_single_key)\n \t\tsetbuf(stdin, NULL);\ndiff --git a/add-interactive.h b/add-interactive.h\nindex 693f125e8e4b..c63f35b14be8 100644\n--- a/add-interactive.h\n+++ b/add-interactive.h\n@@ -18,6 +18,7 @@ struct add_i_state {\n \n \tint use_single_key;\n \tchar *interactive_diff_filter, *interactive_diff_algorithm;\n+\tint context, interhunkcontext;\n };\n \n void init_add_i_state(struct add_i_state *s, struct repository *r);\ndiff --git a/add-patch.c b/add-patch.c\nindex 95c67d8c80c4..b43ca1600738 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n {\n \tstruct strvec args = STRVEC_INIT;\n \tconst char *diff_algorithm = s->s.interactive_diff_algorithm;\n+\tint diff_context = s->s.context;\n+\tint diff_interhunkcontext = s->s.interhunkcontext;\n \tstruct strbuf *plain = &s->plain, *colored = NULL;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\\0';\n@@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \tint res;\n \n \tstrvec_pushv(&args, s->mode->diff_cmd);\n+\tif (diff_context != -1)\n+\t\tstrvec_pushf(&args, \"--unified=%i\", diff_context);\n+\tif (diff_interhunkcontext != -1)\n+\t\tstrvec_pushf(&args, \"--inter-hunk-context=%i\", diff_interhunkcontext);\n \tif (diff_algorithm)\n \t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", diff_algorithm);\n \tif (s->revision) {\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex 1384a8195705..c4b861c360cc 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -52,6 +52,46 @@ test_expect_success 'diff.context honored by \"log\"' '\n \ttest_grep \"^ firstline\" output\n '\n \n+test_expect_success 'diff.context honored by \"add\"' '\n+\tgit add -p >output &&\n+\ttest_grep ! firstline output &&\n+\ttest_config diff.context 8 &&\n+\tgit log -1 -p >output &&\n+\ttest_grep \"^ firstline\" output\n+'\n+\n+test_expect_success 'diff.context honored by \"commit\"' '\n+\t! git commit -p >output &&\n+\ttest_grep ! firstline output &&\n+\ttest_config diff.context 8 &&\n+\t! git commit -p >output &&\n+\ttest_grep \"^ firstline\" output\n+'\n+\n+test_expect_success 'diff.context honored by \"checkout\"' '\n+\tgit checkout -p >output &&\n+\ttest_grep ! firstline output &&\n+\ttest_config diff.context 8 &&\n+\tgit checkout -p >output &&\n+\ttest_grep \"^ firstline\" output\n+'\n+\n+test_expect_success 'diff.context honored by \"stash\"' '\n+\t! git stash -p >output &&\n+\ttest_grep ! firstline output &&\n+\ttest_config diff.context 8 &&\n+\t! git stash -p >output &&\n+\ttest_grep \"^ firstline\" output\n+'\n+\n+test_expect_success 'diff.context honored by \"restore\"' '\n+\tgit restore -p >output &&\n+\ttest_grep ! firstline output &&\n+\ttest_config diff.context 8 &&\n+\tgit restore -p >output &&\n+\ttest_grep \"^ firstline\" output\n+'\n+\n test_expect_success 'The -U option overrides diff.context' '\n \ttest_config diff.context 8 &&\n \tgit log -U4 -1 >output &&\n@@ -82,6 +122,36 @@ test_expect_success 'negative integer config parsing' '\n \ttest_grep \"bad config variable\" output\n '\n \n+test_expect_success 'negative integer config parsing by \"add\"' '\n+\ttest_config diff.context -1 &&\n+\ttest_must_fail git add -p 2>output &&\n+\ttest_grep \"diff.context cannot be negative\" output\n+'\n+\n+test_expect_success 'negative integer config parsing by \"commit\"' '\n+\ttest_config diff.context -1 &&\n+\ttest_must_fail git commit -p 2>output &&\n+\ttest_grep \"bad config variable\" output\n+'\n+\n+test_expect_success 'negative integer config parsing by \"checkout\"' '\n+\ttest_config diff.context -1 &&\n+\ttest_must_fail git checkout -p 2>output &&\n+\ttest_grep \"diff.context cannot be negative\" output\n+'\n+\n+test_expect_success 'negative integer config parsing by \"stash\"' '\n+\ttest_config diff.context -1 &&\n+\ttest_must_fail git stash -p 2>output &&\n+\ttest_grep \"diff.context cannot be negative\" output\n+'\n+\n+test_expect_success 'negative integer config parsing by \"restore\"' '\n+\ttest_config diff.context -1 &&\n+\ttest_must_fail git restore -p 2>output &&\n+\ttest_grep \"diff.context cannot be negative\" output\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":"517739","messageId":"973dfadd1b3c7e214b14c75494b3b3b786227362.1746884789.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v2.git.1746884789.gitgitgadget@gmail.com","subject":"[PATCH v2 4/4] add-patch: add diff.context command line overrides","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-05-10T13:46:29Z","receivedAt":"2025-05-10T13:46:38Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nThis patch compliments the previous commit, where builtins that use\nadd-patch infrastructure now respect diff.context and\ndiff.interHunkContext file configurations.\n\nIn particular, this patch helps users who don't want to set persistent\ncontext configurations or just want a way to override them on a one-time\nbasis, by allowing the relevant builtins to accept corresponding command\nline options that override the file configurations.\n\nThis mimics commands such as diff and log, which allow for both context\nfile configuration and command line overrides.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n Documentation/diff-context-options.adoc | 10 ++++\n Documentation/git-add.adoc              |  2 +\n Documentation/git-checkout.adoc         |  2 +\n Documentation/git-commit.adoc           |  2 +\n Documentation/git-reset.adoc            |  2 +\n Documentation/git-restore.adoc          |  2 +\n Documentation/git-stash.adoc            |  2 +\n add-interactive.c                       | 36 +++++++++++----\n add-interactive.h                       | 16 +++++--\n add-patch.c                             |  5 +-\n builtin/add.c                           | 16 +++++--\n builtin/checkout.c                      | 24 ++++++++--\n builtin/commit.c                        | 11 ++++-\n builtin/reset.c                         | 12 ++++-\n builtin/stash.c                         | 46 ++++++++++++++-----\n commit.h                                |  3 +-\n parse-options.h                         |  2 +\n t/t4032-diff-inter-hunk-context.sh      | 61 +++++++++++++++++++++++++\n t/t4055-diff-context.sh                 | 30 ++++++++++++\n t/t9902-completion.sh                   |  2 +\n 20 files changed, 251 insertions(+), 35 deletions(-)\n create mode 100644 Documentation/diff-context-options.adoc\n\ndiff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc\nnew file mode 100644\nindex 000000000000..e161260358ff\n--- /dev/null\n+++ b/Documentation/diff-context-options.adoc\n@@ -0,0 +1,10 @@\n+`-U<n>`::\n+`--unified=<n>`::\n+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n+\tor 3 if the config option is unset.\n+\n+`--inter-hunk-context=<n>`::\n+\tShow the context between diff hunks, up to the specified _<number>_\n+\tof lines, thereby fusing hunks that are close to each other.\n+\tDefaults to `diff.interHunkContext` or 0 if the config option\n+\tis unset.\ndiff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\nindex eba0b419ce50..b7a735824d6c 100644\n--- a/Documentation/git-add.adoc\n+++ b/Documentation/git-add.adoc\n@@ -104,6 +104,8 @@ This effectively runs `add --interactive`, but bypasses the\n initial command menu and directly jumps to the `patch` subcommand.\n See ``Interactive mode'' for details.\n \n+include::diff-context-options.adoc[]\n+\n `-e`::\n `--edit`::\n \tOpen the diff vs. the index in an editor and let the user\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex a66c53a5cd1e..896372fd7a29 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -289,6 +289,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.\n Note that this option uses the no overlay mode by default (see also\n `--overlay`), and currently doesn't support overlay mode.\n \n+include::diff-context-options.adoc[]\n+\n --ignore-other-worktrees::\n \t`git checkout` refuses when the wanted branch is already checked\n \tout or otherwise in use by another worktree. This option makes\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex dc219025f1eb..ae988a883b5b 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -76,6 +76,8 @@ OPTIONS\n \twhich changes to commit. See linkgit:git-add[1] for\n \tdetails.\n \n+include::diff-context-options.adoc[]\n+\n `-C <commit>`::\n `--reuse-message=<commit>`::\n \tTake an existing _<commit>_ object, and reuse the log message\ndiff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc\nindex 53ab88c5451c..50e8a0ba6f66 100644\n--- a/Documentation/git-reset.adoc\n+++ b/Documentation/git-reset.adoc\n@@ -125,6 +125,8 @@ OPTIONS\n \tseparated with _NUL_ character and all other characters are taken\n \tliterally (including newlines and quotes).\n \n+include::diff-context-options.adoc[]\n+\n `--`::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc\nindex 877b7772e667..1dcc2bb7aea3 100644\n--- a/Documentation/git-restore.adoc\n+++ b/Documentation/git-restore.adoc\n@@ -52,6 +52,8 @@ leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to\n \tMode\" section of linkgit:git-add[1] to learn how to operate\n \tthe `--patch` mode.\n \n+include::diff-context-options.adoc[]\n+\n `-W`::\n `--worktree`::\n `-S`::\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex 1a5177f4986c..0578c619c410 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -208,6 +208,8 @@ to learn how to operate the `--patch` mode.\n The `--patch` option implies `--keep-index`.  You can use\n `--no-keep-index` to override this.\n \n+include::diff-context-options.adoc[]\n+\n -S::\n --staged::\n \tThis option is only valid for `push` and `save` commands.\ndiff --git a/add-interactive.c b/add-interactive.c\nindex cac036441caf..496a44cfe4b6 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -36,7 +36,8 @@ static void init_color(struct repository *r, struct add_i_state *s,\n \tfree(key);\n }\n \n-void init_add_i_state(struct add_i_state *s, struct repository *r)\n+void init_add_i_state(struct add_i_state *s, struct repository *r,\n+\t\t      struct add_p_opt *add_p_opt)\n {\n \tconst char *value;\n \tint context;\n@@ -98,6 +99,17 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n \tif (s->use_single_key)\n \t\tsetbuf(stdin, NULL);\n+\n+\tif (add_p_opt->context != -1) {\n+\t\tif (add_p_opt->context < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"--unified\");\n+\t\ts->context = add_p_opt->context;\n+\t}\n+\tif (add_p_opt->interhunkcontext != -1) {\n+\t\tif (add_p_opt->interhunkcontext < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"--inter-hunk-context\");\n+\t\ts->interhunkcontext = add_p_opt->interhunkcontext;\n+\t}\n }\n \n void clear_add_i_state(struct add_i_state *s)\n@@ -986,6 +998,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,\n \topts->prompt = N_(\"Patch update\");\n \tcount = list_and_choose(s, files, opts);\n \tif (count > 0) {\n+\t\tstruct add_p_opt add_p_opt = {\n+\t\t\t.context = s->context,\n+\t\t\t.interhunkcontext = s->interhunkcontext,\n+\t\t};\n \t\tstruct strvec args = STRVEC_INIT;\n \t\tstruct pathspec ps_selected = { 0 };\n \n@@ -996,7 +1012,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,\n \t\tparse_pathspec(&ps_selected,\n \t\t\t       PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,\n \t\t\t       PATHSPEC_LITERAL_PATH, \"\", args.v);\n-\t\tres = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);\n+\t\tres = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected);\n \t\tstrvec_clear(&args);\n \t\tclear_pathspec(&ps_selected);\n \t}\n@@ -1031,10 +1047,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,\n \tif (count > 0) {\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n-\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\",\n-\t\t\t     oid_to_hex(!is_initial ? &oid :\n-\t\t\t\t\ts->r->hash_algo->empty_tree),\n-\t\t\t     \"--\", NULL);\n+\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\", NULL);\n+\t\tif (s->context != -1)\n+\t\t\tstrvec_pushf(&cmd.args, \"--unified=%i\", s->context);\n+\t\tif (s->interhunkcontext != -1)\n+\t\t\tstrvec_pushf(&cmd.args, \"--inter-hunk-context=%i\", s->interhunkcontext);\n+\t\tstrvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :\n+\t\t\t     s->r->hash_algo->empty_tree), \"--\", NULL);\n \t\tfor (i = 0; i < files->items.nr; i++)\n \t\t\tif (files->selected[i])\n \t\t\t\tstrvec_push(&cmd.args,\n@@ -1127,7 +1146,8 @@ static void command_prompt_help(struct add_i_state *s)\n \t\t\t _(\"(empty) select nothing\"));\n }\n \n-int run_add_i(struct repository *r, const struct pathspec *ps)\n+int run_add_i(struct repository *r, const struct pathspec *ps,\n+\t      struct add_p_opt *add_p_opt)\n {\n \tstruct add_i_state s = { NULL };\n \tstruct print_command_item_data data = { \"[\", \"]\" };\n@@ -1170,7 +1190,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t\t->util = util;\n \t}\n \n-\tinit_add_i_state(&s, r);\n+\tinit_add_i_state(&s, r, add_p_opt);\n \n \t/*\n \t * When color was asked for, use the prompt color for\ndiff --git a/add-interactive.h b/add-interactive.h\nindex c63f35b14be8..4213dcd67b9a 100644\n--- a/add-interactive.h\n+++ b/add-interactive.h\n@@ -3,6 +3,13 @@\n \n #include \"color.h\"\n \n+struct add_p_opt {\n+\tint context;\n+\tint interhunkcontext;\n+};\n+\n+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }\n+\n struct add_i_state {\n \tstruct repository *r;\n \tint use_color;\n@@ -21,12 +28,14 @@ struct add_i_state {\n \tint context, interhunkcontext;\n };\n \n-void init_add_i_state(struct add_i_state *s, struct repository *r);\n+void init_add_i_state(struct add_i_state *s, struct repository *r,\n+\t\t      struct add_p_opt *add_p_opt);\n void clear_add_i_state(struct add_i_state *s);\n \n struct repository;\n struct pathspec;\n-int run_add_i(struct repository *r, const struct pathspec *ps);\n+int run_add_i(struct repository *r, const struct pathspec *ps,\n+\t      struct add_p_opt *add_p_opt);\n \n enum add_p_mode {\n \tADD_P_ADD,\n@@ -37,6 +46,7 @@ enum add_p_mode {\n };\n \n int run_add_p(struct repository *r, enum add_p_mode mode,\n-\t      const char *revision, const struct pathspec *ps);\n+\t      struct add_p_opt *o, const char *revision,\n+\t      const struct pathspec *ps);\n \n #endif\ndiff --git a/add-patch.c b/add-patch.c\nindex b43ca1600738..c0d33820c558 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -1766,14 +1766,15 @@ soft_increment:\n }\n \n int run_add_p(struct repository *r, enum add_p_mode mode,\n-\t      const char *revision, const struct pathspec *ps)\n+\t      struct add_p_opt *o, const char *revision,\n+\t      const struct pathspec *ps)\n {\n \tstruct add_p_state s = {\n \t\t{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tsize_t i, binary_count = 0;\n \n-\tinit_add_i_state(&s.s, r);\n+\tinit_add_i_state(&s.s, r, o);\n \n \tif (mode == ADD_P_STASH)\n \t\ts.mode = &patch_mode_stash;\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 78dfb2657767..a7e6c77e7a74 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -29,6 +29,7 @@ static const char * const builtin_add_usage[] = {\n \tNULL\n };\n static int patch_interactive, add_interactive, edit_interactive;\n+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n static int take_worktree_changes;\n static int add_renormalize;\n static int pathspec_file_nul;\n@@ -157,7 +158,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec *\n int interactive_add(struct repository *repo,\n \t\t    const char **argv,\n \t\t    const char *prefix,\n-\t\t    int patch)\n+\t\t    int patch, struct add_p_opt *add_p_opt)\n {\n \tstruct pathspec pathspec;\n \tint ret;\n@@ -169,9 +170,9 @@ int interactive_add(struct repository *repo,\n \t\t       prefix, argv);\n \n \tif (patch)\n-\t\tret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);\n+\t\tret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec);\n \telse\n-\t\tret = !!run_add_i(repo, &pathspec);\n+\t\tret = !!run_add_i(repo, &pathspec, add_p_opt);\n \n \tclear_pathspec(&pathspec);\n \treturn ret;\n@@ -253,6 +254,8 @@ static struct option builtin_add_options[] = {\n \tOPT_GROUP(\"\"),\n \tOPT_BOOL('i', \"interactive\", &add_interactive, N_(\"interactive picking\")),\n \tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"select hunks interactively\")),\n+\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \tOPT_BOOL('e', \"edit\", &edit_interactive, N_(\"edit current diff and apply\")),\n \tOPT__FORCE(&ignored_too, N_(\"allow adding otherwise ignored files\"), 0),\n \tOPT_BOOL('u', \"update\", &take_worktree_changes, N_(\"update tracked files\")),\n@@ -398,7 +401,12 @@ int cmd_add(int argc,\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--dry-run\", \"--interactive/--patch\");\n \t\tif (pathspec_from_file)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--pathspec-from-file\", \"--interactive/--patch\");\n-\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive));\n+\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--interactive/--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--interactive/--patch\");\n \t}\n \n \tif (edit_interactive) {\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex d185982f3a63..b0b05b71bc29 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -61,6 +61,8 @@ static const char * const restore_usage[] = {\n \n struct checkout_opts {\n \tint patch_mode;\n+\tint patch_context;\n+\tint patch_interhunk_context;\n \tint quiet;\n \tint merge;\n \tint force;\n@@ -104,7 +106,12 @@ struct checkout_opts {\n \tstruct tree *source_tree;\n };\n \n-#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 }\n+#define CHECKOUT_OPTS_INIT { \\\n+\t.conflict_style = -1, \\\n+\t.merge = -1, \\\n+\t.patch_context = -1, \\\n+\t.patch_interhunk_context = -1, \\\n+}\n \n struct branch_info {\n \tchar *name; /* The short name used */\n@@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts,\n \n \tif (opts->patch_mode) {\n \t\tenum add_p_mode patch_mode;\n+\t\tstruct add_p_opt add_p_opt = {\n+\t\t\t.context = opts->patch_context,\n+\t\t\t.interhunkcontext = opts->patch_interhunk_context,\n+\t\t};\n \t\tconst char *rev = new_branch_info->name;\n \t\tchar rev_oid[GIT_MAX_HEXSZ + 1];\n \n@@ -564,8 +575,13 @@ static int checkout_paths(const struct checkout_opts *opts,\n \t\telse\n \t\t\tBUG(\"either flag must have been set, worktree=%d, index=%d\",\n \t\t\t    opts->checkout_worktree, opts->checkout_index);\n-\t\treturn !!run_add_p(the_repository, patch_mode, rev,\n-\t\t\t\t   &opts->pathspec);\n+\t\treturn !!run_add_p(the_repository, patch_mode, &add_p_opt,\n+\t\t\t\t   rev, &opts->pathspec);\n+\t} else {\n+\t\tif (opts->patch_context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (opts->patch_interhunk_context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n \t}\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n@@ -1738,6 +1754,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,\n \t\t\t      N_(\"checkout their version for unmerged files\"),\n \t\t\t      3, PARSE_OPT_NONEG),\n \t\tOPT_BOOL('p', \"patch\", &opts->patch_mode, N_(\"select hunks interactively\")),\n+\t\tOPT_DIFF_UNIFIED(&opts->patch_context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context),\n \t\tOPT_BOOL(0, \"ignore-skip-worktree-bits\", &opts->ignore_skipworktree,\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n \t\tOPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 66bd91fd523d..19ec0ccb2bef 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -19,6 +19,7 @@\n #include \"environment.h\"\n #include \"diff.h\"\n #include \"commit.h\"\n+#include \"add-interactive.h\"\n #include \"gettext.h\"\n #include \"revision.h\"\n #include \"wt-status.h\"\n@@ -122,6 +123,7 @@ static const char *edit_message, *use_message;\n static char *fixup_message, *fixup_commit, *squash_message;\n static const char *fixup_prefix;\n static int all, also, interactive, patch_interactive, only, amend, signoff;\n+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n static int edit_flag = -1; /* unspecified */\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int config_commit_verbose = -1; /* unspecified */\n@@ -400,7 +402,7 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n \t\tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n \n-\t\tif (interactive_add(the_repository, argv, prefix, patch_interactive) != 0)\n+\t\tif (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0)\n \t\t\tdie(_(\"interactive add failed\"));\n \n \t\tthe_repository->index_file = old_repo_index_file;\n@@ -424,6 +426,11 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\tcommit_style = COMMIT_NORMAL;\n \t\tret = get_lock_file_path(&index_lock);\n \t\tgoto out;\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--interactive/--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--interactive/--patch\");\n \t}\n \n \t/*\n@@ -1722,6 +1729,8 @@ int cmd_commit(int argc,\n \t\tOPT_BOOL('i', \"include\", &also, N_(\"add specified files to index for commit\")),\n \t\tOPT_BOOL(0, \"interactive\", &interactive, N_(\"interactively add files\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"interactively add changes\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT_BOOL('o', \"only\", &only, N_(\"commit only specified files\")),\n \t\tOPT_BOOL('n', \"no-verify\", &no_verify, N_(\"bypass pre-commit and commit-msg hooks\")),\n \t\tOPT_BOOL(0, \"dry-run\", &dry_run, N_(\"show what would be committed\")),\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 73b4537a9a56..62db07b0c231 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -346,6 +346,7 @@ int cmd_reset(int argc,\n \tstruct object_id oid;\n \tstruct pathspec pathspec;\n \tint intent_to_add = 0;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tconst struct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"no-refresh\", &no_refresh,\n@@ -370,6 +371,8 @@ int cmd_reset(int argc,\n \t\t\t       PARSE_OPT_OPTARG,\n \t\t\t       option_parse_recurse_submodules_worktree_updater),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode, N_(\"select hunks interactively\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT_BOOL('N', \"intent-to-add\", &intent_to_add,\n \t\t\t\tN_(\"record only the fact that removed paths will be added later\")),\n \t\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n@@ -424,9 +427,14 @@ int cmd_reset(int argc,\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--patch\", \"--{hard,mixed,soft}\");\n \t\ttrace2_cmd_mode(\"patch-interactive\");\n-\t\tupdate_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev,\n-\t\t\t\t   &pathspec);\n+\t\tupdate_ref_status = !!run_add_p(the_repository, ADD_P_RESET,\n+\t\t\t\t\t\t&add_p_opt, rev, &pathspec);\n \t\tgoto cleanup;\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n \t}\n \n \t/* git reset tree [--] paths... can be used to\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex cfbd92852a65..1c68d50ce543 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1242,7 +1242,8 @@ done:\n }\n \n static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n-\t\t       struct strbuf *out_patch, int quiet)\n+\t\t       struct strbuf *out_patch, int quiet,\n+\t\t       struct add_p_opt *add_p_opt)\n {\n \tint ret = 0;\n \tstruct child_process cp_read_tree = CHILD_PROCESS_INIT;\n@@ -1267,7 +1268,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n \told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n \tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n \n-\tret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps);\n+\tret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps);\n \n \tthe_repository->index_file = old_repo_index_file;\n \tif (old_index_env && *old_index_env)\n@@ -1362,8 +1363,8 @@ done:\n }\n \n static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,\n-\t\t\t   int include_untracked, int patch_mode, int only_staged,\n-\t\t\t   struct stash_info *info, struct strbuf *patch,\n+\t\t\t   int include_untracked, int patch_mode, struct add_p_opt *add_p_opt,\n+\t\t\t   int only_staged, struct stash_info *info, struct strbuf *patch,\n \t\t\t   int quiet)\n {\n \tint ret = 0;\n@@ -1439,7 +1440,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\tuntracked_commit_option = 1;\n \t}\n \tif (patch_mode) {\n-\t\tret = stash_patch(info, ps, patch, quiet);\n+\t\tret = stash_patch(info, ps, patch, quiet, add_p_opt);\n \t\tif (ret < 0) {\n \t\t\tif (!quiet)\n \t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n@@ -1513,7 +1514,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n \tif (!check_changes_tracked_files(&ps))\n \t\treturn 0;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n+\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,\n \t\t\t      NULL, 0);\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n@@ -1524,7 +1525,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n }\n \n static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,\n-\t\t\t int keep_index, int patch_mode, int include_untracked, int only_staged)\n+\t\t\t int keep_index, int patch_mode, struct add_p_opt *add_p_opt,\n+\t\t\t int include_untracked, int only_staged)\n {\n \tint ret = 0;\n \tstruct stash_info info = STASH_INFO_INIT;\n@@ -1594,8 +1596,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \n \tif (stash_msg)\n \t\tstrbuf_addstr(&stash_msg_buf, stash_msg);\n-\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,\n-\t\t\t    &info, &patch, quiet)) {\n+\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,\n+\t\t\t    add_p_opt, only_staged, &info, &patch, quiet)) {\n \t\tret = -1;\n \t\tgoto done;\n \t}\n@@ -1768,6 +1770,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \tconst char *stash_msg = NULL;\n \tchar *pathspec_from_file = NULL;\n \tstruct pathspec ps;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n@@ -1775,6 +1778,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n \t\t\t N_(\"include untracked files in stash\")),\n@@ -1826,8 +1831,15 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--pathspec-file-nul\", \"--pathspec-from-file\");\n \t}\n \n+\tif (!patch_mode) {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,\n-\t\t\t    include_untracked, only_staged);\n+\t\t\t    &add_p_opt, include_untracked, only_staged);\n \n \tclear_pathspec(&ps);\n \tfree(pathspec_from_file);\n@@ -1852,6 +1864,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \tconst char *stash_msg = NULL;\n \tstruct pathspec ps;\n \tstruct strbuf stash_msg_buf = STRBUF_INIT;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n@@ -1859,6 +1872,8 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n \t\t\t N_(\"include untracked files in stash\")),\n@@ -1877,8 +1892,17 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \t\tstash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');\n \n \tmemset(&ps, 0, sizeof(ps));\n+\n+\tif (!patch_mode) {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index,\n-\t\t\t    patch_mode, include_untracked, only_staged);\n+\t\t\t    patch_mode, &add_p_opt, include_untracked,\n+\t\t\t    only_staged);\n \n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\ndiff --git a/commit.h b/commit.h\nindex 70c870dae4d4..7a7fedbc2f14 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -2,6 +2,7 @@\n #define COMMIT_H\n \n #include \"object.h\"\n+#include \"add-interactive.h\"\n \n struct signature_check;\n struct strbuf;\n@@ -257,7 +258,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *);\n int interactive_add(struct repository *repo,\n \t\t    const char **argv,\n \t\t    const char *prefix,\n-\t\t    int patch);\n+\t\t    int patch, struct add_p_opt *add_p_opt);\n \n struct commit_extra_header {\n \tstruct commit_extra_header *next;\ndiff --git a/parse-options.h b/parse-options.h\nindex 91c3e3c29b3d..bdae8f116198 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -616,6 +616,8 @@ int parse_opt_tracking_mode(const struct option *, const char *, int);\n #define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, \"pathspec-from-file\", v, N_(\"read pathspec from file\"))\n #define OPT_PATHSPEC_FILE_NUL(v)  OPT_BOOL(0, \"pathspec-file-nul\", v, N_(\"with --pathspec-from-file, pathspec elements are separated with NUL character\"))\n #define OPT_AUTOSTASH(v) OPT_BOOL(0, \"autostash\", v, N_(\"automatically stash/stash pop before and after\"))\n+#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', \"unified\", v, N_(\"generate diffs with <n> lines context\"), PARSE_OPT_NONEG)\n+#define OPT_DIFF_INTERHUNK_CONTEXT(v) OPT_INTEGER_F(0, \"inter-hunk-context\", v, N_(\"show context between diff hunks up to the specified number of lines\"), PARSE_OPT_NONEG)\n \n #define OPT_IPVERSION(v) \\\n \tOPT_SET_INT_F('4', \"ipv4\", (v), N_(\"use IPv4 addresses only\"), \\\ndiff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh\nindex bada0cbd32f7..d5aad6e143a7 100755\n--- a/t/t4032-diff-inter-hunk-context.sh\n+++ b/t/t4032-diff-inter-hunk-context.sh\n@@ -47,6 +47,31 @@ t() {\n \t\"\n }\n \n+t_patch() {\n+\tuse_config=\n+\tgit config --unset diff.interHunkContext\n+\n+\tcase $# in\n+\t4) hunks=$4; cmd=\"add -p -U$3\";;\n+\t5) hunks=$5; cmd=\"add -p -U$3 --inter-hunk-context=$4\";;\n+\t6) hunks=$5; cmd=\"add -p -U$3\"; git config diff.interHunkContext $4; use_config=\"(diff.interHunkContext=$4) \";;\n+\tesac\n+\tlabel=\"$use_config$cmd, $1 common $2\"\n+\tfile=f$1\n+\n+\tif ! test -f $file\n+\tthen\n+\t\tf A $1 B >$file\n+\t\tgit add $file\n+\t\tgit commit -q -m. $file\n+\t\tf X $1 Y >$file\n+\tfi\n+\n+\ttest_expect_success \"$label: count hunks ($hunks)\" \"\n+\t\ttest $(test_write_lines q | git $cmd $file | sed -n 's/^([0-9]*\\/\\([0-9]*\\)) Stage this hunk.*/\\1/p') = $hunks\n+\t\"\n+}\n+\n cat <<EOF >expected.f1.0.1 || exit 1\n diff --git a/f1 b/f1\n --- a/f1\n@@ -107,6 +132,42 @@ t 3 lines\t1\t2\t1\tconfig\n t 9 lines\t3\t2\t2\tconfig\n t 9 lines\t3\t3\t1\tconfig\n \n+# common lines\tctx\tintrctx\thunks\n+t_patch 1 line\t0\t\t2\n+t_patch 1 line\t0\t0\t2\n+t_patch 1 line\t0\t1\t1\n+t_patch 1 line\t0\t2\t1\n+t_patch 1 line\t1\t\t1\n+\n+t_patch 2 lines\t0\t\t2\n+t_patch 2 lines\t0\t0\t2\n+t_patch 2 lines\t0\t1\t2\n+t_patch 2 lines\t0\t2\t1\n+t_patch 2 lines\t1\t\t1\n+\n+t_patch 3 lines\t1\t\t2\n+t_patch 3 lines\t1\t0\t2\n+t_patch 3 lines\t1\t1\t1\n+t_patch 3 lines\t1\t2\t1\n+\n+t_patch 9 lines\t3\t\t2\n+t_patch 9 lines\t3\t2\t2\n+t_patch 9 lines\t3\t3\t1\n+\n+#\t\t\t\t\tuse diff.interHunkContext?\n+t_patch 1 line\t0\t0\t2\tconfig\n+t_patch 1 line\t0\t1\t1\tconfig\n+t_patch 1 line\t0\t2\t1\tconfig\n+t_patch 9 lines\t3\t3\t1\tconfig\n+t_patch 2 lines\t0\t0\t2\tconfig\n+t_patch 2 lines\t0\t1\t2\tconfig\n+t_patch 2 lines\t0\t2\t1\tconfig\n+t_patch 3 lines\t1\t0\t2\tconfig\n+t_patch 3 lines\t1\t1\t1\tconfig\n+t_patch 3 lines\t1\t2\t1\tconfig\n+t_patch 9 lines\t3\t2\t2\tconfig\n+t_patch 9 lines\t3\t3\t1\tconfig\n+\n test_expect_success 'diff.interHunkContext invalid' '\n \tgit config diff.interHunkContext asdf &&\n \ttest_must_fail git diff &&\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex c4b861c360cc..07d993ba6762 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -98,6 +98,36 @@ test_expect_success 'The -U option overrides diff.context' '\n \ttest_grep ! \"^ firstline\" output\n '\n \n+test_expect_success 'The -U option overrides diff.context for \"add\"' '\n+\ttest_config diff.context 8 &&\n+\tgit add -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"commit\"' '\n+\ttest_config diff.context 8 &&\n+\t! git commit -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"checkout\"' '\n+\ttest_config diff.context 8 &&\n+\tgit checkout -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"stash\"' '\n+\ttest_config diff.context 8 &&\n+\t! git stash -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"restore\"' '\n+\ttest_config diff.context 8 &&\n+\tgit restore -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n test_expect_success 'diff.context honored by \"diff\"' '\n \ttest_config diff.context 8 &&\n \tgit diff >output &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 343b8cd1912b..6650d33fba69 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2596,6 +2596,8 @@ test_expect_success 'double dash \"git checkout\"' '\n \t--merge Z\n \t--conflict=Z\n \t--patch Z\n+\t--unified=Z\n+\t--inter-hunk-context=Z\n \t--ignore-skip-worktree-bits Z\n \t--ignore-other-worktrees Z\n \t--recurse-submodules Z\n-- \ngitgitgadget\n"},{"id":"517845","messageId":"xmqqo6vyvtcf.fsf@gitster.g","threadId":"63396","inReplyTo":"4f92a1b4c24a6942fc55d305865bb55833d13c45.1746884789.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/4] test: refactor to use \"test_grep\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-12T13:42:08Z","receivedAt":"2025-05-12T13:42:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Leon Michalak <leonmichalak6@gmail.com>\n>\n> Refactor to use the modern \"test_grep\" test utility instead of regular\n> \"grep\" which provides better debug information if tests fail.\n>\n> This is a prerequisite to the commits that follow which add to both test\n> files.\n>\n> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n> ---\n\nThese mostly look sensible, but I would title & phrase the commit\ndescription to 'use \"test_grep\"', not 'refactor to &'.  It's shorter\nand more direct ;-)\n\nThanks.\n"},{"id":"517870","messageId":"xmqq5xi5vkvi.fsf@gitster.g","threadId":"63396","inReplyTo":"973dfadd1b3c7e214b14c75494b3b3b786227362.1746884789.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-12T16:45:05Z","receivedAt":"2025-05-12T16:45:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc\n> new file mode 100644\n> index 000000000000..e161260358ff\n> --- /dev/null\n> +++ b/Documentation/diff-context-options.adoc\n> @@ -0,0 +1,10 @@\n> +`-U<n>`::\n> +`--unified=<n>`::\n> +\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n> +\tor 3 if the config option is unset.\n> +\n> +`--inter-hunk-context=<n>`::\n> +\tShow the context between diff hunks, up to the specified _<number>_\n> +\tof lines, thereby fusing hunks that are close to each other.\n> +\tDefaults to `diff.interHunkContext` or 0 if the config option\n> +\tis unset.\n\nIt might not be trivial to do but I wonder if we cannot do better\nthan this to share more of the same text across manual pages.  These\ntwo being options understood by `git diff`, we certainly have an\nexisting description for them, no?\n\nOther than that, looking good to me.\n\nThanks.\n"},{"id":"517872","messageId":"CAP9jKjEYgEBBGrPnJ8fkaWuS8RPzBeBqFLE7aTJd5x9PcWu=7Q@mail.gmail.com","threadId":"63396","inReplyTo":"xmqqo6vyvtcf.fsf@gitster.g","subject":"Re: [PATCH v2 1/4] test: refactor to use \"test_grep\"","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-05-12T16:58:30Z","receivedAt":"2025-05-12T16:58:43Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"On Mon, 12 May 2025 at 14:42, Junio C Hamano <gitster@pobox.com> wrote:\n> These mostly look sensible, but I would title & phrase the commit\n> description to 'use \"test_grep\"', not 'refactor to &'.  It's shorter\n> and more direct ;-)\n\nThanks - will make sure to change that in v3 :)\n"},{"id":"517874","messageId":"CAP9jKjFZ-JNwiMrMB2XFNWq45n-LrFfUvSQEj1QWiBRCjp_tOQ@mail.gmail.com","threadId":"63396","inReplyTo":"xmqq5xi5vkvi.fsf@gitster.g","subject":"Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-05-12T17:03:57Z","receivedAt":"2025-05-12T17:04:11Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"On Mon, 12 May 2025 at 17:45, Junio C Hamano <gitster@pobox.com> wrote:\n> It might not be trivial to do but I wonder if we cannot do better\n> than this to share more of the same text across manual pages.  These\n> two being options understood by `git diff`, we certainly have an\n> existing description for them, no?\n\nYes, I did of course notice documentation for `git diff` also has\nthese; ultimately my justification for not changing that to use this\nnew .adoc include as well was for a couple reasons:\n- these two options are not together in the `git diff` documentation\n(not *so* important, and they probably should actually be together?)\n- there is an extra if def which adds on \"implies --patch\" text in the\n`git diff` documentation which isn't the behaviour the add-patch\ncommands are going for, so that makes the intent a little different\nhere\n\nBut would be good to hear if anyone else has any thoughts.\n"},{"id":"517947","messageId":"f00cdfa1-343a-4fa0-bce5-e06d1ed62c22@gmail.com","threadId":"63396","inReplyTo":"f16d3de86110ee61599459a25764248b52883b52.1746884789.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 3/4] add-patch: respect diff.context configuration","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-05-13T13:52:25Z","receivedAt":"2025-05-13T13:52:28Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Leon\n\nOn 10/05/2025 14:46, Leon Michalak via GitGitGadget wrote:\n> From: Leon Michalak <leonmichalak6@gmail.com>\n> \n> Various builtins that use add-patch infrastructure do not respect\n> the user's diff.context and diff.interHunkContext file configurations.\n\nWe could expand this slightly by adding\n\nThis is because the plumbing commands used by \"git add -p\" to generate\nthe diff do not read those config settings. Fix this by reading the\nconfig before generating the patch and passing it along to the diff\ncommand with the \"-U\" and \"--inter-hunk-context\" command-line options.\n\n> This patch fixes this inconsistency.\n> \n> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n> ---\n\n> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n>   \trepo_config_get_string(r, \"diff.algorithm\",\n>   \t\t\t       &s->interactive_diff_algorithm);\n>   \n> +\tif (!repo_config_get_int(r, \"diff.context\", &context)) {\n> +\t\tif (context < 0)\n> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n> +\t\telse\n> +\t\t\ts->context = context;\n> +\t};\n> +\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &interhunkcontext)) {\n> +\t\tif (interhunkcontext < 0)\n> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n> +\t\telse\n> +\t\t\ts->interhunkcontext = interhunkcontext;\n> +\t};\n\nThanks for changing this. This iteration of the code changes looks good\n\n> diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\n> index 1384a8195705..c4b861c360cc 100755\n> --- a/t/t4055-diff-context.sh\n> +++ b/t/t4055-diff-context.sh\n> @@ -52,6 +52,46 @@ test_expect_success 'diff.context honored by \"log\"' '\n>   \ttest_grep \"^ firstline\" output\n>   '\n\nIt's great that you have written tests for this patch but as I said\nlast time I think the new tests should be in t3701-add-interactive.sh\nas we're interested in testing whether \"git add -p\" passes on\ndiff.context to \"git diff\" , not whether \"git diff\" respects\ndiff.context. I still think there are too many tests here as we know\nthat all the different \"-p\" commands share a single code path. Our\ntest suite is slow enough already so we do not want to add new tests\nthat do not increase our code coverage. I would suggest removing\nthese tests and instead add the following in t3701\n\ntest_expect_success 'add -p respects diff.context' '\n\ttest_write_lines a b c d e f g h i j k l m >file &&\n\tgit add file &&\n\ttest_write_lines a b c d e f G h i j k l m >file &&\n\techo y | git -c diff.context=5 add -p >actual &&\n\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n'\n\ntest_expect_success 'add -p respects diff.interHunkContext' '\n\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n\tgit add file &&\n\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n'\n\n> +test_expect_success 'negative integer config parsing by \"add\"' '\n\nPerhaps \"add -p rejects negative diff.context\" would be clearer?\n\n> +\ttest_config diff.context -1 &&\n> +\ttest_must_fail git add -p 2>output &&\n> +\ttest_grep \"diff.context cannot be negative\" output\n> +'\n\nThis is great but again we only need to test a single command and we\nshould do so in t3701. We should also check that negative values of\ndiff.interHunkContext are also rejected.\n\nBest Wishes\n\nPhillip\n"},{"id":"517948","messageId":"431d6fec-3955-4703-8bb3-376eae7ff052@gmail.com","threadId":"63396","inReplyTo":"973dfadd1b3c7e214b14c75494b3b3b786227362.1746884789.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-05-13T13:52:31Z","receivedAt":"2025-05-13T13:52:34Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Leon\n\nOn 10/05/2025 14:46, Leon Michalak via GitGitGadget wrote:\n> From: Leon Michalak <leonmichalak6@gmail.com>\n> \n> This patch compliments the previous commit, where builtins that use\n> add-patch infrastructure now respect diff.context and\n> diff.interHunkContext file configurations.\n> \n> In particular, this patch helps users who don't want to set persistent\n> context configurations or just want a way to override them on a one-time\n> basis, by allowing the relevant builtins to accept corresponding command\n> line options that override the file configurations.\n> \n> This mimics commands such as diff and log, which allow for both context\n> file configuration and command line overrides.\n\nThe code changes here mostly look good, I've left a few comments\nbelow. I think the tests could be improved, I've left some suggestions\non limiting the number of tests while improving the coverage. The new\ntests I'm suggesting that check invalid option combinations are the\nbasis for most of my code comments.\n\nThere is still the issue of what to do with -U0. As I mentioned\npreviously \"git apply\" will fail when we try to apply the patch. We\ncan either pass the appropriate flag when the context is zero or\npossibly use -U0 to mean the default number of context lines.\n\n> diff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc\n> new file mode 100644\n> index 000000000000..e161260358ff\n> --- /dev/null\n> +++ b/Documentation/diff-context-options.adoc\n> @@ -0,0 +1,10 @@\n> +`-U<n>`::\n> +`--unified=<n>`::\n> +\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n> +\tor 3 if the config option is unset.\n> +\n> +`--inter-hunk-context=<n>`::\n> +\tShow the context between diff hunks, up to the specified _<number>_\n> +\tof lines, thereby fusing hunks that are close to each other.\n> +\tDefaults to `diff.interHunkContext` or 0 if the config option\n> +\tis unset.\n\nNice - we reuse the same text for all the \"-p\" commands.\n\n> diff --git a/add-interactive.c b/add-interactive.c\n> [...]\n> @@ -98,6 +99,17 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n>   \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n>   \tif (s->use_single_key)\n>   \t\tsetbuf(stdin, NULL);\n> +\n> +\tif (add_p_opt->context != -1) {\n> +\t\tif (add_p_opt->context < 0)\n> +\t\t\tdie(_(\"%s cannot be negative\"), \"--unified\");\n> +\t\ts->context = add_p_opt->context;\n> +\t}\n> +\tif (add_p_opt->interhunkcontext != -1) {\n> +\t\tif (add_p_opt->interhunkcontext < 0)\n> +\t\t\tdie(_(\"%s cannot be negative\"), \"--inter-hunk-context\");\n> +\t\ts->interhunkcontext = add_p_opt->interhunkcontext;\n> +\t}\n\nCentralizing these checks like this is a good idea.\n\n> @@ -1031,10 +1047,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,\n>   \tif (count > 0) {\n>   \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n>   \n> -\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\",\n> -\t\t\t     oid_to_hex(!is_initial ? &oid :\n> -\t\t\t\t\ts->r->hash_algo->empty_tree),\n> -\t\t\t     \"--\", NULL);\n> +\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\", NULL);\n> +\t\tif (s->context != -1)\n> +\t\t\tstrvec_pushf(&cmd.args, \"--unified=%i\", s->context);\n> +\t\tif (s->interhunkcontext != -1)\n> +\t\t\tstrvec_pushf(&cmd.args, \"--inter-hunk-context=%i\", s->interhunkcontext);\n> +\t\tstrvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :\n> +\t\t\t     s->r->hash_algo->empty_tree), \"--\", NULL);\n\nThis is good - we propagate the values we were given on the\ncommand-line.\n\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> [...]\n> @@ -564,8 +575,13 @@ static int checkout_paths(const struct checkout_opts *opts,\n>   \t\telse\n>   \t\t\tBUG(\"either flag must have been set, worktree=%d, index=%d\",\n>   \t\t\t    opts->checkout_worktree, opts->checkout_index);\n> -\t\treturn !!run_add_p(the_repository, patch_mode, rev,\n> -\t\t\t\t   &opts->pathspec);\n> +\t\treturn !!run_add_p(the_repository, patch_mode, &add_p_opt,\n> +\t\t\t\t   rev, &opts->pathspec);\n> +\t} else {\n> +\t\tif (opts->patch_context != -1)\n> +\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n> +\t\tif (opts->patch_interhunk_context != -1)\n> +\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n>   \t}\n\nThis does not catch \"git checkout -U 7\" because this code is only run\nif we're checking out paths. I think you need to check this is\ncheckout_main() instead.\n\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> [...]\n> @@ -1826,8 +1831,15 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n>   \t\tdie(_(\"the option '%s' requires '%s'\"), \"--pathspec-file-nul\", \"--pathspec-from-file\");\n>   \t}\n>   \n> +\tif (!patch_mode) {\n> +\t\tif (add_p_opt.context != -1)\n> +\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n> +\t\tif (add_p_opt.interhunkcontext != -1)\n> +\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n> +\t}\n> +\n\nThis needs to die on invalid context values as \"git stash\" seems to\nignore the exit code of the subprocess that checks for negative values.\n\n> @@ -1877,8 +1892,17 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n>   \t\tstash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');\n>   \n>   \tmemset(&ps, 0, sizeof(ps));\n> +\n> +\tif (!patch_mode) {\n> +\t\tif (add_p_opt.context != -1)\n> +\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n> +\t\tif (add_p_opt.interhunkcontext != -1)\n> +\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n\nThis needs to die on invalid context values as \"git stash\" seems to\nignore the exit code of the subprocess that checks for negative values.\n\n> diff --git a/commit.h b/commit.h\n> [...]\n>   #include \"object.h\"\n> +#include \"add-interactive.h\"\n>   struct signature_check;\n>   struct strbuf;\n\nLets not add this. Instead lets just add a declaration for \"struct\nadd_p_opt\" like the ones in the context line so that we don't end up\nincluding everything from add-interactive.h when we only need a single\nstruct declaration.\n\n> diff --git a/parse-options.h b/parse-options.h\n> [...]\n> +#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', \"unified\", v,\n>   N_(\"generate diffs with <n> lines context\"), PARSE_OPT_NONEG)\n\nThis looks good\n\n> +#define OPT_DIFF_INTERHUNK_CONTEXT(v) OPT_INTEGER_F(0, \"inter-hunk-context\", v, N_(\"show context between diff hunks up to the specified number of lines\"), PARSE_OPT_NONEG)\n\nThis is a bit verbose but it  matches what is in diff.c.\n\n> diff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh\n> index bada0cbd32f7..d5aad6e143a7 100755\n> --- a/t/t4032-diff-inter-hunk-context.sh\n> +++ b/t/t4032-diff-inter-hunk-context.sh\n> @@ -47,6 +47,31 @@ t() {\n>   \t\"\n>   }\n>   \n> +t_patch() {\n> +\tuse_config=\n> +\tgit config --unset diff.interHunkContext\n> +\n> +\tcase $# in\n> +\t4) hunks=$4; cmd=\"add -p -U$3\";;\n> +\t5) hunks=$5; cmd=\"add -p -U$3 --inter-hunk-context=$4\";;\n> +\t6) hunks=$5; cmd=\"add -p -U$3\"; git config diff.interHunkContext $4; use_config=\"(diff.interHunkContext=$4) \";;\n> +\tesac\n> +\tlabel=\"$use_config$cmd, $1 common $2\"\n> +\tfile=f$1\n> +\n> +\tif ! test -f $file\n> +\tthen\n> +\t\tf A $1 B >$file\n> +\t\tgit add $file\n> +\t\tgit commit -q -m. $file\n> +\t\tf X $1 Y >$file\n> +\tfi\n> +\n> +\ttest_expect_success \"$label: count hunks ($hunks)\" \"\n> +\t\ttest $(test_write_lines q | git $cmd $file | sed -n 's/^([0-9]*\\/\\([0-9]*\\)) Stage this hunk.*/\\1/p') = $hunks\n> +\t\"\n> +}\n> +\n>   cat <<EOF >expected.f1.0.1 || exit 1\n>   diff --git a/f1 b/f1\n>   --- a/f1\n> @@ -107,6 +132,42 @@ t 3 lines\t1\t2\t1\tconfig\n>   t 9 lines\t3\t2\t2\tconfig\n>   t 9 lines\t3\t3\t1\tconfig\n>   \n> +# common lines\tctx\tintrctx\thunks\n> +t_patch 1 line\t0\t\t2\n> +t_patch 1 line\t0\t0\t2\n> +t_patch 1 line\t0\t1\t1\n> +t_patch 1 line\t0\t2\t1\n> +t_patch 1 line\t1\t\t1\n> +\n> +t_patch 2 lines\t0\t\t2\n> +t_patch 2 lines\t0\t0\t2\n> +t_patch 2 lines\t0\t1\t2\n> +t_patch 2 lines\t0\t2\t1\n> +t_patch 2 lines\t1\t\t1\n> +\n> +t_patch 3 lines\t1\t\t2\n> +t_patch 3 lines\t1\t0\t2\n> +t_patch 3 lines\t1\t1\t1\n> +t_patch 3 lines\t1\t2\t1\n> +\n> +t_patch 9 lines\t3\t\t2\n> +t_patch 9 lines\t3\t2\t2\n> +t_patch 9 lines\t3\t3\t1\n> +\n> +#\t\t\t\t\tuse diff.interHunkContext?\n> +t_patch 1 line\t0\t0\t2\tconfig\n> +t_patch 1 line\t0\t1\t1\tconfig\n> +t_patch 1 line\t0\t2\t1\tconfig\n> +t_patch 9 lines\t3\t3\t1\tconfig\n> +t_patch 2 lines\t0\t0\t2\tconfig\n> +t_patch 2 lines\t0\t1\t2\tconfig\n> +t_patch 2 lines\t0\t2\t1\tconfig\n> +t_patch 3 lines\t1\t0\t2\tconfig\n> +t_patch 3 lines\t1\t1\t1\tconfig\n> +t_patch 3 lines\t1\t2\t1\tconfig\n> +t_patch 9 lines\t3\t2\t2\tconfig\n> +t_patch 9 lines\t3\t3\t1\tconfig\n> +\n\nThere are 29 tests here and yet more below. I think we can\nget the test coverage we need much more efficiently with the following\nadded to t3701\n\nfor cmd in add checkout restore 'commit -m file'\ndo\n\ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" \"\n\t\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n\t\tgit add file &&\n\t\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n\t\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n\t\t\t$cmd -p -U 4 --inter-hunk-context 2 >actual &&\n\t\ttest_grep \\\"@@ -2,20 +2,20 @@\\\" actual\n\t\"\ndone\n\t\ntest_expect_success 'reset accepts -U and --inter-hunk-context' '\n\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n\tgit commit -m file file &&\n\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n\tgit add file &&\n\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n\t\treset -p -U 4 --inter-hunk-context 2 >actual &&\n\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n'\n\ntest_expect_success 'stash accepts -U and --inter-hunk-context' '\n\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n\tgit commit -m file file &&\n\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n\t\tstash -p -U 4 --inter-hunk-context 2 >actual &&\n\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n'\n\nThose tests will fail if any of the commands that accept \"-p\" do not\naccept \"-U\" or \"--inter-hunk-context\" or if command-line arguments do\nnot override the config settings. We should also add tests in t3701 to\ncheck that invalid option combinations and values are rejected like so\n\nfor cmd in add checkout commit reset restore 'stash save' 'stash push'\ndo\n\ttest_expect_success \"$cmd rejects invalid context options\" \"\n\t\ttest_must_fail git $cmd -p -U -3 2>actual &&\n\t\ttest_grep -e \\\"--unified cannot be negative\\\" actual &&\n\n\t\ttest_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&\n\t\ttest_grep -e \\\"--inter-hunk-context cannot be negative\\\" actual &&\n\n\t\ttest_must_fail git $cmd -U 7 2>actual &&\n\t\ttest_grep -E \\\".--unified. requires .(--interactive/)?--patch.\\\" actual &&\n\n\t\ttest_must_fail git $cmd --inter-hunk-context 2 2>actual &&\n\t\ttest_grep -E \\\".--inter-hunk-context. requires \n.(--interactive/)?--patch.\\\" actual\n\t\"\ndone\n\nThe \"checkout\", \"stash save\" and \"stash push\" tests above currently\nfail because the implementation does not implement those checks\nproperly.\n\nWith a few tweaks this series will be looking very good\n\nBest Wishes\n\nPhillip\n"},{"id":"517949","messageId":"0862a881-2e0a-4051-949f-3d0b94408fd5@gmail.com","threadId":"63396","inReplyTo":"431d6fec-3955-4703-8bb3-376eae7ff052@gmail.com","subject":"Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-05-13T14:39:06Z","receivedAt":"2025-05-13T14:39:10Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 13/05/2025 14:52, Phillip Wood wrote:\n>> diff --git a/builtin/stash.c b/builtin/stash.c\n>> [...]\n>> @@ -1826,8 +1831,15 @@ static int push_stash(int argc, const char \n>> **argv, const char *prefix,\n>>           die(_(\"the option '%s' requires '%s'\"), \"--pathspec-file- \n>> nul\", \"--pathspec-from-file\");\n>>       }\n>> +    if (!patch_mode) {\n>> +        if (add_p_opt.context != -1)\n>> +            die(_(\"the option '%s' requires '%s'\"), \"--unified\", \"-- \n>> patch\");\n>> +        if (add_p_opt.interhunkcontext != -1)\n>> +            die(_(\"the option '%s' requires '%s'\"), \"--inter-hunk- \n>> context\", \"--patch\");\n>> +    }\n>> +\n> \n> This needs to die on invalid context values as \"git stash\" seems to\n> ignore the exit code of the subprocess that checks for negative values.\n\nLooking more closely the problem is that it quits if there are no \nchanges to stash before validating -U or --inter-hunk-context. I think \nit should validate the options before checking if there is anything to \nstash.\n\nBest Wishes\n\nPhillip\n\n"},{"id":"517950","messageId":"CAP9jKjEbfbRijEowYjkbeQxKC1GqLCoM=hCjhXSgsrvgVAkO0w@mail.gmail.com","threadId":"63396","inReplyTo":"0862a881-2e0a-4051-949f-3d0b94408fd5@gmail.com","subject":"Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-05-13T15:05:32Z","receivedAt":"2025-05-13T15:05:45Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"Hey, thanks for the thorough review Philip. I will properly digest\nthis when I get some free time, but I just wanted to say (I probably\nshould have mentioned this so my bad) that the reason I didn't change\nto test just the singular command (yet, anyway) is that someone else\nthought this was a good idea testing all of them, so I wasn't sure\nwhether to touch it or not in the end, and thought I'd just submit\nthis v2 and gather more opinions. Was this perhaps the wrong approach\nthough?\n"},{"id":"517953","messageId":"xmqqzffgh5rf.fsf@gitster.g","threadId":"63396","inReplyTo":"f00cdfa1-343a-4fa0-bce5-e06d1ed62c22@gmail.com","subject":"Re: [PATCH v2 3/4] add-patch: respect diff.context configuration","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-13T15:47:32Z","receivedAt":"2025-05-13T15:47:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Leon\n>\n> On 10/05/2025 14:46, Leon Michalak via GitGitGadget wrote:\n>> From: Leon Michalak <leonmichalak6@gmail.com>\n>> Various builtins that use add-patch infrastructure do not respect\n>> the user's diff.context and diff.interHunkContext file configurations.\n>\n> We could expand this slightly by adding\n>\n> This is because the plumbing commands used by \"git add -p\" to generate\n> the diff do not read those config settings. Fix this by reading the\n> config before generating the patch and passing it along to the diff\n> command with the \"-U\" and \"--inter-hunk-context\" command-line options.\n>\n>> This patch fixes this inconsistency.\n>> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n>> ---\n>\n>> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n>>   \trepo_config_get_string(r, \"diff.algorithm\",\n>>   \t\t\t       &s->interactive_diff_algorithm);\n>>   +\tif (!repo_config_get_int(r, \"diff.context\", &context)) {\n>> +\t\tif (context < 0)\n>> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n>> +\t\telse\n>> +\t\t\ts->context = context;\n>> +\t};\n>> +\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &interhunkcontext)) {\n>> +\t\tif (interhunkcontext < 0)\n>> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n>> +\t\telse\n>> +\t\t\ts->interhunkcontext = interhunkcontext;\n>> +\t};\n>\n> Thanks for changing this. This iteration of the code changes looks good\n\nLose the ';' (semicolon) after closing {brace}s.\nThis is C; you do not need an empty statement after a {block}.\n\nEverything in your review I am very happy to see.  Thanks for giving\na great review.\n\n"},{"id":"518046","messageId":"8ab91ee3-26b6-4ac5-9332-64649cf329e6@gmail.com","threadId":"63396","inReplyTo":"CAP9jKjEbfbRijEowYjkbeQxKC1GqLCoM=hCjhXSgsrvgVAkO0w@mail.gmail.com","subject":"Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-05-14T15:13:01Z","receivedAt":"2025-05-14T15:13:07Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Leon\n\nOn 13/05/2025 16:05, Leon Michalak wrote:\n> Hey, thanks for the thorough review Philip. I will properly digest\n> this when I get some free time, but I just wanted to say (I probably\n> should have mentioned this so my bad) that the reason I didn't change\n> to test just the singular command (yet, anyway) is that someone else\n> thought this was a good idea testing all of them,\n\nI'd missed that message - have you got a link to it please\n\n> so I wasn't sure\n> whether to touch it or not in the end, and thought I'd just submit\n> this v2 and gather more opinions. Was this perhaps the wrong approach\n> though?\nIf you get conflicting advise then it is a good idea to mention that in \nthe cover letter and explain which option you went with and why.\n\nBest Wishes\n\nPhillip\n\n"},{"id":"518047","messageId":"d878fe4d-163d-415d-846f-f5541cacb22a@gmail.com","threadId":"63396","inReplyTo":"xmqqzffgh5rf.fsf@gitster.g","subject":"Re: [PATCH v2 3/4] add-patch: respect diff.context configuration","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-05-14T15:13:11Z","receivedAt":"2025-05-14T15:13:17Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 13/05/2025 16:47, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n>> On 10/05/2025 14:46, Leon Michalak via GitGitGadget wrote:\n>>\n>>> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n>>>    \trepo_config_get_string(r, \"diff.algorithm\",\n>>>    \t\t\t       &s->interactive_diff_algorithm);\n>>>    +\tif (!repo_config_get_int(r, \"diff.context\", &context)) {\n>>> +\t\tif (context < 0)\n>>> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n>>> +\t\telse\n>>> +\t\t\ts->context = context;\n>>> +\t};\n>>> +\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &interhunkcontext)) {\n>>> +\t\tif (interhunkcontext < 0)\n>>> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n>>> +\t\telse\n>>> +\t\t\ts->interhunkcontext = interhunkcontext;\n>>> +\t};\n>>\n>> Thanks for changing this. This iteration of the code changes looks good\n> \n> Lose the ';' (semicolon) after closing {brace}s.\n> This is C; you do not need an empty statement after a {block}.\n\nOh well spotted, I'd missed that\n\nThanks\n\nPhillip\n"},{"id":"518120","messageId":"xmqqh61m3uat.fsf@gitster.g","threadId":"63396","inReplyTo":"d878fe4d-163d-415d-846f-f5541cacb22a@gmail.com","subject":"Re: [PATCH v2 3/4] add-patch: respect diff.context configuration","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-15T12:58:02Z","receivedAt":"2025-05-15T12:58:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 13/05/2025 16:47, Junio C Hamano wrote:\n>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>>> On 10/05/2025 14:46, Leon Michalak via GitGitGadget wrote:\n>>>\n>>>> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n>>>>    \trepo_config_get_string(r, \"diff.algorithm\",\n>>>>    \t\t\t       &s->interactive_diff_algorithm);\n>>>>    +\tif (!repo_config_get_int(r, \"diff.context\", &context)) {\n>>>> +\t\tif (context < 0)\n>>>> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n>>>> +\t\telse\n>>>> +\t\t\ts->context = context;\n>>>> +\t};\n>>>> +\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &interhunkcontext)) {\n>>>> +\t\tif (interhunkcontext < 0)\n>>>> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n>>>> +\t\telse\n>>>> +\t\t\ts->interhunkcontext = interhunkcontext;\n>>>> +\t};\n>>>\n>>> Thanks for changing this. This iteration of the code changes looks good\n>> Lose the ';' (semicolon) after closing {brace}s.\n>> This is C; you do not need an empty statement after a {block}.\n>\n> Oh well spotted, I'd missed that\n\nHeh, with enough number of eyeballs, all the bugs are shallow.\n"},{"id":"520849","messageId":"pull.1915.v3.git.1751128486.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v2.git.1746884789.gitgitgadget@gmail.com","subject":"[PATCH v3 0/4] Better support for customising context lines in --patch commands","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-06-28T16:34:42Z","receivedAt":"2025-06-28T16:34:51Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"This series of patches attempt to give --interactive/--patch compatible\nbuiltins (\"add\", \"commit\", \"checkout\", \"reset\", \"restore\" and \"stash\")\nbetter support and nicer experience for configuring how many context lines\nare shown in diffs through a variety of ways.\n\nPrior to these patches, the user could not choose how many context lines\nthey saw in --patch commands (apart from one workaround by using\nGIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a\npersistent solution). Additionally, the behaviour around reading from the\ndiff.context and diff.interHunkContext configs was also inconsistent with\nother diff generating commands such as \"log -p\".\n\nThe summarised changes below hopefully make this experience better and fix\nsome inconsistencies:\n\n * diff.context and diff.interHunkContext configs are now respected by\n   --patch compatible commands\n * --unified and --inter-hunk-context command line options have been added\n   to --patch compatible commands (which take prescendence over file\n   configs)\n * \"add\" and \"commit\" in --interactive mode now expose a new \"context\"\n   subcommand which configures the amount of context lines you wish to see\n   in subsequent diffs generated from other subcommands such as \"patch\" or\n   \"diff\"\n\nThe original discussion for this can be read at:\n\n * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/\n\nChanges since v1:\n\n * Update commit descriptions\n * Update tests to use the more modern and robust test_grep and test_config\n   utils\n * Reword some documentation / user messages\n * Ensure each commit is atomic and builds/passes tests on it's own\n * Make new command line options DRY\n * Add tests for interhunk context interaction\n * Error if context config/command line options are negative\n * Drop previous last commit to do with new subcommand for --interactive\n   add/commit. My motivations behind this patch series originally where\n   quite simple, just for add-patch commands to respect context configs.\n   This subcommand, after the discussion in v1, will require more thought\n   and a larger implementation that what I had anticipated. I would prefer\n   to leave this for another time as it's the least impactful but the most\n   time intensive and complicated idea.\n\nChanges since v2:\n\n * Update tests to only test single command (following Philip's suggestion)\n * Add negative option checks\n * Minor commit re-wording\n\nLeon Michalak (4):\n  test: use \"test_grep\"\n  test: use \"test_config\"\n  add-patch: respect diff.context configuration\n  add-patch: add diff.context command line overrides\n\n Documentation/diff-context-options.adoc |  10 ++\n Documentation/git-add.adoc              |   2 +\n Documentation/git-checkout.adoc         |   2 +\n Documentation/git-commit.adoc           |   2 +\n Documentation/git-reset.adoc            |   2 +\n Documentation/git-restore.adoc          |   2 +\n Documentation/git-stash.adoc            |   2 +\n add-interactive.c                       |  53 +++++++++--\n add-interactive.h                       |  17 +++-\n add-patch.c                             |  11 ++-\n builtin/add.c                           |  21 ++++-\n builtin/checkout.c                      |  31 +++++-\n builtin/commit.c                        |  16 +++-\n builtin/reset.c                         |  17 +++-\n builtin/stash.c                         |  56 ++++++++---\n commit.h                                |   3 +-\n parse-options.h                         |   2 +\n t/t3701-add-interactive.sh              | 119 +++++++++++++++++++-----\n t/t4055-diff-context.sh                 |  72 +++++++++-----\n t/t9902-completion.sh                   |   2 +\n 20 files changed, 362 insertions(+), 80 deletions(-)\n create mode 100644 Documentation/diff-context-options.adoc\n\n\nbase-commit: cf6f63ea6bf35173e02e18bdc6a4ba41288acff9\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1915\n\nRange-diff vs v2:\n\n 1:  4f92a1b4c24 ! 1:  044a93014b6 test: refactor to use \"test_grep\"\n     @@ Metadata\n      Author: Leon Michalak <leonmichalak6@gmail.com>\n      \n       ## Commit message ##\n     -    test: refactor to use \"test_grep\"\n     +    test: use \"test_grep\"\n      \n     -    Refactor to use the modern \"test_grep\" test utility instead of regular\n     -    \"grep\" which provides better debug information if tests fail.\n     +    Use the modern \"test_grep\" test utility instead of regular \"grep\" which\n     +    provides better debug information if tests fail.\n      \n          This is a prerequisite to the commits that follow which add to both test\n          files.\n 2:  75424cb8e1c ! 2:  e5c40d37750 test: refactor to use \"test_config\"\n     @@ Metadata\n      Author: Leon Michalak <leonmichalak6@gmail.com>\n      \n       ## Commit message ##\n     -    test: refactor to use \"test_config\"\n     +    test: use \"test_config\"\n      \n     -    Refactor to use the modern \"test_config\" test utility instead of manual\n     -    \"git config\" as the former provides clean up on test completion.\n     +    Use the modern \"test_config\" test utility instead of manual\"git config\"\n     +    as the former provides clean up on test completion.\n      \n          This is a prerequisite to the commits that follow which add to this test\n          file.\n 3:  f16d3de8611 ! 3:  1ec8a138486 add-patch: respect diff.context configuration\n     @@ Commit message\n          the user's diff.context and diff.interHunkContext file configurations.\n          This patch fixes this inconsistency.\n      \n     +    This is because the plumbing commands used by \"git add -p\" to generate\n     +    the diff do not read those config settings. Fix this by reading the\n     +    config before generating the patch and passing it along to the diff\n     +    command with the \"-U\" and \"--inter-hunk-context\" command-line options.\n     +\n          Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n      \n       ## add-interactive.c ##\n     @@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repositor\n      +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n      +\t\telse\n      +\t\t\ts->context = context;\n     -+\t};\n     ++\t}\n      +\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &interhunkcontext)) {\n      +\t\tif (interhunkcontext < 0)\n      +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n      +\t\telse\n      +\t\t\ts->interhunkcontext = interhunkcontext;\n     -+\t};\n     ++\t}\n      +\n       \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n       \tif (s->use_single_key)\n     @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec\n       \t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", diff_algorithm);\n       \tif (s->revision) {\n      \n     - ## t/t4055-diff-context.sh ##\n     -@@ t/t4055-diff-context.sh: test_expect_success 'diff.context honored by \"log\"' '\n     - \ttest_grep \"^ firstline\" output\n     + ## t/t3701-add-interactive.sh ##\n     +@@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n     + \ttest_cmp expect actual\n       '\n       \n     -+test_expect_success 'diff.context honored by \"add\"' '\n     -+\tgit add -p >output &&\n     -+\ttest_grep ! firstline output &&\n     -+\ttest_config diff.context 8 &&\n     -+\tgit log -1 -p >output &&\n     -+\ttest_grep \"^ firstline\" output\n     -+'\n     -+\n     -+test_expect_success 'diff.context honored by \"commit\"' '\n     -+\t! git commit -p >output &&\n     -+\ttest_grep ! firstline output &&\n     -+\ttest_config diff.context 8 &&\n     -+\t! git commit -p >output &&\n     -+\ttest_grep \"^ firstline\" output\n     -+'\n     -+\n     -+test_expect_success 'diff.context honored by \"checkout\"' '\n     -+\tgit checkout -p >output &&\n     -+\ttest_grep ! firstline output &&\n     -+\ttest_config diff.context 8 &&\n     -+\tgit checkout -p >output &&\n     -+\ttest_grep \"^ firstline\" output\n     -+'\n     -+\n     -+test_expect_success 'diff.context honored by \"stash\"' '\n     -+\t! git stash -p >output &&\n     -+\ttest_grep ! firstline output &&\n     -+\ttest_config diff.context 8 &&\n     -+\t! git stash -p >output &&\n     -+\ttest_grep \"^ firstline\" output\n     ++test_expect_success 'add -p respects diff.context' '\n     ++\ttest_write_lines a b c d e f g h i j k l m >file &&\n     ++\tgit add file &&\n     ++\ttest_write_lines a b c d e f G h i j k l m >file &&\n     ++\techo y | git -c diff.context=5 add -p >actual &&\n     ++\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n      +'\n      +\n     -+test_expect_success 'diff.context honored by \"restore\"' '\n     -+\tgit restore -p >output &&\n     -+\ttest_grep ! firstline output &&\n     -+\ttest_config diff.context 8 &&\n     -+\tgit restore -p >output &&\n     -+\ttest_grep \"^ firstline\" output\n     ++test_expect_success 'add -p respects diff.interHunkContext' '\n     ++\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n     ++\tgit add file &&\n     ++\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n     ++\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n     ++\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n      +'\n      +\n     - test_expect_success 'The -U option overrides diff.context' '\n     - \ttest_config diff.context 8 &&\n     - \tgit log -U4 -1 >output &&\n     -@@ t/t4055-diff-context.sh: test_expect_success 'negative integer config parsing' '\n     - \ttest_grep \"bad config variable\" output\n     - '\n     - \n     -+test_expect_success 'negative integer config parsing by \"add\"' '\n     ++test_expect_success 'add -p rejects negative diff.context' '\n      +\ttest_config diff.context -1 &&\n      +\ttest_must_fail git add -p 2>output &&\n      +\ttest_grep \"diff.context cannot be negative\" output\n      +'\n      +\n     -+test_expect_success 'negative integer config parsing by \"commit\"' '\n     -+\ttest_config diff.context -1 &&\n     -+\ttest_must_fail git commit -p 2>output &&\n     -+\ttest_grep \"bad config variable\" output\n     -+'\n     -+\n     -+test_expect_success 'negative integer config parsing by \"checkout\"' '\n     -+\ttest_config diff.context -1 &&\n     -+\ttest_must_fail git checkout -p 2>output &&\n     -+\ttest_grep \"diff.context cannot be negative\" output\n     -+'\n     -+\n     -+test_expect_success 'negative integer config parsing by \"stash\"' '\n     -+\ttest_config diff.context -1 &&\n     -+\ttest_must_fail git stash -p 2>output &&\n     -+\ttest_grep \"diff.context cannot be negative\" output\n     -+'\n     -+\n     -+test_expect_success 'negative integer config parsing by \"restore\"' '\n     -+\ttest_config diff.context -1 &&\n     -+\ttest_must_fail git restore -p 2>output &&\n     -+\ttest_grep \"diff.context cannot be negative\" output\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     + test_done\n 4:  973dfadd1b3 ! 4:  b68c58b667c add-patch: add diff.context command line overrides\n     @@ Documentation/git-checkout.adoc: section of linkgit:git-add[1] to learn how to o\n       \n      +include::diff-context-options.adoc[]\n      +\n     - --ignore-other-worktrees::\n     + `--ignore-other-worktrees`::\n       \t`git checkout` refuses when the wanted branch is already checked\n       \tout or otherwise in use by another worktree. This option makes\n      \n     @@ builtin/add.c: static struct option builtin_add_options[] = {\n       \tOPT_BOOL('e', \"edit\", &edit_interactive, N_(\"edit current diff and apply\")),\n       \tOPT__FORCE(&ignored_too, N_(\"allow adding otherwise ignored files\"), 0),\n       \tOPT_BOOL('u', \"update\", &take_worktree_changes, N_(\"update tracked files\")),\n     +@@ builtin/add.c: int cmd_add(int argc,\n     + \tprepare_repo_settings(repo);\n     + \trepo->settings.command_requires_full_index = 0;\n     + \n     ++\tif (add_p_opt.context < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n     ++\tif (add_p_opt.interhunkcontext < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n     ++\n     + \tif (patch_interactive)\n     + \t\tadd_interactive = 1;\n     + \tif (add_interactive) {\n      @@ builtin/add.c: int cmd_add(int argc,\n       \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--dry-run\", \"--interactive/--patch\");\n       \t\tif (pathspec_from_file)\n     @@ builtin/checkout.c: static int checkout_paths(const struct checkout_opts *opts,\n      -\t\t\t\t   &opts->pathspec);\n      +\t\treturn !!run_add_p(the_repository, patch_mode, &add_p_opt,\n      +\t\t\t\t   rev, &opts->pathspec);\n     -+\t} else {\n     -+\t\tif (opts->patch_context != -1)\n     -+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n     -+\t\tif (opts->patch_interhunk_context != -1)\n     -+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n       \t}\n       \n       \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n     @@ builtin/checkout.c: static struct option *add_checkout_path_options(struct check\n       \t\tOPT_BOOL(0, \"ignore-skip-worktree-bits\", &opts->ignore_skipworktree,\n       \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n       \t\tOPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),\n     +@@ builtin/checkout.c: static int checkout_main(int argc, const char **argv, const char *prefix,\n     + \targc = parse_options(argc, argv, prefix, options,\n     + \t\t\t     usagestr, parseopt_flags);\n     + \n     ++\tif (opts->patch_context < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n     ++\tif (opts->patch_interhunk_context < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n     ++\n     ++\tif (!opts->patch_mode) {\n     ++\t\tif (opts->patch_context != -1)\n     ++\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n     ++\t\tif (opts->patch_interhunk_context != -1)\n     ++\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n     ++\t}\n     ++\n     + \tif (opts->show_progress < 0) {\n     + \t\tif (opts->quiet)\n     + \t\t\topts->show_progress = 0;\n      \n       ## builtin/commit.c ##\n      @@\n     @@ builtin/commit.c: static const char *edit_message, *use_message;\n       static int edit_flag = -1; /* unspecified */\n       static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n       static int config_commit_verbose = -1; /* unspecified */\n     +@@ builtin/commit.c: static const char *prepare_index(const char **argv, const char *prefix,\n     + \tconst char *ret;\n     + \tchar *path = NULL;\n     + \n     ++\tif (add_p_opt.context < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n     ++\tif (add_p_opt.interhunkcontext < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n     ++\n     + \tif (is_status)\n     + \t\trefresh_flags |= REFRESH_UNMERGED;\n     + \tparse_pathspec(&pathspec, 0,\n      @@ builtin/commit.c: static const char *prepare_index(const char **argv, const char *prefix,\n       \t\told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n       \t\tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n     @@ builtin/reset.c: int cmd_reset(int argc,\n       \t\tOPT_BOOL('N', \"intent-to-add\", &intent_to_add,\n       \t\t\t\tN_(\"record only the fact that removed paths will be added later\")),\n       \t\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n     +@@ builtin/reset.c: int cmd_reset(int argc,\n     + \t\toidcpy(&oid, &tree->object.oid);\n     + \t}\n     + \n     ++\tif (add_p_opt.context < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n     ++\tif (add_p_opt.interhunkcontext < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n     ++\n     + \tprepare_repo_settings(the_repository);\n     + \tthe_repository->settings.command_requires_full_index = 0;\n     + \n      @@ builtin/reset.c: int cmd_reset(int argc,\n       \t\tif (reset_type != NONE)\n       \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--patch\", \"--{hard,mixed,soft}\");\n     @@ builtin/stash.c: static int push_stash(int argc, const char **argv, const char *\n      +\t\tif (add_p_opt.interhunkcontext != -1)\n      +\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n      +\t}\n     ++\n     ++\tif (add_p_opt.context < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n     ++\tif (add_p_opt.interhunkcontext < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n      +\n       \tret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,\n      -\t\t\t    include_untracked, only_staged);\n     @@ builtin/stash.c: static int save_stash(int argc, const char **argv, const char *\n       \n       \tmemset(&ps, 0, sizeof(ps));\n      +\n     ++\tif (add_p_opt.context < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n     ++\tif (add_p_opt.interhunkcontext < -1)\n     ++\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n     ++\n      +\tif (!patch_mode) {\n      +\t\tif (add_p_opt.context != -1)\n      +\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n     @@ parse-options.h: int parse_opt_tracking_mode(const struct option *, const char *\n       #define OPT_IPVERSION(v) \\\n       \tOPT_SET_INT_F('4', \"ipv4\", (v), N_(\"use IPv4 addresses only\"), \\\n      \n     - ## t/t4032-diff-inter-hunk-context.sh ##\n     -@@ t/t4032-diff-inter-hunk-context.sh: t() {\n     - \t\"\n     - }\n     + ## t/t3701-add-interactive.sh ##\n     +@@ t/t3701-add-interactive.sh: test_expect_success 'add -p rejects negative diff.context' '\n     + \ttest_grep \"diff.context cannot be negative\" output\n     + '\n       \n     -+t_patch() {\n     -+\tuse_config=\n     -+\tgit config --unset diff.interHunkContext\n     -+\n     -+\tcase $# in\n     -+\t4) hunks=$4; cmd=\"add -p -U$3\";;\n     -+\t5) hunks=$5; cmd=\"add -p -U$3 --inter-hunk-context=$4\";;\n     -+\t6) hunks=$5; cmd=\"add -p -U$3\"; git config diff.interHunkContext $4; use_config=\"(diff.interHunkContext=$4) \";;\n     -+\tesac\n     -+\tlabel=\"$use_config$cmd, $1 common $2\"\n     -+\tfile=f$1\n     -+\n     -+\tif ! test -f $file\n     -+\tthen\n     -+\t\tf A $1 B >$file\n     -+\t\tgit add $file\n     -+\t\tgit commit -q -m. $file\n     -+\t\tf X $1 Y >$file\n     -+\tfi\n     -+\n     -+\ttest_expect_success \"$label: count hunks ($hunks)\" \"\n     -+\t\ttest $(test_write_lines q | git $cmd $file | sed -n 's/^([0-9]*\\/\\([0-9]*\\)) Stage this hunk.*/\\1/p') = $hunks\n     ++for cmd in add checkout restore 'commit -m file'\n     ++do\n     ++\ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" \"\n     ++\t\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n     ++\t\tgit add file &&\n     ++\t\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n     ++\t\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n     ++\t\t\t$cmd -p -U 4 --inter-hunk-context 2 >actual &&\n     ++\t\ttest_grep \\\"@@ -2,20 +2,20 @@\\\" actual\n      +\t\"\n     -+}\n     ++done\n     ++\n     ++test_expect_success 'reset accepts -U and --inter-hunk-context' '\n     ++\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n     ++\tgit commit -m file file &&\n     ++\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n     ++\tgit add file &&\n     ++\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n     ++\t\treset -p -U 4 --inter-hunk-context 2 >actual &&\n     ++\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n     ++'\n     ++\n     ++test_expect_success 'stash accepts -U and --inter-hunk-context' '\n     ++\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n     ++\tgit commit -m file file &&\n     ++\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n     ++\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n     ++\t\tstash -p -U 4 --inter-hunk-context 2 >actual &&\n     ++\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n     ++'\n     ++\n     ++for cmd in add checkout commit reset restore 'stash save' 'stash push'\n     ++do\n     ++\ttest_expect_success \"$cmd rejects invalid context options\" \"\n     ++\t\ttest_must_fail git $cmd -p -U -3 2>actual &&\n     ++\t\tcat actual | echo &&\n     ++\t\ttest_grep -e \\\"'--unified' cannot be negative\\\" actual &&\n     ++\n     ++\t\ttest_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&\n     ++\t\ttest_grep -e \\\"'--inter-hunk-context' cannot be negative\\\" actual &&\n     ++\n     ++\t\ttest_must_fail git $cmd -U 7 2>actual &&\n     ++\t\ttest_grep -E \\\"'--unified' requires '(--interactive/)?--patch'\\\" actual &&\n     ++\n     ++\t\ttest_must_fail git $cmd --inter-hunk-context 2 2>actual &&\n     ++\t\ttest_grep -E \\\"'--inter-hunk-context' requires '(--interactive/)?--patch'\\\" actual\n     ++\t\"\n     ++done\n      +\n     - cat <<EOF >expected.f1.0.1 || exit 1\n     - diff --git a/f1 b/f1\n     - --- a/f1\n     -@@ t/t4032-diff-inter-hunk-context.sh: t 3 lines\t1\t2\t1\tconfig\n     - t 9 lines\t3\t2\t2\tconfig\n     - t 9 lines\t3\t3\t1\tconfig\n     - \n     -+# common lines\tctx\tintrctx\thunks\n     -+t_patch 1 line\t0\t\t2\n     -+t_patch 1 line\t0\t0\t2\n     -+t_patch 1 line\t0\t1\t1\n     -+t_patch 1 line\t0\t2\t1\n     -+t_patch 1 line\t1\t\t1\n     -+\n     -+t_patch 2 lines\t0\t\t2\n     -+t_patch 2 lines\t0\t0\t2\n     -+t_patch 2 lines\t0\t1\t2\n     -+t_patch 2 lines\t0\t2\t1\n     -+t_patch 2 lines\t1\t\t1\n     -+\n     -+t_patch 3 lines\t1\t\t2\n     -+t_patch 3 lines\t1\t0\t2\n     -+t_patch 3 lines\t1\t1\t1\n     -+t_patch 3 lines\t1\t2\t1\n     -+\n     -+t_patch 9 lines\t3\t\t2\n     -+t_patch 9 lines\t3\t2\t2\n     -+t_patch 9 lines\t3\t3\t1\n     -+\n     -+#\t\t\t\t\tuse diff.interHunkContext?\n     -+t_patch 1 line\t0\t0\t2\tconfig\n     -+t_patch 1 line\t0\t1\t1\tconfig\n     -+t_patch 1 line\t0\t2\t1\tconfig\n     -+t_patch 9 lines\t3\t3\t1\tconfig\n     -+t_patch 2 lines\t0\t0\t2\tconfig\n     -+t_patch 2 lines\t0\t1\t2\tconfig\n     -+t_patch 2 lines\t0\t2\t1\tconfig\n     -+t_patch 3 lines\t1\t0\t2\tconfig\n     -+t_patch 3 lines\t1\t1\t1\tconfig\n     -+t_patch 3 lines\t1\t2\t1\tconfig\n     -+t_patch 9 lines\t3\t2\t2\tconfig\n     -+t_patch 9 lines\t3\t3\t1\tconfig\n     -+\n     - test_expect_success 'diff.interHunkContext invalid' '\n     - \tgit config diff.interHunkContext asdf &&\n     - \ttest_must_fail git diff &&\n     + test_done\n      \n       ## t/t4055-diff-context.sh ##\n      @@ t/t4055-diff-context.sh: test_expect_success 'The -U option overrides diff.context' '\n\n-- \ngitgitgadget\n"},{"id":"520850","messageId":"044a93014b677914870a872a9c317a46a5a81699.1751128487.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v3.git.1751128486.gitgitgadget@gmail.com","subject":"[PATCH v3 1/4] test: use \"test_grep\"","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-06-28T16:34:43Z","receivedAt":"2025-06-28T16:34:52Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nUse the modern \"test_grep\" test utility instead of regular \"grep\" which\nprovides better debug information if tests fail.\n\nThis is a prerequisite to the commits that follow which add to both test\nfiles.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n t/t3701-add-interactive.sh | 48 +++++++++++++++++++-------------------\n t/t4055-diff-context.sh    | 28 +++++++++++-----------\n 2 files changed, 38 insertions(+), 38 deletions(-)\n\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex b8a05d95f3f1..b088ee141ff4 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -63,7 +63,7 @@ test_expect_success 'setup (initial)' '\n '\n test_expect_success 'status works (initial)' '\n \tgit add -i </dev/null >output &&\n-\tgrep \"+1/-0 *+2/-0 file\" output\n+\ttest_grep \"+1/-0 *+2/-0 file\" output\n '\n \n test_expect_success 'setup expected' '\n@@ -86,7 +86,7 @@ test_expect_success 'revert works (initial)' '\n \tgit add file &&\n \ttest_write_lines r 1 | git add -i &&\n \tgit ls-files >output &&\n-\t! grep . output\n+\ttest_grep ! . output\n '\n \n test_expect_success 'add untracked (multiple)' '\n@@ -109,7 +109,7 @@ test_expect_success 'setup (commit)' '\n '\n test_expect_success 'status works (commit)' '\n \tgit add -i </dev/null >output &&\n-\tgrep \"+1/-0 *+2/-0 file\" output\n+\ttest_grep \"+1/-0 *+2/-0 file\" output\n '\n \n test_expect_success 'update can stage deletions' '\n@@ -141,7 +141,7 @@ test_expect_success 'revert works (commit)' '\n \tgit add file &&\n \ttest_write_lines r 1 | git add -i &&\n \tgit add -i </dev/null >output &&\n-\tgrep \"unchanged *+3/-0 file\" output\n+\ttest_grep \"unchanged *+3/-0 file\" output\n '\n \n test_expect_success 'reject multi-key input' '\n@@ -185,7 +185,7 @@ test_expect_success 'setup fake editor' '\n test_expect_success 'bad edit rejected' '\n \tgit reset &&\n \ttest_write_lines e n d | git add -p >output &&\n-\tgrep \"hunk does not apply\" output\n+\ttest_grep \"hunk does not apply\" output\n '\n \n test_expect_success 'setup patch' '\n@@ -198,7 +198,7 @@ test_expect_success 'setup patch' '\n test_expect_success 'garbage edit rejected' '\n \tgit reset &&\n \ttest_write_lines e n d | git add -p >output &&\n-\tgrep \"hunk does not apply\" output\n+\ttest_grep \"hunk does not apply\" output\n '\n \n test_expect_success 'setup patch' '\n@@ -313,8 +313,8 @@ test_expect_success FILEMODE 'stage mode and hunk' '\n \tchmod +x file &&\n \tprintf \"y\\\\ny\\\\n\" | git add -p &&\n \tgit diff --cached file >out &&\n-\tgrep \"new mode\" out &&\n-\tgrep \"+content\" out &&\n+\ttest_grep \"new mode\" out &&\n+\ttest_grep \"+content\" out &&\n \tgit diff file >out &&\n \ttest_must_be_empty out\n '\n@@ -636,7 +636,7 @@ test_expect_success 'split hunk \"add -p (edit)\"' '\n \tprintf \"%s\\n\" s e     q n q q |\n \tEDITOR=: git add -p &&\n \tgit diff >actual &&\n-\t! grep \"^+15\" actual\n+\ttest_grep ! \"^+15\" actual\n '\n \n test_expect_success 'split hunk \"add -p (no, yes, edit)\"' '\n@@ -648,7 +648,7 @@ test_expect_success 'split hunk \"add -p (no, yes, edit)\"' '\n \tEDITOR=: git add -p 2>error &&\n \ttest_must_be_empty error &&\n \tgit diff >actual &&\n-\t! grep \"^+31\" actual\n+\ttest_grep ! \"^+31\" actual\n '\n \n test_expect_success 'split hunk with incomplete line at end' '\n@@ -682,7 +682,7 @@ test_expect_success 'edit, adding lines to the first hunk' '\n \tEDITOR=./fake_editor.sh git add -p 2>error &&\n \ttest_must_be_empty error &&\n \tgit diff --cached >actual &&\n-\tgrep \"^+22\" actual\n+\ttest_grep \"^+22\" actual\n '\n \n test_expect_success 'patch mode ignores unmerged entries' '\n@@ -696,7 +696,7 @@ test_expect_success 'patch mode ignores unmerged entries' '\n \ttest_must_fail git merge side &&\n \techo changed >non-conflict.t &&\n \techo y | git add -p >output &&\n-\t! grep a/conflict.t output &&\n+\ttest_grep ! a/conflict.t output &&\n \tcat >expected <<-\\EOF &&\n \t* Unmerged path conflict.t\n \tdiff --git a/non-conflict.t b/non-conflict.t\n@@ -728,7 +728,7 @@ test_expect_success 'diffs can be colorized' '\n \n \t# We do not want to depend on the exact coloring scheme\n \t# git uses for diffs, so just check that we saw some kind of color.\n-\tgrep \"$(printf \"\\\\033\")\" output\n+\ttest_grep \"$(printf \"\\\\033\")\" output\n '\n \n test_expect_success 'colors can be overridden' '\n@@ -743,7 +743,7 @@ test_expect_success 'colors can be overridden' '\n \t\t-c color.interactive.error=blue \\\n \t\tadd -i 2>err.raw <input &&\n \ttest_decode_color <err.raw >err &&\n-\tgrep \"<BLUE>Huh (trigger)?<RESET>\" err &&\n+\ttest_grep \"<BLUE>Huh (trigger)?<RESET>\" err &&\n \n \ttest_write_lines help quit >input &&\n \tforce_color git \\\n@@ -863,7 +863,7 @@ test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '\n \tprintf y >y &&\n \tforce_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&\n \ttest_decode_color <output.raw >output &&\n-\tgrep \"old<\" output\n+\ttest_grep \"old<\" output\n '\n \n test_expect_success 'diffFilter filters diff' '\n@@ -876,7 +876,7 @@ test_expect_success 'diffFilter filters diff' '\n \n \t# avoid depending on the exact coloring or content of the prompts,\n \t# and just make sure we saw our diff prefixed\n-\tgrep foo:.*content output\n+\ttest_grep foo:.*content output\n '\n \n test_expect_success 'detect bogus diffFilter output' '\n@@ -886,7 +886,7 @@ test_expect_success 'detect bogus diffFilter output' '\n \ttest_config interactive.diffFilter \"sed 6d\" &&\n \tprintf y >y &&\n \tforce_color test_must_fail git add -p <y >output 2>&1 &&\n-\tgrep \"mismatched output\" output\n+\ttest_grep \"mismatched output\" output\n '\n \n test_expect_success 'handle iffy colored hunk headers' '\n@@ -896,7 +896,7 @@ test_expect_success 'handle iffy colored hunk headers' '\n \tprintf n >n &&\n \tforce_color git -c interactive.diffFilter=\"sed s/.*@@.*/XX/\" \\\n \t\tadd -p >output 2>&1 <n &&\n-\tgrep \"^XX$\" output\n+\ttest_grep \"^XX$\" output\n '\n \n test_expect_success 'handle very large filtered diff' '\n@@ -1002,7 +1002,7 @@ test_expect_success 'add -p does not expand argument lists' '\n \t# update it, but we want to be sure that our \".\" pathspec\n \t# was not expanded into the argument list of any command.\n \t# So look only for \"not-changed\".\n-\t! grep -E \"^trace: (built-in|exec|run_command): .*not-changed\" trace.out\n+\ttest_grep ! -E \"^trace: (built-in|exec|run_command): .*not-changed\" trace.out\n '\n \n test_expect_success 'hunk-editing handles custom comment char' '\n@@ -1072,21 +1072,21 @@ test_expect_success 'setup different kinds of dirty submodules' '\n \n test_expect_success 'status ignores dirty submodules (except HEAD)' '\n \tgit -C for-submodules add -i </dev/null >output &&\n-\tgrep dirty-head output &&\n-\tgrep dirty-both-ways output &&\n-\t! grep dirty-otherwise output\n+\ttest_grep dirty-head output &&\n+\ttest_grep dirty-both-ways output &&\n+\ttest_grep ! dirty-otherwise output\n '\n \n test_expect_success 'handle submodules' '\n \techo 123 >>for-submodules/dirty-otherwise/initial.t &&\n \n \tforce_color git -C for-submodules add -p dirty-otherwise >output 2>&1 &&\n-\tgrep \"No changes\" output &&\n+\ttest_grep \"No changes\" output &&\n \n \tforce_color git -C for-submodules add -p dirty-head >output 2>&1 <y &&\n \tgit -C for-submodules ls-files --stage dirty-head >actual &&\n \trev=\"$(git -C for-submodules/dirty-head rev-parse HEAD)\" &&\n-\tgrep \"$rev\" actual\n+\ttest_grep \"$rev\" actual\n '\n \n test_expect_success 'set up pathological context' '\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex ec2804eea67c..c66f966a3ab3 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -38,36 +38,36 @@ test_expect_success 'setup' '\n \n test_expect_success 'the default number of context lines is 3' '\n \tgit diff >output &&\n-\t! grep \"^ d\" output &&\n-\tgrep \"^ e\" output &&\n-\tgrep \"^ j\" output &&\n-\t! grep \"^ k\" output\n+\ttest_grep ! \"^ d\" output &&\n+\ttest_grep \"^ e\" output &&\n+\ttest_grep \"^ j\" output &&\n+\ttest_grep ! \"^ k\" output\n '\n \n test_expect_success 'diff.context honored by \"log\"' '\n \tgit log -1 -p >output &&\n-\t! grep firstline output &&\n+\ttest_grep ! firstline output &&\n \tgit config diff.context 8 &&\n \tgit log -1 -p >output &&\n-\tgrep \"^ firstline\" output\n+\ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'The -U option overrides diff.context' '\n \tgit config diff.context 8 &&\n \tgit log -U4 -1 >output &&\n-\t! grep \"^ firstline\" output\n+\ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'diff.context honored by \"diff\"' '\n \tgit config diff.context 8 &&\n \tgit diff >output &&\n-\tgrep \"^ firstline\" output\n+\ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'plumbing not affected' '\n \tgit config diff.context 8 &&\n \tgit diff-files -p >output &&\n-\t! grep \"^ firstline\" output\n+\ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'non-integer config parsing' '\n@@ -85,8 +85,8 @@ test_expect_success 'negative integer config parsing' '\n test_expect_success '-U0 is valid, so is diff.context=0' '\n \tgit config diff.context 0 &&\n \tgit diff >output &&\n-\tgrep \"^-ADDED\" output &&\n-\tgrep \"^+MODIFIED\" output\n+\ttest_grep \"^-ADDED\" output &&\n+\ttest_grep \"^+MODIFIED\" output\n '\n \n test_expect_success '-U2147483647 works' '\n@@ -94,9 +94,9 @@ test_expect_success '-U2147483647 works' '\n \ttest_line_count = 16 x &&\n \tgit diff -U2147483647 >output &&\n \ttest_line_count = 22 output &&\n-\tgrep \"^-ADDED\" output &&\n-\tgrep \"^+MODIFIED\" output &&\n-\tgrep \"^+APPENDED\" output\n+\ttest_grep \"^-ADDED\" output &&\n+\ttest_grep \"^+MODIFIED\" output &&\n+\ttest_grep \"^+APPENDED\" output\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"520851","messageId":"e5c40d37750fe7effa95e8bf2cc24945150364d7.1751128487.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v3.git.1751128486.gitgitgadget@gmail.com","subject":"[PATCH v3 2/4] test: use \"test_config\"","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-06-28T16:34:44Z","receivedAt":"2025-06-28T16:34:53Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nUse the modern \"test_config\" test utility instead of manual\"git config\"\nas the former provides clean up on test completion.\n\nThis is a prerequisite to the commits that follow which add to this test\nfile.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n t/t4055-diff-context.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex c66f966a3ab3..1384a8195705 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -47,43 +47,43 @@ test_expect_success 'the default number of context lines is 3' '\n test_expect_success 'diff.context honored by \"log\"' '\n \tgit log -1 -p >output &&\n \ttest_grep ! firstline output &&\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit log -1 -p >output &&\n \ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'The -U option overrides diff.context' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit log -U4 -1 >output &&\n \ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'diff.context honored by \"diff\"' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit diff >output &&\n \ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'plumbing not affected' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit diff-files -p >output &&\n \ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'non-integer config parsing' '\n-\tgit config diff.context no &&\n+\ttest_config diff.context no &&\n \ttest_must_fail git diff 2>output &&\n \ttest_grep \"bad numeric config value\" output\n '\n \n test_expect_success 'negative integer config parsing' '\n-\tgit config diff.context -1 &&\n+\ttest_config diff.context -1 &&\n \ttest_must_fail git diff 2>output &&\n \ttest_grep \"bad config variable\" output\n '\n \n test_expect_success '-U0 is valid, so is diff.context=0' '\n-\tgit config diff.context 0 &&\n+\ttest_config diff.context 0 &&\n \tgit diff >output &&\n \ttest_grep \"^-ADDED\" output &&\n \ttest_grep \"^+MODIFIED\" output\n-- \ngitgitgadget\n\n"},{"id":"520852","messageId":"b68c58b667c29c66433b4634ad44ee9ec5257e80.1751128487.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v3.git.1751128486.gitgitgadget@gmail.com","subject":"[PATCH v3 4/4] add-patch: add diff.context command line overrides","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-06-28T16:34:46Z","receivedAt":"2025-06-28T16:34:54Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nThis patch compliments the previous commit, where builtins that use\nadd-patch infrastructure now respect diff.context and\ndiff.interHunkContext file configurations.\n\nIn particular, this patch helps users who don't want to set persistent\ncontext configurations or just want a way to override them on a one-time\nbasis, by allowing the relevant builtins to accept corresponding command\nline options that override the file configurations.\n\nThis mimics commands such as diff and log, which allow for both context\nfile configuration and command line overrides.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n Documentation/diff-context-options.adoc | 10 +++++\n Documentation/git-add.adoc              |  2 +\n Documentation/git-checkout.adoc         |  2 +\n Documentation/git-commit.adoc           |  2 +\n Documentation/git-reset.adoc            |  2 +\n Documentation/git-restore.adoc          |  2 +\n Documentation/git-stash.adoc            |  2 +\n add-interactive.c                       | 36 ++++++++++++----\n add-interactive.h                       | 16 +++++--\n add-patch.c                             |  5 ++-\n builtin/add.c                           | 21 ++++++++--\n builtin/checkout.c                      | 31 ++++++++++++--\n builtin/commit.c                        | 16 ++++++-\n builtin/reset.c                         | 17 +++++++-\n builtin/stash.c                         | 56 ++++++++++++++++++++-----\n commit.h                                |  3 +-\n parse-options.h                         |  2 +\n t/t3701-add-interactive.sh              | 49 ++++++++++++++++++++++\n t/t4055-diff-context.sh                 | 30 +++++++++++++\n t/t9902-completion.sh                   |  2 +\n 20 files changed, 271 insertions(+), 35 deletions(-)\n create mode 100644 Documentation/diff-context-options.adoc\n\ndiff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc\nnew file mode 100644\nindex 000000000000..e161260358ff\n--- /dev/null\n+++ b/Documentation/diff-context-options.adoc\n@@ -0,0 +1,10 @@\n+`-U<n>`::\n+`--unified=<n>`::\n+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n+\tor 3 if the config option is unset.\n+\n+`--inter-hunk-context=<n>`::\n+\tShow the context between diff hunks, up to the specified _<number>_\n+\tof lines, thereby fusing hunks that are close to each other.\n+\tDefaults to `diff.interHunkContext` or 0 if the config option\n+\tis unset.\ndiff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\nindex eba0b419ce50..b7a735824d6c 100644\n--- a/Documentation/git-add.adoc\n+++ b/Documentation/git-add.adoc\n@@ -104,6 +104,8 @@ This effectively runs `add --interactive`, but bypasses the\n initial command menu and directly jumps to the `patch` subcommand.\n See ``Interactive mode'' for details.\n \n+include::diff-context-options.adoc[]\n+\n `-e`::\n `--edit`::\n \tOpen the diff vs. the index in an editor and let the user\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex ee83b6d9ba9a..40e02cfd6562 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -289,6 +289,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.\n Note that this option uses the no overlay mode by default (see also\n `--overlay`), and currently doesn't support overlay mode.\n \n+include::diff-context-options.adoc[]\n+\n `--ignore-other-worktrees`::\n \t`git checkout` refuses when the wanted branch is already checked\n \tout or otherwise in use by another worktree. This option makes\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex dc219025f1eb..ae988a883b5b 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -76,6 +76,8 @@ OPTIONS\n \twhich changes to commit. See linkgit:git-add[1] for\n \tdetails.\n \n+include::diff-context-options.adoc[]\n+\n `-C <commit>`::\n `--reuse-message=<commit>`::\n \tTake an existing _<commit>_ object, and reuse the log message\ndiff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc\nindex 53ab88c5451c..50e8a0ba6f66 100644\n--- a/Documentation/git-reset.adoc\n+++ b/Documentation/git-reset.adoc\n@@ -125,6 +125,8 @@ OPTIONS\n \tseparated with _NUL_ character and all other characters are taken\n \tliterally (including newlines and quotes).\n \n+include::diff-context-options.adoc[]\n+\n `--`::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc\nindex 877b7772e667..1dcc2bb7aea3 100644\n--- a/Documentation/git-restore.adoc\n+++ b/Documentation/git-restore.adoc\n@@ -52,6 +52,8 @@ leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to\n \tMode\" section of linkgit:git-add[1] to learn how to operate\n \tthe `--patch` mode.\n \n+include::diff-context-options.adoc[]\n+\n `-W`::\n `--worktree`::\n `-S`::\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex 1a5177f4986c..0578c619c410 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -208,6 +208,8 @@ to learn how to operate the `--patch` mode.\n The `--patch` option implies `--keep-index`.  You can use\n `--no-keep-index` to override this.\n \n+include::diff-context-options.adoc[]\n+\n -S::\n --staged::\n \tThis option is only valid for `push` and `save` commands.\ndiff --git a/add-interactive.c b/add-interactive.c\nindex e0aafb8dd02a..c343568bf7ec 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -36,7 +36,8 @@ static void init_color(struct repository *r, struct add_i_state *s,\n \tfree(key);\n }\n \n-void init_add_i_state(struct add_i_state *s, struct repository *r)\n+void init_add_i_state(struct add_i_state *s, struct repository *r,\n+\t\t      struct add_p_opt *add_p_opt)\n {\n \tconst char *value;\n \tint context;\n@@ -98,6 +99,17 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n \tif (s->use_single_key)\n \t\tsetbuf(stdin, NULL);\n+\n+\tif (add_p_opt->context != -1) {\n+\t\tif (add_p_opt->context < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"--unified\");\n+\t\ts->context = add_p_opt->context;\n+\t}\n+\tif (add_p_opt->interhunkcontext != -1) {\n+\t\tif (add_p_opt->interhunkcontext < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"--inter-hunk-context\");\n+\t\ts->interhunkcontext = add_p_opt->interhunkcontext;\n+\t}\n }\n \n void clear_add_i_state(struct add_i_state *s)\n@@ -986,6 +998,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,\n \topts->prompt = N_(\"Patch update\");\n \tcount = list_and_choose(s, files, opts);\n \tif (count > 0) {\n+\t\tstruct add_p_opt add_p_opt = {\n+\t\t\t.context = s->context,\n+\t\t\t.interhunkcontext = s->interhunkcontext,\n+\t\t};\n \t\tstruct strvec args = STRVEC_INIT;\n \t\tstruct pathspec ps_selected = { 0 };\n \n@@ -996,7 +1012,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,\n \t\tparse_pathspec(&ps_selected,\n \t\t\t       PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,\n \t\t\t       PATHSPEC_LITERAL_PATH, \"\", args.v);\n-\t\tres = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);\n+\t\tres = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected);\n \t\tstrvec_clear(&args);\n \t\tclear_pathspec(&ps_selected);\n \t}\n@@ -1031,10 +1047,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,\n \tif (count > 0) {\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n-\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\",\n-\t\t\t     oid_to_hex(!is_initial ? &oid :\n-\t\t\t\t\ts->r->hash_algo->empty_tree),\n-\t\t\t     \"--\", NULL);\n+\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\", NULL);\n+\t\tif (s->context != -1)\n+\t\t\tstrvec_pushf(&cmd.args, \"--unified=%i\", s->context);\n+\t\tif (s->interhunkcontext != -1)\n+\t\t\tstrvec_pushf(&cmd.args, \"--inter-hunk-context=%i\", s->interhunkcontext);\n+\t\tstrvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :\n+\t\t\t     s->r->hash_algo->empty_tree), \"--\", NULL);\n \t\tfor (i = 0; i < files->items.nr; i++)\n \t\t\tif (files->selected[i])\n \t\t\t\tstrvec_push(&cmd.args,\n@@ -1127,7 +1146,8 @@ static void command_prompt_help(struct add_i_state *s)\n \t\t\t _(\"(empty) select nothing\"));\n }\n \n-int run_add_i(struct repository *r, const struct pathspec *ps)\n+int run_add_i(struct repository *r, const struct pathspec *ps,\n+\t      struct add_p_opt *add_p_opt)\n {\n \tstruct add_i_state s = { NULL };\n \tstruct print_command_item_data data = { \"[\", \"]\" };\n@@ -1170,7 +1190,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t\t->util = util;\n \t}\n \n-\tinit_add_i_state(&s, r);\n+\tinit_add_i_state(&s, r, add_p_opt);\n \n \t/*\n \t * When color was asked for, use the prompt color for\ndiff --git a/add-interactive.h b/add-interactive.h\nindex c63f35b14be8..4213dcd67b9a 100644\n--- a/add-interactive.h\n+++ b/add-interactive.h\n@@ -3,6 +3,13 @@\n \n #include \"color.h\"\n \n+struct add_p_opt {\n+\tint context;\n+\tint interhunkcontext;\n+};\n+\n+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }\n+\n struct add_i_state {\n \tstruct repository *r;\n \tint use_color;\n@@ -21,12 +28,14 @@ struct add_i_state {\n \tint context, interhunkcontext;\n };\n \n-void init_add_i_state(struct add_i_state *s, struct repository *r);\n+void init_add_i_state(struct add_i_state *s, struct repository *r,\n+\t\t      struct add_p_opt *add_p_opt);\n void clear_add_i_state(struct add_i_state *s);\n \n struct repository;\n struct pathspec;\n-int run_add_i(struct repository *r, const struct pathspec *ps);\n+int run_add_i(struct repository *r, const struct pathspec *ps,\n+\t      struct add_p_opt *add_p_opt);\n \n enum add_p_mode {\n \tADD_P_ADD,\n@@ -37,6 +46,7 @@ enum add_p_mode {\n };\n \n int run_add_p(struct repository *r, enum add_p_mode mode,\n-\t      const char *revision, const struct pathspec *ps);\n+\t      struct add_p_opt *o, const char *revision,\n+\t      const struct pathspec *ps);\n \n #endif\ndiff --git a/add-patch.c b/add-patch.c\nindex b43ca1600738..c0d33820c558 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -1766,14 +1766,15 @@ soft_increment:\n }\n \n int run_add_p(struct repository *r, enum add_p_mode mode,\n-\t      const char *revision, const struct pathspec *ps)\n+\t      struct add_p_opt *o, const char *revision,\n+\t      const struct pathspec *ps)\n {\n \tstruct add_p_state s = {\n \t\t{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tsize_t i, binary_count = 0;\n \n-\tinit_add_i_state(&s.s, r);\n+\tinit_add_i_state(&s.s, r, o);\n \n \tif (mode == ADD_P_STASH)\n \t\ts.mode = &patch_mode_stash;\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 7c292ffdc6c2..a00dab9b8fa0 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -29,6 +29,7 @@ static const char * const builtin_add_usage[] = {\n \tNULL\n };\n static int patch_interactive, add_interactive, edit_interactive;\n+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n static int take_worktree_changes;\n static int add_renormalize;\n static int pathspec_file_nul;\n@@ -157,7 +158,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec *\n int interactive_add(struct repository *repo,\n \t\t    const char **argv,\n \t\t    const char *prefix,\n-\t\t    int patch)\n+\t\t    int patch, struct add_p_opt *add_p_opt)\n {\n \tstruct pathspec pathspec;\n \tint ret;\n@@ -169,9 +170,9 @@ int interactive_add(struct repository *repo,\n \t\t       prefix, argv);\n \n \tif (patch)\n-\t\tret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);\n+\t\tret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec);\n \telse\n-\t\tret = !!run_add_i(repo, &pathspec);\n+\t\tret = !!run_add_i(repo, &pathspec, add_p_opt);\n \n \tclear_pathspec(&pathspec);\n \treturn ret;\n@@ -253,6 +254,8 @@ static struct option builtin_add_options[] = {\n \tOPT_GROUP(\"\"),\n \tOPT_BOOL('i', \"interactive\", &add_interactive, N_(\"interactive picking\")),\n \tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"select hunks interactively\")),\n+\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \tOPT_BOOL('e', \"edit\", &edit_interactive, N_(\"edit current diff and apply\")),\n \tOPT__FORCE(&ignored_too, N_(\"allow adding otherwise ignored files\"), 0),\n \tOPT_BOOL('u', \"update\", &take_worktree_changes, N_(\"update tracked files\")),\n@@ -394,6 +397,11 @@ int cmd_add(int argc,\n \tprepare_repo_settings(repo);\n \trepo->settings.command_requires_full_index = 0;\n \n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tif (patch_interactive)\n \t\tadd_interactive = 1;\n \tif (add_interactive) {\n@@ -401,7 +409,12 @@ int cmd_add(int argc,\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--dry-run\", \"--interactive/--patch\");\n \t\tif (pathspec_from_file)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--pathspec-from-file\", \"--interactive/--patch\");\n-\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive));\n+\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--interactive/--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--interactive/--patch\");\n \t}\n \n \tif (edit_interactive) {\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 536192d3456c..3737ba4c3920 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -61,6 +61,8 @@ static const char * const restore_usage[] = {\n \n struct checkout_opts {\n \tint patch_mode;\n+\tint patch_context;\n+\tint patch_interhunk_context;\n \tint quiet;\n \tint merge;\n \tint force;\n@@ -104,7 +106,12 @@ struct checkout_opts {\n \tstruct tree *source_tree;\n };\n \n-#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 }\n+#define CHECKOUT_OPTS_INIT { \\\n+\t.conflict_style = -1, \\\n+\t.merge = -1, \\\n+\t.patch_context = -1, \\\n+\t.patch_interhunk_context = -1, \\\n+}\n \n struct branch_info {\n \tchar *name; /* The short name used */\n@@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts,\n \n \tif (opts->patch_mode) {\n \t\tenum add_p_mode patch_mode;\n+\t\tstruct add_p_opt add_p_opt = {\n+\t\t\t.context = opts->patch_context,\n+\t\t\t.interhunkcontext = opts->patch_interhunk_context,\n+\t\t};\n \t\tconst char *rev = new_branch_info->name;\n \t\tchar rev_oid[GIT_MAX_HEXSZ + 1];\n \n@@ -564,8 +575,8 @@ static int checkout_paths(const struct checkout_opts *opts,\n \t\telse\n \t\t\tBUG(\"either flag must have been set, worktree=%d, index=%d\",\n \t\t\t    opts->checkout_worktree, opts->checkout_index);\n-\t\treturn !!run_add_p(the_repository, patch_mode, rev,\n-\t\t\t\t   &opts->pathspec);\n+\t\treturn !!run_add_p(the_repository, patch_mode, &add_p_opt,\n+\t\t\t\t   rev, &opts->pathspec);\n \t}\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n@@ -1738,6 +1749,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,\n \t\t\t      N_(\"checkout their version for unmerged files\"),\n \t\t\t      3, PARSE_OPT_NONEG),\n \t\tOPT_BOOL('p', \"patch\", &opts->patch_mode, N_(\"select hunks interactively\")),\n+\t\tOPT_DIFF_UNIFIED(&opts->patch_context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context),\n \t\tOPT_BOOL(0, \"ignore-skip-worktree-bits\", &opts->ignore_skipworktree,\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n \t\tOPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),\n@@ -1780,6 +1793,18 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     usagestr, parseopt_flags);\n \n+\tif (opts->patch_context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (opts->patch_interhunk_context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n+\tif (!opts->patch_mode) {\n+\t\tif (opts->patch_context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (opts->patch_interhunk_context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n \tif (opts->show_progress < 0) {\n \t\tif (opts->quiet)\n \t\t\topts->show_progress = 0;\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex fba0dded64a7..73673bc7db9f 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -19,6 +19,7 @@\n #include \"environment.h\"\n #include \"diff.h\"\n #include \"commit.h\"\n+#include \"add-interactive.h\"\n #include \"gettext.h\"\n #include \"revision.h\"\n #include \"wt-status.h\"\n@@ -122,6 +123,7 @@ static const char *edit_message, *use_message;\n static char *fixup_message, *fixup_commit, *squash_message;\n static const char *fixup_prefix;\n static int all, also, interactive, patch_interactive, only, amend, signoff;\n+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n static int edit_flag = -1; /* unspecified */\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int config_commit_verbose = -1; /* unspecified */\n@@ -354,6 +356,11 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \tconst char *ret;\n \tchar *path = NULL;\n \n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tif (is_status)\n \t\trefresh_flags |= REFRESH_UNMERGED;\n \tparse_pathspec(&pathspec, 0,\n@@ -400,7 +407,7 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n \t\tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n \n-\t\tif (interactive_add(the_repository, argv, prefix, patch_interactive) != 0)\n+\t\tif (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0)\n \t\t\tdie(_(\"interactive add failed\"));\n \n \t\tthe_repository->index_file = old_repo_index_file;\n@@ -424,6 +431,11 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\tcommit_style = COMMIT_NORMAL;\n \t\tret = get_lock_file_path(&index_lock);\n \t\tgoto out;\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--interactive/--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--interactive/--patch\");\n \t}\n \n \t/*\n@@ -1722,6 +1734,8 @@ int cmd_commit(int argc,\n \t\tOPT_BOOL('i', \"include\", &also, N_(\"add specified files to index for commit\")),\n \t\tOPT_BOOL(0, \"interactive\", &interactive, N_(\"interactively add files\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"interactively add changes\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT_BOOL('o', \"only\", &only, N_(\"commit only specified files\")),\n \t\tOPT_BOOL('n', \"no-verify\", &no_verify, N_(\"bypass pre-commit and commit-msg hooks\")),\n \t\tOPT_BOOL(0, \"dry-run\", &dry_run, N_(\"show what would be committed\")),\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex dc50ffc1ac59..9fb32795c9c5 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -346,6 +346,7 @@ int cmd_reset(int argc,\n \tstruct object_id oid;\n \tstruct pathspec pathspec;\n \tint intent_to_add = 0;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tconst struct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"no-refresh\", &no_refresh,\n@@ -370,6 +371,8 @@ int cmd_reset(int argc,\n \t\t\t       PARSE_OPT_OPTARG,\n \t\t\t       option_parse_recurse_submodules_worktree_updater),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode, N_(\"select hunks interactively\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT_BOOL('N', \"intent-to-add\", &intent_to_add,\n \t\t\t\tN_(\"record only the fact that removed paths will be added later\")),\n \t\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n@@ -420,6 +423,11 @@ int cmd_reset(int argc,\n \t\toidcpy(&oid, &tree->object.oid);\n \t}\n \n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \n@@ -427,9 +435,14 @@ int cmd_reset(int argc,\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--patch\", \"--{hard,mixed,soft}\");\n \t\ttrace2_cmd_mode(\"patch-interactive\");\n-\t\tupdate_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev,\n-\t\t\t\t   &pathspec);\n+\t\tupdate_ref_status = !!run_add_p(the_repository, ADD_P_RESET,\n+\t\t\t\t\t\t&add_p_opt, rev, &pathspec);\n \t\tgoto cleanup;\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n \t}\n \n \t/* git reset tree [--] paths... can be used to\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 7cd3ad8aa48e..6da162e5e69a 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1242,7 +1242,8 @@ done:\n }\n \n static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n-\t\t       struct strbuf *out_patch, int quiet)\n+\t\t       struct strbuf *out_patch, int quiet,\n+\t\t       struct add_p_opt *add_p_opt)\n {\n \tint ret = 0;\n \tstruct child_process cp_read_tree = CHILD_PROCESS_INIT;\n@@ -1267,7 +1268,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n \told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n \tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n \n-\tret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps);\n+\tret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps);\n \n \tthe_repository->index_file = old_repo_index_file;\n \tif (old_index_env && *old_index_env)\n@@ -1362,8 +1363,8 @@ done:\n }\n \n static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,\n-\t\t\t   int include_untracked, int patch_mode, int only_staged,\n-\t\t\t   struct stash_info *info, struct strbuf *patch,\n+\t\t\t   int include_untracked, int patch_mode, struct add_p_opt *add_p_opt,\n+\t\t\t   int only_staged, struct stash_info *info, struct strbuf *patch,\n \t\t\t   int quiet)\n {\n \tint ret = 0;\n@@ -1444,7 +1445,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\tuntracked_commit_option = 1;\n \t}\n \tif (patch_mode) {\n-\t\tret = stash_patch(info, ps, patch, quiet);\n+\t\tret = stash_patch(info, ps, patch, quiet, add_p_opt);\n \t\tif (ret < 0) {\n \t\t\tif (!quiet)\n \t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n@@ -1519,7 +1520,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n \tif (!check_changes_tracked_files(&ps))\n \t\treturn 0;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n+\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,\n \t\t\t      NULL, 0);\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n@@ -1530,7 +1531,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n }\n \n static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,\n-\t\t\t int keep_index, int patch_mode, int include_untracked, int only_staged)\n+\t\t\t int keep_index, int patch_mode, struct add_p_opt *add_p_opt,\n+\t\t\t int include_untracked, int only_staged)\n {\n \tint ret = 0;\n \tstruct stash_info info = STASH_INFO_INIT;\n@@ -1600,8 +1602,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \n \tif (stash_msg)\n \t\tstrbuf_addstr(&stash_msg_buf, stash_msg);\n-\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,\n-\t\t\t    &info, &patch, quiet)) {\n+\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,\n+\t\t\t    add_p_opt, only_staged, &info, &patch, quiet)) {\n \t\tret = -1;\n \t\tgoto done;\n \t}\n@@ -1774,6 +1776,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \tconst char *stash_msg = NULL;\n \tchar *pathspec_from_file = NULL;\n \tstruct pathspec ps;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n@@ -1781,6 +1784,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n \t\t\t N_(\"include untracked files in stash\")),\n@@ -1836,8 +1841,20 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--pathspec-file-nul\", \"--pathspec-from-file\");\n \t}\n \n+\tif (!patch_mode) {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,\n-\t\t\t    include_untracked, only_staged);\n+\t\t\t    &add_p_opt, include_untracked, only_staged);\n \n \tclear_pathspec(&ps);\n \tfree(pathspec_from_file);\n@@ -1862,6 +1879,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \tconst char *stash_msg = NULL;\n \tstruct pathspec ps;\n \tstruct strbuf stash_msg_buf = STRBUF_INIT;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n@@ -1869,6 +1887,8 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n \t\t\t N_(\"include untracked files in stash\")),\n@@ -1887,8 +1907,22 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \t\tstash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');\n \n \tmemset(&ps, 0, sizeof(ps));\n+\n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n+\tif (!patch_mode) {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index,\n-\t\t\t    patch_mode, include_untracked, only_staged);\n+\t\t\t    patch_mode, &add_p_opt, include_untracked,\n+\t\t\t    only_staged);\n \n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\ndiff --git a/commit.h b/commit.h\nindex 70c870dae4d4..7a7fedbc2f14 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -2,6 +2,7 @@\n #define COMMIT_H\n \n #include \"object.h\"\n+#include \"add-interactive.h\"\n \n struct signature_check;\n struct strbuf;\n@@ -257,7 +258,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *);\n int interactive_add(struct repository *repo,\n \t\t    const char **argv,\n \t\t    const char *prefix,\n-\t\t    int patch);\n+\t\t    int patch, struct add_p_opt *add_p_opt);\n \n struct commit_extra_header {\n \tstruct commit_extra_header *next;\ndiff --git a/parse-options.h b/parse-options.h\nindex 91c3e3c29b3d..bdae8f116198 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -616,6 +616,8 @@ int parse_opt_tracking_mode(const struct option *, const char *, int);\n #define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, \"pathspec-from-file\", v, N_(\"read pathspec from file\"))\n #define OPT_PATHSPEC_FILE_NUL(v)  OPT_BOOL(0, \"pathspec-file-nul\", v, N_(\"with --pathspec-from-file, pathspec elements are separated with NUL character\"))\n #define OPT_AUTOSTASH(v) OPT_BOOL(0, \"autostash\", v, N_(\"automatically stash/stash pop before and after\"))\n+#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', \"unified\", v, N_(\"generate diffs with <n> lines context\"), PARSE_OPT_NONEG)\n+#define OPT_DIFF_INTERHUNK_CONTEXT(v) OPT_INTEGER_F(0, \"inter-hunk-context\", v, N_(\"show context between diff hunks up to the specified number of lines\"), PARSE_OPT_NONEG)\n \n #define OPT_IPVERSION(v) \\\n \tOPT_SET_INT_F('4', \"ipv4\", (v), N_(\"use IPv4 addresses only\"), \\\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex 18dc329ea1f6..d08a87f9ec53 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -1252,4 +1252,53 @@ test_expect_success 'add -p rejects negative diff.context' '\n \ttest_grep \"diff.context cannot be negative\" output\n '\n \n+for cmd in add checkout restore 'commit -m file'\n+do\n+\ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" \"\n+\t\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n+\t\tgit add file &&\n+\t\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n+\t\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n+\t\t\t$cmd -p -U 4 --inter-hunk-context 2 >actual &&\n+\t\ttest_grep \\\"@@ -2,20 +2,20 @@\\\" actual\n+\t\"\n+done\n+\n+test_expect_success 'reset accepts -U and --inter-hunk-context' '\n+\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n+\tgit commit -m file file &&\n+\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n+\tgit add file &&\n+\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n+\t\treset -p -U 4 --inter-hunk-context 2 >actual &&\n+\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n+'\n+\n+test_expect_success 'stash accepts -U and --inter-hunk-context' '\n+\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n+\tgit commit -m file file &&\n+\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n+\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n+\t\tstash -p -U 4 --inter-hunk-context 2 >actual &&\n+\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n+'\n+\n+for cmd in add checkout commit reset restore 'stash save' 'stash push'\n+do\n+\ttest_expect_success \"$cmd rejects invalid context options\" \"\n+\t\ttest_must_fail git $cmd -p -U -3 2>actual &&\n+\t\tcat actual | echo &&\n+\t\ttest_grep -e \\\"'--unified' cannot be negative\\\" actual &&\n+\n+\t\ttest_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&\n+\t\ttest_grep -e \\\"'--inter-hunk-context' cannot be negative\\\" actual &&\n+\n+\t\ttest_must_fail git $cmd -U 7 2>actual &&\n+\t\ttest_grep -E \\\"'--unified' requires '(--interactive/)?--patch'\\\" actual &&\n+\n+\t\ttest_must_fail git $cmd --inter-hunk-context 2 2>actual &&\n+\t\ttest_grep -E \\\"'--inter-hunk-context' requires '(--interactive/)?--patch'\\\" actual\n+\t\"\n+done\n+\n test_done\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex 1384a8195705..0158fe6568cb 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -58,6 +58,36 @@ test_expect_success 'The -U option overrides diff.context' '\n \ttest_grep ! \"^ firstline\" output\n '\n \n+test_expect_success 'The -U option overrides diff.context for \"add\"' '\n+\ttest_config diff.context 8 &&\n+\tgit add -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"commit\"' '\n+\ttest_config diff.context 8 &&\n+\t! git commit -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"checkout\"' '\n+\ttest_config diff.context 8 &&\n+\tgit checkout -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"stash\"' '\n+\ttest_config diff.context 8 &&\n+\t! git stash -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"restore\"' '\n+\ttest_config diff.context 8 &&\n+\tgit restore -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n test_expect_success 'diff.context honored by \"diff\"' '\n \ttest_config diff.context 8 &&\n \tgit diff >output &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 343b8cd1912b..6650d33fba69 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2596,6 +2596,8 @@ test_expect_success 'double dash \"git checkout\"' '\n \t--merge Z\n \t--conflict=Z\n \t--patch Z\n+\t--unified=Z\n+\t--inter-hunk-context=Z\n \t--ignore-skip-worktree-bits Z\n \t--ignore-other-worktrees Z\n \t--recurse-submodules Z\n-- \ngitgitgadget\n"},{"id":"520853","messageId":"1ec8a1384862765795f17e2d0fcb7b601ef2b25f.1751128487.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v3.git.1751128486.gitgitgadget@gmail.com","subject":"[PATCH v3 3/4] add-patch: respect diff.context configuration","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-06-28T16:34:45Z","receivedAt":"2025-06-28T16:34:54Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nVarious builtins that use add-patch infrastructure do not respect\nthe user's diff.context and diff.interHunkContext file configurations.\nThis patch fixes this inconsistency.\n\nThis is because the plumbing commands used by \"git add -p\" to generate\nthe diff do not read those config settings. Fix this by reading the\nconfig before generating the patch and passing it along to the diff\ncommand with the \"-U\" and \"--inter-hunk-context\" command-line options.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n add-interactive.c          | 17 +++++++++++++++++\n add-interactive.h          |  1 +\n add-patch.c                |  6 ++++++\n t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++\n 4 files changed, 46 insertions(+)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 97ff35b6f12a..e0aafb8dd02a 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -39,8 +39,12 @@ static void init_color(struct repository *r, struct add_i_state *s,\n void init_add_i_state(struct add_i_state *s, struct repository *r)\n {\n \tconst char *value;\n+\tint context;\n+\tint interhunkcontext;\n \n \ts->r = r;\n+\ts->context = -1;\n+\ts->interhunkcontext = -1;\n \n \tif (repo_config_get_value(r, \"color.interactive\", &value))\n \t\ts->use_color = -1;\n@@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \trepo_config_get_string(r, \"diff.algorithm\",\n \t\t\t       &s->interactive_diff_algorithm);\n \n+\tif (!repo_config_get_int(r, \"diff.context\", &context)) {\n+\t\tif (context < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n+\t\telse\n+\t\t\ts->context = context;\n+\t}\n+\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &interhunkcontext)) {\n+\t\tif (interhunkcontext < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n+\t\telse\n+\t\t\ts->interhunkcontext = interhunkcontext;\n+\t}\n+\n \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n \tif (s->use_single_key)\n \t\tsetbuf(stdin, NULL);\ndiff --git a/add-interactive.h b/add-interactive.h\nindex 693f125e8e4b..c63f35b14be8 100644\n--- a/add-interactive.h\n+++ b/add-interactive.h\n@@ -18,6 +18,7 @@ struct add_i_state {\n \n \tint use_single_key;\n \tchar *interactive_diff_filter, *interactive_diff_algorithm;\n+\tint context, interhunkcontext;\n };\n \n void init_add_i_state(struct add_i_state *s, struct repository *r);\ndiff --git a/add-patch.c b/add-patch.c\nindex 95c67d8c80c4..b43ca1600738 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n {\n \tstruct strvec args = STRVEC_INIT;\n \tconst char *diff_algorithm = s->s.interactive_diff_algorithm;\n+\tint diff_context = s->s.context;\n+\tint diff_interhunkcontext = s->s.interhunkcontext;\n \tstruct strbuf *plain = &s->plain, *colored = NULL;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\\0';\n@@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \tint res;\n \n \tstrvec_pushv(&args, s->mode->diff_cmd);\n+\tif (diff_context != -1)\n+\t\tstrvec_pushf(&args, \"--unified=%i\", diff_context);\n+\tif (diff_interhunkcontext != -1)\n+\t\tstrvec_pushf(&args, \"--inter-hunk-context=%i\", diff_interhunkcontext);\n \tif (diff_algorithm)\n \t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", diff_algorithm);\n \tif (s->revision) {\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex b088ee141ff4..18dc329ea1f6 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -1230,4 +1230,26 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'add -p respects diff.context' '\n+\ttest_write_lines a b c d e f g h i j k l m >file &&\n+\tgit add file &&\n+\ttest_write_lines a b c d e f G h i j k l m >file &&\n+\techo y | git -c diff.context=5 add -p >actual &&\n+\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n+'\n+\n+test_expect_success 'add -p respects diff.interHunkContext' '\n+\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n+\tgit add file &&\n+\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n+\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n+\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n+'\n+\n+test_expect_success 'add -p rejects negative diff.context' '\n+\ttest_config diff.context -1 &&\n+\ttest_must_fail git add -p 2>output &&\n+\ttest_grep \"diff.context cannot be negative\" output\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"520931","messageId":"xmqqwm8t2oin.fsf@gitster.g","threadId":"63396","inReplyTo":"pull.1915.v3.git.1751128486.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/4] Better support for customising context lines in --patch commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-30T16:16:32Z","receivedAt":"2025-06-30T16:16:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Leon Michalak (4):\n>   test: use \"test_grep\"\n>   test: use \"test_config\"\n\nThese make it sound as if they touch all test scripts under t/ but\napparently that is not what this series is doing (and we do not want\nto see a huge churn like that anyway).  Would something like\n\n    t: use test_grep in t3701, t4055, and t9902\n\nwork better?\n\n>   add-patch: respect diff.context configuration\n>   add-patch: add diff.context command line overrides\n"},{"id":"520932","messageId":"xmqqplel2o6q.fsf@gitster.g","threadId":"63396","inReplyTo":"044a93014b677914870a872a9c317a46a5a81699.1751128487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/4] test: use \"test_grep\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-30T16:23:41Z","receivedAt":"2025-06-30T16:23:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Leon Michalak <leonmichalak6@gmail.com>\n>\n> Use the modern \"test_grep\" test utility instead of regular \"grep\" which\n> provides better debug information if tests fail.\n>\n> This is a prerequisite to the commits that follow which add to both test\n> files.\n\nJust a terminology thing, but we would phrase the last paragraph\nmore like\n\n    As a preparatory clean-up, use the \"test_grep\" test utility\n    instead of regular \"grep\" which provides better debug\n    information if tests fail.\n\nto avoid saying \"This does X\", \"This commit is Y\", etc.  It also\navoids giving a wrong impression by misusing the word \"prerequisite\"\nwhich we almost always use for a step that cannot be skipped.  While\nwe add new tests to the same file, we _could_ leave these existing\ntests as-is, but there is a good reason to making this change\nbeforehand, which we often call is a \"preparatory clean-up\".\n\n> @@ -86,7 +86,7 @@ test_expect_success 'revert works (initial)' '\n>  \tgit add file &&\n>  \ttest_write_lines r 1 | git add -i &&\n>  \tgit ls-files >output &&\n> -\t! grep . output\n> +\ttest_grep ! . output\n>  '\n\nGood (we sometime see people got the negation wrong).\n\nThanks.\n"},{"id":"520933","messageId":"xmqqjz4t2nn9.fsf@gitster.g","threadId":"63396","inReplyTo":"e5c40d37750fe7effa95e8bf2cc24945150364d7.1751128487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/4] test: use \"test_config\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-30T16:35:22Z","receivedAt":"2025-06-30T16:35:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Leon Michalak <leonmichalak6@gmail.com>\n>\n> Use the modern \"test_config\" test utility instead of manual\"git config\"\n> as the former provides clean up on test completion.\n\nHere \"completion\" is not \"all the tests in the script are done\", but\n\"each of the test_expect_{success,failure} piece that uses test_config\".\n\nDrop \"modern\".  It was invented 14 years ago (the same can be said\nfor test_grep which was called test_i18ngrep and had an extra\npurpose, which was invented in the same year).\n\nThis conversion, unlike \"test_grep\" needs to be done a bit\ncarefully.  The fact the configuration is removed after the test\npiece \"test_config\" was used means any tests after the test that was\noriginally using \"git config\" needs to be inspected to make sure\nit was *not* relying on the value that was left by the previous\ntest piece.\n\nFor example...\n\n>  test_expect_success 'diff.context honored by \"log\"' '\n>  \tgit log -1 -p >output &&\n>  \ttest_grep ! firstline output &&\n> -\tgit config diff.context 8 &&\n> +\ttest_config diff.context 8 &&\n>  \tgit log -1 -p >output &&\n>  \ttest_grep \"^ firstline\" output\n>  '\n\n... the test piece after this one may have assumed (wrongly!  The\nassumption does not hold if this test failed before reaching \"git\nconfig\") that diff.context is still set to 8 but that is no longer\nthe case.  But that one is OK because ...\n\n>  test_expect_success 'The -U option overrides diff.context' '\n> -\tgit config diff.context 8 &&\n> +\ttest_config diff.context 8 &&\n>  \tgit log -U4 -1 >output &&\n>  \ttest_grep ! \"^ firstline\" output\n>  '\n\n... it was setting it for itself.  The same can be said with other\ntests (not quoted in this reply).  They all set things up for\nthemselves, which is a good hygiene.\n\nThat means the last one in the patch needs to be inspected\ncarefully.  We do not know from the post-context of the patch what\nthe next test used to expect.\n\n>  test_expect_success '-U0 is valid, so is diff.context=0' '\n> -\tgit config diff.context 0 &&\n> +\ttest_config diff.context 0 &&\n>  \tgit diff >output &&\n>  \ttest_grep \"^-ADDED\" output &&\n>  \ttest_grep \"^+MODIFIED\" output\n\nThe one that comes after this one is about giving an explicit\ncommand line option -U<n>.  It should not be affected by a\ndiff.context conffiguration variable that is lower-precedence\nso we should be OK.\n\nThanks.\n\n"},{"id":"520937","messageId":"xmqqtt3x185d.fsf@gitster.g","threadId":"63396","inReplyTo":"1ec8a1384862765795f17e2d0fcb7b601ef2b25f.1751128487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 3/4] add-patch: respect diff.context configuration","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-30T16:55:26Z","receivedAt":"2025-06-30T16:55:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Leon Michalak <leonmichalak6@gmail.com>\n>\n> Various builtins that use add-patch infrastructure do not respect\n> the user's diff.context and diff.interHunkContext file configurations.\n\nGreat.  \"add-patch.c\" invokes \"diff-files\", \"diff-index\" plumbing\ncommands to do its thing, and these plumbing commands deliberately\nignore such configuration variables, unlike \"diff\" Porcelain command\nthat is meant for end-user consumption.\n\n> This patch fixes this inconsistency.\n\nIf we were spelling it out, we would say \"Fix this inconsistency\" in\nimperative.  But you never talked about \"this inconsistency\" so far,\nso it is not just confusing.  It hints an incorrect conclusion that\nthe difference between plumbing diff-{files,index,tree} and\nPorcelain diff is an inconsistency that needs to be \"fixed\", which\nis not true.\n\nFollow the first paragraph with an explanation why it is a bad\nthing.  For example:\n\n    The user may be used to seeing their diffs with customized\n    context size, but not in the patches \"git add -p\" shows them to\n    pick from.\n\nThat would implicitly tell readers that we would want the patch\nshown by \"add -p\" generated with diff.context given by the user.\nSo we can outline the solution next.\n\n    Teach add-patch infrastructure to read these configuration\n    variables and pass their values when spawning the underlying\n    plumbing commands as their command line option.\n\nor something.\n\n> @@ -39,8 +39,12 @@ static void init_color(struct repository *r, struct add_i_state *s,\n>  void init_add_i_state(struct add_i_state *s, struct repository *r)\n>  {\n>  \tconst char *value;\n> +\tint context;\n> +\tint interhunkcontext;\n>  \n>  \ts->r = r;\n> +\ts->context = -1;\n> +\ts->interhunkcontext = -1;\n\nHmph, context/interhunkcontext variables serve no purpose other than\npeeking into the value before assigning it to s->{context,interhunkcontext}\nmembers.  In a sense, they may be confusing than they are worth.\n\n>  \tif (repo_config_get_value(r, \"color.interactive\", &value))\n>  \t\ts->use_color = -1;\n> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n>  \trepo_config_get_string(r, \"diff.algorithm\",\n>  \t\t\t       &s->interactive_diff_algorithm);\n>  \n> +\tif (!repo_config_get_int(r, \"diff.context\", &context)) {\n> +\t\tif (context < 0)\n> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n> +\t\telse\n> +\t\t\ts->context = context;\n> +\t}\n\nWould the code be easier to understand if it is written more like\n\n\tif (!repo_config_get_int(r, \"diff.context\", &s->context)) {\n\t\tif (s->context < 0)\n\t\t\tdie(...);\n\t}\n\nwith or without {braces} around the (technically) single statement block?\n\n> +\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &interhunkcontext)) {\n> +\t\tif (interhunkcontext < 0)\n> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n> +\t\telse\n> +\t\t\ts->interhunkcontext = interhunkcontext;\n> +\t}\n\nDitto.\n\n> diff --git a/add-interactive.h b/add-interactive.h\n> index 693f125e8e4b..c63f35b14be8 100644\n> --- a/add-interactive.h\n> +++ b/add-interactive.h\n> @@ -18,6 +18,7 @@ struct add_i_state {\n>  \n>  \tint use_single_key;\n>  \tchar *interactive_diff_filter, *interactive_diff_algorithm;\n> +\tint context, interhunkcontext;\n>  };\n>  \n>  void init_add_i_state(struct add_i_state *s, struct repository *r);\n> diff --git a/add-patch.c b/add-patch.c\n> index 95c67d8c80c4..b43ca1600738 100644\n> --- a/add-patch.c\n> +++ b/add-patch.c\n> @@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n>  {\n>  \tstruct strvec args = STRVEC_INIT;\n>  \tconst char *diff_algorithm = s->s.interactive_diff_algorithm;\n> +\tint diff_context = s->s.context;\n> +\tint diff_interhunkcontext = s->s.interhunkcontext;\n>  \tstruct strbuf *plain = &s->plain, *colored = NULL;\n>  \tstruct child_process cp = CHILD_PROCESS_INIT;\n>  \tchar *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\\0';\n> @@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n>  \tint res;\n>  \n>  \tstrvec_pushv(&args, s->mode->diff_cmd);\n> +\tif (diff_context != -1)\n> +\t\tstrvec_pushf(&args, \"--unified=%i\", diff_context);\n> +\tif (diff_interhunkcontext != -1)\n> +\t\tstrvec_pushf(&args, \"--inter-hunk-context=%i\", diff_interhunkcontext);\n\nDitto.  What does it buy us to have these two local variables?  We\nhave the state object 's' available to us here, right?\n\nThanks.\n"},{"id":"520938","messageId":"xmqqplel17s3.fsf@gitster.g","threadId":"63396","inReplyTo":"b68c58b667c29c66433b4634ad44ee9ec5257e80.1751128487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-30T17:03:24Z","receivedAt":"2025-06-30T17:03:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Leon Michalak <leonmichalak6@gmail.com>\n>\n> This patch compliments the previous commit, where builtins that use\n> add-patch infrastructure now respect diff.context and\n> diff.interHunkContext file configurations.\n>\n> In particular, this patch helps users who don't want to set persistent\n> context configurations or just want a way to override them on a one-time\n> basis, by allowing the relevant builtins to accept corresponding command\n> line options that override the file configurations.\n>\n> This mimics commands such as diff and log, which allow for both context\n> file configuration and command line overrides.\n\nI skimmed the patch briefly.  I am not sure if it is a good idea to\n\n * add OPT_DIFF_*() macros to parse-options API, as its utility is\n   very narrow, and forces those who are learning parse-options API\n   to learn one more thing.\n\n * validation of the value range to be duplicated for each and every\n   users of the new OPT_DIFF_*() macros.\n\nbut other than that, looked reasonable to me.\n\nThanks.\n"},{"id":"521000","messageId":"db172526-334f-404d-a9ea-530ec2e6e549@gmail.com","threadId":"63396","inReplyTo":"b68c58b667c29c66433b4634ad44ee9ec5257e80.1751128487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-01T09:59:49Z","receivedAt":"2025-07-01T09:59:52Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Leon\n\nOn 28/06/2025 17:34, Leon Michalak via GitGitGadget wrote:\n> From: Leon Michalak <leonmichalak6@gmail.com>\n>  \n> +for cmd in add checkout restore 'commit -m file'\n> +do\n> +\ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" \"\n\nLooking at this again, I think the test bodies here and below should be \nwrapped in single quotes because they are passed to eval and we want to \nexpand $cmd when the body is evaluated, not before. That would also \nsimplify the quoting inside the tests as we don't need to escape double \nquotes. That's not your fault - you've just copied what I suggested before.\n\n> +test_expect_success 'The -U option overrides diff.context for \"add\"' '\n> +\ttest_config diff.context 8 &&\n> +\tgit add -U4 -p >output &&\n> +\ttest_grep ! \"^ firstline\" output\n> +'\n\nDon't the tests above check this as they set diff.context and \ndiff.interhunkcontext and pass different values to -U and \n--inter-hunk-context?\n\nThanks\n\nPhillip\n"},{"id":"521001","messageId":"7686a5e0-23c3-4e3a-b5ea-3b8c80ba54fa@gmail.com","threadId":"63396","inReplyTo":"xmqqplel17s3.fsf@gitster.g","subject":"Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-01T09:59:55Z","receivedAt":"2025-07-01T10:00:00Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 30/06/2025 18:03, Junio C Hamano wrote:\n> \"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Leon Michalak <leonmichalak6@gmail.com>\n>>\n>> This patch compliments the previous commit, where builtins that use\n>> add-patch infrastructure now respect diff.context and\n>> diff.interHunkContext file configurations.\n>>\n>> In particular, this patch helps users who don't want to set persistent\n>> context configurations or just want a way to override them on a one-time\n>> basis, by allowing the relevant builtins to accept corresponding command\n>> line options that override the file configurations.\n>>\n>> This mimics commands such as diff and log, which allow for both context\n>> file configuration and command line overrides.\n> \n> I skimmed the patch briefly.  I am not sure if it is a good idea to\n> \n>   * add OPT_DIFF_*() macros to parse-options API, as its utility is\n>     very narrow, and forces those who are learning parse-options API\n>     to learn one more thing.\n\nIt means that we have consistent help for all the commands with these \noptions which I think is valuable. We have a number of other macros that \ndefine options that are shared between commands and I think that works \nquite well.\n\n> \n>   * validation of the value range to be duplicated for each and every\n>     users of the new OPT_DIFF_*() macros.\n\nYes the validation is awkward. If we changed the OPT_DIFF_* to use a \ncallback that rejected negative values that would reduce the duplication.\n\n> but other than that, looked reasonable to me.\n\nI've left a couple of comments on the tests but the code changes look \nreasonable to me too\n\nThanks\n\nPhillip\n\n"},{"id":"521002","messageId":"4fa954a7-ce48-41af-8106-21bb6f17d340@gmail.com","threadId":"63396","inReplyTo":"1ec8a1384862765795f17e2d0fcb7b601ef2b25f.1751128487.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 3/4] add-patch: respect diff.context configuration","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-01T10:00:57Z","receivedAt":"2025-07-01T10:01:00Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Leon\n\nOn 28/06/2025 17:34, Leon Michalak via GitGitGadget wrote:\n> From: Leon Michalak <leonmichalak6@gmail.com>\n> \n> Various builtins that use add-patch infrastructure do not respect\n> the user's diff.context and diff.interHunkContext file configurations.\n> This patch fixes this inconsistency.\n> \n> This is because the plumbing commands used by \"git add -p\" to generate\n> the diff do not read those config settings. Fix this by reading the\n> config before generating the patch and passing it along to the diff\n> command with the \"-U\" and \"--inter-hunk-context\" command-line options.\n\nThis looks good to me, thanks for working on it\n\nPhillip\n\n> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n> ---\n>   add-interactive.c          | 17 +++++++++++++++++\n>   add-interactive.h          |  1 +\n>   add-patch.c                |  6 ++++++\n>   t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++\n>   4 files changed, 46 insertions(+)\n> \n> diff --git a/add-interactive.c b/add-interactive.c\n> index 97ff35b6f12a..e0aafb8dd02a 100644\n> --- a/add-interactive.c\n> +++ b/add-interactive.c\n> @@ -39,8 +39,12 @@ static void init_color(struct repository *r, struct add_i_state *s,\n>   void init_add_i_state(struct add_i_state *s, struct repository *r)\n>   {\n>   \tconst char *value;\n> +\tint context;\n> +\tint interhunkcontext;\n>   \n>   \ts->r = r;\n> +\ts->context = -1;\n> +\ts->interhunkcontext = -1;\n>   \n>   \tif (repo_config_get_value(r, \"color.interactive\", &value))\n>   \t\ts->use_color = -1;\n> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n>   \trepo_config_get_string(r, \"diff.algorithm\",\n>   \t\t\t       &s->interactive_diff_algorithm);\n>   \n> +\tif (!repo_config_get_int(r, \"diff.context\", &context)) {\n> +\t\tif (context < 0)\n> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n> +\t\telse\n> +\t\t\ts->context = context;\n> +\t}\n> +\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &interhunkcontext)) {\n> +\t\tif (interhunkcontext < 0)\n> +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n> +\t\telse\n> +\t\t\ts->interhunkcontext = interhunkcontext;\n> +\t}\n> +\n>   \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n>   \tif (s->use_single_key)\n>   \t\tsetbuf(stdin, NULL);\n> diff --git a/add-interactive.h b/add-interactive.h\n> index 693f125e8e4b..c63f35b14be8 100644\n> --- a/add-interactive.h\n> +++ b/add-interactive.h\n> @@ -18,6 +18,7 @@ struct add_i_state {\n>   \n>   \tint use_single_key;\n>   \tchar *interactive_diff_filter, *interactive_diff_algorithm;\n> +\tint context, interhunkcontext;\n>   };\n>   \n>   void init_add_i_state(struct add_i_state *s, struct repository *r);\n> diff --git a/add-patch.c b/add-patch.c\n> index 95c67d8c80c4..b43ca1600738 100644\n> --- a/add-patch.c\n> +++ b/add-patch.c\n> @@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n>   {\n>   \tstruct strvec args = STRVEC_INIT;\n>   \tconst char *diff_algorithm = s->s.interactive_diff_algorithm;\n> +\tint diff_context = s->s.context;\n> +\tint diff_interhunkcontext = s->s.interhunkcontext;\n>   \tstruct strbuf *plain = &s->plain, *colored = NULL;\n>   \tstruct child_process cp = CHILD_PROCESS_INIT;\n>   \tchar *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\\0';\n> @@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n>   \tint res;\n>   \n>   \tstrvec_pushv(&args, s->mode->diff_cmd);\n> +\tif (diff_context != -1)\n> +\t\tstrvec_pushf(&args, \"--unified=%i\", diff_context);\n> +\tif (diff_interhunkcontext != -1)\n> +\t\tstrvec_pushf(&args, \"--inter-hunk-context=%i\", diff_interhunkcontext);\n>   \tif (diff_algorithm)\n>   \t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", diff_algorithm);\n>   \tif (s->revision) {\n> diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\n> index b088ee141ff4..18dc329ea1f6 100755\n> --- a/t/t3701-add-interactive.sh\n> +++ b/t/t3701-add-interactive.sh\n> @@ -1230,4 +1230,26 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n>   \ttest_cmp expect actual\n>   '\n>   \n> +test_expect_success 'add -p respects diff.context' '\n> +\ttest_write_lines a b c d e f g h i j k l m >file &&\n> +\tgit add file &&\n> +\ttest_write_lines a b c d e f G h i j k l m >file &&\n> +\techo y | git -c diff.context=5 add -p >actual &&\n> +\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n> +'\n> +\n> +test_expect_success 'add -p respects diff.interHunkContext' '\n> +\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n> +\tgit add file &&\n> +\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n> +\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n> +\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n> +'\n> +\n> +test_expect_success 'add -p rejects negative diff.context' '\n> +\ttest_config diff.context -1 &&\n> +\ttest_must_fail git add -p 2>output &&\n> +\ttest_grep \"diff.context cannot be negative\" output\n> +'\n> +\n>   test_done\n\n"},{"id":"521072","messageId":"xmqqa55nlxdw.fsf@gitster.g","threadId":"63396","inReplyTo":"7686a5e0-23c3-4e3a-b5ea-3b8c80ba54fa@gmail.com","subject":"Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-01T15:54:35Z","receivedAt":"2025-07-01T15:54:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>>   * add OPT_DIFF_*() macros to parse-options API, as its utility is\n>>     very narrow, and forces those who are learning parse-options API\n>>     to learn one more thing.\n>\n> It means that we have consistent help for all the commands with these\n> options which I think is valuable. We have a number of other macros\n> that define options that are shared between commands and I think that\n> works quite well.\n\nI understand that principe.  What I was wondering was if there are\nenough places to use these particular ones to make it worthwhile to\nenlarge the set of OPT_* macros.\n\n>>   * validation of the value range to be duplicated for each and\n>> every\n>>     users of the new OPT_DIFF_*() macros.\n>\n> Yes the validation is awkward. If we changed the OPT_DIFF_* to use a\n> callback that rejected negative values that would reduce the\n> duplication.\n\nYeah, I was wondering about that approach, too.  Another benefit\nwith the \"validate just after we parse the value before we assign\nthe result to a variable or a struct member\" approach is that we can\nalso complain about -1 that is given from the command line (which\nthe current code ignores, if I am not mistaken, because it needs to\nbe silent if that -1 is there merely because it is the \"not set yet\"\nsentinel value).\n\nOr perhaps the valid value range Réne has been workingon canbe used\nhere?\n"},{"id":"521170","messageId":"2ee8df78-1c8b-42e6-bf93-a5aa42c20af3@gmail.com","threadId":"63396","inReplyTo":"xmqqa55nlxdw.fsf@gitster.g","subject":"Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-02T14:07:15Z","receivedAt":"2025-07-02T14:07:18Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 01/07/2025 16:54, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>>>    * add OPT_DIFF_*() macros to parse-options API, as its utility is\n>>>      very narrow, and forces those who are learning parse-options API\n>>>      to learn one more thing.\n>>\n>> It means that we have consistent help for all the commands with these\n>> options which I think is valuable. We have a number of other macros\n>> that define options that are shared between commands and I think that\n>> works quite well.\n> \n> I understand that principe.  What I was wondering was if there are\n> enough places to use these particular ones to make it worthwhile to\n> enlarge the set of OPT_* macros.\n\nThere are six users of each of these macros so I think it is worthwhile. \nThat's two more users than there are for OPT_RERERE_AUTOUPDATE() and \ntwice as many users as OPT_CONTAINS().\n\n>>>    * validation of the value range to be duplicated for each and\n>>> every\n>>>      users of the new OPT_DIFF_*() macros.\n>>\n>> Yes the validation is awkward. If we changed the OPT_DIFF_* to use a\n>> callback that rejected negative values that would reduce the\n>> duplication.\n> \n> Yeah, I was wondering about that approach, too.  Another benefit\n> with the \"validate just after we parse the value before we assign\n> the result to a variable or a struct member\" approach is that we can\n> also complain about -1 that is given from the command line (which\n> the current code ignores, if I am not mistaken, because it needs to\n> be silent if that -1 is there merely because it is the \"not set yet\"\n> sentinel value).\n> \n> Or perhaps the valid value range Réne has been workingon canbe used\n> here?\n\nThat would be nice\n\nThanks\n\nPhillip\n"},{"id":"521194","messageId":"xmqqtt3ucur6.fsf@gitster.g","threadId":"63396","inReplyTo":"2ee8df78-1c8b-42e6-bf93-a5aa42c20af3@gmail.com","subject":"Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-02T18:28:29Z","receivedAt":"2025-07-02T18:28:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 01/07/2025 16:54, Junio C Hamano wrote:\n>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>> \n>>>>    * add OPT_DIFF_*() macros to parse-options API, as its utility is\n>>>>      very narrow, and forces those who are learning parse-options API\n>>>>      to learn one more thing.\n>>>\n>>> It means that we have consistent help for all the commands with these\n>>> options which I think is valuable. We have a number of other macros\n>>> that define options that are shared between commands and I think that\n>>> works quite well.\n>> I understand that principe.  What I was wondering was if there are\n>> enough places to use these particular ones to make it worthwhile to\n>> enlarge the set of OPT_* macros.\n>\n> There are six users of each of these macros so I think it is\n> worthwhile. That's two more users than there are for\n> OPT_RERERE_AUTOUPDATE() and twice as many users as OPT_CONTAINS().\n\nOK.  Thanks.\n"},{"id":"521593","messageId":"xmqqms9eql73.fsf@gitster.g","threadId":"63396","inReplyTo":"pull.1915.v3.git.1751128486.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/4] Better support for customising context lines in --patch commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-09T00:09:20Z","receivedAt":"2025-07-09T00:09:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This series of patches attempt to give --interactive/--patch compatible\n> builtins (\"add\", \"commit\", \"checkout\", \"reset\", \"restore\" and \"stash\")\n> better support and nicer experience for configuring how many context lines\n> are shown in diffs through a variety of ways.\n>\n> Prior to these patches, the user could not choose how many context lines\n> they saw in --patch commands (apart from one workaround by using\n> GIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a\n> persistent solution). Additionally, the behaviour around reading from the\n> diff.context and diff.interHunkContext configs was also inconsistent with\n> other diff generating commands such as \"log -p\".\n>\n> The summarised changes below hopefully make this experience better and fix\n> some inconsistencies:\n>\n>  * diff.context and diff.interHunkContext configs are now respected by\n>    --patch compatible commands\n>  * --unified and --inter-hunk-context command line options have been added\n>    to --patch compatible commands (which take prescendence over file\n>    configs)\n>  * \"add\" and \"commit\" in --interactive mode now expose a new \"context\"\n>    subcommand which configures the amount of context lines you wish to see\n>    in subsequent diffs generated from other subcommands such as \"patch\" or\n>    \"diff\"\n>\n> The original discussion for this can be read at:\n>\n>  * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/\n>\n> Changes since v1:\n>\n>  * Update commit descriptions\n>  * Update tests to use the more modern and robust test_grep and test_config\n>    utils\n>  * Reword some documentation / user messages\n>  * Ensure each commit is atomic and builds/passes tests on it's own\n>  * Make new command line options DRY\n>  * Add tests for interhunk context interaction\n>  * Error if context config/command line options are negative\n>  * Drop previous last commit to do with new subcommand for --interactive\n>    add/commit. My motivations behind this patch series originally where\n>    quite simple, just for add-patch commands to respect context configs.\n>    This subcommand, after the discussion in v1, will require more thought\n>    and a larger implementation that what I had anticipated. I would prefer\n>    to leave this for another time as it's the least impactful but the most\n>    time intensive and complicated idea.\n>\n> Changes since v2:\n>\n>  * Update tests to only test single command (following Philip's suggestion)\n>  * Add negative option checks\n>  * Minor commit re-wording\n\nThis iteration seems to have attracted a few review comments that\nare left ananswered.  Will we see responses to them, and/or, a\nhopefully small and final update sometime soon?\n\nThanks.\n"},{"id":"521627","messageId":"CAP9jKjE0DiZeCB7LG-a7sFP+GXk8WoLy=uZ4qkyCjzYxpPopvQ@mail.gmail.com","threadId":"63396","inReplyTo":"xmqqms9eql73.fsf@gitster.g","subject":"Re: [PATCH v3 0/4] Better support for customising context lines in --patch commands","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-07-09T07:57:04Z","receivedAt":"2025-07-09T07:57:17Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"I will respond to everything soon I hope, life has been in the way and\nI didn't anticipate so many comments :-)\n\nBased on a skim read of the comments, the main thing I'm not sure I\nwill be able to achieve is implementing the valid value range that a\nRene has been working on as I don't have that context, but I'm not\nsure if this is a necessity to add?\n"},{"id":"521691","messageId":"xmqq7c0hpeg0.fsf@gitster.g","threadId":"63396","inReplyTo":"CAP9jKjE0DiZeCB7LG-a7sFP+GXk8WoLy=uZ4qkyCjzYxpPopvQ@mail.gmail.com","subject":"Re: [PATCH v3 0/4] Better support for customising context lines in --patch commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-09T15:32:47Z","receivedAt":"2025-07-09T15:32:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Leon Michalak <leonmichalak6@gmail.com> writes:\n\n> I will respond to everything soon I hope, life has been in the way and\n> I didn't anticipate so many comments :-)\n\nSure, no problem.  \n\nHistorically, summer is a slower season and these messages I sent\nare primarily for me to keep track of topics on flight.  \"No, I am\non vacation for a few more weeks\" would have been perfectly fine\nresponse ;-).\n\n> Based on a skim read of the comments, the main thing I'm not sure I\n> will be able to achieve is implementing the valid value range that a\n> Rene has been working on as I don't have that context, but I'm not\n> sure if this is a necessity to add?\n"},{"id":"522314","messageId":"pull.1915.v4.git.1752928113.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v3.git.1751128486.gitgitgadget@gmail.com","subject":"[PATCH v4 0/4] Better support for customising context lines in --patch commands","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-07-19T12:28:29Z","receivedAt":"2025-07-19T12:28:37Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"This series of patches attempt to give --interactive/--patch compatible\nbuiltins (\"add\", \"commit\", \"checkout\", \"reset\", \"restore\" and \"stash\")\nbetter support and nicer experience for configuring how many context lines\nare shown in diffs through a variety of ways.\n\nPrior to these patches, the user could not choose how many context lines\nthey saw in --patch commands (apart from one workaround by using\nGIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a\npersistent solution). Additionally, the behaviour around reading from the\ndiff.context and diff.interHunkContext configs was also inconsistent with\nother diff generating commands such as \"log -p\".\n\nThe summarised changes below hopefully make this experience better and fix\nsome inconsistencies:\n\n * diff.context and diff.interHunkContext configs are now respected by\n   --patch compatible commands\n * --unified and --inter-hunk-context command line options have been added\n   to --patch compatible commands (which take prescendence over file\n   configs)\n * \"add\" and \"commit\" in --interactive mode now expose a new \"context\"\n   subcommand which configures the amount of context lines you wish to see\n   in subsequent diffs generated from other subcommands such as \"patch\" or\n   \"diff\"\n\nThe original discussion for this can be read at:\n\n * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/\n\nChanges since v1:\n\n * Update commit descriptions\n * Update tests to use the more modern and robust test_grep and test_config\n   utils\n * Reword some documentation / user messages\n * Ensure each commit is atomic and builds/passes tests on it's own\n * Make new command line options DRY\n * Add tests for interhunk context interaction\n * Error if context config/command line options are negative\n * Drop previous last commit to do with new subcommand for --interactive\n   add/commit. My motivations behind this patch series originally where\n   quite simple, just for add-patch commands to respect context configs.\n   This subcommand, after the discussion in v1, will require more thought\n   and a larger implementation that what I had anticipated. I would prefer\n   to leave this for another time as it's the least impactful but the most\n   time intensive and complicated idea.\n\nChanges since v2:\n\n * Update tests to only test single command (following Philip's suggestion)\n * Add negative option checks\n * Minor commit re-wording\n\nChanges since v3:\n\n * Update commit descriptions\n * Read struct properties directly instead of assigning to variables first\n * Simplify config setting / error checking\n * Remove redundant tests in later commit as they were replaced with better\n   test(s)\n * Change tests to use single quotes (this messes with the grep so was\n   unable to explicitly test single quotes in the error messages, so decided\n   to use regex . instead, which is what some other tests that have this\n   problem seem to use as well)\n\nLeon Michalak (4):\n  t: use test_grep in t3701 and t4055\n  t: use test_config in t4055\n  add-patch: respect diff.context configuration\n  add-patch: add diff.context command line overrides\n\n Documentation/diff-context-options.adoc | 10 +++\n Documentation/git-add.adoc              |  2 +\n Documentation/git-checkout.adoc         |  2 +\n Documentation/git-commit.adoc           |  2 +\n Documentation/git-reset.adoc            |  2 +\n Documentation/git-restore.adoc          |  2 +\n Documentation/git-stash.adoc            |  2 +\n add-interactive.c                       | 45 ++++++++++--\n add-interactive.h                       | 17 ++++-\n add-patch.c                             | 14 ++--\n builtin/add.c                           | 21 +++++-\n builtin/checkout.c                      | 31 +++++++-\n builtin/commit.c                        | 16 +++-\n builtin/reset.c                         | 17 ++++-\n builtin/stash.c                         | 56 +++++++++++---\n commit.h                                |  3 +-\n parse-options.h                         |  2 +\n t/t3701-add-interactive.sh              | 97 +++++++++++++++++++------\n t/t4055-diff-context.sh                 | 72 ++++++++++++------\n t/t9902-completion.sh                   |  2 +\n 20 files changed, 332 insertions(+), 83 deletions(-)\n create mode 100644 Documentation/diff-context-options.adoc\n\n\nbase-commit: cf6f63ea6bf35173e02e18bdc6a4ba41288acff9\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1915\n\nRange-diff vs v3:\n\n 1:  044a93014b6 ! 1:  bbb2bc7082b test: use \"test_grep\"\n     @@ Metadata\n      Author: Leon Michalak <leonmichalak6@gmail.com>\n      \n       ## Commit message ##\n     -    test: use \"test_grep\"\n     +    t: use test_grep in t3701 and t4055\n      \n     -    Use the modern \"test_grep\" test utility instead of regular \"grep\" which\n     -    provides better debug information if tests fail.\n     -\n     -    This is a prerequisite to the commits that follow which add to both test\n     -    files.\n     +    As a preparatory clean-up, use the \"test_grep\" test utility instead of\n     +    regular \"grep\" which provides better debug information if tests fail.\n      \n          Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n      \n 2:  e5c40d37750 ! 2:  feace2d3676 test: use \"test_config\"\n     @@ Metadata\n      Author: Leon Michalak <leonmichalak6@gmail.com>\n      \n       ## Commit message ##\n     -    test: use \"test_config\"\n     +    t: use test_config in t4055\n      \n          Use the modern \"test_config\" test utility instead of manual\"git config\"\n          as the former provides clean up on test completion.\n 3:  1ec8a138486 ! 3:  994029d6602 add-patch: respect diff.context configuration\n     @@ Commit message\n      \n          Various builtins that use add-patch infrastructure do not respect\n          the user's diff.context and diff.interHunkContext file configurations.\n     -    This patch fixes this inconsistency.\n      \n     -    This is because the plumbing commands used by \"git add -p\" to generate\n     -    the diff do not read those config settings. Fix this by reading the\n     -    config before generating the patch and passing it along to the diff\n     -    command with the \"-U\" and \"--inter-hunk-context\" command-line options.\n     +    The user may be used to seeing their diffs with customized context size,\n     +    but not in the patches \"git add -p\" shows them to pick from.\n     +\n     +    Teach add-patch infrastructure to read these configuration variables and\n     +    pass their values when spawning the underlying plumbing commands as\n     +    their command line option.\n      \n          Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>\n      \n       ## add-interactive.c ##\n     -@@ add-interactive.c: static void init_color(struct repository *r, struct add_i_state *s,\n     - void init_add_i_state(struct add_i_state *s, struct repository *r)\n     - {\n     +@@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)\n       \tconst char *value;\n     -+\tint context;\n     -+\tint interhunkcontext;\n       \n       \ts->r = r;\n      +\ts->context = -1;\n     @@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repositor\n       \trepo_config_get_string(r, \"diff.algorithm\",\n       \t\t\t       &s->interactive_diff_algorithm);\n       \n     -+\tif (!repo_config_get_int(r, \"diff.context\", &context)) {\n     -+\t\tif (context < 0)\n     ++\tif (!repo_config_get_int(r, \"diff.context\", &s->context))\n     ++\t\tif (s->context < 0)\n      +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n     -+\t\telse\n     -+\t\t\ts->context = context;\n     -+\t}\n     -+\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &interhunkcontext)) {\n     -+\t\tif (interhunkcontext < 0)\n     ++\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &s->interhunkcontext))\n     ++\t\tif (s->interhunkcontext < 0)\n      +\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n     -+\t\telse\n     -+\t\t\ts->interhunkcontext = interhunkcontext;\n     -+\t}\n      +\n       \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n       \tif (s->use_single_key)\n     @@ add-interactive.h: struct add_i_state {\n       void init_add_i_state(struct add_i_state *s, struct repository *r);\n      \n       ## add-patch.c ##\n     -@@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n     +@@ add-patch.c: static int normalize_marker(const char *p)\n     + static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n       {\n       \tstruct strvec args = STRVEC_INIT;\n     - \tconst char *diff_algorithm = s->s.interactive_diff_algorithm;\n     -+\tint diff_context = s->s.context;\n     -+\tint diff_interhunkcontext = s->s.interhunkcontext;\n     +-\tconst char *diff_algorithm = s->s.interactive_diff_algorithm;\n       \tstruct strbuf *plain = &s->plain, *colored = NULL;\n       \tstruct child_process cp = CHILD_PROCESS_INIT;\n       \tchar *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\\0';\n     @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec\n       \tint res;\n       \n       \tstrvec_pushv(&args, s->mode->diff_cmd);\n     -+\tif (diff_context != -1)\n     -+\t\tstrvec_pushf(&args, \"--unified=%i\", diff_context);\n     -+\tif (diff_interhunkcontext != -1)\n     -+\t\tstrvec_pushf(&args, \"--inter-hunk-context=%i\", diff_interhunkcontext);\n     - \tif (diff_algorithm)\n     - \t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", diff_algorithm);\n     +-\tif (diff_algorithm)\n     +-\t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", diff_algorithm);\n     ++\tif (s->s.context != -1)\n     ++\t\tstrvec_pushf(&args, \"--unified=%i\", s->s.context);\n     ++\tif (s->s.interhunkcontext != -1)\n     ++\t\tstrvec_pushf(&args, \"--inter-hunk-context=%i\", s->s.interhunkcontext);\n     ++\tif (s->s.interactive_diff_algorithm)\n     ++\t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", s->s.interactive_diff_algorithm);\n       \tif (s->revision) {\n     + \t\tstruct object_id oid;\n     + \t\tstrvec_push(&args,\n      \n       ## t/t3701-add-interactive.sh ##\n      @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n 4:  b68c58b667c ! 4:  2774b930406 add-patch: add diff.context command line overrides\n     @@ add-interactive.c: static void init_color(struct repository *r, struct add_i_sta\n      +\t\t      struct add_p_opt *add_p_opt)\n       {\n       \tconst char *value;\n     - \tint context;\n     + \n      @@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)\n       \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n       \tif (s->use_single_key)\n     @@ parse-options.h: int parse_opt_tracking_mode(const struct option *, const char *\n       \tOPT_SET_INT_F('4', \"ipv4\", (v), N_(\"use IPv4 addresses only\"), \\\n      \n       ## t/t3701-add-interactive.sh ##\n     -@@ t/t3701-add-interactive.sh: test_expect_success 'add -p rejects negative diff.context' '\n     - \ttest_grep \"diff.context cannot be negative\" output\n     +@@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n     + \ttest_cmp expect actual\n       '\n       \n     +-test_expect_success 'add -p respects diff.context' '\n     +-\ttest_write_lines a b c d e f g h i j k l m >file &&\n      +for cmd in add checkout restore 'commit -m file'\n      +do\n     -+\ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" \"\n     ++\ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" '\n      +\t\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n      +\t\tgit add file &&\n      +\t\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n      +\t\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n      +\t\t\t$cmd -p -U 4 --inter-hunk-context 2 >actual &&\n     -+\t\ttest_grep \\\"@@ -2,20 +2,20 @@\\\" actual\n     -+\t\"\n     ++\t\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n     ++\t'\n      +done\n      +\n      +test_expect_success 'reset accepts -U and --inter-hunk-context' '\n      +\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n      +\tgit commit -m file file &&\n      +\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n     -+\tgit add file &&\n     + \tgit add file &&\n     +-\ttest_write_lines a b c d e f G h i j k l m >file &&\n     +-\techo y | git -c diff.context=5 add -p >actual &&\n     +-\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n      +\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n      +\t\treset -p -U 4 --inter-hunk-context 2 >actual &&\n      +\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n     -+'\n     -+\n     + '\n     + \n     +-test_expect_success 'add -p respects diff.interHunkContext' '\n     +-\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n     +-\tgit add file &&\n     +-\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n     +-\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n     +-\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n      +test_expect_success 'stash accepts -U and --inter-hunk-context' '\n      +\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n      +\tgit commit -m file file &&\n     @@ t/t3701-add-interactive.sh: test_expect_success 'add -p rejects negative diff.co\n      +\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n      +\t\tstash -p -U 4 --inter-hunk-context 2 >actual &&\n      +\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n     -+'\n     -+\n     -+for cmd in add checkout commit reset restore 'stash save' 'stash push'\n     + '\n     + \n     +-test_expect_success 'add -p rejects negative diff.context' '\n     +-\ttest_config diff.context -1 &&\n     +-\ttest_must_fail git add -p 2>output &&\n     +-\ttest_grep \"diff.context cannot be negative\" output\n     +-'\n     ++for cmd in add checkout commit reset restore \"stash save\" \"stash push\"\n      +do\n     -+\ttest_expect_success \"$cmd rejects invalid context options\" \"\n     ++\ttest_expect_success \"$cmd rejects invalid context options\" '\n      +\t\ttest_must_fail git $cmd -p -U -3 2>actual &&\n      +\t\tcat actual | echo &&\n     -+\t\ttest_grep -e \\\"'--unified' cannot be negative\\\" actual &&\n     ++\t\ttest_grep -e \".--unified. cannot be negative\" actual &&\n      +\n      +\t\ttest_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&\n     -+\t\ttest_grep -e \\\"'--inter-hunk-context' cannot be negative\\\" actual &&\n     ++\t\ttest_grep -e \".--inter-hunk-context. cannot be negative\" actual &&\n      +\n      +\t\ttest_must_fail git $cmd -U 7 2>actual &&\n     -+\t\ttest_grep -E \\\"'--unified' requires '(--interactive/)?--patch'\\\" actual &&\n     ++\t\ttest_grep -E \".--unified. requires .(--interactive/)?--patch.\" actual &&\n      +\n      +\t\ttest_must_fail git $cmd --inter-hunk-context 2 2>actual &&\n     -+\t\ttest_grep -E \\\"'--inter-hunk-context' requires '(--interactive/)?--patch'\\\" actual\n     -+\t\"\n     ++\t\ttest_grep -E \".--inter-hunk-context. requires .(--interactive/)?--patch.\" actual\n     ++\t'\n      +done\n     -+\n     + \n       test_done\n      \n       ## t/t4055-diff-context.sh ##\n\n-- \ngitgitgadget\n"},{"id":"522315","messageId":"bbb2bc7082baaa11fd428efb375c5d142f82e7fd.1752928113.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v4.git.1752928113.gitgitgadget@gmail.com","subject":"[PATCH v4 1/4] t: use test_grep in t3701 and t4055","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-07-19T12:28:30Z","receivedAt":"2025-07-19T12:28:38Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nAs a preparatory clean-up, use the \"test_grep\" test utility instead of\nregular \"grep\" which provides better debug information if tests fail.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n t/t3701-add-interactive.sh | 48 +++++++++++++++++++-------------------\n t/t4055-diff-context.sh    | 28 +++++++++++-----------\n 2 files changed, 38 insertions(+), 38 deletions(-)\n\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex b8a05d95f3f1..b088ee141ff4 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -63,7 +63,7 @@ test_expect_success 'setup (initial)' '\n '\n test_expect_success 'status works (initial)' '\n \tgit add -i </dev/null >output &&\n-\tgrep \"+1/-0 *+2/-0 file\" output\n+\ttest_grep \"+1/-0 *+2/-0 file\" output\n '\n \n test_expect_success 'setup expected' '\n@@ -86,7 +86,7 @@ test_expect_success 'revert works (initial)' '\n \tgit add file &&\n \ttest_write_lines r 1 | git add -i &&\n \tgit ls-files >output &&\n-\t! grep . output\n+\ttest_grep ! . output\n '\n \n test_expect_success 'add untracked (multiple)' '\n@@ -109,7 +109,7 @@ test_expect_success 'setup (commit)' '\n '\n test_expect_success 'status works (commit)' '\n \tgit add -i </dev/null >output &&\n-\tgrep \"+1/-0 *+2/-0 file\" output\n+\ttest_grep \"+1/-0 *+2/-0 file\" output\n '\n \n test_expect_success 'update can stage deletions' '\n@@ -141,7 +141,7 @@ test_expect_success 'revert works (commit)' '\n \tgit add file &&\n \ttest_write_lines r 1 | git add -i &&\n \tgit add -i </dev/null >output &&\n-\tgrep \"unchanged *+3/-0 file\" output\n+\ttest_grep \"unchanged *+3/-0 file\" output\n '\n \n test_expect_success 'reject multi-key input' '\n@@ -185,7 +185,7 @@ test_expect_success 'setup fake editor' '\n test_expect_success 'bad edit rejected' '\n \tgit reset &&\n \ttest_write_lines e n d | git add -p >output &&\n-\tgrep \"hunk does not apply\" output\n+\ttest_grep \"hunk does not apply\" output\n '\n \n test_expect_success 'setup patch' '\n@@ -198,7 +198,7 @@ test_expect_success 'setup patch' '\n test_expect_success 'garbage edit rejected' '\n \tgit reset &&\n \ttest_write_lines e n d | git add -p >output &&\n-\tgrep \"hunk does not apply\" output\n+\ttest_grep \"hunk does not apply\" output\n '\n \n test_expect_success 'setup patch' '\n@@ -313,8 +313,8 @@ test_expect_success FILEMODE 'stage mode and hunk' '\n \tchmod +x file &&\n \tprintf \"y\\\\ny\\\\n\" | git add -p &&\n \tgit diff --cached file >out &&\n-\tgrep \"new mode\" out &&\n-\tgrep \"+content\" out &&\n+\ttest_grep \"new mode\" out &&\n+\ttest_grep \"+content\" out &&\n \tgit diff file >out &&\n \ttest_must_be_empty out\n '\n@@ -636,7 +636,7 @@ test_expect_success 'split hunk \"add -p (edit)\"' '\n \tprintf \"%s\\n\" s e     q n q q |\n \tEDITOR=: git add -p &&\n \tgit diff >actual &&\n-\t! grep \"^+15\" actual\n+\ttest_grep ! \"^+15\" actual\n '\n \n test_expect_success 'split hunk \"add -p (no, yes, edit)\"' '\n@@ -648,7 +648,7 @@ test_expect_success 'split hunk \"add -p (no, yes, edit)\"' '\n \tEDITOR=: git add -p 2>error &&\n \ttest_must_be_empty error &&\n \tgit diff >actual &&\n-\t! grep \"^+31\" actual\n+\ttest_grep ! \"^+31\" actual\n '\n \n test_expect_success 'split hunk with incomplete line at end' '\n@@ -682,7 +682,7 @@ test_expect_success 'edit, adding lines to the first hunk' '\n \tEDITOR=./fake_editor.sh git add -p 2>error &&\n \ttest_must_be_empty error &&\n \tgit diff --cached >actual &&\n-\tgrep \"^+22\" actual\n+\ttest_grep \"^+22\" actual\n '\n \n test_expect_success 'patch mode ignores unmerged entries' '\n@@ -696,7 +696,7 @@ test_expect_success 'patch mode ignores unmerged entries' '\n \ttest_must_fail git merge side &&\n \techo changed >non-conflict.t &&\n \techo y | git add -p >output &&\n-\t! grep a/conflict.t output &&\n+\ttest_grep ! a/conflict.t output &&\n \tcat >expected <<-\\EOF &&\n \t* Unmerged path conflict.t\n \tdiff --git a/non-conflict.t b/non-conflict.t\n@@ -728,7 +728,7 @@ test_expect_success 'diffs can be colorized' '\n \n \t# We do not want to depend on the exact coloring scheme\n \t# git uses for diffs, so just check that we saw some kind of color.\n-\tgrep \"$(printf \"\\\\033\")\" output\n+\ttest_grep \"$(printf \"\\\\033\")\" output\n '\n \n test_expect_success 'colors can be overridden' '\n@@ -743,7 +743,7 @@ test_expect_success 'colors can be overridden' '\n \t\t-c color.interactive.error=blue \\\n \t\tadd -i 2>err.raw <input &&\n \ttest_decode_color <err.raw >err &&\n-\tgrep \"<BLUE>Huh (trigger)?<RESET>\" err &&\n+\ttest_grep \"<BLUE>Huh (trigger)?<RESET>\" err &&\n \n \ttest_write_lines help quit >input &&\n \tforce_color git \\\n@@ -863,7 +863,7 @@ test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '\n \tprintf y >y &&\n \tforce_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&\n \ttest_decode_color <output.raw >output &&\n-\tgrep \"old<\" output\n+\ttest_grep \"old<\" output\n '\n \n test_expect_success 'diffFilter filters diff' '\n@@ -876,7 +876,7 @@ test_expect_success 'diffFilter filters diff' '\n \n \t# avoid depending on the exact coloring or content of the prompts,\n \t# and just make sure we saw our diff prefixed\n-\tgrep foo:.*content output\n+\ttest_grep foo:.*content output\n '\n \n test_expect_success 'detect bogus diffFilter output' '\n@@ -886,7 +886,7 @@ test_expect_success 'detect bogus diffFilter output' '\n \ttest_config interactive.diffFilter \"sed 6d\" &&\n \tprintf y >y &&\n \tforce_color test_must_fail git add -p <y >output 2>&1 &&\n-\tgrep \"mismatched output\" output\n+\ttest_grep \"mismatched output\" output\n '\n \n test_expect_success 'handle iffy colored hunk headers' '\n@@ -896,7 +896,7 @@ test_expect_success 'handle iffy colored hunk headers' '\n \tprintf n >n &&\n \tforce_color git -c interactive.diffFilter=\"sed s/.*@@.*/XX/\" \\\n \t\tadd -p >output 2>&1 <n &&\n-\tgrep \"^XX$\" output\n+\ttest_grep \"^XX$\" output\n '\n \n test_expect_success 'handle very large filtered diff' '\n@@ -1002,7 +1002,7 @@ test_expect_success 'add -p does not expand argument lists' '\n \t# update it, but we want to be sure that our \".\" pathspec\n \t# was not expanded into the argument list of any command.\n \t# So look only for \"not-changed\".\n-\t! grep -E \"^trace: (built-in|exec|run_command): .*not-changed\" trace.out\n+\ttest_grep ! -E \"^trace: (built-in|exec|run_command): .*not-changed\" trace.out\n '\n \n test_expect_success 'hunk-editing handles custom comment char' '\n@@ -1072,21 +1072,21 @@ test_expect_success 'setup different kinds of dirty submodules' '\n \n test_expect_success 'status ignores dirty submodules (except HEAD)' '\n \tgit -C for-submodules add -i </dev/null >output &&\n-\tgrep dirty-head output &&\n-\tgrep dirty-both-ways output &&\n-\t! grep dirty-otherwise output\n+\ttest_grep dirty-head output &&\n+\ttest_grep dirty-both-ways output &&\n+\ttest_grep ! dirty-otherwise output\n '\n \n test_expect_success 'handle submodules' '\n \techo 123 >>for-submodules/dirty-otherwise/initial.t &&\n \n \tforce_color git -C for-submodules add -p dirty-otherwise >output 2>&1 &&\n-\tgrep \"No changes\" output &&\n+\ttest_grep \"No changes\" output &&\n \n \tforce_color git -C for-submodules add -p dirty-head >output 2>&1 <y &&\n \tgit -C for-submodules ls-files --stage dirty-head >actual &&\n \trev=\"$(git -C for-submodules/dirty-head rev-parse HEAD)\" &&\n-\tgrep \"$rev\" actual\n+\ttest_grep \"$rev\" actual\n '\n \n test_expect_success 'set up pathological context' '\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex ec2804eea67c..c66f966a3ab3 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -38,36 +38,36 @@ test_expect_success 'setup' '\n \n test_expect_success 'the default number of context lines is 3' '\n \tgit diff >output &&\n-\t! grep \"^ d\" output &&\n-\tgrep \"^ e\" output &&\n-\tgrep \"^ j\" output &&\n-\t! grep \"^ k\" output\n+\ttest_grep ! \"^ d\" output &&\n+\ttest_grep \"^ e\" output &&\n+\ttest_grep \"^ j\" output &&\n+\ttest_grep ! \"^ k\" output\n '\n \n test_expect_success 'diff.context honored by \"log\"' '\n \tgit log -1 -p >output &&\n-\t! grep firstline output &&\n+\ttest_grep ! firstline output &&\n \tgit config diff.context 8 &&\n \tgit log -1 -p >output &&\n-\tgrep \"^ firstline\" output\n+\ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'The -U option overrides diff.context' '\n \tgit config diff.context 8 &&\n \tgit log -U4 -1 >output &&\n-\t! grep \"^ firstline\" output\n+\ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'diff.context honored by \"diff\"' '\n \tgit config diff.context 8 &&\n \tgit diff >output &&\n-\tgrep \"^ firstline\" output\n+\ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'plumbing not affected' '\n \tgit config diff.context 8 &&\n \tgit diff-files -p >output &&\n-\t! grep \"^ firstline\" output\n+\ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'non-integer config parsing' '\n@@ -85,8 +85,8 @@ test_expect_success 'negative integer config parsing' '\n test_expect_success '-U0 is valid, so is diff.context=0' '\n \tgit config diff.context 0 &&\n \tgit diff >output &&\n-\tgrep \"^-ADDED\" output &&\n-\tgrep \"^+MODIFIED\" output\n+\ttest_grep \"^-ADDED\" output &&\n+\ttest_grep \"^+MODIFIED\" output\n '\n \n test_expect_success '-U2147483647 works' '\n@@ -94,9 +94,9 @@ test_expect_success '-U2147483647 works' '\n \ttest_line_count = 16 x &&\n \tgit diff -U2147483647 >output &&\n \ttest_line_count = 22 output &&\n-\tgrep \"^-ADDED\" output &&\n-\tgrep \"^+MODIFIED\" output &&\n-\tgrep \"^+APPENDED\" output\n+\ttest_grep \"^-ADDED\" output &&\n+\ttest_grep \"^+MODIFIED\" output &&\n+\ttest_grep \"^+APPENDED\" output\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"522316","messageId":"feace2d3676a03d720e9c85b7842f47446e9462e.1752928113.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v4.git.1752928113.gitgitgadget@gmail.com","subject":"[PATCH v4 2/4] t: use test_config in t4055","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-07-19T12:28:31Z","receivedAt":"2025-07-19T12:28:39Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nUse the modern \"test_config\" test utility instead of manual\"git config\"\nas the former provides clean up on test completion.\n\nThis is a prerequisite to the commits that follow which add to this test\nfile.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n t/t4055-diff-context.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex c66f966a3ab3..1384a8195705 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -47,43 +47,43 @@ test_expect_success 'the default number of context lines is 3' '\n test_expect_success 'diff.context honored by \"log\"' '\n \tgit log -1 -p >output &&\n \ttest_grep ! firstline output &&\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit log -1 -p >output &&\n \ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'The -U option overrides diff.context' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit log -U4 -1 >output &&\n \ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'diff.context honored by \"diff\"' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit diff >output &&\n \ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'plumbing not affected' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit diff-files -p >output &&\n \ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'non-integer config parsing' '\n-\tgit config diff.context no &&\n+\ttest_config diff.context no &&\n \ttest_must_fail git diff 2>output &&\n \ttest_grep \"bad numeric config value\" output\n '\n \n test_expect_success 'negative integer config parsing' '\n-\tgit config diff.context -1 &&\n+\ttest_config diff.context -1 &&\n \ttest_must_fail git diff 2>output &&\n \ttest_grep \"bad config variable\" output\n '\n \n test_expect_success '-U0 is valid, so is diff.context=0' '\n-\tgit config diff.context 0 &&\n+\ttest_config diff.context 0 &&\n \tgit diff >output &&\n \ttest_grep \"^-ADDED\" output &&\n \ttest_grep \"^+MODIFIED\" output\n-- \ngitgitgadget\n\n"},{"id":"522317","messageId":"994029d66029e0eb1b93a6675e4df9c5c6fb76f9.1752928113.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v4.git.1752928113.gitgitgadget@gmail.com","subject":"[PATCH v4 3/4] add-patch: respect diff.context configuration","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-07-19T12:28:32Z","receivedAt":"2025-07-19T12:28:40Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nVarious builtins that use add-patch infrastructure do not respect\nthe user's diff.context and diff.interHunkContext file configurations.\n\nThe user may be used to seeing their diffs with customized context size,\nbut not in the patches \"git add -p\" shows them to pick from.\n\nTeach add-patch infrastructure to read these configuration variables and\npass their values when spawning the underlying plumbing commands as\ntheir command line option.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n add-interactive.c          |  9 +++++++++\n add-interactive.h          |  1 +\n add-patch.c                |  9 ++++++---\n t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++\n 4 files changed, 38 insertions(+), 3 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 97ff35b6f12a..eb3d0d3ada84 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -41,6 +41,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \tconst char *value;\n \n \ts->r = r;\n+\ts->context = -1;\n+\ts->interhunkcontext = -1;\n \n \tif (repo_config_get_value(r, \"color.interactive\", &value))\n \t\ts->use_color = -1;\n@@ -78,6 +80,13 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \trepo_config_get_string(r, \"diff.algorithm\",\n \t\t\t       &s->interactive_diff_algorithm);\n \n+\tif (!repo_config_get_int(r, \"diff.context\", &s->context))\n+\t\tif (s->context < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n+\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &s->interhunkcontext))\n+\t\tif (s->interhunkcontext < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n+\n \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n \tif (s->use_single_key)\n \t\tsetbuf(stdin, NULL);\ndiff --git a/add-interactive.h b/add-interactive.h\nindex 693f125e8e4b..c63f35b14be8 100644\n--- a/add-interactive.h\n+++ b/add-interactive.h\n@@ -18,6 +18,7 @@ struct add_i_state {\n \n \tint use_single_key;\n \tchar *interactive_diff_filter, *interactive_diff_algorithm;\n+\tint context, interhunkcontext;\n };\n \n void init_add_i_state(struct add_i_state *s, struct repository *r);\ndiff --git a/add-patch.c b/add-patch.c\nindex 95c67d8c80c4..b0125b51ba45 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -414,7 +414,6 @@ static int normalize_marker(const char *p)\n static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n {\n \tstruct strvec args = STRVEC_INIT;\n-\tconst char *diff_algorithm = s->s.interactive_diff_algorithm;\n \tstruct strbuf *plain = &s->plain, *colored = NULL;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\\0';\n@@ -424,8 +423,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \tint res;\n \n \tstrvec_pushv(&args, s->mode->diff_cmd);\n-\tif (diff_algorithm)\n-\t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", diff_algorithm);\n+\tif (s->s.context != -1)\n+\t\tstrvec_pushf(&args, \"--unified=%i\", s->s.context);\n+\tif (s->s.interhunkcontext != -1)\n+\t\tstrvec_pushf(&args, \"--inter-hunk-context=%i\", s->s.interhunkcontext);\n+\tif (s->s.interactive_diff_algorithm)\n+\t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", s->s.interactive_diff_algorithm);\n \tif (s->revision) {\n \t\tstruct object_id oid;\n \t\tstrvec_push(&args,\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex b088ee141ff4..18dc329ea1f6 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -1230,4 +1230,26 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'add -p respects diff.context' '\n+\ttest_write_lines a b c d e f g h i j k l m >file &&\n+\tgit add file &&\n+\ttest_write_lines a b c d e f G h i j k l m >file &&\n+\techo y | git -c diff.context=5 add -p >actual &&\n+\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n+'\n+\n+test_expect_success 'add -p respects diff.interHunkContext' '\n+\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n+\tgit add file &&\n+\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n+\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n+\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n+'\n+\n+test_expect_success 'add -p rejects negative diff.context' '\n+\ttest_config diff.context -1 &&\n+\ttest_must_fail git add -p 2>output &&\n+\ttest_grep \"diff.context cannot be negative\" output\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"522318","messageId":"2774b930406819ea60f522e0e0741e4046ee01ee.1752928113.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v4.git.1752928113.gitgitgadget@gmail.com","subject":"[PATCH v4 4/4] add-patch: add diff.context command line overrides","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-07-19T12:28:33Z","receivedAt":"2025-07-19T12:28:41Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nThis patch compliments the previous commit, where builtins that use\nadd-patch infrastructure now respect diff.context and\ndiff.interHunkContext file configurations.\n\nIn particular, this patch helps users who don't want to set persistent\ncontext configurations or just want a way to override them on a one-time\nbasis, by allowing the relevant builtins to accept corresponding command\nline options that override the file configurations.\n\nThis mimics commands such as diff and log, which allow for both context\nfile configuration and command line overrides.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n Documentation/diff-context-options.adoc | 10 +++++\n Documentation/git-add.adoc              |  2 +\n Documentation/git-checkout.adoc         |  2 +\n Documentation/git-commit.adoc           |  2 +\n Documentation/git-reset.adoc            |  2 +\n Documentation/git-restore.adoc          |  2 +\n Documentation/git-stash.adoc            |  2 +\n add-interactive.c                       | 36 +++++++++++----\n add-interactive.h                       | 16 +++++--\n add-patch.c                             |  5 ++-\n builtin/add.c                           | 21 +++++++--\n builtin/checkout.c                      | 31 +++++++++++--\n builtin/commit.c                        | 16 ++++++-\n builtin/reset.c                         | 17 ++++++-\n builtin/stash.c                         | 56 ++++++++++++++++++-----\n commit.h                                |  3 +-\n parse-options.h                         |  2 +\n t/t3701-add-interactive.sh              | 59 ++++++++++++++++++-------\n t/t4055-diff-context.sh                 | 30 +++++++++++++\n t/t9902-completion.sh                   |  2 +\n 20 files changed, 265 insertions(+), 51 deletions(-)\n create mode 100644 Documentation/diff-context-options.adoc\n\ndiff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc\nnew file mode 100644\nindex 000000000000..e161260358ff\n--- /dev/null\n+++ b/Documentation/diff-context-options.adoc\n@@ -0,0 +1,10 @@\n+`-U<n>`::\n+`--unified=<n>`::\n+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n+\tor 3 if the config option is unset.\n+\n+`--inter-hunk-context=<n>`::\n+\tShow the context between diff hunks, up to the specified _<number>_\n+\tof lines, thereby fusing hunks that are close to each other.\n+\tDefaults to `diff.interHunkContext` or 0 if the config option\n+\tis unset.\ndiff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\nindex eba0b419ce50..b7a735824d6c 100644\n--- a/Documentation/git-add.adoc\n+++ b/Documentation/git-add.adoc\n@@ -104,6 +104,8 @@ This effectively runs `add --interactive`, but bypasses the\n initial command menu and directly jumps to the `patch` subcommand.\n See ``Interactive mode'' for details.\n \n+include::diff-context-options.adoc[]\n+\n `-e`::\n `--edit`::\n \tOpen the diff vs. the index in an editor and let the user\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex ee83b6d9ba9a..40e02cfd6562 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -289,6 +289,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.\n Note that this option uses the no overlay mode by default (see also\n `--overlay`), and currently doesn't support overlay mode.\n \n+include::diff-context-options.adoc[]\n+\n `--ignore-other-worktrees`::\n \t`git checkout` refuses when the wanted branch is already checked\n \tout or otherwise in use by another worktree. This option makes\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex dc219025f1eb..ae988a883b5b 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -76,6 +76,8 @@ OPTIONS\n \twhich changes to commit. See linkgit:git-add[1] for\n \tdetails.\n \n+include::diff-context-options.adoc[]\n+\n `-C <commit>`::\n `--reuse-message=<commit>`::\n \tTake an existing _<commit>_ object, and reuse the log message\ndiff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc\nindex 53ab88c5451c..50e8a0ba6f66 100644\n--- a/Documentation/git-reset.adoc\n+++ b/Documentation/git-reset.adoc\n@@ -125,6 +125,8 @@ OPTIONS\n \tseparated with _NUL_ character and all other characters are taken\n \tliterally (including newlines and quotes).\n \n+include::diff-context-options.adoc[]\n+\n `--`::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc\nindex 877b7772e667..1dcc2bb7aea3 100644\n--- a/Documentation/git-restore.adoc\n+++ b/Documentation/git-restore.adoc\n@@ -52,6 +52,8 @@ leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to\n \tMode\" section of linkgit:git-add[1] to learn how to operate\n \tthe `--patch` mode.\n \n+include::diff-context-options.adoc[]\n+\n `-W`::\n `--worktree`::\n `-S`::\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex 1a5177f4986c..0578c619c410 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -208,6 +208,8 @@ to learn how to operate the `--patch` mode.\n The `--patch` option implies `--keep-index`.  You can use\n `--no-keep-index` to override this.\n \n+include::diff-context-options.adoc[]\n+\n -S::\n --staged::\n \tThis option is only valid for `push` and `save` commands.\ndiff --git a/add-interactive.c b/add-interactive.c\nindex eb3d0d3ada84..3e692b47eca0 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -36,7 +36,8 @@ static void init_color(struct repository *r, struct add_i_state *s,\n \tfree(key);\n }\n \n-void init_add_i_state(struct add_i_state *s, struct repository *r)\n+void init_add_i_state(struct add_i_state *s, struct repository *r,\n+\t\t      struct add_p_opt *add_p_opt)\n {\n \tconst char *value;\n \n@@ -90,6 +91,17 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n \tif (s->use_single_key)\n \t\tsetbuf(stdin, NULL);\n+\n+\tif (add_p_opt->context != -1) {\n+\t\tif (add_p_opt->context < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"--unified\");\n+\t\ts->context = add_p_opt->context;\n+\t}\n+\tif (add_p_opt->interhunkcontext != -1) {\n+\t\tif (add_p_opt->interhunkcontext < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"--inter-hunk-context\");\n+\t\ts->interhunkcontext = add_p_opt->interhunkcontext;\n+\t}\n }\n \n void clear_add_i_state(struct add_i_state *s)\n@@ -978,6 +990,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,\n \topts->prompt = N_(\"Patch update\");\n \tcount = list_and_choose(s, files, opts);\n \tif (count > 0) {\n+\t\tstruct add_p_opt add_p_opt = {\n+\t\t\t.context = s->context,\n+\t\t\t.interhunkcontext = s->interhunkcontext,\n+\t\t};\n \t\tstruct strvec args = STRVEC_INIT;\n \t\tstruct pathspec ps_selected = { 0 };\n \n@@ -988,7 +1004,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,\n \t\tparse_pathspec(&ps_selected,\n \t\t\t       PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,\n \t\t\t       PATHSPEC_LITERAL_PATH, \"\", args.v);\n-\t\tres = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);\n+\t\tres = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected);\n \t\tstrvec_clear(&args);\n \t\tclear_pathspec(&ps_selected);\n \t}\n@@ -1023,10 +1039,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,\n \tif (count > 0) {\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n-\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\",\n-\t\t\t     oid_to_hex(!is_initial ? &oid :\n-\t\t\t\t\ts->r->hash_algo->empty_tree),\n-\t\t\t     \"--\", NULL);\n+\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\", NULL);\n+\t\tif (s->context != -1)\n+\t\t\tstrvec_pushf(&cmd.args, \"--unified=%i\", s->context);\n+\t\tif (s->interhunkcontext != -1)\n+\t\t\tstrvec_pushf(&cmd.args, \"--inter-hunk-context=%i\", s->interhunkcontext);\n+\t\tstrvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :\n+\t\t\t     s->r->hash_algo->empty_tree), \"--\", NULL);\n \t\tfor (i = 0; i < files->items.nr; i++)\n \t\t\tif (files->selected[i])\n \t\t\t\tstrvec_push(&cmd.args,\n@@ -1119,7 +1138,8 @@ static void command_prompt_help(struct add_i_state *s)\n \t\t\t _(\"(empty) select nothing\"));\n }\n \n-int run_add_i(struct repository *r, const struct pathspec *ps)\n+int run_add_i(struct repository *r, const struct pathspec *ps,\n+\t      struct add_p_opt *add_p_opt)\n {\n \tstruct add_i_state s = { NULL };\n \tstruct print_command_item_data data = { \"[\", \"]\" };\n@@ -1162,7 +1182,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t\t->util = util;\n \t}\n \n-\tinit_add_i_state(&s, r);\n+\tinit_add_i_state(&s, r, add_p_opt);\n \n \t/*\n \t * When color was asked for, use the prompt color for\ndiff --git a/add-interactive.h b/add-interactive.h\nindex c63f35b14be8..4213dcd67b9a 100644\n--- a/add-interactive.h\n+++ b/add-interactive.h\n@@ -3,6 +3,13 @@\n \n #include \"color.h\"\n \n+struct add_p_opt {\n+\tint context;\n+\tint interhunkcontext;\n+};\n+\n+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }\n+\n struct add_i_state {\n \tstruct repository *r;\n \tint use_color;\n@@ -21,12 +28,14 @@ struct add_i_state {\n \tint context, interhunkcontext;\n };\n \n-void init_add_i_state(struct add_i_state *s, struct repository *r);\n+void init_add_i_state(struct add_i_state *s, struct repository *r,\n+\t\t      struct add_p_opt *add_p_opt);\n void clear_add_i_state(struct add_i_state *s);\n \n struct repository;\n struct pathspec;\n-int run_add_i(struct repository *r, const struct pathspec *ps);\n+int run_add_i(struct repository *r, const struct pathspec *ps,\n+\t      struct add_p_opt *add_p_opt);\n \n enum add_p_mode {\n \tADD_P_ADD,\n@@ -37,6 +46,7 @@ enum add_p_mode {\n };\n \n int run_add_p(struct repository *r, enum add_p_mode mode,\n-\t      const char *revision, const struct pathspec *ps);\n+\t      struct add_p_opt *o, const char *revision,\n+\t      const struct pathspec *ps);\n \n #endif\ndiff --git a/add-patch.c b/add-patch.c\nindex b0125b51ba45..302e6ba7d9a3 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -1763,14 +1763,15 @@ soft_increment:\n }\n \n int run_add_p(struct repository *r, enum add_p_mode mode,\n-\t      const char *revision, const struct pathspec *ps)\n+\t      struct add_p_opt *o, const char *revision,\n+\t      const struct pathspec *ps)\n {\n \tstruct add_p_state s = {\n \t\t{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tsize_t i, binary_count = 0;\n \n-\tinit_add_i_state(&s.s, r);\n+\tinit_add_i_state(&s.s, r, o);\n \n \tif (mode == ADD_P_STASH)\n \t\ts.mode = &patch_mode_stash;\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 7c292ffdc6c2..a00dab9b8fa0 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -29,6 +29,7 @@ static const char * const builtin_add_usage[] = {\n \tNULL\n };\n static int patch_interactive, add_interactive, edit_interactive;\n+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n static int take_worktree_changes;\n static int add_renormalize;\n static int pathspec_file_nul;\n@@ -157,7 +158,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec *\n int interactive_add(struct repository *repo,\n \t\t    const char **argv,\n \t\t    const char *prefix,\n-\t\t    int patch)\n+\t\t    int patch, struct add_p_opt *add_p_opt)\n {\n \tstruct pathspec pathspec;\n \tint ret;\n@@ -169,9 +170,9 @@ int interactive_add(struct repository *repo,\n \t\t       prefix, argv);\n \n \tif (patch)\n-\t\tret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);\n+\t\tret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec);\n \telse\n-\t\tret = !!run_add_i(repo, &pathspec);\n+\t\tret = !!run_add_i(repo, &pathspec, add_p_opt);\n \n \tclear_pathspec(&pathspec);\n \treturn ret;\n@@ -253,6 +254,8 @@ static struct option builtin_add_options[] = {\n \tOPT_GROUP(\"\"),\n \tOPT_BOOL('i', \"interactive\", &add_interactive, N_(\"interactive picking\")),\n \tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"select hunks interactively\")),\n+\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \tOPT_BOOL('e', \"edit\", &edit_interactive, N_(\"edit current diff and apply\")),\n \tOPT__FORCE(&ignored_too, N_(\"allow adding otherwise ignored files\"), 0),\n \tOPT_BOOL('u', \"update\", &take_worktree_changes, N_(\"update tracked files\")),\n@@ -394,6 +397,11 @@ int cmd_add(int argc,\n \tprepare_repo_settings(repo);\n \trepo->settings.command_requires_full_index = 0;\n \n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tif (patch_interactive)\n \t\tadd_interactive = 1;\n \tif (add_interactive) {\n@@ -401,7 +409,12 @@ int cmd_add(int argc,\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--dry-run\", \"--interactive/--patch\");\n \t\tif (pathspec_from_file)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--pathspec-from-file\", \"--interactive/--patch\");\n-\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive));\n+\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--interactive/--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--interactive/--patch\");\n \t}\n \n \tif (edit_interactive) {\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 536192d3456c..3737ba4c3920 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -61,6 +61,8 @@ static const char * const restore_usage[] = {\n \n struct checkout_opts {\n \tint patch_mode;\n+\tint patch_context;\n+\tint patch_interhunk_context;\n \tint quiet;\n \tint merge;\n \tint force;\n@@ -104,7 +106,12 @@ struct checkout_opts {\n \tstruct tree *source_tree;\n };\n \n-#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 }\n+#define CHECKOUT_OPTS_INIT { \\\n+\t.conflict_style = -1, \\\n+\t.merge = -1, \\\n+\t.patch_context = -1, \\\n+\t.patch_interhunk_context = -1, \\\n+}\n \n struct branch_info {\n \tchar *name; /* The short name used */\n@@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts,\n \n \tif (opts->patch_mode) {\n \t\tenum add_p_mode patch_mode;\n+\t\tstruct add_p_opt add_p_opt = {\n+\t\t\t.context = opts->patch_context,\n+\t\t\t.interhunkcontext = opts->patch_interhunk_context,\n+\t\t};\n \t\tconst char *rev = new_branch_info->name;\n \t\tchar rev_oid[GIT_MAX_HEXSZ + 1];\n \n@@ -564,8 +575,8 @@ static int checkout_paths(const struct checkout_opts *opts,\n \t\telse\n \t\t\tBUG(\"either flag must have been set, worktree=%d, index=%d\",\n \t\t\t    opts->checkout_worktree, opts->checkout_index);\n-\t\treturn !!run_add_p(the_repository, patch_mode, rev,\n-\t\t\t\t   &opts->pathspec);\n+\t\treturn !!run_add_p(the_repository, patch_mode, &add_p_opt,\n+\t\t\t\t   rev, &opts->pathspec);\n \t}\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n@@ -1738,6 +1749,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,\n \t\t\t      N_(\"checkout their version for unmerged files\"),\n \t\t\t      3, PARSE_OPT_NONEG),\n \t\tOPT_BOOL('p', \"patch\", &opts->patch_mode, N_(\"select hunks interactively\")),\n+\t\tOPT_DIFF_UNIFIED(&opts->patch_context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context),\n \t\tOPT_BOOL(0, \"ignore-skip-worktree-bits\", &opts->ignore_skipworktree,\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n \t\tOPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),\n@@ -1780,6 +1793,18 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     usagestr, parseopt_flags);\n \n+\tif (opts->patch_context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (opts->patch_interhunk_context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n+\tif (!opts->patch_mode) {\n+\t\tif (opts->patch_context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (opts->patch_interhunk_context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n \tif (opts->show_progress < 0) {\n \t\tif (opts->quiet)\n \t\t\topts->show_progress = 0;\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex fba0dded64a7..73673bc7db9f 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -19,6 +19,7 @@\n #include \"environment.h\"\n #include \"diff.h\"\n #include \"commit.h\"\n+#include \"add-interactive.h\"\n #include \"gettext.h\"\n #include \"revision.h\"\n #include \"wt-status.h\"\n@@ -122,6 +123,7 @@ static const char *edit_message, *use_message;\n static char *fixup_message, *fixup_commit, *squash_message;\n static const char *fixup_prefix;\n static int all, also, interactive, patch_interactive, only, amend, signoff;\n+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n static int edit_flag = -1; /* unspecified */\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int config_commit_verbose = -1; /* unspecified */\n@@ -354,6 +356,11 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \tconst char *ret;\n \tchar *path = NULL;\n \n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tif (is_status)\n \t\trefresh_flags |= REFRESH_UNMERGED;\n \tparse_pathspec(&pathspec, 0,\n@@ -400,7 +407,7 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n \t\tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n \n-\t\tif (interactive_add(the_repository, argv, prefix, patch_interactive) != 0)\n+\t\tif (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0)\n \t\t\tdie(_(\"interactive add failed\"));\n \n \t\tthe_repository->index_file = old_repo_index_file;\n@@ -424,6 +431,11 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\tcommit_style = COMMIT_NORMAL;\n \t\tret = get_lock_file_path(&index_lock);\n \t\tgoto out;\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--interactive/--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--interactive/--patch\");\n \t}\n \n \t/*\n@@ -1722,6 +1734,8 @@ int cmd_commit(int argc,\n \t\tOPT_BOOL('i', \"include\", &also, N_(\"add specified files to index for commit\")),\n \t\tOPT_BOOL(0, \"interactive\", &interactive, N_(\"interactively add files\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"interactively add changes\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT_BOOL('o', \"only\", &only, N_(\"commit only specified files\")),\n \t\tOPT_BOOL('n', \"no-verify\", &no_verify, N_(\"bypass pre-commit and commit-msg hooks\")),\n \t\tOPT_BOOL(0, \"dry-run\", &dry_run, N_(\"show what would be committed\")),\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex dc50ffc1ac59..9fb32795c9c5 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -346,6 +346,7 @@ int cmd_reset(int argc,\n \tstruct object_id oid;\n \tstruct pathspec pathspec;\n \tint intent_to_add = 0;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tconst struct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"no-refresh\", &no_refresh,\n@@ -370,6 +371,8 @@ int cmd_reset(int argc,\n \t\t\t       PARSE_OPT_OPTARG,\n \t\t\t       option_parse_recurse_submodules_worktree_updater),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode, N_(\"select hunks interactively\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT_BOOL('N', \"intent-to-add\", &intent_to_add,\n \t\t\t\tN_(\"record only the fact that removed paths will be added later\")),\n \t\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n@@ -420,6 +423,11 @@ int cmd_reset(int argc,\n \t\toidcpy(&oid, &tree->object.oid);\n \t}\n \n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \n@@ -427,9 +435,14 @@ int cmd_reset(int argc,\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--patch\", \"--{hard,mixed,soft}\");\n \t\ttrace2_cmd_mode(\"patch-interactive\");\n-\t\tupdate_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev,\n-\t\t\t\t   &pathspec);\n+\t\tupdate_ref_status = !!run_add_p(the_repository, ADD_P_RESET,\n+\t\t\t\t\t\t&add_p_opt, rev, &pathspec);\n \t\tgoto cleanup;\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n \t}\n \n \t/* git reset tree [--] paths... can be used to\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 7cd3ad8aa48e..6da162e5e69a 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1242,7 +1242,8 @@ done:\n }\n \n static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n-\t\t       struct strbuf *out_patch, int quiet)\n+\t\t       struct strbuf *out_patch, int quiet,\n+\t\t       struct add_p_opt *add_p_opt)\n {\n \tint ret = 0;\n \tstruct child_process cp_read_tree = CHILD_PROCESS_INIT;\n@@ -1267,7 +1268,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n \told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n \tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n \n-\tret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps);\n+\tret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps);\n \n \tthe_repository->index_file = old_repo_index_file;\n \tif (old_index_env && *old_index_env)\n@@ -1362,8 +1363,8 @@ done:\n }\n \n static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,\n-\t\t\t   int include_untracked, int patch_mode, int only_staged,\n-\t\t\t   struct stash_info *info, struct strbuf *patch,\n+\t\t\t   int include_untracked, int patch_mode, struct add_p_opt *add_p_opt,\n+\t\t\t   int only_staged, struct stash_info *info, struct strbuf *patch,\n \t\t\t   int quiet)\n {\n \tint ret = 0;\n@@ -1444,7 +1445,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\tuntracked_commit_option = 1;\n \t}\n \tif (patch_mode) {\n-\t\tret = stash_patch(info, ps, patch, quiet);\n+\t\tret = stash_patch(info, ps, patch, quiet, add_p_opt);\n \t\tif (ret < 0) {\n \t\t\tif (!quiet)\n \t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n@@ -1519,7 +1520,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n \tif (!check_changes_tracked_files(&ps))\n \t\treturn 0;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n+\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,\n \t\t\t      NULL, 0);\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n@@ -1530,7 +1531,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n }\n \n static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,\n-\t\t\t int keep_index, int patch_mode, int include_untracked, int only_staged)\n+\t\t\t int keep_index, int patch_mode, struct add_p_opt *add_p_opt,\n+\t\t\t int include_untracked, int only_staged)\n {\n \tint ret = 0;\n \tstruct stash_info info = STASH_INFO_INIT;\n@@ -1600,8 +1602,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \n \tif (stash_msg)\n \t\tstrbuf_addstr(&stash_msg_buf, stash_msg);\n-\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,\n-\t\t\t    &info, &patch, quiet)) {\n+\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,\n+\t\t\t    add_p_opt, only_staged, &info, &patch, quiet)) {\n \t\tret = -1;\n \t\tgoto done;\n \t}\n@@ -1774,6 +1776,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \tconst char *stash_msg = NULL;\n \tchar *pathspec_from_file = NULL;\n \tstruct pathspec ps;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n@@ -1781,6 +1784,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n \t\t\t N_(\"include untracked files in stash\")),\n@@ -1836,8 +1841,20 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--pathspec-file-nul\", \"--pathspec-from-file\");\n \t}\n \n+\tif (!patch_mode) {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,\n-\t\t\t    include_untracked, only_staged);\n+\t\t\t    &add_p_opt, include_untracked, only_staged);\n \n \tclear_pathspec(&ps);\n \tfree(pathspec_from_file);\n@@ -1862,6 +1879,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \tconst char *stash_msg = NULL;\n \tstruct pathspec ps;\n \tstruct strbuf stash_msg_buf = STRBUF_INIT;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n@@ -1869,6 +1887,8 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n \t\t\t N_(\"include untracked files in stash\")),\n@@ -1887,8 +1907,22 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \t\tstash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');\n \n \tmemset(&ps, 0, sizeof(ps));\n+\n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n+\tif (!patch_mode) {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index,\n-\t\t\t    patch_mode, include_untracked, only_staged);\n+\t\t\t    patch_mode, &add_p_opt, include_untracked,\n+\t\t\t    only_staged);\n \n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\ndiff --git a/commit.h b/commit.h\nindex 70c870dae4d4..7a7fedbc2f14 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -2,6 +2,7 @@\n #define COMMIT_H\n \n #include \"object.h\"\n+#include \"add-interactive.h\"\n \n struct signature_check;\n struct strbuf;\n@@ -257,7 +258,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *);\n int interactive_add(struct repository *repo,\n \t\t    const char **argv,\n \t\t    const char *prefix,\n-\t\t    int patch);\n+\t\t    int patch, struct add_p_opt *add_p_opt);\n \n struct commit_extra_header {\n \tstruct commit_extra_header *next;\ndiff --git a/parse-options.h b/parse-options.h\nindex 91c3e3c29b3d..bdae8f116198 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -616,6 +616,8 @@ int parse_opt_tracking_mode(const struct option *, const char *, int);\n #define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, \"pathspec-from-file\", v, N_(\"read pathspec from file\"))\n #define OPT_PATHSPEC_FILE_NUL(v)  OPT_BOOL(0, \"pathspec-file-nul\", v, N_(\"with --pathspec-from-file, pathspec elements are separated with NUL character\"))\n #define OPT_AUTOSTASH(v) OPT_BOOL(0, \"autostash\", v, N_(\"automatically stash/stash pop before and after\"))\n+#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', \"unified\", v, N_(\"generate diffs with <n> lines context\"), PARSE_OPT_NONEG)\n+#define OPT_DIFF_INTERHUNK_CONTEXT(v) OPT_INTEGER_F(0, \"inter-hunk-context\", v, N_(\"show context between diff hunks up to the specified number of lines\"), PARSE_OPT_NONEG)\n \n #define OPT_IPVERSION(v) \\\n \tOPT_SET_INT_F('4', \"ipv4\", (v), N_(\"use IPv4 addresses only\"), \\\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex 18dc329ea1f6..7fd9a23c9987 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -1230,26 +1230,53 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add -p respects diff.context' '\n-\ttest_write_lines a b c d e f g h i j k l m >file &&\n+for cmd in add checkout restore 'commit -m file'\n+do\n+\ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" '\n+\t\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n+\t\tgit add file &&\n+\t\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n+\t\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n+\t\t\t$cmd -p -U 4 --inter-hunk-context 2 >actual &&\n+\t\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n+\t'\n+done\n+\n+test_expect_success 'reset accepts -U and --inter-hunk-context' '\n+\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n+\tgit commit -m file file &&\n+\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n \tgit add file &&\n-\ttest_write_lines a b c d e f G h i j k l m >file &&\n-\techo y | git -c diff.context=5 add -p >actual &&\n-\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n+\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n+\t\treset -p -U 4 --inter-hunk-context 2 >actual &&\n+\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n '\n \n-test_expect_success 'add -p respects diff.interHunkContext' '\n-\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n-\tgit add file &&\n-\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n-\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n-\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n+test_expect_success 'stash accepts -U and --inter-hunk-context' '\n+\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n+\tgit commit -m file file &&\n+\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n+\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n+\t\tstash -p -U 4 --inter-hunk-context 2 >actual &&\n+\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n '\n \n-test_expect_success 'add -p rejects negative diff.context' '\n-\ttest_config diff.context -1 &&\n-\ttest_must_fail git add -p 2>output &&\n-\ttest_grep \"diff.context cannot be negative\" output\n-'\n+for cmd in add checkout commit reset restore \"stash save\" \"stash push\"\n+do\n+\ttest_expect_success \"$cmd rejects invalid context options\" '\n+\t\ttest_must_fail git $cmd -p -U -3 2>actual &&\n+\t\tcat actual | echo &&\n+\t\ttest_grep -e \".--unified. cannot be negative\" actual &&\n+\n+\t\ttest_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&\n+\t\ttest_grep -e \".--inter-hunk-context. cannot be negative\" actual &&\n+\n+\t\ttest_must_fail git $cmd -U 7 2>actual &&\n+\t\ttest_grep -E \".--unified. requires .(--interactive/)?--patch.\" actual &&\n+\n+\t\ttest_must_fail git $cmd --inter-hunk-context 2 2>actual &&\n+\t\ttest_grep -E \".--inter-hunk-context. requires .(--interactive/)?--patch.\" actual\n+\t'\n+done\n \n test_done\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex 1384a8195705..0158fe6568cb 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -58,6 +58,36 @@ test_expect_success 'The -U option overrides diff.context' '\n \ttest_grep ! \"^ firstline\" output\n '\n \n+test_expect_success 'The -U option overrides diff.context for \"add\"' '\n+\ttest_config diff.context 8 &&\n+\tgit add -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"commit\"' '\n+\ttest_config diff.context 8 &&\n+\t! git commit -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"checkout\"' '\n+\ttest_config diff.context 8 &&\n+\tgit checkout -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"stash\"' '\n+\ttest_config diff.context 8 &&\n+\t! git stash -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n+test_expect_success 'The -U option overrides diff.context for \"restore\"' '\n+\ttest_config diff.context 8 &&\n+\tgit restore -U4 -p >output &&\n+\ttest_grep ! \"^ firstline\" output\n+'\n+\n test_expect_success 'diff.context honored by \"diff\"' '\n \ttest_config diff.context 8 &&\n \tgit diff >output &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 343b8cd1912b..6650d33fba69 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2596,6 +2596,8 @@ test_expect_success 'double dash \"git checkout\"' '\n \t--merge Z\n \t--conflict=Z\n \t--patch Z\n+\t--unified=Z\n+\t--inter-hunk-context=Z\n \t--ignore-skip-worktree-bits Z\n \t--ignore-other-worktrees Z\n \t--recurse-submodules Z\n-- \ngitgitgadget\n"},{"id":"522363","messageId":"xmqqfrepzdxd.fsf@gitster.g","threadId":"63396","inReplyTo":"pull.1915.v4.git.1752928113.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 0/4] Better support for customising context lines in --patch commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-21T16:50:06Z","receivedAt":"2025-07-21T16:50:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Changes since v3:\n>\n>  * Update commit descriptions\n>  * Read struct properties directly instead of assigning to variables first\n>  * Simplify config setting / error checking\n>  * Remove redundant tests in later commit as they were replaced with better\n>    test(s)\n>  * Change tests to use single quotes (this messes with the grep so was\n>    unable to explicitly test single quotes in the error messages, so decided\n>    to use regex . instead, which is what some other tests that have this\n>    problem seem to use as well)\n\nAll of the above looked reasonably well done.  Will replace.\n\nUnless there are objections, let me mark the topic for 'next' soonish.\n\nThanks.\n"},{"id":"522463","messageId":"076bb5e9-e8c4-466d-b8dd-bc84bba708b2@gmail.com","threadId":"63396","inReplyTo":"2774b930406819ea60f522e0e0741e4046ee01ee.1752928113.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 4/4] add-patch: add diff.context command line overrides","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-22T16:01:55Z","receivedAt":"2025-07-22T16:02:01Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Leon\n\nOn 19/07/2025 13:28, Leon Michalak via GitGitGadget wrote:\n> From: Leon Michalak <leonmichalak6@gmail.com>\n> \n> This patch compliments the previous commit, where builtins that use\n> add-patch infrastructure now respect diff.context and\n> diff.interHunkContext file configurations.\n> \n> In particular, this patch helps users who don't want to set persistent\n> context configurations or just want a way to override them on a one-time\n> basis, by allowing the relevant builtins to accept corresponding command\n> line options that override the file configurations.\n> \n> This mimics commands such as diff and log, which allow for both context\n> file configuration and command line overrides.\n\nThanks for updating the quoting in the tests. Unfortunately this patch \nnow deletes the tests added in the last commit which I don't think is \ncorrect.\n\n>   \n> -test_expect_success 'add -p respects diff.context' '\n> -\ttest_write_lines a b c d e f g h i j k l m >file &&\nI think there is some confusion here - why are we deleting the tests \nadded in the last commit? This removes the test coverage for \ndiff.context and diff.interHunkContext\n\n> +for cmd in add checkout restore 'commit -m file'\n> +do\n> +\ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" '\n> +\t\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n> +\t\tgit add file &&\n> +\t\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n> +\t\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n> +\t\t\t$cmd -p -U 4 --inter-hunk-context 2 >actual &&\n> +\t\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n> +\t'\n> +done\n> +\n> +test_expect_success 'reset accepts -U and --inter-hunk-context' '\n> +\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n> +\tgit commit -m file file &&\n> +\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n>   \tgit add file &&\n> -\ttest_write_lines a b c d e f G h i j k l m >file &&\n> -\techo y | git -c diff.context=5 add -p >actual &&\n> -\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n> +\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n> +\t\treset -p -U 4 --inter-hunk-context 2 >actual &&\n> +\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n>   '\n>   \n> -test_expect_success 'add -p respects diff.interHunkContext' '\n> -\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n> -\tgit add file &&\n> -\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n> -\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n> -\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n\nThis is also deleting a test added in the last patch\n\n> +test_expect_success 'stash accepts -U and --inter-hunk-context' '\n> +\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n> +\tgit commit -m file file &&\n> +\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n> +\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n> +\t\tstash -p -U 4 --inter-hunk-context 2 >actual &&\n> +\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n>   '\n>   \n> -test_expect_success 'add -p rejects negative diff.context' '\n> -\ttest_config diff.context -1 &&\n> -\ttest_must_fail git add -p 2>output &&\n> -\ttest_grep \"diff.context cannot be negative\" output\n> -'\n\nand so is this. The tests you're adding look good but we shouldn't be \ndeleting the existing ones.\n\n> +for cmd in add checkout commit reset restore \"stash save\" \"stash push\"\n> +do\n> +\ttest_expect_success \"$cmd rejects invalid context options\" '\n> +\t\ttest_must_fail git $cmd -p -U -3 2>actual &&\n> +\t\tcat actual | echo &&\n> +\t\ttest_grep -e \".--unified. cannot be negative\" actual &&\n> +\n> +\t\ttest_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&\n> +\t\ttest_grep -e \".--inter-hunk-context. cannot be negative\" actual &&\n> +\n> +\t\ttest_must_fail git $cmd -U 7 2>actual &&\n> +\t\ttest_grep -E \".--unified. requires .(--interactive/)?--patch.\" actual &&\n> +\n> +\t\ttest_must_fail git $cmd --inter-hunk-context 2 2>actual &&\n> +\t\ttest_grep -E \".--inter-hunk-context. requires .(--interactive/)?--patch.\" actual\n> +\t'\n> +done\n\nThis looks good as well\n>   test_done\nAs I said last time I do not think the tests below add any value. They \nalso do not compensate for the removal of the tests for diff.context \nthat are deleted above as they all pass -U on the commandline.\n\n> diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\n> index 1384a8195705..0158fe6568cb 100755\n> --- a/t/t4055-diff-context.sh\n> +++ b/t/t4055-diff-context.sh\n> @@ -58,6 +58,36 @@ test_expect_success 'The -U option overrides diff.context' '\n>   \ttest_grep ! \"^ firstline\" output\n>   '\n>   \n> +test_expect_success 'The -U option overrides diff.context for \"add\"' '\n> +\ttest_config diff.context 8 &&\n> +\tgit add -U4 -p >output &&\n> +\ttest_grep ! \"^ firstline\" output\n> +'\n> +\n> +test_expect_success 'The -U option overrides diff.context for \"commit\"' '\n> +\ttest_config diff.context 8 &&\n> +\t! git commit -U4 -p >output &&\n> +\ttest_grep ! \"^ firstline\" output\n> +'\n> +\n> +test_expect_success 'The -U option overrides diff.context for \"checkout\"' '\n> +\ttest_config diff.context 8 &&\n> +\tgit checkout -U4 -p >output &&\n> +\ttest_grep ! \"^ firstline\" output\n> +'\n> +\n> +test_expect_success 'The -U option overrides diff.context for \"stash\"' '\n> +\ttest_config diff.context 8 &&\n> +\t! git stash -U4 -p >output &&\n> +\ttest_grep ! \"^ firstline\" output\n> +'\n> +\n> +test_expect_success 'The -U option overrides diff.context for \"restore\"' '\n> +\ttest_config diff.context 8 &&\n> +\tgit restore -U4 -p >output &&\n> +\ttest_grep ! \"^ firstline\" output\n> +'\n> +\n>   test_expect_success 'diff.context honored by \"diff\"' '\n>   \ttest_config diff.context 8 &&\n>   \tgit diff >output &&\n\nThanks\n\nPhillip\n\n"},{"id":"522466","messageId":"49703ba5-5f2a-4e91-b9c7-5500720d5d3d@gmail.com","threadId":"63396","inReplyTo":"xmqqfrepzdxd.fsf@gitster.g","subject":"Re: [PATCH v4 0/4] Better support for customising context lines in --patch commands","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-22T16:05:37Z","receivedAt":"2025-07-22T16:05:40Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 21/07/2025 17:50, Junio C Hamano wrote:\n> \"Leon Michalak via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> Changes since v3:\n>>\n>>   * Update commit descriptions\n>>   * Read struct properties directly instead of assigning to variables first\n>>   * Simplify config setting / error checking\n>>   * Remove redundant tests in later commit as they were replaced with better\n>>     test(s)\n\nI've left some comments on patch 4 about this. It seems to me that it is \ndeleting useful tests and replacing them with redundant tests.\n\n>>   * Change tests to use single quotes (this messes with the grep so was\n>>     unable to explicitly test single quotes in the error messages, so decided\n>>     to use regex . instead, which is what some other tests that have this\n>>     problem seem to use as well)\n\nThanks for doing that\n\n> \n> All of the above looked reasonably well done.  Will replace.\n> \n> Unless there are objections, let me mark the topic for 'next' soonish.\n\nI think we want to sort out the test changes in patch 4. Previously we \ndiscussed centralizing the option parsing in that patch as well but we \ncan always do that as a follow up later.\n\nThanks\n\nPhillip\n\n"},{"id":"522474","messageId":"xmqqv7nkrvks.fsf@gitster.g","threadId":"63396","inReplyTo":"49703ba5-5f2a-4e91-b9c7-5500720d5d3d@gmail.com","subject":"Re: [PATCH v4 0/4] Better support for customising context lines in --patch commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-22T17:20:35Z","receivedAt":"2025-07-22T17:20:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> All of the above looked reasonably well done.  Will replace.\n>> Unless there are objections, let me mark the topic for 'next'\n>> soonish.\n>\n> I think we want to sort out the test changes in patch 4. Previously we\n> discussed centralizing the option parsing in that patch as well but we\n> can always do that as a follow up later.\n\nYeah, the tests can probably use a bit more polish before we can\nmove forward.  Thanks for carefully reading them over.\n"},{"id":"522483","messageId":"CAP9jKjExau58dP+=MJXiKWvCcOaBmhB-yta0x76s7b0XWqBQxw@mail.gmail.com","threadId":"63396","inReplyTo":"076bb5e9-e8c4-466d-b8dd-bc84bba708b2@gmail.com","subject":"Re: [PATCH v4 4/4] add-patch: add diff.context command line overrides","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-07-22T18:02:35Z","receivedAt":"2025-07-22T18:02:48Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"Hey Philip, looking again for a second time and re-reading previous\nreplies on this thread, I think I misunderstood a previous reply.\n\nI will add these back as soon as I can get back to the PC, thanks for\nspotting that!\n\nOn Tue, 22 Jul 2025 at 17:02, Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Leon\n>\n> On 19/07/2025 13:28, Leon Michalak via GitGitGadget wrote:\n> > From: Leon Michalak <leonmichalak6@gmail.com>\n> >\n> > This patch compliments the previous commit, where builtins that use\n> > add-patch infrastructure now respect diff.context and\n> > diff.interHunkContext file configurations.\n> >\n> > In particular, this patch helps users who don't want to set persistent\n> > context configurations or just want a way to override them on a one-time\n> > basis, by allowing the relevant builtins to accept corresponding command\n> > line options that override the file configurations.\n> >\n> > This mimics commands such as diff and log, which allow for both context\n> > file configuration and command line overrides.\n>\n> Thanks for updating the quoting in the tests. Unfortunately this patch\n> now deletes the tests added in the last commit which I don't think is\n> correct.\n>\n> >\n> > -test_expect_success 'add -p respects diff.context' '\n> > -     test_write_lines a b c d e f g h i j k l m >file &&\n> I think there is some confusion here - why are we deleting the tests\n> added in the last commit? This removes the test coverage for\n> diff.context and diff.interHunkContext\n>\n> > +for cmd in add checkout restore 'commit -m file'\n> > +do\n> > +     test_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" '\n> > +             test_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n> > +             git add file &&\n> > +             test_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n> > +             echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n> > +                     $cmd -p -U 4 --inter-hunk-context 2 >actual &&\n> > +             test_grep \"@@ -2,20 +2,20 @@\" actual\n> > +     '\n> > +done\n> > +\n> > +test_expect_success 'reset accepts -U and --inter-hunk-context' '\n> > +     test_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n> > +     git commit -m file file &&\n> > +     test_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n> >       git add file &&\n> > -     test_write_lines a b c d e f G h i j k l m >file &&\n> > -     echo y | git -c diff.context=5 add -p >actual &&\n> > -     test_grep \"@@ -2,11 +2,11 @@\" actual\n> > +     echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n> > +             reset -p -U 4 --inter-hunk-context 2 >actual &&\n> > +     test_grep \"@@ -2,20 +2,20 @@\" actual\n> >   '\n> >\n> > -test_expect_success 'add -p respects diff.interHunkContext' '\n> > -     test_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n> > -     git add file &&\n> > -     test_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n> > -     echo y | git -c diff.interhunkcontext=2 add -p >actual &&\n> > -     test_grep \"@@ -2,16 +2,16 @@\" actual\n>\n> This is also deleting a test added in the last patch\n>\n> > +test_expect_success 'stash accepts -U and --inter-hunk-context' '\n> > +     test_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n> > +     git commit -m file file &&\n> > +     test_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n> > +     echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n> > +             stash -p -U 4 --inter-hunk-context 2 >actual &&\n> > +     test_grep \"@@ -2,20 +2,20 @@\" actual\n> >   '\n> >\n> > -test_expect_success 'add -p rejects negative diff.context' '\n> > -     test_config diff.context -1 &&\n> > -     test_must_fail git add -p 2>output &&\n> > -     test_grep \"diff.context cannot be negative\" output\n> > -'\n>\n> and so is this. The tests you're adding look good but we shouldn't be\n> deleting the existing ones.\n>\n> > +for cmd in add checkout commit reset restore \"stash save\" \"stash push\"\n> > +do\n> > +     test_expect_success \"$cmd rejects invalid context options\" '\n> > +             test_must_fail git $cmd -p -U -3 2>actual &&\n> > +             cat actual | echo &&\n> > +             test_grep -e \".--unified. cannot be negative\" actual &&\n> > +\n> > +             test_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&\n> > +             test_grep -e \".--inter-hunk-context. cannot be negative\" actual &&\n> > +\n> > +             test_must_fail git $cmd -U 7 2>actual &&\n> > +             test_grep -E \".--unified. requires .(--interactive/)?--patch.\" actual &&\n> > +\n> > +             test_must_fail git $cmd --inter-hunk-context 2 2>actual &&\n> > +             test_grep -E \".--inter-hunk-context. requires .(--interactive/)?--patch.\" actual\n> > +     '\n> > +done\n>\n> This looks good as well\n> >   test_done\n> As I said last time I do not think the tests below add any value. They\n> also do not compensate for the removal of the tests for diff.context\n> that are deleted above as they all pass -U on the commandline.\n>\n> > diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\n> > index 1384a8195705..0158fe6568cb 100755\n> > --- a/t/t4055-diff-context.sh\n> > +++ b/t/t4055-diff-context.sh\n> > @@ -58,6 +58,36 @@ test_expect_success 'The -U option overrides diff.context' '\n> >       test_grep ! \"^ firstline\" output\n> >   '\n> >\n> > +test_expect_success 'The -U option overrides diff.context for \"add\"' '\n> > +     test_config diff.context 8 &&\n> > +     git add -U4 -p >output &&\n> > +     test_grep ! \"^ firstline\" output\n> > +'\n> > +\n> > +test_expect_success 'The -U option overrides diff.context for \"commit\"' '\n> > +     test_config diff.context 8 &&\n> > +     ! git commit -U4 -p >output &&\n> > +     test_grep ! \"^ firstline\" output\n> > +'\n> > +\n> > +test_expect_success 'The -U option overrides diff.context for \"checkout\"' '\n> > +     test_config diff.context 8 &&\n> > +     git checkout -U4 -p >output &&\n> > +     test_grep ! \"^ firstline\" output\n> > +'\n> > +\n> > +test_expect_success 'The -U option overrides diff.context for \"stash\"' '\n> > +     test_config diff.context 8 &&\n> > +     ! git stash -U4 -p >output &&\n> > +     test_grep ! \"^ firstline\" output\n> > +'\n> > +\n> > +test_expect_success 'The -U option overrides diff.context for \"restore\"' '\n> > +     test_config diff.context 8 &&\n> > +     git restore -U4 -p >output &&\n> > +     test_grep ! \"^ firstline\" output\n> > +'\n> > +\n> >   test_expect_success 'diff.context honored by \"diff\"' '\n> >       test_config diff.context 8 &&\n> >       git diff >output &&\n>\n> Thanks\n>\n> Phillip\n>\n"},{"id":"522484","messageId":"CAP9jKjE3FT4c-0gphCmo2yDmMPni-d3vOhcJXUb1PPm=p5_GMA@mail.gmail.com","threadId":"63396","inReplyTo":"CAP9jKjExau58dP+=MJXiKWvCcOaBmhB-yta0x76s7b0XWqBQxw@mail.gmail.com","subject":"Re: [PATCH v4 4/4] add-patch: add diff.context command line overrides","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-07-22T18:05:19Z","receivedAt":"2025-07-22T18:05:31Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"Apologies for the top-posting above, this is not my regular workflow!\n"},{"id":"522540","messageId":"20250723094622.21639-1-phillip.wood123@gmail.com","threadId":"63396","inReplyTo":"CAP9jKjExau58dP+=MJXiKWvCcOaBmhB-yta0x76s7b0XWqBQxw@mail.gmail.com","subject":"Re: [PATCH v4 4/4] add-patch: add diff.context command line overrides","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-23T09:41:51Z","receivedAt":"2025-07-23T09:46:51Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Leon\n\nHere is a fixup commit for the tests which can be squashed into patch 4\n\nBest Wishes\n\nPhillip\n\n---- 8< ----\nFrom: Phillip Wood <phillip.wood@dunelm.org.uk>\nSubject: [PATCH] fixup! add-patch: add diff.context command line overrides\n\nRestore the test coverage for diff.context and diff.interHunkContext\nadded in f08d4ae6e56 (add-patch: respect diff.context configuration,\n2025-07-19) and remove the redunant tests in t4055 added by bd6d6ba1321\n(add-patch: add diff.context command line overrides, 2025-07-19) which\nduplicate the coverage of the tests added to t3071 in the same commit.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++\n t/t4055-diff-context.sh    | 30 ------------------------------\n 2 files changed, 22 insertions(+), 30 deletions(-)\n\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex 7fd9a23c998..04d2a198352 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -1230,6 +1230,28 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'add -p respects diff.context' '\n+\ttest_write_lines a b c d e f g h i j k l m >file &&\n+\tgit add file &&\n+\ttest_write_lines a b c d e f G h i j k l m >file &&\n+\techo y | git -c diff.context=5 add -p >actual &&\n+\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n+'\n+\n+test_expect_success 'add -p respects diff.interHunkContext' '\n+\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n+\tgit add file &&\n+\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n+\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n+\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n+'\n+\n+test_expect_success 'add -p rejects negative diff.context' '\n+\ttest_config diff.context -1 &&\n+\ttest_must_fail git add -p 2>output &&\n+\ttest_grep \"diff.context cannot be negative\" output\n+'\n+\n for cmd in add checkout restore 'commit -m file'\n do\n \ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" '\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex 0158fe6568c..1384a819570 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -58,36 +58,6 @@ test_expect_success 'The -U option overrides diff.context' '\n \ttest_grep ! \"^ firstline\" output\n '\n \n-test_expect_success 'The -U option overrides diff.context for \"add\"' '\n-\ttest_config diff.context 8 &&\n-\tgit add -U4 -p >output &&\n-\ttest_grep ! \"^ firstline\" output\n-'\n-\n-test_expect_success 'The -U option overrides diff.context for \"commit\"' '\n-\ttest_config diff.context 8 &&\n-\t! git commit -U4 -p >output &&\n-\ttest_grep ! \"^ firstline\" output\n-'\n-\n-test_expect_success 'The -U option overrides diff.context for \"checkout\"' '\n-\ttest_config diff.context 8 &&\n-\tgit checkout -U4 -p >output &&\n-\ttest_grep ! \"^ firstline\" output\n-'\n-\n-test_expect_success 'The -U option overrides diff.context for \"stash\"' '\n-\ttest_config diff.context 8 &&\n-\t! git stash -U4 -p >output &&\n-\ttest_grep ! \"^ firstline\" output\n-'\n-\n-test_expect_success 'The -U option overrides diff.context for \"restore\"' '\n-\ttest_config diff.context 8 &&\n-\tgit restore -U4 -p >output &&\n-\ttest_grep ! \"^ firstline\" output\n-'\n-\n test_expect_success 'diff.context honored by \"diff\"' '\n \ttest_config diff.context 8 &&\n \tgit diff >output &&\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"522920","messageId":"pull.1915.v5.git.1753772511.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v4.git.1752928113.gitgitgadget@gmail.com","subject":"[PATCH v5 0/4] Better support for customising context lines in --patch commands","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-07-29T07:01:47Z","receivedAt":"2025-07-29T07:01:55Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"This series of patches attempt to give --interactive/--patch compatible\nbuiltins (\"add\", \"commit\", \"checkout\", \"reset\", \"restore\" and \"stash\")\nbetter support and nicer experience for configuring how many context lines\nare shown in diffs through a variety of ways.\n\nPrior to these patches, the user could not choose how many context lines\nthey saw in --patch commands (apart from one workaround by using\nGIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a\npersistent solution). Additionally, the behaviour around reading from the\ndiff.context and diff.interHunkContext configs was also inconsistent with\nother diff generating commands such as \"log -p\".\n\nThe summarised changes below hopefully make this experience better and fix\nsome inconsistencies:\n\n * diff.context and diff.interHunkContext configs are now respected by\n   --patch compatible commands\n * --unified and --inter-hunk-context command line options have been added\n   to --patch compatible commands (which take prescendence over file\n   configs)\n * \"add\" and \"commit\" in --interactive mode now expose a new \"context\"\n   subcommand which configures the amount of context lines you wish to see\n   in subsequent diffs generated from other subcommands such as \"patch\" or\n   \"diff\"\n\nThe original discussion for this can be read at:\n\n * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/\n\nChanges since v1:\n\n * Update commit descriptions\n * Update tests to use the more modern and robust test_grep and test_config\n   utils\n * Reword some documentation / user messages\n * Ensure each commit is atomic and builds/passes tests on it's own\n * Make new command line options DRY\n * Add tests for interhunk context interaction\n * Error if context config/command line options are negative\n * Drop previous last commit to do with new subcommand for --interactive\n   add/commit. My motivations behind this patch series originally where\n   quite simple, just for add-patch commands to respect context configs.\n   This subcommand, after the discussion in v1, will require more thought\n   and a larger implementation that what I had anticipated. I would prefer\n   to leave this for another time as it's the least impactful but the most\n   time intensive and complicated idea.\n\nChanges since v2:\n\n * Update tests to only test single command (following Philip's suggestion)\n * Add negative option checks\n * Minor commit re-wording\n\nChanges since v3:\n\n * Update commit descriptions\n * Read struct properties directly instead of assigning to variables first\n * Simplify config setting / error checking\n * Remove redundant tests in later commit as they were replaced with better\n   test(s)\n * Change tests to use single quotes (this messes with the grep so was\n   unable to explicitly test single quotes in the error messages, so decided\n   to use regex . instead, which is what some other tests that have this\n   problem seem to use as well)\n\nChanges since v4:\n\n * Add back tests to maintain good coverage and remove redundant tests\n\nLeon Michalak (4):\n  t: use test_grep in t3701 and t4055\n  t: use test_config in t4055\n  add-patch: respect diff.context configuration\n  add-patch: add diff.context command line overrides\n\n Documentation/diff-context-options.adoc |  10 ++\n Documentation/git-add.adoc              |   2 +\n Documentation/git-checkout.adoc         |   2 +\n Documentation/git-commit.adoc           |   2 +\n Documentation/git-reset.adoc            |   2 +\n Documentation/git-restore.adoc          |   2 +\n Documentation/git-stash.adoc            |   2 +\n add-interactive.c                       |  45 +++++++--\n add-interactive.h                       |  17 +++-\n add-patch.c                             |  14 ++-\n builtin/add.c                           |  21 ++++-\n builtin/checkout.c                      |  31 +++++-\n builtin/commit.c                        |  16 +++-\n builtin/reset.c                         |  17 +++-\n builtin/stash.c                         |  56 ++++++++---\n commit.h                                |   3 +-\n parse-options.h                         |   2 +\n t/t3701-add-interactive.sh              | 119 +++++++++++++++++++-----\n t/t4055-diff-context.sh                 |  42 ++++-----\n t/t9902-completion.sh                   |   2 +\n 20 files changed, 324 insertions(+), 83 deletions(-)\n create mode 100644 Documentation/diff-context-options.adoc\n\n\nbase-commit: cf6f63ea6bf35173e02e18bdc6a4ba41288acff9\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/1915\n\nRange-diff vs v4:\n\n 1:  bbb2bc7082b = 1:  bbb2bc7082b t: use test_grep in t3701 and t4055\n 2:  feace2d3676 = 2:  feace2d3676 t: use test_config in t4055\n 3:  994029d6602 = 3:  994029d6602 add-patch: respect diff.context configuration\n 4:  2774b930406 ! 4:  9731e5b76fb add-patch: add diff.context command line overrides\n     @@ parse-options.h: int parse_opt_tracking_mode(const struct option *, const char *\n       \tOPT_SET_INT_F('4', \"ipv4\", (v), N_(\"use IPv4 addresses only\"), \\\n      \n       ## t/t3701-add-interactive.sh ##\n     -@@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n     - \ttest_cmp expect actual\n     +@@ t/t3701-add-interactive.sh: test_expect_success 'add -p rejects negative diff.context' '\n     + \ttest_grep \"diff.context cannot be negative\" output\n       '\n       \n     --test_expect_success 'add -p respects diff.context' '\n     --\ttest_write_lines a b c d e f g h i j k l m >file &&\n      +for cmd in add checkout restore 'commit -m file'\n      +do\n      +\ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" '\n     @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.\n      +\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n      +\tgit commit -m file file &&\n      +\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n     - \tgit add file &&\n     --\ttest_write_lines a b c d e f G h i j k l m >file &&\n     --\techo y | git -c diff.context=5 add -p >actual &&\n     --\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n     ++\tgit add file &&\n      +\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n      +\t\treset -p -U 4 --inter-hunk-context 2 >actual &&\n      +\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n     - '\n     - \n     --test_expect_success 'add -p respects diff.interHunkContext' '\n     --\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n     --\tgit add file &&\n     --\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n     --\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n     --\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n     ++'\n     ++\n      +test_expect_success 'stash accepts -U and --inter-hunk-context' '\n      +\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n      +\tgit commit -m file file &&\n     @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.\n      +\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n      +\t\tstash -p -U 4 --inter-hunk-context 2 >actual &&\n      +\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n     - '\n     - \n     --test_expect_success 'add -p rejects negative diff.context' '\n     --\ttest_config diff.context -1 &&\n     --\ttest_must_fail git add -p 2>output &&\n     --\ttest_grep \"diff.context cannot be negative\" output\n     --'\n     ++'\n     ++\n      +for cmd in add checkout commit reset restore \"stash save\" \"stash push\"\n      +do\n      +\ttest_expect_success \"$cmd rejects invalid context options\" '\n     @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.\n      +\t\ttest_grep -E \".--inter-hunk-context. requires .(--interactive/)?--patch.\" actual\n      +\t'\n      +done\n     - \n     - test_done\n     -\n     - ## t/t4055-diff-context.sh ##\n     -@@ t/t4055-diff-context.sh: test_expect_success 'The -U option overrides diff.context' '\n     - \ttest_grep ! \"^ firstline\" output\n     - '\n     - \n     -+test_expect_success 'The -U option overrides diff.context for \"add\"' '\n     -+\ttest_config diff.context 8 &&\n     -+\tgit add -U4 -p >output &&\n     -+\ttest_grep ! \"^ firstline\" output\n     -+'\n     -+\n     -+test_expect_success 'The -U option overrides diff.context for \"commit\"' '\n     -+\ttest_config diff.context 8 &&\n     -+\t! git commit -U4 -p >output &&\n     -+\ttest_grep ! \"^ firstline\" output\n     -+'\n     -+\n     -+test_expect_success 'The -U option overrides diff.context for \"checkout\"' '\n     -+\ttest_config diff.context 8 &&\n     -+\tgit checkout -U4 -p >output &&\n     -+\ttest_grep ! \"^ firstline\" output\n     -+'\n      +\n     -+test_expect_success 'The -U option overrides diff.context for \"stash\"' '\n     -+\ttest_config diff.context 8 &&\n     -+\t! git stash -U4 -p >output &&\n     -+\ttest_grep ! \"^ firstline\" output\n     -+'\n     -+\n     -+test_expect_success 'The -U option overrides diff.context for \"restore\"' '\n     -+\ttest_config diff.context 8 &&\n     -+\tgit restore -U4 -p >output &&\n     -+\ttest_grep ! \"^ firstline\" output\n     -+'\n     -+\n     - test_expect_success 'diff.context honored by \"diff\"' '\n     - \ttest_config diff.context 8 &&\n     - \tgit diff >output &&\n     + test_done\n      \n       ## t/t9902-completion.sh ##\n      @@ t/t9902-completion.sh: test_expect_success 'double dash \"git checkout\"' '\n\n-- \ngitgitgadget\n"},{"id":"522921","messageId":"feace2d3676a03d720e9c85b7842f47446e9462e.1753772511.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v5.git.1753772511.gitgitgadget@gmail.com","subject":"[PATCH v5 2/4] t: use test_config in t4055","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-07-29T07:01:49Z","receivedAt":"2025-07-29T07:01:57Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nUse the modern \"test_config\" test utility instead of manual\"git config\"\nas the former provides clean up on test completion.\n\nThis is a prerequisite to the commits that follow which add to this test\nfile.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n t/t4055-diff-context.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex c66f966a3ab3..1384a8195705 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -47,43 +47,43 @@ test_expect_success 'the default number of context lines is 3' '\n test_expect_success 'diff.context honored by \"log\"' '\n \tgit log -1 -p >output &&\n \ttest_grep ! firstline output &&\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit log -1 -p >output &&\n \ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'The -U option overrides diff.context' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit log -U4 -1 >output &&\n \ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'diff.context honored by \"diff\"' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit diff >output &&\n \ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'plumbing not affected' '\n-\tgit config diff.context 8 &&\n+\ttest_config diff.context 8 &&\n \tgit diff-files -p >output &&\n \ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'non-integer config parsing' '\n-\tgit config diff.context no &&\n+\ttest_config diff.context no &&\n \ttest_must_fail git diff 2>output &&\n \ttest_grep \"bad numeric config value\" output\n '\n \n test_expect_success 'negative integer config parsing' '\n-\tgit config diff.context -1 &&\n+\ttest_config diff.context -1 &&\n \ttest_must_fail git diff 2>output &&\n \ttest_grep \"bad config variable\" output\n '\n \n test_expect_success '-U0 is valid, so is diff.context=0' '\n-\tgit config diff.context 0 &&\n+\ttest_config diff.context 0 &&\n \tgit diff >output &&\n \ttest_grep \"^-ADDED\" output &&\n \ttest_grep \"^+MODIFIED\" output\n-- \ngitgitgadget\n\n"},{"id":"522922","messageId":"994029d66029e0eb1b93a6675e4df9c5c6fb76f9.1753772511.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v5.git.1753772511.gitgitgadget@gmail.com","subject":"[PATCH v5 3/4] add-patch: respect diff.context configuration","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-07-29T07:01:50Z","receivedAt":"2025-07-29T07:01:58Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nVarious builtins that use add-patch infrastructure do not respect\nthe user's diff.context and diff.interHunkContext file configurations.\n\nThe user may be used to seeing their diffs with customized context size,\nbut not in the patches \"git add -p\" shows them to pick from.\n\nTeach add-patch infrastructure to read these configuration variables and\npass their values when spawning the underlying plumbing commands as\ntheir command line option.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n add-interactive.c          |  9 +++++++++\n add-interactive.h          |  1 +\n add-patch.c                |  9 ++++++---\n t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++\n 4 files changed, 38 insertions(+), 3 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 97ff35b6f12a..eb3d0d3ada84 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -41,6 +41,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \tconst char *value;\n \n \ts->r = r;\n+\ts->context = -1;\n+\ts->interhunkcontext = -1;\n \n \tif (repo_config_get_value(r, \"color.interactive\", &value))\n \t\ts->use_color = -1;\n@@ -78,6 +80,13 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \trepo_config_get_string(r, \"diff.algorithm\",\n \t\t\t       &s->interactive_diff_algorithm);\n \n+\tif (!repo_config_get_int(r, \"diff.context\", &s->context))\n+\t\tif (s->context < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"diff.context\");\n+\tif (!repo_config_get_int(r, \"diff.interHunkContext\", &s->interhunkcontext))\n+\t\tif (s->interhunkcontext < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"diff.interHunkContext\");\n+\n \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n \tif (s->use_single_key)\n \t\tsetbuf(stdin, NULL);\ndiff --git a/add-interactive.h b/add-interactive.h\nindex 693f125e8e4b..c63f35b14be8 100644\n--- a/add-interactive.h\n+++ b/add-interactive.h\n@@ -18,6 +18,7 @@ struct add_i_state {\n \n \tint use_single_key;\n \tchar *interactive_diff_filter, *interactive_diff_algorithm;\n+\tint context, interhunkcontext;\n };\n \n void init_add_i_state(struct add_i_state *s, struct repository *r);\ndiff --git a/add-patch.c b/add-patch.c\nindex 95c67d8c80c4..b0125b51ba45 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -414,7 +414,6 @@ static int normalize_marker(const char *p)\n static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n {\n \tstruct strvec args = STRVEC_INIT;\n-\tconst char *diff_algorithm = s->s.interactive_diff_algorithm;\n \tstruct strbuf *plain = &s->plain, *colored = NULL;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\\0';\n@@ -424,8 +423,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \tint res;\n \n \tstrvec_pushv(&args, s->mode->diff_cmd);\n-\tif (diff_algorithm)\n-\t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", diff_algorithm);\n+\tif (s->s.context != -1)\n+\t\tstrvec_pushf(&args, \"--unified=%i\", s->s.context);\n+\tif (s->s.interhunkcontext != -1)\n+\t\tstrvec_pushf(&args, \"--inter-hunk-context=%i\", s->s.interhunkcontext);\n+\tif (s->s.interactive_diff_algorithm)\n+\t\tstrvec_pushf(&args, \"--diff-algorithm=%s\", s->s.interactive_diff_algorithm);\n \tif (s->revision) {\n \t\tstruct object_id oid;\n \t\tstrvec_push(&args,\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex b088ee141ff4..18dc329ea1f6 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -1230,4 +1230,26 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'add -p respects diff.context' '\n+\ttest_write_lines a b c d e f g h i j k l m >file &&\n+\tgit add file &&\n+\ttest_write_lines a b c d e f G h i j k l m >file &&\n+\techo y | git -c diff.context=5 add -p >actual &&\n+\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n+'\n+\n+test_expect_success 'add -p respects diff.interHunkContext' '\n+\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n+\tgit add file &&\n+\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n+\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n+\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n+'\n+\n+test_expect_success 'add -p rejects negative diff.context' '\n+\ttest_config diff.context -1 &&\n+\ttest_must_fail git add -p 2>output &&\n+\ttest_grep \"diff.context cannot be negative\" output\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"522923","messageId":"bbb2bc7082baaa11fd428efb375c5d142f82e7fd.1753772511.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v5.git.1753772511.gitgitgadget@gmail.com","subject":"[PATCH v5 1/4] t: use test_grep in t3701 and t4055","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-07-29T07:01:48Z","receivedAt":"2025-07-29T07:01:58Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nAs a preparatory clean-up, use the \"test_grep\" test utility instead of\nregular \"grep\" which provides better debug information if tests fail.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n t/t3701-add-interactive.sh | 48 +++++++++++++++++++-------------------\n t/t4055-diff-context.sh    | 28 +++++++++++-----------\n 2 files changed, 38 insertions(+), 38 deletions(-)\n\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex b8a05d95f3f1..b088ee141ff4 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -63,7 +63,7 @@ test_expect_success 'setup (initial)' '\n '\n test_expect_success 'status works (initial)' '\n \tgit add -i </dev/null >output &&\n-\tgrep \"+1/-0 *+2/-0 file\" output\n+\ttest_grep \"+1/-0 *+2/-0 file\" output\n '\n \n test_expect_success 'setup expected' '\n@@ -86,7 +86,7 @@ test_expect_success 'revert works (initial)' '\n \tgit add file &&\n \ttest_write_lines r 1 | git add -i &&\n \tgit ls-files >output &&\n-\t! grep . output\n+\ttest_grep ! . output\n '\n \n test_expect_success 'add untracked (multiple)' '\n@@ -109,7 +109,7 @@ test_expect_success 'setup (commit)' '\n '\n test_expect_success 'status works (commit)' '\n \tgit add -i </dev/null >output &&\n-\tgrep \"+1/-0 *+2/-0 file\" output\n+\ttest_grep \"+1/-0 *+2/-0 file\" output\n '\n \n test_expect_success 'update can stage deletions' '\n@@ -141,7 +141,7 @@ test_expect_success 'revert works (commit)' '\n \tgit add file &&\n \ttest_write_lines r 1 | git add -i &&\n \tgit add -i </dev/null >output &&\n-\tgrep \"unchanged *+3/-0 file\" output\n+\ttest_grep \"unchanged *+3/-0 file\" output\n '\n \n test_expect_success 'reject multi-key input' '\n@@ -185,7 +185,7 @@ test_expect_success 'setup fake editor' '\n test_expect_success 'bad edit rejected' '\n \tgit reset &&\n \ttest_write_lines e n d | git add -p >output &&\n-\tgrep \"hunk does not apply\" output\n+\ttest_grep \"hunk does not apply\" output\n '\n \n test_expect_success 'setup patch' '\n@@ -198,7 +198,7 @@ test_expect_success 'setup patch' '\n test_expect_success 'garbage edit rejected' '\n \tgit reset &&\n \ttest_write_lines e n d | git add -p >output &&\n-\tgrep \"hunk does not apply\" output\n+\ttest_grep \"hunk does not apply\" output\n '\n \n test_expect_success 'setup patch' '\n@@ -313,8 +313,8 @@ test_expect_success FILEMODE 'stage mode and hunk' '\n \tchmod +x file &&\n \tprintf \"y\\\\ny\\\\n\" | git add -p &&\n \tgit diff --cached file >out &&\n-\tgrep \"new mode\" out &&\n-\tgrep \"+content\" out &&\n+\ttest_grep \"new mode\" out &&\n+\ttest_grep \"+content\" out &&\n \tgit diff file >out &&\n \ttest_must_be_empty out\n '\n@@ -636,7 +636,7 @@ test_expect_success 'split hunk \"add -p (edit)\"' '\n \tprintf \"%s\\n\" s e     q n q q |\n \tEDITOR=: git add -p &&\n \tgit diff >actual &&\n-\t! grep \"^+15\" actual\n+\ttest_grep ! \"^+15\" actual\n '\n \n test_expect_success 'split hunk \"add -p (no, yes, edit)\"' '\n@@ -648,7 +648,7 @@ test_expect_success 'split hunk \"add -p (no, yes, edit)\"' '\n \tEDITOR=: git add -p 2>error &&\n \ttest_must_be_empty error &&\n \tgit diff >actual &&\n-\t! grep \"^+31\" actual\n+\ttest_grep ! \"^+31\" actual\n '\n \n test_expect_success 'split hunk with incomplete line at end' '\n@@ -682,7 +682,7 @@ test_expect_success 'edit, adding lines to the first hunk' '\n \tEDITOR=./fake_editor.sh git add -p 2>error &&\n \ttest_must_be_empty error &&\n \tgit diff --cached >actual &&\n-\tgrep \"^+22\" actual\n+\ttest_grep \"^+22\" actual\n '\n \n test_expect_success 'patch mode ignores unmerged entries' '\n@@ -696,7 +696,7 @@ test_expect_success 'patch mode ignores unmerged entries' '\n \ttest_must_fail git merge side &&\n \techo changed >non-conflict.t &&\n \techo y | git add -p >output &&\n-\t! grep a/conflict.t output &&\n+\ttest_grep ! a/conflict.t output &&\n \tcat >expected <<-\\EOF &&\n \t* Unmerged path conflict.t\n \tdiff --git a/non-conflict.t b/non-conflict.t\n@@ -728,7 +728,7 @@ test_expect_success 'diffs can be colorized' '\n \n \t# We do not want to depend on the exact coloring scheme\n \t# git uses for diffs, so just check that we saw some kind of color.\n-\tgrep \"$(printf \"\\\\033\")\" output\n+\ttest_grep \"$(printf \"\\\\033\")\" output\n '\n \n test_expect_success 'colors can be overridden' '\n@@ -743,7 +743,7 @@ test_expect_success 'colors can be overridden' '\n \t\t-c color.interactive.error=blue \\\n \t\tadd -i 2>err.raw <input &&\n \ttest_decode_color <err.raw >err &&\n-\tgrep \"<BLUE>Huh (trigger)?<RESET>\" err &&\n+\ttest_grep \"<BLUE>Huh (trigger)?<RESET>\" err &&\n \n \ttest_write_lines help quit >input &&\n \tforce_color git \\\n@@ -863,7 +863,7 @@ test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '\n \tprintf y >y &&\n \tforce_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&\n \ttest_decode_color <output.raw >output &&\n-\tgrep \"old<\" output\n+\ttest_grep \"old<\" output\n '\n \n test_expect_success 'diffFilter filters diff' '\n@@ -876,7 +876,7 @@ test_expect_success 'diffFilter filters diff' '\n \n \t# avoid depending on the exact coloring or content of the prompts,\n \t# and just make sure we saw our diff prefixed\n-\tgrep foo:.*content output\n+\ttest_grep foo:.*content output\n '\n \n test_expect_success 'detect bogus diffFilter output' '\n@@ -886,7 +886,7 @@ test_expect_success 'detect bogus diffFilter output' '\n \ttest_config interactive.diffFilter \"sed 6d\" &&\n \tprintf y >y &&\n \tforce_color test_must_fail git add -p <y >output 2>&1 &&\n-\tgrep \"mismatched output\" output\n+\ttest_grep \"mismatched output\" output\n '\n \n test_expect_success 'handle iffy colored hunk headers' '\n@@ -896,7 +896,7 @@ test_expect_success 'handle iffy colored hunk headers' '\n \tprintf n >n &&\n \tforce_color git -c interactive.diffFilter=\"sed s/.*@@.*/XX/\" \\\n \t\tadd -p >output 2>&1 <n &&\n-\tgrep \"^XX$\" output\n+\ttest_grep \"^XX$\" output\n '\n \n test_expect_success 'handle very large filtered diff' '\n@@ -1002,7 +1002,7 @@ test_expect_success 'add -p does not expand argument lists' '\n \t# update it, but we want to be sure that our \".\" pathspec\n \t# was not expanded into the argument list of any command.\n \t# So look only for \"not-changed\".\n-\t! grep -E \"^trace: (built-in|exec|run_command): .*not-changed\" trace.out\n+\ttest_grep ! -E \"^trace: (built-in|exec|run_command): .*not-changed\" trace.out\n '\n \n test_expect_success 'hunk-editing handles custom comment char' '\n@@ -1072,21 +1072,21 @@ test_expect_success 'setup different kinds of dirty submodules' '\n \n test_expect_success 'status ignores dirty submodules (except HEAD)' '\n \tgit -C for-submodules add -i </dev/null >output &&\n-\tgrep dirty-head output &&\n-\tgrep dirty-both-ways output &&\n-\t! grep dirty-otherwise output\n+\ttest_grep dirty-head output &&\n+\ttest_grep dirty-both-ways output &&\n+\ttest_grep ! dirty-otherwise output\n '\n \n test_expect_success 'handle submodules' '\n \techo 123 >>for-submodules/dirty-otherwise/initial.t &&\n \n \tforce_color git -C for-submodules add -p dirty-otherwise >output 2>&1 &&\n-\tgrep \"No changes\" output &&\n+\ttest_grep \"No changes\" output &&\n \n \tforce_color git -C for-submodules add -p dirty-head >output 2>&1 <y &&\n \tgit -C for-submodules ls-files --stage dirty-head >actual &&\n \trev=\"$(git -C for-submodules/dirty-head rev-parse HEAD)\" &&\n-\tgrep \"$rev\" actual\n+\ttest_grep \"$rev\" actual\n '\n \n test_expect_success 'set up pathological context' '\ndiff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh\nindex ec2804eea67c..c66f966a3ab3 100755\n--- a/t/t4055-diff-context.sh\n+++ b/t/t4055-diff-context.sh\n@@ -38,36 +38,36 @@ test_expect_success 'setup' '\n \n test_expect_success 'the default number of context lines is 3' '\n \tgit diff >output &&\n-\t! grep \"^ d\" output &&\n-\tgrep \"^ e\" output &&\n-\tgrep \"^ j\" output &&\n-\t! grep \"^ k\" output\n+\ttest_grep ! \"^ d\" output &&\n+\ttest_grep \"^ e\" output &&\n+\ttest_grep \"^ j\" output &&\n+\ttest_grep ! \"^ k\" output\n '\n \n test_expect_success 'diff.context honored by \"log\"' '\n \tgit log -1 -p >output &&\n-\t! grep firstline output &&\n+\ttest_grep ! firstline output &&\n \tgit config diff.context 8 &&\n \tgit log -1 -p >output &&\n-\tgrep \"^ firstline\" output\n+\ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'The -U option overrides diff.context' '\n \tgit config diff.context 8 &&\n \tgit log -U4 -1 >output &&\n-\t! grep \"^ firstline\" output\n+\ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'diff.context honored by \"diff\"' '\n \tgit config diff.context 8 &&\n \tgit diff >output &&\n-\tgrep \"^ firstline\" output\n+\ttest_grep \"^ firstline\" output\n '\n \n test_expect_success 'plumbing not affected' '\n \tgit config diff.context 8 &&\n \tgit diff-files -p >output &&\n-\t! grep \"^ firstline\" output\n+\ttest_grep ! \"^ firstline\" output\n '\n \n test_expect_success 'non-integer config parsing' '\n@@ -85,8 +85,8 @@ test_expect_success 'negative integer config parsing' '\n test_expect_success '-U0 is valid, so is diff.context=0' '\n \tgit config diff.context 0 &&\n \tgit diff >output &&\n-\tgrep \"^-ADDED\" output &&\n-\tgrep \"^+MODIFIED\" output\n+\ttest_grep \"^-ADDED\" output &&\n+\ttest_grep \"^+MODIFIED\" output\n '\n \n test_expect_success '-U2147483647 works' '\n@@ -94,9 +94,9 @@ test_expect_success '-U2147483647 works' '\n \ttest_line_count = 16 x &&\n \tgit diff -U2147483647 >output &&\n \ttest_line_count = 22 output &&\n-\tgrep \"^-ADDED\" output &&\n-\tgrep \"^+MODIFIED\" output &&\n-\tgrep \"^+APPENDED\" output\n+\ttest_grep \"^-ADDED\" output &&\n+\ttest_grep \"^+MODIFIED\" output &&\n+\ttest_grep \"^+APPENDED\" output\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"522924","messageId":"9731e5b76fb4f8ffb76a90914dc97b9562c2f850.1753772511.git.gitgitgadget@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v5.git.1753772511.gitgitgadget@gmail.com","subject":"[PATCH v5 4/4] add-patch: add diff.context command line overrides","fromName":"Leon Michalak via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-07-29T07:01:51Z","receivedAt":"2025-07-29T07:01:59Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"From: Leon Michalak <leonmichalak6@gmail.com>\n\nThis patch compliments the previous commit, where builtins that use\nadd-patch infrastructure now respect diff.context and\ndiff.interHunkContext file configurations.\n\nIn particular, this patch helps users who don't want to set persistent\ncontext configurations or just want a way to override them on a one-time\nbasis, by allowing the relevant builtins to accept corresponding command\nline options that override the file configurations.\n\nThis mimics commands such as diff and log, which allow for both context\nfile configuration and command line overrides.\n\nSigned-off-by: Leon Michalak <leonmichalak6@gmail.com>\n---\n Documentation/diff-context-options.adoc | 10 +++++\n Documentation/git-add.adoc              |  2 +\n Documentation/git-checkout.adoc         |  2 +\n Documentation/git-commit.adoc           |  2 +\n Documentation/git-reset.adoc            |  2 +\n Documentation/git-restore.adoc          |  2 +\n Documentation/git-stash.adoc            |  2 +\n add-interactive.c                       | 36 ++++++++++++----\n add-interactive.h                       | 16 +++++--\n add-patch.c                             |  5 ++-\n builtin/add.c                           | 21 ++++++++--\n builtin/checkout.c                      | 31 ++++++++++++--\n builtin/commit.c                        | 16 ++++++-\n builtin/reset.c                         | 17 +++++++-\n builtin/stash.c                         | 56 ++++++++++++++++++++-----\n commit.h                                |  3 +-\n parse-options.h                         |  2 +\n t/t3701-add-interactive.sh              | 49 ++++++++++++++++++++++\n t/t9902-completion.sh                   |  2 +\n 19 files changed, 241 insertions(+), 35 deletions(-)\n create mode 100644 Documentation/diff-context-options.adoc\n\ndiff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc\nnew file mode 100644\nindex 000000000000..e161260358ff\n--- /dev/null\n+++ b/Documentation/diff-context-options.adoc\n@@ -0,0 +1,10 @@\n+`-U<n>`::\n+`--unified=<n>`::\n+\tGenerate diffs with _<n>_ lines of context. Defaults to `diff.context`\n+\tor 3 if the config option is unset.\n+\n+`--inter-hunk-context=<n>`::\n+\tShow the context between diff hunks, up to the specified _<number>_\n+\tof lines, thereby fusing hunks that are close to each other.\n+\tDefaults to `diff.interHunkContext` or 0 if the config option\n+\tis unset.\ndiff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\nindex eba0b419ce50..b7a735824d6c 100644\n--- a/Documentation/git-add.adoc\n+++ b/Documentation/git-add.adoc\n@@ -104,6 +104,8 @@ This effectively runs `add --interactive`, but bypasses the\n initial command menu and directly jumps to the `patch` subcommand.\n See ``Interactive mode'' for details.\n \n+include::diff-context-options.adoc[]\n+\n `-e`::\n `--edit`::\n \tOpen the diff vs. the index in an editor and let the user\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex ee83b6d9ba9a..40e02cfd6562 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -289,6 +289,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.\n Note that this option uses the no overlay mode by default (see also\n `--overlay`), and currently doesn't support overlay mode.\n \n+include::diff-context-options.adoc[]\n+\n `--ignore-other-worktrees`::\n \t`git checkout` refuses when the wanted branch is already checked\n \tout or otherwise in use by another worktree. This option makes\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex dc219025f1eb..ae988a883b5b 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -76,6 +76,8 @@ OPTIONS\n \twhich changes to commit. See linkgit:git-add[1] for\n \tdetails.\n \n+include::diff-context-options.adoc[]\n+\n `-C <commit>`::\n `--reuse-message=<commit>`::\n \tTake an existing _<commit>_ object, and reuse the log message\ndiff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc\nindex 53ab88c5451c..50e8a0ba6f66 100644\n--- a/Documentation/git-reset.adoc\n+++ b/Documentation/git-reset.adoc\n@@ -125,6 +125,8 @@ OPTIONS\n \tseparated with _NUL_ character and all other characters are taken\n \tliterally (including newlines and quotes).\n \n+include::diff-context-options.adoc[]\n+\n `--`::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc\nindex 877b7772e667..1dcc2bb7aea3 100644\n--- a/Documentation/git-restore.adoc\n+++ b/Documentation/git-restore.adoc\n@@ -52,6 +52,8 @@ leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to\n \tMode\" section of linkgit:git-add[1] to learn how to operate\n \tthe `--patch` mode.\n \n+include::diff-context-options.adoc[]\n+\n `-W`::\n `--worktree`::\n `-S`::\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex 1a5177f4986c..0578c619c410 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -208,6 +208,8 @@ to learn how to operate the `--patch` mode.\n The `--patch` option implies `--keep-index`.  You can use\n `--no-keep-index` to override this.\n \n+include::diff-context-options.adoc[]\n+\n -S::\n --staged::\n \tThis option is only valid for `push` and `save` commands.\ndiff --git a/add-interactive.c b/add-interactive.c\nindex eb3d0d3ada84..3e692b47eca0 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -36,7 +36,8 @@ static void init_color(struct repository *r, struct add_i_state *s,\n \tfree(key);\n }\n \n-void init_add_i_state(struct add_i_state *s, struct repository *r)\n+void init_add_i_state(struct add_i_state *s, struct repository *r,\n+\t\t      struct add_p_opt *add_p_opt)\n {\n \tconst char *value;\n \n@@ -90,6 +91,17 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)\n \trepo_config_get_bool(r, \"interactive.singlekey\", &s->use_single_key);\n \tif (s->use_single_key)\n \t\tsetbuf(stdin, NULL);\n+\n+\tif (add_p_opt->context != -1) {\n+\t\tif (add_p_opt->context < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"--unified\");\n+\t\ts->context = add_p_opt->context;\n+\t}\n+\tif (add_p_opt->interhunkcontext != -1) {\n+\t\tif (add_p_opt->interhunkcontext < 0)\n+\t\t\tdie(_(\"%s cannot be negative\"), \"--inter-hunk-context\");\n+\t\ts->interhunkcontext = add_p_opt->interhunkcontext;\n+\t}\n }\n \n void clear_add_i_state(struct add_i_state *s)\n@@ -978,6 +990,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,\n \topts->prompt = N_(\"Patch update\");\n \tcount = list_and_choose(s, files, opts);\n \tif (count > 0) {\n+\t\tstruct add_p_opt add_p_opt = {\n+\t\t\t.context = s->context,\n+\t\t\t.interhunkcontext = s->interhunkcontext,\n+\t\t};\n \t\tstruct strvec args = STRVEC_INIT;\n \t\tstruct pathspec ps_selected = { 0 };\n \n@@ -988,7 +1004,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,\n \t\tparse_pathspec(&ps_selected,\n \t\t\t       PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,\n \t\t\t       PATHSPEC_LITERAL_PATH, \"\", args.v);\n-\t\tres = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);\n+\t\tres = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected);\n \t\tstrvec_clear(&args);\n \t\tclear_pathspec(&ps_selected);\n \t}\n@@ -1023,10 +1039,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,\n \tif (count > 0) {\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n-\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\",\n-\t\t\t     oid_to_hex(!is_initial ? &oid :\n-\t\t\t\t\ts->r->hash_algo->empty_tree),\n-\t\t\t     \"--\", NULL);\n+\t\tstrvec_pushl(&cmd.args, \"git\", \"diff\", \"-p\", \"--cached\", NULL);\n+\t\tif (s->context != -1)\n+\t\t\tstrvec_pushf(&cmd.args, \"--unified=%i\", s->context);\n+\t\tif (s->interhunkcontext != -1)\n+\t\t\tstrvec_pushf(&cmd.args, \"--inter-hunk-context=%i\", s->interhunkcontext);\n+\t\tstrvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :\n+\t\t\t     s->r->hash_algo->empty_tree), \"--\", NULL);\n \t\tfor (i = 0; i < files->items.nr; i++)\n \t\t\tif (files->selected[i])\n \t\t\t\tstrvec_push(&cmd.args,\n@@ -1119,7 +1138,8 @@ static void command_prompt_help(struct add_i_state *s)\n \t\t\t _(\"(empty) select nothing\"));\n }\n \n-int run_add_i(struct repository *r, const struct pathspec *ps)\n+int run_add_i(struct repository *r, const struct pathspec *ps,\n+\t      struct add_p_opt *add_p_opt)\n {\n \tstruct add_i_state s = { NULL };\n \tstruct print_command_item_data data = { \"[\", \"]\" };\n@@ -1162,7 +1182,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)\n \t\t\t->util = util;\n \t}\n \n-\tinit_add_i_state(&s, r);\n+\tinit_add_i_state(&s, r, add_p_opt);\n \n \t/*\n \t * When color was asked for, use the prompt color for\ndiff --git a/add-interactive.h b/add-interactive.h\nindex c63f35b14be8..4213dcd67b9a 100644\n--- a/add-interactive.h\n+++ b/add-interactive.h\n@@ -3,6 +3,13 @@\n \n #include \"color.h\"\n \n+struct add_p_opt {\n+\tint context;\n+\tint interhunkcontext;\n+};\n+\n+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }\n+\n struct add_i_state {\n \tstruct repository *r;\n \tint use_color;\n@@ -21,12 +28,14 @@ struct add_i_state {\n \tint context, interhunkcontext;\n };\n \n-void init_add_i_state(struct add_i_state *s, struct repository *r);\n+void init_add_i_state(struct add_i_state *s, struct repository *r,\n+\t\t      struct add_p_opt *add_p_opt);\n void clear_add_i_state(struct add_i_state *s);\n \n struct repository;\n struct pathspec;\n-int run_add_i(struct repository *r, const struct pathspec *ps);\n+int run_add_i(struct repository *r, const struct pathspec *ps,\n+\t      struct add_p_opt *add_p_opt);\n \n enum add_p_mode {\n \tADD_P_ADD,\n@@ -37,6 +46,7 @@ enum add_p_mode {\n };\n \n int run_add_p(struct repository *r, enum add_p_mode mode,\n-\t      const char *revision, const struct pathspec *ps);\n+\t      struct add_p_opt *o, const char *revision,\n+\t      const struct pathspec *ps);\n \n #endif\ndiff --git a/add-patch.c b/add-patch.c\nindex b0125b51ba45..302e6ba7d9a3 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -1763,14 +1763,15 @@ soft_increment:\n }\n \n int run_add_p(struct repository *r, enum add_p_mode mode,\n-\t      const char *revision, const struct pathspec *ps)\n+\t      struct add_p_opt *o, const char *revision,\n+\t      const struct pathspec *ps)\n {\n \tstruct add_p_state s = {\n \t\t{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT\n \t};\n \tsize_t i, binary_count = 0;\n \n-\tinit_add_i_state(&s.s, r);\n+\tinit_add_i_state(&s.s, r, o);\n \n \tif (mode == ADD_P_STASH)\n \t\ts.mode = &patch_mode_stash;\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 7c292ffdc6c2..a00dab9b8fa0 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -29,6 +29,7 @@ static const char * const builtin_add_usage[] = {\n \tNULL\n };\n static int patch_interactive, add_interactive, edit_interactive;\n+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n static int take_worktree_changes;\n static int add_renormalize;\n static int pathspec_file_nul;\n@@ -157,7 +158,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec *\n int interactive_add(struct repository *repo,\n \t\t    const char **argv,\n \t\t    const char *prefix,\n-\t\t    int patch)\n+\t\t    int patch, struct add_p_opt *add_p_opt)\n {\n \tstruct pathspec pathspec;\n \tint ret;\n@@ -169,9 +170,9 @@ int interactive_add(struct repository *repo,\n \t\t       prefix, argv);\n \n \tif (patch)\n-\t\tret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);\n+\t\tret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec);\n \telse\n-\t\tret = !!run_add_i(repo, &pathspec);\n+\t\tret = !!run_add_i(repo, &pathspec, add_p_opt);\n \n \tclear_pathspec(&pathspec);\n \treturn ret;\n@@ -253,6 +254,8 @@ static struct option builtin_add_options[] = {\n \tOPT_GROUP(\"\"),\n \tOPT_BOOL('i', \"interactive\", &add_interactive, N_(\"interactive picking\")),\n \tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"select hunks interactively\")),\n+\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \tOPT_BOOL('e', \"edit\", &edit_interactive, N_(\"edit current diff and apply\")),\n \tOPT__FORCE(&ignored_too, N_(\"allow adding otherwise ignored files\"), 0),\n \tOPT_BOOL('u', \"update\", &take_worktree_changes, N_(\"update tracked files\")),\n@@ -394,6 +397,11 @@ int cmd_add(int argc,\n \tprepare_repo_settings(repo);\n \trepo->settings.command_requires_full_index = 0;\n \n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tif (patch_interactive)\n \t\tadd_interactive = 1;\n \tif (add_interactive) {\n@@ -401,7 +409,12 @@ int cmd_add(int argc,\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--dry-run\", \"--interactive/--patch\");\n \t\tif (pathspec_from_file)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--pathspec-from-file\", \"--interactive/--patch\");\n-\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive));\n+\t\texit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--interactive/--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--interactive/--patch\");\n \t}\n \n \tif (edit_interactive) {\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 536192d3456c..3737ba4c3920 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -61,6 +61,8 @@ static const char * const restore_usage[] = {\n \n struct checkout_opts {\n \tint patch_mode;\n+\tint patch_context;\n+\tint patch_interhunk_context;\n \tint quiet;\n \tint merge;\n \tint force;\n@@ -104,7 +106,12 @@ struct checkout_opts {\n \tstruct tree *source_tree;\n };\n \n-#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 }\n+#define CHECKOUT_OPTS_INIT { \\\n+\t.conflict_style = -1, \\\n+\t.merge = -1, \\\n+\t.patch_context = -1, \\\n+\t.patch_interhunk_context = -1, \\\n+}\n \n struct branch_info {\n \tchar *name; /* The short name used */\n@@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts,\n \n \tif (opts->patch_mode) {\n \t\tenum add_p_mode patch_mode;\n+\t\tstruct add_p_opt add_p_opt = {\n+\t\t\t.context = opts->patch_context,\n+\t\t\t.interhunkcontext = opts->patch_interhunk_context,\n+\t\t};\n \t\tconst char *rev = new_branch_info->name;\n \t\tchar rev_oid[GIT_MAX_HEXSZ + 1];\n \n@@ -564,8 +575,8 @@ static int checkout_paths(const struct checkout_opts *opts,\n \t\telse\n \t\t\tBUG(\"either flag must have been set, worktree=%d, index=%d\",\n \t\t\t    opts->checkout_worktree, opts->checkout_index);\n-\t\treturn !!run_add_p(the_repository, patch_mode, rev,\n-\t\t\t\t   &opts->pathspec);\n+\t\treturn !!run_add_p(the_repository, patch_mode, &add_p_opt,\n+\t\t\t\t   rev, &opts->pathspec);\n \t}\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n@@ -1738,6 +1749,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,\n \t\t\t      N_(\"checkout their version for unmerged files\"),\n \t\t\t      3, PARSE_OPT_NONEG),\n \t\tOPT_BOOL('p', \"patch\", &opts->patch_mode, N_(\"select hunks interactively\")),\n+\t\tOPT_DIFF_UNIFIED(&opts->patch_context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context),\n \t\tOPT_BOOL(0, \"ignore-skip-worktree-bits\", &opts->ignore_skipworktree,\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n \t\tOPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),\n@@ -1780,6 +1793,18 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     usagestr, parseopt_flags);\n \n+\tif (opts->patch_context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (opts->patch_interhunk_context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n+\tif (!opts->patch_mode) {\n+\t\tif (opts->patch_context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (opts->patch_interhunk_context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n \tif (opts->show_progress < 0) {\n \t\tif (opts->quiet)\n \t\t\topts->show_progress = 0;\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex fba0dded64a7..73673bc7db9f 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -19,6 +19,7 @@\n #include \"environment.h\"\n #include \"diff.h\"\n #include \"commit.h\"\n+#include \"add-interactive.h\"\n #include \"gettext.h\"\n #include \"revision.h\"\n #include \"wt-status.h\"\n@@ -122,6 +123,7 @@ static const char *edit_message, *use_message;\n static char *fixup_message, *fixup_commit, *squash_message;\n static const char *fixup_prefix;\n static int all, also, interactive, patch_interactive, only, amend, signoff;\n+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n static int edit_flag = -1; /* unspecified */\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int config_commit_verbose = -1; /* unspecified */\n@@ -354,6 +356,11 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \tconst char *ret;\n \tchar *path = NULL;\n \n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tif (is_status)\n \t\trefresh_flags |= REFRESH_UNMERGED;\n \tparse_pathspec(&pathspec, 0,\n@@ -400,7 +407,7 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n \t\tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n \n-\t\tif (interactive_add(the_repository, argv, prefix, patch_interactive) != 0)\n+\t\tif (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0)\n \t\t\tdie(_(\"interactive add failed\"));\n \n \t\tthe_repository->index_file = old_repo_index_file;\n@@ -424,6 +431,11 @@ static const char *prepare_index(const char **argv, const char *prefix,\n \t\tcommit_style = COMMIT_NORMAL;\n \t\tret = get_lock_file_path(&index_lock);\n \t\tgoto out;\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--interactive/--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--interactive/--patch\");\n \t}\n \n \t/*\n@@ -1722,6 +1734,8 @@ int cmd_commit(int argc,\n \t\tOPT_BOOL('i', \"include\", &also, N_(\"add specified files to index for commit\")),\n \t\tOPT_BOOL(0, \"interactive\", &interactive, N_(\"interactively add files\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_interactive, N_(\"interactively add changes\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT_BOOL('o', \"only\", &only, N_(\"commit only specified files\")),\n \t\tOPT_BOOL('n', \"no-verify\", &no_verify, N_(\"bypass pre-commit and commit-msg hooks\")),\n \t\tOPT_BOOL(0, \"dry-run\", &dry_run, N_(\"show what would be committed\")),\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex dc50ffc1ac59..9fb32795c9c5 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -346,6 +346,7 @@ int cmd_reset(int argc,\n \tstruct object_id oid;\n \tstruct pathspec pathspec;\n \tint intent_to_add = 0;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tconst struct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"no-refresh\", &no_refresh,\n@@ -370,6 +371,8 @@ int cmd_reset(int argc,\n \t\t\t       PARSE_OPT_OPTARG,\n \t\t\t       option_parse_recurse_submodules_worktree_updater),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode, N_(\"select hunks interactively\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT_BOOL('N', \"intent-to-add\", &intent_to_add,\n \t\t\t\tN_(\"record only the fact that removed paths will be added later\")),\n \t\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n@@ -420,6 +423,11 @@ int cmd_reset(int argc,\n \t\toidcpy(&oid, &tree->object.oid);\n \t}\n \n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \n@@ -427,9 +435,14 @@ int cmd_reset(int argc,\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--patch\", \"--{hard,mixed,soft}\");\n \t\ttrace2_cmd_mode(\"patch-interactive\");\n-\t\tupdate_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev,\n-\t\t\t\t   &pathspec);\n+\t\tupdate_ref_status = !!run_add_p(the_repository, ADD_P_RESET,\n+\t\t\t\t\t\t&add_p_opt, rev, &pathspec);\n \t\tgoto cleanup;\n+\t} else {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n \t}\n \n \t/* git reset tree [--] paths... can be used to\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 7cd3ad8aa48e..6da162e5e69a 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1242,7 +1242,8 @@ done:\n }\n \n static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n-\t\t       struct strbuf *out_patch, int quiet)\n+\t\t       struct strbuf *out_patch, int quiet,\n+\t\t       struct add_p_opt *add_p_opt)\n {\n \tint ret = 0;\n \tstruct child_process cp_read_tree = CHILD_PROCESS_INIT;\n@@ -1267,7 +1268,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n \told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n \tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n \n-\tret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps);\n+\tret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps);\n \n \tthe_repository->index_file = old_repo_index_file;\n \tif (old_index_env && *old_index_env)\n@@ -1362,8 +1363,8 @@ done:\n }\n \n static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,\n-\t\t\t   int include_untracked, int patch_mode, int only_staged,\n-\t\t\t   struct stash_info *info, struct strbuf *patch,\n+\t\t\t   int include_untracked, int patch_mode, struct add_p_opt *add_p_opt,\n+\t\t\t   int only_staged, struct stash_info *info, struct strbuf *patch,\n \t\t\t   int quiet)\n {\n \tint ret = 0;\n@@ -1444,7 +1445,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\tuntracked_commit_option = 1;\n \t}\n \tif (patch_mode) {\n-\t\tret = stash_patch(info, ps, patch, quiet);\n+\t\tret = stash_patch(info, ps, patch, quiet, add_p_opt);\n \t\tif (ret < 0) {\n \t\t\tif (!quiet)\n \t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n@@ -1519,7 +1520,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n \tif (!check_changes_tracked_files(&ps))\n \t\treturn 0;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n+\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,\n \t\t\t      NULL, 0);\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n@@ -1530,7 +1531,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n }\n \n static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,\n-\t\t\t int keep_index, int patch_mode, int include_untracked, int only_staged)\n+\t\t\t int keep_index, int patch_mode, struct add_p_opt *add_p_opt,\n+\t\t\t int include_untracked, int only_staged)\n {\n \tint ret = 0;\n \tstruct stash_info info = STASH_INFO_INIT;\n@@ -1600,8 +1602,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \n \tif (stash_msg)\n \t\tstrbuf_addstr(&stash_msg_buf, stash_msg);\n-\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,\n-\t\t\t    &info, &patch, quiet)) {\n+\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,\n+\t\t\t    add_p_opt, only_staged, &info, &patch, quiet)) {\n \t\tret = -1;\n \t\tgoto done;\n \t}\n@@ -1774,6 +1776,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \tconst char *stash_msg = NULL;\n \tchar *pathspec_from_file = NULL;\n \tstruct pathspec ps;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n@@ -1781,6 +1784,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n \t\t\t N_(\"include untracked files in stash\")),\n@@ -1836,8 +1841,20 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--pathspec-file-nul\", \"--pathspec-from-file\");\n \t}\n \n+\tif (!patch_mode) {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,\n-\t\t\t    include_untracked, only_staged);\n+\t\t\t    &add_p_opt, include_untracked, only_staged);\n \n \tclear_pathspec(&ps);\n \tfree(pathspec_from_file);\n@@ -1862,6 +1879,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \tconst char *stash_msg = NULL;\n \tstruct pathspec ps;\n \tstruct strbuf stash_msg_buf = STRBUF_INIT;\n+\tstruct add_p_opt add_p_opt = ADD_P_OPT_INIT;\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n@@ -1869,6 +1887,8 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n+\t\tOPT_DIFF_UNIFIED(&add_p_opt.context),\n+\t\tOPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n \t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n \t\t\t N_(\"include untracked files in stash\")),\n@@ -1887,8 +1907,22 @@ static int save_stash(int argc, const char **argv, const char *prefix,\n \t\tstash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');\n \n \tmemset(&ps, 0, sizeof(ps));\n+\n+\tif (add_p_opt.context < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--unified\");\n+\tif (add_p_opt.interhunkcontext < -1)\n+\t\tdie(_(\"'%s' cannot be negative\"), \"--inter-hunk-context\");\n+\n+\tif (!patch_mode) {\n+\t\tif (add_p_opt.context != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--unified\", \"--patch\");\n+\t\tif (add_p_opt.interhunkcontext != -1)\n+\t\t\tdie(_(\"the option '%s' requires '%s'\"), \"--inter-hunk-context\", \"--patch\");\n+\t}\n+\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index,\n-\t\t\t    patch_mode, include_untracked, only_staged);\n+\t\t\t    patch_mode, &add_p_opt, include_untracked,\n+\t\t\t    only_staged);\n \n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\ndiff --git a/commit.h b/commit.h\nindex 70c870dae4d4..7a7fedbc2f14 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -2,6 +2,7 @@\n #define COMMIT_H\n \n #include \"object.h\"\n+#include \"add-interactive.h\"\n \n struct signature_check;\n struct strbuf;\n@@ -257,7 +258,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *);\n int interactive_add(struct repository *repo,\n \t\t    const char **argv,\n \t\t    const char *prefix,\n-\t\t    int patch);\n+\t\t    int patch, struct add_p_opt *add_p_opt);\n \n struct commit_extra_header {\n \tstruct commit_extra_header *next;\ndiff --git a/parse-options.h b/parse-options.h\nindex 91c3e3c29b3d..bdae8f116198 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -616,6 +616,8 @@ int parse_opt_tracking_mode(const struct option *, const char *, int);\n #define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, \"pathspec-from-file\", v, N_(\"read pathspec from file\"))\n #define OPT_PATHSPEC_FILE_NUL(v)  OPT_BOOL(0, \"pathspec-file-nul\", v, N_(\"with --pathspec-from-file, pathspec elements are separated with NUL character\"))\n #define OPT_AUTOSTASH(v) OPT_BOOL(0, \"autostash\", v, N_(\"automatically stash/stash pop before and after\"))\n+#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', \"unified\", v, N_(\"generate diffs with <n> lines context\"), PARSE_OPT_NONEG)\n+#define OPT_DIFF_INTERHUNK_CONTEXT(v) OPT_INTEGER_F(0, \"inter-hunk-context\", v, N_(\"show context between diff hunks up to the specified number of lines\"), PARSE_OPT_NONEG)\n \n #define OPT_IPVERSION(v) \\\n \tOPT_SET_INT_F('4', \"ipv4\", (v), N_(\"use IPv4 addresses only\"), \\\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex 18dc329ea1f6..04d2a1983525 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -1252,4 +1252,53 @@ test_expect_success 'add -p rejects negative diff.context' '\n \ttest_grep \"diff.context cannot be negative\" output\n '\n \n+for cmd in add checkout restore 'commit -m file'\n+do\n+\ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" '\n+\t\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n+\t\tgit add file &&\n+\t\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n+\t\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n+\t\t\t$cmd -p -U 4 --inter-hunk-context 2 >actual &&\n+\t\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n+\t'\n+done\n+\n+test_expect_success 'reset accepts -U and --inter-hunk-context' '\n+\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n+\tgit commit -m file file &&\n+\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n+\tgit add file &&\n+\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n+\t\treset -p -U 4 --inter-hunk-context 2 >actual &&\n+\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n+'\n+\n+test_expect_success 'stash accepts -U and --inter-hunk-context' '\n+\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n+\tgit commit -m file file &&\n+\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n+\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n+\t\tstash -p -U 4 --inter-hunk-context 2 >actual &&\n+\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n+'\n+\n+for cmd in add checkout commit reset restore \"stash save\" \"stash push\"\n+do\n+\ttest_expect_success \"$cmd rejects invalid context options\" '\n+\t\ttest_must_fail git $cmd -p -U -3 2>actual &&\n+\t\tcat actual | echo &&\n+\t\ttest_grep -e \".--unified. cannot be negative\" actual &&\n+\n+\t\ttest_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&\n+\t\ttest_grep -e \".--inter-hunk-context. cannot be negative\" actual &&\n+\n+\t\ttest_must_fail git $cmd -U 7 2>actual &&\n+\t\ttest_grep -E \".--unified. requires .(--interactive/)?--patch.\" actual &&\n+\n+\t\ttest_must_fail git $cmd --inter-hunk-context 2 2>actual &&\n+\t\ttest_grep -E \".--inter-hunk-context. requires .(--interactive/)?--patch.\" actual\n+\t'\n+done\n+\n test_done\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 343b8cd1912b..6650d33fba69 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2596,6 +2596,8 @@ test_expect_success 'double dash \"git checkout\"' '\n \t--merge Z\n \t--conflict=Z\n \t--patch Z\n+\t--unified=Z\n+\t--inter-hunk-context=Z\n \t--ignore-skip-worktree-bits Z\n \t--ignore-other-worktrees Z\n \t--recurse-submodules Z\n-- \ngitgitgadget\n"},{"id":"522965","messageId":"ec7481b5-5244-4a76-962f-d3bed9e4172a@gmail.com","threadId":"63396","inReplyTo":"pull.1915.v5.git.1753772511.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 0/4] Better support for customising context lines in --patch commands","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-29T15:21:03Z","receivedAt":"2025-07-29T15:21:09Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Leon\n\nThe range-diff below looks good to me. Thanks for working on this\n\nPhillip\n\nOn 29/07/2025 08:01, Leon Michalak via GitGitGadget wrote:\n> This series of patches attempt to give --interactive/--patch compatible\n> builtins (\"add\", \"commit\", \"checkout\", \"reset\", \"restore\" and \"stash\")\n> better support and nicer experience for configuring how many context lines\n> are shown in diffs through a variety of ways.\n> \n> Prior to these patches, the user could not choose how many context lines\n> they saw in --patch commands (apart from one workaround by using\n> GIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a\n> persistent solution). Additionally, the behaviour around reading from the\n> diff.context and diff.interHunkContext configs was also inconsistent with\n> other diff generating commands such as \"log -p\".\n> \n> The summarised changes below hopefully make this experience better and fix\n> some inconsistencies:\n> \n>   * diff.context and diff.interHunkContext configs are now respected by\n>     --patch compatible commands\n>   * --unified and --inter-hunk-context command line options have been added\n>     to --patch compatible commands (which take prescendence over file\n>     configs)\n>   * \"add\" and \"commit\" in --interactive mode now expose a new \"context\"\n>     subcommand which configures the amount of context lines you wish to see\n>     in subsequent diffs generated from other subcommands such as \"patch\" or\n>     \"diff\"\n> \n> The original discussion for this can be read at:\n> \n>   * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/\n> \n> Changes since v1:\n> \n>   * Update commit descriptions\n>   * Update tests to use the more modern and robust test_grep and test_config\n>     utils\n>   * Reword some documentation / user messages\n>   * Ensure each commit is atomic and builds/passes tests on it's own\n>   * Make new command line options DRY\n>   * Add tests for interhunk context interaction\n>   * Error if context config/command line options are negative\n>   * Drop previous last commit to do with new subcommand for --interactive\n>     add/commit. My motivations behind this patch series originally where\n>     quite simple, just for add-patch commands to respect context configs.\n>     This subcommand, after the discussion in v1, will require more thought\n>     and a larger implementation that what I had anticipated. I would prefer\n>     to leave this for another time as it's the least impactful but the most\n>     time intensive and complicated idea.\n> \n> Changes since v2:\n> \n>   * Update tests to only test single command (following Philip's suggestion)\n>   * Add negative option checks\n>   * Minor commit re-wording\n> \n> Changes since v3:\n> \n>   * Update commit descriptions\n>   * Read struct properties directly instead of assigning to variables first\n>   * Simplify config setting / error checking\n>   * Remove redundant tests in later commit as they were replaced with better\n>     test(s)\n>   * Change tests to use single quotes (this messes with the grep so was\n>     unable to explicitly test single quotes in the error messages, so decided\n>     to use regex . instead, which is what some other tests that have this\n>     problem seem to use as well)\n> \n> Changes since v4:\n> \n>   * Add back tests to maintain good coverage and remove redundant tests\n> \n> Leon Michalak (4):\n>    t: use test_grep in t3701 and t4055\n>    t: use test_config in t4055\n>    add-patch: respect diff.context configuration\n>    add-patch: add diff.context command line overrides\n> \n>   Documentation/diff-context-options.adoc |  10 ++\n>   Documentation/git-add.adoc              |   2 +\n>   Documentation/git-checkout.adoc         |   2 +\n>   Documentation/git-commit.adoc           |   2 +\n>   Documentation/git-reset.adoc            |   2 +\n>   Documentation/git-restore.adoc          |   2 +\n>   Documentation/git-stash.adoc            |   2 +\n>   add-interactive.c                       |  45 +++++++--\n>   add-interactive.h                       |  17 +++-\n>   add-patch.c                             |  14 ++-\n>   builtin/add.c                           |  21 ++++-\n>   builtin/checkout.c                      |  31 +++++-\n>   builtin/commit.c                        |  16 +++-\n>   builtin/reset.c                         |  17 +++-\n>   builtin/stash.c                         |  56 ++++++++---\n>   commit.h                                |   3 +-\n>   parse-options.h                         |   2 +\n>   t/t3701-add-interactive.sh              | 119 +++++++++++++++++++-----\n>   t/t4055-diff-context.sh                 |  42 ++++-----\n>   t/t9902-completion.sh                   |   2 +\n>   20 files changed, 324 insertions(+), 83 deletions(-)\n>   create mode 100644 Documentation/diff-context-options.adoc\n> \n> \n> base-commit: cf6f63ea6bf35173e02e18bdc6a4ba41288acff9\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v5\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v5\n> Pull-Request: https://github.com/gitgitgadget/git/pull/1915\n> \n> Range-diff vs v4:\n> \n>   1:  bbb2bc7082b = 1:  bbb2bc7082b t: use test_grep in t3701 and t4055\n>   2:  feace2d3676 = 2:  feace2d3676 t: use test_config in t4055\n>   3:  994029d6602 = 3:  994029d6602 add-patch: respect diff.context configuration\n>   4:  2774b930406 ! 4:  9731e5b76fb add-patch: add diff.context command line overrides\n>       @@ parse-options.h: int parse_opt_tracking_mode(const struct option *, const char *\n>         \tOPT_SET_INT_F('4', \"ipv4\", (v), N_(\"use IPv4 addresses only\"), \\\n>        \n>         ## t/t3701-add-interactive.sh ##\n>       -@@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '\n>       - \ttest_cmp expect actual\n>       +@@ t/t3701-add-interactive.sh: test_expect_success 'add -p rejects negative diff.context' '\n>       + \ttest_grep \"diff.context cannot be negative\" output\n>         '\n>         \n>       --test_expect_success 'add -p respects diff.context' '\n>       --\ttest_write_lines a b c d e f g h i j k l m >file &&\n>        +for cmd in add checkout restore 'commit -m file'\n>        +do\n>        +\ttest_expect_success \"${cmd%% *} accepts -U and --inter-hunk-context\" '\n>       @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.\n>        +\ttest_write_lines a b c d e f g h i j k l m n o p q r s t u v >file &&\n>        +\tgit commit -m file file &&\n>        +\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n>       - \tgit add file &&\n>       --\ttest_write_lines a b c d e f G h i j k l m >file &&\n>       --\techo y | git -c diff.context=5 add -p >actual &&\n>       --\ttest_grep \"@@ -2,11 +2,11 @@\" actual\n>       ++\tgit add file &&\n>        +\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n>        +\t\treset -p -U 4 --inter-hunk-context 2 >actual &&\n>        +\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n>       - '\n>       -\n>       --test_expect_success 'add -p respects diff.interHunkContext' '\n>       --\ttest_write_lines a b c d e f g h i j k l m n o p q r s >file &&\n>       --\tgit add file &&\n>       --\ttest_write_lines a b c d E f g i i j k l m N o p q r s >file &&\n>       --\techo y | git -c diff.interhunkcontext=2 add -p >actual &&\n>       --\ttest_grep \"@@ -2,16 +2,16 @@\" actual\n>       ++'\n>       ++\n>        +test_expect_success 'stash accepts -U and --inter-hunk-context' '\n>        +\ttest_write_lines a b c d e F g h i j k l m n o p Q r s t u v >file &&\n>        +\tgit commit -m file file &&\n>       @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.\n>        +\techo y | git -c diff.context=5 -c diff.interhunkcontext=1 \\\n>        +\t\tstash -p -U 4 --inter-hunk-context 2 >actual &&\n>        +\ttest_grep \"@@ -2,20 +2,20 @@\" actual\n>       - '\n>       -\n>       --test_expect_success 'add -p rejects negative diff.context' '\n>       --\ttest_config diff.context -1 &&\n>       --\ttest_must_fail git add -p 2>output &&\n>       --\ttest_grep \"diff.context cannot be negative\" output\n>       --'\n>       ++'\n>       ++\n>        +for cmd in add checkout commit reset restore \"stash save\" \"stash push\"\n>        +do\n>        +\ttest_expect_success \"$cmd rejects invalid context options\" '\n>       @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.\n>        +\t\ttest_grep -E \".--inter-hunk-context. requires .(--interactive/)?--patch.\" actual\n>        +\t'\n>        +done\n>       -\n>       - test_done\n>       -\n>       - ## t/t4055-diff-context.sh ##\n>       -@@ t/t4055-diff-context.sh: test_expect_success 'The -U option overrides diff.context' '\n>       - \ttest_grep ! \"^ firstline\" output\n>       - '\n>       -\n>       -+test_expect_success 'The -U option overrides diff.context for \"add\"' '\n>       -+\ttest_config diff.context 8 &&\n>       -+\tgit add -U4 -p >output &&\n>       -+\ttest_grep ! \"^ firstline\" output\n>       -+'\n>       -+\n>       -+test_expect_success 'The -U option overrides diff.context for \"commit\"' '\n>       -+\ttest_config diff.context 8 &&\n>       -+\t! git commit -U4 -p >output &&\n>       -+\ttest_grep ! \"^ firstline\" output\n>       -+'\n>       -+\n>       -+test_expect_success 'The -U option overrides diff.context for \"checkout\"' '\n>       -+\ttest_config diff.context 8 &&\n>       -+\tgit checkout -U4 -p >output &&\n>       -+\ttest_grep ! \"^ firstline\" output\n>       -+'\n>        +\n>       -+test_expect_success 'The -U option overrides diff.context for \"stash\"' '\n>       -+\ttest_config diff.context 8 &&\n>       -+\t! git stash -U4 -p >output &&\n>       -+\ttest_grep ! \"^ firstline\" output\n>       -+'\n>       -+\n>       -+test_expect_success 'The -U option overrides diff.context for \"restore\"' '\n>       -+\ttest_config diff.context 8 &&\n>       -+\tgit restore -U4 -p >output &&\n>       -+\ttest_grep ! \"^ firstline\" output\n>       -+'\n>       -+\n>       - test_expect_success 'diff.context honored by \"diff\"' '\n>       - \ttest_config diff.context 8 &&\n>       - \tgit diff >output &&\n>       + test_done\n>        \n>         ## t/t9902-completion.sh ##\n>        @@ t/t9902-completion.sh: test_expect_success 'double dash \"git checkout\"' '\n> \n\n"},{"id":"522967","messageId":"xmqqy0s7ouug.fsf@gitster.g","threadId":"63396","inReplyTo":"ec7481b5-5244-4a76-962f-d3bed9e4172a@gmail.com","subject":"Re: [PATCH v5 0/4] Better support for customising context lines in --patch commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-29T15:55:03Z","receivedAt":"2025-07-29T15:55:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Leon\n>\n> The range-diff below looks good to me. Thanks for working on this\n>\n> Phillip\n\nYeah, the new one matches the previous one plus your fixes to the\ntests, which I agree with fully.\n\nWill queue.  Let's mark the topic for 'next'.\n\nThanks, both of you.\n\n"},{"id":"522970","messageId":"CAP9jKjHyfr-E44fWkrDE_zAWKD7OS45tOf1DgGdBzDoxEty51Q@mail.gmail.com","threadId":"63396","inReplyTo":"xmqqy0s7ouug.fsf@gitster.g","subject":"Re: [PATCH v5 0/4] Better support for customising context lines in --patch commands","fromName":"Leon Michalak","fromEmail":"leonmichalak6@gmail.com","sentAt":"2025-07-29T16:18:22Z","receivedAt":"2025-07-29T16:18:36Z","isPatch":true,"sender":{"key":"leonmichalak6@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63664402?v=4"},"body":"Great! Thanks Philip, Junio and everyone else who helped me get this\nfinished and in.\n\nWas a good experience :)\n"}]}