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" <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.

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 <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.

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 <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

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, phillip.wood123@gmail.com 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

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" <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.

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 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

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):

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?

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 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

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):

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.

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 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

`--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
21 changes: 17 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 @@ -394,14 +397,24 @@ 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) {
if (show_only)
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
31 changes: 28 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,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);
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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;
Expand Down
Loading
Loading