Merge branch 'lm/add-p-context'

"git add/etc -p" now honor the diff.context configuration variable,
and also they learn to honor the -U<n> command-line option.

* lm/add-p-context:
  add-patch: add diff.context command line overrides
  add-patch: respect diff.context configuration
  t: use test_config in t4055
  t: use test_grep in t3701 and t4055
maint
Junio C Hamano 2025-08-04 08:10:33 -07:00
commit d63f334a82
20 changed files with 324 additions and 83 deletions

View File

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

View File

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

View File

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

View File

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

View File

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


View File

@ -50,6 +50,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`::

View File

@ -222,6 +222,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.

View File

@ -36,11 +36,14 @@ 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;

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

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

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

@ -979,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);
}
@ -1014,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,
@ -1110,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 = { "[", "]" };
@ -1153,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

View File

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

View File

@ -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,
@ -1760,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;

View File

@ -30,6 +30,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;
@ -158,7 +159,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;
@ -170,9 +171,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;
@ -254,6 +255,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")),
@ -395,6 +398,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) {
@ -402,7 +410,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) {

View File

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

View File

@ -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");
}

/*
@ -1726,6 +1738,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")),

View File

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

View File

@ -1301,7 +1301,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;
@ -1326,7 +1327,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)
@ -1421,8 +1422,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;
@ -1503,7 +1504,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 "
@ -1578,7 +1579,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));
@ -1589,7 +1590,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;
@ -1659,8 +1661,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;
}
@ -1833,6 +1835,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")),
@ -1840,6 +1843,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")),
@ -1895,8 +1900,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);
@ -1921,6 +1938,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")),
@ -1928,6 +1946,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")),
@ -1946,8 +1966,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;

View File

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

View File

@ -623,6 +623,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"), \

View File

@ -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' '
@ -1230,4 +1230,75 @@ 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" '
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

View File

@ -38,55 +38,55 @@ 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 &&
git config diff.context 8 &&
test_grep ! firstline output &&
test_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 &&
test_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 &&
test_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 &&
test_config diff.context 8 &&
git diff-files -p >output &&
! grep "^ firstline" 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 &&
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

View File

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