Subject: [PATCH 3/3] add-interactive: add new "context" subcommand

From: "Leon Michalak via GitGitGadget" <[email-masked]>
Date: Mon, 05 May 2025 09:18:39 +0000

From: Leon Michalak <[email-masked]>

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 <[email-masked]>
---
 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