Skip to content

Better support for customising context lines in --patch commands #1915

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Documentation/diff-context-options.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
`-U<n>`::
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Junio C Hamano wrote (reply to this):

"Leon Michalak via GitGitGadget" <[email protected]> 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.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Leon Michalak wrote (reply to this):

On Mon, 12 May 2025 at 17:45, Junio C Hamano <[email protected]> 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.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Phillip Wood wrote (reply to this):

Hi Leon

On 10/05/2025 14:46, Leon Michalak via GitGitGadget wrote:
> From: Leon Michalak <[email protected]>
> > 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

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Phillip Wood wrote (reply to this):

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

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Leon Michalak wrote (reply to this):

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?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, [email protected] wrote (reply to this):

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

`--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.
2 changes: 2 additions & 0 deletions Documentation/git-add.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Documentation/git-checkout.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Documentation/git-commit.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Documentation/git-reset.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 2 additions & 0 deletions Documentation/git-restore.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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`::
Expand Down
2 changes: 2 additions & 0 deletions Documentation/git-stash.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
53 changes: 45 additions & 8 deletions add-interactive.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,16 @@ 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;
int interhunkcontext;

s->r = r;
s->context = -1;
s->interhunkcontext = -1;

if (repo_config_get_value(r, "color.interactive", &value))
s->use_color = -1;
Expand Down Expand Up @@ -78,9 +83,33 @@ 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);

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)
Expand Down Expand Up @@ -969,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 };

Expand All @@ -979,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);
}
Expand Down Expand Up @@ -1014,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,
Expand Down Expand Up @@ -1110,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 = { "[", "]" };
Expand Down Expand Up @@ -1153,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
Expand Down
17 changes: 14 additions & 3 deletions add-interactive.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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
11 changes: 9 additions & 2 deletions add-patch.c
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) {
Expand Down Expand Up @@ -1760,14 +1766,15 @@ static int patch_update_file(struct add_p_state *s,
}

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;
Expand Down
16 changes: 12 additions & 4 deletions builtin/add.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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")),
Expand Down Expand Up @@ -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) {
Expand Down
24 changes: 21 additions & 3 deletions builtin/checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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];

Expand All @@ -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);
Expand Down Expand Up @@ -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),
Expand Down
Loading
Loading