aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2025-07-29 09:28:50 -0700
committerJunio C Hamano <gitster@pobox.com>2025-07-29 09:28:50 -0700
commit393fa4b64b12da3d3fdcefb02739bb1e82291dcc (patch)
tree37d9676a25f69b289f6d738642a43ae68d8b140e
parentMerge branch 'ps/config-wo-the-repository' into next (diff)
parentadd-patch: add diff.context command line overrides (diff)
downloadgit-393fa4b64b12da3d3fdcefb02739bb1e82291dcc.tar.gz
git-393fa4b64b12da3d3fdcefb02739bb1e82291dcc.zip
Merge branch 'lm/add-p-context' into next
"git add/etc -p" now honors diff.context configuration variable, and learns to honor -U<n> 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
-rw-r--r--Documentation/diff-context-options.adoc10
-rw-r--r--Documentation/git-add.adoc2
-rw-r--r--Documentation/git-checkout.adoc2
-rw-r--r--Documentation/git-commit.adoc2
-rw-r--r--Documentation/git-reset.adoc2
-rw-r--r--Documentation/git-restore.adoc2
-rw-r--r--Documentation/git-stash.adoc2
-rw-r--r--add-interactive.c45
-rw-r--r--add-interactive.h17
-rw-r--r--add-patch.c14
-rw-r--r--builtin/add.c21
-rw-r--r--builtin/checkout.c31
-rw-r--r--builtin/commit.c16
-rw-r--r--builtin/reset.c17
-rw-r--r--builtin/stash.c56
-rw-r--r--commit.h3
-rw-r--r--parse-options.h2
-rwxr-xr-xt/t3701-add-interactive.sh119
-rwxr-xr-xt/t4055-diff-context.sh42
-rwxr-xr-xt/t9902-completion.sh2
20 files changed, 324 insertions, 83 deletions
diff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc
new file mode 100644
index 0000000000..e161260358
--- /dev/null
+++ b/Documentation/diff-context-options.adoc
@@ -0,0 +1,10 @@
+`-U<n>`::
+`--unified=<n>`::
+ Generate diffs with _<n>_ lines of context. Defaults to `diff.context`
+ or 3 if the config option is unset.
+
+`--inter-hunk-context=<n>`::
+ Show the context between diff hunks, up to the specified _<number>_
+ of lines, thereby fusing hunks that are close to each other.
+ Defaults to `diff.interHunkContext` or 0 if the config option
+ is unset.
diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index eba0b419ce..b7a735824d 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -104,6 +104,8 @@ This effectively runs `add --interactive`, but bypasses the
initial command menu and directly jumps to the `patch` subcommand.
See ``Interactive mode'' for details.
+include::diff-context-options.adoc[]
+
`-e`::
`--edit`::
Open the diff vs. the index in an editor and let the user
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index ee83b6d9ba..40e02cfd65 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -289,6 +289,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
Note that this option uses the no overlay mode by default (see also
`--overlay`), and currently doesn't support overlay mode.
+include::diff-context-options.adoc[]
+
`--ignore-other-worktrees`::
`git checkout` refuses when the wanted branch is already checked
out or otherwise in use by another worktree. This option makes
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc
index dc219025f1..ae988a883b 100644
--- a/Documentation/git-commit.adoc
+++ b/Documentation/git-commit.adoc
@@ -76,6 +76,8 @@ OPTIONS
which changes to commit. See linkgit:git-add[1] for
details.
+include::diff-context-options.adoc[]
+
`-C <commit>`::
`--reuse-message=<commit>`::
Take an existing _<commit>_ object, and reuse the log message
diff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc
index 53ab88c545..50e8a0ba6f 100644
--- a/Documentation/git-reset.adoc
+++ b/Documentation/git-reset.adoc
@@ -125,6 +125,8 @@ OPTIONS
separated with _NUL_ character and all other characters are taken
literally (including newlines and quotes).
+include::diff-context-options.adoc[]
+
`--`::
Do not interpret any more arguments as options.
diff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc
index 3763a7f4f4..961eef0137 100644
--- a/Documentation/git-restore.adoc
+++ b/Documentation/git-restore.adoc
@@ -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`::
diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc
index e5e6c9d37f..e2300a19a2 100644
--- a/Documentation/git-stash.adoc
+++ b/Documentation/git-stash.adoc
@@ -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.
diff --git a/add-interactive.c b/add-interactive.c
index 97ff35b6f1..3e692b47ec 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -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
diff --git a/add-interactive.h b/add-interactive.h
index 693f125e8e..4213dcd67b 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -3,6 +3,13 @@
#include "color.h"
+struct add_p_opt {
+ int context;
+ int interhunkcontext;
+};
+
+#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 }
+
struct add_i_state {
struct repository *r;
int use_color;
@@ -18,14 +25,17 @@ struct add_i_state {
int use_single_key;
char *interactive_diff_filter, *interactive_diff_algorithm;
+ int context, interhunkcontext;
};
-void init_add_i_state(struct add_i_state *s, struct repository *r);
+void init_add_i_state(struct add_i_state *s, struct repository *r,
+ struct add_p_opt *add_p_opt);
void clear_add_i_state(struct add_i_state *s);
struct repository;
struct pathspec;
-int run_add_i(struct repository *r, const struct pathspec *ps);
+int run_add_i(struct repository *r, const struct pathspec *ps,
+ struct add_p_opt *add_p_opt);
enum add_p_mode {
ADD_P_ADD,
@@ -36,6 +46,7 @@ enum add_p_mode {
};
int run_add_p(struct repository *r, enum add_p_mode mode,
- const char *revision, const struct pathspec *ps);
+ struct add_p_opt *o, const char *revision,
+ const struct pathspec *ps);
#endif
diff --git a/add-patch.c b/add-patch.c
index 95c67d8c80..302e6ba7d9 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -414,7 +414,6 @@ static int normalize_marker(const char *p)
static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
{
struct strvec args = STRVEC_INIT;
- const char *diff_algorithm = s->s.interactive_diff_algorithm;
struct strbuf *plain = &s->plain, *colored = NULL;
struct child_process cp = CHILD_PROCESS_INIT;
char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
@@ -424,8 +423,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
int res;
strvec_pushv(&args, s->mode->diff_cmd);
- if (diff_algorithm)
- strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
+ if (s->s.context != -1)
+ strvec_pushf(&args, "--unified=%i", s->s.context);
+ if (s->s.interhunkcontext != -1)
+ strvec_pushf(&args, "--inter-hunk-context=%i", s->s.interhunkcontext);
+ if (s->s.interactive_diff_algorithm)
+ strvec_pushf(&args, "--diff-algorithm=%s", s->s.interactive_diff_algorithm);
if (s->revision) {
struct object_id oid;
strvec_push(&args,
@@ -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;
diff --git a/builtin/add.c b/builtin/add.c
index 88d1d330fa..0235854f80 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -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) {
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 37efde5989..3c196c150a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -61,6 +61,8 @@ static const char * const restore_usage[] = {
struct checkout_opts {
int patch_mode;
+ int patch_context;
+ int patch_interhunk_context;
int quiet;
int merge;
int force;
@@ -104,7 +106,12 @@ struct checkout_opts {
struct tree *source_tree;
};
-#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 }
+#define CHECKOUT_OPTS_INIT { \
+ .conflict_style = -1, \
+ .merge = -1, \
+ .patch_context = -1, \
+ .patch_interhunk_context = -1, \
+}
struct branch_info {
char *name; /* The short name used */
@@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts,
if (opts->patch_mode) {
enum add_p_mode patch_mode;
+ struct add_p_opt add_p_opt = {
+ .context = opts->patch_context,
+ .interhunkcontext = opts->patch_interhunk_context,
+ };
const char *rev = new_branch_info->name;
char rev_oid[GIT_MAX_HEXSZ + 1];
@@ -564,8 +575,8 @@ static int checkout_paths(const struct checkout_opts *opts,
else
BUG("either flag must have been set, worktree=%d, index=%d",
opts->checkout_worktree, opts->checkout_index);
- return !!run_add_p(the_repository, patch_mode, rev,
- &opts->pathspec);
+ return !!run_add_p(the_repository, patch_mode, &add_p_opt,
+ rev, &opts->pathspec);
}
repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -1738,6 +1749,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
N_("checkout their version for unmerged files"),
3, PARSE_OPT_NONEG),
OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
+ OPT_DIFF_UNIFIED(&opts->patch_context),
+ OPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context),
OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
N_("do not limit pathspecs to sparse entries only")),
OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),
@@ -1780,6 +1793,18 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
argc = parse_options(argc, argv, prefix, options,
usagestr, parseopt_flags);
+ if (opts->patch_context < -1)
+ die(_("'%s' cannot be negative"), "--unified");
+ if (opts->patch_interhunk_context < -1)
+ die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
+ if (!opts->patch_mode) {
+ if (opts->patch_context != -1)
+ die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+ if (opts->patch_interhunk_context != -1)
+ die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
+ }
+
if (opts->show_progress < 0) {
if (opts->quiet)
opts->show_progress = 0;
diff --git a/builtin/commit.c b/builtin/commit.c
index 757f51eac8..b5b9608813 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -19,6 +19,7 @@
#include "environment.h"
#include "diff.h"
#include "commit.h"
+#include "add-interactive.h"
#include "gettext.h"
#include "revision.h"
#include "wt-status.h"
@@ -122,6 +123,7 @@ static const char *edit_message, *use_message;
static char *fixup_message, *fixup_commit, *squash_message;
static const char *fixup_prefix;
static int all, also, interactive, patch_interactive, only, amend, signoff;
+static struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
static int edit_flag = -1; /* unspecified */
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static int config_commit_verbose = -1; /* unspecified */
@@ -354,6 +356,11 @@ static const char *prepare_index(const char **argv, const char *prefix,
const char *ret;
char *path = NULL;
+ if (add_p_opt.context < -1)
+ die(_("'%s' cannot be negative"), "--unified");
+ if (add_p_opt.interhunkcontext < -1)
+ die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
if (is_status)
refresh_flags |= REFRESH_UNMERGED;
parse_pathspec(&pathspec, 0,
@@ -400,7 +407,7 @@ static const char *prepare_index(const char **argv, const char *prefix,
old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
- if (interactive_add(the_repository, argv, prefix, patch_interactive) != 0)
+ if (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0)
die(_("interactive add failed"));
the_repository->index_file = old_repo_index_file;
@@ -424,6 +431,11 @@ static const char *prepare_index(const char **argv, const char *prefix,
commit_style = COMMIT_NORMAL;
ret = get_lock_file_path(&index_lock);
goto out;
+ } else {
+ if (add_p_opt.context != -1)
+ die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch");
+ if (add_p_opt.interhunkcontext != -1)
+ die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch");
}
/*
@@ -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")),
diff --git a/builtin/reset.c b/builtin/reset.c
index 00492c19b0..ed35802af1 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -346,6 +346,7 @@ int cmd_reset(int argc,
struct object_id oid;
struct pathspec pathspec;
int intent_to_add = 0;
+ struct add_p_opt add_p_opt = ADD_P_OPT_INIT;
const struct option options[] = {
OPT__QUIET(&quiet, N_("be quiet, only report errors")),
OPT_BOOL(0, "no-refresh", &no_refresh,
@@ -370,6 +371,8 @@ int cmd_reset(int argc,
PARSE_OPT_OPTARG,
option_parse_recurse_submodules_worktree_updater),
OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
+ OPT_DIFF_UNIFIED(&add_p_opt.context),
+ OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext),
OPT_BOOL('N', "intent-to-add", &intent_to_add,
N_("record only the fact that removed paths will be added later")),
OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
@@ -420,6 +423,11 @@ int cmd_reset(int argc,
oidcpy(&oid, &tree->object.oid);
}
+ if (add_p_opt.context < -1)
+ die(_("'%s' cannot be negative"), "--unified");
+ if (add_p_opt.interhunkcontext < -1)
+ die(_("'%s' cannot be negative"), "--inter-hunk-context");
+
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
@@ -427,9 +435,14 @@ int cmd_reset(int argc,
if (reset_type != NONE)
die(_("options '%s' and '%s' cannot be used together"), "--patch", "--{hard,mixed,soft}");
trace2_cmd_mode("patch-interactive");
- update_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev,
- &pathspec);
+ update_ref_status = !!run_add_p(the_repository, ADD_P_RESET,
+ &add_p_opt, rev, &pathspec);
goto cleanup;
+ } else {
+ if (add_p_opt.context != -1)
+ die(_("the option '%s' requires '%s'"), "--unified", "--patch");
+ if (add_p_opt.interhunkcontext != -1)
+ die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
}
/* git reset tree [--] paths... can be used to
diff --git a/builtin/stash.c b/builtin/stash.c
index 821b58a14e..1977e50df2 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -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;
diff --git a/commit.h b/commit.h
index 9630c076d6..1d6e0c7518 100644
--- a/commit.h
+++ b/commit.h
@@ -2,6 +2,7 @@
#define COMMIT_H
#include "object.h"
+#include "add-interactive.h"
struct signature_check;
struct strbuf;
@@ -257,7 +258,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *);
int interactive_add(struct repository *repo,
const char **argv,
const char *prefix,
- int patch);
+ int patch, struct add_p_opt *add_p_opt);
struct commit_extra_header {
struct commit_extra_header *next;
diff --git a/parse-options.h b/parse-options.h
index 312045604d..706de9729f 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -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"), \
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index b8a05d95f3..04d2a19835 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -63,7 +63,7 @@ test_expect_success 'setup (initial)' '
'
test_expect_success 'status works (initial)' '
git add -i </dev/null >output &&
- grep "+1/-0 *+2/-0 file" output
+ test_grep "+1/-0 *+2/-0 file" output
'
test_expect_success 'setup expected' '
@@ -86,7 +86,7 @@ test_expect_success 'revert works (initial)' '
git add file &&
test_write_lines r 1 | git add -i &&
git ls-files >output &&
- ! grep . output
+ test_grep ! . output
'
test_expect_success 'add untracked (multiple)' '
@@ -109,7 +109,7 @@ test_expect_success 'setup (commit)' '
'
test_expect_success 'status works (commit)' '
git add -i </dev/null >output &&
- grep "+1/-0 *+2/-0 file" output
+ test_grep "+1/-0 *+2/-0 file" output
'
test_expect_success 'update can stage deletions' '
@@ -141,7 +141,7 @@ test_expect_success 'revert works (commit)' '
git add file &&
test_write_lines r 1 | git add -i &&
git add -i </dev/null >output &&
- grep "unchanged *+3/-0 file" output
+ test_grep "unchanged *+3/-0 file" output
'
test_expect_success 'reject multi-key input' '
@@ -185,7 +185,7 @@ test_expect_success 'setup fake editor' '
test_expect_success 'bad edit rejected' '
git reset &&
test_write_lines e n d | git add -p >output &&
- grep "hunk does not apply" output
+ test_grep "hunk does not apply" output
'
test_expect_success 'setup patch' '
@@ -198,7 +198,7 @@ test_expect_success 'setup patch' '
test_expect_success 'garbage edit rejected' '
git reset &&
test_write_lines e n d | git add -p >output &&
- grep "hunk does not apply" output
+ test_grep "hunk does not apply" output
'
test_expect_success 'setup patch' '
@@ -313,8 +313,8 @@ test_expect_success FILEMODE 'stage mode and hunk' '
chmod +x file &&
printf "y\\ny\\n" | git add -p &&
git diff --cached file >out &&
- grep "new mode" out &&
- grep "+content" out &&
+ test_grep "new mode" out &&
+ test_grep "+content" out &&
git diff file >out &&
test_must_be_empty out
'
@@ -636,7 +636,7 @@ test_expect_success 'split hunk "add -p (edit)"' '
printf "%s\n" s e q n q q |
EDITOR=: git add -p &&
git diff >actual &&
- ! grep "^+15" actual
+ test_grep ! "^+15" actual
'
test_expect_success 'split hunk "add -p (no, yes, edit)"' '
@@ -648,7 +648,7 @@ test_expect_success 'split hunk "add -p (no, yes, edit)"' '
EDITOR=: git add -p 2>error &&
test_must_be_empty error &&
git diff >actual &&
- ! grep "^+31" actual
+ test_grep ! "^+31" actual
'
test_expect_success 'split hunk with incomplete line at end' '
@@ -682,7 +682,7 @@ test_expect_success 'edit, adding lines to the first hunk' '
EDITOR=./fake_editor.sh git add -p 2>error &&
test_must_be_empty error &&
git diff --cached >actual &&
- grep "^+22" actual
+ test_grep "^+22" actual
'
test_expect_success 'patch mode ignores unmerged entries' '
@@ -696,7 +696,7 @@ test_expect_success 'patch mode ignores unmerged entries' '
test_must_fail git merge side &&
echo changed >non-conflict.t &&
echo y | git add -p >output &&
- ! grep a/conflict.t output &&
+ test_grep ! a/conflict.t output &&
cat >expected <<-\EOF &&
* Unmerged path conflict.t
diff --git a/non-conflict.t b/non-conflict.t
@@ -728,7 +728,7 @@ test_expect_success 'diffs can be colorized' '
# We do not want to depend on the exact coloring scheme
# git uses for diffs, so just check that we saw some kind of color.
- grep "$(printf "\\033")" output
+ test_grep "$(printf "\\033")" output
'
test_expect_success 'colors can be overridden' '
@@ -743,7 +743,7 @@ test_expect_success 'colors can be overridden' '
-c color.interactive.error=blue \
add -i 2>err.raw <input &&
test_decode_color <err.raw >err &&
- grep "<BLUE>Huh (trigger)?<RESET>" err &&
+ test_grep "<BLUE>Huh (trigger)?<RESET>" err &&
test_write_lines help quit >input &&
force_color git \
@@ -863,7 +863,7 @@ test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '
printf y >y &&
force_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&
test_decode_color <output.raw >output &&
- grep "old<" output
+ test_grep "old<" output
'
test_expect_success 'diffFilter filters diff' '
@@ -876,7 +876,7 @@ test_expect_success 'diffFilter filters diff' '
# avoid depending on the exact coloring or content of the prompts,
# and just make sure we saw our diff prefixed
- grep foo:.*content output
+ test_grep foo:.*content output
'
test_expect_success 'detect bogus diffFilter output' '
@@ -886,7 +886,7 @@ test_expect_success 'detect bogus diffFilter output' '
test_config interactive.diffFilter "sed 6d" &&
printf y >y &&
force_color test_must_fail git add -p <y >output 2>&1 &&
- grep "mismatched output" output
+ test_grep "mismatched output" output
'
test_expect_success 'handle iffy colored hunk headers' '
@@ -896,7 +896,7 @@ test_expect_success 'handle iffy colored hunk headers' '
printf n >n &&
force_color git -c interactive.diffFilter="sed s/.*@@.*/XX/" \
add -p >output 2>&1 <n &&
- grep "^XX$" output
+ test_grep "^XX$" output
'
test_expect_success 'handle very large filtered diff' '
@@ -1002,7 +1002,7 @@ test_expect_success 'add -p does not expand argument lists' '
# update it, but we want to be sure that our "." pathspec
# was not expanded into the argument list of any command.
# So look only for "not-changed".
- ! grep -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
+ test_grep ! -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
'
test_expect_success 'hunk-editing handles custom comment char' '
@@ -1072,21 +1072,21 @@ test_expect_success 'setup different kinds of dirty submodules' '
test_expect_success 'status ignores dirty submodules (except HEAD)' '
git -C for-submodules add -i </dev/null >output &&
- grep dirty-head output &&
- grep dirty-both-ways output &&
- ! grep dirty-otherwise output
+ test_grep dirty-head output &&
+ test_grep dirty-both-ways output &&
+ test_grep ! dirty-otherwise output
'
test_expect_success 'handle submodules' '
echo 123 >>for-submodules/dirty-otherwise/initial.t &&
force_color git -C for-submodules add -p dirty-otherwise >output 2>&1 &&
- grep "No changes" output &&
+ test_grep "No changes" output &&
force_color git -C for-submodules add -p dirty-head >output 2>&1 <y &&
git -C for-submodules ls-files --stage dirty-head >actual &&
rev="$(git -C for-submodules/dirty-head rev-parse HEAD)" &&
- grep "$rev" actual
+ test_grep "$rev" actual
'
test_expect_success 'set up pathological context' '
@@ -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
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index ec2804eea6..1384a81957 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -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
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 343b8cd191..6650d33fba 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2596,6 +2596,8 @@ test_expect_success 'double dash "git checkout"' '
--merge Z
--conflict=Z
--patch Z
+ --unified=Z
+ --inter-hunk-context=Z
--ignore-skip-worktree-bits Z
--ignore-other-worktrees Z
--recurse-submodules Z