git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] Better support for customising context lines in --patch commands
@ 2025-05-05  9:18 Leon Michalak via GitGitGadget
  2025-05-05  9:18 ` [PATCH 1/3] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
                   ` (3 more replies)
  0 siblings, 4 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-05-05  9:18 UTC (permalink / raw)
  To: git; +Cc: Leon Michalak

This series of patches attempt to give --interactive/--patch compatible
builtins ("add", "commit", "checkout", "reset", "restore" and "stash")
better support and nicer experience for configuring how many context lines
are shown in diffs through a variety of ways.

Prior to these patches, the user could not choose how many context lines
they saw in --patch commands (apart from one workaround by using
GIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a
persistent solution). Additionally, the behaviour around reading from the
diff.context and diff.interHunkContext configs was also inconsistent with
other diff generating commands such as "log -p".

The summarised changes below hopefully make this experience better and fix
some inconsistencies:

 * diff.context and diff.interHunkContext configs are now respected by
   --patch compatible commands
 * --unified and --inter-hunk-context command line options have been added
   to --patch compatible commands (which take prescendence over file
   configs)
 * "add" and "commit" in --interactive mode now expose a new "context"
   subcommand which configures the amount of context lines you wish to see
   in subsequent diffs generated from other subcommands such as "patch" or
   "diff"

The original discussion for this can be read at:

 * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/

Leon Michalak (3):
  add-patch: respect diff.context configuration
  add-patch: add diff.context command line overrides
  add-interactive: add new "context" subcommand

 Documentation/git-add.adoc      |  21 ++++++-
 Documentation/git-checkout.adoc |  11 ++++
 Documentation/git-commit.adoc   |  11 ++++
 Documentation/git-reset.adoc    |  11 ++++
 Documentation/git-restore.adoc  |  11 ++++
 Documentation/git-stash.adoc    |  11 ++++
 add-interactive.c               | 107 +++++++++++++++++++++++++++++---
 add-interactive.h               |  17 ++++-
 add-patch.c                     |  11 +++-
 builtin/add.c                   |  21 +++++--
 builtin/checkout.c              |  28 ++++++++-
 builtin/commit.c                |  15 ++++-
 builtin/reset.c                 |  16 ++++-
 builtin/stash.c                 |  54 ++++++++++++----
 commit.h                        |   3 +-
 t/t3701-add-interactive.sh      |  36 +++++++++--
 t/t4055-diff-context.sh         |  78 ++++++++++++++++++++++-
 t/t9902-completion.sh           |   2 +
 18 files changed, 420 insertions(+), 44 deletions(-)


base-commit: f65182a99e545d2f2bc22e6c1c2da192133b16a3
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1915
-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 77+ messages in thread

* [PATCH 1/3] add-patch: respect diff.context configuration
  2025-05-05  9:18 [PATCH 0/3] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
@ 2025-05-05  9:18 ` Leon Michalak via GitGitGadget
  2025-05-05 20:29   ` Eric Sunshine
                     ` (2 more replies)
  2025-05-05  9:18 ` [PATCH 2/3] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
                   ` (2 subsequent siblings)
  3 siblings, 3 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-05-05  9:18 UTC (permalink / raw)
  To: git; +Cc: Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

This aims to teach relevant builtins (that take in `--patch`) to respect
the user's diff.context and diff.interHunkContext file configurations.

Since these are both UI options and `--patch` is designed for the end user,
I believe this was previously just an inconsistency, which this patch hopes
to address.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 add-interactive.c       | 16 ++++++++++----
 add-patch.c             |  6 ++++++
 t/t4055-diff-context.sh | 48 ++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 65 insertions(+), 5 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 97ff35b6f12a..ad12dc416598 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -41,6 +41,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	const char *value;
 
 	s->r = r;
+	s->context = -1;
+	s->interhunkcontext = -1;
 
 	if (repo_config_get_value(r, "color.interactive", &value))
 		s->use_color = -1;
@@ -78,6 +80,9 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	repo_config_get_string(r, "diff.algorithm",
 			       &s->interactive_diff_algorithm);
 
+	repo_config_get_int(r, "diff.context", &s->context);
+	repo_config_get_int(r, "diff.interHunkContext", &s->interhunkcontext);
+
 	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
 	if (s->use_single_key)
 		setbuf(stdin, NULL);
@@ -1014,10 +1019,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
 	if (count > 0) {
 		struct child_process cmd = CHILD_PROCESS_INIT;
 
-		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached",
-			     oid_to_hex(!is_initial ? &oid :
-					s->r->hash_algo->empty_tree),
-			     "--", NULL);
+		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL);
+		if (s->context != -1)
+			strvec_pushf(&cmd.args, "--unified=%i", s->context);
+		if (s->interhunkcontext != -1)
+			strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->interhunkcontext);
+		strvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :
+			     s->r->hash_algo->empty_tree), "--", NULL);
 		for (i = 0; i < files->items.nr; i++)
 			if (files->selected[i])
 				strvec_push(&cmd.args,
diff --git a/add-patch.c b/add-patch.c
index 95c67d8c80c4..b43ca1600738 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 {
 	struct strvec args = STRVEC_INIT;
 	const char *diff_algorithm = s->s.interactive_diff_algorithm;
+	int diff_context = s->s.context;
+	int diff_interhunkcontext = s->s.interhunkcontext;
 	struct strbuf *plain = &s->plain, *colored = NULL;
 	struct child_process cp = CHILD_PROCESS_INIT;
 	char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
@@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 	int res;
 
 	strvec_pushv(&args, s->mode->diff_cmd);
+	if (diff_context != -1)
+		strvec_pushf(&args, "--unified=%i", diff_context);
+	if (diff_interhunkcontext != -1)
+		strvec_pushf(&args, "--inter-hunk-context=%i", diff_interhunkcontext);
 	if (diff_algorithm)
 		strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
 	if (s->revision) {
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index ec2804eea67c..9c024200ade7 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -49,7 +49,53 @@ test_expect_success 'diff.context honored by "log"' '
 	! grep firstline output &&
 	git config diff.context 8 &&
 	git log -1 -p >output &&
-	grep "^ firstline" output
+	grep "^ firstline" output &&
+	git config --unset diff.context
+'
+
+test_expect_success 'diff.context honored by "add"' '
+	git add -p >output &&
+	! grep firstline output &&
+	git config diff.context 8 &&
+	git add -p >output &&
+	grep "^ firstline" output &&
+	git config --unset diff.context
+'
+
+test_expect_success 'diff.context honored by "commit"' '
+	! git commit -p >output &&
+	! grep firstline output &&
+	git config diff.context 8 &&
+	! git commit -p >output &&
+	grep "^ firstline" output &&
+	git config --unset diff.context
+'
+
+test_expect_success 'diff.context honored by "checkout"' '
+	git checkout -p >output &&
+	! grep firstline output &&
+	git config diff.context 8 &&
+	git checkout -p >output &&
+	grep "^ firstline" output &&
+	git config --unset diff.context
+'
+
+test_expect_success 'diff.context honored by "stash"' '
+	! git stash -p >output &&
+	! grep firstline output &&
+	git config diff.context 8 &&
+	! git stash -p >output &&
+	grep "^ firstline" output &&
+	git config --unset diff.context
+'
+
+test_expect_success 'diff.context honored by "restore"' '
+	git restore -p >output &&
+	! grep firstline output &&
+	git config diff.context 8 &&
+	git restore -p >output &&
+	grep "^ firstline" output &&
+	git config --unset diff.context
 '
 
 test_expect_success 'The -U option overrides diff.context' '
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH 2/3] add-patch: add diff.context command line overrides
  2025-05-05  9:18 [PATCH 0/3] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
  2025-05-05  9:18 ` [PATCH 1/3] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
@ 2025-05-05  9:18 ` Leon Michalak via GitGitGadget
  2025-05-05  9:49   ` Kristoffer Haugsbakk
  2025-05-07  9:51   ` Phillip Wood
  2025-05-05  9:18 ` [PATCH 3/3] add-interactive: add new "context" subcommand Leon Michalak via GitGitGadget
  2025-05-10 13:46 ` [PATCH v2 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
  3 siblings, 2 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-05-05  9:18 UTC (permalink / raw)
  To: git; +Cc: Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

This patch compliments `8b91eef812`, where builtins that accept
`--patch` options now respect `diff.context` and `diff.interHunkContext`
file configurations.

In particular, this patch helps users who don't want to set persistent
context configurations or just want a way to override them on a one-time
basis, by allowing the relevant builtins to accept corresponding command
line options that override the file configurations.

This mimics `diff` which allows for both context file configuration and
command line overrides.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 Documentation/git-add.adoc      | 11 +++++++
 Documentation/git-checkout.adoc | 11 +++++++
 Documentation/git-commit.adoc   | 11 +++++++
 Documentation/git-reset.adoc    | 11 +++++++
 Documentation/git-restore.adoc  | 11 +++++++
 Documentation/git-stash.adoc    | 11 +++++++
 add-interactive.c               | 19 +++++++++---
 add-interactive.h               | 17 +++++++++--
 add-patch.c                     |  5 +--
 builtin/add.c                   | 21 ++++++++++---
 builtin/checkout.c              | 28 +++++++++++++++--
 builtin/commit.c                | 15 ++++++++-
 builtin/reset.c                 | 16 ++++++++--
 builtin/stash.c                 | 54 ++++++++++++++++++++++++++-------
 commit.h                        |  3 +-
 t/t4055-diff-context.sh         | 30 ++++++++++++++++++
 t/t9902-completion.sh           |  2 ++
 17 files changed, 245 insertions(+), 31 deletions(-)

diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index eba0b419ce50..e3a50706acd5 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -104,6 +104,17 @@ This effectively runs `add --interactive`, but bypasses the
 initial command menu and directly jumps to the `patch` subcommand.
 See ``Interactive mode'' for details.
 
+`-U<n>`::
+`--unified=<n>`::
+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
+	or 3 if the config option is unset. Implies `--interactive/--patch`.
+
+`--inter-hunk-context=<n>`::
+	Show the context between diff hunks, up to the specified _<number>_
+	of lines, thereby fusing hunks that are close to each other.
+	Defaults to `diff.interHunkContext` or 0 if the config option
+	is unset. Implies `--interactive/--patch`.
+
 `-e`::
 `--edit`::
 	Open the diff vs. the index in an editor and let the user
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index a66c53a5cd1e..46286a6c8c9e 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -289,6 +289,17 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 Note that this option uses the no overlay mode by default (see also
 `--overlay`), and currently doesn't support overlay mode.
 
+`-U<n>`::
+`--unified=<n>`::
+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
+	or 3 if the config option is unset. Implies `--patch`.
+
+`--inter-hunk-context=<n>`::
+	Show the context between diff hunks, up to the specified _<number>_
+	of lines, thereby fusing hunks that are close to each other.
+	Defaults to `diff.interHunkContext` or 0 if the config option
+	is unset. Implies `--patch`.
+
 --ignore-other-worktrees::
 	`git checkout` refuses when the wanted branch is already checked
 	out or otherwise in use by another worktree. This option makes
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc
index dc219025f1eb..c0b206aee9c9 100644
--- a/Documentation/git-commit.adoc
+++ b/Documentation/git-commit.adoc
@@ -76,6 +76,17 @@ OPTIONS
 	which changes to commit. See linkgit:git-add[1] for
 	details.
 
+`-U<n>`::
+`--unified=<n>`::
+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
+	or 3 if the config option is unset. Implies `--interactive/--patch`.
+
+`--inter-hunk-context=<n>`::
+	Show the context between diff hunks, up to the specified _<number>_
+	of lines, thereby fusing hunks that are close to each other.
+	Defaults to `diff.interHunkContext` or 0 if the config option
+	is unset. Implies `--interactive/--patch`.
+
 `-C <commit>`::
 `--reuse-message=<commit>`::
 	Take an existing _<commit>_ object, and reuse the log message
diff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc
index 53ab88c5451c..6fcc86a3b960 100644
--- a/Documentation/git-reset.adoc
+++ b/Documentation/git-reset.adoc
@@ -125,6 +125,17 @@ OPTIONS
 	separated with _NUL_ character and all other characters are taken
 	literally (including newlines and quotes).
 
+`-U<n>`::
+`--unified=<n>`::
+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
+	or 3 if the config option is unset. Implies `--patch`.
+
+`--inter-hunk-context=<n>`::
+	Show the context between diff hunks, up to the specified _<number>_
+	of lines, thereby fusing hunks that are close to each other.
+	Defaults to `diff.interHunkContext` or 0 if the config option
+	is unset. Implies `--patch`.
+
 `--`::
 	Do not interpret any more arguments as options.
 
diff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc
index 877b7772e667..49d7e6b5a5c8 100644
--- a/Documentation/git-restore.adoc
+++ b/Documentation/git-restore.adoc
@@ -52,6 +52,17 @@ leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to
 	Mode" section of linkgit:git-add[1] to learn how to operate
 	the `--patch` mode.
 
+`-U<n>`::
+`--unified=<n>`::
+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
+	or 3 if the config option is unset. Implies `--patch`.
+
+`--inter-hunk-context=<n>`::
+	Show the context between diff hunks, up to the specified _<number>_
+	of lines, thereby fusing hunks that are close to each other.
+	Defaults to `diff.interHunkContext` or 0 if the config option
+	is unset. Implies `--patch`.
+
 `-W`::
 `--worktree`::
 `-S`::
diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc
index 1a5177f4986c..24916fa0723e 100644
--- a/Documentation/git-stash.adoc
+++ b/Documentation/git-stash.adoc
@@ -208,6 +208,17 @@ to learn how to operate the `--patch` mode.
 The `--patch` option implies `--keep-index`.  You can use
 `--no-keep-index` to override this.
 
+`-U<n>`::
+`--unified=<n>`::
+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
+	or 3 if the config option is unset. Implies `--patch`.
+
+`--inter-hunk-context=<n>`::
+	Show the context between diff hunks, up to the specified _<number>_
+	of lines, thereby fusing hunks that are close to each other.
+	Defaults to `diff.interHunkContext` or 0 if the config option
+	is unset. Implies `--patch`.
+
 -S::
 --staged::
 	This option is only valid for `push` and `save` commands.
diff --git a/add-interactive.c b/add-interactive.c
index ad12dc416598..1ea8eb711a60 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -36,7 +36,8 @@ static void init_color(struct repository *r, struct add_i_state *s,
 	free(key);
 }
 
-void init_add_i_state(struct add_i_state *s, struct repository *r)
+void init_add_i_state(struct add_i_state *s, struct repository *r,
+		      struct add_p_opt *add_p_opt)
 {
 	const char *value;
 
@@ -86,6 +87,11 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
 	if (s->use_single_key)
 		setbuf(stdin, NULL);
+
+	if (add_p_opt->context != -1)
+		s->context = add_p_opt->context;
+	if (add_p_opt->interhunkcontext != -1)
+		s->interhunkcontext = add_p_opt->interhunkcontext;
 }
 
 void clear_add_i_state(struct add_i_state *s)
@@ -974,6 +980,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 	opts->prompt = N_("Patch update");
 	count = list_and_choose(s, files, opts);
 	if (count > 0) {
+		struct add_p_opt add_p_opt = {
+			.context = s->context,
+			.interhunkcontext = s->interhunkcontext,
+		};
 		struct strvec args = STRVEC_INIT;
 		struct pathspec ps_selected = { 0 };
 
@@ -984,7 +994,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 		parse_pathspec(&ps_selected,
 			       PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
 			       PATHSPEC_LITERAL_PATH, "", args.v);
-		res = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);
+		res = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected);
 		strvec_clear(&args);
 		clear_pathspec(&ps_selected);
 	}
@@ -1118,7 +1128,8 @@ static void command_prompt_help(struct add_i_state *s)
 			 _("(empty) select nothing"));
 }
 
-int run_add_i(struct repository *r, const struct pathspec *ps)
+int run_add_i(struct repository *r, const struct pathspec *ps,
+	      struct add_p_opt *add_p_opt)
 {
 	struct add_i_state s = { NULL };
 	struct print_command_item_data data = { "[", "]" };
@@ -1161,7 +1172,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 			->util = util;
 	}
 
-	init_add_i_state(&s, r);
+	init_add_i_state(&s, r, add_p_opt);
 
 	/*
 	 * When color was asked for, use the prompt color for
diff --git a/add-interactive.h b/add-interactive.h
index 693f125e8e4b..653f07a917b8 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -3,6 +3,13 @@
 
 #include "color.h"
 
+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }
+
+struct add_p_opt {
+	int context;
+	int interhunkcontext;
+};
+
 struct add_i_state {
 	struct repository *r;
 	int use_color;
@@ -18,14 +25,17 @@ struct add_i_state {
 
 	int use_single_key;
 	char *interactive_diff_filter, *interactive_diff_algorithm;
+	int context, interhunkcontext;
 };
 
-void init_add_i_state(struct add_i_state *s, struct repository *r);
+void init_add_i_state(struct add_i_state *s, struct repository *r,
+		      struct add_p_opt *add_p_opt);
 void clear_add_i_state(struct add_i_state *s);
 
 struct repository;
 struct pathspec;
-int run_add_i(struct repository *r, const struct pathspec *ps);
+int run_add_i(struct repository *r, const struct pathspec *ps,
+	      struct add_p_opt *add_p_opt);
 
 enum add_p_mode {
 	ADD_P_ADD,
@@ -36,6 +46,7 @@ enum add_p_mode {
 };
 
 int run_add_p(struct repository *r, enum add_p_mode mode,
-	      const char *revision, const struct pathspec *ps);
+	      struct add_p_opt *o, const char *revision,
+	      const struct pathspec *ps);
 
 #endif
diff --git a/add-patch.c b/add-patch.c
index b43ca1600738..c0d33820c558 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -1766,14 +1766,15 @@ soft_increment:
 }
 
 int run_add_p(struct repository *r, enum add_p_mode mode,
-	      const char *revision, const struct pathspec *ps)
+	      struct add_p_opt *o, const char *revision,
+	      const struct pathspec *ps)
 {
 	struct add_p_state s = {
 		{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	size_t i, binary_count = 0;
 
-	init_add_i_state(&s.s, r);
+	init_add_i_state(&s.s, r, o);
 
 	if (mode == ADD_P_STASH)
 		s.mode = &patch_mode_stash;
diff --git a/builtin/add.c b/builtin/add.c
index 78dfb2657767..9b4e618b3fe2 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -29,6 +29,7 @@ static const char * const builtin_add_usage[] = {
 	NULL
 };
 static int patch_interactive, add_interactive, edit_interactive;
+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 static int take_worktree_changes;
 static int add_renormalize;
 static int pathspec_file_nul;
@@ -157,7 +158,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec *
 int interactive_add(struct repository *repo,
 		    const char **argv,
 		    const char *prefix,
-		    int patch)
+		    int patch, struct add_p_opt *add_p_opt)
 {
 	struct pathspec pathspec;
 	int ret;
@@ -169,9 +170,10 @@ int interactive_add(struct repository *repo,
 		       prefix, argv);
 
 	if (patch)
-		ret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);
+		ret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL,
+				  &pathspec);
 	else
-		ret = !!run_add_i(repo, &pathspec);
+		ret = !!run_add_i(repo, &pathspec, add_p_opt);
 
 	clear_pathspec(&pathspec);
 	return ret;
@@ -253,6 +255,12 @@ static struct option builtin_add_options[] = {
 	OPT_GROUP(""),
 	OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
 	OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
+	OPT_INTEGER_F('U', "unified", &add_p_opt.context,
+		      N_("generate diffs with <n> lines context, implies --interactive/--patch"),
+		      PARSE_OPT_NONEG),
+	OPT_INTEGER_F(0, "inter-hunk-context", &add_p_opt.interhunkcontext,
+		      N_("show context between diff hunks up to the specified number of lines, implies --interactive/--patch"),
+		      PARSE_OPT_NONEG),
 	OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
 	OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
 	OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
@@ -398,7 +406,12 @@ int cmd_add(int argc,
 			die(_("options '%s' and '%s' cannot be used together"), "--dry-run", "--interactive/--patch");
 		if (pathspec_from_file)
 			die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch");
-		exit(interactive_add(repo, argv + 1, prefix, patch_interactive));
+		exit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
 	}
 
 	if (edit_interactive) {
diff --git a/builtin/checkout.c b/builtin/checkout.c
index d185982f3a63..22bd7b1d2b3b 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -61,6 +61,8 @@ static const char * const restore_usage[] = {
 
 struct checkout_opts {
 	int patch_mode;
+	int patch_context;
+	int patch_interhunk_context;
 	int quiet;
 	int merge;
 	int force;
@@ -104,7 +106,12 @@ struct checkout_opts {
 	struct tree *source_tree;
 };
 
-#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 }
+#define CHECKOUT_OPTS_INIT { \
+	.conflict_style = -1, \
+	.merge = -1, \
+	.patch_context = -1, \
+	.patch_interhunk_context = -1, \
+}
 
 struct branch_info {
 	char *name; /* The short name used */
@@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts,
 
 	if (opts->patch_mode) {
 		enum add_p_mode patch_mode;
+		struct add_p_opt add_p_opt = {
+			.context = opts->patch_context,
+			.interhunkcontext = opts->patch_interhunk_context,
+		};
 		const char *rev = new_branch_info->name;
 		char rev_oid[GIT_MAX_HEXSZ + 1];
 
@@ -564,8 +575,13 @@ static int checkout_paths(const struct checkout_opts *opts,
 		else
 			BUG("either flag must have been set, worktree=%d, index=%d",
 			    opts->checkout_worktree, opts->checkout_index);
-		return !!run_add_p(the_repository, patch_mode, rev,
-				   &opts->pathspec);
+		return !!run_add_p(the_repository, patch_mode, &add_p_opt,
+				   rev, &opts->pathspec);
+	} else {
+		if (opts->patch_context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (opts->patch_interhunk_context != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
 	}
 
 	repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -1738,6 +1754,12 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
 		OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
+		OPT_INTEGER_F('U', "unified", &opts->patch_context,
+			N_("generate diffs with <n> lines context, implies --patch"),
+			PARSE_OPT_NONEG),
+		OPT_INTEGER_F(0, "inter-hunk-context", &opts->patch_interhunk_context,
+			N_("show context between diff hunks up to the specified number of lines, implies --patch"),
+			PARSE_OPT_NONEG),
 		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),
diff --git a/builtin/commit.c b/builtin/commit.c
index 66bd91fd523d..5831fd019717 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -19,6 +19,7 @@
 #include "environment.h"
 #include "diff.h"
 #include "commit.h"
+#include "add-interactive.h"
 #include "gettext.h"
 #include "revision.h"
 #include "wt-status.h"
@@ -122,6 +123,7 @@ static const char *edit_message, *use_message;
 static char *fixup_message, *fixup_commit, *squash_message;
 static const char *fixup_prefix;
 static int all, also, interactive, patch_interactive, only, amend, signoff;
+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
@@ -400,7 +402,7 @@ static const char *prepare_index(const char **argv, const char *prefix,
 		old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
 		setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
 
-		if (interactive_add(the_repository, argv, prefix, patch_interactive) != 0)
+		if (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0)
 			die(_("interactive add failed"));
 
 		the_repository->index_file = old_repo_index_file;
@@ -424,6 +426,11 @@ static const char *prepare_index(const char **argv, const char *prefix,
 		commit_style = COMMIT_NORMAL;
 		ret = get_lock_file_path(&index_lock);
 		goto out;
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
 	}
 
 	/*
@@ -1722,6 +1729,12 @@ int cmd_commit(int argc,
 		OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")),
 		OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
 		OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
+		OPT_INTEGER_F('U', "unified", &add_p_opt.context,
+			      N_("generate diffs with <n> lines context, implies --interactive/--patch"),
+			      PARSE_OPT_NONEG),
+		OPT_INTEGER_F(0, "inter-hunk-context", &add_p_opt.interhunkcontext,
+			      N_("show context between diff hunks up to the specified number of lines, implies --interactive/--patch"),
+			      PARSE_OPT_NONEG),
 		OPT_BOOL('o', "only", &only, N_("commit only specified files")),
 		OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")),
 		OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
diff --git a/builtin/reset.c b/builtin/reset.c
index 73b4537a9a56..ee39d6277381 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -346,6 +346,7 @@ int cmd_reset(int argc,
 	struct object_id oid;
 	struct pathspec pathspec;
 	int intent_to_add = 0;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	const struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
 		OPT_BOOL(0, "no-refresh", &no_refresh,
@@ -370,6 +371,12 @@ int cmd_reset(int argc,
 			       PARSE_OPT_OPTARG,
 			       option_parse_recurse_submodules_worktree_updater),
 		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
+		OPT_INTEGER_F('U', "unified", &add_p_opt.context,
+			       N_("generate diffs with <n> lines context, implies --patch"),
+			       PARSE_OPT_NONEG),
+		OPT_INTEGER_F(0, "inter-hunk-context", &add_p_opt.interhunkcontext,
+			       N_("show context between diff hunks up to the specified number of lines, implies --patch"),
+			       PARSE_OPT_NONEG),
 		OPT_BOOL('N', "intent-to-add", &intent_to_add,
 				N_("record only the fact that removed paths will be added later")),
 		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
@@ -424,9 +431,14 @@ int cmd_reset(int argc,
 		if (reset_type != NONE)
 			die(_("options '%s' and '%s' cannot be used together"), "--patch", "--{hard,mixed,soft}");
 		trace2_cmd_mode("patch-interactive");
-		update_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev,
-				   &pathspec);
+		update_ref_status = !!run_add_p(the_repository, ADD_P_RESET,
+						&add_p_opt, rev, &pathspec);
 		goto cleanup;
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
 	}
 
 	/* git reset tree [--] paths... can be used to
diff --git a/builtin/stash.c b/builtin/stash.c
index cfbd92852a65..dc82bc34b762 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -1242,7 +1242,8 @@ done:
 }
 
 static int stash_patch(struct stash_info *info, const struct pathspec *ps,
-		       struct strbuf *out_patch, int quiet)
+		       struct strbuf *out_patch, int quiet,
+		       struct add_p_opt *add_p_opt)
 {
 	int ret = 0;
 	struct child_process cp_read_tree = CHILD_PROCESS_INIT;
@@ -1267,7 +1268,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
 	old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
 	setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
 
-	ret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps);
+	ret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps);
 
 	the_repository->index_file = old_repo_index_file;
 	if (old_index_env && *old_index_env)
@@ -1362,8 +1363,8 @@ done:
 }
 
 static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,
-			   int include_untracked, int patch_mode, int only_staged,
-			   struct stash_info *info, struct strbuf *patch,
+			   int include_untracked, int patch_mode, struct add_p_opt *add_p_opt,
+			   int only_staged, struct stash_info *info, struct strbuf *patch,
 			   int quiet)
 {
 	int ret = 0;
@@ -1439,7 +1440,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
 		untracked_commit_option = 1;
 	}
 	if (patch_mode) {
-		ret = stash_patch(info, ps, patch, quiet);
+		ret = stash_patch(info, ps, patch, quiet, add_p_opt);
 		if (ret < 0) {
 			if (!quiet)
 				fprintf_ln(stderr, _("Cannot save the current "
@@ -1513,7 +1514,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
 	if (!check_changes_tracked_files(&ps))
 		return 0;
 
-	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,
+	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,
 			      NULL, 0);
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
@@ -1524,7 +1525,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
 }
 
 static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,
-			 int keep_index, int patch_mode, int include_untracked, int only_staged)
+			 int keep_index, int patch_mode, struct add_p_opt *add_p_opt,
+			 int include_untracked, int only_staged)
 {
 	int ret = 0;
 	struct stash_info info = STASH_INFO_INIT;
@@ -1594,8 +1596,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
 
 	if (stash_msg)
 		strbuf_addstr(&stash_msg_buf, stash_msg);
-	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,
-			    &info, &patch, quiet)) {
+	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
+			    add_p_opt, only_staged, &info, &patch, quiet)) {
 		ret = -1;
 		goto done;
 	}
@@ -1768,6 +1770,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 	const char *stash_msg = NULL;
 	char *pathspec_from_file = NULL;
 	struct pathspec ps;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	struct option options[] = {
 		OPT_BOOL('k', "keep-index", &keep_index,
 			 N_("keep index")),
@@ -1775,6 +1778,12 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_INTEGER_F('U', "unified", &add_p_opt.context,
+			      N_("generate diffs with <n> lines context, implies --patch"),
+			      PARSE_OPT_NONEG),
+		OPT_INTEGER_F(0, "inter-hunk-context", &add_p_opt.interhunkcontext,
+			      N_("show context between diff hunks up to the specified number of lines, implies --patch"),
+			      PARSE_OPT_NONEG),
 		OPT__QUIET(&quiet, N_("quiet mode")),
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
@@ -1826,8 +1835,15 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 		die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file");
 	}
 
+	if (!patch_mode) {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
-			    include_untracked, only_staged);
+			    &add_p_opt, include_untracked, only_staged);
 
 	clear_pathspec(&ps);
 	free(pathspec_from_file);
@@ -1852,6 +1868,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 	const char *stash_msg = NULL;
 	struct pathspec ps;
 	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	struct option options[] = {
 		OPT_BOOL('k', "keep-index", &keep_index,
 			 N_("keep index")),
@@ -1859,6 +1876,12 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_INTEGER_F('U', "unified", &add_p_opt.context,
+			      N_("generate diffs with <n> lines context, implies --patch"),
+			      PARSE_OPT_NONEG),
+		OPT_INTEGER_F(0, "inter-hunk-context", &add_p_opt.interhunkcontext,
+			      N_("show context between diff hunks up to the specified number of lines, implies --patch"),
+			      PARSE_OPT_NONEG),
 		OPT__QUIET(&quiet, N_("quiet mode")),
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
@@ -1877,8 +1900,17 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 		stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
+
+	if (!patch_mode) {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
-			    patch_mode, include_untracked, only_staged);
+			    patch_mode, &add_p_opt, include_untracked,
+			    only_staged);
 
 	strbuf_release(&stash_msg_buf);
 	return ret;
diff --git a/commit.h b/commit.h
index 70c870dae4d4..7a7fedbc2f14 100644
--- a/commit.h
+++ b/commit.h
@@ -2,6 +2,7 @@
 #define COMMIT_H
 
 #include "object.h"
+#include "add-interactive.h"
 
 struct signature_check;
 struct strbuf;
@@ -257,7 +258,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *);
 int interactive_add(struct repository *repo,
 		    const char **argv,
 		    const char *prefix,
-		    int patch);
+		    int patch, struct add_p_opt *add_p_opt);
 
 struct commit_extra_header {
 	struct commit_extra_header *next;
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index 9c024200ade7..1c1e62b434ec 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -104,6 +104,36 @@ test_expect_success 'The -U option overrides diff.context' '
 	! grep "^ firstline" output
 '
 
+test_expect_success 'The -U option overrides diff.context for "add"' '
+	git config diff.context 8 &&
+	git add -U4 -p >output &&
+	! grep "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "commit"' '
+	git config diff.context 8 &&
+	! git commit -U4 -p >output &&
+	! grep "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "checkout"' '
+	git config diff.context 8 &&
+	git checkout -U4 -p >output &&
+	! grep "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "stash"' '
+	git config diff.context 8 &&
+	! git stash -U4 -p >output &&
+	! grep "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "restore"' '
+	git config diff.context 8 &&
+	git restore -U4 -p >output &&
+	! grep "^ firstline" output
+'
+
 test_expect_success 'diff.context honored by "diff"' '
 	git config diff.context 8 &&
 	git diff >output &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 343b8cd1912b..6650d33fba69 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2596,6 +2596,8 @@ test_expect_success 'double dash "git checkout"' '
 	--merge Z
 	--conflict=Z
 	--patch Z
+	--unified=Z
+	--inter-hunk-context=Z
 	--ignore-skip-worktree-bits Z
 	--ignore-other-worktrees Z
 	--recurse-submodules Z
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH 3/3] add-interactive: add new "context" subcommand
  2025-05-05  9:18 [PATCH 0/3] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
  2025-05-05  9:18 ` [PATCH 1/3] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
  2025-05-05  9:18 ` [PATCH 2/3] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
@ 2025-05-05  9:18 ` Leon Michalak via GitGitGadget
  2025-05-06  0:02   ` Eric Sunshine
  2025-05-10 13:46 ` [PATCH v2 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
  3 siblings, 1 reply; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-05-05  9:18 UTC (permalink / raw)
  To: git; +Cc: Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

This teaches `add/commit --interactive` a new "context" subcommand, which
changes the amount of context lines subsequent subcommands like "patch"
or "diff" generate in their diffs.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 Documentation/git-add.adoc | 10 ++++--
 add-interactive.c          | 72 +++++++++++++++++++++++++++++++++++++-
 t/t3701-add-interactive.sh | 36 ++++++++++++++++---
 3 files changed, 110 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index e3a50706acd5..1ab98c9b876e 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -265,14 +265,15 @@ and type return, like this:
 ------------
     *** Commands ***
       1: status       2: update       3: revert       4: add untracked
-      5: patch        6: diff         7: quit         8: help
+      5: patch        6: diff         7: context      8: quit
+      9: help
     What now> 1
 ------------
 
 You also could say `s` or `sta` or `status` above as long as the
 choice is unique.
 
-The main command loop has 6 subcommands (plus help and quit).
+The main command loop has 7 subcommands (plus help and quit).
 
 status::
 
@@ -373,6 +374,11 @@ diff::
   This lets you review what will be committed (i.e. between
   `HEAD` and index).
 
+context::
+
+  This lets you change the amount of context lines shown in diffs that
+  the 'patch' and 'diff' subcommands generate.
+
 
 EDITING PATCHES
 ---------------
diff --git a/add-interactive.c b/add-interactive.c
index 1ea8eb711a60..42b1bb8c5b64 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -20,6 +20,8 @@
 #include "prompt.h"
 #include "tree.h"
 
+static void choose_prompt_help_context(struct add_i_state *s);
+
 static void init_color(struct repository *r, struct add_i_state *s,
 		       const char *section_and_slot, char *dst,
 		       const char *default_color)
@@ -259,7 +261,8 @@ static void list(struct add_i_state *s, struct string_list *list, int *selected,
 		opts->print_item(i, selected ? selected[i] : 0, list->items + i,
 				 opts->print_item_data);
 
-		if ((opts->columns) && ((i + 1) % (opts->columns))) {
+		if (i < list->nr - 1 &&
+		    (opts->columns) && ((i + 1) % (opts->columns))) {
 			putchar('\t');
 			last_lf = 0;
 		}
@@ -1047,6 +1050,60 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
 	return res;
 }
 
+static int run_context(struct add_i_state *s, const struct pathspec *ps UNUSED,
+		       struct prefix_item_list *files UNUSED,
+		       struct list_and_choose_options *opts UNUSED)
+{
+	struct diff_options diffopts;
+	struct strbuf input = STRBUF_INIT;
+	int res = 0;
+
+	repo_diff_setup(s->r, &diffopts);
+
+	for (;;) {
+		int new_context;
+		char *endp;
+
+		strbuf_reset(&input);
+
+		color_fprintf(stdout, s->header_color, "  %s:", N_("Current"));
+		fprintf(stdout, " %i\n", s->context == -1 ?
+			diffopts.context : s->context);
+
+		color_fprintf(stdout, s->prompt_color, "%s", N_("Change context"));
+		fputs("> ", stdout);
+		fflush(stdout);
+
+		if (git_read_line_interactively(&input) == EOF) {
+			putchar('\n');
+			break;
+		}
+
+		if (!input.len)
+			break;
+
+		if (!strcmp(input.buf, "?")) {
+			choose_prompt_help_context(s);
+			continue;
+		}
+
+		new_context = strtol(input.buf, &endp, 10);
+		if (*endp) {
+			color_fprintf_ln(stderr, s->error_color,
+				_("Context must be a numerical value"));
+			continue;
+		}
+
+		s->context = new_context;
+
+		break;
+	}
+
+	strbuf_release(&input);
+	putchar('\n');
+	return res;
+}
+
 static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED,
 		    struct prefix_item_list *files UNUSED,
 		    struct list_and_choose_options *opts UNUSED)
@@ -1061,6 +1118,8 @@ static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED,
 			 _("pick hunks and update selectively"));
 	color_fprintf_ln(stdout, s->help_color, "diff          - %s",
 			 _("view diff between HEAD and index"));
+	color_fprintf_ln(stdout, s->help_color, "context       - %s",
+			 _("change how many context lines diffs are generated with"));
 	color_fprintf_ln(stdout, s->help_color, "add untracked - %s",
 			 _("add contents of untracked files to the staged set of changes"));
 
@@ -1087,6 +1146,16 @@ static void choose_prompt_help(struct add_i_state *s)
 			 _("(empty) finish selecting"));
 }
 
+static void choose_prompt_help_context(struct add_i_state *s)
+{
+	color_fprintf_ln(stdout, s->help_color, "%s",
+			 _("Prompt help:"));
+	color_fprintf_ln(stdout, s->help_color, "<n>        - %s",
+			 _("specify new context lines amount"));
+	color_fprintf_ln(stdout, s->help_color, "           - %s",
+			 _("(empty) finish selecting"));
+}
+
 typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
 			 struct prefix_item_list *files,
 			 struct list_and_choose_options *opts);
@@ -1147,6 +1216,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps,
 		{ "add untracked", run_add_untracked },
 		{ "patch", run_patch },
 		{ "diff", run_diff },
+		{ "context", run_context },
 		{ "quit", NULL },
 		{ "help", run_help },
 	};
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index b8a05d95f3f1..9dcbc07d5876 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -758,16 +758,19 @@ test_expect_success 'colors can be overridden' '
 
 	<RED>*** Commands ***<RESET>
 	  1: <YELLOW>s<RESET>tatus	  2: <YELLOW>u<RESET>pdate	  3: <YELLOW>r<RESET>evert	  4: <YELLOW>a<RESET>dd untracked
-	  5: <YELLOW>p<RESET>atch	  6: <YELLOW>d<RESET>iff	  7: <YELLOW>q<RESET>uit	  8: <YELLOW>h<RESET>elp
+	  5: <YELLOW>p<RESET>atch	  6: <YELLOW>d<RESET>iff	  7: <YELLOW>c<RESET>ontext	  8: <YELLOW>q<RESET>uit
+	  9: <YELLOW>h<RESET>elp
 	<YELLOW>What now<RESET>> <GREEN>status        - show paths with changes<RESET>
 	<GREEN>update        - add working tree state to the staged set of changes<RESET>
 	<GREEN>revert        - revert staged set of changes back to the HEAD version<RESET>
 	<GREEN>patch         - pick hunks and update selectively<RESET>
 	<GREEN>diff          - view diff between HEAD and index<RESET>
+	<GREEN>context       - change how many context lines diffs are generated with<RESET>
 	<GREEN>add untracked - add contents of untracked files to the staged set of changes<RESET>
 	<RED>*** Commands ***<RESET>
 	  1: <YELLOW>s<RESET>tatus	  2: <YELLOW>u<RESET>pdate	  3: <YELLOW>r<RESET>evert	  4: <YELLOW>a<RESET>dd untracked
-	  5: <YELLOW>p<RESET>atch	  6: <YELLOW>d<RESET>iff	  7: <YELLOW>q<RESET>uit	  8: <YELLOW>h<RESET>elp
+	  5: <YELLOW>p<RESET>atch	  6: <YELLOW>d<RESET>iff	  7: <YELLOW>c<RESET>ontext	  8: <YELLOW>q<RESET>uit
+	  9: <YELLOW>h<RESET>elp
 	<YELLOW>What now<RESET>> Bye.
 	EOF
 	test_cmp expect actual &&
@@ -831,7 +834,8 @@ test_expect_success 'brackets appear without color' '
 	|
 	|*** Commands ***
 	|  1: [s]tatus	  2: [u]pdate	  3: [r]evert	  4: [a]dd untracked
-	|  5: [p]atch	  6: [d]iff	  7: [q]uit	  8: [h]elp
+	|  5: [p]atch	  6: [d]iff	  7: [c]ontext	  8: [q]uit
+	|  9: [h]elp
 	|What now> Bye.
 	EOF
 
@@ -1172,16 +1176,19 @@ test_expect_success 'show help from add--helper' '
 
 	<BOLD>*** Commands ***<RESET>
 	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
-	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>c<RESET>ontext	  8: <BOLD;BLUE>q<RESET>uit
+	  9: <BOLD;BLUE>h<RESET>elp
 	<BOLD;BLUE>What now<RESET>> <BOLD;RED>status        - show paths with changes<RESET>
 	<BOLD;RED>update        - add working tree state to the staged set of changes<RESET>
 	<BOLD;RED>revert        - revert staged set of changes back to the HEAD version<RESET>
 	<BOLD;RED>patch         - pick hunks and update selectively<RESET>
 	<BOLD;RED>diff          - view diff between HEAD and index<RESET>
+	<BOLD;RED>context       - change how many context lines diffs are generated with<RESET>
 	<BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET>
 	<BOLD>*** Commands ***<RESET>
 	  1: <BOLD;BLUE>s<RESET>tatus	  2: <BOLD;BLUE>u<RESET>pdate	  3: <BOLD;BLUE>r<RESET>evert	  4: <BOLD;BLUE>a<RESET>dd untracked
-	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>q<RESET>uit	  8: <BOLD;BLUE>h<RESET>elp
+	  5: <BOLD;BLUE>p<RESET>atch	  6: <BOLD;BLUE>d<RESET>iff	  7: <BOLD;BLUE>c<RESET>ontext	  8: <BOLD;BLUE>q<RESET>uit
+	  9: <BOLD;BLUE>h<RESET>elp
 	<BOLD;BLUE>What now<RESET>>$SP
 	Bye.
 	EOF
@@ -1230,4 +1237,23 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
 	test_cmp expect actual
 '
 
+test_expect_success 'change context works' '
+	git reset --hard &&
+	cat >template <<-\EOF &&
+	firstline
+	preline
+	TARGET
+	postline
+	lastline
+	EOF
+	sed "/TARGET/d" >x <template &&
+	git update-index --add x &&
+	git commit -m initial &&
+	sed "s/TARGET/ADDED/" >x <template &&
+	test_write_lines p 1 | git add -i >output &&
+	grep firstline output &&
+	test_write_lines c 0 p 1 | git add -i >output &&
+	! grep firstline output
+'
+
 test_done
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 77+ messages in thread

* Re: [PATCH 2/3] add-patch: add diff.context command line overrides
  2025-05-05  9:18 ` [PATCH 2/3] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
@ 2025-05-05  9:49   ` Kristoffer Haugsbakk
       [not found]     ` <CAP9jKjHj7WP91aKA9SE94zYj+naBGLUs99mF3G4BhTGcGjFDUQ@mail.gmail.com>
  2025-05-07  9:51   ` Phillip Wood
  1 sibling, 1 reply; 77+ messages in thread
From: Kristoffer Haugsbakk @ 2025-05-05  9:49 UTC (permalink / raw)
  To: Josh Soref, git; +Cc: Leon Michalak

On Mon, May 5, 2025, at 11:18, Leon Michalak via GitGitGadget wrote:
> From: Leon Michalak <leonmichalak6@gmail.com>
>
> This patch compliments `8b91eef812`, where builtins that accept
> `--patch` options now respect `diff.context` and `diff.interHunkContext`
> file configurations.

8b91eef812 is patch 1.  This hash will change once the patches have been
imported via git-am(1).  So it won’t make sense when these patches land
as commits.

I think the usual approach is to refer to a previous commit in the
series as “in a previous commit we...”.  Or maybe “in the previous
commit” for this patch and “two commits ago” for patch 3.

For commits that are in the stable history (like `master`) the
convention is to use:

    git show -s --pretty=reference <commit>

Without backticks (`).  See SubmittingPatches, commit-reference.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 2/3] add-patch: add diff.context command line overrides
       [not found]     ` <CAP9jKjHj7WP91aKA9SE94zYj+naBGLUs99mF3G4BhTGcGjFDUQ@mail.gmail.com>
@ 2025-05-05 10:11       ` Leon Michalak
  0 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak @ 2025-05-05 10:11 UTC (permalink / raw)
  To: Kristoffer Haugsbakk; +Cc: Josh Soref, git

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.

This being my first ever contribution, I hope I am at least a little
forgiven for any mistakes, which of course I'm more than happy to
correct and learn from.

On Mon, 5 May 2025 at 11:08, Leon Michalak <leonmichalak6@gmail.com> wrote:
>
> 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.
>
> This being my first ever contribution, I hope I am at least a little forgiven for any mistakes, which of course I can correct :)
>
>
> On Mon, 5 May 2025, 10:50 Kristoffer Haugsbakk, <kristofferhaugsbakk@fastmail.com> wrote:
>>
>> On Mon, May 5, 2025, at 11:18, Leon Michalak via GitGitGadget wrote:
>> > From: Leon Michalak <leonmichalak6@gmail.com>
>> >
>> > This patch compliments `8b91eef812`, where builtins that accept
>> > `--patch` options now respect `diff.context` and `diff.interHunkContext`
>> > file configurations.
>>
>> 8b91eef812 is patch 1.  This hash will change once the patches have been
>> imported via git-am(1).  So it won’t make sense when these patches land
>> as commits.
>>
>> I think the usual approach is to refer to a previous commit in the
>> series as “in a previous commit we...”.  Or maybe “in the previous
>> commit” for this patch and “two commits ago” for patch 3.
>>
>> For commits that are in the stable history (like `master`) the
>> convention is to use:
>>
>>     git show -s --pretty=reference <commit>
>>
>> Without backticks (`).  See SubmittingPatches, commit-reference.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 1/3] add-patch: respect diff.context configuration
  2025-05-05  9:18 ` [PATCH 1/3] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
@ 2025-05-05 20:29   ` Eric Sunshine
  2025-05-06  8:55   ` Phillip Wood
  2025-05-06 17:29   ` Junio C Hamano
  2 siblings, 0 replies; 77+ messages in thread
From: Eric Sunshine @ 2025-05-05 20:29 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget; +Cc: git, Leon Michalak

On Mon, May 5, 2025 at 5:18 AM Leon Michalak via GitGitGadget
<gitgitgadget@gmail.com> wrote:
> This aims to teach relevant builtins (that take in `--patch`) to respect
> the user's diff.context and diff.interHunkContext file configurations.
>
> Since these are both UI options and `--patch` is designed for the end user,
> I believe this was previously just an inconsistency, which this patch hopes
> to address.
>
> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
> ---
> diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
> @@ -49,7 +49,53 @@ test_expect_success 'diff.context honored by "log"' '
>         ! grep firstline output &&
>         git config diff.context 8 &&
>         git log -1 -p >output &&
> -       grep "^ firstline" output
> +       grep "^ firstline" output &&
> +       git config --unset diff.context
> +'
> +
> +test_expect_success 'diff.context honored by "add"' '
> +       git add -p >output &&
> +       ! grep firstline output &&
> +       git config diff.context 8 &&
> +       git add -p >output &&
> +       grep "^ firstline" output &&
> +       git config --unset diff.context
> +'

Be aware that if any command in the &&-chain prior to `git config
--unset` fails, then `git config --unset` itself will not be executed,
hence the cleanup won't happen. The way to address this is to instead
use `test_config` to configure the value since it will ensure that the
value gets "unset" regardless of whether the test succeeds or fails:

   test_config diff.context 8 &&

The same comment applies to all the new tests.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 3/3] add-interactive: add new "context" subcommand
  2025-05-05  9:18 ` [PATCH 3/3] add-interactive: add new "context" subcommand Leon Michalak via GitGitGadget
@ 2025-05-06  0:02   ` Eric Sunshine
  2025-05-06  7:20     ` Leon Michalak
  2025-05-06 16:37     ` Junio C Hamano
  0 siblings, 2 replies; 77+ messages in thread
From: Eric Sunshine @ 2025-05-06  0:02 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget; +Cc: git, Leon Michalak

On Mon, May 5, 2025 at 5:19 AM Leon Michalak via GitGitGadget
<gitgitgadget@gmail.com> wrote:
> This teaches `add/commit --interactive` a new "context" subcommand, which
> changes the amount of context lines subsequent subcommands like "patch"
> or "diff" generate in their diffs.
>
> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
> ---
> diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
> @@ -265,14 +265,15 @@ and type return, like this:
>  ------------
>      *** Commands ***
>        1: status       2: update       3: revert       4: add untracked
> -      5: patch        6: diff         7: quit         8: help
> +      5: patch        6: diff         7: context      8: quit
> +      9: help
>      What now> 1

I'm not a `git add/commit --interactive' user, but I can imagine that
inserting "context" at 7 and bumping "quit" and "help" to 8 and 9,
respectively, is going to play havoc with muscle memory people have
built up over the years. To make this more friendly for existing
users, I'd suggest adding this new command at the end of the list
without changing the existing command numbers.

Also, looking at this list, I can't help but think that "context"
feels out of place among the other action-oriented commands. Moreover,
if --interactive mode grows more configuration/setting-like commands
in the future, do we really want to keep extending this menu for them?
Specifically, I'm wondering if it would instead make sense to
introduce a new item "9: settings" which takes the user to a
"Settings" submenu from which the number of context lines can be set.

> -The main command loop has 6 subcommands (plus help and quit).
> +The main command loop has 7 subcommands (plus help and quit).

Since you're touching this anyhow, let's fix this maintenance burden
once and for all by writing more it generically, perhaps like this:

   The main command loop has several subcommands (plus help and quit).

> +context::
> +
> +  This lets you change the amount of context lines shown in diffs that
> +  the 'patch' and 'diff' subcommands generate.

s/amount/number/

> diff --git a/add-interactive.c b/add-interactive.c
> @@ -1061,6 +1118,8 @@ static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED,
> +       color_fprintf_ln(stdout, s->help_color, "context       - %s",
> +                        _("change how many context lines diffs are generated with"));

Perhaps:

    _("change the number of diff context lines"));

> @@ -1087,6 +1146,16 @@ static void choose_prompt_help(struct add_i_state *s)
> +static void choose_prompt_help_context(struct add_i_state *s)
> +{
> +       color_fprintf_ln(stdout, s->help_color, "%s",
> +                        _("Prompt help:"));
> +       color_fprintf_ln(stdout, s->help_color, "<n>        - %s",
> +                        _("specify new context lines amount"));

Likewise:

    _("change number of diff context lines"));

> +       color_fprintf_ln(stdout, s->help_color, "           - %s",
> +                        _("(empty) finish selecting"));

"finish selecting" looks like a copy/paste error from elsewhere in
this source file. Perhaps you meant something like:

    _("(empty) don't change number of context lines"));

> diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
> @@ -1230,4 +1237,23 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
> +test_expect_success 'change context works' '
> +       git reset --hard &&
> +       cat >template <<-\EOF &&
> +       firstline
> +       preline
> +       TARGET
> +       postline
> +       lastline
> +       EOF
> +       sed "/TARGET/d" >x <template &&
> +       git update-index --add x &&
> +       git commit -m initial &&
> +       sed "s/TARGET/ADDED/" >x <template &&
> +       test_write_lines p 1 | git add -i >output &&
> +       grep firstline output &&
> +       test_write_lines c 0 p 1 | git add -i >output &&
> +       ! grep firstline output
> +'

This script does have its share of bare `grep` invocations, but these
days we prefer `test_grep`, which also appears often in this script,
so the following would be more appropriate:

    test_grep firstline output &&
    ...
    test_grep ! firstline output

Note the placement of "!" when used with `test_grep`.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 3/3] add-interactive: add new "context" subcommand
  2025-05-06  0:02   ` Eric Sunshine
@ 2025-05-06  7:20     ` Leon Michalak
  2025-05-06  8:28       ` Christian Couder
  2025-05-06 16:37     ` Junio C Hamano
  1 sibling, 1 reply; 77+ messages in thread
From: Leon Michalak @ 2025-05-06  7:20 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Leon Michalak via GitGitGadget, git

Valid points, I don't think I have any objections to anything listed.

Would it be recommended to update to test_grep (and test_config from
previous message) in the same test files whilst I'm at it?

Thanks for the review :)

On Tue, 6 May 2025 at 01:02, Eric Sunshine <sunshine@sunshineco.com> wrote:
>
> On Mon, May 5, 2025 at 5:19 AM Leon Michalak via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> > This teaches `add/commit --interactive` a new "context" subcommand, which
> > changes the amount of context lines subsequent subcommands like "patch"
> > or "diff" generate in their diffs.
> >
> > Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
> > ---
> > diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
> > @@ -265,14 +265,15 @@ and type return, like this:
> >  ------------
> >      *** Commands ***
> >        1: status       2: update       3: revert       4: add untracked
> > -      5: patch        6: diff         7: quit         8: help
> > +      5: patch        6: diff         7: context      8: quit
> > +      9: help
> >      What now> 1
>
> I'm not a `git add/commit --interactive' user, but I can imagine that
> inserting "context" at 7 and bumping "quit" and "help" to 8 and 9,
> respectively, is going to play havoc with muscle memory people have
> built up over the years. To make this more friendly for existing
> users, I'd suggest adding this new command at the end of the list
> without changing the existing command numbers.
>
> Also, looking at this list, I can't help but think that "context"
> feels out of place among the other action-oriented commands. Moreover,
> if --interactive mode grows more configuration/setting-like commands
> in the future, do we really want to keep extending this menu for them?
> Specifically, I'm wondering if it would instead make sense to
> introduce a new item "9: settings" which takes the user to a
> "Settings" submenu from which the number of context lines can be set.
>
> > -The main command loop has 6 subcommands (plus help and quit).
> > +The main command loop has 7 subcommands (plus help and quit).
>
> Since you're touching this anyhow, let's fix this maintenance burden
> once and for all by writing more it generically, perhaps like this:
>
>    The main command loop has several subcommands (plus help and quit).
>
> > +context::
> > +
> > +  This lets you change the amount of context lines shown in diffs that
> > +  the 'patch' and 'diff' subcommands generate.
>
> s/amount/number/
>
> > diff --git a/add-interactive.c b/add-interactive.c
> > @@ -1061,6 +1118,8 @@ static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED,
> > +       color_fprintf_ln(stdout, s->help_color, "context       - %s",
> > +                        _("change how many context lines diffs are generated with"));
>
> Perhaps:
>
>     _("change the number of diff context lines"));
>
> > @@ -1087,6 +1146,16 @@ static void choose_prompt_help(struct add_i_state *s)
> > +static void choose_prompt_help_context(struct add_i_state *s)
> > +{
> > +       color_fprintf_ln(stdout, s->help_color, "%s",
> > +                        _("Prompt help:"));
> > +       color_fprintf_ln(stdout, s->help_color, "<n>        - %s",
> > +                        _("specify new context lines amount"));
>
> Likewise:
>
>     _("change number of diff context lines"));
>
> > +       color_fprintf_ln(stdout, s->help_color, "           - %s",
> > +                        _("(empty) finish selecting"));
>
> "finish selecting" looks like a copy/paste error from elsewhere in
> this source file. Perhaps you meant something like:
>
>     _("(empty) don't change number of context lines"));
>
> > diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
> > @@ -1230,4 +1237,23 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
> > +test_expect_success 'change context works' '
> > +       git reset --hard &&
> > +       cat >template <<-\EOF &&
> > +       firstline
> > +       preline
> > +       TARGET
> > +       postline
> > +       lastline
> > +       EOF
> > +       sed "/TARGET/d" >x <template &&
> > +       git update-index --add x &&
> > +       git commit -m initial &&
> > +       sed "s/TARGET/ADDED/" >x <template &&
> > +       test_write_lines p 1 | git add -i >output &&
> > +       grep firstline output &&
> > +       test_write_lines c 0 p 1 | git add -i >output &&
> > +       ! grep firstline output
> > +'
>
> This script does have its share of bare `grep` invocations, but these
> days we prefer `test_grep`, which also appears often in this script,
> so the following would be more appropriate:
>
>     test_grep firstline output &&
>     ...
>     test_grep ! firstline output
>
> Note the placement of "!" when used with `test_grep`.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 3/3] add-interactive: add new "context" subcommand
  2025-05-06  7:20     ` Leon Michalak
@ 2025-05-06  8:28       ` Christian Couder
  2025-05-06 17:07         ` Leon Michalak
  0 siblings, 1 reply; 77+ messages in thread
From: Christian Couder @ 2025-05-06  8:28 UTC (permalink / raw)
  To: Leon Michalak; +Cc: Eric Sunshine, Leon Michalak via GitGitGadget, git

On Tue, May 6, 2025 at 9:37 AM Leon Michalak <leonmichalak6@gmail.com> wrote:
>
> Valid points, I don't think I have any objections to anything listed.

Please don't "top post" and reply inline instead (see
https://en.wikipedia.org/wiki/Posting_style) as it's the standard on
this list.

> Would it be recommended to update to test_grep (and test_config from
> previous message) in the same test files whilst I'm at it?

If you want to replace pre existing "grep" commands with "test_grep"
and pre existing `git config` with `test_config`, I would recommend
doing it in preparatory patches that go first in your patch series.

Thanks!

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 1/3] add-patch: respect diff.context configuration
  2025-05-05  9:18 ` [PATCH 1/3] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
  2025-05-05 20:29   ` Eric Sunshine
@ 2025-05-06  8:55   ` Phillip Wood
  2025-05-06 17:29   ` Junio C Hamano
  2 siblings, 0 replies; 77+ messages in thread
From: Phillip Wood @ 2025-05-06  8:55 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget, git; +Cc: Leon Michalak, Eric Sunshine

Hi Leon

Thanks for working on this. I've left a few comments below but for your 
first submission this is very good.

On 05/05/2025 10:18, Leon Michalak via GitGitGadget wrote:
> From: Leon Michalak <leonmichalak6@gmail.com>
> 
> This aims to teach relevant builtins (that take in `--patch`) to respect
> the user's diff.context and diff.interHunkContext file configurations.

This is a good summary of the intent of the patch. I'd maybe drop "This 
aims" and instead say that the various commands do not respect the 
config and this patch fixes that.

> Since these are both UI options and `--patch` is designed for the end user,
> I believe this was previously just an inconsistency, which this patch hopes
> to address.
> 
> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
> ---
>   add-interactive.c       | 16 ++++++++++----
>   add-patch.c             |  6 ++++++
>   t/t4055-diff-context.sh | 48 ++++++++++++++++++++++++++++++++++++++++-
>   3 files changed, 65 insertions(+), 5 deletions(-)
> 
> diff --git a/add-interactive.c b/add-interactive.c
> index 97ff35b6f12a..ad12dc416598 100644
> --- a/add-interactive.c
> +++ b/add-interactive.c
> @@ -41,6 +41,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
>   	const char *value;
>   
>   	s->r = r;
> +	s->context = -1;
> +	s->interhunkcontext = -1;
>   
>   	if (repo_config_get_value(r, "color.interactive", &value))
>   		s->use_color = -1;
> @@ -78,6 +80,9 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
>   	repo_config_get_string(r, "diff.algorithm",
>   			       &s->interactive_diff_algorithm);
>   
> +	repo_config_get_int(r, "diff.context", &s->context);
> +	repo_config_get_int(r, "diff.interHunkContext", &s->interhunkcontext);

This looks good

> @@ -1014,10 +1019,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
>   	if (count > 0) {
>   		struct child_process cmd = CHILD_PROCESS_INIT;
>   
> -		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached",
> -			     oid_to_hex(!is_initial ? &oid :
> -					s->r->hash_algo->empty_tree),
> -			     "--", NULL);
> +		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL);

As we're running "git diff" here nothing needs to be changed because it 
reads all of the relevant config variables itself. This is why the 
existing code does not worry about adding "--algorithm".

> diff --git a/add-patch.c b/add-patch.c
> index 95c67d8c80c4..b43ca1600738 100644
> --- a/add-patch.c
> +++ b/add-patch.c
> @@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
>   {
>   	struct strvec args = STRVEC_INIT;
>   	const char *diff_algorithm = s->s.interactive_diff_algorithm;
> +	int diff_context = s->s.context;
> +	int diff_interhunkcontext = s->s.interhunkcontext;
>   	struct strbuf *plain = &s->plain, *colored = NULL;
>   	struct child_process cp = CHILD_PROCESS_INIT;
>   	char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
> @@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
>   	int res;
>   
>   	strvec_pushv(&args, s->mode->diff_cmd);
> +	if (diff_context != -1)
> +		strvec_pushf(&args, "--unified=%i", diff_context);
> +	if (diff_interhunkcontext != -1)
> +		strvec_pushf(&args, "--inter-hunk-context=%i", diff_interhunkcontext);

This is good - if the user has set diff.config or diff.interhunkcontext 
then we pass the appropriate values to "git diff-index" or "git 
diff-files" which unlike "git diff" do not read those config values 
themselves.

>   	if (diff_algorithm)
>   		strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
>   	if (s->revision) {
> diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
> index ec2804eea67c..9c024200ade7 100755
> --- a/t/t4055-diff-context.sh
> +++ b/t/t4055-diff-context.sh

I'm not sure this is the best home for the new tests. Normally tests 
related to the code in add-patch.c go into t3701-add-interactive.sh

> @@ -49,7 +49,53 @@ test_expect_success 'diff.context honored by "log"' '
>   	! grep firstline output &&
>   	git config diff.context 8 &&
>   	git log -1 -p >output &&
> -	grep "^ firstline" output
> +	grep "^ firstline" output &&
> +	git config --unset diff.context
> +'
> +
> +test_expect_success 'diff.context honored by "add"' '
> +	git add -p >output &&
> +	! grep firstline output &&
> +	git config diff.context 8 &&
> +	git add -p >output &&
> +	grep "^ firstline" output &&
> +	git config --unset diff.context

Eric has already mentioned using "test_config", I would go one step 
further and say that as we're only running a single command we should 
use "git add -c diff.context=* add -p" to avoid having to change the on 
disk config at all.

I would also recommend using "test_grep ..." rather than "grep ..." and 
"test_grep ! .." instead of "! grep ..." as they provide useful 
debugging information if the test fails.

Given the way the code is structured with the config being read in 
add-patch.c I'm not sure how much value there is in testing all the 
different "-p" commands. It would however be very useful to check that 
"git -c diff.interhunkcontext=8 add -p" works as expected.

I'll leave it there for now, I'll try and look at the other patches 
later today or maybe tomorrow.

Best Wishes

Phillip

> +'
> +
> +test_expect_success 'diff.context honored by "commit"' '
> +	! git commit -p >output &&
> +	! grep firstline output &&
> +	git config diff.context 8 &&
> +	! git commit -p >output &&
> +	grep "^ firstline" output &&
> +	git config --unset diff.context
> +'
> +
> +test_expect_success 'diff.context honored by "checkout"' '
> +	git checkout -p >output &&
> +	! grep firstline output &&
> +	git config diff.context 8 &&
> +	git checkout -p >output &&
> +	grep "^ firstline" output &&
> +	git config --unset diff.context
> +'
> +
> +test_expect_success 'diff.context honored by "stash"' '
> +	! git stash -p >output &&
> +	! grep firstline output &&
> +	git config diff.context 8 &&
> +	! git stash -p >output &&
> +	grep "^ firstline" output &&
> +	git config --unset diff.context
> +'
> +
> +test_expect_success 'diff.context honored by "restore"' '
> +	git restore -p >output &&
> +	! grep firstline output &&
> +	git config diff.context 8 &&
> +	git restore -p >output &&
> +	grep "^ firstline" output &&
> +	git config --unset diff.context
>   '
>   
>   test_expect_success 'The -U option overrides diff.context' '


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 3/3] add-interactive: add new "context" subcommand
  2025-05-06  0:02   ` Eric Sunshine
  2025-05-06  7:20     ` Leon Michalak
@ 2025-05-06 16:37     ` Junio C Hamano
  2025-05-06 17:25       ` Leon Michalak
  2025-05-07 13:30       ` Phillip Wood
  1 sibling, 2 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-05-06 16:37 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Leon Michalak via GitGitGadget, git, Leon Michalak

Eric Sunshine <sunshine@sunshineco.com> writes:

> On Mon, May 5, 2025 at 5:19 AM Leon Michalak via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
>> This teaches `add/commit --interactive` a new "context" subcommand, which
>> changes the amount of context lines subsequent subcommands like "patch"
>> or "diff" generate in their diffs.
>>
>> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
>> ---
>> diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
>> @@ -265,14 +265,15 @@ and type return, like this:
>>  ------------
>>      *** Commands ***
>>        1: status       2: update       3: revert       4: add untracked
>> -      5: patch        6: diff         7: quit         8: help
>> +      5: patch        6: diff         7: context      8: quit
>> +      9: help
>>      What now> 1
>
> I'm not a `git add/commit --interactive' user, but I can imagine that
> inserting "context" at 7 and bumping "quit" and "help" to 8 and 9,
> respectively, is going to play havoc with muscle memory people have
> built up over the years. To make this more friendly for existing
> users, I'd suggest adding this new command at the end of the list
> without changing the existing command numbers.

Hmph, in the real UI, the first letters of these commands (which
have deliberately been chosen to be unique) are shown in different
color to hint that the answer to the prompt can be like 's' (for
status).  I would hate to see that quit/help, which are somewhat
special, move from the very end position.

Perhaps the example of the documentation page shown above should
respond with 's', not '1' to show more prominently that it is
possible and encouraged?  I dunno.

> Also, looking at this list, I can't help but think that "context"
> feels out of place among the other action-oriented commands. Moreover,
> if --interactive mode grows more configuration/setting-like commands
> in the future, do we really want to keep extending this menu for them?

That is a valid concern.

> Specifically, I'm wondering if it would instead make sense to
> introduce a new item "9: settings" which takes the user to a
> "Settings" submenu from which the number of context lines can be set.

's' is taken for 'status', so it shouldn't be '9: settings' (it
should come before 'quit' instead), but I think I know where you are
going.

An alternative that may work better is to allow subcommand specific
parameters in the answer to the "What now>" prompt, e.g.

	What now> r -U7

to choose an equivalent to "git revert -p -U7".

Thanks.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 3/3] add-interactive: add new "context" subcommand
  2025-05-06  8:28       ` Christian Couder
@ 2025-05-06 17:07         ` Leon Michalak
  0 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak @ 2025-05-06 17:07 UTC (permalink / raw)
  To: Christian Couder; +Cc: Eric Sunshine, Leon Michalak via GitGitGadget, git

On Tue, 6 May 2025 at 09:28, Christian Couder
<christian.couder@gmail.com> wrote:
> Please don't "top post" and reply inline instead (see
> https://en.wikipedia.org/wiki/Posting_style) as it's the standard on
> this list.

Thanks, I rarely use email like this so overlooked this aspect. I hope
this email is better? If not let me know.

> If you want to replace pre existing "grep" commands with "test_grep"
> and pre existing `git config` with `test_config`, I would recommend
> doing it in preparatory patches that go first in your patch series.

Yes that makes sense :)

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 3/3] add-interactive: add new "context" subcommand
  2025-05-06 16:37     ` Junio C Hamano
@ 2025-05-06 17:25       ` Leon Michalak
  2025-05-07 13:30       ` Phillip Wood
  1 sibling, 0 replies; 77+ messages in thread
From: Leon Michalak @ 2025-05-06 17:25 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Eric Sunshine, Leon Michalak via GitGitGadget, git

On Tue, 6 May 2025 at 17:37, Junio C Hamano <gitster@pobox.com> wrote:
> Hmph, in the real UI, the first letters of these commands (which
> have deliberately been chosen to be unique) are shown in different
> color to hint that the answer to the prompt can be like 's' (for
> status).  I would hate to see that quit/help, which are somewhat
> special, move from the very end position.

This is the main reason I chose to put the new subcommand before as I
also feel quit/help are better suited to be last, although I do see
the argument against muscle memory and is valid.

> Perhaps the example of the documentation page shown above should
> respond with 's', not '1' to show more prominently that it is
> possible and encouraged?  I dunno.

Writing the first letters as opposed to the numbers is how I
personally use this, just to add another perspective.

Would "config" as opposed to "settings" fix the unique lettering
problem? Or perhaps config is not the best description for what it
would do (I sometimes find myself using config/settings
interchangeably but the majority may not...)?

Overall I feel the first two patches cover my use case the most so I
am also not opposed to completely dropping the last patch about the
`--interactive` context subcommand. Inherited diff context and command
line overrides I feel are a great base to begin with either way. Just
some thoughts :)

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 1/3] add-patch: respect diff.context configuration
  2025-05-05  9:18 ` [PATCH 1/3] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
  2025-05-05 20:29   ` Eric Sunshine
  2025-05-06  8:55   ` Phillip Wood
@ 2025-05-06 17:29   ` Junio C Hamano
  2 siblings, 0 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-05-06 17:29 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget; +Cc: git, Leon Michalak

"Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Leon Michalak <leonmichalak6@gmail.com>
>
> This aims to teach relevant builtins (that take in `--patch`) to respect
> the user's diff.context and diff.interHunkContext file configurations.
>
> Since these are both UI options and `--patch` is designed for the end user,
> I believe this was previously just an inconsistency, which this patch hopes
> to address.

The usual way to compose a log message of this project is to

 - Give an observation on how the current system works in the present
   tense (so no need to say "Currently X is Y", just "X is Y"), and
   discuss what you perceive as a problem in it.

 - Propose a solution (optional---often, problem description
   trivially leads to an obvious solution in reader's minds).

 - Give commands to the codebase to "become like so".

in this order.  "Various built-in commands that use add-patch
infrastructure do not honor diff.context and diff.interHunkContext
configuration variables."  Would be the first sentence to explain
the current situation.  Follow that sentence with an explanation why
it is a bad thing (i.e. it is possible that "do not honor" may be a
deliberate design decision---perhaps the implementation of "add -p"
is impossible if it has to honor diff.context that is set to 0; show
evidence that no such deliberate design decision was there when the
feature was introduced from the list archive, and say that not
honoring the configuration is an inconvenient inconsistency).

Such an explanation would be sufficient for readers to guess the
"solution", so stopping there should be fine.

Thanks.

> @@ -1014,10 +1019,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
>  	if (count > 0) {
>  		struct child_process cmd = CHILD_PROCESS_INIT;
>  
> -		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached",
> -			     oid_to_hex(!is_initial ? &oid :
> -					s->r->hash_algo->empty_tree),
> -			     "--", NULL);
> +		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL);
> +		if (s->context != -1)
> +			strvec_pushf(&cmd.args, "--unified=%i", s->context);
> +		if (s->interhunkcontext != -1)
> +			strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->interhunkcontext);
> +		strvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :
> +			     s->r->hash_algo->empty_tree), "--", NULL);

OK.

> +test_expect_success 'diff.context honored by "add"' '
> +	git add -p >output &&
> +	! grep firstline output &&

	test_grep ! firstline output

?

> +	git config diff.context 8 &&
> +	git add -p >output &&
> +	grep "^ firstline" output &&

Likewise.  

	test_grep "^firstline" output

?

> +	git config --unset diff.context

Not like this.  Either use test_when_finished to arrange that this
unset is run when you leave, or use test_config.

	test_expect_success title '
		test_config diff.context 8 &&
		do your tests
	'

will reset diff.conftext when the above piece is done.

	test_expect_success title '
		test_when_finished "git config unset diff.context" &&
		git config set diff.context 8 &&
		do your tests
	'

is an equivalent.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 2/3] add-patch: add diff.context command line overrides
  2025-05-05  9:18 ` [PATCH 2/3] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
  2025-05-05  9:49   ` Kristoffer Haugsbakk
@ 2025-05-07  9:51   ` Phillip Wood
  2025-05-07 18:07     ` Junio C Hamano
  1 sibling, 1 reply; 77+ messages in thread
From: Phillip Wood @ 2025-05-07  9:51 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget, git; +Cc: Leon Michalak

Hi Leon

On 05/05/2025 10:18, Leon Michalak via GitGitGadget wrote:
> From: Leon Michalak <leonmichalak6@gmail.com>
> 
> This patch compliments `8b91eef812`, where builtins that accept
> `--patch` options now respect `diff.context` and `diff.interHunkContext`
> file configurations.
> 
> In particular, this patch helps users who don't want to set persistent
> context configurations or just want a way to override them on a one-time
> basis, by allowing the relevant builtins to accept corresponding command
> line options that override the file configurations.
> 
> This mimics `diff` which allows for both context file configuration and
> command line overrides.
> 
> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>

I'm somewhat torn about the name "--unified" as I don't think it is 
particularly informative - it only makes sense if one knows the diff 
option and we only support a single diff format. Having said that it is 
probably better to reuse the name from "git diff" for consistency.

> +`-U<n>`::
> +`--unified=<n>`::
> +	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
> +	or 3 if the config option is unset. Implies `--interactive/--patch`.
> +
> +`--inter-hunk-context=<n>`::
> +	Show the context between diff hunks, up to the specified _<number>_
> +	of lines, thereby fusing hunks that are close to each other.
> +	Defaults to `diff.interHunkContext` or 0 if the config option
> +	is unset. Implies `--interactive/--patch`.

This documentation is repeated for each command. I think it would be 
better to put this in separate file that is then included where it is 
needed. That way if we need to update the documentation in the future we 
only have one copy to worry about. The syntax to include a file called 
diff-context-options.adoc is

include::diff-context-options.adoc[]

> --- a/add-interactive.c
> +++ b/add-interactive.c
> @@ -86,6 +87,11 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
>   	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
>   	if (s->use_single_key)
>   		setbuf(stdin, NULL);
> +
> +	if (add_p_opt->context != -1)
> +		s->context = add_p_opt->context;
> +	if (add_p_opt->interhunkcontext != -1)
> +		s->interhunkcontext = add_p_opt->interhunkcontext;

This happens after we read the config so the command line option wins - 
good.

> index 693f125e8e4b..653f07a917b8 100644
> --- a/add-interactive.h
> +++ b/add-interactive.h
> @@ -3,6 +3,13 @@
>   
>   #include "color.h"
>   
> +#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }

I think we normally define initializer macros after the struct they 
initialize so the reader can more easily check the fields are all 
initialized correctly

> +struct add_p_opt {
> +	int context;
> +	int interhunkcontext;
> +};
> +
>   struct add_i_state {
>   	struct repository *r;
>   	int use_color;
> @@ -18,14 +25,17 @@ struct add_i_state {
>   
>   	int use_single_key;
>   	char *interactive_diff_filter, *interactive_diff_algorithm;
> +	int context, interhunkcontext;

Should this be in the previous patch? It is a good idea to run

     git rebase --keep-base --exec 'make && cd t && prove -j8 
tests-that-you-think-might-fail :: --root=/dev/shm'

before submitting a patch series so that you can be sure the patches all 
compile and the tests pass. Running the whole test suite can be pretty 
slow so just run the tests that are relevant to your changes.

> @@ -169,9 +170,10 @@ int interactive_add(struct repository *repo,
>   		       prefix, argv);
>   
>   	if (patch)
> -		ret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);
> +		ret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL,
> +				  &pathspec);

This line could be unwraped and still fit within 80 columns I think.

>   	else
> -		ret = !!run_add_i(repo, &pathspec);
> +		ret = !!run_add_i(repo, &pathspec, add_p_opt);
>   
>   	clear_pathspec(&pathspec);
>   	return ret;
> @@ -253,6 +255,12 @@ static struct option builtin_add_options[] = {
>   	OPT_GROUP(""),
>   	OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
>   	OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
> +	OPT_INTEGER_F('U', "unified", &add_p_opt.context,
> +		      N_("generate diffs with <n> lines context, implies --interactive/--patch"),

It cannot imply both --interactive and --patch as they are two different 
operations. I think it should imply --patch because that option is 
supported by all the commands we're adding -U to.

> +		      PARSE_OPT_NONEG),
> +	OPT_INTEGER_F(0, "inter-hunk-context", &add_p_opt.interhunkcontext,
> +		      N_("show context between diff hunks up to the specified number of lines, implies --interactive/--patch"),
> +		      PARSE_OPT_NONEG),

As these two options are duplicated in many commands we should define a 
preprocessor macro for them in parse-options.h. If you look at the end 
of that file there are a bunch of similar definitions.

>   	OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
>   	OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
>   	OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
> @@ -398,7 +406,12 @@ int cmd_add(int argc,
>   			die(_("options '%s' and '%s' cannot be used together"), "--dry-run", "--interactive/--patch");
>   		if (pathspec_from_file)
>   			die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch");
> -		exit(interactive_add(repo, argv + 1, prefix, patch_interactive));
> +		exit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));
> +	} else {
> +		if (add_p_opt.context != -1)
> +			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
> +		if (add_p_opt.interhunkcontext != -1)
> +			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");

I don't understand this - I thought the help for --unified said it 
implied --patch but here it is erroring out if --patch is not given.

Somewhere we should check that the values given for the context and 
interhunk context are non-negative. git_diff_ui_config() errors out if 
the context is negative and we should do the same in add-patch.c 
otherwise we'll get some wierd error about "git diff-index" failing. We 
should also think about -U0 as "git apply" rejects hunks without any 
context unless by default and I dont think "add -p" passes the option to 
enable that and I'm not sure it should.

> +test_expect_success 'The -U option overrides diff.context for "add"' '
> +	git config diff.context 8 &&
> +	git add -U4 -p >output &&
> +	! grep "^ firstline" output
> +'

It is great that you've added tests and I do think we should test all 
the commands here as unlike the previous patch there isn't a common code 
path. My other comments about the tests in the previous patch apply here 
though I think.

We should have a test to check negative context values and possibly zero 
are rejected.

Thanks

Phillip


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 3/3] add-interactive: add new "context" subcommand
  2025-05-06 16:37     ` Junio C Hamano
  2025-05-06 17:25       ` Leon Michalak
@ 2025-05-07 13:30       ` Phillip Wood
  2025-05-07 19:10         ` Junio C Hamano
  1 sibling, 1 reply; 77+ messages in thread
From: Phillip Wood @ 2025-05-07 13:30 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine
  Cc: Leon Michalak via GitGitGadget, git, Leon Michalak

On 06/05/2025 17:37, Junio C Hamano wrote:
> 
> An alternative that may work better is to allow subcommand specific
> parameters in the answer to the "What now>" prompt, e.g.
> 
> 	What now> r -U7
> 
> to choose an equivalent to "git revert -p -U7".

I think the best solution would be to allow users to re-display the 
current hunk with more context inside "add -p" rather than changing "add 
-i". That way a user who needs more context to make a decision on the 
current hunk can get it without restarting the whole process of 
selecting hunks [1]. If the user knows up front that they want a certain 
amount of context they can use "git add -p -U <context>" or "git add -i 
-U <context>" which was added in the preceding patch.

Best Wishes

Phillip

[1] With "git add -p" they can quit and carry on from where they left 
off but "git stash -p" doesn't let you amend an existing stash and git 
refuses to pop a stash if the working tree is dirty. This means if a 
stash is split in two because the user exited to change the context they 
cannot pop both stashes on top of each other.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 2/3] add-patch: add diff.context command line overrides
  2025-05-07  9:51   ` Phillip Wood
@ 2025-05-07 18:07     ` Junio C Hamano
  2025-05-07 18:28       ` Leon Michalak
  0 siblings, 1 reply; 77+ messages in thread
From: Junio C Hamano @ 2025-05-07 18:07 UTC (permalink / raw)
  To: Phillip Wood; +Cc: Leon Michalak via GitGitGadget, git, Leon Michalak

Phillip Wood <phillip.wood123@gmail.com> writes:

>
>> +`-U<n>`::
>> +`--unified=<n>`::
>> +	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
>> +	or 3 if the config option is unset. Implies `--interactive/--patch`.
>> +
>> +`--inter-hunk-context=<n>`::
>> +	Show the context between diff hunks, up to the specified _<number>_
>> +	of lines, thereby fusing hunks that are close to each other.
>> +	Defaults to `diff.interHunkContext` or 0 if the config option
>> +	is unset. Implies `--interactive/--patch`.
>
> This documentation is repeated for each command. I think it would be
> better to put this in separate file that is then included where it is
> needed. That way if we need to update the documentation in the future
> we only have one copy to worry about. The syntax to include a file
> called diff-context-options.adoc is
>
> include::diff-context-options.adoc[]

Excellent suggestion.

I however think "-U implies -i/-p" makes no sense at all.  What if
the user said "git add -U6"?  Do we run "git add -i -U6" or go
directly to "git add -p -U6"?

I think it is better to encourage the user to be more explicit to
require them to say "git revert -U6 -p" or "git add -U6 -i".

If -U<n> is given and there is no "-i" (for "add" only) or "-p" (for
everything), just error out *until* other modes of these commands
find a good use of context width information, at which point we can
use the context length information to drive these commands in their
non-"patch" mode.

Even if we cannot think of a way to use the diff output (and with
configurable context length) in modes that are not "--patch" for
these commands RIGHT NOW, that does not mean -U<n> would never
become useful in these modes for the commands.  Let's not close the
door prematurely from our future developers by making -U<n> imply
anything, and make it an error to use -U<N> without -p/-i for now.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 2/3] add-patch: add diff.context command line overrides
  2025-05-07 18:07     ` Junio C Hamano
@ 2025-05-07 18:28       ` Leon Michalak
  2025-05-07 20:25         ` Junio C Hamano
  0 siblings, 1 reply; 77+ messages in thread
From: Leon Michalak @ 2025-05-07 18:28 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Phillip Wood, Leon Michalak via GitGitGadget, git

I think I may be misunderstanding, so I'll elaborate on what I
personally intended my documentation to mean.

When I put in the documentation "implies --interactive/--patch" it
reads to me as "this assumes you are also using either --interactive
or --patch and that if you don't specify one or the other it will do
nothing or possibly error (which is what I chose in the end, based on
the initial discussion in a separate thread)". I didn't think it would
read as "you must have both settings" or "if you don't specify these
the command will assume it as if you had and effectively act as if you
had".

I'm not sure if the wording was confusing or it generally has
different meanings to others so perhaps that might clarify at least
what I intended :)

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 3/3] add-interactive: add new "context" subcommand
  2025-05-07 13:30       ` Phillip Wood
@ 2025-05-07 19:10         ` Junio C Hamano
  0 siblings, 0 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-05-07 19:10 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Eric Sunshine, Leon Michalak via GitGitGadget, git, Leon Michalak

Phillip Wood <phillip.wood123@gmail.com> writes:

> On 06/05/2025 17:37, Junio C Hamano wrote:
>> An alternative that may work better is to allow subcommand specific
>> parameters in the answer to the "What now>" prompt, e.g.
>> 	What now> r -U7
>> to choose an equivalent to "git revert -p -U7".
>
> I think the best solution would be to allow users to re-display the
> current hunk with more context inside "add -p" rather than changing
> "add -i". That way a user who needs more context to make a decision on
> the current hunk can get it without restarting the whole process of
> selecting hunks [1]. If the user knows up front that they want a
> certain amount of context they can use "git add -p -U <context>" or
> "git add -i -U <context>" which was added in the preceding patch.

Yes, but notice that the above is about interactive mode "add -i"
that is setting the diff.context to 7 for subsequent "patch"
command, not against "Stage this hunk [y,n,q,...]?" prompt inside
"add -p".  Once you go into the "--patch" mode, especially after you
let the user to edit some hunks, "redisplay this hunk with wider
context" is an impossible operation with the current code structure,
I think, as the implementation does not keep track of information
that are needed to do so.

So, this is like "restarting 'add -p' with -U7", but done from the
"add -i" session.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH 2/3] add-patch: add diff.context command line overrides
  2025-05-07 18:28       ` Leon Michalak
@ 2025-05-07 20:25         ` Junio C Hamano
  0 siblings, 0 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-05-07 20:25 UTC (permalink / raw)
  To: Leon Michalak; +Cc: Phillip Wood, Leon Michalak via GitGitGadget, git

Leon Michalak <leonmichalak6@gmail.com> writes:

> I think I may be misunderstanding, so I'll elaborate on what I
> personally intended my documentation to mean.
>
> When I put in the documentation "implies --interactive/--patch" it
> reads to me as "this assumes you are also using either --interactive
> or --patch and that if you don't specify one or the other it will do
> nothing or possibly error (which is what I chose in the end, based on
> the initial discussion in a separate thread)". I didn't think it would
> read as "you must have both settings" or "if you don't specify these
> the command will assume it as if you had and effectively act as if you
> had".
>
> I'm not sure if the wording was confusing or it generally has
> different meanings to others so perhaps that might clarify at least
> what I intended :)

We use "imply" to mean quite a different thing from what you said in
the above explanation, and that is where my reaction came from.

We say "option A implies option B" only when the command behaves as
if the user gave option B when only option A is given (and without
giving B).  For example, "git commit --help" has

        `--short`::
                When doing a dry-run, give the output in the short-format. See
                linkgit:git-status[1] for details. Implies `--dry-run`.

The `--short` option does not make any sense when you are actually
creating a commit; if you say "git commit --short", the command
behaves as if you said "git commit --dry-run --short".

If something works only under certain condition, it seems we tend to
say "this only works when/with ...", so in this case, we would have
said "The -U<n> option is only effective for --interactive or --patch
mode of this command" to express what you meant to convey, I think.

Thanks.


^ permalink raw reply	[flat|nested] 77+ messages in thread

* [PATCH v2 0/4] Better support for customising context lines in --patch commands
  2025-05-05  9:18 [PATCH 0/3] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
                   ` (2 preceding siblings ...)
  2025-05-05  9:18 ` [PATCH 3/3] add-interactive: add new "context" subcommand Leon Michalak via GitGitGadget
@ 2025-05-10 13:46 ` Leon Michalak via GitGitGadget
  2025-05-10 13:46   ` [PATCH v2 1/4] test: refactor to use "test_grep" Leon Michalak via GitGitGadget
                     ` (4 more replies)
  3 siblings, 5 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-05-10 13:46 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

This series of patches attempt to give --interactive/--patch compatible
builtins ("add", "commit", "checkout", "reset", "restore" and "stash")
better support and nicer experience for configuring how many context lines
are shown in diffs through a variety of ways.

Prior to these patches, the user could not choose how many context lines
they saw in --patch commands (apart from one workaround by using
GIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a
persistent solution). Additionally, the behaviour around reading from the
diff.context and diff.interHunkContext configs was also inconsistent with
other diff generating commands such as "log -p".

The summarised changes below hopefully make this experience better and fix
some inconsistencies:

 * diff.context and diff.interHunkContext configs are now respected by
   --patch compatible commands
 * --unified and --inter-hunk-context command line options have been added
   to --patch compatible commands (which take prescendence over file
   configs)
 * "add" and "commit" in --interactive mode now expose a new "context"
   subcommand which configures the amount of context lines you wish to see
   in subsequent diffs generated from other subcommands such as "patch" or
   "diff"

The original discussion for this can be read at:

 * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/

Changes since v1:

 * Update commit descriptions
 * Update tests to use the more modern and robust test_grep and test_config
   utils
 * Reword some documentation / user messages
 * Ensure each commit is atomic and builds/passes tests on it's own
 * Make new command line options DRY
 * Add tests for interhunk context interaction
 * Error if context config/command line options are negative
 * Drop previous last commit to do with new subcommand for --interactive
   add/commit. My motivations behind this patch series originally where
   quite simple, just for add-patch commands to respect context configs.
   This subcommand, after the discussion in v1, will require more thought
   and a larger implementation that what I had anticipated. I would prefer
   to leave this for another time as it's the least impactful but the most
   time intensive and complicated idea.

Leon Michalak (4):
  test: refactor to use "test_grep"
  test: refactor to use "test_config"
  add-patch: respect diff.context configuration
  add-patch: add diff.context command line overrides

 Documentation/diff-context-options.adoc |  10 ++
 Documentation/git-add.adoc              |   2 +
 Documentation/git-checkout.adoc         |   2 +
 Documentation/git-commit.adoc           |   2 +
 Documentation/git-reset.adoc            |   2 +
 Documentation/git-restore.adoc          |   2 +
 Documentation/git-stash.adoc            |   2 +
 add-interactive.c                       |  53 +++++++--
 add-interactive.h                       |  17 ++-
 add-patch.c                             |  11 +-
 builtin/add.c                           |  16 ++-
 builtin/checkout.c                      |  24 +++-
 builtin/commit.c                        |  11 +-
 builtin/reset.c                         |  12 +-
 builtin/stash.c                         |  46 ++++++--
 commit.h                                |   3 +-
 parse-options.h                         |   2 +
 t/t3701-add-interactive.sh              |  48 ++++----
 t/t4032-diff-inter-hunk-context.sh      |  61 ++++++++++
 t/t4055-diff-context.sh                 | 142 ++++++++++++++++++++----
 t/t9902-completion.sh                   |   2 +
 21 files changed, 390 insertions(+), 80 deletions(-)
 create mode 100644 Documentation/diff-context-options.adoc


base-commit: f65182a99e545d2f2bc22e6c1c2da192133b16a3
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/1915

Range-diff vs v1:

 -:  ----------- > 1:  4f92a1b4c24 test: refactor to use "test_grep"
 -:  ----------- > 2:  75424cb8e1c test: refactor to use "test_config"
 1:  8b91eef8120 ! 3:  f16d3de8611 add-patch: respect diff.context configuration
     @@ Metadata
       ## Commit message ##
          add-patch: respect diff.context configuration
      
     -    This aims to teach relevant builtins (that take in `--patch`) to respect
     +    Various builtins that use add-patch infrastructure do not respect
          the user's diff.context and diff.interHunkContext file configurations.
     -
     -    Since these are both UI options and `--patch` is designed for the end user,
     -    I believe this was previously just an inconsistency, which this patch hopes
     -    to address.
     +    This patch fixes this inconsistency.
      
          Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
      
       ## add-interactive.c ##
     -@@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)
     +@@ add-interactive.c: static void init_color(struct repository *r, struct add_i_state *s,
     + void init_add_i_state(struct add_i_state *s, struct repository *r)
     + {
       	const char *value;
     ++	int context;
     ++	int interhunkcontext;
       
       	s->r = r;
      +	s->context = -1;
     @@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repositor
       	repo_config_get_string(r, "diff.algorithm",
       			       &s->interactive_diff_algorithm);
       
     -+	repo_config_get_int(r, "diff.context", &s->context);
     -+	repo_config_get_int(r, "diff.interHunkContext", &s->interhunkcontext);
     ++	if (!repo_config_get_int(r, "diff.context", &context)) {
     ++		if (context < 0)
     ++			die(_("%s cannot be negative"), "diff.context");
     ++		else
     ++			s->context = context;
     ++	};
     ++	if (!repo_config_get_int(r, "diff.interHunkContext", &interhunkcontext)) {
     ++		if (interhunkcontext < 0)
     ++			die(_("%s cannot be negative"), "diff.interHunkContext");
     ++		else
     ++			s->interhunkcontext = interhunkcontext;
     ++	};
      +
       	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
       	if (s->use_single_key)
       		setbuf(stdin, NULL);
     -@@ add-interactive.c: static int run_diff(struct add_i_state *s, const struct pathspec *ps,
     - 	if (count > 0) {
     - 		struct child_process cmd = CHILD_PROCESS_INIT;
     +
     + ## add-interactive.h ##
     +@@ add-interactive.h: struct add_i_state {
     + 
     + 	int use_single_key;
     + 	char *interactive_diff_filter, *interactive_diff_algorithm;
     ++	int context, interhunkcontext;
     + };
       
     --		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached",
     --			     oid_to_hex(!is_initial ? &oid :
     --					s->r->hash_algo->empty_tree),
     --			     "--", NULL);
     -+		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL);
     -+		if (s->context != -1)
     -+			strvec_pushf(&cmd.args, "--unified=%i", s->context);
     -+		if (s->interhunkcontext != -1)
     -+			strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->interhunkcontext);
     -+		strvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :
     -+			     s->r->hash_algo->empty_tree), "--", NULL);
     - 		for (i = 0; i < files->items.nr; i++)
     - 			if (files->selected[i])
     - 				strvec_push(&cmd.args,
     + void init_add_i_state(struct add_i_state *s, struct repository *r);
      
       ## add-patch.c ##
      @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
     @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec
      
       ## t/t4055-diff-context.sh ##
      @@ t/t4055-diff-context.sh: test_expect_success 'diff.context honored by "log"' '
     - 	! grep firstline output &&
     - 	git config diff.context 8 &&
     - 	git log -1 -p >output &&
     --	grep "^ firstline" output
     -+	grep "^ firstline" output &&
     -+	git config --unset diff.context
     -+'
     -+
     + 	test_grep "^ firstline" output
     + '
     + 
      +test_expect_success 'diff.context honored by "add"' '
      +	git add -p >output &&
     -+	! grep firstline output &&
     -+	git config diff.context 8 &&
     -+	git add -p >output &&
     -+	grep "^ firstline" output &&
     -+	git config --unset diff.context
     ++	test_grep ! firstline output &&
     ++	test_config diff.context 8 &&
     ++	git log -1 -p >output &&
     ++	test_grep "^ firstline" output
      +'
      +
      +test_expect_success 'diff.context honored by "commit"' '
      +	! git commit -p >output &&
     -+	! grep firstline output &&
     -+	git config diff.context 8 &&
     ++	test_grep ! firstline output &&
     ++	test_config diff.context 8 &&
      +	! git commit -p >output &&
     -+	grep "^ firstline" output &&
     -+	git config --unset diff.context
     ++	test_grep "^ firstline" output
      +'
      +
      +test_expect_success 'diff.context honored by "checkout"' '
      +	git checkout -p >output &&
     -+	! grep firstline output &&
     -+	git config diff.context 8 &&
     ++	test_grep ! firstline output &&
     ++	test_config diff.context 8 &&
      +	git checkout -p >output &&
     -+	grep "^ firstline" output &&
     -+	git config --unset diff.context
     ++	test_grep "^ firstline" output
      +'
      +
      +test_expect_success 'diff.context honored by "stash"' '
      +	! git stash -p >output &&
     -+	! grep firstline output &&
     -+	git config diff.context 8 &&
     ++	test_grep ! firstline output &&
     ++	test_config diff.context 8 &&
      +	! git stash -p >output &&
     -+	grep "^ firstline" output &&
     -+	git config --unset diff.context
     ++	test_grep "^ firstline" output
      +'
      +
      +test_expect_success 'diff.context honored by "restore"' '
      +	git restore -p >output &&
     -+	! grep firstline output &&
     -+	git config diff.context 8 &&
     ++	test_grep ! firstline output &&
     ++	test_config diff.context 8 &&
      +	git restore -p >output &&
     -+	grep "^ firstline" output &&
     -+	git config --unset diff.context
     ++	test_grep "^ firstline" output
     ++'
     ++
     + test_expect_success 'The -U option overrides diff.context' '
     + 	test_config diff.context 8 &&
     + 	git log -U4 -1 >output &&
     +@@ t/t4055-diff-context.sh: test_expect_success 'negative integer config parsing' '
     + 	test_grep "bad config variable" output
       '
       
     - test_expect_success 'The -U option overrides diff.context' '
     ++test_expect_success 'negative integer config parsing by "add"' '
     ++	test_config diff.context -1 &&
     ++	test_must_fail git add -p 2>output &&
     ++	test_grep "diff.context cannot be negative" output
     ++'
     ++
     ++test_expect_success 'negative integer config parsing by "commit"' '
     ++	test_config diff.context -1 &&
     ++	test_must_fail git commit -p 2>output &&
     ++	test_grep "bad config variable" output
     ++'
     ++
     ++test_expect_success 'negative integer config parsing by "checkout"' '
     ++	test_config diff.context -1 &&
     ++	test_must_fail git checkout -p 2>output &&
     ++	test_grep "diff.context cannot be negative" output
     ++'
     ++
     ++test_expect_success 'negative integer config parsing by "stash"' '
     ++	test_config diff.context -1 &&
     ++	test_must_fail git stash -p 2>output &&
     ++	test_grep "diff.context cannot be negative" output
     ++'
     ++
     ++test_expect_success 'negative integer config parsing by "restore"' '
     ++	test_config diff.context -1 &&
     ++	test_must_fail git restore -p 2>output &&
     ++	test_grep "diff.context cannot be negative" output
     ++'
     ++
     + test_expect_success '-U0 is valid, so is diff.context=0' '
     + 	test_config diff.context 0 &&
     + 	git diff >output &&
 2:  7700eb173e7 ! 4:  973dfadd1b3 add-patch: add diff.context command line overrides
     @@ Metadata
       ## Commit message ##
          add-patch: add diff.context command line overrides
      
     -    This patch compliments `8b91eef812`, where builtins that accept
     -    `--patch` options now respect `diff.context` and `diff.interHunkContext`
     -    file configurations.
     +    This patch compliments the previous commit, where builtins that use
     +    add-patch infrastructure now respect diff.context and
     +    diff.interHunkContext file configurations.
      
          In particular, this patch helps users who don't want to set persistent
          context configurations or just want a way to override them on a one-time
          basis, by allowing the relevant builtins to accept corresponding command
          line options that override the file configurations.
      
     -    This mimics `diff` which allows for both context file configuration and
     -    command line overrides.
     +    This mimics commands such as diff and log, which allow for both context
     +    file configuration and command line overrides.
      
          Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
      
     - ## Documentation/git-add.adoc ##
     -@@ Documentation/git-add.adoc: This effectively runs `add --interactive`, but bypasses the
     - initial command menu and directly jumps to the `patch` subcommand.
     - See ``Interactive mode'' for details.
     - 
     + ## Documentation/diff-context-options.adoc (new) ##
     +@@
      +`-U<n>`::
      +`--unified=<n>`::
      +	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
     -+	or 3 if the config option is unset. Implies `--interactive/--patch`.
     ++	or 3 if the config option is unset.
      +
      +`--inter-hunk-context=<n>`::
      +	Show the context between diff hunks, up to the specified _<number>_
      +	of lines, thereby fusing hunks that are close to each other.
      +	Defaults to `diff.interHunkContext` or 0 if the config option
     -+	is unset. Implies `--interactive/--patch`.
     ++	is unset.
     +
     + ## Documentation/git-add.adoc ##
     +@@ Documentation/git-add.adoc: This effectively runs `add --interactive`, but bypasses the
     + initial command menu and directly jumps to the `patch` subcommand.
     + See ``Interactive mode'' for details.
     + 
     ++include::diff-context-options.adoc[]
      +
       `-e`::
       `--edit`::
     @@ Documentation/git-checkout.adoc: section of linkgit:git-add[1] to learn how to o
       Note that this option uses the no overlay mode by default (see also
       `--overlay`), and currently doesn't support overlay mode.
       
     -+`-U<n>`::
     -+`--unified=<n>`::
     -+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
     -+	or 3 if the config option is unset. Implies `--patch`.
     -+
     -+`--inter-hunk-context=<n>`::
     -+	Show the context between diff hunks, up to the specified _<number>_
     -+	of lines, thereby fusing hunks that are close to each other.
     -+	Defaults to `diff.interHunkContext` or 0 if the config option
     -+	is unset. Implies `--patch`.
     ++include::diff-context-options.adoc[]
      +
       --ignore-other-worktrees::
       	`git checkout` refuses when the wanted branch is already checked
     @@ Documentation/git-commit.adoc: OPTIONS
       	which changes to commit. See linkgit:git-add[1] for
       	details.
       
     -+`-U<n>`::
     -+`--unified=<n>`::
     -+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
     -+	or 3 if the config option is unset. Implies `--interactive/--patch`.
     -+
     -+`--inter-hunk-context=<n>`::
     -+	Show the context between diff hunks, up to the specified _<number>_
     -+	of lines, thereby fusing hunks that are close to each other.
     -+	Defaults to `diff.interHunkContext` or 0 if the config option
     -+	is unset. Implies `--interactive/--patch`.
     ++include::diff-context-options.adoc[]
      +
       `-C <commit>`::
       `--reuse-message=<commit>`::
     @@ Documentation/git-reset.adoc: OPTIONS
       	separated with _NUL_ character and all other characters are taken
       	literally (including newlines and quotes).
       
     -+`-U<n>`::
     -+`--unified=<n>`::
     -+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
     -+	or 3 if the config option is unset. Implies `--patch`.
     -+
     -+`--inter-hunk-context=<n>`::
     -+	Show the context between diff hunks, up to the specified _<number>_
     -+	of lines, thereby fusing hunks that are close to each other.
     -+	Defaults to `diff.interHunkContext` or 0 if the config option
     -+	is unset. Implies `--patch`.
     ++include::diff-context-options.adoc[]
      +
       `--`::
       	Do not interpret any more arguments as options.
     @@ Documentation/git-restore.adoc: leave out at most one of _<rev-A>__ and _<rev-B>
       	Mode" section of linkgit:git-add[1] to learn how to operate
       	the `--patch` mode.
       
     -+`-U<n>`::
     -+`--unified=<n>`::
     -+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
     -+	or 3 if the config option is unset. Implies `--patch`.
     -+
     -+`--inter-hunk-context=<n>`::
     -+	Show the context between diff hunks, up to the specified _<number>_
     -+	of lines, thereby fusing hunks that are close to each other.
     -+	Defaults to `diff.interHunkContext` or 0 if the config option
     -+	is unset. Implies `--patch`.
     ++include::diff-context-options.adoc[]
      +
       `-W`::
       `--worktree`::
     @@ Documentation/git-stash.adoc: to learn how to operate the `--patch` mode.
       The `--patch` option implies `--keep-index`.  You can use
       `--no-keep-index` to override this.
       
     -+`-U<n>`::
     -+`--unified=<n>`::
     -+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
     -+	or 3 if the config option is unset. Implies `--patch`.
     -+
     -+`--inter-hunk-context=<n>`::
     -+	Show the context between diff hunks, up to the specified _<number>_
     -+	of lines, thereby fusing hunks that are close to each other.
     -+	Defaults to `diff.interHunkContext` or 0 if the config option
     -+	is unset. Implies `--patch`.
     ++include::diff-context-options.adoc[]
      +
       -S::
       --staged::
     @@ add-interactive.c: static void init_color(struct repository *r, struct add_i_sta
      +		      struct add_p_opt *add_p_opt)
       {
       	const char *value;
     - 
     + 	int context;
      @@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)
       	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
       	if (s->use_single_key)
       		setbuf(stdin, NULL);
      +
     -+	if (add_p_opt->context != -1)
     ++	if (add_p_opt->context != -1) {
     ++		if (add_p_opt->context < 0)
     ++			die(_("%s cannot be negative"), "--unified");
      +		s->context = add_p_opt->context;
     -+	if (add_p_opt->interhunkcontext != -1)
     ++	}
     ++	if (add_p_opt->interhunkcontext != -1) {
     ++		if (add_p_opt->interhunkcontext < 0)
     ++			die(_("%s cannot be negative"), "--inter-hunk-context");
      +		s->interhunkcontext = add_p_opt->interhunkcontext;
     ++	}
       }
       
       void clear_add_i_state(struct add_i_state *s)
     @@ add-interactive.c: static int run_patch(struct add_i_state *s, const struct path
       		strvec_clear(&args);
       		clear_pathspec(&ps_selected);
       	}
     +@@ add-interactive.c: static int run_diff(struct add_i_state *s, const struct pathspec *ps,
     + 	if (count > 0) {
     + 		struct child_process cmd = CHILD_PROCESS_INIT;
     + 
     +-		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached",
     +-			     oid_to_hex(!is_initial ? &oid :
     +-					s->r->hash_algo->empty_tree),
     +-			     "--", NULL);
     ++		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL);
     ++		if (s->context != -1)
     ++			strvec_pushf(&cmd.args, "--unified=%i", s->context);
     ++		if (s->interhunkcontext != -1)
     ++			strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->interhunkcontext);
     ++		strvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :
     ++			     s->r->hash_algo->empty_tree), "--", NULL);
     + 		for (i = 0; i < files->items.nr; i++)
     + 			if (files->selected[i])
     + 				strvec_push(&cmd.args,
      @@ add-interactive.c: static void command_prompt_help(struct add_i_state *s)
       			 _("(empty) select nothing"));
       }
     @@ add-interactive.h
       
       #include "color.h"
       
     -+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }
     -+
      +struct add_p_opt {
      +	int context;
      +	int interhunkcontext;
      +};
     ++
     ++#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }
      +
       struct add_i_state {
       	struct repository *r;
       	int use_color;
      @@ add-interactive.h: struct add_i_state {
     - 
     - 	int use_single_key;
     - 	char *interactive_diff_filter, *interactive_diff_algorithm;
     -+	int context, interhunkcontext;
     + 	int context, interhunkcontext;
       };
       
      -void init_add_i_state(struct add_i_state *s, struct repository *r);
     @@ builtin/add.c: int interactive_add(struct repository *repo,
       
       	if (patch)
      -		ret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);
     -+		ret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL,
     -+				  &pathspec);
     ++		ret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec);
       	else
      -		ret = !!run_add_i(repo, &pathspec);
      +		ret = !!run_add_i(repo, &pathspec, add_p_opt);
     @@ builtin/add.c: static struct option builtin_add_options[] = {
       	OPT_GROUP(""),
       	OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
       	OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
     -+	OPT_INTEGER_F('U', "unified", &add_p_opt.context,
     -+		      N_("generate diffs with <n> lines context, implies --interactive/--patch"),
     -+		      PARSE_OPT_NONEG),
     -+	OPT_INTEGER_F(0, "inter-hunk-context", &add_p_opt.interhunkcontext,
     -+		      N_("show context between diff hunks up to the specified number of lines, implies --interactive/--patch"),
     -+		      PARSE_OPT_NONEG),
     ++	OPT_DIFF_UNIFIED(&add_p_opt.context),
     ++	OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
       	OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
       	OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
       	OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
     @@ builtin/checkout.c: static struct option *add_checkout_path_options(struct check
       			      N_("checkout their version for unmerged files"),
       			      3, PARSE_OPT_NONEG),
       		OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
     -+		OPT_INTEGER_F('U', "unified", &opts->patch_context,
     -+			N_("generate diffs with <n> lines context, implies --patch"),
     -+			PARSE_OPT_NONEG),
     -+		OPT_INTEGER_F(0, "inter-hunk-context", &opts->patch_interhunk_context,
     -+			N_("show context between diff hunks up to the specified number of lines, implies --patch"),
     -+			PARSE_OPT_NONEG),
     ++		OPT_DIFF_UNIFIED(&opts->patch_context),
     ++		OPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context),
       		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
       			 N_("do not limit pathspecs to sparse entries only")),
       		OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),
     @@ builtin/commit.c: int cmd_commit(int argc,
       		OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")),
       		OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
       		OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
     -+		OPT_INTEGER_F('U', "unified", &add_p_opt.context,
     -+			      N_("generate diffs with <n> lines context, implies --interactive/--patch"),
     -+			      PARSE_OPT_NONEG),
     -+		OPT_INTEGER_F(0, "inter-hunk-context", &add_p_opt.interhunkcontext,
     -+			      N_("show context between diff hunks up to the specified number of lines, implies --interactive/--patch"),
     -+			      PARSE_OPT_NONEG),
     ++		OPT_DIFF_UNIFIED(&add_p_opt.context),
     ++		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
       		OPT_BOOL('o', "only", &only, N_("commit only specified files")),
       		OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")),
       		OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
     @@ builtin/reset.c: int cmd_reset(int argc,
       			       PARSE_OPT_OPTARG,
       			       option_parse_recurse_submodules_worktree_updater),
       		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
     -+		OPT_INTEGER_F('U', "unified", &add_p_opt.context,
     -+			       N_("generate diffs with <n> lines context, implies --patch"),
     -+			       PARSE_OPT_NONEG),
     -+		OPT_INTEGER_F(0, "inter-hunk-context", &add_p_opt.interhunkcontext,
     -+			       N_("show context between diff hunks up to the specified number of lines, implies --patch"),
     -+			       PARSE_OPT_NONEG),
     ++		OPT_DIFF_UNIFIED(&add_p_opt.context),
     ++		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
       		OPT_BOOL('N', "intent-to-add", &intent_to_add,
       				N_("record only the fact that removed paths will be added later")),
       		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
     @@ builtin/stash.c: static int push_stash(int argc, const char **argv, const char *
       			 N_("stash staged changes only")),
       		OPT_BOOL('p', "patch", &patch_mode,
       			 N_("stash in patch mode")),
     -+		OPT_INTEGER_F('U', "unified", &add_p_opt.context,
     -+			      N_("generate diffs with <n> lines context, implies --patch"),
     -+			      PARSE_OPT_NONEG),
     -+		OPT_INTEGER_F(0, "inter-hunk-context", &add_p_opt.interhunkcontext,
     -+			      N_("show context between diff hunks up to the specified number of lines, implies --patch"),
     -+			      PARSE_OPT_NONEG),
     ++		OPT_DIFF_UNIFIED(&add_p_opt.context),
     ++		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
       		OPT__QUIET(&quiet, N_("quiet mode")),
       		OPT_BOOL('u', "include-untracked", &include_untracked,
       			 N_("include untracked files in stash")),
     @@ builtin/stash.c: static int save_stash(int argc, const char **argv, const char *
       			 N_("stash staged changes only")),
       		OPT_BOOL('p', "patch", &patch_mode,
       			 N_("stash in patch mode")),
     -+		OPT_INTEGER_F('U', "unified", &add_p_opt.context,
     -+			      N_("generate diffs with <n> lines context, implies --patch"),
     -+			      PARSE_OPT_NONEG),
     -+		OPT_INTEGER_F(0, "inter-hunk-context", &add_p_opt.interhunkcontext,
     -+			      N_("show context between diff hunks up to the specified number of lines, implies --patch"),
     -+			      PARSE_OPT_NONEG),
     ++		OPT_DIFF_UNIFIED(&add_p_opt.context),
     ++		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
       		OPT__QUIET(&quiet, N_("quiet mode")),
       		OPT_BOOL('u', "include-untracked", &include_untracked,
       			 N_("include untracked files in stash")),
     @@ commit.h: int for_each_commit_graft(each_commit_graft_fn, void *);
       struct commit_extra_header {
       	struct commit_extra_header *next;
      
     + ## parse-options.h ##
     +@@ parse-options.h: int parse_opt_tracking_mode(const struct option *, const char *, int);
     + #define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file"))
     + #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"))
     + #define OPT_AUTOSTASH(v) OPT_BOOL(0, "autostash", v, N_("automatically stash/stash pop before and after"))
     ++#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', "unified", v, N_("generate diffs with <n> lines context"), PARSE_OPT_NONEG)
     ++#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)
     + 
     + #define OPT_IPVERSION(v) \
     + 	OPT_SET_INT_F('4', "ipv4", (v), N_("use IPv4 addresses only"), \
     +
     + ## t/t4032-diff-inter-hunk-context.sh ##
     +@@ t/t4032-diff-inter-hunk-context.sh: t() {
     + 	"
     + }
     + 
     ++t_patch() {
     ++	use_config=
     ++	git config --unset diff.interHunkContext
     ++
     ++	case $# in
     ++	4) hunks=$4; cmd="add -p -U$3";;
     ++	5) hunks=$5; cmd="add -p -U$3 --inter-hunk-context=$4";;
     ++	6) hunks=$5; cmd="add -p -U$3"; git config diff.interHunkContext $4; use_config="(diff.interHunkContext=$4) ";;
     ++	esac
     ++	label="$use_config$cmd, $1 common $2"
     ++	file=f$1
     ++
     ++	if ! test -f $file
     ++	then
     ++		f A $1 B >$file
     ++		git add $file
     ++		git commit -q -m. $file
     ++		f X $1 Y >$file
     ++	fi
     ++
     ++	test_expect_success "$label: count hunks ($hunks)" "
     ++		test $(test_write_lines q | git $cmd $file | sed -n 's/^([0-9]*\/\([0-9]*\)) Stage this hunk.*/\1/p') = $hunks
     ++	"
     ++}
     ++
     + cat <<EOF >expected.f1.0.1 || exit 1
     + diff --git a/f1 b/f1
     + --- a/f1
     +@@ t/t4032-diff-inter-hunk-context.sh: t 3 lines	1	2	1	config
     + t 9 lines	3	2	2	config
     + t 9 lines	3	3	1	config
     + 
     ++# common lines	ctx	intrctx	hunks
     ++t_patch 1 line	0		2
     ++t_patch 1 line	0	0	2
     ++t_patch 1 line	0	1	1
     ++t_patch 1 line	0	2	1
     ++t_patch 1 line	1		1
     ++
     ++t_patch 2 lines	0		2
     ++t_patch 2 lines	0	0	2
     ++t_patch 2 lines	0	1	2
     ++t_patch 2 lines	0	2	1
     ++t_patch 2 lines	1		1
     ++
     ++t_patch 3 lines	1		2
     ++t_patch 3 lines	1	0	2
     ++t_patch 3 lines	1	1	1
     ++t_patch 3 lines	1	2	1
     ++
     ++t_patch 9 lines	3		2
     ++t_patch 9 lines	3	2	2
     ++t_patch 9 lines	3	3	1
     ++
     ++#					use diff.interHunkContext?
     ++t_patch 1 line	0	0	2	config
     ++t_patch 1 line	0	1	1	config
     ++t_patch 1 line	0	2	1	config
     ++t_patch 9 lines	3	3	1	config
     ++t_patch 2 lines	0	0	2	config
     ++t_patch 2 lines	0	1	2	config
     ++t_patch 2 lines	0	2	1	config
     ++t_patch 3 lines	1	0	2	config
     ++t_patch 3 lines	1	1	1	config
     ++t_patch 3 lines	1	2	1	config
     ++t_patch 9 lines	3	2	2	config
     ++t_patch 9 lines	3	3	1	config
     ++
     + test_expect_success 'diff.interHunkContext invalid' '
     + 	git config diff.interHunkContext asdf &&
     + 	test_must_fail git diff &&
     +
       ## t/t4055-diff-context.sh ##
      @@ t/t4055-diff-context.sh: test_expect_success 'The -U option overrides diff.context' '
     - 	! grep "^ firstline" output
     + 	test_grep ! "^ firstline" output
       '
       
      +test_expect_success 'The -U option overrides diff.context for "add"' '
     -+	git config diff.context 8 &&
     ++	test_config diff.context 8 &&
      +	git add -U4 -p >output &&
     -+	! grep "^ firstline" output
     ++	test_grep ! "^ firstline" output
      +'
      +
      +test_expect_success 'The -U option overrides diff.context for "commit"' '
     -+	git config diff.context 8 &&
     ++	test_config diff.context 8 &&
      +	! git commit -U4 -p >output &&
     -+	! grep "^ firstline" output
     ++	test_grep ! "^ firstline" output
      +'
      +
      +test_expect_success 'The -U option overrides diff.context for "checkout"' '
     -+	git config diff.context 8 &&
     ++	test_config diff.context 8 &&
      +	git checkout -U4 -p >output &&
     -+	! grep "^ firstline" output
     ++	test_grep ! "^ firstline" output
      +'
      +
      +test_expect_success 'The -U option overrides diff.context for "stash"' '
     -+	git config diff.context 8 &&
     ++	test_config diff.context 8 &&
      +	! git stash -U4 -p >output &&
     -+	! grep "^ firstline" output
     ++	test_grep ! "^ firstline" output
      +'
      +
      +test_expect_success 'The -U option overrides diff.context for "restore"' '
     -+	git config diff.context 8 &&
     ++	test_config diff.context 8 &&
      +	git restore -U4 -p >output &&
     -+	! grep "^ firstline" output
     ++	test_grep ! "^ firstline" output
      +'
      +
       test_expect_success 'diff.context honored by "diff"' '
     - 	git config diff.context 8 &&
     + 	test_config diff.context 8 &&
       	git diff >output &&
      
       ## t/t9902-completion.sh ##
 3:  b4b7854f330 < -:  ----------- add-interactive: add new "context" subcommand

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 77+ messages in thread

* [PATCH v2 1/4] test: refactor to use "test_grep"
  2025-05-10 13:46 ` [PATCH v2 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
@ 2025-05-10 13:46   ` Leon Michalak via GitGitGadget
  2025-05-12 13:42     ` Junio C Hamano
  2025-05-10 13:46   ` [PATCH v2 2/4] test: refactor to use "test_config" Leon Michalak via GitGitGadget
                     ` (3 subsequent siblings)
  4 siblings, 1 reply; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-05-10 13:46 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

Refactor to use the modern "test_grep" test utility instead of regular
"grep" which provides better debug information if tests fail.

This is a prerequisite to the commits that follow which add to both test
files.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 t/t3701-add-interactive.sh | 48 +++++++++++++++++++-------------------
 t/t4055-diff-context.sh    | 28 +++++++++++-----------
 2 files changed, 38 insertions(+), 38 deletions(-)

diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index b8a05d95f3f1..b088ee141ff4 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -63,7 +63,7 @@ test_expect_success 'setup (initial)' '
 '
 test_expect_success 'status works (initial)' '
 	git add -i </dev/null >output &&
-	grep "+1/-0 *+2/-0 file" output
+	test_grep "+1/-0 *+2/-0 file" output
 '
 
 test_expect_success 'setup expected' '
@@ -86,7 +86,7 @@ test_expect_success 'revert works (initial)' '
 	git add file &&
 	test_write_lines r 1 | git add -i &&
 	git ls-files >output &&
-	! grep . output
+	test_grep ! . output
 '
 
 test_expect_success 'add untracked (multiple)' '
@@ -109,7 +109,7 @@ test_expect_success 'setup (commit)' '
 '
 test_expect_success 'status works (commit)' '
 	git add -i </dev/null >output &&
-	grep "+1/-0 *+2/-0 file" output
+	test_grep "+1/-0 *+2/-0 file" output
 '
 
 test_expect_success 'update can stage deletions' '
@@ -141,7 +141,7 @@ test_expect_success 'revert works (commit)' '
 	git add file &&
 	test_write_lines r 1 | git add -i &&
 	git add -i </dev/null >output &&
-	grep "unchanged *+3/-0 file" output
+	test_grep "unchanged *+3/-0 file" output
 '
 
 test_expect_success 'reject multi-key input' '
@@ -185,7 +185,7 @@ test_expect_success 'setup fake editor' '
 test_expect_success 'bad edit rejected' '
 	git reset &&
 	test_write_lines e n d | git add -p >output &&
-	grep "hunk does not apply" output
+	test_grep "hunk does not apply" output
 '
 
 test_expect_success 'setup patch' '
@@ -198,7 +198,7 @@ test_expect_success 'setup patch' '
 test_expect_success 'garbage edit rejected' '
 	git reset &&
 	test_write_lines e n d | git add -p >output &&
-	grep "hunk does not apply" output
+	test_grep "hunk does not apply" output
 '
 
 test_expect_success 'setup patch' '
@@ -313,8 +313,8 @@ test_expect_success FILEMODE 'stage mode and hunk' '
 	chmod +x file &&
 	printf "y\\ny\\n" | git add -p &&
 	git diff --cached file >out &&
-	grep "new mode" out &&
-	grep "+content" out &&
+	test_grep "new mode" out &&
+	test_grep "+content" out &&
 	git diff file >out &&
 	test_must_be_empty out
 '
@@ -636,7 +636,7 @@ test_expect_success 'split hunk "add -p (edit)"' '
 	printf "%s\n" s e     q n q q |
 	EDITOR=: git add -p &&
 	git diff >actual &&
-	! grep "^+15" actual
+	test_grep ! "^+15" actual
 '
 
 test_expect_success 'split hunk "add -p (no, yes, edit)"' '
@@ -648,7 +648,7 @@ test_expect_success 'split hunk "add -p (no, yes, edit)"' '
 	EDITOR=: git add -p 2>error &&
 	test_must_be_empty error &&
 	git diff >actual &&
-	! grep "^+31" actual
+	test_grep ! "^+31" actual
 '
 
 test_expect_success 'split hunk with incomplete line at end' '
@@ -682,7 +682,7 @@ test_expect_success 'edit, adding lines to the first hunk' '
 	EDITOR=./fake_editor.sh git add -p 2>error &&
 	test_must_be_empty error &&
 	git diff --cached >actual &&
-	grep "^+22" actual
+	test_grep "^+22" actual
 '
 
 test_expect_success 'patch mode ignores unmerged entries' '
@@ -696,7 +696,7 @@ test_expect_success 'patch mode ignores unmerged entries' '
 	test_must_fail git merge side &&
 	echo changed >non-conflict.t &&
 	echo y | git add -p >output &&
-	! grep a/conflict.t output &&
+	test_grep ! a/conflict.t output &&
 	cat >expected <<-\EOF &&
 	* Unmerged path conflict.t
 	diff --git a/non-conflict.t b/non-conflict.t
@@ -728,7 +728,7 @@ test_expect_success 'diffs can be colorized' '
 
 	# We do not want to depend on the exact coloring scheme
 	# git uses for diffs, so just check that we saw some kind of color.
-	grep "$(printf "\\033")" output
+	test_grep "$(printf "\\033")" output
 '
 
 test_expect_success 'colors can be overridden' '
@@ -743,7 +743,7 @@ test_expect_success 'colors can be overridden' '
 		-c color.interactive.error=blue \
 		add -i 2>err.raw <input &&
 	test_decode_color <err.raw >err &&
-	grep "<BLUE>Huh (trigger)?<RESET>" err &&
+	test_grep "<BLUE>Huh (trigger)?<RESET>" err &&
 
 	test_write_lines help quit >input &&
 	force_color git \
@@ -863,7 +863,7 @@ test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '
 	printf y >y &&
 	force_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&
 	test_decode_color <output.raw >output &&
-	grep "old<" output
+	test_grep "old<" output
 '
 
 test_expect_success 'diffFilter filters diff' '
@@ -876,7 +876,7 @@ test_expect_success 'diffFilter filters diff' '
 
 	# avoid depending on the exact coloring or content of the prompts,
 	# and just make sure we saw our diff prefixed
-	grep foo:.*content output
+	test_grep foo:.*content output
 '
 
 test_expect_success 'detect bogus diffFilter output' '
@@ -886,7 +886,7 @@ test_expect_success 'detect bogus diffFilter output' '
 	test_config interactive.diffFilter "sed 6d" &&
 	printf y >y &&
 	force_color test_must_fail git add -p <y >output 2>&1 &&
-	grep "mismatched output" output
+	test_grep "mismatched output" output
 '
 
 test_expect_success 'handle iffy colored hunk headers' '
@@ -896,7 +896,7 @@ test_expect_success 'handle iffy colored hunk headers' '
 	printf n >n &&
 	force_color git -c interactive.diffFilter="sed s/.*@@.*/XX/" \
 		add -p >output 2>&1 <n &&
-	grep "^XX$" output
+	test_grep "^XX$" output
 '
 
 test_expect_success 'handle very large filtered diff' '
@@ -1002,7 +1002,7 @@ test_expect_success 'add -p does not expand argument lists' '
 	# update it, but we want to be sure that our "." pathspec
 	# was not expanded into the argument list of any command.
 	# So look only for "not-changed".
-	! grep -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
+	test_grep ! -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
 '
 
 test_expect_success 'hunk-editing handles custom comment char' '
@@ -1072,21 +1072,21 @@ test_expect_success 'setup different kinds of dirty submodules' '
 
 test_expect_success 'status ignores dirty submodules (except HEAD)' '
 	git -C for-submodules add -i </dev/null >output &&
-	grep dirty-head output &&
-	grep dirty-both-ways output &&
-	! grep dirty-otherwise output
+	test_grep dirty-head output &&
+	test_grep dirty-both-ways output &&
+	test_grep ! dirty-otherwise output
 '
 
 test_expect_success 'handle submodules' '
 	echo 123 >>for-submodules/dirty-otherwise/initial.t &&
 
 	force_color git -C for-submodules add -p dirty-otherwise >output 2>&1 &&
-	grep "No changes" output &&
+	test_grep "No changes" output &&
 
 	force_color git -C for-submodules add -p dirty-head >output 2>&1 <y &&
 	git -C for-submodules ls-files --stage dirty-head >actual &&
 	rev="$(git -C for-submodules/dirty-head rev-parse HEAD)" &&
-	grep "$rev" actual
+	test_grep "$rev" actual
 '
 
 test_expect_success 'set up pathological context' '
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index ec2804eea67c..c66f966a3ab3 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -38,36 +38,36 @@ test_expect_success 'setup' '
 
 test_expect_success 'the default number of context lines is 3' '
 	git diff >output &&
-	! grep "^ d" output &&
-	grep "^ e" output &&
-	grep "^ j" output &&
-	! grep "^ k" output
+	test_grep ! "^ d" output &&
+	test_grep "^ e" output &&
+	test_grep "^ j" output &&
+	test_grep ! "^ k" output
 '
 
 test_expect_success 'diff.context honored by "log"' '
 	git log -1 -p >output &&
-	! grep firstline output &&
+	test_grep ! firstline output &&
 	git config diff.context 8 &&
 	git log -1 -p >output &&
-	grep "^ firstline" output
+	test_grep "^ firstline" output
 '
 
 test_expect_success 'The -U option overrides diff.context' '
 	git config diff.context 8 &&
 	git log -U4 -1 >output &&
-	! grep "^ firstline" output
+	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'diff.context honored by "diff"' '
 	git config diff.context 8 &&
 	git diff >output &&
-	grep "^ firstline" output
+	test_grep "^ firstline" output
 '
 
 test_expect_success 'plumbing not affected' '
 	git config diff.context 8 &&
 	git diff-files -p >output &&
-	! grep "^ firstline" output
+	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'non-integer config parsing' '
@@ -85,8 +85,8 @@ test_expect_success 'negative integer config parsing' '
 test_expect_success '-U0 is valid, so is diff.context=0' '
 	git config diff.context 0 &&
 	git diff >output &&
-	grep "^-ADDED" output &&
-	grep "^+MODIFIED" output
+	test_grep "^-ADDED" output &&
+	test_grep "^+MODIFIED" output
 '
 
 test_expect_success '-U2147483647 works' '
@@ -94,9 +94,9 @@ test_expect_success '-U2147483647 works' '
 	test_line_count = 16 x &&
 	git diff -U2147483647 >output &&
 	test_line_count = 22 output &&
-	grep "^-ADDED" output &&
-	grep "^+MODIFIED" output &&
-	grep "^+APPENDED" output
+	test_grep "^-ADDED" output &&
+	test_grep "^+MODIFIED" output &&
+	test_grep "^+APPENDED" output
 '
 
 test_done
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v2 2/4] test: refactor to use "test_config"
  2025-05-10 13:46 ` [PATCH v2 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
  2025-05-10 13:46   ` [PATCH v2 1/4] test: refactor to use "test_grep" Leon Michalak via GitGitGadget
@ 2025-05-10 13:46   ` Leon Michalak via GitGitGadget
  2025-05-10 13:46   ` [PATCH v2 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-05-10 13:46 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

Refactor to use the modern "test_config" test utility instead of manual
"git config" as the former provides clean up on test completion.

This is a prerequisite to the commits that follow which add to this test
file.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 t/t4055-diff-context.sh | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index c66f966a3ab3..1384a8195705 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -47,43 +47,43 @@ test_expect_success 'the default number of context lines is 3' '
 test_expect_success 'diff.context honored by "log"' '
 	git log -1 -p >output &&
 	test_grep ! firstline output &&
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git log -1 -p >output &&
 	test_grep "^ firstline" output
 '
 
 test_expect_success 'The -U option overrides diff.context' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git log -U4 -1 >output &&
 	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'diff.context honored by "diff"' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git diff >output &&
 	test_grep "^ firstline" output
 '
 
 test_expect_success 'plumbing not affected' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git diff-files -p >output &&
 	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'non-integer config parsing' '
-	git config diff.context no &&
+	test_config diff.context no &&
 	test_must_fail git diff 2>output &&
 	test_grep "bad numeric config value" output
 '
 
 test_expect_success 'negative integer config parsing' '
-	git config diff.context -1 &&
+	test_config diff.context -1 &&
 	test_must_fail git diff 2>output &&
 	test_grep "bad config variable" output
 '
 
 test_expect_success '-U0 is valid, so is diff.context=0' '
-	git config diff.context 0 &&
+	test_config diff.context 0 &&
 	git diff >output &&
 	test_grep "^-ADDED" output &&
 	test_grep "^+MODIFIED" output
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v2 3/4] add-patch: respect diff.context configuration
  2025-05-10 13:46 ` [PATCH v2 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
  2025-05-10 13:46   ` [PATCH v2 1/4] test: refactor to use "test_grep" Leon Michalak via GitGitGadget
  2025-05-10 13:46   ` [PATCH v2 2/4] test: refactor to use "test_config" Leon Michalak via GitGitGadget
@ 2025-05-10 13:46   ` Leon Michalak via GitGitGadget
  2025-05-13 13:52     ` Phillip Wood
  2025-05-10 13:46   ` [PATCH v2 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
  2025-06-28 16:34   ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
  4 siblings, 1 reply; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-05-10 13:46 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

Various builtins that use add-patch infrastructure do not respect
the user's diff.context and diff.interHunkContext file configurations.
This patch fixes this inconsistency.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 add-interactive.c       | 17 ++++++++++
 add-interactive.h       |  1 +
 add-patch.c             |  6 ++++
 t/t4055-diff-context.sh | 70 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 94 insertions(+)

diff --git a/add-interactive.c b/add-interactive.c
index 97ff35b6f12a..cac036441caf 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -39,8 +39,12 @@ static void init_color(struct repository *r, struct add_i_state *s,
 void init_add_i_state(struct add_i_state *s, struct repository *r)
 {
 	const char *value;
+	int context;
+	int interhunkcontext;
 
 	s->r = r;
+	s->context = -1;
+	s->interhunkcontext = -1;
 
 	if (repo_config_get_value(r, "color.interactive", &value))
 		s->use_color = -1;
@@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	repo_config_get_string(r, "diff.algorithm",
 			       &s->interactive_diff_algorithm);
 
+	if (!repo_config_get_int(r, "diff.context", &context)) {
+		if (context < 0)
+			die(_("%s cannot be negative"), "diff.context");
+		else
+			s->context = context;
+	};
+	if (!repo_config_get_int(r, "diff.interHunkContext", &interhunkcontext)) {
+		if (interhunkcontext < 0)
+			die(_("%s cannot be negative"), "diff.interHunkContext");
+		else
+			s->interhunkcontext = interhunkcontext;
+	};
+
 	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
 	if (s->use_single_key)
 		setbuf(stdin, NULL);
diff --git a/add-interactive.h b/add-interactive.h
index 693f125e8e4b..c63f35b14be8 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -18,6 +18,7 @@ struct add_i_state {
 
 	int use_single_key;
 	char *interactive_diff_filter, *interactive_diff_algorithm;
+	int context, interhunkcontext;
 };
 
 void init_add_i_state(struct add_i_state *s, struct repository *r);
diff --git a/add-patch.c b/add-patch.c
index 95c67d8c80c4..b43ca1600738 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 {
 	struct strvec args = STRVEC_INIT;
 	const char *diff_algorithm = s->s.interactive_diff_algorithm;
+	int diff_context = s->s.context;
+	int diff_interhunkcontext = s->s.interhunkcontext;
 	struct strbuf *plain = &s->plain, *colored = NULL;
 	struct child_process cp = CHILD_PROCESS_INIT;
 	char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
@@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 	int res;
 
 	strvec_pushv(&args, s->mode->diff_cmd);
+	if (diff_context != -1)
+		strvec_pushf(&args, "--unified=%i", diff_context);
+	if (diff_interhunkcontext != -1)
+		strvec_pushf(&args, "--inter-hunk-context=%i", diff_interhunkcontext);
 	if (diff_algorithm)
 		strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
 	if (s->revision) {
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index 1384a8195705..c4b861c360cc 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -52,6 +52,46 @@ test_expect_success 'diff.context honored by "log"' '
 	test_grep "^ firstline" output
 '
 
+test_expect_success 'diff.context honored by "add"' '
+	git add -p >output &&
+	test_grep ! firstline output &&
+	test_config diff.context 8 &&
+	git log -1 -p >output &&
+	test_grep "^ firstline" output
+'
+
+test_expect_success 'diff.context honored by "commit"' '
+	! git commit -p >output &&
+	test_grep ! firstline output &&
+	test_config diff.context 8 &&
+	! git commit -p >output &&
+	test_grep "^ firstline" output
+'
+
+test_expect_success 'diff.context honored by "checkout"' '
+	git checkout -p >output &&
+	test_grep ! firstline output &&
+	test_config diff.context 8 &&
+	git checkout -p >output &&
+	test_grep "^ firstline" output
+'
+
+test_expect_success 'diff.context honored by "stash"' '
+	! git stash -p >output &&
+	test_grep ! firstline output &&
+	test_config diff.context 8 &&
+	! git stash -p >output &&
+	test_grep "^ firstline" output
+'
+
+test_expect_success 'diff.context honored by "restore"' '
+	git restore -p >output &&
+	test_grep ! firstline output &&
+	test_config diff.context 8 &&
+	git restore -p >output &&
+	test_grep "^ firstline" output
+'
+
 test_expect_success 'The -U option overrides diff.context' '
 	test_config diff.context 8 &&
 	git log -U4 -1 >output &&
@@ -82,6 +122,36 @@ test_expect_success 'negative integer config parsing' '
 	test_grep "bad config variable" output
 '
 
+test_expect_success 'negative integer config parsing by "add"' '
+	test_config diff.context -1 &&
+	test_must_fail git add -p 2>output &&
+	test_grep "diff.context cannot be negative" output
+'
+
+test_expect_success 'negative integer config parsing by "commit"' '
+	test_config diff.context -1 &&
+	test_must_fail git commit -p 2>output &&
+	test_grep "bad config variable" output
+'
+
+test_expect_success 'negative integer config parsing by "checkout"' '
+	test_config diff.context -1 &&
+	test_must_fail git checkout -p 2>output &&
+	test_grep "diff.context cannot be negative" output
+'
+
+test_expect_success 'negative integer config parsing by "stash"' '
+	test_config diff.context -1 &&
+	test_must_fail git stash -p 2>output &&
+	test_grep "diff.context cannot be negative" output
+'
+
+test_expect_success 'negative integer config parsing by "restore"' '
+	test_config diff.context -1 &&
+	test_must_fail git restore -p 2>output &&
+	test_grep "diff.context cannot be negative" output
+'
+
 test_expect_success '-U0 is valid, so is diff.context=0' '
 	test_config diff.context 0 &&
 	git diff >output &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v2 4/4] add-patch: add diff.context command line overrides
  2025-05-10 13:46 ` [PATCH v2 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
                     ` (2 preceding siblings ...)
  2025-05-10 13:46   ` [PATCH v2 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
@ 2025-05-10 13:46   ` Leon Michalak via GitGitGadget
  2025-05-12 16:45     ` Junio C Hamano
  2025-05-13 13:52     ` Phillip Wood
  2025-06-28 16:34   ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
  4 siblings, 2 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-05-10 13:46 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

This patch compliments the previous commit, where builtins that use
add-patch infrastructure now respect diff.context and
diff.interHunkContext file configurations.

In particular, this patch helps users who don't want to set persistent
context configurations or just want a way to override them on a one-time
basis, by allowing the relevant builtins to accept corresponding command
line options that override the file configurations.

This mimics commands such as diff and log, which allow for both context
file configuration and command line overrides.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 Documentation/diff-context-options.adoc | 10 ++++
 Documentation/git-add.adoc              |  2 +
 Documentation/git-checkout.adoc         |  2 +
 Documentation/git-commit.adoc           |  2 +
 Documentation/git-reset.adoc            |  2 +
 Documentation/git-restore.adoc          |  2 +
 Documentation/git-stash.adoc            |  2 +
 add-interactive.c                       | 36 +++++++++++----
 add-interactive.h                       | 16 +++++--
 add-patch.c                             |  5 +-
 builtin/add.c                           | 16 +++++--
 builtin/checkout.c                      | 24 ++++++++--
 builtin/commit.c                        | 11 ++++-
 builtin/reset.c                         | 12 ++++-
 builtin/stash.c                         | 46 ++++++++++++++-----
 commit.h                                |  3 +-
 parse-options.h                         |  2 +
 t/t4032-diff-inter-hunk-context.sh      | 61 +++++++++++++++++++++++++
 t/t4055-diff-context.sh                 | 30 ++++++++++++
 t/t9902-completion.sh                   |  2 +
 20 files changed, 251 insertions(+), 35 deletions(-)
 create mode 100644 Documentation/diff-context-options.adoc

diff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc
new file mode 100644
index 000000000000..e161260358ff
--- /dev/null
+++ b/Documentation/diff-context-options.adoc
@@ -0,0 +1,10 @@
+`-U<n>`::
+`--unified=<n>`::
+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
+	or 3 if the config option is unset.
+
+`--inter-hunk-context=<n>`::
+	Show the context between diff hunks, up to the specified _<number>_
+	of lines, thereby fusing hunks that are close to each other.
+	Defaults to `diff.interHunkContext` or 0 if the config option
+	is unset.
diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index eba0b419ce50..b7a735824d6c 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -104,6 +104,8 @@ This effectively runs `add --interactive`, but bypasses the
 initial command menu and directly jumps to the `patch` subcommand.
 See ``Interactive mode'' for details.
 
+include::diff-context-options.adoc[]
+
 `-e`::
 `--edit`::
 	Open the diff vs. the index in an editor and let the user
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index a66c53a5cd1e..896372fd7a29 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -289,6 +289,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 Note that this option uses the no overlay mode by default (see also
 `--overlay`), and currently doesn't support overlay mode.
 
+include::diff-context-options.adoc[]
+
 --ignore-other-worktrees::
 	`git checkout` refuses when the wanted branch is already checked
 	out or otherwise in use by another worktree. This option makes
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc
index dc219025f1eb..ae988a883b5b 100644
--- a/Documentation/git-commit.adoc
+++ b/Documentation/git-commit.adoc
@@ -76,6 +76,8 @@ OPTIONS
 	which changes to commit. See linkgit:git-add[1] for
 	details.
 
+include::diff-context-options.adoc[]
+
 `-C <commit>`::
 `--reuse-message=<commit>`::
 	Take an existing _<commit>_ object, and reuse the log message
diff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc
index 53ab88c5451c..50e8a0ba6f66 100644
--- a/Documentation/git-reset.adoc
+++ b/Documentation/git-reset.adoc
@@ -125,6 +125,8 @@ OPTIONS
 	separated with _NUL_ character and all other characters are taken
 	literally (including newlines and quotes).
 
+include::diff-context-options.adoc[]
+
 `--`::
 	Do not interpret any more arguments as options.
 
diff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc
index 877b7772e667..1dcc2bb7aea3 100644
--- a/Documentation/git-restore.adoc
+++ b/Documentation/git-restore.adoc
@@ -52,6 +52,8 @@ leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to
 	Mode" section of linkgit:git-add[1] to learn how to operate
 	the `--patch` mode.
 
+include::diff-context-options.adoc[]
+
 `-W`::
 `--worktree`::
 `-S`::
diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc
index 1a5177f4986c..0578c619c410 100644
--- a/Documentation/git-stash.adoc
+++ b/Documentation/git-stash.adoc
@@ -208,6 +208,8 @@ to learn how to operate the `--patch` mode.
 The `--patch` option implies `--keep-index`.  You can use
 `--no-keep-index` to override this.
 
+include::diff-context-options.adoc[]
+
 -S::
 --staged::
 	This option is only valid for `push` and `save` commands.
diff --git a/add-interactive.c b/add-interactive.c
index cac036441caf..496a44cfe4b6 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -36,7 +36,8 @@ static void init_color(struct repository *r, struct add_i_state *s,
 	free(key);
 }
 
-void init_add_i_state(struct add_i_state *s, struct repository *r)
+void init_add_i_state(struct add_i_state *s, struct repository *r,
+		      struct add_p_opt *add_p_opt)
 {
 	const char *value;
 	int context;
@@ -98,6 +99,17 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
 	if (s->use_single_key)
 		setbuf(stdin, NULL);
+
+	if (add_p_opt->context != -1) {
+		if (add_p_opt->context < 0)
+			die(_("%s cannot be negative"), "--unified");
+		s->context = add_p_opt->context;
+	}
+	if (add_p_opt->interhunkcontext != -1) {
+		if (add_p_opt->interhunkcontext < 0)
+			die(_("%s cannot be negative"), "--inter-hunk-context");
+		s->interhunkcontext = add_p_opt->interhunkcontext;
+	}
 }
 
 void clear_add_i_state(struct add_i_state *s)
@@ -986,6 +998,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 	opts->prompt = N_("Patch update");
 	count = list_and_choose(s, files, opts);
 	if (count > 0) {
+		struct add_p_opt add_p_opt = {
+			.context = s->context,
+			.interhunkcontext = s->interhunkcontext,
+		};
 		struct strvec args = STRVEC_INIT;
 		struct pathspec ps_selected = { 0 };
 
@@ -996,7 +1012,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 		parse_pathspec(&ps_selected,
 			       PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
 			       PATHSPEC_LITERAL_PATH, "", args.v);
-		res = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);
+		res = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected);
 		strvec_clear(&args);
 		clear_pathspec(&ps_selected);
 	}
@@ -1031,10 +1047,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
 	if (count > 0) {
 		struct child_process cmd = CHILD_PROCESS_INIT;
 
-		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached",
-			     oid_to_hex(!is_initial ? &oid :
-					s->r->hash_algo->empty_tree),
-			     "--", NULL);
+		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL);
+		if (s->context != -1)
+			strvec_pushf(&cmd.args, "--unified=%i", s->context);
+		if (s->interhunkcontext != -1)
+			strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->interhunkcontext);
+		strvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :
+			     s->r->hash_algo->empty_tree), "--", NULL);
 		for (i = 0; i < files->items.nr; i++)
 			if (files->selected[i])
 				strvec_push(&cmd.args,
@@ -1127,7 +1146,8 @@ static void command_prompt_help(struct add_i_state *s)
 			 _("(empty) select nothing"));
 }
 
-int run_add_i(struct repository *r, const struct pathspec *ps)
+int run_add_i(struct repository *r, const struct pathspec *ps,
+	      struct add_p_opt *add_p_opt)
 {
 	struct add_i_state s = { NULL };
 	struct print_command_item_data data = { "[", "]" };
@@ -1170,7 +1190,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 			->util = util;
 	}
 
-	init_add_i_state(&s, r);
+	init_add_i_state(&s, r, add_p_opt);
 
 	/*
 	 * When color was asked for, use the prompt color for
diff --git a/add-interactive.h b/add-interactive.h
index c63f35b14be8..4213dcd67b9a 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -3,6 +3,13 @@
 
 #include "color.h"
 
+struct add_p_opt {
+	int context;
+	int interhunkcontext;
+};
+
+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }
+
 struct add_i_state {
 	struct repository *r;
 	int use_color;
@@ -21,12 +28,14 @@ struct add_i_state {
 	int context, interhunkcontext;
 };
 
-void init_add_i_state(struct add_i_state *s, struct repository *r);
+void init_add_i_state(struct add_i_state *s, struct repository *r,
+		      struct add_p_opt *add_p_opt);
 void clear_add_i_state(struct add_i_state *s);
 
 struct repository;
 struct pathspec;
-int run_add_i(struct repository *r, const struct pathspec *ps);
+int run_add_i(struct repository *r, const struct pathspec *ps,
+	      struct add_p_opt *add_p_opt);
 
 enum add_p_mode {
 	ADD_P_ADD,
@@ -37,6 +46,7 @@ enum add_p_mode {
 };
 
 int run_add_p(struct repository *r, enum add_p_mode mode,
-	      const char *revision, const struct pathspec *ps);
+	      struct add_p_opt *o, const char *revision,
+	      const struct pathspec *ps);
 
 #endif
diff --git a/add-patch.c b/add-patch.c
index b43ca1600738..c0d33820c558 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -1766,14 +1766,15 @@ soft_increment:
 }
 
 int run_add_p(struct repository *r, enum add_p_mode mode,
-	      const char *revision, const struct pathspec *ps)
+	      struct add_p_opt *o, const char *revision,
+	      const struct pathspec *ps)
 {
 	struct add_p_state s = {
 		{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	size_t i, binary_count = 0;
 
-	init_add_i_state(&s.s, r);
+	init_add_i_state(&s.s, r, o);
 
 	if (mode == ADD_P_STASH)
 		s.mode = &patch_mode_stash;
diff --git a/builtin/add.c b/builtin/add.c
index 78dfb2657767..a7e6c77e7a74 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -29,6 +29,7 @@ static const char * const builtin_add_usage[] = {
 	NULL
 };
 static int patch_interactive, add_interactive, edit_interactive;
+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 static int take_worktree_changes;
 static int add_renormalize;
 static int pathspec_file_nul;
@@ -157,7 +158,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec *
 int interactive_add(struct repository *repo,
 		    const char **argv,
 		    const char *prefix,
-		    int patch)
+		    int patch, struct add_p_opt *add_p_opt)
 {
 	struct pathspec pathspec;
 	int ret;
@@ -169,9 +170,9 @@ int interactive_add(struct repository *repo,
 		       prefix, argv);
 
 	if (patch)
-		ret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);
+		ret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec);
 	else
-		ret = !!run_add_i(repo, &pathspec);
+		ret = !!run_add_i(repo, &pathspec, add_p_opt);
 
 	clear_pathspec(&pathspec);
 	return ret;
@@ -253,6 +254,8 @@ static struct option builtin_add_options[] = {
 	OPT_GROUP(""),
 	OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
 	OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
+	OPT_DIFF_UNIFIED(&add_p_opt.context),
+	OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 	OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
 	OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
 	OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
@@ -398,7 +401,12 @@ int cmd_add(int argc,
 			die(_("options '%s' and '%s' cannot be used together"), "--dry-run", "--interactive/--patch");
 		if (pathspec_from_file)
 			die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch");
-		exit(interactive_add(repo, argv + 1, prefix, patch_interactive));
+		exit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
 	}
 
 	if (edit_interactive) {
diff --git a/builtin/checkout.c b/builtin/checkout.c
index d185982f3a63..b0b05b71bc29 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -61,6 +61,8 @@ static const char * const restore_usage[] = {
 
 struct checkout_opts {
 	int patch_mode;
+	int patch_context;
+	int patch_interhunk_context;
 	int quiet;
 	int merge;
 	int force;
@@ -104,7 +106,12 @@ struct checkout_opts {
 	struct tree *source_tree;
 };
 
-#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 }
+#define CHECKOUT_OPTS_INIT { \
+	.conflict_style = -1, \
+	.merge = -1, \
+	.patch_context = -1, \
+	.patch_interhunk_context = -1, \
+}
 
 struct branch_info {
 	char *name; /* The short name used */
@@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts,
 
 	if (opts->patch_mode) {
 		enum add_p_mode patch_mode;
+		struct add_p_opt add_p_opt = {
+			.context = opts->patch_context,
+			.interhunkcontext = opts->patch_interhunk_context,
+		};
 		const char *rev = new_branch_info->name;
 		char rev_oid[GIT_MAX_HEXSZ + 1];
 
@@ -564,8 +575,13 @@ static int checkout_paths(const struct checkout_opts *opts,
 		else
 			BUG("either flag must have been set, worktree=%d, index=%d",
 			    opts->checkout_worktree, opts->checkout_index);
-		return !!run_add_p(the_repository, patch_mode, rev,
-				   &opts->pathspec);
+		return !!run_add_p(the_repository, patch_mode, &add_p_opt,
+				   rev, &opts->pathspec);
+	} else {
+		if (opts->patch_context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (opts->patch_interhunk_context != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
 	}
 
 	repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -1738,6 +1754,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
 		OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
+		OPT_DIFF_UNIFIED(&opts->patch_context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context),
 		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),
diff --git a/builtin/commit.c b/builtin/commit.c
index 66bd91fd523d..19ec0ccb2bef 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -19,6 +19,7 @@
 #include "environment.h"
 #include "diff.h"
 #include "commit.h"
+#include "add-interactive.h"
 #include "gettext.h"
 #include "revision.h"
 #include "wt-status.h"
@@ -122,6 +123,7 @@ static const char *edit_message, *use_message;
 static char *fixup_message, *fixup_commit, *squash_message;
 static const char *fixup_prefix;
 static int all, also, interactive, patch_interactive, only, amend, signoff;
+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
@@ -400,7 +402,7 @@ static const char *prepare_index(const char **argv, const char *prefix,
 		old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
 		setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
 
-		if (interactive_add(the_repository, argv, prefix, patch_interactive) != 0)
+		if (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0)
 			die(_("interactive add failed"));
 
 		the_repository->index_file = old_repo_index_file;
@@ -424,6 +426,11 @@ static const char *prepare_index(const char **argv, const char *prefix,
 		commit_style = COMMIT_NORMAL;
 		ret = get_lock_file_path(&index_lock);
 		goto out;
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
 	}
 
 	/*
@@ -1722,6 +1729,8 @@ int cmd_commit(int argc,
 		OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")),
 		OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
 		OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT_BOOL('o', "only", &only, N_("commit only specified files")),
 		OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")),
 		OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
diff --git a/builtin/reset.c b/builtin/reset.c
index 73b4537a9a56..62db07b0c231 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -346,6 +346,7 @@ int cmd_reset(int argc,
 	struct object_id oid;
 	struct pathspec pathspec;
 	int intent_to_add = 0;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	const struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
 		OPT_BOOL(0, "no-refresh", &no_refresh,
@@ -370,6 +371,8 @@ int cmd_reset(int argc,
 			       PARSE_OPT_OPTARG,
 			       option_parse_recurse_submodules_worktree_updater),
 		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT_BOOL('N', "intent-to-add", &intent_to_add,
 				N_("record only the fact that removed paths will be added later")),
 		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
@@ -424,9 +427,14 @@ int cmd_reset(int argc,
 		if (reset_type != NONE)
 			die(_("options '%s' and '%s' cannot be used together"), "--patch", "--{hard,mixed,soft}");
 		trace2_cmd_mode("patch-interactive");
-		update_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev,
-				   &pathspec);
+		update_ref_status = !!run_add_p(the_repository, ADD_P_RESET,
+						&add_p_opt, rev, &pathspec);
 		goto cleanup;
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
 	}
 
 	/* git reset tree [--] paths... can be used to
diff --git a/builtin/stash.c b/builtin/stash.c
index cfbd92852a65..1c68d50ce543 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -1242,7 +1242,8 @@ done:
 }
 
 static int stash_patch(struct stash_info *info, const struct pathspec *ps,
-		       struct strbuf *out_patch, int quiet)
+		       struct strbuf *out_patch, int quiet,
+		       struct add_p_opt *add_p_opt)
 {
 	int ret = 0;
 	struct child_process cp_read_tree = CHILD_PROCESS_INIT;
@@ -1267,7 +1268,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
 	old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
 	setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
 
-	ret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps);
+	ret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps);
 
 	the_repository->index_file = old_repo_index_file;
 	if (old_index_env && *old_index_env)
@@ -1362,8 +1363,8 @@ done:
 }
 
 static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,
-			   int include_untracked, int patch_mode, int only_staged,
-			   struct stash_info *info, struct strbuf *patch,
+			   int include_untracked, int patch_mode, struct add_p_opt *add_p_opt,
+			   int only_staged, struct stash_info *info, struct strbuf *patch,
 			   int quiet)
 {
 	int ret = 0;
@@ -1439,7 +1440,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
 		untracked_commit_option = 1;
 	}
 	if (patch_mode) {
-		ret = stash_patch(info, ps, patch, quiet);
+		ret = stash_patch(info, ps, patch, quiet, add_p_opt);
 		if (ret < 0) {
 			if (!quiet)
 				fprintf_ln(stderr, _("Cannot save the current "
@@ -1513,7 +1514,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
 	if (!check_changes_tracked_files(&ps))
 		return 0;
 
-	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,
+	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,
 			      NULL, 0);
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
@@ -1524,7 +1525,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
 }
 
 static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,
-			 int keep_index, int patch_mode, int include_untracked, int only_staged)
+			 int keep_index, int patch_mode, struct add_p_opt *add_p_opt,
+			 int include_untracked, int only_staged)
 {
 	int ret = 0;
 	struct stash_info info = STASH_INFO_INIT;
@@ -1594,8 +1596,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
 
 	if (stash_msg)
 		strbuf_addstr(&stash_msg_buf, stash_msg);
-	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,
-			    &info, &patch, quiet)) {
+	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
+			    add_p_opt, only_staged, &info, &patch, quiet)) {
 		ret = -1;
 		goto done;
 	}
@@ -1768,6 +1770,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 	const char *stash_msg = NULL;
 	char *pathspec_from_file = NULL;
 	struct pathspec ps;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	struct option options[] = {
 		OPT_BOOL('k', "keep-index", &keep_index,
 			 N_("keep index")),
@@ -1775,6 +1778,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT__QUIET(&quiet, N_("quiet mode")),
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
@@ -1826,8 +1831,15 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 		die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file");
 	}
 
+	if (!patch_mode) {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
-			    include_untracked, only_staged);
+			    &add_p_opt, include_untracked, only_staged);
 
 	clear_pathspec(&ps);
 	free(pathspec_from_file);
@@ -1852,6 +1864,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 	const char *stash_msg = NULL;
 	struct pathspec ps;
 	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	struct option options[] = {
 		OPT_BOOL('k', "keep-index", &keep_index,
 			 N_("keep index")),
@@ -1859,6 +1872,8 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT__QUIET(&quiet, N_("quiet mode")),
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
@@ -1877,8 +1892,17 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 		stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
+
+	if (!patch_mode) {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
-			    patch_mode, include_untracked, only_staged);
+			    patch_mode, &add_p_opt, include_untracked,
+			    only_staged);
 
 	strbuf_release(&stash_msg_buf);
 	return ret;
diff --git a/commit.h b/commit.h
index 70c870dae4d4..7a7fedbc2f14 100644
--- a/commit.h
+++ b/commit.h
@@ -2,6 +2,7 @@
 #define COMMIT_H
 
 #include "object.h"
+#include "add-interactive.h"
 
 struct signature_check;
 struct strbuf;
@@ -257,7 +258,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *);
 int interactive_add(struct repository *repo,
 		    const char **argv,
 		    const char *prefix,
-		    int patch);
+		    int patch, struct add_p_opt *add_p_opt);
 
 struct commit_extra_header {
 	struct commit_extra_header *next;
diff --git a/parse-options.h b/parse-options.h
index 91c3e3c29b3d..bdae8f116198 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -616,6 +616,8 @@ int parse_opt_tracking_mode(const struct option *, const char *, int);
 #define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file"))
 #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"))
 #define OPT_AUTOSTASH(v) OPT_BOOL(0, "autostash", v, N_("automatically stash/stash pop before and after"))
+#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', "unified", v, N_("generate diffs with <n> lines context"), PARSE_OPT_NONEG)
+#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)
 
 #define OPT_IPVERSION(v) \
 	OPT_SET_INT_F('4', "ipv4", (v), N_("use IPv4 addresses only"), \
diff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh
index bada0cbd32f7..d5aad6e143a7 100755
--- a/t/t4032-diff-inter-hunk-context.sh
+++ b/t/t4032-diff-inter-hunk-context.sh
@@ -47,6 +47,31 @@ t() {
 	"
 }
 
+t_patch() {
+	use_config=
+	git config --unset diff.interHunkContext
+
+	case $# in
+	4) hunks=$4; cmd="add -p -U$3";;
+	5) hunks=$5; cmd="add -p -U$3 --inter-hunk-context=$4";;
+	6) hunks=$5; cmd="add -p -U$3"; git config diff.interHunkContext $4; use_config="(diff.interHunkContext=$4) ";;
+	esac
+	label="$use_config$cmd, $1 common $2"
+	file=f$1
+
+	if ! test -f $file
+	then
+		f A $1 B >$file
+		git add $file
+		git commit -q -m. $file
+		f X $1 Y >$file
+	fi
+
+	test_expect_success "$label: count hunks ($hunks)" "
+		test $(test_write_lines q | git $cmd $file | sed -n 's/^([0-9]*\/\([0-9]*\)) Stage this hunk.*/\1/p') = $hunks
+	"
+}
+
 cat <<EOF >expected.f1.0.1 || exit 1
 diff --git a/f1 b/f1
 --- a/f1
@@ -107,6 +132,42 @@ t 3 lines	1	2	1	config
 t 9 lines	3	2	2	config
 t 9 lines	3	3	1	config
 
+# common lines	ctx	intrctx	hunks
+t_patch 1 line	0		2
+t_patch 1 line	0	0	2
+t_patch 1 line	0	1	1
+t_patch 1 line	0	2	1
+t_patch 1 line	1		1
+
+t_patch 2 lines	0		2
+t_patch 2 lines	0	0	2
+t_patch 2 lines	0	1	2
+t_patch 2 lines	0	2	1
+t_patch 2 lines	1		1
+
+t_patch 3 lines	1		2
+t_patch 3 lines	1	0	2
+t_patch 3 lines	1	1	1
+t_patch 3 lines	1	2	1
+
+t_patch 9 lines	3		2
+t_patch 9 lines	3	2	2
+t_patch 9 lines	3	3	1
+
+#					use diff.interHunkContext?
+t_patch 1 line	0	0	2	config
+t_patch 1 line	0	1	1	config
+t_patch 1 line	0	2	1	config
+t_patch 9 lines	3	3	1	config
+t_patch 2 lines	0	0	2	config
+t_patch 2 lines	0	1	2	config
+t_patch 2 lines	0	2	1	config
+t_patch 3 lines	1	0	2	config
+t_patch 3 lines	1	1	1	config
+t_patch 3 lines	1	2	1	config
+t_patch 9 lines	3	2	2	config
+t_patch 9 lines	3	3	1	config
+
 test_expect_success 'diff.interHunkContext invalid' '
 	git config diff.interHunkContext asdf &&
 	test_must_fail git diff &&
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index c4b861c360cc..07d993ba6762 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -98,6 +98,36 @@ test_expect_success 'The -U option overrides diff.context' '
 	test_grep ! "^ firstline" output
 '
 
+test_expect_success 'The -U option overrides diff.context for "add"' '
+	test_config diff.context 8 &&
+	git add -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "commit"' '
+	test_config diff.context 8 &&
+	! git commit -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "checkout"' '
+	test_config diff.context 8 &&
+	git checkout -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "stash"' '
+	test_config diff.context 8 &&
+	! git stash -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "restore"' '
+	test_config diff.context 8 &&
+	git restore -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
 test_expect_success 'diff.context honored by "diff"' '
 	test_config diff.context 8 &&
 	git diff >output &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 343b8cd1912b..6650d33fba69 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2596,6 +2596,8 @@ test_expect_success 'double dash "git checkout"' '
 	--merge Z
 	--conflict=Z
 	--patch Z
+	--unified=Z
+	--inter-hunk-context=Z
 	--ignore-skip-worktree-bits Z
 	--ignore-other-worktrees Z
 	--recurse-submodules Z
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 1/4] test: refactor to use "test_grep"
  2025-05-10 13:46   ` [PATCH v2 1/4] test: refactor to use "test_grep" Leon Michalak via GitGitGadget
@ 2025-05-12 13:42     ` Junio C Hamano
  2025-05-12 16:58       ` Leon Michalak
  0 siblings, 1 reply; 77+ messages in thread
From: Junio C Hamano @ 2025-05-12 13:42 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

"Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Leon Michalak <leonmichalak6@gmail.com>
>
> Refactor to use the modern "test_grep" test utility instead of regular
> "grep" which provides better debug information if tests fail.
>
> This is a prerequisite to the commits that follow which add to both test
> files.
>
> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
> ---

These mostly look sensible, but I would title & phrase the commit
description to 'use "test_grep"', not 'refactor to &'.  It's shorter
and more direct ;-)

Thanks.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides
  2025-05-10 13:46   ` [PATCH v2 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
@ 2025-05-12 16:45     ` Junio C Hamano
  2025-05-12 17:03       ` Leon Michalak
  2025-05-13 13:52     ` Phillip Wood
  1 sibling, 1 reply; 77+ messages in thread
From: Junio C Hamano @ 2025-05-12 16:45 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

"Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:

> diff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc
> new file mode 100644
> index 000000000000..e161260358ff
> --- /dev/null
> +++ b/Documentation/diff-context-options.adoc
> @@ -0,0 +1,10 @@
> +`-U<n>`::
> +`--unified=<n>`::
> +	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
> +	or 3 if the config option is unset.
> +
> +`--inter-hunk-context=<n>`::
> +	Show the context between diff hunks, up to the specified _<number>_
> +	of lines, thereby fusing hunks that are close to each other.
> +	Defaults to `diff.interHunkContext` or 0 if the config option
> +	is unset.

It might not be trivial to do but I wonder if we cannot do better
than this to share more of the same text across manual pages.  These
two being options understood by `git diff`, we certainly have an
existing description for them, no?

Other than that, looking good to me.

Thanks.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 1/4] test: refactor to use "test_grep"
  2025-05-12 13:42     ` Junio C Hamano
@ 2025-05-12 16:58       ` Leon Michalak
  0 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak @ 2025-05-12 16:58 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Phillip Wood

On Mon, 12 May 2025 at 14:42, Junio C Hamano <gitster@pobox.com> wrote:
> These mostly look sensible, but I would title & phrase the commit
> description to 'use "test_grep"', not 'refactor to &'.  It's shorter
> and more direct ;-)

Thanks - will make sure to change that in v3 :)

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides
  2025-05-12 16:45     ` Junio C Hamano
@ 2025-05-12 17:03       ` Leon Michalak
  0 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak @ 2025-05-12 17:03 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Phillip Wood

On Mon, 12 May 2025 at 17:45, Junio C Hamano <gitster@pobox.com> wrote:
> It might not be trivial to do but I wonder if we cannot do better
> than this to share more of the same text across manual pages.  These
> two being options understood by `git diff`, we certainly have an
> existing description for them, no?

Yes, I did of course notice documentation for `git diff` also has
these; ultimately my justification for not changing that to use this
new .adoc include as well was for a couple reasons:
- these two options are not together in the `git diff` documentation
(not *so* important, and they probably should actually be together?)
- there is an extra if def which adds on "implies --patch" text in the
`git diff` documentation which isn't the behaviour the add-patch
commands are going for, so that makes the intent a little different
here

But would be good to hear if anyone else has any thoughts.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 3/4] add-patch: respect diff.context configuration
  2025-05-10 13:46   ` [PATCH v2 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
@ 2025-05-13 13:52     ` Phillip Wood
  2025-05-13 15:47       ` Junio C Hamano
  0 siblings, 1 reply; 77+ messages in thread
From: Phillip Wood @ 2025-05-13 13:52 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget, git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Leon Michalak

Hi Leon

On 10/05/2025 14:46, Leon Michalak via GitGitGadget wrote:
> From: Leon Michalak <leonmichalak6@gmail.com>
> 
> Various builtins that use add-patch infrastructure do not respect
> the user's diff.context and diff.interHunkContext file configurations.

We could expand this slightly by adding

This is because the plumbing commands used by "git add -p" to generate
the diff do not read those config settings. Fix this by reading the
config before generating the patch and passing it along to the diff
command with the "-U" and "--inter-hunk-context" command-line options.

> This patch fixes this inconsistency.
> 
> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
> ---

> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
>   	repo_config_get_string(r, "diff.algorithm",
>   			       &s->interactive_diff_algorithm);
>   
> +	if (!repo_config_get_int(r, "diff.context", &context)) {
> +		if (context < 0)
> +			die(_("%s cannot be negative"), "diff.context");
> +		else
> +			s->context = context;
> +	};
> +	if (!repo_config_get_int(r, "diff.interHunkContext", &interhunkcontext)) {
> +		if (interhunkcontext < 0)
> +			die(_("%s cannot be negative"), "diff.interHunkContext");
> +		else
> +			s->interhunkcontext = interhunkcontext;
> +	};

Thanks for changing this. This iteration of the code changes looks good

> diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
> index 1384a8195705..c4b861c360cc 100755
> --- a/t/t4055-diff-context.sh
> +++ b/t/t4055-diff-context.sh
> @@ -52,6 +52,46 @@ test_expect_success 'diff.context honored by "log"' '
>   	test_grep "^ firstline" output
>   '

It's great that you have written tests for this patch but as I said
last time I think the new tests should be in t3701-add-interactive.sh
as we're interested in testing whether "git add -p" passes on
diff.context to "git diff" , not whether "git diff" respects
diff.context. I still think there are too many tests here as we know
that all the different "-p" commands share a single code path. Our
test suite is slow enough already so we do not want to add new tests
that do not increase our code coverage. I would suggest removing
these tests and instead add the following in t3701

test_expect_success 'add -p respects diff.context' '
	test_write_lines a b c d e f g h i j k l m >file &&
	git add file &&
	test_write_lines a b c d e f G h i j k l m >file &&
	echo y | git -c diff.context=5 add -p >actual &&
	test_grep "@@ -2,11 +2,11 @@" actual
'

test_expect_success 'add -p respects diff.interHunkContext' '
	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
	git add file &&
	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
	test_grep "@@ -2,16 +2,16 @@" actual
'

> +test_expect_success 'negative integer config parsing by "add"' '

Perhaps "add -p rejects negative diff.context" would be clearer?

> +	test_config diff.context -1 &&
> +	test_must_fail git add -p 2>output &&
> +	test_grep "diff.context cannot be negative" output
> +'

This is great but again we only need to test a single command and we
should do so in t3701. We should also check that negative values of
diff.interHunkContext are also rejected.

Best Wishes

Phillip

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides
  2025-05-10 13:46   ` [PATCH v2 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
  2025-05-12 16:45     ` Junio C Hamano
@ 2025-05-13 13:52     ` Phillip Wood
  2025-05-13 14:39       ` Phillip Wood
  1 sibling, 1 reply; 77+ messages in thread
From: Phillip Wood @ 2025-05-13 13:52 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget, git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Leon Michalak

Hi Leon

On 10/05/2025 14:46, Leon Michalak via GitGitGadget wrote:
> From: Leon Michalak <leonmichalak6@gmail.com>
> 
> This patch compliments the previous commit, where builtins that use
> add-patch infrastructure now respect diff.context and
> diff.interHunkContext file configurations.
> 
> In particular, this patch helps users who don't want to set persistent
> context configurations or just want a way to override them on a one-time
> basis, by allowing the relevant builtins to accept corresponding command
> line options that override the file configurations.
> 
> This mimics commands such as diff and log, which allow for both context
> file configuration and command line overrides.

The code changes here mostly look good, I've left a few comments
below. I think the tests could be improved, I've left some suggestions
on limiting the number of tests while improving the coverage. The new
tests I'm suggesting that check invalid option combinations are the
basis for most of my code comments.

There is still the issue of what to do with -U0. As I mentioned
previously "git apply" will fail when we try to apply the patch. We
can either pass the appropriate flag when the context is zero or
possibly use -U0 to mean the default number of context lines.

> diff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc
> new file mode 100644
> index 000000000000..e161260358ff
> --- /dev/null
> +++ b/Documentation/diff-context-options.adoc
> @@ -0,0 +1,10 @@
> +`-U<n>`::
> +`--unified=<n>`::
> +	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
> +	or 3 if the config option is unset.
> +
> +`--inter-hunk-context=<n>`::
> +	Show the context between diff hunks, up to the specified _<number>_
> +	of lines, thereby fusing hunks that are close to each other.
> +	Defaults to `diff.interHunkContext` or 0 if the config option
> +	is unset.

Nice - we reuse the same text for all the "-p" commands.

> diff --git a/add-interactive.c b/add-interactive.c
> [...]
> @@ -98,6 +99,17 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
>   	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
>   	if (s->use_single_key)
>   		setbuf(stdin, NULL);
> +
> +	if (add_p_opt->context != -1) {
> +		if (add_p_opt->context < 0)
> +			die(_("%s cannot be negative"), "--unified");
> +		s->context = add_p_opt->context;
> +	}
> +	if (add_p_opt->interhunkcontext != -1) {
> +		if (add_p_opt->interhunkcontext < 0)
> +			die(_("%s cannot be negative"), "--inter-hunk-context");
> +		s->interhunkcontext = add_p_opt->interhunkcontext;
> +	}

Centralizing these checks like this is a good idea.

> @@ -1031,10 +1047,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
>   	if (count > 0) {
>   		struct child_process cmd = CHILD_PROCESS_INIT;
>   
> -		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached",
> -			     oid_to_hex(!is_initial ? &oid :
> -					s->r->hash_algo->empty_tree),
> -			     "--", NULL);
> +		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL);
> +		if (s->context != -1)
> +			strvec_pushf(&cmd.args, "--unified=%i", s->context);
> +		if (s->interhunkcontext != -1)
> +			strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->interhunkcontext);
> +		strvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :
> +			     s->r->hash_algo->empty_tree), "--", NULL);

This is good - we propagate the values we were given on the
command-line.

> diff --git a/builtin/checkout.c b/builtin/checkout.c
> [...]
> @@ -564,8 +575,13 @@ static int checkout_paths(const struct checkout_opts *opts,
>   		else
>   			BUG("either flag must have been set, worktree=%d, index=%d",
>   			    opts->checkout_worktree, opts->checkout_index);
> -		return !!run_add_p(the_repository, patch_mode, rev,
> -				   &opts->pathspec);
> +		return !!run_add_p(the_repository, patch_mode, &add_p_opt,
> +				   rev, &opts->pathspec);
> +	} else {
> +		if (opts->patch_context != -1)
> +			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
> +		if (opts->patch_interhunk_context != -1)
> +			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
>   	}

This does not catch "git checkout -U 7" because this code is only run
if we're checking out paths. I think you need to check this is
checkout_main() instead.

> diff --git a/builtin/stash.c b/builtin/stash.c
> [...]
> @@ -1826,8 +1831,15 @@ static int push_stash(int argc, const char **argv, const char *prefix,
>   		die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file");
>   	}
>   
> +	if (!patch_mode) {
> +		if (add_p_opt.context != -1)
> +			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
> +		if (add_p_opt.interhunkcontext != -1)
> +			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
> +	}
> +

This needs to die on invalid context values as "git stash" seems to
ignore the exit code of the subprocess that checks for negative values.

> @@ -1877,8 +1892,17 @@ static int save_stash(int argc, const char **argv, const char *prefix,
>   		stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
>   
>   	memset(&ps, 0, sizeof(ps));
> +
> +	if (!patch_mode) {
> +		if (add_p_opt.context != -1)
> +			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
> +		if (add_p_opt.interhunkcontext != -1)
> +			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");

This needs to die on invalid context values as "git stash" seems to
ignore the exit code of the subprocess that checks for negative values.

> diff --git a/commit.h b/commit.h
> [...]
>   #include "object.h"
> +#include "add-interactive.h"
>   struct signature_check;
>   struct strbuf;

Lets not add this. Instead lets just add a declaration for "struct
add_p_opt" like the ones in the context line so that we don't end up
including everything from add-interactive.h when we only need a single
struct declaration.

> diff --git a/parse-options.h b/parse-options.h
> [...]
> +#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', "unified", v,
>   N_("generate diffs with <n> lines context"), PARSE_OPT_NONEG)

This looks good

> +#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)

This is a bit verbose but it  matches what is in diff.c.

> diff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh
> index bada0cbd32f7..d5aad6e143a7 100755
> --- a/t/t4032-diff-inter-hunk-context.sh
> +++ b/t/t4032-diff-inter-hunk-context.sh
> @@ -47,6 +47,31 @@ t() {
>   	"
>   }
>   
> +t_patch() {
> +	use_config=
> +	git config --unset diff.interHunkContext
> +
> +	case $# in
> +	4) hunks=$4; cmd="add -p -U$3";;
> +	5) hunks=$5; cmd="add -p -U$3 --inter-hunk-context=$4";;
> +	6) hunks=$5; cmd="add -p -U$3"; git config diff.interHunkContext $4; use_config="(diff.interHunkContext=$4) ";;
> +	esac
> +	label="$use_config$cmd, $1 common $2"
> +	file=f$1
> +
> +	if ! test -f $file
> +	then
> +		f A $1 B >$file
> +		git add $file
> +		git commit -q -m. $file
> +		f X $1 Y >$file
> +	fi
> +
> +	test_expect_success "$label: count hunks ($hunks)" "
> +		test $(test_write_lines q | git $cmd $file | sed -n 's/^([0-9]*\/\([0-9]*\)) Stage this hunk.*/\1/p') = $hunks
> +	"
> +}
> +
>   cat <<EOF >expected.f1.0.1 || exit 1
>   diff --git a/f1 b/f1
>   --- a/f1
> @@ -107,6 +132,42 @@ t 3 lines	1	2	1	config
>   t 9 lines	3	2	2	config
>   t 9 lines	3	3	1	config
>   
> +# common lines	ctx	intrctx	hunks
> +t_patch 1 line	0		2
> +t_patch 1 line	0	0	2
> +t_patch 1 line	0	1	1
> +t_patch 1 line	0	2	1
> +t_patch 1 line	1		1
> +
> +t_patch 2 lines	0		2
> +t_patch 2 lines	0	0	2
> +t_patch 2 lines	0	1	2
> +t_patch 2 lines	0	2	1
> +t_patch 2 lines	1		1
> +
> +t_patch 3 lines	1		2
> +t_patch 3 lines	1	0	2
> +t_patch 3 lines	1	1	1
> +t_patch 3 lines	1	2	1
> +
> +t_patch 9 lines	3		2
> +t_patch 9 lines	3	2	2
> +t_patch 9 lines	3	3	1
> +
> +#					use diff.interHunkContext?
> +t_patch 1 line	0	0	2	config
> +t_patch 1 line	0	1	1	config
> +t_patch 1 line	0	2	1	config
> +t_patch 9 lines	3	3	1	config
> +t_patch 2 lines	0	0	2	config
> +t_patch 2 lines	0	1	2	config
> +t_patch 2 lines	0	2	1	config
> +t_patch 3 lines	1	0	2	config
> +t_patch 3 lines	1	1	1	config
> +t_patch 3 lines	1	2	1	config
> +t_patch 9 lines	3	2	2	config
> +t_patch 9 lines	3	3	1	config
> +

There are 29 tests here and yet more below. I think we can
get the test coverage we need much more efficiently with the following
added to t3701

for cmd in add checkout restore 'commit -m file'
do
	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" "
		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 &&
		git add file &&
		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 &&
		echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
			$cmd -p -U 4 --inter-hunk-context 2 >actual &&
		test_grep \"@@ -2,20 +2,20 @@\" actual
	"
done
	
test_expect_success 'reset accepts -U and --inter-hunk-context' '
	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 &&
	git commit -m file file &&
	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 &&
	git add file &&
	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
		reset -p -U 4 --inter-hunk-context 2 >actual &&
	test_grep "@@ -2,20 +2,20 @@" actual
'

test_expect_success 'stash accepts -U and --inter-hunk-context' '
	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 &&
	git commit -m file file &&
	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 &&
	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
		stash -p -U 4 --inter-hunk-context 2 >actual &&
	test_grep "@@ -2,20 +2,20 @@" actual
'

Those tests will fail if any of the commands that accept "-p" do not
accept "-U" or "--inter-hunk-context" or if command-line arguments do
not override the config settings. We should also add tests in t3701 to
check that invalid option combinations and values are rejected like so

for cmd in add checkout commit reset restore 'stash save' 'stash push'
do
	test_expect_success "$cmd rejects invalid context options" "
		test_must_fail git $cmd -p -U -3 2>actual &&
		test_grep -e \"--unified cannot be negative\" actual &&

		test_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&
		test_grep -e \"--inter-hunk-context cannot be negative\" actual &&

		test_must_fail git $cmd -U 7 2>actual &&
		test_grep -E \".--unified. requires .(--interactive/)?--patch.\" actual &&

		test_must_fail git $cmd --inter-hunk-context 2 2>actual &&
		test_grep -E \".--inter-hunk-context. requires 
.(--interactive/)?--patch.\" actual
	"
done

The "checkout", "stash save" and "stash push" tests above currently
fail because the implementation does not implement those checks
properly.

With a few tweaks this series will be looking very good

Best Wishes

Phillip

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides
  2025-05-13 13:52     ` Phillip Wood
@ 2025-05-13 14:39       ` Phillip Wood
  2025-05-13 15:05         ` Leon Michalak
  0 siblings, 1 reply; 77+ messages in thread
From: Phillip Wood @ 2025-05-13 14:39 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget, git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Leon Michalak

On 13/05/2025 14:52, Phillip Wood wrote:
>> diff --git a/builtin/stash.c b/builtin/stash.c
>> [...]
>> @@ -1826,8 +1831,15 @@ static int push_stash(int argc, const char 
>> **argv, const char *prefix,
>>           die(_("the option '%s' requires '%s'"), "--pathspec-file- 
>> nul", "--pathspec-from-file");
>>       }
>> +    if (!patch_mode) {
>> +        if (add_p_opt.context != -1)
>> +            die(_("the option '%s' requires '%s'"), "--unified", "-- 
>> patch");
>> +        if (add_p_opt.interhunkcontext != -1)
>> +            die(_("the option '%s' requires '%s'"), "--inter-hunk- 
>> context", "--patch");
>> +    }
>> +
> 
> This needs to die on invalid context values as "git stash" seems to
> ignore the exit code of the subprocess that checks for negative values.

Looking more closely the problem is that it quits if there are no 
changes to stash before validating -U or --inter-hunk-context. I think 
it should validate the options before checking if there is anything to 
stash.

Best Wishes

Phillip


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides
  2025-05-13 14:39       ` Phillip Wood
@ 2025-05-13 15:05         ` Leon Michalak
  2025-05-14 15:13           ` phillip.wood123
  0 siblings, 1 reply; 77+ messages in thread
From: Leon Michalak @ 2025-05-13 15:05 UTC (permalink / raw)
  To: phillip.wood
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder

Hey, thanks for the thorough review Philip. I will properly digest
this when I get some free time, but I just wanted to say (I probably
should have mentioned this so my bad) that the reason I didn't change
to test just the singular command (yet, anyway) is that someone else
thought this was a good idea testing all of them, so I wasn't sure
whether to touch it or not in the end, and thought I'd just submit
this v2 and gather more opinions. Was this perhaps the wrong approach
though?

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 3/4] add-patch: respect diff.context configuration
  2025-05-13 13:52     ` Phillip Wood
@ 2025-05-13 15:47       ` Junio C Hamano
  2025-05-14 15:13         ` Phillip Wood
  0 siblings, 1 reply; 77+ messages in thread
From: Junio C Hamano @ 2025-05-13 15:47 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Leon Michalak

Phillip Wood <phillip.wood123@gmail.com> writes:

> Hi Leon
>
> On 10/05/2025 14:46, Leon Michalak via GitGitGadget wrote:
>> From: Leon Michalak <leonmichalak6@gmail.com>
>> Various builtins that use add-patch infrastructure do not respect
>> the user's diff.context and diff.interHunkContext file configurations.
>
> We could expand this slightly by adding
>
> This is because the plumbing commands used by "git add -p" to generate
> the diff do not read those config settings. Fix this by reading the
> config before generating the patch and passing it along to the diff
> command with the "-U" and "--inter-hunk-context" command-line options.
>
>> This patch fixes this inconsistency.
>> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
>> ---
>
>> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
>>   	repo_config_get_string(r, "diff.algorithm",
>>   			       &s->interactive_diff_algorithm);
>>   +	if (!repo_config_get_int(r, "diff.context", &context)) {
>> +		if (context < 0)
>> +			die(_("%s cannot be negative"), "diff.context");
>> +		else
>> +			s->context = context;
>> +	};
>> +	if (!repo_config_get_int(r, "diff.interHunkContext", &interhunkcontext)) {
>> +		if (interhunkcontext < 0)
>> +			die(_("%s cannot be negative"), "diff.interHunkContext");
>> +		else
>> +			s->interhunkcontext = interhunkcontext;
>> +	};
>
> Thanks for changing this. This iteration of the code changes looks good

Lose the ';' (semicolon) after closing {brace}s.
This is C; you do not need an empty statement after a {block}.

Everything in your review I am very happy to see.  Thanks for giving
a great review.


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 4/4] add-patch: add diff.context command line overrides
  2025-05-13 15:05         ` Leon Michalak
@ 2025-05-14 15:13           ` phillip.wood123
  0 siblings, 0 replies; 77+ messages in thread
From: phillip.wood123 @ 2025-05-14 15:13 UTC (permalink / raw)
  To: Leon Michalak, phillip.wood
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder

Hi Leon

On 13/05/2025 16:05, Leon Michalak wrote:
> Hey, thanks for the thorough review Philip. I will properly digest
> this when I get some free time, but I just wanted to say (I probably
> should have mentioned this so my bad) that the reason I didn't change
> to test just the singular command (yet, anyway) is that someone else
> thought this was a good idea testing all of them,

I'd missed that message - have you got a link to it please

> so I wasn't sure
> whether to touch it or not in the end, and thought I'd just submit
> this v2 and gather more opinions. Was this perhaps the wrong approach
> though?
If you get conflicting advise then it is a good idea to mention that in 
the cover letter and explain which option you went with and why.

Best Wishes

Phillip


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 3/4] add-patch: respect diff.context configuration
  2025-05-13 15:47       ` Junio C Hamano
@ 2025-05-14 15:13         ` Phillip Wood
  2025-05-15 12:58           ` Junio C Hamano
  0 siblings, 1 reply; 77+ messages in thread
From: Phillip Wood @ 2025-05-14 15:13 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Leon Michalak

On 13/05/2025 16:47, Junio C Hamano wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
>> On 10/05/2025 14:46, Leon Michalak via GitGitGadget wrote:
>>
>>> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
>>>    	repo_config_get_string(r, "diff.algorithm",
>>>    			       &s->interactive_diff_algorithm);
>>>    +	if (!repo_config_get_int(r, "diff.context", &context)) {
>>> +		if (context < 0)
>>> +			die(_("%s cannot be negative"), "diff.context");
>>> +		else
>>> +			s->context = context;
>>> +	};
>>> +	if (!repo_config_get_int(r, "diff.interHunkContext", &interhunkcontext)) {
>>> +		if (interhunkcontext < 0)
>>> +			die(_("%s cannot be negative"), "diff.interHunkContext");
>>> +		else
>>> +			s->interhunkcontext = interhunkcontext;
>>> +	};
>>
>> Thanks for changing this. This iteration of the code changes looks good
> 
> Lose the ';' (semicolon) after closing {brace}s.
> This is C; you do not need an empty statement after a {block}.

Oh well spotted, I'd missed that

Thanks

Phillip

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v2 3/4] add-patch: respect diff.context configuration
  2025-05-14 15:13         ` Phillip Wood
@ 2025-05-15 12:58           ` Junio C Hamano
  0 siblings, 0 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-05-15 12:58 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Leon Michalak

Phillip Wood <phillip.wood123@gmail.com> writes:

> On 13/05/2025 16:47, Junio C Hamano wrote:
>> Phillip Wood <phillip.wood123@gmail.com> writes:
>>> On 10/05/2025 14:46, Leon Michalak via GitGitGadget wrote:
>>>
>>>> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
>>>>    	repo_config_get_string(r, "diff.algorithm",
>>>>    			       &s->interactive_diff_algorithm);
>>>>    +	if (!repo_config_get_int(r, "diff.context", &context)) {
>>>> +		if (context < 0)
>>>> +			die(_("%s cannot be negative"), "diff.context");
>>>> +		else
>>>> +			s->context = context;
>>>> +	};
>>>> +	if (!repo_config_get_int(r, "diff.interHunkContext", &interhunkcontext)) {
>>>> +		if (interhunkcontext < 0)
>>>> +			die(_("%s cannot be negative"), "diff.interHunkContext");
>>>> +		else
>>>> +			s->interhunkcontext = interhunkcontext;
>>>> +	};
>>>
>>> Thanks for changing this. This iteration of the code changes looks good
>> Lose the ';' (semicolon) after closing {brace}s.
>> This is C; you do not need an empty statement after a {block}.
>
> Oh well spotted, I'd missed that

Heh, with enough number of eyeballs, all the bugs are shallow.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* [PATCH v3 0/4] Better support for customising context lines in --patch commands
  2025-05-10 13:46 ` [PATCH v2 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
                     ` (3 preceding siblings ...)
  2025-05-10 13:46   ` [PATCH v2 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
@ 2025-06-28 16:34   ` Leon Michalak via GitGitGadget
  2025-06-28 16:34     ` [PATCH v3 1/4] test: use "test_grep" Leon Michalak via GitGitGadget
                       ` (6 more replies)
  4 siblings, 7 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-06-28 16:34 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

This series of patches attempt to give --interactive/--patch compatible
builtins ("add", "commit", "checkout", "reset", "restore" and "stash")
better support and nicer experience for configuring how many context lines
are shown in diffs through a variety of ways.

Prior to these patches, the user could not choose how many context lines
they saw in --patch commands (apart from one workaround by using
GIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a
persistent solution). Additionally, the behaviour around reading from the
diff.context and diff.interHunkContext configs was also inconsistent with
other diff generating commands such as "log -p".

The summarised changes below hopefully make this experience better and fix
some inconsistencies:

 * diff.context and diff.interHunkContext configs are now respected by
   --patch compatible commands
 * --unified and --inter-hunk-context command line options have been added
   to --patch compatible commands (which take prescendence over file
   configs)
 * "add" and "commit" in --interactive mode now expose a new "context"
   subcommand which configures the amount of context lines you wish to see
   in subsequent diffs generated from other subcommands such as "patch" or
   "diff"

The original discussion for this can be read at:

 * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/

Changes since v1:

 * Update commit descriptions
 * Update tests to use the more modern and robust test_grep and test_config
   utils
 * Reword some documentation / user messages
 * Ensure each commit is atomic and builds/passes tests on it's own
 * Make new command line options DRY
 * Add tests for interhunk context interaction
 * Error if context config/command line options are negative
 * Drop previous last commit to do with new subcommand for --interactive
   add/commit. My motivations behind this patch series originally where
   quite simple, just for add-patch commands to respect context configs.
   This subcommand, after the discussion in v1, will require more thought
   and a larger implementation that what I had anticipated. I would prefer
   to leave this for another time as it's the least impactful but the most
   time intensive and complicated idea.

Changes since v2:

 * Update tests to only test single command (following Philip's suggestion)
 * Add negative option checks
 * Minor commit re-wording

Leon Michalak (4):
  test: use "test_grep"
  test: use "test_config"
  add-patch: respect diff.context configuration
  add-patch: add diff.context command line overrides

 Documentation/diff-context-options.adoc |  10 ++
 Documentation/git-add.adoc              |   2 +
 Documentation/git-checkout.adoc         |   2 +
 Documentation/git-commit.adoc           |   2 +
 Documentation/git-reset.adoc            |   2 +
 Documentation/git-restore.adoc          |   2 +
 Documentation/git-stash.adoc            |   2 +
 add-interactive.c                       |  53 +++++++++--
 add-interactive.h                       |  17 +++-
 add-patch.c                             |  11 ++-
 builtin/add.c                           |  21 ++++-
 builtin/checkout.c                      |  31 +++++-
 builtin/commit.c                        |  16 +++-
 builtin/reset.c                         |  17 +++-
 builtin/stash.c                         |  56 ++++++++---
 commit.h                                |   3 +-
 parse-options.h                         |   2 +
 t/t3701-add-interactive.sh              | 119 +++++++++++++++++++-----
 t/t4055-diff-context.sh                 |  72 +++++++++-----
 t/t9902-completion.sh                   |   2 +
 20 files changed, 362 insertions(+), 80 deletions(-)
 create mode 100644 Documentation/diff-context-options.adoc


base-commit: cf6f63ea6bf35173e02e18bdc6a4ba41288acff9
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/1915

Range-diff vs v2:

 1:  4f92a1b4c24 ! 1:  044a93014b6 test: refactor to use "test_grep"
     @@ Metadata
      Author: Leon Michalak <leonmichalak6@gmail.com>
      
       ## Commit message ##
     -    test: refactor to use "test_grep"
     +    test: use "test_grep"
      
     -    Refactor to use the modern "test_grep" test utility instead of regular
     -    "grep" which provides better debug information if tests fail.
     +    Use the modern "test_grep" test utility instead of regular "grep" which
     +    provides better debug information if tests fail.
      
          This is a prerequisite to the commits that follow which add to both test
          files.
 2:  75424cb8e1c ! 2:  e5c40d37750 test: refactor to use "test_config"
     @@ Metadata
      Author: Leon Michalak <leonmichalak6@gmail.com>
      
       ## Commit message ##
     -    test: refactor to use "test_config"
     +    test: use "test_config"
      
     -    Refactor to use the modern "test_config" test utility instead of manual
     -    "git config" as the former provides clean up on test completion.
     +    Use the modern "test_config" test utility instead of manual"git config"
     +    as the former provides clean up on test completion.
      
          This is a prerequisite to the commits that follow which add to this test
          file.
 3:  f16d3de8611 ! 3:  1ec8a138486 add-patch: respect diff.context configuration
     @@ Commit message
          the user's diff.context and diff.interHunkContext file configurations.
          This patch fixes this inconsistency.
      
     +    This is because the plumbing commands used by "git add -p" to generate
     +    the diff do not read those config settings. Fix this by reading the
     +    config before generating the patch and passing it along to the diff
     +    command with the "-U" and "--inter-hunk-context" command-line options.
     +
          Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
      
       ## add-interactive.c ##
     @@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repositor
      +			die(_("%s cannot be negative"), "diff.context");
      +		else
      +			s->context = context;
     -+	};
     ++	}
      +	if (!repo_config_get_int(r, "diff.interHunkContext", &interhunkcontext)) {
      +		if (interhunkcontext < 0)
      +			die(_("%s cannot be negative"), "diff.interHunkContext");
      +		else
      +			s->interhunkcontext = interhunkcontext;
     -+	};
     ++	}
      +
       	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
       	if (s->use_single_key)
     @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec
       		strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
       	if (s->revision) {
      
     - ## t/t4055-diff-context.sh ##
     -@@ t/t4055-diff-context.sh: test_expect_success 'diff.context honored by "log"' '
     - 	test_grep "^ firstline" output
     + ## t/t3701-add-interactive.sh ##
     +@@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
     + 	test_cmp expect actual
       '
       
     -+test_expect_success 'diff.context honored by "add"' '
     -+	git add -p >output &&
     -+	test_grep ! firstline output &&
     -+	test_config diff.context 8 &&
     -+	git log -1 -p >output &&
     -+	test_grep "^ firstline" output
     -+'
     -+
     -+test_expect_success 'diff.context honored by "commit"' '
     -+	! git commit -p >output &&
     -+	test_grep ! firstline output &&
     -+	test_config diff.context 8 &&
     -+	! git commit -p >output &&
     -+	test_grep "^ firstline" output
     -+'
     -+
     -+test_expect_success 'diff.context honored by "checkout"' '
     -+	git checkout -p >output &&
     -+	test_grep ! firstline output &&
     -+	test_config diff.context 8 &&
     -+	git checkout -p >output &&
     -+	test_grep "^ firstline" output
     -+'
     -+
     -+test_expect_success 'diff.context honored by "stash"' '
     -+	! git stash -p >output &&
     -+	test_grep ! firstline output &&
     -+	test_config diff.context 8 &&
     -+	! git stash -p >output &&
     -+	test_grep "^ firstline" output
     ++test_expect_success 'add -p respects diff.context' '
     ++	test_write_lines a b c d e f g h i j k l m >file &&
     ++	git add file &&
     ++	test_write_lines a b c d e f G h i j k l m >file &&
     ++	echo y | git -c diff.context=5 add -p >actual &&
     ++	test_grep "@@ -2,11 +2,11 @@" actual
      +'
      +
     -+test_expect_success 'diff.context honored by "restore"' '
     -+	git restore -p >output &&
     -+	test_grep ! firstline output &&
     -+	test_config diff.context 8 &&
     -+	git restore -p >output &&
     -+	test_grep "^ firstline" output
     ++test_expect_success 'add -p respects diff.interHunkContext' '
     ++	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
     ++	git add file &&
     ++	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
     ++	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
     ++	test_grep "@@ -2,16 +2,16 @@" actual
      +'
      +
     - test_expect_success 'The -U option overrides diff.context' '
     - 	test_config diff.context 8 &&
     - 	git log -U4 -1 >output &&
     -@@ t/t4055-diff-context.sh: test_expect_success 'negative integer config parsing' '
     - 	test_grep "bad config variable" output
     - '
     - 
     -+test_expect_success 'negative integer config parsing by "add"' '
     ++test_expect_success 'add -p rejects negative diff.context' '
      +	test_config diff.context -1 &&
      +	test_must_fail git add -p 2>output &&
      +	test_grep "diff.context cannot be negative" output
      +'
      +
     -+test_expect_success 'negative integer config parsing by "commit"' '
     -+	test_config diff.context -1 &&
     -+	test_must_fail git commit -p 2>output &&
     -+	test_grep "bad config variable" output
     -+'
     -+
     -+test_expect_success 'negative integer config parsing by "checkout"' '
     -+	test_config diff.context -1 &&
     -+	test_must_fail git checkout -p 2>output &&
     -+	test_grep "diff.context cannot be negative" output
     -+'
     -+
     -+test_expect_success 'negative integer config parsing by "stash"' '
     -+	test_config diff.context -1 &&
     -+	test_must_fail git stash -p 2>output &&
     -+	test_grep "diff.context cannot be negative" output
     -+'
     -+
     -+test_expect_success 'negative integer config parsing by "restore"' '
     -+	test_config diff.context -1 &&
     -+	test_must_fail git restore -p 2>output &&
     -+	test_grep "diff.context cannot be negative" output
     -+'
     -+
     - test_expect_success '-U0 is valid, so is diff.context=0' '
     - 	test_config diff.context 0 &&
     - 	git diff >output &&
     + test_done
 4:  973dfadd1b3 ! 4:  b68c58b667c add-patch: add diff.context command line overrides
     @@ Documentation/git-checkout.adoc: section of linkgit:git-add[1] to learn how to o
       
      +include::diff-context-options.adoc[]
      +
     - --ignore-other-worktrees::
     + `--ignore-other-worktrees`::
       	`git checkout` refuses when the wanted branch is already checked
       	out or otherwise in use by another worktree. This option makes
      
     @@ builtin/add.c: static struct option builtin_add_options[] = {
       	OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
       	OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
       	OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
     +@@ builtin/add.c: int cmd_add(int argc,
     + 	prepare_repo_settings(repo);
     + 	repo->settings.command_requires_full_index = 0;
     + 
     ++	if (add_p_opt.context < -1)
     ++		die(_("'%s' cannot be negative"), "--unified");
     ++	if (add_p_opt.interhunkcontext < -1)
     ++		die(_("'%s' cannot be negative"), "--inter-hunk-context");
     ++
     + 	if (patch_interactive)
     + 		add_interactive = 1;
     + 	if (add_interactive) {
      @@ builtin/add.c: int cmd_add(int argc,
       			die(_("options '%s' and '%s' cannot be used together"), "--dry-run", "--interactive/--patch");
       		if (pathspec_from_file)
     @@ builtin/checkout.c: static int checkout_paths(const struct checkout_opts *opts,
      -				   &opts->pathspec);
      +		return !!run_add_p(the_repository, patch_mode, &add_p_opt,
      +				   rev, &opts->pathspec);
     -+	} else {
     -+		if (opts->patch_context != -1)
     -+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
     -+		if (opts->patch_interhunk_context != -1)
     -+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
       	}
       
       	repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
     @@ builtin/checkout.c: static struct option *add_checkout_path_options(struct check
       		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
       			 N_("do not limit pathspecs to sparse entries only")),
       		OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),
     +@@ builtin/checkout.c: static int checkout_main(int argc, const char **argv, const char *prefix,
     + 	argc = parse_options(argc, argv, prefix, options,
     + 			     usagestr, parseopt_flags);
     + 
     ++	if (opts->patch_context < -1)
     ++		die(_("'%s' cannot be negative"), "--unified");
     ++	if (opts->patch_interhunk_context < -1)
     ++		die(_("'%s' cannot be negative"), "--inter-hunk-context");
     ++
     ++	if (!opts->patch_mode) {
     ++		if (opts->patch_context != -1)
     ++			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
     ++		if (opts->patch_interhunk_context != -1)
     ++			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
     ++	}
     ++
     + 	if (opts->show_progress < 0) {
     + 		if (opts->quiet)
     + 			opts->show_progress = 0;
      
       ## builtin/commit.c ##
      @@
     @@ builtin/commit.c: static const char *edit_message, *use_message;
       static int edit_flag = -1; /* unspecified */
       static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
       static int config_commit_verbose = -1; /* unspecified */
     +@@ builtin/commit.c: static const char *prepare_index(const char **argv, const char *prefix,
     + 	const char *ret;
     + 	char *path = NULL;
     + 
     ++	if (add_p_opt.context < -1)
     ++		die(_("'%s' cannot be negative"), "--unified");
     ++	if (add_p_opt.interhunkcontext < -1)
     ++		die(_("'%s' cannot be negative"), "--inter-hunk-context");
     ++
     + 	if (is_status)
     + 		refresh_flags |= REFRESH_UNMERGED;
     + 	parse_pathspec(&pathspec, 0,
      @@ builtin/commit.c: static const char *prepare_index(const char **argv, const char *prefix,
       		old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
       		setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
     @@ builtin/reset.c: int cmd_reset(int argc,
       		OPT_BOOL('N', "intent-to-add", &intent_to_add,
       				N_("record only the fact that removed paths will be added later")),
       		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
     +@@ builtin/reset.c: int cmd_reset(int argc,
     + 		oidcpy(&oid, &tree->object.oid);
     + 	}
     + 
     ++	if (add_p_opt.context < -1)
     ++		die(_("'%s' cannot be negative"), "--unified");
     ++	if (add_p_opt.interhunkcontext < -1)
     ++		die(_("'%s' cannot be negative"), "--inter-hunk-context");
     ++
     + 	prepare_repo_settings(the_repository);
     + 	the_repository->settings.command_requires_full_index = 0;
     + 
      @@ builtin/reset.c: int cmd_reset(int argc,
       		if (reset_type != NONE)
       			die(_("options '%s' and '%s' cannot be used together"), "--patch", "--{hard,mixed,soft}");
     @@ builtin/stash.c: static int push_stash(int argc, const char **argv, const char *
      +		if (add_p_opt.interhunkcontext != -1)
      +			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
      +	}
     ++
     ++	if (add_p_opt.context < -1)
     ++		die(_("'%s' cannot be negative"), "--unified");
     ++	if (add_p_opt.interhunkcontext < -1)
     ++		die(_("'%s' cannot be negative"), "--inter-hunk-context");
      +
       	ret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
      -			    include_untracked, only_staged);
     @@ builtin/stash.c: static int save_stash(int argc, const char **argv, const char *
       
       	memset(&ps, 0, sizeof(ps));
      +
     ++	if (add_p_opt.context < -1)
     ++		die(_("'%s' cannot be negative"), "--unified");
     ++	if (add_p_opt.interhunkcontext < -1)
     ++		die(_("'%s' cannot be negative"), "--inter-hunk-context");
     ++
      +	if (!patch_mode) {
      +		if (add_p_opt.context != -1)
      +			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
     @@ parse-options.h: int parse_opt_tracking_mode(const struct option *, const char *
       #define OPT_IPVERSION(v) \
       	OPT_SET_INT_F('4', "ipv4", (v), N_("use IPv4 addresses only"), \
      
     - ## t/t4032-diff-inter-hunk-context.sh ##
     -@@ t/t4032-diff-inter-hunk-context.sh: t() {
     - 	"
     - }
     + ## t/t3701-add-interactive.sh ##
     +@@ t/t3701-add-interactive.sh: test_expect_success 'add -p rejects negative diff.context' '
     + 	test_grep "diff.context cannot be negative" output
     + '
       
     -+t_patch() {
     -+	use_config=
     -+	git config --unset diff.interHunkContext
     -+
     -+	case $# in
     -+	4) hunks=$4; cmd="add -p -U$3";;
     -+	5) hunks=$5; cmd="add -p -U$3 --inter-hunk-context=$4";;
     -+	6) hunks=$5; cmd="add -p -U$3"; git config diff.interHunkContext $4; use_config="(diff.interHunkContext=$4) ";;
     -+	esac
     -+	label="$use_config$cmd, $1 common $2"
     -+	file=f$1
     -+
     -+	if ! test -f $file
     -+	then
     -+		f A $1 B >$file
     -+		git add $file
     -+		git commit -q -m. $file
     -+		f X $1 Y >$file
     -+	fi
     -+
     -+	test_expect_success "$label: count hunks ($hunks)" "
     -+		test $(test_write_lines q | git $cmd $file | sed -n 's/^([0-9]*\/\([0-9]*\)) Stage this hunk.*/\1/p') = $hunks
     ++for cmd in add checkout restore 'commit -m file'
     ++do
     ++	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" "
     ++		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 &&
     ++		git add file &&
     ++		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 &&
     ++		echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
     ++			$cmd -p -U 4 --inter-hunk-context 2 >actual &&
     ++		test_grep \"@@ -2,20 +2,20 @@\" actual
      +	"
     -+}
     ++done
     ++
     ++test_expect_success 'reset accepts -U and --inter-hunk-context' '
     ++	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 &&
     ++	git commit -m file file &&
     ++	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 &&
     ++	git add file &&
     ++	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
     ++		reset -p -U 4 --inter-hunk-context 2 >actual &&
     ++	test_grep "@@ -2,20 +2,20 @@" actual
     ++'
     ++
     ++test_expect_success 'stash accepts -U and --inter-hunk-context' '
     ++	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 &&
     ++	git commit -m file file &&
     ++	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 &&
     ++	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
     ++		stash -p -U 4 --inter-hunk-context 2 >actual &&
     ++	test_grep "@@ -2,20 +2,20 @@" actual
     ++'
     ++
     ++for cmd in add checkout commit reset restore 'stash save' 'stash push'
     ++do
     ++	test_expect_success "$cmd rejects invalid context options" "
     ++		test_must_fail git $cmd -p -U -3 2>actual &&
     ++		cat actual | echo &&
     ++		test_grep -e \"'--unified' cannot be negative\" actual &&
     ++
     ++		test_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&
     ++		test_grep -e \"'--inter-hunk-context' cannot be negative\" actual &&
     ++
     ++		test_must_fail git $cmd -U 7 2>actual &&
     ++		test_grep -E \"'--unified' requires '(--interactive/)?--patch'\" actual &&
     ++
     ++		test_must_fail git $cmd --inter-hunk-context 2 2>actual &&
     ++		test_grep -E \"'--inter-hunk-context' requires '(--interactive/)?--patch'\" actual
     ++	"
     ++done
      +
     - cat <<EOF >expected.f1.0.1 || exit 1
     - diff --git a/f1 b/f1
     - --- a/f1
     -@@ t/t4032-diff-inter-hunk-context.sh: t 3 lines	1	2	1	config
     - t 9 lines	3	2	2	config
     - t 9 lines	3	3	1	config
     - 
     -+# common lines	ctx	intrctx	hunks
     -+t_patch 1 line	0		2
     -+t_patch 1 line	0	0	2
     -+t_patch 1 line	0	1	1
     -+t_patch 1 line	0	2	1
     -+t_patch 1 line	1		1
     -+
     -+t_patch 2 lines	0		2
     -+t_patch 2 lines	0	0	2
     -+t_patch 2 lines	0	1	2
     -+t_patch 2 lines	0	2	1
     -+t_patch 2 lines	1		1
     -+
     -+t_patch 3 lines	1		2
     -+t_patch 3 lines	1	0	2
     -+t_patch 3 lines	1	1	1
     -+t_patch 3 lines	1	2	1
     -+
     -+t_patch 9 lines	3		2
     -+t_patch 9 lines	3	2	2
     -+t_patch 9 lines	3	3	1
     -+
     -+#					use diff.interHunkContext?
     -+t_patch 1 line	0	0	2	config
     -+t_patch 1 line	0	1	1	config
     -+t_patch 1 line	0	2	1	config
     -+t_patch 9 lines	3	3	1	config
     -+t_patch 2 lines	0	0	2	config
     -+t_patch 2 lines	0	1	2	config
     -+t_patch 2 lines	0	2	1	config
     -+t_patch 3 lines	1	0	2	config
     -+t_patch 3 lines	1	1	1	config
     -+t_patch 3 lines	1	2	1	config
     -+t_patch 9 lines	3	2	2	config
     -+t_patch 9 lines	3	3	1	config
     -+
     - test_expect_success 'diff.interHunkContext invalid' '
     - 	git config diff.interHunkContext asdf &&
     - 	test_must_fail git diff &&
     + test_done
      
       ## t/t4055-diff-context.sh ##
      @@ t/t4055-diff-context.sh: test_expect_success 'The -U option overrides diff.context' '

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 77+ messages in thread

* [PATCH v3 1/4] test: use "test_grep"
  2025-06-28 16:34   ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
@ 2025-06-28 16:34     ` Leon Michalak via GitGitGadget
  2025-06-30 16:23       ` Junio C Hamano
  2025-06-28 16:34     ` [PATCH v3 2/4] test: use "test_config" Leon Michalak via GitGitGadget
                       ` (5 subsequent siblings)
  6 siblings, 1 reply; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-06-28 16:34 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

Use the modern "test_grep" test utility instead of regular "grep" which
provides better debug information if tests fail.

This is a prerequisite to the commits that follow which add to both test
files.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 t/t3701-add-interactive.sh | 48 +++++++++++++++++++-------------------
 t/t4055-diff-context.sh    | 28 +++++++++++-----------
 2 files changed, 38 insertions(+), 38 deletions(-)

diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index b8a05d95f3f1..b088ee141ff4 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -63,7 +63,7 @@ test_expect_success 'setup (initial)' '
 '
 test_expect_success 'status works (initial)' '
 	git add -i </dev/null >output &&
-	grep "+1/-0 *+2/-0 file" output
+	test_grep "+1/-0 *+2/-0 file" output
 '
 
 test_expect_success 'setup expected' '
@@ -86,7 +86,7 @@ test_expect_success 'revert works (initial)' '
 	git add file &&
 	test_write_lines r 1 | git add -i &&
 	git ls-files >output &&
-	! grep . output
+	test_grep ! . output
 '
 
 test_expect_success 'add untracked (multiple)' '
@@ -109,7 +109,7 @@ test_expect_success 'setup (commit)' '
 '
 test_expect_success 'status works (commit)' '
 	git add -i </dev/null >output &&
-	grep "+1/-0 *+2/-0 file" output
+	test_grep "+1/-0 *+2/-0 file" output
 '
 
 test_expect_success 'update can stage deletions' '
@@ -141,7 +141,7 @@ test_expect_success 'revert works (commit)' '
 	git add file &&
 	test_write_lines r 1 | git add -i &&
 	git add -i </dev/null >output &&
-	grep "unchanged *+3/-0 file" output
+	test_grep "unchanged *+3/-0 file" output
 '
 
 test_expect_success 'reject multi-key input' '
@@ -185,7 +185,7 @@ test_expect_success 'setup fake editor' '
 test_expect_success 'bad edit rejected' '
 	git reset &&
 	test_write_lines e n d | git add -p >output &&
-	grep "hunk does not apply" output
+	test_grep "hunk does not apply" output
 '
 
 test_expect_success 'setup patch' '
@@ -198,7 +198,7 @@ test_expect_success 'setup patch' '
 test_expect_success 'garbage edit rejected' '
 	git reset &&
 	test_write_lines e n d | git add -p >output &&
-	grep "hunk does not apply" output
+	test_grep "hunk does not apply" output
 '
 
 test_expect_success 'setup patch' '
@@ -313,8 +313,8 @@ test_expect_success FILEMODE 'stage mode and hunk' '
 	chmod +x file &&
 	printf "y\\ny\\n" | git add -p &&
 	git diff --cached file >out &&
-	grep "new mode" out &&
-	grep "+content" out &&
+	test_grep "new mode" out &&
+	test_grep "+content" out &&
 	git diff file >out &&
 	test_must_be_empty out
 '
@@ -636,7 +636,7 @@ test_expect_success 'split hunk "add -p (edit)"' '
 	printf "%s\n" s e     q n q q |
 	EDITOR=: git add -p &&
 	git diff >actual &&
-	! grep "^+15" actual
+	test_grep ! "^+15" actual
 '
 
 test_expect_success 'split hunk "add -p (no, yes, edit)"' '
@@ -648,7 +648,7 @@ test_expect_success 'split hunk "add -p (no, yes, edit)"' '
 	EDITOR=: git add -p 2>error &&
 	test_must_be_empty error &&
 	git diff >actual &&
-	! grep "^+31" actual
+	test_grep ! "^+31" actual
 '
 
 test_expect_success 'split hunk with incomplete line at end' '
@@ -682,7 +682,7 @@ test_expect_success 'edit, adding lines to the first hunk' '
 	EDITOR=./fake_editor.sh git add -p 2>error &&
 	test_must_be_empty error &&
 	git diff --cached >actual &&
-	grep "^+22" actual
+	test_grep "^+22" actual
 '
 
 test_expect_success 'patch mode ignores unmerged entries' '
@@ -696,7 +696,7 @@ test_expect_success 'patch mode ignores unmerged entries' '
 	test_must_fail git merge side &&
 	echo changed >non-conflict.t &&
 	echo y | git add -p >output &&
-	! grep a/conflict.t output &&
+	test_grep ! a/conflict.t output &&
 	cat >expected <<-\EOF &&
 	* Unmerged path conflict.t
 	diff --git a/non-conflict.t b/non-conflict.t
@@ -728,7 +728,7 @@ test_expect_success 'diffs can be colorized' '
 
 	# We do not want to depend on the exact coloring scheme
 	# git uses for diffs, so just check that we saw some kind of color.
-	grep "$(printf "\\033")" output
+	test_grep "$(printf "\\033")" output
 '
 
 test_expect_success 'colors can be overridden' '
@@ -743,7 +743,7 @@ test_expect_success 'colors can be overridden' '
 		-c color.interactive.error=blue \
 		add -i 2>err.raw <input &&
 	test_decode_color <err.raw >err &&
-	grep "<BLUE>Huh (trigger)?<RESET>" err &&
+	test_grep "<BLUE>Huh (trigger)?<RESET>" err &&
 
 	test_write_lines help quit >input &&
 	force_color git \
@@ -863,7 +863,7 @@ test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '
 	printf y >y &&
 	force_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&
 	test_decode_color <output.raw >output &&
-	grep "old<" output
+	test_grep "old<" output
 '
 
 test_expect_success 'diffFilter filters diff' '
@@ -876,7 +876,7 @@ test_expect_success 'diffFilter filters diff' '
 
 	# avoid depending on the exact coloring or content of the prompts,
 	# and just make sure we saw our diff prefixed
-	grep foo:.*content output
+	test_grep foo:.*content output
 '
 
 test_expect_success 'detect bogus diffFilter output' '
@@ -886,7 +886,7 @@ test_expect_success 'detect bogus diffFilter output' '
 	test_config interactive.diffFilter "sed 6d" &&
 	printf y >y &&
 	force_color test_must_fail git add -p <y >output 2>&1 &&
-	grep "mismatched output" output
+	test_grep "mismatched output" output
 '
 
 test_expect_success 'handle iffy colored hunk headers' '
@@ -896,7 +896,7 @@ test_expect_success 'handle iffy colored hunk headers' '
 	printf n >n &&
 	force_color git -c interactive.diffFilter="sed s/.*@@.*/XX/" \
 		add -p >output 2>&1 <n &&
-	grep "^XX$" output
+	test_grep "^XX$" output
 '
 
 test_expect_success 'handle very large filtered diff' '
@@ -1002,7 +1002,7 @@ test_expect_success 'add -p does not expand argument lists' '
 	# update it, but we want to be sure that our "." pathspec
 	# was not expanded into the argument list of any command.
 	# So look only for "not-changed".
-	! grep -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
+	test_grep ! -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
 '
 
 test_expect_success 'hunk-editing handles custom comment char' '
@@ -1072,21 +1072,21 @@ test_expect_success 'setup different kinds of dirty submodules' '
 
 test_expect_success 'status ignores dirty submodules (except HEAD)' '
 	git -C for-submodules add -i </dev/null >output &&
-	grep dirty-head output &&
-	grep dirty-both-ways output &&
-	! grep dirty-otherwise output
+	test_grep dirty-head output &&
+	test_grep dirty-both-ways output &&
+	test_grep ! dirty-otherwise output
 '
 
 test_expect_success 'handle submodules' '
 	echo 123 >>for-submodules/dirty-otherwise/initial.t &&
 
 	force_color git -C for-submodules add -p dirty-otherwise >output 2>&1 &&
-	grep "No changes" output &&
+	test_grep "No changes" output &&
 
 	force_color git -C for-submodules add -p dirty-head >output 2>&1 <y &&
 	git -C for-submodules ls-files --stage dirty-head >actual &&
 	rev="$(git -C for-submodules/dirty-head rev-parse HEAD)" &&
-	grep "$rev" actual
+	test_grep "$rev" actual
 '
 
 test_expect_success 'set up pathological context' '
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index ec2804eea67c..c66f966a3ab3 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -38,36 +38,36 @@ test_expect_success 'setup' '
 
 test_expect_success 'the default number of context lines is 3' '
 	git diff >output &&
-	! grep "^ d" output &&
-	grep "^ e" output &&
-	grep "^ j" output &&
-	! grep "^ k" output
+	test_grep ! "^ d" output &&
+	test_grep "^ e" output &&
+	test_grep "^ j" output &&
+	test_grep ! "^ k" output
 '
 
 test_expect_success 'diff.context honored by "log"' '
 	git log -1 -p >output &&
-	! grep firstline output &&
+	test_grep ! firstline output &&
 	git config diff.context 8 &&
 	git log -1 -p >output &&
-	grep "^ firstline" output
+	test_grep "^ firstline" output
 '
 
 test_expect_success 'The -U option overrides diff.context' '
 	git config diff.context 8 &&
 	git log -U4 -1 >output &&
-	! grep "^ firstline" output
+	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'diff.context honored by "diff"' '
 	git config diff.context 8 &&
 	git diff >output &&
-	grep "^ firstline" output
+	test_grep "^ firstline" output
 '
 
 test_expect_success 'plumbing not affected' '
 	git config diff.context 8 &&
 	git diff-files -p >output &&
-	! grep "^ firstline" output
+	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'non-integer config parsing' '
@@ -85,8 +85,8 @@ test_expect_success 'negative integer config parsing' '
 test_expect_success '-U0 is valid, so is diff.context=0' '
 	git config diff.context 0 &&
 	git diff >output &&
-	grep "^-ADDED" output &&
-	grep "^+MODIFIED" output
+	test_grep "^-ADDED" output &&
+	test_grep "^+MODIFIED" output
 '
 
 test_expect_success '-U2147483647 works' '
@@ -94,9 +94,9 @@ test_expect_success '-U2147483647 works' '
 	test_line_count = 16 x &&
 	git diff -U2147483647 >output &&
 	test_line_count = 22 output &&
-	grep "^-ADDED" output &&
-	grep "^+MODIFIED" output &&
-	grep "^+APPENDED" output
+	test_grep "^-ADDED" output &&
+	test_grep "^+MODIFIED" output &&
+	test_grep "^+APPENDED" output
 '
 
 test_done
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v3 2/4] test: use "test_config"
  2025-06-28 16:34   ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
  2025-06-28 16:34     ` [PATCH v3 1/4] test: use "test_grep" Leon Michalak via GitGitGadget
@ 2025-06-28 16:34     ` Leon Michalak via GitGitGadget
  2025-06-30 16:35       ` Junio C Hamano
  2025-06-28 16:34     ` [PATCH v3 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
                       ` (4 subsequent siblings)
  6 siblings, 1 reply; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-06-28 16:34 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

Use the modern "test_config" test utility instead of manual"git config"
as the former provides clean up on test completion.

This is a prerequisite to the commits that follow which add to this test
file.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 t/t4055-diff-context.sh | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index c66f966a3ab3..1384a8195705 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -47,43 +47,43 @@ test_expect_success 'the default number of context lines is 3' '
 test_expect_success 'diff.context honored by "log"' '
 	git log -1 -p >output &&
 	test_grep ! firstline output &&
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git log -1 -p >output &&
 	test_grep "^ firstline" output
 '
 
 test_expect_success 'The -U option overrides diff.context' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git log -U4 -1 >output &&
 	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'diff.context honored by "diff"' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git diff >output &&
 	test_grep "^ firstline" output
 '
 
 test_expect_success 'plumbing not affected' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git diff-files -p >output &&
 	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'non-integer config parsing' '
-	git config diff.context no &&
+	test_config diff.context no &&
 	test_must_fail git diff 2>output &&
 	test_grep "bad numeric config value" output
 '
 
 test_expect_success 'negative integer config parsing' '
-	git config diff.context -1 &&
+	test_config diff.context -1 &&
 	test_must_fail git diff 2>output &&
 	test_grep "bad config variable" output
 '
 
 test_expect_success '-U0 is valid, so is diff.context=0' '
-	git config diff.context 0 &&
+	test_config diff.context 0 &&
 	git diff >output &&
 	test_grep "^-ADDED" output &&
 	test_grep "^+MODIFIED" output
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v3 3/4] add-patch: respect diff.context configuration
  2025-06-28 16:34   ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
  2025-06-28 16:34     ` [PATCH v3 1/4] test: use "test_grep" Leon Michalak via GitGitGadget
  2025-06-28 16:34     ` [PATCH v3 2/4] test: use "test_config" Leon Michalak via GitGitGadget
@ 2025-06-28 16:34     ` Leon Michalak via GitGitGadget
  2025-06-30 16:55       ` Junio C Hamano
  2025-07-01 10:00       ` Phillip Wood
  2025-06-28 16:34     ` [PATCH v3 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
                       ` (3 subsequent siblings)
  6 siblings, 2 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-06-28 16:34 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

Various builtins that use add-patch infrastructure do not respect
the user's diff.context and diff.interHunkContext file configurations.
This patch fixes this inconsistency.

This is because the plumbing commands used by "git add -p" to generate
the diff do not read those config settings. Fix this by reading the
config before generating the patch and passing it along to the diff
command with the "-U" and "--inter-hunk-context" command-line options.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 add-interactive.c          | 17 +++++++++++++++++
 add-interactive.h          |  1 +
 add-patch.c                |  6 ++++++
 t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++
 4 files changed, 46 insertions(+)

diff --git a/add-interactive.c b/add-interactive.c
index 97ff35b6f12a..e0aafb8dd02a 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -39,8 +39,12 @@ static void init_color(struct repository *r, struct add_i_state *s,
 void init_add_i_state(struct add_i_state *s, struct repository *r)
 {
 	const char *value;
+	int context;
+	int interhunkcontext;
 
 	s->r = r;
+	s->context = -1;
+	s->interhunkcontext = -1;
 
 	if (repo_config_get_value(r, "color.interactive", &value))
 		s->use_color = -1;
@@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	repo_config_get_string(r, "diff.algorithm",
 			       &s->interactive_diff_algorithm);
 
+	if (!repo_config_get_int(r, "diff.context", &context)) {
+		if (context < 0)
+			die(_("%s cannot be negative"), "diff.context");
+		else
+			s->context = context;
+	}
+	if (!repo_config_get_int(r, "diff.interHunkContext", &interhunkcontext)) {
+		if (interhunkcontext < 0)
+			die(_("%s cannot be negative"), "diff.interHunkContext");
+		else
+			s->interhunkcontext = interhunkcontext;
+	}
+
 	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
 	if (s->use_single_key)
 		setbuf(stdin, NULL);
diff --git a/add-interactive.h b/add-interactive.h
index 693f125e8e4b..c63f35b14be8 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -18,6 +18,7 @@ struct add_i_state {
 
 	int use_single_key;
 	char *interactive_diff_filter, *interactive_diff_algorithm;
+	int context, interhunkcontext;
 };
 
 void init_add_i_state(struct add_i_state *s, struct repository *r);
diff --git a/add-patch.c b/add-patch.c
index 95c67d8c80c4..b43ca1600738 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 {
 	struct strvec args = STRVEC_INIT;
 	const char *diff_algorithm = s->s.interactive_diff_algorithm;
+	int diff_context = s->s.context;
+	int diff_interhunkcontext = s->s.interhunkcontext;
 	struct strbuf *plain = &s->plain, *colored = NULL;
 	struct child_process cp = CHILD_PROCESS_INIT;
 	char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
@@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 	int res;
 
 	strvec_pushv(&args, s->mode->diff_cmd);
+	if (diff_context != -1)
+		strvec_pushf(&args, "--unified=%i", diff_context);
+	if (diff_interhunkcontext != -1)
+		strvec_pushf(&args, "--inter-hunk-context=%i", diff_interhunkcontext);
 	if (diff_algorithm)
 		strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
 	if (s->revision) {
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index b088ee141ff4..18dc329ea1f6 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -1230,4 +1230,26 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
 	test_cmp expect actual
 '
 
+test_expect_success 'add -p respects diff.context' '
+	test_write_lines a b c d e f g h i j k l m >file &&
+	git add file &&
+	test_write_lines a b c d e f G h i j k l m >file &&
+	echo y | git -c diff.context=5 add -p >actual &&
+	test_grep "@@ -2,11 +2,11 @@" actual
+'
+
+test_expect_success 'add -p respects diff.interHunkContext' '
+	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
+	git add file &&
+	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
+	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
+	test_grep "@@ -2,16 +2,16 @@" actual
+'
+
+test_expect_success 'add -p rejects negative diff.context' '
+	test_config diff.context -1 &&
+	test_must_fail git add -p 2>output &&
+	test_grep "diff.context cannot be negative" output
+'
+
 test_done
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v3 4/4] add-patch: add diff.context command line overrides
  2025-06-28 16:34   ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
                       ` (2 preceding siblings ...)
  2025-06-28 16:34     ` [PATCH v3 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
@ 2025-06-28 16:34     ` Leon Michalak via GitGitGadget
  2025-06-30 17:03       ` Junio C Hamano
  2025-07-01  9:59       ` Phillip Wood
  2025-06-30 16:16     ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Junio C Hamano
                       ` (2 subsequent siblings)
  6 siblings, 2 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-06-28 16:34 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

This patch compliments the previous commit, where builtins that use
add-patch infrastructure now respect diff.context and
diff.interHunkContext file configurations.

In particular, this patch helps users who don't want to set persistent
context configurations or just want a way to override them on a one-time
basis, by allowing the relevant builtins to accept corresponding command
line options that override the file configurations.

This mimics commands such as diff and log, which allow for both context
file configuration and command line overrides.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 Documentation/diff-context-options.adoc | 10 +++++
 Documentation/git-add.adoc              |  2 +
 Documentation/git-checkout.adoc         |  2 +
 Documentation/git-commit.adoc           |  2 +
 Documentation/git-reset.adoc            |  2 +
 Documentation/git-restore.adoc          |  2 +
 Documentation/git-stash.adoc            |  2 +
 add-interactive.c                       | 36 ++++++++++++----
 add-interactive.h                       | 16 +++++--
 add-patch.c                             |  5 ++-
 builtin/add.c                           | 21 ++++++++--
 builtin/checkout.c                      | 31 ++++++++++++--
 builtin/commit.c                        | 16 ++++++-
 builtin/reset.c                         | 17 +++++++-
 builtin/stash.c                         | 56 ++++++++++++++++++++-----
 commit.h                                |  3 +-
 parse-options.h                         |  2 +
 t/t3701-add-interactive.sh              | 49 ++++++++++++++++++++++
 t/t4055-diff-context.sh                 | 30 +++++++++++++
 t/t9902-completion.sh                   |  2 +
 20 files changed, 271 insertions(+), 35 deletions(-)
 create mode 100644 Documentation/diff-context-options.adoc

diff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc
new file mode 100644
index 000000000000..e161260358ff
--- /dev/null
+++ b/Documentation/diff-context-options.adoc
@@ -0,0 +1,10 @@
+`-U<n>`::
+`--unified=<n>`::
+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
+	or 3 if the config option is unset.
+
+`--inter-hunk-context=<n>`::
+	Show the context between diff hunks, up to the specified _<number>_
+	of lines, thereby fusing hunks that are close to each other.
+	Defaults to `diff.interHunkContext` or 0 if the config option
+	is unset.
diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index eba0b419ce50..b7a735824d6c 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -104,6 +104,8 @@ This effectively runs `add --interactive`, but bypasses the
 initial command menu and directly jumps to the `patch` subcommand.
 See ``Interactive mode'' for details.
 
+include::diff-context-options.adoc[]
+
 `-e`::
 `--edit`::
 	Open the diff vs. the index in an editor and let the user
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index ee83b6d9ba9a..40e02cfd6562 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -289,6 +289,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 Note that this option uses the no overlay mode by default (see also
 `--overlay`), and currently doesn't support overlay mode.
 
+include::diff-context-options.adoc[]
+
 `--ignore-other-worktrees`::
 	`git checkout` refuses when the wanted branch is already checked
 	out or otherwise in use by another worktree. This option makes
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc
index dc219025f1eb..ae988a883b5b 100644
--- a/Documentation/git-commit.adoc
+++ b/Documentation/git-commit.adoc
@@ -76,6 +76,8 @@ OPTIONS
 	which changes to commit. See linkgit:git-add[1] for
 	details.
 
+include::diff-context-options.adoc[]
+
 `-C <commit>`::
 `--reuse-message=<commit>`::
 	Take an existing _<commit>_ object, and reuse the log message
diff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc
index 53ab88c5451c..50e8a0ba6f66 100644
--- a/Documentation/git-reset.adoc
+++ b/Documentation/git-reset.adoc
@@ -125,6 +125,8 @@ OPTIONS
 	separated with _NUL_ character and all other characters are taken
 	literally (including newlines and quotes).
 
+include::diff-context-options.adoc[]
+
 `--`::
 	Do not interpret any more arguments as options.
 
diff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc
index 877b7772e667..1dcc2bb7aea3 100644
--- a/Documentation/git-restore.adoc
+++ b/Documentation/git-restore.adoc
@@ -52,6 +52,8 @@ leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to
 	Mode" section of linkgit:git-add[1] to learn how to operate
 	the `--patch` mode.
 
+include::diff-context-options.adoc[]
+
 `-W`::
 `--worktree`::
 `-S`::
diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc
index 1a5177f4986c..0578c619c410 100644
--- a/Documentation/git-stash.adoc
+++ b/Documentation/git-stash.adoc
@@ -208,6 +208,8 @@ to learn how to operate the `--patch` mode.
 The `--patch` option implies `--keep-index`.  You can use
 `--no-keep-index` to override this.
 
+include::diff-context-options.adoc[]
+
 -S::
 --staged::
 	This option is only valid for `push` and `save` commands.
diff --git a/add-interactive.c b/add-interactive.c
index e0aafb8dd02a..c343568bf7ec 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -36,7 +36,8 @@ static void init_color(struct repository *r, struct add_i_state *s,
 	free(key);
 }
 
-void init_add_i_state(struct add_i_state *s, struct repository *r)
+void init_add_i_state(struct add_i_state *s, struct repository *r,
+		      struct add_p_opt *add_p_opt)
 {
 	const char *value;
 	int context;
@@ -98,6 +99,17 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
 	if (s->use_single_key)
 		setbuf(stdin, NULL);
+
+	if (add_p_opt->context != -1) {
+		if (add_p_opt->context < 0)
+			die(_("%s cannot be negative"), "--unified");
+		s->context = add_p_opt->context;
+	}
+	if (add_p_opt->interhunkcontext != -1) {
+		if (add_p_opt->interhunkcontext < 0)
+			die(_("%s cannot be negative"), "--inter-hunk-context");
+		s->interhunkcontext = add_p_opt->interhunkcontext;
+	}
 }
 
 void clear_add_i_state(struct add_i_state *s)
@@ -986,6 +998,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 	opts->prompt = N_("Patch update");
 	count = list_and_choose(s, files, opts);
 	if (count > 0) {
+		struct add_p_opt add_p_opt = {
+			.context = s->context,
+			.interhunkcontext = s->interhunkcontext,
+		};
 		struct strvec args = STRVEC_INIT;
 		struct pathspec ps_selected = { 0 };
 
@@ -996,7 +1012,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 		parse_pathspec(&ps_selected,
 			       PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
 			       PATHSPEC_LITERAL_PATH, "", args.v);
-		res = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);
+		res = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected);
 		strvec_clear(&args);
 		clear_pathspec(&ps_selected);
 	}
@@ -1031,10 +1047,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
 	if (count > 0) {
 		struct child_process cmd = CHILD_PROCESS_INIT;
 
-		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached",
-			     oid_to_hex(!is_initial ? &oid :
-					s->r->hash_algo->empty_tree),
-			     "--", NULL);
+		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL);
+		if (s->context != -1)
+			strvec_pushf(&cmd.args, "--unified=%i", s->context);
+		if (s->interhunkcontext != -1)
+			strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->interhunkcontext);
+		strvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :
+			     s->r->hash_algo->empty_tree), "--", NULL);
 		for (i = 0; i < files->items.nr; i++)
 			if (files->selected[i])
 				strvec_push(&cmd.args,
@@ -1127,7 +1146,8 @@ static void command_prompt_help(struct add_i_state *s)
 			 _("(empty) select nothing"));
 }
 
-int run_add_i(struct repository *r, const struct pathspec *ps)
+int run_add_i(struct repository *r, const struct pathspec *ps,
+	      struct add_p_opt *add_p_opt)
 {
 	struct add_i_state s = { NULL };
 	struct print_command_item_data data = { "[", "]" };
@@ -1170,7 +1190,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 			->util = util;
 	}
 
-	init_add_i_state(&s, r);
+	init_add_i_state(&s, r, add_p_opt);
 
 	/*
 	 * When color was asked for, use the prompt color for
diff --git a/add-interactive.h b/add-interactive.h
index c63f35b14be8..4213dcd67b9a 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -3,6 +3,13 @@
 
 #include "color.h"
 
+struct add_p_opt {
+	int context;
+	int interhunkcontext;
+};
+
+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }
+
 struct add_i_state {
 	struct repository *r;
 	int use_color;
@@ -21,12 +28,14 @@ struct add_i_state {
 	int context, interhunkcontext;
 };
 
-void init_add_i_state(struct add_i_state *s, struct repository *r);
+void init_add_i_state(struct add_i_state *s, struct repository *r,
+		      struct add_p_opt *add_p_opt);
 void clear_add_i_state(struct add_i_state *s);
 
 struct repository;
 struct pathspec;
-int run_add_i(struct repository *r, const struct pathspec *ps);
+int run_add_i(struct repository *r, const struct pathspec *ps,
+	      struct add_p_opt *add_p_opt);
 
 enum add_p_mode {
 	ADD_P_ADD,
@@ -37,6 +46,7 @@ enum add_p_mode {
 };
 
 int run_add_p(struct repository *r, enum add_p_mode mode,
-	      const char *revision, const struct pathspec *ps);
+	      struct add_p_opt *o, const char *revision,
+	      const struct pathspec *ps);
 
 #endif
diff --git a/add-patch.c b/add-patch.c
index b43ca1600738..c0d33820c558 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -1766,14 +1766,15 @@ soft_increment:
 }
 
 int run_add_p(struct repository *r, enum add_p_mode mode,
-	      const char *revision, const struct pathspec *ps)
+	      struct add_p_opt *o, const char *revision,
+	      const struct pathspec *ps)
 {
 	struct add_p_state s = {
 		{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	size_t i, binary_count = 0;
 
-	init_add_i_state(&s.s, r);
+	init_add_i_state(&s.s, r, o);
 
 	if (mode == ADD_P_STASH)
 		s.mode = &patch_mode_stash;
diff --git a/builtin/add.c b/builtin/add.c
index 7c292ffdc6c2..a00dab9b8fa0 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -29,6 +29,7 @@ static const char * const builtin_add_usage[] = {
 	NULL
 };
 static int patch_interactive, add_interactive, edit_interactive;
+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 static int take_worktree_changes;
 static int add_renormalize;
 static int pathspec_file_nul;
@@ -157,7 +158,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec *
 int interactive_add(struct repository *repo,
 		    const char **argv,
 		    const char *prefix,
-		    int patch)
+		    int patch, struct add_p_opt *add_p_opt)
 {
 	struct pathspec pathspec;
 	int ret;
@@ -169,9 +170,9 @@ int interactive_add(struct repository *repo,
 		       prefix, argv);
 
 	if (patch)
-		ret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);
+		ret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec);
 	else
-		ret = !!run_add_i(repo, &pathspec);
+		ret = !!run_add_i(repo, &pathspec, add_p_opt);
 
 	clear_pathspec(&pathspec);
 	return ret;
@@ -253,6 +254,8 @@ static struct option builtin_add_options[] = {
 	OPT_GROUP(""),
 	OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
 	OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
+	OPT_DIFF_UNIFIED(&add_p_opt.context),
+	OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 	OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
 	OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
 	OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
@@ -394,6 +397,11 @@ int cmd_add(int argc,
 	prepare_repo_settings(repo);
 	repo->settings.command_requires_full_index = 0;
 
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	if (patch_interactive)
 		add_interactive = 1;
 	if (add_interactive) {
@@ -401,7 +409,12 @@ int cmd_add(int argc,
 			die(_("options '%s' and '%s' cannot be used together"), "--dry-run", "--interactive/--patch");
 		if (pathspec_from_file)
 			die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch");
-		exit(interactive_add(repo, argv + 1, prefix, patch_interactive));
+		exit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
 	}
 
 	if (edit_interactive) {
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 536192d3456c..3737ba4c3920 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -61,6 +61,8 @@ static const char * const restore_usage[] = {
 
 struct checkout_opts {
 	int patch_mode;
+	int patch_context;
+	int patch_interhunk_context;
 	int quiet;
 	int merge;
 	int force;
@@ -104,7 +106,12 @@ struct checkout_opts {
 	struct tree *source_tree;
 };
 
-#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 }
+#define CHECKOUT_OPTS_INIT { \
+	.conflict_style = -1, \
+	.merge = -1, \
+	.patch_context = -1, \
+	.patch_interhunk_context = -1, \
+}
 
 struct branch_info {
 	char *name; /* The short name used */
@@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts,
 
 	if (opts->patch_mode) {
 		enum add_p_mode patch_mode;
+		struct add_p_opt add_p_opt = {
+			.context = opts->patch_context,
+			.interhunkcontext = opts->patch_interhunk_context,
+		};
 		const char *rev = new_branch_info->name;
 		char rev_oid[GIT_MAX_HEXSZ + 1];
 
@@ -564,8 +575,8 @@ static int checkout_paths(const struct checkout_opts *opts,
 		else
 			BUG("either flag must have been set, worktree=%d, index=%d",
 			    opts->checkout_worktree, opts->checkout_index);
-		return !!run_add_p(the_repository, patch_mode, rev,
-				   &opts->pathspec);
+		return !!run_add_p(the_repository, patch_mode, &add_p_opt,
+				   rev, &opts->pathspec);
 	}
 
 	repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -1738,6 +1749,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
 		OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
+		OPT_DIFF_UNIFIED(&opts->patch_context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context),
 		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),
@@ -1780,6 +1793,18 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 	argc = parse_options(argc, argv, prefix, options,
 			     usagestr, parseopt_flags);
 
+	if (opts->patch_context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (opts->patch_interhunk_context < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
+	if (!opts->patch_mode) {
+		if (opts->patch_context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (opts->patch_interhunk_context != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
diff --git a/builtin/commit.c b/builtin/commit.c
index fba0dded64a7..73673bc7db9f 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -19,6 +19,7 @@
 #include "environment.h"
 #include "diff.h"
 #include "commit.h"
+#include "add-interactive.h"
 #include "gettext.h"
 #include "revision.h"
 #include "wt-status.h"
@@ -122,6 +123,7 @@ static const char *edit_message, *use_message;
 static char *fixup_message, *fixup_commit, *squash_message;
 static const char *fixup_prefix;
 static int all, also, interactive, patch_interactive, only, amend, signoff;
+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
@@ -354,6 +356,11 @@ static const char *prepare_index(const char **argv, const char *prefix,
 	const char *ret;
 	char *path = NULL;
 
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	if (is_status)
 		refresh_flags |= REFRESH_UNMERGED;
 	parse_pathspec(&pathspec, 0,
@@ -400,7 +407,7 @@ static const char *prepare_index(const char **argv, const char *prefix,
 		old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
 		setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
 
-		if (interactive_add(the_repository, argv, prefix, patch_interactive) != 0)
+		if (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0)
 			die(_("interactive add failed"));
 
 		the_repository->index_file = old_repo_index_file;
@@ -424,6 +431,11 @@ static const char *prepare_index(const char **argv, const char *prefix,
 		commit_style = COMMIT_NORMAL;
 		ret = get_lock_file_path(&index_lock);
 		goto out;
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
 	}
 
 	/*
@@ -1722,6 +1734,8 @@ int cmd_commit(int argc,
 		OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")),
 		OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
 		OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT_BOOL('o', "only", &only, N_("commit only specified files")),
 		OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")),
 		OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
diff --git a/builtin/reset.c b/builtin/reset.c
index dc50ffc1ac59..9fb32795c9c5 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -346,6 +346,7 @@ int cmd_reset(int argc,
 	struct object_id oid;
 	struct pathspec pathspec;
 	int intent_to_add = 0;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	const struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
 		OPT_BOOL(0, "no-refresh", &no_refresh,
@@ -370,6 +371,8 @@ int cmd_reset(int argc,
 			       PARSE_OPT_OPTARG,
 			       option_parse_recurse_submodules_worktree_updater),
 		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT_BOOL('N', "intent-to-add", &intent_to_add,
 				N_("record only the fact that removed paths will be added later")),
 		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
@@ -420,6 +423,11 @@ int cmd_reset(int argc,
 		oidcpy(&oid, &tree->object.oid);
 	}
 
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	prepare_repo_settings(the_repository);
 	the_repository->settings.command_requires_full_index = 0;
 
@@ -427,9 +435,14 @@ int cmd_reset(int argc,
 		if (reset_type != NONE)
 			die(_("options '%s' and '%s' cannot be used together"), "--patch", "--{hard,mixed,soft}");
 		trace2_cmd_mode("patch-interactive");
-		update_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev,
-				   &pathspec);
+		update_ref_status = !!run_add_p(the_repository, ADD_P_RESET,
+						&add_p_opt, rev, &pathspec);
 		goto cleanup;
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
 	}
 
 	/* git reset tree [--] paths... can be used to
diff --git a/builtin/stash.c b/builtin/stash.c
index 7cd3ad8aa48e..6da162e5e69a 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -1242,7 +1242,8 @@ done:
 }
 
 static int stash_patch(struct stash_info *info, const struct pathspec *ps,
-		       struct strbuf *out_patch, int quiet)
+		       struct strbuf *out_patch, int quiet,
+		       struct add_p_opt *add_p_opt)
 {
 	int ret = 0;
 	struct child_process cp_read_tree = CHILD_PROCESS_INIT;
@@ -1267,7 +1268,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
 	old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
 	setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
 
-	ret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps);
+	ret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps);
 
 	the_repository->index_file = old_repo_index_file;
 	if (old_index_env && *old_index_env)
@@ -1362,8 +1363,8 @@ done:
 }
 
 static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,
-			   int include_untracked, int patch_mode, int only_staged,
-			   struct stash_info *info, struct strbuf *patch,
+			   int include_untracked, int patch_mode, struct add_p_opt *add_p_opt,
+			   int only_staged, struct stash_info *info, struct strbuf *patch,
 			   int quiet)
 {
 	int ret = 0;
@@ -1444,7 +1445,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
 		untracked_commit_option = 1;
 	}
 	if (patch_mode) {
-		ret = stash_patch(info, ps, patch, quiet);
+		ret = stash_patch(info, ps, patch, quiet, add_p_opt);
 		if (ret < 0) {
 			if (!quiet)
 				fprintf_ln(stderr, _("Cannot save the current "
@@ -1519,7 +1520,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
 	if (!check_changes_tracked_files(&ps))
 		return 0;
 
-	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,
+	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,
 			      NULL, 0);
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
@@ -1530,7 +1531,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
 }
 
 static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,
-			 int keep_index, int patch_mode, int include_untracked, int only_staged)
+			 int keep_index, int patch_mode, struct add_p_opt *add_p_opt,
+			 int include_untracked, int only_staged)
 {
 	int ret = 0;
 	struct stash_info info = STASH_INFO_INIT;
@@ -1600,8 +1602,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
 
 	if (stash_msg)
 		strbuf_addstr(&stash_msg_buf, stash_msg);
-	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,
-			    &info, &patch, quiet)) {
+	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
+			    add_p_opt, only_staged, &info, &patch, quiet)) {
 		ret = -1;
 		goto done;
 	}
@@ -1774,6 +1776,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 	const char *stash_msg = NULL;
 	char *pathspec_from_file = NULL;
 	struct pathspec ps;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	struct option options[] = {
 		OPT_BOOL('k', "keep-index", &keep_index,
 			 N_("keep index")),
@@ -1781,6 +1784,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT__QUIET(&quiet, N_("quiet mode")),
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
@@ -1836,8 +1841,20 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 		die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file");
 	}
 
+	if (!patch_mode) {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
-			    include_untracked, only_staged);
+			    &add_p_opt, include_untracked, only_staged);
 
 	clear_pathspec(&ps);
 	free(pathspec_from_file);
@@ -1862,6 +1879,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 	const char *stash_msg = NULL;
 	struct pathspec ps;
 	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	struct option options[] = {
 		OPT_BOOL('k', "keep-index", &keep_index,
 			 N_("keep index")),
@@ -1869,6 +1887,8 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT__QUIET(&quiet, N_("quiet mode")),
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
@@ -1887,8 +1907,22 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 		stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
+
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
+	if (!patch_mode) {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
-			    patch_mode, include_untracked, only_staged);
+			    patch_mode, &add_p_opt, include_untracked,
+			    only_staged);
 
 	strbuf_release(&stash_msg_buf);
 	return ret;
diff --git a/commit.h b/commit.h
index 70c870dae4d4..7a7fedbc2f14 100644
--- a/commit.h
+++ b/commit.h
@@ -2,6 +2,7 @@
 #define COMMIT_H
 
 #include "object.h"
+#include "add-interactive.h"
 
 struct signature_check;
 struct strbuf;
@@ -257,7 +258,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *);
 int interactive_add(struct repository *repo,
 		    const char **argv,
 		    const char *prefix,
-		    int patch);
+		    int patch, struct add_p_opt *add_p_opt);
 
 struct commit_extra_header {
 	struct commit_extra_header *next;
diff --git a/parse-options.h b/parse-options.h
index 91c3e3c29b3d..bdae8f116198 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -616,6 +616,8 @@ int parse_opt_tracking_mode(const struct option *, const char *, int);
 #define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file"))
 #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"))
 #define OPT_AUTOSTASH(v) OPT_BOOL(0, "autostash", v, N_("automatically stash/stash pop before and after"))
+#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', "unified", v, N_("generate diffs with <n> lines context"), PARSE_OPT_NONEG)
+#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)
 
 #define OPT_IPVERSION(v) \
 	OPT_SET_INT_F('4', "ipv4", (v), N_("use IPv4 addresses only"), \
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 18dc329ea1f6..d08a87f9ec53 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -1252,4 +1252,53 @@ test_expect_success 'add -p rejects negative diff.context' '
 	test_grep "diff.context cannot be negative" output
 '
 
+for cmd in add checkout restore 'commit -m file'
+do
+	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" "
+		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 &&
+		git add file &&
+		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 &&
+		echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
+			$cmd -p -U 4 --inter-hunk-context 2 >actual &&
+		test_grep \"@@ -2,20 +2,20 @@\" actual
+	"
+done
+
+test_expect_success 'reset accepts -U and --inter-hunk-context' '
+	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 &&
+	git commit -m file file &&
+	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 &&
+	git add file &&
+	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
+		reset -p -U 4 --inter-hunk-context 2 >actual &&
+	test_grep "@@ -2,20 +2,20 @@" actual
+'
+
+test_expect_success 'stash accepts -U and --inter-hunk-context' '
+	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 &&
+	git commit -m file file &&
+	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 &&
+	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
+		stash -p -U 4 --inter-hunk-context 2 >actual &&
+	test_grep "@@ -2,20 +2,20 @@" actual
+'
+
+for cmd in add checkout commit reset restore 'stash save' 'stash push'
+do
+	test_expect_success "$cmd rejects invalid context options" "
+		test_must_fail git $cmd -p -U -3 2>actual &&
+		cat actual | echo &&
+		test_grep -e \"'--unified' cannot be negative\" actual &&
+
+		test_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&
+		test_grep -e \"'--inter-hunk-context' cannot be negative\" actual &&
+
+		test_must_fail git $cmd -U 7 2>actual &&
+		test_grep -E \"'--unified' requires '(--interactive/)?--patch'\" actual &&
+
+		test_must_fail git $cmd --inter-hunk-context 2 2>actual &&
+		test_grep -E \"'--inter-hunk-context' requires '(--interactive/)?--patch'\" actual
+	"
+done
+
 test_done
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index 1384a8195705..0158fe6568cb 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -58,6 +58,36 @@ test_expect_success 'The -U option overrides diff.context' '
 	test_grep ! "^ firstline" output
 '
 
+test_expect_success 'The -U option overrides diff.context for "add"' '
+	test_config diff.context 8 &&
+	git add -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "commit"' '
+	test_config diff.context 8 &&
+	! git commit -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "checkout"' '
+	test_config diff.context 8 &&
+	git checkout -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "stash"' '
+	test_config diff.context 8 &&
+	! git stash -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "restore"' '
+	test_config diff.context 8 &&
+	git restore -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
 test_expect_success 'diff.context honored by "diff"' '
 	test_config diff.context 8 &&
 	git diff >output &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 343b8cd1912b..6650d33fba69 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2596,6 +2596,8 @@ test_expect_success 'double dash "git checkout"' '
 	--merge Z
 	--conflict=Z
 	--patch Z
+	--unified=Z
+	--inter-hunk-context=Z
 	--ignore-skip-worktree-bits Z
 	--ignore-other-worktrees Z
 	--recurse-submodules Z
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 0/4] Better support for customising context lines in --patch commands
  2025-06-28 16:34   ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
                       ` (3 preceding siblings ...)
  2025-06-28 16:34     ` [PATCH v3 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
@ 2025-06-30 16:16     ` Junio C Hamano
  2025-07-09  0:09     ` Junio C Hamano
  2025-07-19 12:28     ` [PATCH v4 " Leon Michalak via GitGitGadget
  6 siblings, 0 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-06-30 16:16 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

"Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:

> Leon Michalak (4):
>   test: use "test_grep"
>   test: use "test_config"

These make it sound as if they touch all test scripts under t/ but
apparently that is not what this series is doing (and we do not want
to see a huge churn like that anyway).  Would something like

    t: use test_grep in t3701, t4055, and t9902

work better?

>   add-patch: respect diff.context configuration
>   add-patch: add diff.context command line overrides

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 1/4] test: use "test_grep"
  2025-06-28 16:34     ` [PATCH v3 1/4] test: use "test_grep" Leon Michalak via GitGitGadget
@ 2025-06-30 16:23       ` Junio C Hamano
  0 siblings, 0 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-06-30 16:23 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

"Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Leon Michalak <leonmichalak6@gmail.com>
>
> Use the modern "test_grep" test utility instead of regular "grep" which
> provides better debug information if tests fail.
>
> This is a prerequisite to the commits that follow which add to both test
> files.

Just a terminology thing, but we would phrase the last paragraph
more like

    As a preparatory clean-up, use the "test_grep" test utility
    instead of regular "grep" which provides better debug
    information if tests fail.

to avoid saying "This does X", "This commit is Y", etc.  It also
avoids giving a wrong impression by misusing the word "prerequisite"
which we almost always use for a step that cannot be skipped.  While
we add new tests to the same file, we _could_ leave these existing
tests as-is, but there is a good reason to making this change
beforehand, which we often call is a "preparatory clean-up".

> @@ -86,7 +86,7 @@ test_expect_success 'revert works (initial)' '
>  	git add file &&
>  	test_write_lines r 1 | git add -i &&
>  	git ls-files >output &&
> -	! grep . output
> +	test_grep ! . output
>  '

Good (we sometime see people got the negation wrong).

Thanks.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 2/4] test: use "test_config"
  2025-06-28 16:34     ` [PATCH v3 2/4] test: use "test_config" Leon Michalak via GitGitGadget
@ 2025-06-30 16:35       ` Junio C Hamano
  0 siblings, 0 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-06-30 16:35 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

"Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Leon Michalak <leonmichalak6@gmail.com>
>
> Use the modern "test_config" test utility instead of manual"git config"
> as the former provides clean up on test completion.

Here "completion" is not "all the tests in the script are done", but
"each of the test_expect_{success,failure} piece that uses test_config".

Drop "modern".  It was invented 14 years ago (the same can be said
for test_grep which was called test_i18ngrep and had an extra
purpose, which was invented in the same year).

This conversion, unlike "test_grep" needs to be done a bit
carefully.  The fact the configuration is removed after the test
piece "test_config" was used means any tests after the test that was
originally using "git config" needs to be inspected to make sure
it was *not* relying on the value that was left by the previous
test piece.

For example...

>  test_expect_success 'diff.context honored by "log"' '
>  	git log -1 -p >output &&
>  	test_grep ! firstline output &&
> -	git config diff.context 8 &&
> +	test_config diff.context 8 &&
>  	git log -1 -p >output &&
>  	test_grep "^ firstline" output
>  '

... the test piece after this one may have assumed (wrongly!  The
assumption does not hold if this test failed before reaching "git
config") that diff.context is still set to 8 but that is no longer
the case.  But that one is OK because ...

>  test_expect_success 'The -U option overrides diff.context' '
> -	git config diff.context 8 &&
> +	test_config diff.context 8 &&
>  	git log -U4 -1 >output &&
>  	test_grep ! "^ firstline" output
>  '

... it was setting it for itself.  The same can be said with other
tests (not quoted in this reply).  They all set things up for
themselves, which is a good hygiene.

That means the last one in the patch needs to be inspected
carefully.  We do not know from the post-context of the patch what
the next test used to expect.

>  test_expect_success '-U0 is valid, so is diff.context=0' '
> -	git config diff.context 0 &&
> +	test_config diff.context 0 &&
>  	git diff >output &&
>  	test_grep "^-ADDED" output &&
>  	test_grep "^+MODIFIED" output

The one that comes after this one is about giving an explicit
command line option -U<n>.  It should not be affected by a
diff.context conffiguration variable that is lower-precedence
so we should be OK.

Thanks.


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 3/4] add-patch: respect diff.context configuration
  2025-06-28 16:34     ` [PATCH v3 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
@ 2025-06-30 16:55       ` Junio C Hamano
  2025-07-01 10:00       ` Phillip Wood
  1 sibling, 0 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-06-30 16:55 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

"Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Leon Michalak <leonmichalak6@gmail.com>
>
> Various builtins that use add-patch infrastructure do not respect
> the user's diff.context and diff.interHunkContext file configurations.

Great.  "add-patch.c" invokes "diff-files", "diff-index" plumbing
commands to do its thing, and these plumbing commands deliberately
ignore such configuration variables, unlike "diff" Porcelain command
that is meant for end-user consumption.

> This patch fixes this inconsistency.

If we were spelling it out, we would say "Fix this inconsistency" in
imperative.  But you never talked about "this inconsistency" so far,
so it is not just confusing.  It hints an incorrect conclusion that
the difference between plumbing diff-{files,index,tree} and
Porcelain diff is an inconsistency that needs to be "fixed", which
is not true.

Follow the first paragraph with an explanation why it is a bad
thing.  For example:

    The user may be used to seeing their diffs with customized
    context size, but not in the patches "git add -p" shows them to
    pick from.

That would implicitly tell readers that we would want the patch
shown by "add -p" generated with diff.context given by the user.
So we can outline the solution next.

    Teach add-patch infrastructure to read these configuration
    variables and pass their values when spawning the underlying
    plumbing commands as their command line option.

or something.

> @@ -39,8 +39,12 @@ static void init_color(struct repository *r, struct add_i_state *s,
>  void init_add_i_state(struct add_i_state *s, struct repository *r)
>  {
>  	const char *value;
> +	int context;
> +	int interhunkcontext;
>  
>  	s->r = r;
> +	s->context = -1;
> +	s->interhunkcontext = -1;

Hmph, context/interhunkcontext variables serve no purpose other than
peeking into the value before assigning it to s->{context,interhunkcontext}
members.  In a sense, they may be confusing than they are worth.

>  	if (repo_config_get_value(r, "color.interactive", &value))
>  		s->use_color = -1;
> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
>  	repo_config_get_string(r, "diff.algorithm",
>  			       &s->interactive_diff_algorithm);
>  
> +	if (!repo_config_get_int(r, "diff.context", &context)) {
> +		if (context < 0)
> +			die(_("%s cannot be negative"), "diff.context");
> +		else
> +			s->context = context;
> +	}

Would the code be easier to understand if it is written more like

	if (!repo_config_get_int(r, "diff.context", &s->context)) {
		if (s->context < 0)
			die(...);
	}

with or without {braces} around the (technically) single statement block?

> +	if (!repo_config_get_int(r, "diff.interHunkContext", &interhunkcontext)) {
> +		if (interhunkcontext < 0)
> +			die(_("%s cannot be negative"), "diff.interHunkContext");
> +		else
> +			s->interhunkcontext = interhunkcontext;
> +	}

Ditto.

> diff --git a/add-interactive.h b/add-interactive.h
> index 693f125e8e4b..c63f35b14be8 100644
> --- a/add-interactive.h
> +++ b/add-interactive.h
> @@ -18,6 +18,7 @@ struct add_i_state {
>  
>  	int use_single_key;
>  	char *interactive_diff_filter, *interactive_diff_algorithm;
> +	int context, interhunkcontext;
>  };
>  
>  void init_add_i_state(struct add_i_state *s, struct repository *r);
> diff --git a/add-patch.c b/add-patch.c
> index 95c67d8c80c4..b43ca1600738 100644
> --- a/add-patch.c
> +++ b/add-patch.c
> @@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
>  {
>  	struct strvec args = STRVEC_INIT;
>  	const char *diff_algorithm = s->s.interactive_diff_algorithm;
> +	int diff_context = s->s.context;
> +	int diff_interhunkcontext = s->s.interhunkcontext;
>  	struct strbuf *plain = &s->plain, *colored = NULL;
>  	struct child_process cp = CHILD_PROCESS_INIT;
>  	char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
> @@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
>  	int res;
>  
>  	strvec_pushv(&args, s->mode->diff_cmd);
> +	if (diff_context != -1)
> +		strvec_pushf(&args, "--unified=%i", diff_context);
> +	if (diff_interhunkcontext != -1)
> +		strvec_pushf(&args, "--inter-hunk-context=%i", diff_interhunkcontext);

Ditto.  What does it buy us to have these two local variables?  We
have the state object 's' available to us here, right?

Thanks.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides
  2025-06-28 16:34     ` [PATCH v3 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
@ 2025-06-30 17:03       ` Junio C Hamano
  2025-07-01  9:59         ` Phillip Wood
  2025-07-01  9:59       ` Phillip Wood
  1 sibling, 1 reply; 77+ messages in thread
From: Junio C Hamano @ 2025-06-30 17:03 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

"Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Leon Michalak <leonmichalak6@gmail.com>
>
> This patch compliments the previous commit, where builtins that use
> add-patch infrastructure now respect diff.context and
> diff.interHunkContext file configurations.
>
> In particular, this patch helps users who don't want to set persistent
> context configurations or just want a way to override them on a one-time
> basis, by allowing the relevant builtins to accept corresponding command
> line options that override the file configurations.
>
> This mimics commands such as diff and log, which allow for both context
> file configuration and command line overrides.

I skimmed the patch briefly.  I am not sure if it is a good idea to

 * add OPT_DIFF_*() macros to parse-options API, as its utility is
   very narrow, and forces those who are learning parse-options API
   to learn one more thing.

 * validation of the value range to be duplicated for each and every
   users of the new OPT_DIFF_*() macros.

but other than that, looked reasonable to me.

Thanks.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides
  2025-06-28 16:34     ` [PATCH v3 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
  2025-06-30 17:03       ` Junio C Hamano
@ 2025-07-01  9:59       ` Phillip Wood
  1 sibling, 0 replies; 77+ messages in thread
From: Phillip Wood @ 2025-07-01  9:59 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget, git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Leon Michalak

Hi Leon

On 28/06/2025 17:34, Leon Michalak via GitGitGadget wrote:
> From: Leon Michalak <leonmichalak6@gmail.com>
>  
> +for cmd in add checkout restore 'commit -m file'
> +do
> +	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" "

Looking at this again, I think the test bodies here and below should be 
wrapped in single quotes because they are passed to eval and we want to 
expand $cmd when the body is evaluated, not before. That would also 
simplify the quoting inside the tests as we don't need to escape double 
quotes. That's not your fault - you've just copied what I suggested before.

> +test_expect_success 'The -U option overrides diff.context for "add"' '
> +	test_config diff.context 8 &&
> +	git add -U4 -p >output &&
> +	test_grep ! "^ firstline" output
> +'

Don't the tests above check this as they set diff.context and 
diff.interhunkcontext and pass different values to -U and 
--inter-hunk-context?

Thanks

Phillip

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides
  2025-06-30 17:03       ` Junio C Hamano
@ 2025-07-01  9:59         ` Phillip Wood
  2025-07-01 15:54           ` Junio C Hamano
  0 siblings, 1 reply; 77+ messages in thread
From: Phillip Wood @ 2025-07-01  9:59 UTC (permalink / raw)
  To: Junio C Hamano, Leon Michalak via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Leon Michalak

On 30/06/2025 18:03, Junio C Hamano wrote:
> "Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:
> 
>> From: Leon Michalak <leonmichalak6@gmail.com>
>>
>> This patch compliments the previous commit, where builtins that use
>> add-patch infrastructure now respect diff.context and
>> diff.interHunkContext file configurations.
>>
>> In particular, this patch helps users who don't want to set persistent
>> context configurations or just want a way to override them on a one-time
>> basis, by allowing the relevant builtins to accept corresponding command
>> line options that override the file configurations.
>>
>> This mimics commands such as diff and log, which allow for both context
>> file configuration and command line overrides.
> 
> I skimmed the patch briefly.  I am not sure if it is a good idea to
> 
>   * add OPT_DIFF_*() macros to parse-options API, as its utility is
>     very narrow, and forces those who are learning parse-options API
>     to learn one more thing.

It means that we have consistent help for all the commands with these 
options which I think is valuable. We have a number of other macros that 
define options that are shared between commands and I think that works 
quite well.

> 
>   * validation of the value range to be duplicated for each and every
>     users of the new OPT_DIFF_*() macros.

Yes the validation is awkward. If we changed the OPT_DIFF_* to use a 
callback that rejected negative values that would reduce the duplication.

> but other than that, looked reasonable to me.

I've left a couple of comments on the tests but the code changes look 
reasonable to me too

Thanks

Phillip


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 3/4] add-patch: respect diff.context configuration
  2025-06-28 16:34     ` [PATCH v3 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
  2025-06-30 16:55       ` Junio C Hamano
@ 2025-07-01 10:00       ` Phillip Wood
  1 sibling, 0 replies; 77+ messages in thread
From: Phillip Wood @ 2025-07-01 10:00 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget, git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Leon Michalak

Hi Leon

On 28/06/2025 17:34, Leon Michalak via GitGitGadget wrote:
> From: Leon Michalak <leonmichalak6@gmail.com>
> 
> Various builtins that use add-patch infrastructure do not respect
> the user's diff.context and diff.interHunkContext file configurations.
> This patch fixes this inconsistency.
> 
> This is because the plumbing commands used by "git add -p" to generate
> the diff do not read those config settings. Fix this by reading the
> config before generating the patch and passing it along to the diff
> command with the "-U" and "--inter-hunk-context" command-line options.

This looks good to me, thanks for working on it

Phillip

> Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
> ---
>   add-interactive.c          | 17 +++++++++++++++++
>   add-interactive.h          |  1 +
>   add-patch.c                |  6 ++++++
>   t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++
>   4 files changed, 46 insertions(+)
> 
> diff --git a/add-interactive.c b/add-interactive.c
> index 97ff35b6f12a..e0aafb8dd02a 100644
> --- a/add-interactive.c
> +++ b/add-interactive.c
> @@ -39,8 +39,12 @@ static void init_color(struct repository *r, struct add_i_state *s,
>   void init_add_i_state(struct add_i_state *s, struct repository *r)
>   {
>   	const char *value;
> +	int context;
> +	int interhunkcontext;
>   
>   	s->r = r;
> +	s->context = -1;
> +	s->interhunkcontext = -1;
>   
>   	if (repo_config_get_value(r, "color.interactive", &value))
>   		s->use_color = -1;
> @@ -78,6 +82,19 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
>   	repo_config_get_string(r, "diff.algorithm",
>   			       &s->interactive_diff_algorithm);
>   
> +	if (!repo_config_get_int(r, "diff.context", &context)) {
> +		if (context < 0)
> +			die(_("%s cannot be negative"), "diff.context");
> +		else
> +			s->context = context;
> +	}
> +	if (!repo_config_get_int(r, "diff.interHunkContext", &interhunkcontext)) {
> +		if (interhunkcontext < 0)
> +			die(_("%s cannot be negative"), "diff.interHunkContext");
> +		else
> +			s->interhunkcontext = interhunkcontext;
> +	}
> +
>   	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
>   	if (s->use_single_key)
>   		setbuf(stdin, NULL);
> diff --git a/add-interactive.h b/add-interactive.h
> index 693f125e8e4b..c63f35b14be8 100644
> --- a/add-interactive.h
> +++ b/add-interactive.h
> @@ -18,6 +18,7 @@ struct add_i_state {
>   
>   	int use_single_key;
>   	char *interactive_diff_filter, *interactive_diff_algorithm;
> +	int context, interhunkcontext;
>   };
>   
>   void init_add_i_state(struct add_i_state *s, struct repository *r);
> diff --git a/add-patch.c b/add-patch.c
> index 95c67d8c80c4..b43ca1600738 100644
> --- a/add-patch.c
> +++ b/add-patch.c
> @@ -415,6 +415,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
>   {
>   	struct strvec args = STRVEC_INIT;
>   	const char *diff_algorithm = s->s.interactive_diff_algorithm;
> +	int diff_context = s->s.context;
> +	int diff_interhunkcontext = s->s.interhunkcontext;
>   	struct strbuf *plain = &s->plain, *colored = NULL;
>   	struct child_process cp = CHILD_PROCESS_INIT;
>   	char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
> @@ -424,6 +426,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
>   	int res;
>   
>   	strvec_pushv(&args, s->mode->diff_cmd);
> +	if (diff_context != -1)
> +		strvec_pushf(&args, "--unified=%i", diff_context);
> +	if (diff_interhunkcontext != -1)
> +		strvec_pushf(&args, "--inter-hunk-context=%i", diff_interhunkcontext);
>   	if (diff_algorithm)
>   		strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
>   	if (s->revision) {
> diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
> index b088ee141ff4..18dc329ea1f6 100755
> --- a/t/t3701-add-interactive.sh
> +++ b/t/t3701-add-interactive.sh
> @@ -1230,4 +1230,26 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
>   	test_cmp expect actual
>   '
>   
> +test_expect_success 'add -p respects diff.context' '
> +	test_write_lines a b c d e f g h i j k l m >file &&
> +	git add file &&
> +	test_write_lines a b c d e f G h i j k l m >file &&
> +	echo y | git -c diff.context=5 add -p >actual &&
> +	test_grep "@@ -2,11 +2,11 @@" actual
> +'
> +
> +test_expect_success 'add -p respects diff.interHunkContext' '
> +	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
> +	git add file &&
> +	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
> +	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
> +	test_grep "@@ -2,16 +2,16 @@" actual
> +'
> +
> +test_expect_success 'add -p rejects negative diff.context' '
> +	test_config diff.context -1 &&
> +	test_must_fail git add -p 2>output &&
> +	test_grep "diff.context cannot be negative" output
> +'
> +
>   test_done


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides
  2025-07-01  9:59         ` Phillip Wood
@ 2025-07-01 15:54           ` Junio C Hamano
  2025-07-02 14:07             ` Phillip Wood
  0 siblings, 1 reply; 77+ messages in thread
From: Junio C Hamano @ 2025-07-01 15:54 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Leon Michalak

Phillip Wood <phillip.wood123@gmail.com> writes:

>>   * add OPT_DIFF_*() macros to parse-options API, as its utility is
>>     very narrow, and forces those who are learning parse-options API
>>     to learn one more thing.
>
> It means that we have consistent help for all the commands with these
> options which I think is valuable. We have a number of other macros
> that define options that are shared between commands and I think that
> works quite well.

I understand that principe.  What I was wondering was if there are
enough places to use these particular ones to make it worthwhile to
enlarge the set of OPT_* macros.

>>   * validation of the value range to be duplicated for each and
>> every
>>     users of the new OPT_DIFF_*() macros.
>
> Yes the validation is awkward. If we changed the OPT_DIFF_* to use a
> callback that rejected negative values that would reduce the
> duplication.

Yeah, I was wondering about that approach, too.  Another benefit
with the "validate just after we parse the value before we assign
the result to a variable or a struct member" approach is that we can
also complain about -1 that is given from the command line (which
the current code ignores, if I am not mistaken, because it needs to
be silent if that -1 is there merely because it is the "not set yet"
sentinel value).

Or perhaps the valid value range Réne has been workingon canbe used
here?

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides
  2025-07-01 15:54           ` Junio C Hamano
@ 2025-07-02 14:07             ` Phillip Wood
  2025-07-02 18:28               ` Junio C Hamano
  0 siblings, 1 reply; 77+ messages in thread
From: Phillip Wood @ 2025-07-02 14:07 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Leon Michalak

On 01/07/2025 16:54, Junio C Hamano wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
> 
>>>    * add OPT_DIFF_*() macros to parse-options API, as its utility is
>>>      very narrow, and forces those who are learning parse-options API
>>>      to learn one more thing.
>>
>> It means that we have consistent help for all the commands with these
>> options which I think is valuable. We have a number of other macros
>> that define options that are shared between commands and I think that
>> works quite well.
> 
> I understand that principe.  What I was wondering was if there are
> enough places to use these particular ones to make it worthwhile to
> enlarge the set of OPT_* macros.

There are six users of each of these macros so I think it is worthwhile. 
That's two more users than there are for OPT_RERERE_AUTOUPDATE() and 
twice as many users as OPT_CONTAINS().

>>>    * validation of the value range to be duplicated for each and
>>> every
>>>      users of the new OPT_DIFF_*() macros.
>>
>> Yes the validation is awkward. If we changed the OPT_DIFF_* to use a
>> callback that rejected negative values that would reduce the
>> duplication.
> 
> Yeah, I was wondering about that approach, too.  Another benefit
> with the "validate just after we parse the value before we assign
> the result to a variable or a struct member" approach is that we can
> also complain about -1 that is given from the command line (which
> the current code ignores, if I am not mistaken, because it needs to
> be silent if that -1 is there merely because it is the "not set yet"
> sentinel value).
> 
> Or perhaps the valid value range Réne has been workingon canbe used
> here?

That would be nice

Thanks

Phillip

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 4/4] add-patch: add diff.context command line overrides
  2025-07-02 14:07             ` Phillip Wood
@ 2025-07-02 18:28               ` Junio C Hamano
  0 siblings, 0 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-07-02 18:28 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Leon Michalak

Phillip Wood <phillip.wood123@gmail.com> writes:

> On 01/07/2025 16:54, Junio C Hamano wrote:
>> Phillip Wood <phillip.wood123@gmail.com> writes:
>> 
>>>>    * add OPT_DIFF_*() macros to parse-options API, as its utility is
>>>>      very narrow, and forces those who are learning parse-options API
>>>>      to learn one more thing.
>>>
>>> It means that we have consistent help for all the commands with these
>>> options which I think is valuable. We have a number of other macros
>>> that define options that are shared between commands and I think that
>>> works quite well.
>> I understand that principe.  What I was wondering was if there are
>> enough places to use these particular ones to make it worthwhile to
>> enlarge the set of OPT_* macros.
>
> There are six users of each of these macros so I think it is
> worthwhile. That's two more users than there are for
> OPT_RERERE_AUTOUPDATE() and twice as many users as OPT_CONTAINS().

OK.  Thanks.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 0/4] Better support for customising context lines in --patch commands
  2025-06-28 16:34   ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
                       ` (4 preceding siblings ...)
  2025-06-30 16:16     ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Junio C Hamano
@ 2025-07-09  0:09     ` Junio C Hamano
  2025-07-09  7:57       ` Leon Michalak
  2025-07-19 12:28     ` [PATCH v4 " Leon Michalak via GitGitGadget
  6 siblings, 1 reply; 77+ messages in thread
From: Junio C Hamano @ 2025-07-09  0:09 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

"Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:

> This series of patches attempt to give --interactive/--patch compatible
> builtins ("add", "commit", "checkout", "reset", "restore" and "stash")
> better support and nicer experience for configuring how many context lines
> are shown in diffs through a variety of ways.
>
> Prior to these patches, the user could not choose how many context lines
> they saw in --patch commands (apart from one workaround by using
> GIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a
> persistent solution). Additionally, the behaviour around reading from the
> diff.context and diff.interHunkContext configs was also inconsistent with
> other diff generating commands such as "log -p".
>
> The summarised changes below hopefully make this experience better and fix
> some inconsistencies:
>
>  * diff.context and diff.interHunkContext configs are now respected by
>    --patch compatible commands
>  * --unified and --inter-hunk-context command line options have been added
>    to --patch compatible commands (which take prescendence over file
>    configs)
>  * "add" and "commit" in --interactive mode now expose a new "context"
>    subcommand which configures the amount of context lines you wish to see
>    in subsequent diffs generated from other subcommands such as "patch" or
>    "diff"
>
> The original discussion for this can be read at:
>
>  * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/
>
> Changes since v1:
>
>  * Update commit descriptions
>  * Update tests to use the more modern and robust test_grep and test_config
>    utils
>  * Reword some documentation / user messages
>  * Ensure each commit is atomic and builds/passes tests on it's own
>  * Make new command line options DRY
>  * Add tests for interhunk context interaction
>  * Error if context config/command line options are negative
>  * Drop previous last commit to do with new subcommand for --interactive
>    add/commit. My motivations behind this patch series originally where
>    quite simple, just for add-patch commands to respect context configs.
>    This subcommand, after the discussion in v1, will require more thought
>    and a larger implementation that what I had anticipated. I would prefer
>    to leave this for another time as it's the least impactful but the most
>    time intensive and complicated idea.
>
> Changes since v2:
>
>  * Update tests to only test single command (following Philip's suggestion)
>  * Add negative option checks
>  * Minor commit re-wording

This iteration seems to have attracted a few review comments that
are left ananswered.  Will we see responses to them, and/or, a
hopefully small and final update sometime soon?

Thanks.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 0/4] Better support for customising context lines in --patch commands
  2025-07-09  0:09     ` Junio C Hamano
@ 2025-07-09  7:57       ` Leon Michalak
  2025-07-09 15:32         ` Junio C Hamano
  0 siblings, 1 reply; 77+ messages in thread
From: Leon Michalak @ 2025-07-09  7:57 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Phillip Wood

I will respond to everything soon I hope, life has been in the way and
I didn't anticipate so many comments :-)

Based on a skim read of the comments, the main thing I'm not sure I
will be able to achieve is implementing the valid value range that a
Rene has been working on as I don't have that context, but I'm not
sure if this is a necessity to add?

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v3 0/4] Better support for customising context lines in --patch commands
  2025-07-09  7:57       ` Leon Michalak
@ 2025-07-09 15:32         ` Junio C Hamano
  0 siblings, 0 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-07-09 15:32 UTC (permalink / raw)
  To: Leon Michalak
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Phillip Wood

Leon Michalak <leonmichalak6@gmail.com> writes:

> I will respond to everything soon I hope, life has been in the way and
> I didn't anticipate so many comments :-)

Sure, no problem.  

Historically, summer is a slower season and these messages I sent
are primarily for me to keep track of topics on flight.  "No, I am
on vacation for a few more weeks" would have been perfectly fine
response ;-).

> Based on a skim read of the comments, the main thing I'm not sure I
> will be able to achieve is implementing the valid value range that a
> Rene has been working on as I don't have that context, but I'm not
> sure if this is a necessity to add?

^ permalink raw reply	[flat|nested] 77+ messages in thread

* [PATCH v4 0/4] Better support for customising context lines in --patch commands
  2025-06-28 16:34   ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
                       ` (5 preceding siblings ...)
  2025-07-09  0:09     ` Junio C Hamano
@ 2025-07-19 12:28     ` Leon Michalak via GitGitGadget
  2025-07-19 12:28       ` [PATCH v4 1/4] t: use test_grep in t3701 and t4055 Leon Michalak via GitGitGadget
                         ` (5 more replies)
  6 siblings, 6 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-07-19 12:28 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

This series of patches attempt to give --interactive/--patch compatible
builtins ("add", "commit", "checkout", "reset", "restore" and "stash")
better support and nicer experience for configuring how many context lines
are shown in diffs through a variety of ways.

Prior to these patches, the user could not choose how many context lines
they saw in --patch commands (apart from one workaround by using
GIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a
persistent solution). Additionally, the behaviour around reading from the
diff.context and diff.interHunkContext configs was also inconsistent with
other diff generating commands such as "log -p".

The summarised changes below hopefully make this experience better and fix
some inconsistencies:

 * diff.context and diff.interHunkContext configs are now respected by
   --patch compatible commands
 * --unified and --inter-hunk-context command line options have been added
   to --patch compatible commands (which take prescendence over file
   configs)
 * "add" and "commit" in --interactive mode now expose a new "context"
   subcommand which configures the amount of context lines you wish to see
   in subsequent diffs generated from other subcommands such as "patch" or
   "diff"

The original discussion for this can be read at:

 * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/

Changes since v1:

 * Update commit descriptions
 * Update tests to use the more modern and robust test_grep and test_config
   utils
 * Reword some documentation / user messages
 * Ensure each commit is atomic and builds/passes tests on it's own
 * Make new command line options DRY
 * Add tests for interhunk context interaction
 * Error if context config/command line options are negative
 * Drop previous last commit to do with new subcommand for --interactive
   add/commit. My motivations behind this patch series originally where
   quite simple, just for add-patch commands to respect context configs.
   This subcommand, after the discussion in v1, will require more thought
   and a larger implementation that what I had anticipated. I would prefer
   to leave this for another time as it's the least impactful but the most
   time intensive and complicated idea.

Changes since v2:

 * Update tests to only test single command (following Philip's suggestion)
 * Add negative option checks
 * Minor commit re-wording

Changes since v3:

 * Update commit descriptions
 * Read struct properties directly instead of assigning to variables first
 * Simplify config setting / error checking
 * Remove redundant tests in later commit as they were replaced with better
   test(s)
 * Change tests to use single quotes (this messes with the grep so was
   unable to explicitly test single quotes in the error messages, so decided
   to use regex . instead, which is what some other tests that have this
   problem seem to use as well)

Leon Michalak (4):
  t: use test_grep in t3701 and t4055
  t: use test_config in t4055
  add-patch: respect diff.context configuration
  add-patch: add diff.context command line overrides

 Documentation/diff-context-options.adoc | 10 +++
 Documentation/git-add.adoc              |  2 +
 Documentation/git-checkout.adoc         |  2 +
 Documentation/git-commit.adoc           |  2 +
 Documentation/git-reset.adoc            |  2 +
 Documentation/git-restore.adoc          |  2 +
 Documentation/git-stash.adoc            |  2 +
 add-interactive.c                       | 45 ++++++++++--
 add-interactive.h                       | 17 ++++-
 add-patch.c                             | 14 ++--
 builtin/add.c                           | 21 +++++-
 builtin/checkout.c                      | 31 +++++++-
 builtin/commit.c                        | 16 +++-
 builtin/reset.c                         | 17 ++++-
 builtin/stash.c                         | 56 +++++++++++---
 commit.h                                |  3 +-
 parse-options.h                         |  2 +
 t/t3701-add-interactive.sh              | 97 +++++++++++++++++++------
 t/t4055-diff-context.sh                 | 72 ++++++++++++------
 t/t9902-completion.sh                   |  2 +
 20 files changed, 332 insertions(+), 83 deletions(-)
 create mode 100644 Documentation/diff-context-options.adoc


base-commit: cf6f63ea6bf35173e02e18bdc6a4ba41288acff9
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/1915

Range-diff vs v3:

 1:  044a93014b6 ! 1:  bbb2bc7082b test: use "test_grep"
     @@ Metadata
      Author: Leon Michalak <leonmichalak6@gmail.com>
      
       ## Commit message ##
     -    test: use "test_grep"
     +    t: use test_grep in t3701 and t4055
      
     -    Use the modern "test_grep" test utility instead of regular "grep" which
     -    provides better debug information if tests fail.
     -
     -    This is a prerequisite to the commits that follow which add to both test
     -    files.
     +    As a preparatory clean-up, use the "test_grep" test utility instead of
     +    regular "grep" which provides better debug information if tests fail.
      
          Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
      
 2:  e5c40d37750 ! 2:  feace2d3676 test: use "test_config"
     @@ Metadata
      Author: Leon Michalak <leonmichalak6@gmail.com>
      
       ## Commit message ##
     -    test: use "test_config"
     +    t: use test_config in t4055
      
          Use the modern "test_config" test utility instead of manual"git config"
          as the former provides clean up on test completion.
 3:  1ec8a138486 ! 3:  994029d6602 add-patch: respect diff.context configuration
     @@ Commit message
      
          Various builtins that use add-patch infrastructure do not respect
          the user's diff.context and diff.interHunkContext file configurations.
     -    This patch fixes this inconsistency.
      
     -    This is because the plumbing commands used by "git add -p" to generate
     -    the diff do not read those config settings. Fix this by reading the
     -    config before generating the patch and passing it along to the diff
     -    command with the "-U" and "--inter-hunk-context" command-line options.
     +    The user may be used to seeing their diffs with customized context size,
     +    but not in the patches "git add -p" shows them to pick from.
     +
     +    Teach add-patch infrastructure to read these configuration variables and
     +    pass their values when spawning the underlying plumbing commands as
     +    their command line option.
      
          Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
      
       ## add-interactive.c ##
     -@@ add-interactive.c: static void init_color(struct repository *r, struct add_i_state *s,
     - void init_add_i_state(struct add_i_state *s, struct repository *r)
     - {
     +@@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)
       	const char *value;
     -+	int context;
     -+	int interhunkcontext;
       
       	s->r = r;
      +	s->context = -1;
     @@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repositor
       	repo_config_get_string(r, "diff.algorithm",
       			       &s->interactive_diff_algorithm);
       
     -+	if (!repo_config_get_int(r, "diff.context", &context)) {
     -+		if (context < 0)
     ++	if (!repo_config_get_int(r, "diff.context", &s->context))
     ++		if (s->context < 0)
      +			die(_("%s cannot be negative"), "diff.context");
     -+		else
     -+			s->context = context;
     -+	}
     -+	if (!repo_config_get_int(r, "diff.interHunkContext", &interhunkcontext)) {
     -+		if (interhunkcontext < 0)
     ++	if (!repo_config_get_int(r, "diff.interHunkContext", &s->interhunkcontext))
     ++		if (s->interhunkcontext < 0)
      +			die(_("%s cannot be negative"), "diff.interHunkContext");
     -+		else
     -+			s->interhunkcontext = interhunkcontext;
     -+	}
      +
       	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
       	if (s->use_single_key)
     @@ add-interactive.h: struct add_i_state {
       void init_add_i_state(struct add_i_state *s, struct repository *r);
      
       ## add-patch.c ##
     -@@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
     +@@ add-patch.c: static int normalize_marker(const char *p)
     + static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
       {
       	struct strvec args = STRVEC_INIT;
     - 	const char *diff_algorithm = s->s.interactive_diff_algorithm;
     -+	int diff_context = s->s.context;
     -+	int diff_interhunkcontext = s->s.interhunkcontext;
     +-	const char *diff_algorithm = s->s.interactive_diff_algorithm;
       	struct strbuf *plain = &s->plain, *colored = NULL;
       	struct child_process cp = CHILD_PROCESS_INIT;
       	char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
     @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec
       	int res;
       
       	strvec_pushv(&args, s->mode->diff_cmd);
     -+	if (diff_context != -1)
     -+		strvec_pushf(&args, "--unified=%i", diff_context);
     -+	if (diff_interhunkcontext != -1)
     -+		strvec_pushf(&args, "--inter-hunk-context=%i", diff_interhunkcontext);
     - 	if (diff_algorithm)
     - 		strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
     +-	if (diff_algorithm)
     +-		strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
     ++	if (s->s.context != -1)
     ++		strvec_pushf(&args, "--unified=%i", s->s.context);
     ++	if (s->s.interhunkcontext != -1)
     ++		strvec_pushf(&args, "--inter-hunk-context=%i", s->s.interhunkcontext);
     ++	if (s->s.interactive_diff_algorithm)
     ++		strvec_pushf(&args, "--diff-algorithm=%s", s->s.interactive_diff_algorithm);
       	if (s->revision) {
     + 		struct object_id oid;
     + 		strvec_push(&args,
      
       ## t/t3701-add-interactive.sh ##
      @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
 4:  b68c58b667c ! 4:  2774b930406 add-patch: add diff.context command line overrides
     @@ add-interactive.c: static void init_color(struct repository *r, struct add_i_sta
      +		      struct add_p_opt *add_p_opt)
       {
       	const char *value;
     - 	int context;
     + 
      @@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)
       	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
       	if (s->use_single_key)
     @@ parse-options.h: int parse_opt_tracking_mode(const struct option *, const char *
       	OPT_SET_INT_F('4', "ipv4", (v), N_("use IPv4 addresses only"), \
      
       ## t/t3701-add-interactive.sh ##
     -@@ t/t3701-add-interactive.sh: test_expect_success 'add -p rejects negative diff.context' '
     - 	test_grep "diff.context cannot be negative" output
     +@@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
     + 	test_cmp expect actual
       '
       
     +-test_expect_success 'add -p respects diff.context' '
     +-	test_write_lines a b c d e f g h i j k l m >file &&
      +for cmd in add checkout restore 'commit -m file'
      +do
     -+	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" "
     ++	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" '
      +		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 &&
      +		git add file &&
      +		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 &&
      +		echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
      +			$cmd -p -U 4 --inter-hunk-context 2 >actual &&
     -+		test_grep \"@@ -2,20 +2,20 @@\" actual
     -+	"
     ++		test_grep "@@ -2,20 +2,20 @@" actual
     ++	'
      +done
      +
      +test_expect_success 'reset accepts -U and --inter-hunk-context' '
      +	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 &&
      +	git commit -m file file &&
      +	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 &&
     -+	git add file &&
     + 	git add file &&
     +-	test_write_lines a b c d e f G h i j k l m >file &&
     +-	echo y | git -c diff.context=5 add -p >actual &&
     +-	test_grep "@@ -2,11 +2,11 @@" actual
      +	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
      +		reset -p -U 4 --inter-hunk-context 2 >actual &&
      +	test_grep "@@ -2,20 +2,20 @@" actual
     -+'
     -+
     + '
     + 
     +-test_expect_success 'add -p respects diff.interHunkContext' '
     +-	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
     +-	git add file &&
     +-	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
     +-	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
     +-	test_grep "@@ -2,16 +2,16 @@" actual
      +test_expect_success 'stash accepts -U and --inter-hunk-context' '
      +	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 &&
      +	git commit -m file file &&
     @@ t/t3701-add-interactive.sh: test_expect_success 'add -p rejects negative diff.co
      +	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
      +		stash -p -U 4 --inter-hunk-context 2 >actual &&
      +	test_grep "@@ -2,20 +2,20 @@" actual
     -+'
     -+
     -+for cmd in add checkout commit reset restore 'stash save' 'stash push'
     + '
     + 
     +-test_expect_success 'add -p rejects negative diff.context' '
     +-	test_config diff.context -1 &&
     +-	test_must_fail git add -p 2>output &&
     +-	test_grep "diff.context cannot be negative" output
     +-'
     ++for cmd in add checkout commit reset restore "stash save" "stash push"
      +do
     -+	test_expect_success "$cmd rejects invalid context options" "
     ++	test_expect_success "$cmd rejects invalid context options" '
      +		test_must_fail git $cmd -p -U -3 2>actual &&
      +		cat actual | echo &&
     -+		test_grep -e \"'--unified' cannot be negative\" actual &&
     ++		test_grep -e ".--unified. cannot be negative" actual &&
      +
      +		test_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&
     -+		test_grep -e \"'--inter-hunk-context' cannot be negative\" actual &&
     ++		test_grep -e ".--inter-hunk-context. cannot be negative" actual &&
      +
      +		test_must_fail git $cmd -U 7 2>actual &&
     -+		test_grep -E \"'--unified' requires '(--interactive/)?--patch'\" actual &&
     ++		test_grep -E ".--unified. requires .(--interactive/)?--patch." actual &&
      +
      +		test_must_fail git $cmd --inter-hunk-context 2 2>actual &&
     -+		test_grep -E \"'--inter-hunk-context' requires '(--interactive/)?--patch'\" actual
     -+	"
     ++		test_grep -E ".--inter-hunk-context. requires .(--interactive/)?--patch." actual
     ++	'
      +done
     -+
     + 
       test_done
      
       ## t/t4055-diff-context.sh ##

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 77+ messages in thread

* [PATCH v4 1/4] t: use test_grep in t3701 and t4055
  2025-07-19 12:28     ` [PATCH v4 " Leon Michalak via GitGitGadget
@ 2025-07-19 12:28       ` Leon Michalak via GitGitGadget
  2025-07-19 12:28       ` [PATCH v4 2/4] t: use test_config in t4055 Leon Michalak via GitGitGadget
                         ` (4 subsequent siblings)
  5 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-07-19 12:28 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

As a preparatory clean-up, use the "test_grep" test utility instead of
regular "grep" which provides better debug information if tests fail.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 t/t3701-add-interactive.sh | 48 +++++++++++++++++++-------------------
 t/t4055-diff-context.sh    | 28 +++++++++++-----------
 2 files changed, 38 insertions(+), 38 deletions(-)

diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index b8a05d95f3f1..b088ee141ff4 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -63,7 +63,7 @@ test_expect_success 'setup (initial)' '
 '
 test_expect_success 'status works (initial)' '
 	git add -i </dev/null >output &&
-	grep "+1/-0 *+2/-0 file" output
+	test_grep "+1/-0 *+2/-0 file" output
 '
 
 test_expect_success 'setup expected' '
@@ -86,7 +86,7 @@ test_expect_success 'revert works (initial)' '
 	git add file &&
 	test_write_lines r 1 | git add -i &&
 	git ls-files >output &&
-	! grep . output
+	test_grep ! . output
 '
 
 test_expect_success 'add untracked (multiple)' '
@@ -109,7 +109,7 @@ test_expect_success 'setup (commit)' '
 '
 test_expect_success 'status works (commit)' '
 	git add -i </dev/null >output &&
-	grep "+1/-0 *+2/-0 file" output
+	test_grep "+1/-0 *+2/-0 file" output
 '
 
 test_expect_success 'update can stage deletions' '
@@ -141,7 +141,7 @@ test_expect_success 'revert works (commit)' '
 	git add file &&
 	test_write_lines r 1 | git add -i &&
 	git add -i </dev/null >output &&
-	grep "unchanged *+3/-0 file" output
+	test_grep "unchanged *+3/-0 file" output
 '
 
 test_expect_success 'reject multi-key input' '
@@ -185,7 +185,7 @@ test_expect_success 'setup fake editor' '
 test_expect_success 'bad edit rejected' '
 	git reset &&
 	test_write_lines e n d | git add -p >output &&
-	grep "hunk does not apply" output
+	test_grep "hunk does not apply" output
 '
 
 test_expect_success 'setup patch' '
@@ -198,7 +198,7 @@ test_expect_success 'setup patch' '
 test_expect_success 'garbage edit rejected' '
 	git reset &&
 	test_write_lines e n d | git add -p >output &&
-	grep "hunk does not apply" output
+	test_grep "hunk does not apply" output
 '
 
 test_expect_success 'setup patch' '
@@ -313,8 +313,8 @@ test_expect_success FILEMODE 'stage mode and hunk' '
 	chmod +x file &&
 	printf "y\\ny\\n" | git add -p &&
 	git diff --cached file >out &&
-	grep "new mode" out &&
-	grep "+content" out &&
+	test_grep "new mode" out &&
+	test_grep "+content" out &&
 	git diff file >out &&
 	test_must_be_empty out
 '
@@ -636,7 +636,7 @@ test_expect_success 'split hunk "add -p (edit)"' '
 	printf "%s\n" s e     q n q q |
 	EDITOR=: git add -p &&
 	git diff >actual &&
-	! grep "^+15" actual
+	test_grep ! "^+15" actual
 '
 
 test_expect_success 'split hunk "add -p (no, yes, edit)"' '
@@ -648,7 +648,7 @@ test_expect_success 'split hunk "add -p (no, yes, edit)"' '
 	EDITOR=: git add -p 2>error &&
 	test_must_be_empty error &&
 	git diff >actual &&
-	! grep "^+31" actual
+	test_grep ! "^+31" actual
 '
 
 test_expect_success 'split hunk with incomplete line at end' '
@@ -682,7 +682,7 @@ test_expect_success 'edit, adding lines to the first hunk' '
 	EDITOR=./fake_editor.sh git add -p 2>error &&
 	test_must_be_empty error &&
 	git diff --cached >actual &&
-	grep "^+22" actual
+	test_grep "^+22" actual
 '
 
 test_expect_success 'patch mode ignores unmerged entries' '
@@ -696,7 +696,7 @@ test_expect_success 'patch mode ignores unmerged entries' '
 	test_must_fail git merge side &&
 	echo changed >non-conflict.t &&
 	echo y | git add -p >output &&
-	! grep a/conflict.t output &&
+	test_grep ! a/conflict.t output &&
 	cat >expected <<-\EOF &&
 	* Unmerged path conflict.t
 	diff --git a/non-conflict.t b/non-conflict.t
@@ -728,7 +728,7 @@ test_expect_success 'diffs can be colorized' '
 
 	# We do not want to depend on the exact coloring scheme
 	# git uses for diffs, so just check that we saw some kind of color.
-	grep "$(printf "\\033")" output
+	test_grep "$(printf "\\033")" output
 '
 
 test_expect_success 'colors can be overridden' '
@@ -743,7 +743,7 @@ test_expect_success 'colors can be overridden' '
 		-c color.interactive.error=blue \
 		add -i 2>err.raw <input &&
 	test_decode_color <err.raw >err &&
-	grep "<BLUE>Huh (trigger)?<RESET>" err &&
+	test_grep "<BLUE>Huh (trigger)?<RESET>" err &&
 
 	test_write_lines help quit >input &&
 	force_color git \
@@ -863,7 +863,7 @@ test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '
 	printf y >y &&
 	force_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&
 	test_decode_color <output.raw >output &&
-	grep "old<" output
+	test_grep "old<" output
 '
 
 test_expect_success 'diffFilter filters diff' '
@@ -876,7 +876,7 @@ test_expect_success 'diffFilter filters diff' '
 
 	# avoid depending on the exact coloring or content of the prompts,
 	# and just make sure we saw our diff prefixed
-	grep foo:.*content output
+	test_grep foo:.*content output
 '
 
 test_expect_success 'detect bogus diffFilter output' '
@@ -886,7 +886,7 @@ test_expect_success 'detect bogus diffFilter output' '
 	test_config interactive.diffFilter "sed 6d" &&
 	printf y >y &&
 	force_color test_must_fail git add -p <y >output 2>&1 &&
-	grep "mismatched output" output
+	test_grep "mismatched output" output
 '
 
 test_expect_success 'handle iffy colored hunk headers' '
@@ -896,7 +896,7 @@ test_expect_success 'handle iffy colored hunk headers' '
 	printf n >n &&
 	force_color git -c interactive.diffFilter="sed s/.*@@.*/XX/" \
 		add -p >output 2>&1 <n &&
-	grep "^XX$" output
+	test_grep "^XX$" output
 '
 
 test_expect_success 'handle very large filtered diff' '
@@ -1002,7 +1002,7 @@ test_expect_success 'add -p does not expand argument lists' '
 	# update it, but we want to be sure that our "." pathspec
 	# was not expanded into the argument list of any command.
 	# So look only for "not-changed".
-	! grep -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
+	test_grep ! -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
 '
 
 test_expect_success 'hunk-editing handles custom comment char' '
@@ -1072,21 +1072,21 @@ test_expect_success 'setup different kinds of dirty submodules' '
 
 test_expect_success 'status ignores dirty submodules (except HEAD)' '
 	git -C for-submodules add -i </dev/null >output &&
-	grep dirty-head output &&
-	grep dirty-both-ways output &&
-	! grep dirty-otherwise output
+	test_grep dirty-head output &&
+	test_grep dirty-both-ways output &&
+	test_grep ! dirty-otherwise output
 '
 
 test_expect_success 'handle submodules' '
 	echo 123 >>for-submodules/dirty-otherwise/initial.t &&
 
 	force_color git -C for-submodules add -p dirty-otherwise >output 2>&1 &&
-	grep "No changes" output &&
+	test_grep "No changes" output &&
 
 	force_color git -C for-submodules add -p dirty-head >output 2>&1 <y &&
 	git -C for-submodules ls-files --stage dirty-head >actual &&
 	rev="$(git -C for-submodules/dirty-head rev-parse HEAD)" &&
-	grep "$rev" actual
+	test_grep "$rev" actual
 '
 
 test_expect_success 'set up pathological context' '
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index ec2804eea67c..c66f966a3ab3 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -38,36 +38,36 @@ test_expect_success 'setup' '
 
 test_expect_success 'the default number of context lines is 3' '
 	git diff >output &&
-	! grep "^ d" output &&
-	grep "^ e" output &&
-	grep "^ j" output &&
-	! grep "^ k" output
+	test_grep ! "^ d" output &&
+	test_grep "^ e" output &&
+	test_grep "^ j" output &&
+	test_grep ! "^ k" output
 '
 
 test_expect_success 'diff.context honored by "log"' '
 	git log -1 -p >output &&
-	! grep firstline output &&
+	test_grep ! firstline output &&
 	git config diff.context 8 &&
 	git log -1 -p >output &&
-	grep "^ firstline" output
+	test_grep "^ firstline" output
 '
 
 test_expect_success 'The -U option overrides diff.context' '
 	git config diff.context 8 &&
 	git log -U4 -1 >output &&
-	! grep "^ firstline" output
+	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'diff.context honored by "diff"' '
 	git config diff.context 8 &&
 	git diff >output &&
-	grep "^ firstline" output
+	test_grep "^ firstline" output
 '
 
 test_expect_success 'plumbing not affected' '
 	git config diff.context 8 &&
 	git diff-files -p >output &&
-	! grep "^ firstline" output
+	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'non-integer config parsing' '
@@ -85,8 +85,8 @@ test_expect_success 'negative integer config parsing' '
 test_expect_success '-U0 is valid, so is diff.context=0' '
 	git config diff.context 0 &&
 	git diff >output &&
-	grep "^-ADDED" output &&
-	grep "^+MODIFIED" output
+	test_grep "^-ADDED" output &&
+	test_grep "^+MODIFIED" output
 '
 
 test_expect_success '-U2147483647 works' '
@@ -94,9 +94,9 @@ test_expect_success '-U2147483647 works' '
 	test_line_count = 16 x &&
 	git diff -U2147483647 >output &&
 	test_line_count = 22 output &&
-	grep "^-ADDED" output &&
-	grep "^+MODIFIED" output &&
-	grep "^+APPENDED" output
+	test_grep "^-ADDED" output &&
+	test_grep "^+MODIFIED" output &&
+	test_grep "^+APPENDED" output
 '
 
 test_done
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v4 2/4] t: use test_config in t4055
  2025-07-19 12:28     ` [PATCH v4 " Leon Michalak via GitGitGadget
  2025-07-19 12:28       ` [PATCH v4 1/4] t: use test_grep in t3701 and t4055 Leon Michalak via GitGitGadget
@ 2025-07-19 12:28       ` Leon Michalak via GitGitGadget
  2025-07-19 12:28       ` [PATCH v4 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
                         ` (3 subsequent siblings)
  5 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-07-19 12:28 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

Use the modern "test_config" test utility instead of manual"git config"
as the former provides clean up on test completion.

This is a prerequisite to the commits that follow which add to this test
file.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 t/t4055-diff-context.sh | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index c66f966a3ab3..1384a8195705 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -47,43 +47,43 @@ test_expect_success 'the default number of context lines is 3' '
 test_expect_success 'diff.context honored by "log"' '
 	git log -1 -p >output &&
 	test_grep ! firstline output &&
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git log -1 -p >output &&
 	test_grep "^ firstline" output
 '
 
 test_expect_success 'The -U option overrides diff.context' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git log -U4 -1 >output &&
 	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'diff.context honored by "diff"' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git diff >output &&
 	test_grep "^ firstline" output
 '
 
 test_expect_success 'plumbing not affected' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git diff-files -p >output &&
 	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'non-integer config parsing' '
-	git config diff.context no &&
+	test_config diff.context no &&
 	test_must_fail git diff 2>output &&
 	test_grep "bad numeric config value" output
 '
 
 test_expect_success 'negative integer config parsing' '
-	git config diff.context -1 &&
+	test_config diff.context -1 &&
 	test_must_fail git diff 2>output &&
 	test_grep "bad config variable" output
 '
 
 test_expect_success '-U0 is valid, so is diff.context=0' '
-	git config diff.context 0 &&
+	test_config diff.context 0 &&
 	git diff >output &&
 	test_grep "^-ADDED" output &&
 	test_grep "^+MODIFIED" output
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v4 3/4] add-patch: respect diff.context configuration
  2025-07-19 12:28     ` [PATCH v4 " Leon Michalak via GitGitGadget
  2025-07-19 12:28       ` [PATCH v4 1/4] t: use test_grep in t3701 and t4055 Leon Michalak via GitGitGadget
  2025-07-19 12:28       ` [PATCH v4 2/4] t: use test_config in t4055 Leon Michalak via GitGitGadget
@ 2025-07-19 12:28       ` Leon Michalak via GitGitGadget
  2025-07-19 12:28       ` [PATCH v4 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
                         ` (2 subsequent siblings)
  5 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-07-19 12:28 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

Various builtins that use add-patch infrastructure do not respect
the user's diff.context and diff.interHunkContext file configurations.

The user may be used to seeing their diffs with customized context size,
but not in the patches "git add -p" shows them to pick from.

Teach add-patch infrastructure to read these configuration variables and
pass their values when spawning the underlying plumbing commands as
their command line option.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 add-interactive.c          |  9 +++++++++
 add-interactive.h          |  1 +
 add-patch.c                |  9 ++++++---
 t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++
 4 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 97ff35b6f12a..eb3d0d3ada84 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -41,6 +41,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	const char *value;
 
 	s->r = r;
+	s->context = -1;
+	s->interhunkcontext = -1;
 
 	if (repo_config_get_value(r, "color.interactive", &value))
 		s->use_color = -1;
@@ -78,6 +80,13 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	repo_config_get_string(r, "diff.algorithm",
 			       &s->interactive_diff_algorithm);
 
+	if (!repo_config_get_int(r, "diff.context", &s->context))
+		if (s->context < 0)
+			die(_("%s cannot be negative"), "diff.context");
+	if (!repo_config_get_int(r, "diff.interHunkContext", &s->interhunkcontext))
+		if (s->interhunkcontext < 0)
+			die(_("%s cannot be negative"), "diff.interHunkContext");
+
 	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
 	if (s->use_single_key)
 		setbuf(stdin, NULL);
diff --git a/add-interactive.h b/add-interactive.h
index 693f125e8e4b..c63f35b14be8 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -18,6 +18,7 @@ struct add_i_state {
 
 	int use_single_key;
 	char *interactive_diff_filter, *interactive_diff_algorithm;
+	int context, interhunkcontext;
 };
 
 void init_add_i_state(struct add_i_state *s, struct repository *r);
diff --git a/add-patch.c b/add-patch.c
index 95c67d8c80c4..b0125b51ba45 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -414,7 +414,6 @@ static int normalize_marker(const char *p)
 static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 {
 	struct strvec args = STRVEC_INIT;
-	const char *diff_algorithm = s->s.interactive_diff_algorithm;
 	struct strbuf *plain = &s->plain, *colored = NULL;
 	struct child_process cp = CHILD_PROCESS_INIT;
 	char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
@@ -424,8 +423,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 	int res;
 
 	strvec_pushv(&args, s->mode->diff_cmd);
-	if (diff_algorithm)
-		strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
+	if (s->s.context != -1)
+		strvec_pushf(&args, "--unified=%i", s->s.context);
+	if (s->s.interhunkcontext != -1)
+		strvec_pushf(&args, "--inter-hunk-context=%i", s->s.interhunkcontext);
+	if (s->s.interactive_diff_algorithm)
+		strvec_pushf(&args, "--diff-algorithm=%s", s->s.interactive_diff_algorithm);
 	if (s->revision) {
 		struct object_id oid;
 		strvec_push(&args,
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index b088ee141ff4..18dc329ea1f6 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -1230,4 +1230,26 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
 	test_cmp expect actual
 '
 
+test_expect_success 'add -p respects diff.context' '
+	test_write_lines a b c d e f g h i j k l m >file &&
+	git add file &&
+	test_write_lines a b c d e f G h i j k l m >file &&
+	echo y | git -c diff.context=5 add -p >actual &&
+	test_grep "@@ -2,11 +2,11 @@" actual
+'
+
+test_expect_success 'add -p respects diff.interHunkContext' '
+	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
+	git add file &&
+	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
+	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
+	test_grep "@@ -2,16 +2,16 @@" actual
+'
+
+test_expect_success 'add -p rejects negative diff.context' '
+	test_config diff.context -1 &&
+	test_must_fail git add -p 2>output &&
+	test_grep "diff.context cannot be negative" output
+'
+
 test_done
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v4 4/4] add-patch: add diff.context command line overrides
  2025-07-19 12:28     ` [PATCH v4 " Leon Michalak via GitGitGadget
                         ` (2 preceding siblings ...)
  2025-07-19 12:28       ` [PATCH v4 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
@ 2025-07-19 12:28       ` Leon Michalak via GitGitGadget
  2025-07-22 16:01         ` Phillip Wood
  2025-07-21 16:50       ` [PATCH v4 0/4] Better support for customising context lines in --patch commands Junio C Hamano
  2025-07-29  7:01       ` [PATCH v5 " Leon Michalak via GitGitGadget
  5 siblings, 1 reply; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-07-19 12:28 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

This patch compliments the previous commit, where builtins that use
add-patch infrastructure now respect diff.context and
diff.interHunkContext file configurations.

In particular, this patch helps users who don't want to set persistent
context configurations or just want a way to override them on a one-time
basis, by allowing the relevant builtins to accept corresponding command
line options that override the file configurations.

This mimics commands such as diff and log, which allow for both context
file configuration and command line overrides.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 Documentation/diff-context-options.adoc | 10 +++++
 Documentation/git-add.adoc              |  2 +
 Documentation/git-checkout.adoc         |  2 +
 Documentation/git-commit.adoc           |  2 +
 Documentation/git-reset.adoc            |  2 +
 Documentation/git-restore.adoc          |  2 +
 Documentation/git-stash.adoc            |  2 +
 add-interactive.c                       | 36 +++++++++++----
 add-interactive.h                       | 16 +++++--
 add-patch.c                             |  5 ++-
 builtin/add.c                           | 21 +++++++--
 builtin/checkout.c                      | 31 +++++++++++--
 builtin/commit.c                        | 16 ++++++-
 builtin/reset.c                         | 17 ++++++-
 builtin/stash.c                         | 56 ++++++++++++++++++-----
 commit.h                                |  3 +-
 parse-options.h                         |  2 +
 t/t3701-add-interactive.sh              | 59 ++++++++++++++++++-------
 t/t4055-diff-context.sh                 | 30 +++++++++++++
 t/t9902-completion.sh                   |  2 +
 20 files changed, 265 insertions(+), 51 deletions(-)
 create mode 100644 Documentation/diff-context-options.adoc

diff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc
new file mode 100644
index 000000000000..e161260358ff
--- /dev/null
+++ b/Documentation/diff-context-options.adoc
@@ -0,0 +1,10 @@
+`-U<n>`::
+`--unified=<n>`::
+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
+	or 3 if the config option is unset.
+
+`--inter-hunk-context=<n>`::
+	Show the context between diff hunks, up to the specified _<number>_
+	of lines, thereby fusing hunks that are close to each other.
+	Defaults to `diff.interHunkContext` or 0 if the config option
+	is unset.
diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index eba0b419ce50..b7a735824d6c 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -104,6 +104,8 @@ This effectively runs `add --interactive`, but bypasses the
 initial command menu and directly jumps to the `patch` subcommand.
 See ``Interactive mode'' for details.
 
+include::diff-context-options.adoc[]
+
 `-e`::
 `--edit`::
 	Open the diff vs. the index in an editor and let the user
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index ee83b6d9ba9a..40e02cfd6562 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -289,6 +289,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 Note that this option uses the no overlay mode by default (see also
 `--overlay`), and currently doesn't support overlay mode.
 
+include::diff-context-options.adoc[]
+
 `--ignore-other-worktrees`::
 	`git checkout` refuses when the wanted branch is already checked
 	out or otherwise in use by another worktree. This option makes
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc
index dc219025f1eb..ae988a883b5b 100644
--- a/Documentation/git-commit.adoc
+++ b/Documentation/git-commit.adoc
@@ -76,6 +76,8 @@ OPTIONS
 	which changes to commit. See linkgit:git-add[1] for
 	details.
 
+include::diff-context-options.adoc[]
+
 `-C <commit>`::
 `--reuse-message=<commit>`::
 	Take an existing _<commit>_ object, and reuse the log message
diff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc
index 53ab88c5451c..50e8a0ba6f66 100644
--- a/Documentation/git-reset.adoc
+++ b/Documentation/git-reset.adoc
@@ -125,6 +125,8 @@ OPTIONS
 	separated with _NUL_ character and all other characters are taken
 	literally (including newlines and quotes).
 
+include::diff-context-options.adoc[]
+
 `--`::
 	Do not interpret any more arguments as options.
 
diff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc
index 877b7772e667..1dcc2bb7aea3 100644
--- a/Documentation/git-restore.adoc
+++ b/Documentation/git-restore.adoc
@@ -52,6 +52,8 @@ leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to
 	Mode" section of linkgit:git-add[1] to learn how to operate
 	the `--patch` mode.
 
+include::diff-context-options.adoc[]
+
 `-W`::
 `--worktree`::
 `-S`::
diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc
index 1a5177f4986c..0578c619c410 100644
--- a/Documentation/git-stash.adoc
+++ b/Documentation/git-stash.adoc
@@ -208,6 +208,8 @@ to learn how to operate the `--patch` mode.
 The `--patch` option implies `--keep-index`.  You can use
 `--no-keep-index` to override this.
 
+include::diff-context-options.adoc[]
+
 -S::
 --staged::
 	This option is only valid for `push` and `save` commands.
diff --git a/add-interactive.c b/add-interactive.c
index eb3d0d3ada84..3e692b47eca0 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -36,7 +36,8 @@ static void init_color(struct repository *r, struct add_i_state *s,
 	free(key);
 }
 
-void init_add_i_state(struct add_i_state *s, struct repository *r)
+void init_add_i_state(struct add_i_state *s, struct repository *r,
+		      struct add_p_opt *add_p_opt)
 {
 	const char *value;
 
@@ -90,6 +91,17 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
 	if (s->use_single_key)
 		setbuf(stdin, NULL);
+
+	if (add_p_opt->context != -1) {
+		if (add_p_opt->context < 0)
+			die(_("%s cannot be negative"), "--unified");
+		s->context = add_p_opt->context;
+	}
+	if (add_p_opt->interhunkcontext != -1) {
+		if (add_p_opt->interhunkcontext < 0)
+			die(_("%s cannot be negative"), "--inter-hunk-context");
+		s->interhunkcontext = add_p_opt->interhunkcontext;
+	}
 }
 
 void clear_add_i_state(struct add_i_state *s)
@@ -978,6 +990,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 	opts->prompt = N_("Patch update");
 	count = list_and_choose(s, files, opts);
 	if (count > 0) {
+		struct add_p_opt add_p_opt = {
+			.context = s->context,
+			.interhunkcontext = s->interhunkcontext,
+		};
 		struct strvec args = STRVEC_INIT;
 		struct pathspec ps_selected = { 0 };
 
@@ -988,7 +1004,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 		parse_pathspec(&ps_selected,
 			       PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
 			       PATHSPEC_LITERAL_PATH, "", args.v);
-		res = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);
+		res = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected);
 		strvec_clear(&args);
 		clear_pathspec(&ps_selected);
 	}
@@ -1023,10 +1039,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
 	if (count > 0) {
 		struct child_process cmd = CHILD_PROCESS_INIT;
 
-		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached",
-			     oid_to_hex(!is_initial ? &oid :
-					s->r->hash_algo->empty_tree),
-			     "--", NULL);
+		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL);
+		if (s->context != -1)
+			strvec_pushf(&cmd.args, "--unified=%i", s->context);
+		if (s->interhunkcontext != -1)
+			strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->interhunkcontext);
+		strvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :
+			     s->r->hash_algo->empty_tree), "--", NULL);
 		for (i = 0; i < files->items.nr; i++)
 			if (files->selected[i])
 				strvec_push(&cmd.args,
@@ -1119,7 +1138,8 @@ static void command_prompt_help(struct add_i_state *s)
 			 _("(empty) select nothing"));
 }
 
-int run_add_i(struct repository *r, const struct pathspec *ps)
+int run_add_i(struct repository *r, const struct pathspec *ps,
+	      struct add_p_opt *add_p_opt)
 {
 	struct add_i_state s = { NULL };
 	struct print_command_item_data data = { "[", "]" };
@@ -1162,7 +1182,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 			->util = util;
 	}
 
-	init_add_i_state(&s, r);
+	init_add_i_state(&s, r, add_p_opt);
 
 	/*
 	 * When color was asked for, use the prompt color for
diff --git a/add-interactive.h b/add-interactive.h
index c63f35b14be8..4213dcd67b9a 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -3,6 +3,13 @@
 
 #include "color.h"
 
+struct add_p_opt {
+	int context;
+	int interhunkcontext;
+};
+
+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }
+
 struct add_i_state {
 	struct repository *r;
 	int use_color;
@@ -21,12 +28,14 @@ struct add_i_state {
 	int context, interhunkcontext;
 };
 
-void init_add_i_state(struct add_i_state *s, struct repository *r);
+void init_add_i_state(struct add_i_state *s, struct repository *r,
+		      struct add_p_opt *add_p_opt);
 void clear_add_i_state(struct add_i_state *s);
 
 struct repository;
 struct pathspec;
-int run_add_i(struct repository *r, const struct pathspec *ps);
+int run_add_i(struct repository *r, const struct pathspec *ps,
+	      struct add_p_opt *add_p_opt);
 
 enum add_p_mode {
 	ADD_P_ADD,
@@ -37,6 +46,7 @@ enum add_p_mode {
 };
 
 int run_add_p(struct repository *r, enum add_p_mode mode,
-	      const char *revision, const struct pathspec *ps);
+	      struct add_p_opt *o, const char *revision,
+	      const struct pathspec *ps);
 
 #endif
diff --git a/add-patch.c b/add-patch.c
index b0125b51ba45..302e6ba7d9a3 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -1763,14 +1763,15 @@ soft_increment:
 }
 
 int run_add_p(struct repository *r, enum add_p_mode mode,
-	      const char *revision, const struct pathspec *ps)
+	      struct add_p_opt *o, const char *revision,
+	      const struct pathspec *ps)
 {
 	struct add_p_state s = {
 		{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	size_t i, binary_count = 0;
 
-	init_add_i_state(&s.s, r);
+	init_add_i_state(&s.s, r, o);
 
 	if (mode == ADD_P_STASH)
 		s.mode = &patch_mode_stash;
diff --git a/builtin/add.c b/builtin/add.c
index 7c292ffdc6c2..a00dab9b8fa0 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -29,6 +29,7 @@ static const char * const builtin_add_usage[] = {
 	NULL
 };
 static int patch_interactive, add_interactive, edit_interactive;
+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 static int take_worktree_changes;
 static int add_renormalize;
 static int pathspec_file_nul;
@@ -157,7 +158,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec *
 int interactive_add(struct repository *repo,
 		    const char **argv,
 		    const char *prefix,
-		    int patch)
+		    int patch, struct add_p_opt *add_p_opt)
 {
 	struct pathspec pathspec;
 	int ret;
@@ -169,9 +170,9 @@ int interactive_add(struct repository *repo,
 		       prefix, argv);
 
 	if (patch)
-		ret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);
+		ret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec);
 	else
-		ret = !!run_add_i(repo, &pathspec);
+		ret = !!run_add_i(repo, &pathspec, add_p_opt);
 
 	clear_pathspec(&pathspec);
 	return ret;
@@ -253,6 +254,8 @@ static struct option builtin_add_options[] = {
 	OPT_GROUP(""),
 	OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
 	OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
+	OPT_DIFF_UNIFIED(&add_p_opt.context),
+	OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 	OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
 	OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
 	OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
@@ -394,6 +397,11 @@ int cmd_add(int argc,
 	prepare_repo_settings(repo);
 	repo->settings.command_requires_full_index = 0;
 
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	if (patch_interactive)
 		add_interactive = 1;
 	if (add_interactive) {
@@ -401,7 +409,12 @@ int cmd_add(int argc,
 			die(_("options '%s' and '%s' cannot be used together"), "--dry-run", "--interactive/--patch");
 		if (pathspec_from_file)
 			die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch");
-		exit(interactive_add(repo, argv + 1, prefix, patch_interactive));
+		exit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
 	}
 
 	if (edit_interactive) {
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 536192d3456c..3737ba4c3920 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -61,6 +61,8 @@ static const char * const restore_usage[] = {
 
 struct checkout_opts {
 	int patch_mode;
+	int patch_context;
+	int patch_interhunk_context;
 	int quiet;
 	int merge;
 	int force;
@@ -104,7 +106,12 @@ struct checkout_opts {
 	struct tree *source_tree;
 };
 
-#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 }
+#define CHECKOUT_OPTS_INIT { \
+	.conflict_style = -1, \
+	.merge = -1, \
+	.patch_context = -1, \
+	.patch_interhunk_context = -1, \
+}
 
 struct branch_info {
 	char *name; /* The short name used */
@@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts,
 
 	if (opts->patch_mode) {
 		enum add_p_mode patch_mode;
+		struct add_p_opt add_p_opt = {
+			.context = opts->patch_context,
+			.interhunkcontext = opts->patch_interhunk_context,
+		};
 		const char *rev = new_branch_info->name;
 		char rev_oid[GIT_MAX_HEXSZ + 1];
 
@@ -564,8 +575,8 @@ static int checkout_paths(const struct checkout_opts *opts,
 		else
 			BUG("either flag must have been set, worktree=%d, index=%d",
 			    opts->checkout_worktree, opts->checkout_index);
-		return !!run_add_p(the_repository, patch_mode, rev,
-				   &opts->pathspec);
+		return !!run_add_p(the_repository, patch_mode, &add_p_opt,
+				   rev, &opts->pathspec);
 	}
 
 	repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -1738,6 +1749,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
 		OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
+		OPT_DIFF_UNIFIED(&opts->patch_context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context),
 		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),
@@ -1780,6 +1793,18 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 	argc = parse_options(argc, argv, prefix, options,
 			     usagestr, parseopt_flags);
 
+	if (opts->patch_context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (opts->patch_interhunk_context < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
+	if (!opts->patch_mode) {
+		if (opts->patch_context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (opts->patch_interhunk_context != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
diff --git a/builtin/commit.c b/builtin/commit.c
index fba0dded64a7..73673bc7db9f 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -19,6 +19,7 @@
 #include "environment.h"
 #include "diff.h"
 #include "commit.h"
+#include "add-interactive.h"
 #include "gettext.h"
 #include "revision.h"
 #include "wt-status.h"
@@ -122,6 +123,7 @@ static const char *edit_message, *use_message;
 static char *fixup_message, *fixup_commit, *squash_message;
 static const char *fixup_prefix;
 static int all, also, interactive, patch_interactive, only, amend, signoff;
+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
@@ -354,6 +356,11 @@ static const char *prepare_index(const char **argv, const char *prefix,
 	const char *ret;
 	char *path = NULL;
 
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	if (is_status)
 		refresh_flags |= REFRESH_UNMERGED;
 	parse_pathspec(&pathspec, 0,
@@ -400,7 +407,7 @@ static const char *prepare_index(const char **argv, const char *prefix,
 		old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
 		setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
 
-		if (interactive_add(the_repository, argv, prefix, patch_interactive) != 0)
+		if (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0)
 			die(_("interactive add failed"));
 
 		the_repository->index_file = old_repo_index_file;
@@ -424,6 +431,11 @@ static const char *prepare_index(const char **argv, const char *prefix,
 		commit_style = COMMIT_NORMAL;
 		ret = get_lock_file_path(&index_lock);
 		goto out;
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
 	}
 
 	/*
@@ -1722,6 +1734,8 @@ int cmd_commit(int argc,
 		OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")),
 		OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
 		OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT_BOOL('o', "only", &only, N_("commit only specified files")),
 		OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")),
 		OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
diff --git a/builtin/reset.c b/builtin/reset.c
index dc50ffc1ac59..9fb32795c9c5 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -346,6 +346,7 @@ int cmd_reset(int argc,
 	struct object_id oid;
 	struct pathspec pathspec;
 	int intent_to_add = 0;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	const struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
 		OPT_BOOL(0, "no-refresh", &no_refresh,
@@ -370,6 +371,8 @@ int cmd_reset(int argc,
 			       PARSE_OPT_OPTARG,
 			       option_parse_recurse_submodules_worktree_updater),
 		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT_BOOL('N', "intent-to-add", &intent_to_add,
 				N_("record only the fact that removed paths will be added later")),
 		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
@@ -420,6 +423,11 @@ int cmd_reset(int argc,
 		oidcpy(&oid, &tree->object.oid);
 	}
 
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	prepare_repo_settings(the_repository);
 	the_repository->settings.command_requires_full_index = 0;
 
@@ -427,9 +435,14 @@ int cmd_reset(int argc,
 		if (reset_type != NONE)
 			die(_("options '%s' and '%s' cannot be used together"), "--patch", "--{hard,mixed,soft}");
 		trace2_cmd_mode("patch-interactive");
-		update_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev,
-				   &pathspec);
+		update_ref_status = !!run_add_p(the_repository, ADD_P_RESET,
+						&add_p_opt, rev, &pathspec);
 		goto cleanup;
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
 	}
 
 	/* git reset tree [--] paths... can be used to
diff --git a/builtin/stash.c b/builtin/stash.c
index 7cd3ad8aa48e..6da162e5e69a 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -1242,7 +1242,8 @@ done:
 }
 
 static int stash_patch(struct stash_info *info, const struct pathspec *ps,
-		       struct strbuf *out_patch, int quiet)
+		       struct strbuf *out_patch, int quiet,
+		       struct add_p_opt *add_p_opt)
 {
 	int ret = 0;
 	struct child_process cp_read_tree = CHILD_PROCESS_INIT;
@@ -1267,7 +1268,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
 	old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
 	setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
 
-	ret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps);
+	ret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps);
 
 	the_repository->index_file = old_repo_index_file;
 	if (old_index_env && *old_index_env)
@@ -1362,8 +1363,8 @@ done:
 }
 
 static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,
-			   int include_untracked, int patch_mode, int only_staged,
-			   struct stash_info *info, struct strbuf *patch,
+			   int include_untracked, int patch_mode, struct add_p_opt *add_p_opt,
+			   int only_staged, struct stash_info *info, struct strbuf *patch,
 			   int quiet)
 {
 	int ret = 0;
@@ -1444,7 +1445,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
 		untracked_commit_option = 1;
 	}
 	if (patch_mode) {
-		ret = stash_patch(info, ps, patch, quiet);
+		ret = stash_patch(info, ps, patch, quiet, add_p_opt);
 		if (ret < 0) {
 			if (!quiet)
 				fprintf_ln(stderr, _("Cannot save the current "
@@ -1519,7 +1520,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
 	if (!check_changes_tracked_files(&ps))
 		return 0;
 
-	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,
+	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,
 			      NULL, 0);
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
@@ -1530,7 +1531,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
 }
 
 static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,
-			 int keep_index, int patch_mode, int include_untracked, int only_staged)
+			 int keep_index, int patch_mode, struct add_p_opt *add_p_opt,
+			 int include_untracked, int only_staged)
 {
 	int ret = 0;
 	struct stash_info info = STASH_INFO_INIT;
@@ -1600,8 +1602,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
 
 	if (stash_msg)
 		strbuf_addstr(&stash_msg_buf, stash_msg);
-	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,
-			    &info, &patch, quiet)) {
+	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
+			    add_p_opt, only_staged, &info, &patch, quiet)) {
 		ret = -1;
 		goto done;
 	}
@@ -1774,6 +1776,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 	const char *stash_msg = NULL;
 	char *pathspec_from_file = NULL;
 	struct pathspec ps;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	struct option options[] = {
 		OPT_BOOL('k', "keep-index", &keep_index,
 			 N_("keep index")),
@@ -1781,6 +1784,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT__QUIET(&quiet, N_("quiet mode")),
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
@@ -1836,8 +1841,20 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 		die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file");
 	}
 
+	if (!patch_mode) {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
-			    include_untracked, only_staged);
+			    &add_p_opt, include_untracked, only_staged);
 
 	clear_pathspec(&ps);
 	free(pathspec_from_file);
@@ -1862,6 +1879,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 	const char *stash_msg = NULL;
 	struct pathspec ps;
 	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	struct option options[] = {
 		OPT_BOOL('k', "keep-index", &keep_index,
 			 N_("keep index")),
@@ -1869,6 +1887,8 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT__QUIET(&quiet, N_("quiet mode")),
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
@@ -1887,8 +1907,22 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 		stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
+
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
+	if (!patch_mode) {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
-			    patch_mode, include_untracked, only_staged);
+			    patch_mode, &add_p_opt, include_untracked,
+			    only_staged);
 
 	strbuf_release(&stash_msg_buf);
 	return ret;
diff --git a/commit.h b/commit.h
index 70c870dae4d4..7a7fedbc2f14 100644
--- a/commit.h
+++ b/commit.h
@@ -2,6 +2,7 @@
 #define COMMIT_H
 
 #include "object.h"
+#include "add-interactive.h"
 
 struct signature_check;
 struct strbuf;
@@ -257,7 +258,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *);
 int interactive_add(struct repository *repo,
 		    const char **argv,
 		    const char *prefix,
-		    int patch);
+		    int patch, struct add_p_opt *add_p_opt);
 
 struct commit_extra_header {
 	struct commit_extra_header *next;
diff --git a/parse-options.h b/parse-options.h
index 91c3e3c29b3d..bdae8f116198 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -616,6 +616,8 @@ int parse_opt_tracking_mode(const struct option *, const char *, int);
 #define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file"))
 #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"))
 #define OPT_AUTOSTASH(v) OPT_BOOL(0, "autostash", v, N_("automatically stash/stash pop before and after"))
+#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', "unified", v, N_("generate diffs with <n> lines context"), PARSE_OPT_NONEG)
+#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)
 
 #define OPT_IPVERSION(v) \
 	OPT_SET_INT_F('4', "ipv4", (v), N_("use IPv4 addresses only"), \
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 18dc329ea1f6..7fd9a23c9987 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -1230,26 +1230,53 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
 	test_cmp expect actual
 '
 
-test_expect_success 'add -p respects diff.context' '
-	test_write_lines a b c d e f g h i j k l m >file &&
+for cmd in add checkout restore 'commit -m file'
+do
+	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" '
+		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 &&
+		git add file &&
+		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 &&
+		echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
+			$cmd -p -U 4 --inter-hunk-context 2 >actual &&
+		test_grep "@@ -2,20 +2,20 @@" actual
+	'
+done
+
+test_expect_success 'reset accepts -U and --inter-hunk-context' '
+	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 &&
+	git commit -m file file &&
+	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 &&
 	git add file &&
-	test_write_lines a b c d e f G h i j k l m >file &&
-	echo y | git -c diff.context=5 add -p >actual &&
-	test_grep "@@ -2,11 +2,11 @@" actual
+	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
+		reset -p -U 4 --inter-hunk-context 2 >actual &&
+	test_grep "@@ -2,20 +2,20 @@" actual
 '
 
-test_expect_success 'add -p respects diff.interHunkContext' '
-	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
-	git add file &&
-	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
-	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
-	test_grep "@@ -2,16 +2,16 @@" actual
+test_expect_success 'stash accepts -U and --inter-hunk-context' '
+	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 &&
+	git commit -m file file &&
+	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 &&
+	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
+		stash -p -U 4 --inter-hunk-context 2 >actual &&
+	test_grep "@@ -2,20 +2,20 @@" actual
 '
 
-test_expect_success 'add -p rejects negative diff.context' '
-	test_config diff.context -1 &&
-	test_must_fail git add -p 2>output &&
-	test_grep "diff.context cannot be negative" output
-'
+for cmd in add checkout commit reset restore "stash save" "stash push"
+do
+	test_expect_success "$cmd rejects invalid context options" '
+		test_must_fail git $cmd -p -U -3 2>actual &&
+		cat actual | echo &&
+		test_grep -e ".--unified. cannot be negative" actual &&
+
+		test_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&
+		test_grep -e ".--inter-hunk-context. cannot be negative" actual &&
+
+		test_must_fail git $cmd -U 7 2>actual &&
+		test_grep -E ".--unified. requires .(--interactive/)?--patch." actual &&
+
+		test_must_fail git $cmd --inter-hunk-context 2 2>actual &&
+		test_grep -E ".--inter-hunk-context. requires .(--interactive/)?--patch." actual
+	'
+done
 
 test_done
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index 1384a8195705..0158fe6568cb 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -58,6 +58,36 @@ test_expect_success 'The -U option overrides diff.context' '
 	test_grep ! "^ firstline" output
 '
 
+test_expect_success 'The -U option overrides diff.context for "add"' '
+	test_config diff.context 8 &&
+	git add -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "commit"' '
+	test_config diff.context 8 &&
+	! git commit -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "checkout"' '
+	test_config diff.context 8 &&
+	git checkout -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "stash"' '
+	test_config diff.context 8 &&
+	! git stash -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
+test_expect_success 'The -U option overrides diff.context for "restore"' '
+	test_config diff.context 8 &&
+	git restore -U4 -p >output &&
+	test_grep ! "^ firstline" output
+'
+
 test_expect_success 'diff.context honored by "diff"' '
 	test_config diff.context 8 &&
 	git diff >output &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 343b8cd1912b..6650d33fba69 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2596,6 +2596,8 @@ test_expect_success 'double dash "git checkout"' '
 	--merge Z
 	--conflict=Z
 	--patch Z
+	--unified=Z
+	--inter-hunk-context=Z
 	--ignore-skip-worktree-bits Z
 	--ignore-other-worktrees Z
 	--recurse-submodules Z
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 77+ messages in thread

* Re: [PATCH v4 0/4] Better support for customising context lines in --patch commands
  2025-07-19 12:28     ` [PATCH v4 " Leon Michalak via GitGitGadget
                         ` (3 preceding siblings ...)
  2025-07-19 12:28       ` [PATCH v4 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
@ 2025-07-21 16:50       ` Junio C Hamano
  2025-07-22 16:05         ` Phillip Wood
  2025-07-29  7:01       ` [PATCH v5 " Leon Michalak via GitGitGadget
  5 siblings, 1 reply; 77+ messages in thread
From: Junio C Hamano @ 2025-07-21 16:50 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

"Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:

> Changes since v3:
>
>  * Update commit descriptions
>  * Read struct properties directly instead of assigning to variables first
>  * Simplify config setting / error checking
>  * Remove redundant tests in later commit as they were replaced with better
>    test(s)
>  * Change tests to use single quotes (this messes with the grep so was
>    unable to explicitly test single quotes in the error messages, so decided
>    to use regex . instead, which is what some other tests that have this
>    problem seem to use as well)

All of the above looked reasonably well done.  Will replace.

Unless there are objections, let me mark the topic for 'next' soonish.

Thanks.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v4 4/4] add-patch: add diff.context command line overrides
  2025-07-19 12:28       ` [PATCH v4 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
@ 2025-07-22 16:01         ` Phillip Wood
  2025-07-22 18:02           ` Leon Michalak
  0 siblings, 1 reply; 77+ messages in thread
From: Phillip Wood @ 2025-07-22 16:01 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget, git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Leon Michalak

Hi Leon

On 19/07/2025 13:28, Leon Michalak via GitGitGadget wrote:
> From: Leon Michalak <leonmichalak6@gmail.com>
> 
> This patch compliments the previous commit, where builtins that use
> add-patch infrastructure now respect diff.context and
> diff.interHunkContext file configurations.
> 
> In particular, this patch helps users who don't want to set persistent
> context configurations or just want a way to override them on a one-time
> basis, by allowing the relevant builtins to accept corresponding command
> line options that override the file configurations.
> 
> This mimics commands such as diff and log, which allow for both context
> file configuration and command line overrides.

Thanks for updating the quoting in the tests. Unfortunately this patch 
now deletes the tests added in the last commit which I don't think is 
correct.

>   
> -test_expect_success 'add -p respects diff.context' '
> -	test_write_lines a b c d e f g h i j k l m >file &&
I think there is some confusion here - why are we deleting the tests 
added in the last commit? This removes the test coverage for 
diff.context and diff.interHunkContext

> +for cmd in add checkout restore 'commit -m file'
> +do
> +	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" '
> +		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 &&
> +		git add file &&
> +		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 &&
> +		echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
> +			$cmd -p -U 4 --inter-hunk-context 2 >actual &&
> +		test_grep "@@ -2,20 +2,20 @@" actual
> +	'
> +done
> +
> +test_expect_success 'reset accepts -U and --inter-hunk-context' '
> +	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 &&
> +	git commit -m file file &&
> +	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 &&
>   	git add file &&
> -	test_write_lines a b c d e f G h i j k l m >file &&
> -	echo y | git -c diff.context=5 add -p >actual &&
> -	test_grep "@@ -2,11 +2,11 @@" actual
> +	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
> +		reset -p -U 4 --inter-hunk-context 2 >actual &&
> +	test_grep "@@ -2,20 +2,20 @@" actual
>   '
>   
> -test_expect_success 'add -p respects diff.interHunkContext' '
> -	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
> -	git add file &&
> -	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
> -	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
> -	test_grep "@@ -2,16 +2,16 @@" actual

This is also deleting a test added in the last patch

> +test_expect_success 'stash accepts -U and --inter-hunk-context' '
> +	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 &&
> +	git commit -m file file &&
> +	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 &&
> +	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
> +		stash -p -U 4 --inter-hunk-context 2 >actual &&
> +	test_grep "@@ -2,20 +2,20 @@" actual
>   '
>   
> -test_expect_success 'add -p rejects negative diff.context' '
> -	test_config diff.context -1 &&
> -	test_must_fail git add -p 2>output &&
> -	test_grep "diff.context cannot be negative" output
> -'

and so is this. The tests you're adding look good but we shouldn't be 
deleting the existing ones.

> +for cmd in add checkout commit reset restore "stash save" "stash push"
> +do
> +	test_expect_success "$cmd rejects invalid context options" '
> +		test_must_fail git $cmd -p -U -3 2>actual &&
> +		cat actual | echo &&
> +		test_grep -e ".--unified. cannot be negative" actual &&
> +
> +		test_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&
> +		test_grep -e ".--inter-hunk-context. cannot be negative" actual &&
> +
> +		test_must_fail git $cmd -U 7 2>actual &&
> +		test_grep -E ".--unified. requires .(--interactive/)?--patch." actual &&
> +
> +		test_must_fail git $cmd --inter-hunk-context 2 2>actual &&
> +		test_grep -E ".--inter-hunk-context. requires .(--interactive/)?--patch." actual
> +	'
> +done

This looks good as well
>   test_done
As I said last time I do not think the tests below add any value. They 
also do not compensate for the removal of the tests for diff.context 
that are deleted above as they all pass -U on the commandline.

> diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
> index 1384a8195705..0158fe6568cb 100755
> --- a/t/t4055-diff-context.sh
> +++ b/t/t4055-diff-context.sh
> @@ -58,6 +58,36 @@ test_expect_success 'The -U option overrides diff.context' '
>   	test_grep ! "^ firstline" output
>   '
>   
> +test_expect_success 'The -U option overrides diff.context for "add"' '
> +	test_config diff.context 8 &&
> +	git add -U4 -p >output &&
> +	test_grep ! "^ firstline" output
> +'
> +
> +test_expect_success 'The -U option overrides diff.context for "commit"' '
> +	test_config diff.context 8 &&
> +	! git commit -U4 -p >output &&
> +	test_grep ! "^ firstline" output
> +'
> +
> +test_expect_success 'The -U option overrides diff.context for "checkout"' '
> +	test_config diff.context 8 &&
> +	git checkout -U4 -p >output &&
> +	test_grep ! "^ firstline" output
> +'
> +
> +test_expect_success 'The -U option overrides diff.context for "stash"' '
> +	test_config diff.context 8 &&
> +	! git stash -U4 -p >output &&
> +	test_grep ! "^ firstline" output
> +'
> +
> +test_expect_success 'The -U option overrides diff.context for "restore"' '
> +	test_config diff.context 8 &&
> +	git restore -U4 -p >output &&
> +	test_grep ! "^ firstline" output
> +'
> +
>   test_expect_success 'diff.context honored by "diff"' '
>   	test_config diff.context 8 &&
>   	git diff >output &&

Thanks

Phillip


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v4 0/4] Better support for customising context lines in --patch commands
  2025-07-21 16:50       ` [PATCH v4 0/4] Better support for customising context lines in --patch commands Junio C Hamano
@ 2025-07-22 16:05         ` Phillip Wood
  2025-07-22 17:20           ` Junio C Hamano
  0 siblings, 1 reply; 77+ messages in thread
From: Phillip Wood @ 2025-07-22 16:05 UTC (permalink / raw)
  To: Junio C Hamano, Leon Michalak via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Leon Michalak

On 21/07/2025 17:50, Junio C Hamano wrote:
> "Leon Michalak via GitGitGadget" <gitgitgadget@gmail.com> writes:
> 
>> Changes since v3:
>>
>>   * Update commit descriptions
>>   * Read struct properties directly instead of assigning to variables first
>>   * Simplify config setting / error checking
>>   * Remove redundant tests in later commit as they were replaced with better
>>     test(s)

I've left some comments on patch 4 about this. It seems to me that it is 
deleting useful tests and replacing them with redundant tests.

>>   * Change tests to use single quotes (this messes with the grep so was
>>     unable to explicitly test single quotes in the error messages, so decided
>>     to use regex . instead, which is what some other tests that have this
>>     problem seem to use as well)

Thanks for doing that

> 
> All of the above looked reasonably well done.  Will replace.
> 
> Unless there are objections, let me mark the topic for 'next' soonish.

I think we want to sort out the test changes in patch 4. Previously we 
discussed centralizing the option parsing in that patch as well but we 
can always do that as a follow up later.

Thanks

Phillip


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v4 0/4] Better support for customising context lines in --patch commands
  2025-07-22 16:05         ` Phillip Wood
@ 2025-07-22 17:20           ` Junio C Hamano
  0 siblings, 0 replies; 77+ messages in thread
From: Junio C Hamano @ 2025-07-22 17:20 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Leon Michalak

Phillip Wood <phillip.wood123@gmail.com> writes:

>> All of the above looked reasonably well done.  Will replace.
>> Unless there are objections, let me mark the topic for 'next'
>> soonish.
>
> I think we want to sort out the test changes in patch 4. Previously we
> discussed centralizing the option parsing in that patch as well but we
> can always do that as a follow up later.

Yeah, the tests can probably use a bit more polish before we can
move forward.  Thanks for carefully reading them over.

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v4 4/4] add-patch: add diff.context command line overrides
  2025-07-22 16:01         ` Phillip Wood
@ 2025-07-22 18:02           ` Leon Michalak
  2025-07-22 18:05             ` Leon Michalak
  2025-07-23  9:41             ` Phillip Wood
  0 siblings, 2 replies; 77+ messages in thread
From: Leon Michalak @ 2025-07-22 18:02 UTC (permalink / raw)
  To: phillip.wood
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder

Hey Philip, looking again for a second time and re-reading previous
replies on this thread, I think I misunderstood a previous reply.

I will add these back as soon as I can get back to the PC, thanks for
spotting that!

On Tue, 22 Jul 2025 at 17:02, Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> Hi Leon
>
> On 19/07/2025 13:28, Leon Michalak via GitGitGadget wrote:
> > From: Leon Michalak <leonmichalak6@gmail.com>
> >
> > This patch compliments the previous commit, where builtins that use
> > add-patch infrastructure now respect diff.context and
> > diff.interHunkContext file configurations.
> >
> > In particular, this patch helps users who don't want to set persistent
> > context configurations or just want a way to override them on a one-time
> > basis, by allowing the relevant builtins to accept corresponding command
> > line options that override the file configurations.
> >
> > This mimics commands such as diff and log, which allow for both context
> > file configuration and command line overrides.
>
> Thanks for updating the quoting in the tests. Unfortunately this patch
> now deletes the tests added in the last commit which I don't think is
> correct.
>
> >
> > -test_expect_success 'add -p respects diff.context' '
> > -     test_write_lines a b c d e f g h i j k l m >file &&
> I think there is some confusion here - why are we deleting the tests
> added in the last commit? This removes the test coverage for
> diff.context and diff.interHunkContext
>
> > +for cmd in add checkout restore 'commit -m file'
> > +do
> > +     test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" '
> > +             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 &&
> > +             git add file &&
> > +             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 &&
> > +             echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
> > +                     $cmd -p -U 4 --inter-hunk-context 2 >actual &&
> > +             test_grep "@@ -2,20 +2,20 @@" actual
> > +     '
> > +done
> > +
> > +test_expect_success 'reset accepts -U and --inter-hunk-context' '
> > +     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 &&
> > +     git commit -m file file &&
> > +     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 &&
> >       git add file &&
> > -     test_write_lines a b c d e f G h i j k l m >file &&
> > -     echo y | git -c diff.context=5 add -p >actual &&
> > -     test_grep "@@ -2,11 +2,11 @@" actual
> > +     echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
> > +             reset -p -U 4 --inter-hunk-context 2 >actual &&
> > +     test_grep "@@ -2,20 +2,20 @@" actual
> >   '
> >
> > -test_expect_success 'add -p respects diff.interHunkContext' '
> > -     test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
> > -     git add file &&
> > -     test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
> > -     echo y | git -c diff.interhunkcontext=2 add -p >actual &&
> > -     test_grep "@@ -2,16 +2,16 @@" actual
>
> This is also deleting a test added in the last patch
>
> > +test_expect_success 'stash accepts -U and --inter-hunk-context' '
> > +     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 &&
> > +     git commit -m file file &&
> > +     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 &&
> > +     echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
> > +             stash -p -U 4 --inter-hunk-context 2 >actual &&
> > +     test_grep "@@ -2,20 +2,20 @@" actual
> >   '
> >
> > -test_expect_success 'add -p rejects negative diff.context' '
> > -     test_config diff.context -1 &&
> > -     test_must_fail git add -p 2>output &&
> > -     test_grep "diff.context cannot be negative" output
> > -'
>
> and so is this. The tests you're adding look good but we shouldn't be
> deleting the existing ones.
>
> > +for cmd in add checkout commit reset restore "stash save" "stash push"
> > +do
> > +     test_expect_success "$cmd rejects invalid context options" '
> > +             test_must_fail git $cmd -p -U -3 2>actual &&
> > +             cat actual | echo &&
> > +             test_grep -e ".--unified. cannot be negative" actual &&
> > +
> > +             test_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&
> > +             test_grep -e ".--inter-hunk-context. cannot be negative" actual &&
> > +
> > +             test_must_fail git $cmd -U 7 2>actual &&
> > +             test_grep -E ".--unified. requires .(--interactive/)?--patch." actual &&
> > +
> > +             test_must_fail git $cmd --inter-hunk-context 2 2>actual &&
> > +             test_grep -E ".--inter-hunk-context. requires .(--interactive/)?--patch." actual
> > +     '
> > +done
>
> This looks good as well
> >   test_done
> As I said last time I do not think the tests below add any value. They
> also do not compensate for the removal of the tests for diff.context
> that are deleted above as they all pass -U on the commandline.
>
> > diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
> > index 1384a8195705..0158fe6568cb 100755
> > --- a/t/t4055-diff-context.sh
> > +++ b/t/t4055-diff-context.sh
> > @@ -58,6 +58,36 @@ test_expect_success 'The -U option overrides diff.context' '
> >       test_grep ! "^ firstline" output
> >   '
> >
> > +test_expect_success 'The -U option overrides diff.context for "add"' '
> > +     test_config diff.context 8 &&
> > +     git add -U4 -p >output &&
> > +     test_grep ! "^ firstline" output
> > +'
> > +
> > +test_expect_success 'The -U option overrides diff.context for "commit"' '
> > +     test_config diff.context 8 &&
> > +     ! git commit -U4 -p >output &&
> > +     test_grep ! "^ firstline" output
> > +'
> > +
> > +test_expect_success 'The -U option overrides diff.context for "checkout"' '
> > +     test_config diff.context 8 &&
> > +     git checkout -U4 -p >output &&
> > +     test_grep ! "^ firstline" output
> > +'
> > +
> > +test_expect_success 'The -U option overrides diff.context for "stash"' '
> > +     test_config diff.context 8 &&
> > +     ! git stash -U4 -p >output &&
> > +     test_grep ! "^ firstline" output
> > +'
> > +
> > +test_expect_success 'The -U option overrides diff.context for "restore"' '
> > +     test_config diff.context 8 &&
> > +     git restore -U4 -p >output &&
> > +     test_grep ! "^ firstline" output
> > +'
> > +
> >   test_expect_success 'diff.context honored by "diff"' '
> >       test_config diff.context 8 &&
> >       git diff >output &&
>
> Thanks
>
> Phillip
>

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v4 4/4] add-patch: add diff.context command line overrides
  2025-07-22 18:02           ` Leon Michalak
@ 2025-07-22 18:05             ` Leon Michalak
  2025-07-23  9:41             ` Phillip Wood
  1 sibling, 0 replies; 77+ messages in thread
From: Leon Michalak @ 2025-07-22 18:05 UTC (permalink / raw)
  To: phillip.wood
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder

Apologies for the top-posting above, this is not my regular workflow!

^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v4 4/4] add-patch: add diff.context command line overrides
  2025-07-22 18:02           ` Leon Michalak
  2025-07-22 18:05             ` Leon Michalak
@ 2025-07-23  9:41             ` Phillip Wood
  1 sibling, 0 replies; 77+ messages in thread
From: Phillip Wood @ 2025-07-23  9:41 UTC (permalink / raw)
  To: leonmichalak6
  Cc: christian.couder, git, gitgitgadget, kristofferhaugsbakk,
	phillip.wood, sunshine

Hi Leon

Here is a fixup commit for the tests which can be squashed into patch 4

Best Wishes

Phillip

---- 8< ----
From: Phillip Wood <phillip.wood@dunelm.org.uk>
Subject: [PATCH] fixup! add-patch: add diff.context command line overrides

Restore the test coverage for diff.context and diff.interHunkContext
added in f08d4ae6e56 (add-patch: respect diff.context configuration,
2025-07-19) and remove the redunant tests in t4055 added by bd6d6ba1321
(add-patch: add diff.context command line overrides, 2025-07-19) which
duplicate the coverage of the tests added to t3071 in the same commit.

Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---
 t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++
 t/t4055-diff-context.sh    | 30 ------------------------------
 2 files changed, 22 insertions(+), 30 deletions(-)

diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 7fd9a23c998..04d2a198352 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -1230,6 +1230,28 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
 	test_cmp expect actual
 '
 
+test_expect_success 'add -p respects diff.context' '
+	test_write_lines a b c d e f g h i j k l m >file &&
+	git add file &&
+	test_write_lines a b c d e f G h i j k l m >file &&
+	echo y | git -c diff.context=5 add -p >actual &&
+	test_grep "@@ -2,11 +2,11 @@" actual
+'
+
+test_expect_success 'add -p respects diff.interHunkContext' '
+	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
+	git add file &&
+	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
+	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
+	test_grep "@@ -2,16 +2,16 @@" actual
+'
+
+test_expect_success 'add -p rejects negative diff.context' '
+	test_config diff.context -1 &&
+	test_must_fail git add -p 2>output &&
+	test_grep "diff.context cannot be negative" output
+'
+
 for cmd in add checkout restore 'commit -m file'
 do
 	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" '
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index 0158fe6568c..1384a819570 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -58,36 +58,6 @@ test_expect_success 'The -U option overrides diff.context' '
 	test_grep ! "^ firstline" output
 '
 
-test_expect_success 'The -U option overrides diff.context for "add"' '
-	test_config diff.context 8 &&
-	git add -U4 -p >output &&
-	test_grep ! "^ firstline" output
-'
-
-test_expect_success 'The -U option overrides diff.context for "commit"' '
-	test_config diff.context 8 &&
-	! git commit -U4 -p >output &&
-	test_grep ! "^ firstline" output
-'
-
-test_expect_success 'The -U option overrides diff.context for "checkout"' '
-	test_config diff.context 8 &&
-	git checkout -U4 -p >output &&
-	test_grep ! "^ firstline" output
-'
-
-test_expect_success 'The -U option overrides diff.context for "stash"' '
-	test_config diff.context 8 &&
-	! git stash -U4 -p >output &&
-	test_grep ! "^ firstline" output
-'
-
-test_expect_success 'The -U option overrides diff.context for "restore"' '
-	test_config diff.context 8 &&
-	git restore -U4 -p >output &&
-	test_grep ! "^ firstline" output
-'
-
 test_expect_success 'diff.context honored by "diff"' '
 	test_config diff.context 8 &&
 	git diff >output &&
-- 
2.49.0.897.gfad3eb7d210


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v5 0/4] Better support for customising context lines in --patch commands
  2025-07-19 12:28     ` [PATCH v4 " Leon Michalak via GitGitGadget
                         ` (4 preceding siblings ...)
  2025-07-21 16:50       ` [PATCH v4 0/4] Better support for customising context lines in --patch commands Junio C Hamano
@ 2025-07-29  7:01       ` Leon Michalak via GitGitGadget
  2025-07-29  7:01         ` [PATCH v5 1/4] t: use test_grep in t3701 and t4055 Leon Michalak via GitGitGadget
                           ` (4 more replies)
  5 siblings, 5 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-07-29  7:01 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak

This series of patches attempt to give --interactive/--patch compatible
builtins ("add", "commit", "checkout", "reset", "restore" and "stash")
better support and nicer experience for configuring how many context lines
are shown in diffs through a variety of ways.

Prior to these patches, the user could not choose how many context lines
they saw in --patch commands (apart from one workaround by using
GIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a
persistent solution). Additionally, the behaviour around reading from the
diff.context and diff.interHunkContext configs was also inconsistent with
other diff generating commands such as "log -p".

The summarised changes below hopefully make this experience better and fix
some inconsistencies:

 * diff.context and diff.interHunkContext configs are now respected by
   --patch compatible commands
 * --unified and --inter-hunk-context command line options have been added
   to --patch compatible commands (which take prescendence over file
   configs)
 * "add" and "commit" in --interactive mode now expose a new "context"
   subcommand which configures the amount of context lines you wish to see
   in subsequent diffs generated from other subcommands such as "patch" or
   "diff"

The original discussion for this can be read at:

 * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/

Changes since v1:

 * Update commit descriptions
 * Update tests to use the more modern and robust test_grep and test_config
   utils
 * Reword some documentation / user messages
 * Ensure each commit is atomic and builds/passes tests on it's own
 * Make new command line options DRY
 * Add tests for interhunk context interaction
 * Error if context config/command line options are negative
 * Drop previous last commit to do with new subcommand for --interactive
   add/commit. My motivations behind this patch series originally where
   quite simple, just for add-patch commands to respect context configs.
   This subcommand, after the discussion in v1, will require more thought
   and a larger implementation that what I had anticipated. I would prefer
   to leave this for another time as it's the least impactful but the most
   time intensive and complicated idea.

Changes since v2:

 * Update tests to only test single command (following Philip's suggestion)
 * Add negative option checks
 * Minor commit re-wording

Changes since v3:

 * Update commit descriptions
 * Read struct properties directly instead of assigning to variables first
 * Simplify config setting / error checking
 * Remove redundant tests in later commit as they were replaced with better
   test(s)
 * Change tests to use single quotes (this messes with the grep so was
   unable to explicitly test single quotes in the error messages, so decided
   to use regex . instead, which is what some other tests that have this
   problem seem to use as well)

Changes since v4:

 * Add back tests to maintain good coverage and remove redundant tests

Leon Michalak (4):
  t: use test_grep in t3701 and t4055
  t: use test_config in t4055
  add-patch: respect diff.context configuration
  add-patch: add diff.context command line overrides

 Documentation/diff-context-options.adoc |  10 ++
 Documentation/git-add.adoc              |   2 +
 Documentation/git-checkout.adoc         |   2 +
 Documentation/git-commit.adoc           |   2 +
 Documentation/git-reset.adoc            |   2 +
 Documentation/git-restore.adoc          |   2 +
 Documentation/git-stash.adoc            |   2 +
 add-interactive.c                       |  45 +++++++--
 add-interactive.h                       |  17 +++-
 add-patch.c                             |  14 ++-
 builtin/add.c                           |  21 ++++-
 builtin/checkout.c                      |  31 +++++-
 builtin/commit.c                        |  16 +++-
 builtin/reset.c                         |  17 +++-
 builtin/stash.c                         |  56 ++++++++---
 commit.h                                |   3 +-
 parse-options.h                         |   2 +
 t/t3701-add-interactive.sh              | 119 +++++++++++++++++++-----
 t/t4055-diff-context.sh                 |  42 ++++-----
 t/t9902-completion.sh                   |   2 +
 20 files changed, 324 insertions(+), 83 deletions(-)
 create mode 100644 Documentation/diff-context-options.adoc


base-commit: cf6f63ea6bf35173e02e18bdc6a4ba41288acff9
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v5
Pull-Request: https://github.com/gitgitgadget/git/pull/1915

Range-diff vs v4:

 1:  bbb2bc7082b = 1:  bbb2bc7082b t: use test_grep in t3701 and t4055
 2:  feace2d3676 = 2:  feace2d3676 t: use test_config in t4055
 3:  994029d6602 = 3:  994029d6602 add-patch: respect diff.context configuration
 4:  2774b930406 ! 4:  9731e5b76fb add-patch: add diff.context command line overrides
     @@ parse-options.h: int parse_opt_tracking_mode(const struct option *, const char *
       	OPT_SET_INT_F('4', "ipv4", (v), N_("use IPv4 addresses only"), \
      
       ## t/t3701-add-interactive.sh ##
     -@@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
     - 	test_cmp expect actual
     +@@ t/t3701-add-interactive.sh: test_expect_success 'add -p rejects negative diff.context' '
     + 	test_grep "diff.context cannot be negative" output
       '
       
     --test_expect_success 'add -p respects diff.context' '
     --	test_write_lines a b c d e f g h i j k l m >file &&
      +for cmd in add checkout restore 'commit -m file'
      +do
      +	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" '
     @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.
      +	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 &&
      +	git commit -m file file &&
      +	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 &&
     - 	git add file &&
     --	test_write_lines a b c d e f G h i j k l m >file &&
     --	echo y | git -c diff.context=5 add -p >actual &&
     --	test_grep "@@ -2,11 +2,11 @@" actual
     ++	git add file &&
      +	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
      +		reset -p -U 4 --inter-hunk-context 2 >actual &&
      +	test_grep "@@ -2,20 +2,20 @@" actual
     - '
     - 
     --test_expect_success 'add -p respects diff.interHunkContext' '
     --	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
     --	git add file &&
     --	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
     --	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
     --	test_grep "@@ -2,16 +2,16 @@" actual
     ++'
     ++
      +test_expect_success 'stash accepts -U and --inter-hunk-context' '
      +	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 &&
      +	git commit -m file file &&
     @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.
      +	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
      +		stash -p -U 4 --inter-hunk-context 2 >actual &&
      +	test_grep "@@ -2,20 +2,20 @@" actual
     - '
     - 
     --test_expect_success 'add -p rejects negative diff.context' '
     --	test_config diff.context -1 &&
     --	test_must_fail git add -p 2>output &&
     --	test_grep "diff.context cannot be negative" output
     --'
     ++'
     ++
      +for cmd in add checkout commit reset restore "stash save" "stash push"
      +do
      +	test_expect_success "$cmd rejects invalid context options" '
     @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.
      +		test_grep -E ".--inter-hunk-context. requires .(--interactive/)?--patch." actual
      +	'
      +done
     - 
     - test_done
     -
     - ## t/t4055-diff-context.sh ##
     -@@ t/t4055-diff-context.sh: test_expect_success 'The -U option overrides diff.context' '
     - 	test_grep ! "^ firstline" output
     - '
     - 
     -+test_expect_success 'The -U option overrides diff.context for "add"' '
     -+	test_config diff.context 8 &&
     -+	git add -U4 -p >output &&
     -+	test_grep ! "^ firstline" output
     -+'
     -+
     -+test_expect_success 'The -U option overrides diff.context for "commit"' '
     -+	test_config diff.context 8 &&
     -+	! git commit -U4 -p >output &&
     -+	test_grep ! "^ firstline" output
     -+'
     -+
     -+test_expect_success 'The -U option overrides diff.context for "checkout"' '
     -+	test_config diff.context 8 &&
     -+	git checkout -U4 -p >output &&
     -+	test_grep ! "^ firstline" output
     -+'
      +
     -+test_expect_success 'The -U option overrides diff.context for "stash"' '
     -+	test_config diff.context 8 &&
     -+	! git stash -U4 -p >output &&
     -+	test_grep ! "^ firstline" output
     -+'
     -+
     -+test_expect_success 'The -U option overrides diff.context for "restore"' '
     -+	test_config diff.context 8 &&
     -+	git restore -U4 -p >output &&
     -+	test_grep ! "^ firstline" output
     -+'
     -+
     - test_expect_success 'diff.context honored by "diff"' '
     - 	test_config diff.context 8 &&
     - 	git diff >output &&
     + test_done
      
       ## t/t9902-completion.sh ##
      @@ t/t9902-completion.sh: test_expect_success 'double dash "git checkout"' '

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 77+ messages in thread

* [PATCH v5 1/4] t: use test_grep in t3701 and t4055
  2025-07-29  7:01       ` [PATCH v5 " Leon Michalak via GitGitGadget
@ 2025-07-29  7:01         ` Leon Michalak via GitGitGadget
  2025-07-29  7:01         ` [PATCH v5 2/4] t: use test_config in t4055 Leon Michalak via GitGitGadget
                           ` (3 subsequent siblings)
  4 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-07-29  7:01 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

As a preparatory clean-up, use the "test_grep" test utility instead of
regular "grep" which provides better debug information if tests fail.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 t/t3701-add-interactive.sh | 48 +++++++++++++++++++-------------------
 t/t4055-diff-context.sh    | 28 +++++++++++-----------
 2 files changed, 38 insertions(+), 38 deletions(-)

diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index b8a05d95f3f1..b088ee141ff4 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -63,7 +63,7 @@ test_expect_success 'setup (initial)' '
 '
 test_expect_success 'status works (initial)' '
 	git add -i </dev/null >output &&
-	grep "+1/-0 *+2/-0 file" output
+	test_grep "+1/-0 *+2/-0 file" output
 '
 
 test_expect_success 'setup expected' '
@@ -86,7 +86,7 @@ test_expect_success 'revert works (initial)' '
 	git add file &&
 	test_write_lines r 1 | git add -i &&
 	git ls-files >output &&
-	! grep . output
+	test_grep ! . output
 '
 
 test_expect_success 'add untracked (multiple)' '
@@ -109,7 +109,7 @@ test_expect_success 'setup (commit)' '
 '
 test_expect_success 'status works (commit)' '
 	git add -i </dev/null >output &&
-	grep "+1/-0 *+2/-0 file" output
+	test_grep "+1/-0 *+2/-0 file" output
 '
 
 test_expect_success 'update can stage deletions' '
@@ -141,7 +141,7 @@ test_expect_success 'revert works (commit)' '
 	git add file &&
 	test_write_lines r 1 | git add -i &&
 	git add -i </dev/null >output &&
-	grep "unchanged *+3/-0 file" output
+	test_grep "unchanged *+3/-0 file" output
 '
 
 test_expect_success 'reject multi-key input' '
@@ -185,7 +185,7 @@ test_expect_success 'setup fake editor' '
 test_expect_success 'bad edit rejected' '
 	git reset &&
 	test_write_lines e n d | git add -p >output &&
-	grep "hunk does not apply" output
+	test_grep "hunk does not apply" output
 '
 
 test_expect_success 'setup patch' '
@@ -198,7 +198,7 @@ test_expect_success 'setup patch' '
 test_expect_success 'garbage edit rejected' '
 	git reset &&
 	test_write_lines e n d | git add -p >output &&
-	grep "hunk does not apply" output
+	test_grep "hunk does not apply" output
 '
 
 test_expect_success 'setup patch' '
@@ -313,8 +313,8 @@ test_expect_success FILEMODE 'stage mode and hunk' '
 	chmod +x file &&
 	printf "y\\ny\\n" | git add -p &&
 	git diff --cached file >out &&
-	grep "new mode" out &&
-	grep "+content" out &&
+	test_grep "new mode" out &&
+	test_grep "+content" out &&
 	git diff file >out &&
 	test_must_be_empty out
 '
@@ -636,7 +636,7 @@ test_expect_success 'split hunk "add -p (edit)"' '
 	printf "%s\n" s e     q n q q |
 	EDITOR=: git add -p &&
 	git diff >actual &&
-	! grep "^+15" actual
+	test_grep ! "^+15" actual
 '
 
 test_expect_success 'split hunk "add -p (no, yes, edit)"' '
@@ -648,7 +648,7 @@ test_expect_success 'split hunk "add -p (no, yes, edit)"' '
 	EDITOR=: git add -p 2>error &&
 	test_must_be_empty error &&
 	git diff >actual &&
-	! grep "^+31" actual
+	test_grep ! "^+31" actual
 '
 
 test_expect_success 'split hunk with incomplete line at end' '
@@ -682,7 +682,7 @@ test_expect_success 'edit, adding lines to the first hunk' '
 	EDITOR=./fake_editor.sh git add -p 2>error &&
 	test_must_be_empty error &&
 	git diff --cached >actual &&
-	grep "^+22" actual
+	test_grep "^+22" actual
 '
 
 test_expect_success 'patch mode ignores unmerged entries' '
@@ -696,7 +696,7 @@ test_expect_success 'patch mode ignores unmerged entries' '
 	test_must_fail git merge side &&
 	echo changed >non-conflict.t &&
 	echo y | git add -p >output &&
-	! grep a/conflict.t output &&
+	test_grep ! a/conflict.t output &&
 	cat >expected <<-\EOF &&
 	* Unmerged path conflict.t
 	diff --git a/non-conflict.t b/non-conflict.t
@@ -728,7 +728,7 @@ test_expect_success 'diffs can be colorized' '
 
 	# We do not want to depend on the exact coloring scheme
 	# git uses for diffs, so just check that we saw some kind of color.
-	grep "$(printf "\\033")" output
+	test_grep "$(printf "\\033")" output
 '
 
 test_expect_success 'colors can be overridden' '
@@ -743,7 +743,7 @@ test_expect_success 'colors can be overridden' '
 		-c color.interactive.error=blue \
 		add -i 2>err.raw <input &&
 	test_decode_color <err.raw >err &&
-	grep "<BLUE>Huh (trigger)?<RESET>" err &&
+	test_grep "<BLUE>Huh (trigger)?<RESET>" err &&
 
 	test_write_lines help quit >input &&
 	force_color git \
@@ -863,7 +863,7 @@ test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '
 	printf y >y &&
 	force_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&
 	test_decode_color <output.raw >output &&
-	grep "old<" output
+	test_grep "old<" output
 '
 
 test_expect_success 'diffFilter filters diff' '
@@ -876,7 +876,7 @@ test_expect_success 'diffFilter filters diff' '
 
 	# avoid depending on the exact coloring or content of the prompts,
 	# and just make sure we saw our diff prefixed
-	grep foo:.*content output
+	test_grep foo:.*content output
 '
 
 test_expect_success 'detect bogus diffFilter output' '
@@ -886,7 +886,7 @@ test_expect_success 'detect bogus diffFilter output' '
 	test_config interactive.diffFilter "sed 6d" &&
 	printf y >y &&
 	force_color test_must_fail git add -p <y >output 2>&1 &&
-	grep "mismatched output" output
+	test_grep "mismatched output" output
 '
 
 test_expect_success 'handle iffy colored hunk headers' '
@@ -896,7 +896,7 @@ test_expect_success 'handle iffy colored hunk headers' '
 	printf n >n &&
 	force_color git -c interactive.diffFilter="sed s/.*@@.*/XX/" \
 		add -p >output 2>&1 <n &&
-	grep "^XX$" output
+	test_grep "^XX$" output
 '
 
 test_expect_success 'handle very large filtered diff' '
@@ -1002,7 +1002,7 @@ test_expect_success 'add -p does not expand argument lists' '
 	# update it, but we want to be sure that our "." pathspec
 	# was not expanded into the argument list of any command.
 	# So look only for "not-changed".
-	! grep -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
+	test_grep ! -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
 '
 
 test_expect_success 'hunk-editing handles custom comment char' '
@@ -1072,21 +1072,21 @@ test_expect_success 'setup different kinds of dirty submodules' '
 
 test_expect_success 'status ignores dirty submodules (except HEAD)' '
 	git -C for-submodules add -i </dev/null >output &&
-	grep dirty-head output &&
-	grep dirty-both-ways output &&
-	! grep dirty-otherwise output
+	test_grep dirty-head output &&
+	test_grep dirty-both-ways output &&
+	test_grep ! dirty-otherwise output
 '
 
 test_expect_success 'handle submodules' '
 	echo 123 >>for-submodules/dirty-otherwise/initial.t &&
 
 	force_color git -C for-submodules add -p dirty-otherwise >output 2>&1 &&
-	grep "No changes" output &&
+	test_grep "No changes" output &&
 
 	force_color git -C for-submodules add -p dirty-head >output 2>&1 <y &&
 	git -C for-submodules ls-files --stage dirty-head >actual &&
 	rev="$(git -C for-submodules/dirty-head rev-parse HEAD)" &&
-	grep "$rev" actual
+	test_grep "$rev" actual
 '
 
 test_expect_success 'set up pathological context' '
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index ec2804eea67c..c66f966a3ab3 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -38,36 +38,36 @@ test_expect_success 'setup' '
 
 test_expect_success 'the default number of context lines is 3' '
 	git diff >output &&
-	! grep "^ d" output &&
-	grep "^ e" output &&
-	grep "^ j" output &&
-	! grep "^ k" output
+	test_grep ! "^ d" output &&
+	test_grep "^ e" output &&
+	test_grep "^ j" output &&
+	test_grep ! "^ k" output
 '
 
 test_expect_success 'diff.context honored by "log"' '
 	git log -1 -p >output &&
-	! grep firstline output &&
+	test_grep ! firstline output &&
 	git config diff.context 8 &&
 	git log -1 -p >output &&
-	grep "^ firstline" output
+	test_grep "^ firstline" output
 '
 
 test_expect_success 'The -U option overrides diff.context' '
 	git config diff.context 8 &&
 	git log -U4 -1 >output &&
-	! grep "^ firstline" output
+	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'diff.context honored by "diff"' '
 	git config diff.context 8 &&
 	git diff >output &&
-	grep "^ firstline" output
+	test_grep "^ firstline" output
 '
 
 test_expect_success 'plumbing not affected' '
 	git config diff.context 8 &&
 	git diff-files -p >output &&
-	! grep "^ firstline" output
+	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'non-integer config parsing' '
@@ -85,8 +85,8 @@ test_expect_success 'negative integer config parsing' '
 test_expect_success '-U0 is valid, so is diff.context=0' '
 	git config diff.context 0 &&
 	git diff >output &&
-	grep "^-ADDED" output &&
-	grep "^+MODIFIED" output
+	test_grep "^-ADDED" output &&
+	test_grep "^+MODIFIED" output
 '
 
 test_expect_success '-U2147483647 works' '
@@ -94,9 +94,9 @@ test_expect_success '-U2147483647 works' '
 	test_line_count = 16 x &&
 	git diff -U2147483647 >output &&
 	test_line_count = 22 output &&
-	grep "^-ADDED" output &&
-	grep "^+MODIFIED" output &&
-	grep "^+APPENDED" output
+	test_grep "^-ADDED" output &&
+	test_grep "^+MODIFIED" output &&
+	test_grep "^+APPENDED" output
 '
 
 test_done
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v5 2/4] t: use test_config in t4055
  2025-07-29  7:01       ` [PATCH v5 " Leon Michalak via GitGitGadget
  2025-07-29  7:01         ` [PATCH v5 1/4] t: use test_grep in t3701 and t4055 Leon Michalak via GitGitGadget
@ 2025-07-29  7:01         ` Leon Michalak via GitGitGadget
  2025-07-29  7:01         ` [PATCH v5 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
                           ` (2 subsequent siblings)
  4 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-07-29  7:01 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

Use the modern "test_config" test utility instead of manual"git config"
as the former provides clean up on test completion.

This is a prerequisite to the commits that follow which add to this test
file.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 t/t4055-diff-context.sh | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index c66f966a3ab3..1384a8195705 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -47,43 +47,43 @@ test_expect_success 'the default number of context lines is 3' '
 test_expect_success 'diff.context honored by "log"' '
 	git log -1 -p >output &&
 	test_grep ! firstline output &&
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git log -1 -p >output &&
 	test_grep "^ firstline" output
 '
 
 test_expect_success 'The -U option overrides diff.context' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git log -U4 -1 >output &&
 	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'diff.context honored by "diff"' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git diff >output &&
 	test_grep "^ firstline" output
 '
 
 test_expect_success 'plumbing not affected' '
-	git config diff.context 8 &&
+	test_config diff.context 8 &&
 	git diff-files -p >output &&
 	test_grep ! "^ firstline" output
 '
 
 test_expect_success 'non-integer config parsing' '
-	git config diff.context no &&
+	test_config diff.context no &&
 	test_must_fail git diff 2>output &&
 	test_grep "bad numeric config value" output
 '
 
 test_expect_success 'negative integer config parsing' '
-	git config diff.context -1 &&
+	test_config diff.context -1 &&
 	test_must_fail git diff 2>output &&
 	test_grep "bad config variable" output
 '
 
 test_expect_success '-U0 is valid, so is diff.context=0' '
-	git config diff.context 0 &&
+	test_config diff.context 0 &&
 	git diff >output &&
 	test_grep "^-ADDED" output &&
 	test_grep "^+MODIFIED" output
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v5 3/4] add-patch: respect diff.context configuration
  2025-07-29  7:01       ` [PATCH v5 " Leon Michalak via GitGitGadget
  2025-07-29  7:01         ` [PATCH v5 1/4] t: use test_grep in t3701 and t4055 Leon Michalak via GitGitGadget
  2025-07-29  7:01         ` [PATCH v5 2/4] t: use test_config in t4055 Leon Michalak via GitGitGadget
@ 2025-07-29  7:01         ` Leon Michalak via GitGitGadget
  2025-07-29  7:01         ` [PATCH v5 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
  2025-07-29 15:21         ` [PATCH v5 0/4] Better support for customising context lines in --patch commands Phillip Wood
  4 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-07-29  7:01 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

Various builtins that use add-patch infrastructure do not respect
the user's diff.context and diff.interHunkContext file configurations.

The user may be used to seeing their diffs with customized context size,
but not in the patches "git add -p" shows them to pick from.

Teach add-patch infrastructure to read these configuration variables and
pass their values when spawning the underlying plumbing commands as
their command line option.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 add-interactive.c          |  9 +++++++++
 add-interactive.h          |  1 +
 add-patch.c                |  9 ++++++---
 t/t3701-add-interactive.sh | 22 ++++++++++++++++++++++
 4 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/add-interactive.c b/add-interactive.c
index 97ff35b6f12a..eb3d0d3ada84 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -41,6 +41,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	const char *value;
 
 	s->r = r;
+	s->context = -1;
+	s->interhunkcontext = -1;
 
 	if (repo_config_get_value(r, "color.interactive", &value))
 		s->use_color = -1;
@@ -78,6 +80,13 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	repo_config_get_string(r, "diff.algorithm",
 			       &s->interactive_diff_algorithm);
 
+	if (!repo_config_get_int(r, "diff.context", &s->context))
+		if (s->context < 0)
+			die(_("%s cannot be negative"), "diff.context");
+	if (!repo_config_get_int(r, "diff.interHunkContext", &s->interhunkcontext))
+		if (s->interhunkcontext < 0)
+			die(_("%s cannot be negative"), "diff.interHunkContext");
+
 	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
 	if (s->use_single_key)
 		setbuf(stdin, NULL);
diff --git a/add-interactive.h b/add-interactive.h
index 693f125e8e4b..c63f35b14be8 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -18,6 +18,7 @@ struct add_i_state {
 
 	int use_single_key;
 	char *interactive_diff_filter, *interactive_diff_algorithm;
+	int context, interhunkcontext;
 };
 
 void init_add_i_state(struct add_i_state *s, struct repository *r);
diff --git a/add-patch.c b/add-patch.c
index 95c67d8c80c4..b0125b51ba45 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -414,7 +414,6 @@ static int normalize_marker(const char *p)
 static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 {
 	struct strvec args = STRVEC_INIT;
-	const char *diff_algorithm = s->s.interactive_diff_algorithm;
 	struct strbuf *plain = &s->plain, *colored = NULL;
 	struct child_process cp = CHILD_PROCESS_INIT;
 	char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
@@ -424,8 +423,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 	int res;
 
 	strvec_pushv(&args, s->mode->diff_cmd);
-	if (diff_algorithm)
-		strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
+	if (s->s.context != -1)
+		strvec_pushf(&args, "--unified=%i", s->s.context);
+	if (s->s.interhunkcontext != -1)
+		strvec_pushf(&args, "--inter-hunk-context=%i", s->s.interhunkcontext);
+	if (s->s.interactive_diff_algorithm)
+		strvec_pushf(&args, "--diff-algorithm=%s", s->s.interactive_diff_algorithm);
 	if (s->revision) {
 		struct object_id oid;
 		strvec_push(&args,
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index b088ee141ff4..18dc329ea1f6 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -1230,4 +1230,26 @@ test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
 	test_cmp expect actual
 '
 
+test_expect_success 'add -p respects diff.context' '
+	test_write_lines a b c d e f g h i j k l m >file &&
+	git add file &&
+	test_write_lines a b c d e f G h i j k l m >file &&
+	echo y | git -c diff.context=5 add -p >actual &&
+	test_grep "@@ -2,11 +2,11 @@" actual
+'
+
+test_expect_success 'add -p respects diff.interHunkContext' '
+	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
+	git add file &&
+	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
+	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
+	test_grep "@@ -2,16 +2,16 @@" actual
+'
+
+test_expect_success 'add -p rejects negative diff.context' '
+	test_config diff.context -1 &&
+	test_must_fail git add -p 2>output &&
+	test_grep "diff.context cannot be negative" output
+'
+
 test_done
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 77+ messages in thread

* [PATCH v5 4/4] add-patch: add diff.context command line overrides
  2025-07-29  7:01       ` [PATCH v5 " Leon Michalak via GitGitGadget
                           ` (2 preceding siblings ...)
  2025-07-29  7:01         ` [PATCH v5 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
@ 2025-07-29  7:01         ` Leon Michalak via GitGitGadget
  2025-07-29 15:21         ` [PATCH v5 0/4] Better support for customising context lines in --patch commands Phillip Wood
  4 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak via GitGitGadget @ 2025-07-29  7:01 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Phillip Wood, Leon Michalak, Leon Michalak

From: Leon Michalak <leonmichalak6@gmail.com>

This patch compliments the previous commit, where builtins that use
add-patch infrastructure now respect diff.context and
diff.interHunkContext file configurations.

In particular, this patch helps users who don't want to set persistent
context configurations or just want a way to override them on a one-time
basis, by allowing the relevant builtins to accept corresponding command
line options that override the file configurations.

This mimics commands such as diff and log, which allow for both context
file configuration and command line overrides.

Signed-off-by: Leon Michalak <leonmichalak6@gmail.com>
---
 Documentation/diff-context-options.adoc | 10 +++++
 Documentation/git-add.adoc              |  2 +
 Documentation/git-checkout.adoc         |  2 +
 Documentation/git-commit.adoc           |  2 +
 Documentation/git-reset.adoc            |  2 +
 Documentation/git-restore.adoc          |  2 +
 Documentation/git-stash.adoc            |  2 +
 add-interactive.c                       | 36 ++++++++++++----
 add-interactive.h                       | 16 +++++--
 add-patch.c                             |  5 ++-
 builtin/add.c                           | 21 ++++++++--
 builtin/checkout.c                      | 31 ++++++++++++--
 builtin/commit.c                        | 16 ++++++-
 builtin/reset.c                         | 17 +++++++-
 builtin/stash.c                         | 56 ++++++++++++++++++++-----
 commit.h                                |  3 +-
 parse-options.h                         |  2 +
 t/t3701-add-interactive.sh              | 49 ++++++++++++++++++++++
 t/t9902-completion.sh                   |  2 +
 19 files changed, 241 insertions(+), 35 deletions(-)
 create mode 100644 Documentation/diff-context-options.adoc

diff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc
new file mode 100644
index 000000000000..e161260358ff
--- /dev/null
+++ b/Documentation/diff-context-options.adoc
@@ -0,0 +1,10 @@
+`-U<n>`::
+`--unified=<n>`::
+	Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
+	or 3 if the config option is unset.
+
+`--inter-hunk-context=<n>`::
+	Show the context between diff hunks, up to the specified _<number>_
+	of lines, thereby fusing hunks that are close to each other.
+	Defaults to `diff.interHunkContext` or 0 if the config option
+	is unset.
diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index eba0b419ce50..b7a735824d6c 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -104,6 +104,8 @@ This effectively runs `add --interactive`, but bypasses the
 initial command menu and directly jumps to the `patch` subcommand.
 See ``Interactive mode'' for details.
 
+include::diff-context-options.adoc[]
+
 `-e`::
 `--edit`::
 	Open the diff vs. the index in an editor and let the user
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index ee83b6d9ba9a..40e02cfd6562 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -289,6 +289,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 Note that this option uses the no overlay mode by default (see also
 `--overlay`), and currently doesn't support overlay mode.
 
+include::diff-context-options.adoc[]
+
 `--ignore-other-worktrees`::
 	`git checkout` refuses when the wanted branch is already checked
 	out or otherwise in use by another worktree. This option makes
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc
index dc219025f1eb..ae988a883b5b 100644
--- a/Documentation/git-commit.adoc
+++ b/Documentation/git-commit.adoc
@@ -76,6 +76,8 @@ OPTIONS
 	which changes to commit. See linkgit:git-add[1] for
 	details.
 
+include::diff-context-options.adoc[]
+
 `-C <commit>`::
 `--reuse-message=<commit>`::
 	Take an existing _<commit>_ object, and reuse the log message
diff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc
index 53ab88c5451c..50e8a0ba6f66 100644
--- a/Documentation/git-reset.adoc
+++ b/Documentation/git-reset.adoc
@@ -125,6 +125,8 @@ OPTIONS
 	separated with _NUL_ character and all other characters are taken
 	literally (including newlines and quotes).
 
+include::diff-context-options.adoc[]
+
 `--`::
 	Do not interpret any more arguments as options.
 
diff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc
index 877b7772e667..1dcc2bb7aea3 100644
--- a/Documentation/git-restore.adoc
+++ b/Documentation/git-restore.adoc
@@ -52,6 +52,8 @@ leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to
 	Mode" section of linkgit:git-add[1] to learn how to operate
 	the `--patch` mode.
 
+include::diff-context-options.adoc[]
+
 `-W`::
 `--worktree`::
 `-S`::
diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc
index 1a5177f4986c..0578c619c410 100644
--- a/Documentation/git-stash.adoc
+++ b/Documentation/git-stash.adoc
@@ -208,6 +208,8 @@ to learn how to operate the `--patch` mode.
 The `--patch` option implies `--keep-index`.  You can use
 `--no-keep-index` to override this.
 
+include::diff-context-options.adoc[]
+
 -S::
 --staged::
 	This option is only valid for `push` and `save` commands.
diff --git a/add-interactive.c b/add-interactive.c
index eb3d0d3ada84..3e692b47eca0 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -36,7 +36,8 @@ static void init_color(struct repository *r, struct add_i_state *s,
 	free(key);
 }
 
-void init_add_i_state(struct add_i_state *s, struct repository *r)
+void init_add_i_state(struct add_i_state *s, struct repository *r,
+		      struct add_p_opt *add_p_opt)
 {
 	const char *value;
 
@@ -90,6 +91,17 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
 	repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
 	if (s->use_single_key)
 		setbuf(stdin, NULL);
+
+	if (add_p_opt->context != -1) {
+		if (add_p_opt->context < 0)
+			die(_("%s cannot be negative"), "--unified");
+		s->context = add_p_opt->context;
+	}
+	if (add_p_opt->interhunkcontext != -1) {
+		if (add_p_opt->interhunkcontext < 0)
+			die(_("%s cannot be negative"), "--inter-hunk-context");
+		s->interhunkcontext = add_p_opt->interhunkcontext;
+	}
 }
 
 void clear_add_i_state(struct add_i_state *s)
@@ -978,6 +990,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 	opts->prompt = N_("Patch update");
 	count = list_and_choose(s, files, opts);
 	if (count > 0) {
+		struct add_p_opt add_p_opt = {
+			.context = s->context,
+			.interhunkcontext = s->interhunkcontext,
+		};
 		struct strvec args = STRVEC_INIT;
 		struct pathspec ps_selected = { 0 };
 
@@ -988,7 +1004,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 		parse_pathspec(&ps_selected,
 			       PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
 			       PATHSPEC_LITERAL_PATH, "", args.v);
-		res = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);
+		res = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected);
 		strvec_clear(&args);
 		clear_pathspec(&ps_selected);
 	}
@@ -1023,10 +1039,13 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
 	if (count > 0) {
 		struct child_process cmd = CHILD_PROCESS_INIT;
 
-		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached",
-			     oid_to_hex(!is_initial ? &oid :
-					s->r->hash_algo->empty_tree),
-			     "--", NULL);
+		strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL);
+		if (s->context != -1)
+			strvec_pushf(&cmd.args, "--unified=%i", s->context);
+		if (s->interhunkcontext != -1)
+			strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->interhunkcontext);
+		strvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :
+			     s->r->hash_algo->empty_tree), "--", NULL);
 		for (i = 0; i < files->items.nr; i++)
 			if (files->selected[i])
 				strvec_push(&cmd.args,
@@ -1119,7 +1138,8 @@ static void command_prompt_help(struct add_i_state *s)
 			 _("(empty) select nothing"));
 }
 
-int run_add_i(struct repository *r, const struct pathspec *ps)
+int run_add_i(struct repository *r, const struct pathspec *ps,
+	      struct add_p_opt *add_p_opt)
 {
 	struct add_i_state s = { NULL };
 	struct print_command_item_data data = { "[", "]" };
@@ -1162,7 +1182,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
 			->util = util;
 	}
 
-	init_add_i_state(&s, r);
+	init_add_i_state(&s, r, add_p_opt);
 
 	/*
 	 * When color was asked for, use the prompt color for
diff --git a/add-interactive.h b/add-interactive.h
index c63f35b14be8..4213dcd67b9a 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -3,6 +3,13 @@
 
 #include "color.h"
 
+struct add_p_opt {
+	int context;
+	int interhunkcontext;
+};
+
+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }
+
 struct add_i_state {
 	struct repository *r;
 	int use_color;
@@ -21,12 +28,14 @@ struct add_i_state {
 	int context, interhunkcontext;
 };
 
-void init_add_i_state(struct add_i_state *s, struct repository *r);
+void init_add_i_state(struct add_i_state *s, struct repository *r,
+		      struct add_p_opt *add_p_opt);
 void clear_add_i_state(struct add_i_state *s);
 
 struct repository;
 struct pathspec;
-int run_add_i(struct repository *r, const struct pathspec *ps);
+int run_add_i(struct repository *r, const struct pathspec *ps,
+	      struct add_p_opt *add_p_opt);
 
 enum add_p_mode {
 	ADD_P_ADD,
@@ -37,6 +46,7 @@ enum add_p_mode {
 };
 
 int run_add_p(struct repository *r, enum add_p_mode mode,
-	      const char *revision, const struct pathspec *ps);
+	      struct add_p_opt *o, const char *revision,
+	      const struct pathspec *ps);
 
 #endif
diff --git a/add-patch.c b/add-patch.c
index b0125b51ba45..302e6ba7d9a3 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -1763,14 +1763,15 @@ soft_increment:
 }
 
 int run_add_p(struct repository *r, enum add_p_mode mode,
-	      const char *revision, const struct pathspec *ps)
+	      struct add_p_opt *o, const char *revision,
+	      const struct pathspec *ps)
 {
 	struct add_p_state s = {
 		{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 	};
 	size_t i, binary_count = 0;
 
-	init_add_i_state(&s.s, r);
+	init_add_i_state(&s.s, r, o);
 
 	if (mode == ADD_P_STASH)
 		s.mode = &patch_mode_stash;
diff --git a/builtin/add.c b/builtin/add.c
index 7c292ffdc6c2..a00dab9b8fa0 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -29,6 +29,7 @@ static const char * const builtin_add_usage[] = {
 	NULL
 };
 static int patch_interactive, add_interactive, edit_interactive;
+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 static int take_worktree_changes;
 static int add_renormalize;
 static int pathspec_file_nul;
@@ -157,7 +158,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec *
 int interactive_add(struct repository *repo,
 		    const char **argv,
 		    const char *prefix,
-		    int patch)
+		    int patch, struct add_p_opt *add_p_opt)
 {
 	struct pathspec pathspec;
 	int ret;
@@ -169,9 +170,9 @@ int interactive_add(struct repository *repo,
 		       prefix, argv);
 
 	if (patch)
-		ret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec);
+		ret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec);
 	else
-		ret = !!run_add_i(repo, &pathspec);
+		ret = !!run_add_i(repo, &pathspec, add_p_opt);
 
 	clear_pathspec(&pathspec);
 	return ret;
@@ -253,6 +254,8 @@ static struct option builtin_add_options[] = {
 	OPT_GROUP(""),
 	OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
 	OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
+	OPT_DIFF_UNIFIED(&add_p_opt.context),
+	OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 	OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
 	OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
 	OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
@@ -394,6 +397,11 @@ int cmd_add(int argc,
 	prepare_repo_settings(repo);
 	repo->settings.command_requires_full_index = 0;
 
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	if (patch_interactive)
 		add_interactive = 1;
 	if (add_interactive) {
@@ -401,7 +409,12 @@ int cmd_add(int argc,
 			die(_("options '%s' and '%s' cannot be used together"), "--dry-run", "--interactive/--patch");
 		if (pathspec_from_file)
 			die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch");
-		exit(interactive_add(repo, argv + 1, prefix, patch_interactive));
+		exit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt));
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
 	}
 
 	if (edit_interactive) {
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 536192d3456c..3737ba4c3920 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -61,6 +61,8 @@ static const char * const restore_usage[] = {
 
 struct checkout_opts {
 	int patch_mode;
+	int patch_context;
+	int patch_interhunk_context;
 	int quiet;
 	int merge;
 	int force;
@@ -104,7 +106,12 @@ struct checkout_opts {
 	struct tree *source_tree;
 };
 
-#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 }
+#define CHECKOUT_OPTS_INIT { \
+	.conflict_style = -1, \
+	.merge = -1, \
+	.patch_context = -1, \
+	.patch_interhunk_context = -1, \
+}
 
 struct branch_info {
 	char *name; /* The short name used */
@@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts,
 
 	if (opts->patch_mode) {
 		enum add_p_mode patch_mode;
+		struct add_p_opt add_p_opt = {
+			.context = opts->patch_context,
+			.interhunkcontext = opts->patch_interhunk_context,
+		};
 		const char *rev = new_branch_info->name;
 		char rev_oid[GIT_MAX_HEXSZ + 1];
 
@@ -564,8 +575,8 @@ static int checkout_paths(const struct checkout_opts *opts,
 		else
 			BUG("either flag must have been set, worktree=%d, index=%d",
 			    opts->checkout_worktree, opts->checkout_index);
-		return !!run_add_p(the_repository, patch_mode, rev,
-				   &opts->pathspec);
+		return !!run_add_p(the_repository, patch_mode, &add_p_opt,
+				   rev, &opts->pathspec);
 	}
 
 	repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -1738,6 +1749,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
 		OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
+		OPT_DIFF_UNIFIED(&opts->patch_context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context),
 		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),
@@ -1780,6 +1793,18 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 	argc = parse_options(argc, argv, prefix, options,
 			     usagestr, parseopt_flags);
 
+	if (opts->patch_context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (opts->patch_interhunk_context < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
+	if (!opts->patch_mode) {
+		if (opts->patch_context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (opts->patch_interhunk_context != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
diff --git a/builtin/commit.c b/builtin/commit.c
index fba0dded64a7..73673bc7db9f 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -19,6 +19,7 @@
 #include "environment.h"
 #include "diff.h"
 #include "commit.h"
+#include "add-interactive.h"
 #include "gettext.h"
 #include "revision.h"
 #include "wt-status.h"
@@ -122,6 +123,7 @@ static const char *edit_message, *use_message;
 static char *fixup_message, *fixup_commit, *squash_message;
 static const char *fixup_prefix;
 static int all, also, interactive, patch_interactive, only, amend, signoff;
+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
@@ -354,6 +356,11 @@ static const char *prepare_index(const char **argv, const char *prefix,
 	const char *ret;
 	char *path = NULL;
 
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	if (is_status)
 		refresh_flags |= REFRESH_UNMERGED;
 	parse_pathspec(&pathspec, 0,
@@ -400,7 +407,7 @@ static const char *prepare_index(const char **argv, const char *prefix,
 		old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
 		setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
 
-		if (interactive_add(the_repository, argv, prefix, patch_interactive) != 0)
+		if (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0)
 			die(_("interactive add failed"));
 
 		the_repository->index_file = old_repo_index_file;
@@ -424,6 +431,11 @@ static const char *prepare_index(const char **argv, const char *prefix,
 		commit_style = COMMIT_NORMAL;
 		ret = get_lock_file_path(&index_lock);
 		goto out;
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
 	}
 
 	/*
@@ -1722,6 +1734,8 @@ int cmd_commit(int argc,
 		OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")),
 		OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
 		OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT_BOOL('o', "only", &only, N_("commit only specified files")),
 		OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")),
 		OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
diff --git a/builtin/reset.c b/builtin/reset.c
index dc50ffc1ac59..9fb32795c9c5 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -346,6 +346,7 @@ int cmd_reset(int argc,
 	struct object_id oid;
 	struct pathspec pathspec;
 	int intent_to_add = 0;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	const struct option options[] = {
 		OPT__QUIET(&quiet, N_("be quiet, only report errors")),
 		OPT_BOOL(0, "no-refresh", &no_refresh,
@@ -370,6 +371,8 @@ int cmd_reset(int argc,
 			       PARSE_OPT_OPTARG,
 			       option_parse_recurse_submodules_worktree_updater),
 		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT_BOOL('N', "intent-to-add", &intent_to_add,
 				N_("record only the fact that removed paths will be added later")),
 		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
@@ -420,6 +423,11 @@ int cmd_reset(int argc,
 		oidcpy(&oid, &tree->object.oid);
 	}
 
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	prepare_repo_settings(the_repository);
 	the_repository->settings.command_requires_full_index = 0;
 
@@ -427,9 +435,14 @@ int cmd_reset(int argc,
 		if (reset_type != NONE)
 			die(_("options '%s' and '%s' cannot be used together"), "--patch", "--{hard,mixed,soft}");
 		trace2_cmd_mode("patch-interactive");
-		update_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev,
-				   &pathspec);
+		update_ref_status = !!run_add_p(the_repository, ADD_P_RESET,
+						&add_p_opt, rev, &pathspec);
 		goto cleanup;
+	} else {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
 	}
 
 	/* git reset tree [--] paths... can be used to
diff --git a/builtin/stash.c b/builtin/stash.c
index 7cd3ad8aa48e..6da162e5e69a 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -1242,7 +1242,8 @@ done:
 }
 
 static int stash_patch(struct stash_info *info, const struct pathspec *ps,
-		       struct strbuf *out_patch, int quiet)
+		       struct strbuf *out_patch, int quiet,
+		       struct add_p_opt *add_p_opt)
 {
 	int ret = 0;
 	struct child_process cp_read_tree = CHILD_PROCESS_INIT;
@@ -1267,7 +1268,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
 	old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
 	setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
 
-	ret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps);
+	ret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps);
 
 	the_repository->index_file = old_repo_index_file;
 	if (old_index_env && *old_index_env)
@@ -1362,8 +1363,8 @@ done:
 }
 
 static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,
-			   int include_untracked, int patch_mode, int only_staged,
-			   struct stash_info *info, struct strbuf *patch,
+			   int include_untracked, int patch_mode, struct add_p_opt *add_p_opt,
+			   int only_staged, struct stash_info *info, struct strbuf *patch,
 			   int quiet)
 {
 	int ret = 0;
@@ -1444,7 +1445,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
 		untracked_commit_option = 1;
 	}
 	if (patch_mode) {
-		ret = stash_patch(info, ps, patch, quiet);
+		ret = stash_patch(info, ps, patch, quiet, add_p_opt);
 		if (ret < 0) {
 			if (!quiet)
 				fprintf_ln(stderr, _("Cannot save the current "
@@ -1519,7 +1520,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
 	if (!check_changes_tracked_files(&ps))
 		return 0;
 
-	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,
+	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,
 			      NULL, 0);
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
@@ -1530,7 +1531,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
 }
 
 static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,
-			 int keep_index, int patch_mode, int include_untracked, int only_staged)
+			 int keep_index, int patch_mode, struct add_p_opt *add_p_opt,
+			 int include_untracked, int only_staged)
 {
 	int ret = 0;
 	struct stash_info info = STASH_INFO_INIT;
@@ -1600,8 +1602,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
 
 	if (stash_msg)
 		strbuf_addstr(&stash_msg_buf, stash_msg);
-	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,
-			    &info, &patch, quiet)) {
+	if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
+			    add_p_opt, only_staged, &info, &patch, quiet)) {
 		ret = -1;
 		goto done;
 	}
@@ -1774,6 +1776,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 	const char *stash_msg = NULL;
 	char *pathspec_from_file = NULL;
 	struct pathspec ps;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	struct option options[] = {
 		OPT_BOOL('k', "keep-index", &keep_index,
 			 N_("keep index")),
@@ -1781,6 +1784,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT__QUIET(&quiet, N_("quiet mode")),
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
@@ -1836,8 +1841,20 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 		die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file");
 	}
 
+	if (!patch_mode) {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
-			    include_untracked, only_staged);
+			    &add_p_opt, include_untracked, only_staged);
 
 	clear_pathspec(&ps);
 	free(pathspec_from_file);
@@ -1862,6 +1879,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 	const char *stash_msg = NULL;
 	struct pathspec ps;
 	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
 	struct option options[] = {
 		OPT_BOOL('k', "keep-index", &keep_index,
 			 N_("keep index")),
@@ -1869,6 +1887,8 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 			 N_("stash staged changes only")),
 		OPT_BOOL('p', "patch", &patch_mode,
 			 N_("stash in patch mode")),
+		OPT_DIFF_UNIFIED(&add_p_opt.context),
+		OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
 		OPT__QUIET(&quiet, N_("quiet mode")),
 		OPT_BOOL('u', "include-untracked", &include_untracked,
 			 N_("include untracked files in stash")),
@@ -1887,8 +1907,22 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 		stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
+
+	if (add_p_opt.context < -1)
+		die(_("'%s' cannot be negative"), "--unified");
+	if (add_p_opt.interhunkcontext < -1)
+		die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
+	if (!patch_mode) {
+		if (add_p_opt.context != -1)
+			die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+		if (add_p_opt.interhunkcontext != -1)
+			die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+	}
+
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
-			    patch_mode, include_untracked, only_staged);
+			    patch_mode, &add_p_opt, include_untracked,
+			    only_staged);
 
 	strbuf_release(&stash_msg_buf);
 	return ret;
diff --git a/commit.h b/commit.h
index 70c870dae4d4..7a7fedbc2f14 100644
--- a/commit.h
+++ b/commit.h
@@ -2,6 +2,7 @@
 #define COMMIT_H
 
 #include "object.h"
+#include "add-interactive.h"
 
 struct signature_check;
 struct strbuf;
@@ -257,7 +258,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *);
 int interactive_add(struct repository *repo,
 		    const char **argv,
 		    const char *prefix,
-		    int patch);
+		    int patch, struct add_p_opt *add_p_opt);
 
 struct commit_extra_header {
 	struct commit_extra_header *next;
diff --git a/parse-options.h b/parse-options.h
index 91c3e3c29b3d..bdae8f116198 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -616,6 +616,8 @@ int parse_opt_tracking_mode(const struct option *, const char *, int);
 #define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file"))
 #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"))
 #define OPT_AUTOSTASH(v) OPT_BOOL(0, "autostash", v, N_("automatically stash/stash pop before and after"))
+#define OPT_DIFF_UNIFIED(v) OPT_INTEGER_F('U', "unified", v, N_("generate diffs with <n> lines context"), PARSE_OPT_NONEG)
+#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)
 
 #define OPT_IPVERSION(v) \
 	OPT_SET_INT_F('4', "ipv4", (v), N_("use IPv4 addresses only"), \
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 18dc329ea1f6..04d2a1983525 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -1252,4 +1252,53 @@ test_expect_success 'add -p rejects negative diff.context' '
 	test_grep "diff.context cannot be negative" output
 '
 
+for cmd in add checkout restore 'commit -m file'
+do
+	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" '
+		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 &&
+		git add file &&
+		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 &&
+		echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
+			$cmd -p -U 4 --inter-hunk-context 2 >actual &&
+		test_grep "@@ -2,20 +2,20 @@" actual
+	'
+done
+
+test_expect_success 'reset accepts -U and --inter-hunk-context' '
+	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 &&
+	git commit -m file file &&
+	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 &&
+	git add file &&
+	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
+		reset -p -U 4 --inter-hunk-context 2 >actual &&
+	test_grep "@@ -2,20 +2,20 @@" actual
+'
+
+test_expect_success 'stash accepts -U and --inter-hunk-context' '
+	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 &&
+	git commit -m file file &&
+	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 &&
+	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
+		stash -p -U 4 --inter-hunk-context 2 >actual &&
+	test_grep "@@ -2,20 +2,20 @@" actual
+'
+
+for cmd in add checkout commit reset restore "stash save" "stash push"
+do
+	test_expect_success "$cmd rejects invalid context options" '
+		test_must_fail git $cmd -p -U -3 2>actual &&
+		cat actual | echo &&
+		test_grep -e ".--unified. cannot be negative" actual &&
+
+		test_must_fail git $cmd -p --inter-hunk-context -3 2>actual &&
+		test_grep -e ".--inter-hunk-context. cannot be negative" actual &&
+
+		test_must_fail git $cmd -U 7 2>actual &&
+		test_grep -E ".--unified. requires .(--interactive/)?--patch." actual &&
+
+		test_must_fail git $cmd --inter-hunk-context 2 2>actual &&
+		test_grep -E ".--inter-hunk-context. requires .(--interactive/)?--patch." actual
+	'
+done
+
 test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 343b8cd1912b..6650d33fba69 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2596,6 +2596,8 @@ test_expect_success 'double dash "git checkout"' '
 	--merge Z
 	--conflict=Z
 	--patch Z
+	--unified=Z
+	--inter-hunk-context=Z
 	--ignore-skip-worktree-bits Z
 	--ignore-other-worktrees Z
 	--recurse-submodules Z
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 77+ messages in thread

* Re: [PATCH v5 0/4] Better support for customising context lines in --patch commands
  2025-07-29  7:01       ` [PATCH v5 " Leon Michalak via GitGitGadget
                           ` (3 preceding siblings ...)
  2025-07-29  7:01         ` [PATCH v5 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
@ 2025-07-29 15:21         ` Phillip Wood
  2025-07-29 15:55           ` Junio C Hamano
  4 siblings, 1 reply; 77+ messages in thread
From: Phillip Wood @ 2025-07-29 15:21 UTC (permalink / raw)
  To: Leon Michalak via GitGitGadget, git
  Cc: Kristoffer Haugsbakk, Eric Sunshine, Christian Couder,
	Leon Michalak

Hi Leon

The range-diff below looks good to me. Thanks for working on this

Phillip

On 29/07/2025 08:01, Leon Michalak via GitGitGadget wrote:
> This series of patches attempt to give --interactive/--patch compatible
> builtins ("add", "commit", "checkout", "reset", "restore" and "stash")
> better support and nicer experience for configuring how many context lines
> are shown in diffs through a variety of ways.
> 
> Prior to these patches, the user could not choose how many context lines
> they saw in --patch commands (apart from one workaround by using
> GIT_DIFF_OPTS=-u<number> ..., however this isn't a good user experience or a
> persistent solution). Additionally, the behaviour around reading from the
> diff.context and diff.interHunkContext configs was also inconsistent with
> other diff generating commands such as "log -p".
> 
> The summarised changes below hopefully make this experience better and fix
> some inconsistencies:
> 
>   * diff.context and diff.interHunkContext configs are now respected by
>     --patch compatible commands
>   * --unified and --inter-hunk-context command line options have been added
>     to --patch compatible commands (which take prescendence over file
>     configs)
>   * "add" and "commit" in --interactive mode now expose a new "context"
>     subcommand which configures the amount of context lines you wish to see
>     in subsequent diffs generated from other subcommands such as "patch" or
>     "diff"
> 
> The original discussion for this can be read at:
> 
>   * https://lore.kernel.org/git/CAP9jKjGb-Rcr=RLJEzeFdtrekYM+qmHy+1T1fykU3n9cV4GhGw@mail.gmail.com/
> 
> Changes since v1:
> 
>   * Update commit descriptions
>   * Update tests to use the more modern and robust test_grep and test_config
>     utils
>   * Reword some documentation / user messages
>   * Ensure each commit is atomic and builds/passes tests on it's own
>   * Make new command line options DRY
>   * Add tests for interhunk context interaction
>   * Error if context config/command line options are negative
>   * Drop previous last commit to do with new subcommand for --interactive
>     add/commit. My motivations behind this patch series originally where
>     quite simple, just for add-patch commands to respect context configs.
>     This subcommand, after the discussion in v1, will require more thought
>     and a larger implementation that what I had anticipated. I would prefer
>     to leave this for another time as it's the least impactful but the most
>     time intensive and complicated idea.
> 
> Changes since v2:
> 
>   * Update tests to only test single command (following Philip's suggestion)
>   * Add negative option checks
>   * Minor commit re-wording
> 
> Changes since v3:
> 
>   * Update commit descriptions
>   * Read struct properties directly instead of assigning to variables first
>   * Simplify config setting / error checking
>   * Remove redundant tests in later commit as they were replaced with better
>     test(s)
>   * Change tests to use single quotes (this messes with the grep so was
>     unable to explicitly test single quotes in the error messages, so decided
>     to use regex . instead, which is what some other tests that have this
>     problem seem to use as well)
> 
> Changes since v4:
> 
>   * Add back tests to maintain good coverage and remove redundant tests
> 
> Leon Michalak (4):
>    t: use test_grep in t3701 and t4055
>    t: use test_config in t4055
>    add-patch: respect diff.context configuration
>    add-patch: add diff.context command line overrides
> 
>   Documentation/diff-context-options.adoc |  10 ++
>   Documentation/git-add.adoc              |   2 +
>   Documentation/git-checkout.adoc         |   2 +
>   Documentation/git-commit.adoc           |   2 +
>   Documentation/git-reset.adoc            |   2 +
>   Documentation/git-restore.adoc          |   2 +
>   Documentation/git-stash.adoc            |   2 +
>   add-interactive.c                       |  45 +++++++--
>   add-interactive.h                       |  17 +++-
>   add-patch.c                             |  14 ++-
>   builtin/add.c                           |  21 ++++-
>   builtin/checkout.c                      |  31 +++++-
>   builtin/commit.c                        |  16 +++-
>   builtin/reset.c                         |  17 +++-
>   builtin/stash.c                         |  56 ++++++++---
>   commit.h                                |   3 +-
>   parse-options.h                         |   2 +
>   t/t3701-add-interactive.sh              | 119 +++++++++++++++++++-----
>   t/t4055-diff-context.sh                 |  42 ++++-----
>   t/t9902-completion.sh                   |   2 +
>   20 files changed, 324 insertions(+), 83 deletions(-)
>   create mode 100644 Documentation/diff-context-options.adoc
> 
> 
> base-commit: cf6f63ea6bf35173e02e18bdc6a4ba41288acff9
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1915%2FNinjaInShade%2Finteractive-patch-context-v5
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1915/NinjaInShade/interactive-patch-context-v5
> Pull-Request: https://github.com/gitgitgadget/git/pull/1915
> 
> Range-diff vs v4:
> 
>   1:  bbb2bc7082b = 1:  bbb2bc7082b t: use test_grep in t3701 and t4055
>   2:  feace2d3676 = 2:  feace2d3676 t: use test_config in t4055
>   3:  994029d6602 = 3:  994029d6602 add-patch: respect diff.context configuration
>   4:  2774b930406 ! 4:  9731e5b76fb add-patch: add diff.context command line overrides
>       @@ parse-options.h: int parse_opt_tracking_mode(const struct option *, const char *
>         	OPT_SET_INT_F('4', "ipv4", (v), N_("use IPv4 addresses only"), \
>        
>         ## t/t3701-add-interactive.sh ##
>       -@@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.suppressBlankEmpty' '
>       - 	test_cmp expect actual
>       +@@ t/t3701-add-interactive.sh: test_expect_success 'add -p rejects negative diff.context' '
>       + 	test_grep "diff.context cannot be negative" output
>         '
>         
>       --test_expect_success 'add -p respects diff.context' '
>       --	test_write_lines a b c d e f g h i j k l m >file &&
>        +for cmd in add checkout restore 'commit -m file'
>        +do
>        +	test_expect_success "${cmd%% *} accepts -U and --inter-hunk-context" '
>       @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.
>        +	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 &&
>        +	git commit -m file file &&
>        +	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 &&
>       - 	git add file &&
>       --	test_write_lines a b c d e f G h i j k l m >file &&
>       --	echo y | git -c diff.context=5 add -p >actual &&
>       --	test_grep "@@ -2,11 +2,11 @@" actual
>       ++	git add file &&
>        +	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
>        +		reset -p -U 4 --inter-hunk-context 2 >actual &&
>        +	test_grep "@@ -2,20 +2,20 @@" actual
>       - '
>       -
>       --test_expect_success 'add -p respects diff.interHunkContext' '
>       --	test_write_lines a b c d e f g h i j k l m n o p q r s >file &&
>       --	git add file &&
>       --	test_write_lines a b c d E f g i i j k l m N o p q r s >file &&
>       --	echo y | git -c diff.interhunkcontext=2 add -p >actual &&
>       --	test_grep "@@ -2,16 +2,16 @@" actual
>       ++'
>       ++
>        +test_expect_success 'stash accepts -U and --inter-hunk-context' '
>        +	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 &&
>        +	git commit -m file file &&
>       @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.
>        +	echo y | git -c diff.context=5 -c diff.interhunkcontext=1 \
>        +		stash -p -U 4 --inter-hunk-context 2 >actual &&
>        +	test_grep "@@ -2,20 +2,20 @@" actual
>       - '
>       -
>       --test_expect_success 'add -p rejects negative diff.context' '
>       --	test_config diff.context -1 &&
>       --	test_must_fail git add -p 2>output &&
>       --	test_grep "diff.context cannot be negative" output
>       --'
>       ++'
>       ++
>        +for cmd in add checkout commit reset restore "stash save" "stash push"
>        +do
>        +	test_expect_success "$cmd rejects invalid context options" '
>       @@ t/t3701-add-interactive.sh: test_expect_success 'hunk splitting works with diff.
>        +		test_grep -E ".--inter-hunk-context. requires .(--interactive/)?--patch." actual
>        +	'
>        +done
>       -
>       - test_done
>       -
>       - ## t/t4055-diff-context.sh ##
>       -@@ t/t4055-diff-context.sh: test_expect_success 'The -U option overrides diff.context' '
>       - 	test_grep ! "^ firstline" output
>       - '
>       -
>       -+test_expect_success 'The -U option overrides diff.context for "add"' '
>       -+	test_config diff.context 8 &&
>       -+	git add -U4 -p >output &&
>       -+	test_grep ! "^ firstline" output
>       -+'
>       -+
>       -+test_expect_success 'The -U option overrides diff.context for "commit"' '
>       -+	test_config diff.context 8 &&
>       -+	! git commit -U4 -p >output &&
>       -+	test_grep ! "^ firstline" output
>       -+'
>       -+
>       -+test_expect_success 'The -U option overrides diff.context for "checkout"' '
>       -+	test_config diff.context 8 &&
>       -+	git checkout -U4 -p >output &&
>       -+	test_grep ! "^ firstline" output
>       -+'
>        +
>       -+test_expect_success 'The -U option overrides diff.context for "stash"' '
>       -+	test_config diff.context 8 &&
>       -+	! git stash -U4 -p >output &&
>       -+	test_grep ! "^ firstline" output
>       -+'
>       -+
>       -+test_expect_success 'The -U option overrides diff.context for "restore"' '
>       -+	test_config diff.context 8 &&
>       -+	git restore -U4 -p >output &&
>       -+	test_grep ! "^ firstline" output
>       -+'
>       -+
>       - test_expect_success 'diff.context honored by "diff"' '
>       - 	test_config diff.context 8 &&
>       - 	git diff >output &&
>       + test_done
>        
>         ## t/t9902-completion.sh ##
>        @@ t/t9902-completion.sh: test_expect_success 'double dash "git checkout"' '
> 


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v5 0/4] Better support for customising context lines in --patch commands
  2025-07-29 15:21         ` [PATCH v5 0/4] Better support for customising context lines in --patch commands Phillip Wood
@ 2025-07-29 15:55           ` Junio C Hamano
  2025-07-29 16:18             ` Leon Michalak
  0 siblings, 1 reply; 77+ messages in thread
From: Junio C Hamano @ 2025-07-29 15:55 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Leon Michalak via GitGitGadget, git, Kristoffer Haugsbakk,
	Eric Sunshine, Christian Couder, Leon Michalak

Phillip Wood <phillip.wood123@gmail.com> writes:

> Hi Leon
>
> The range-diff below looks good to me. Thanks for working on this
>
> Phillip

Yeah, the new one matches the previous one plus your fixes to the
tests, which I agree with fully.

Will queue.  Let's mark the topic for 'next'.

Thanks, both of you.


^ permalink raw reply	[flat|nested] 77+ messages in thread

* Re: [PATCH v5 0/4] Better support for customising context lines in --patch commands
  2025-07-29 15:55           ` Junio C Hamano
@ 2025-07-29 16:18             ` Leon Michalak
  0 siblings, 0 replies; 77+ messages in thread
From: Leon Michalak @ 2025-07-29 16:18 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Phillip Wood, Leon Michalak via GitGitGadget, git,
	Kristoffer Haugsbakk, Eric Sunshine, Christian Couder

Great! Thanks Philip, Junio and everyone else who helped me get this
finished and in.

Was a good experience :)

^ permalink raw reply	[flat|nested] 77+ messages in thread

end of thread, other threads:[~2025-07-29 16:18 UTC | newest]

Thread overview: 77+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-05-05  9:18 [PATCH 0/3] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
2025-05-05  9:18 ` [PATCH 1/3] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
2025-05-05 20:29   ` Eric Sunshine
2025-05-06  8:55   ` Phillip Wood
2025-05-06 17:29   ` Junio C Hamano
2025-05-05  9:18 ` [PATCH 2/3] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
2025-05-05  9:49   ` Kristoffer Haugsbakk
     [not found]     ` <CAP9jKjHj7WP91aKA9SE94zYj+naBGLUs99mF3G4BhTGcGjFDUQ@mail.gmail.com>
2025-05-05 10:11       ` Leon Michalak
2025-05-07  9:51   ` Phillip Wood
2025-05-07 18:07     ` Junio C Hamano
2025-05-07 18:28       ` Leon Michalak
2025-05-07 20:25         ` Junio C Hamano
2025-05-05  9:18 ` [PATCH 3/3] add-interactive: add new "context" subcommand Leon Michalak via GitGitGadget
2025-05-06  0:02   ` Eric Sunshine
2025-05-06  7:20     ` Leon Michalak
2025-05-06  8:28       ` Christian Couder
2025-05-06 17:07         ` Leon Michalak
2025-05-06 16:37     ` Junio C Hamano
2025-05-06 17:25       ` Leon Michalak
2025-05-07 13:30       ` Phillip Wood
2025-05-07 19:10         ` Junio C Hamano
2025-05-10 13:46 ` [PATCH v2 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
2025-05-10 13:46   ` [PATCH v2 1/4] test: refactor to use "test_grep" Leon Michalak via GitGitGadget
2025-05-12 13:42     ` Junio C Hamano
2025-05-12 16:58       ` Leon Michalak
2025-05-10 13:46   ` [PATCH v2 2/4] test: refactor to use "test_config" Leon Michalak via GitGitGadget
2025-05-10 13:46   ` [PATCH v2 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
2025-05-13 13:52     ` Phillip Wood
2025-05-13 15:47       ` Junio C Hamano
2025-05-14 15:13         ` Phillip Wood
2025-05-15 12:58           ` Junio C Hamano
2025-05-10 13:46   ` [PATCH v2 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
2025-05-12 16:45     ` Junio C Hamano
2025-05-12 17:03       ` Leon Michalak
2025-05-13 13:52     ` Phillip Wood
2025-05-13 14:39       ` Phillip Wood
2025-05-13 15:05         ` Leon Michalak
2025-05-14 15:13           ` phillip.wood123
2025-06-28 16:34   ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Leon Michalak via GitGitGadget
2025-06-28 16:34     ` [PATCH v3 1/4] test: use "test_grep" Leon Michalak via GitGitGadget
2025-06-30 16:23       ` Junio C Hamano
2025-06-28 16:34     ` [PATCH v3 2/4] test: use "test_config" Leon Michalak via GitGitGadget
2025-06-30 16:35       ` Junio C Hamano
2025-06-28 16:34     ` [PATCH v3 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
2025-06-30 16:55       ` Junio C Hamano
2025-07-01 10:00       ` Phillip Wood
2025-06-28 16:34     ` [PATCH v3 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
2025-06-30 17:03       ` Junio C Hamano
2025-07-01  9:59         ` Phillip Wood
2025-07-01 15:54           ` Junio C Hamano
2025-07-02 14:07             ` Phillip Wood
2025-07-02 18:28               ` Junio C Hamano
2025-07-01  9:59       ` Phillip Wood
2025-06-30 16:16     ` [PATCH v3 0/4] Better support for customising context lines in --patch commands Junio C Hamano
2025-07-09  0:09     ` Junio C Hamano
2025-07-09  7:57       ` Leon Michalak
2025-07-09 15:32         ` Junio C Hamano
2025-07-19 12:28     ` [PATCH v4 " Leon Michalak via GitGitGadget
2025-07-19 12:28       ` [PATCH v4 1/4] t: use test_grep in t3701 and t4055 Leon Michalak via GitGitGadget
2025-07-19 12:28       ` [PATCH v4 2/4] t: use test_config in t4055 Leon Michalak via GitGitGadget
2025-07-19 12:28       ` [PATCH v4 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
2025-07-19 12:28       ` [PATCH v4 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
2025-07-22 16:01         ` Phillip Wood
2025-07-22 18:02           ` Leon Michalak
2025-07-22 18:05             ` Leon Michalak
2025-07-23  9:41             ` Phillip Wood
2025-07-21 16:50       ` [PATCH v4 0/4] Better support for customising context lines in --patch commands Junio C Hamano
2025-07-22 16:05         ` Phillip Wood
2025-07-22 17:20           ` Junio C Hamano
2025-07-29  7:01       ` [PATCH v5 " Leon Michalak via GitGitGadget
2025-07-29  7:01         ` [PATCH v5 1/4] t: use test_grep in t3701 and t4055 Leon Michalak via GitGitGadget
2025-07-29  7:01         ` [PATCH v5 2/4] t: use test_config in t4055 Leon Michalak via GitGitGadget
2025-07-29  7:01         ` [PATCH v5 3/4] add-patch: respect diff.context configuration Leon Michalak via GitGitGadget
2025-07-29  7:01         ` [PATCH v5 4/4] add-patch: add diff.context command line overrides Leon Michalak via GitGitGadget
2025-07-29 15:21         ` [PATCH v5 0/4] Better support for customising context lines in --patch commands Phillip Wood
2025-07-29 15:55           ` Junio C Hamano
2025-07-29 16:18             ` Leon Michalak

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).