mirror of
https://github.com/git/git
synced 2024-07-17 02:57:48 +00:00
Merge branch 'tr/reset-checkout-patch'
* tr/reset-checkout-patch: stash: simplify defaulting to "save" and reject unknown options Make test case number unique tests: disable interactive hunk selection tests if perl is not available DWIM 'git stash save -p' for 'git stash -p' Implement 'git stash save --patch' Implement 'git checkout --patch' Implement 'git reset --patch' builtin-add: refactor the meat of interactive_add() Add a small patch-mode testing library git-apply--interactive: Refactor patch mode code Make 'git stash -k' a short form for 'git stash save --keep-index'
This commit is contained in:
commit
54f0bdc811
|
@ -11,6 +11,7 @@ SYNOPSIS
|
||||||
'git checkout' [-q] [-f] [-m] [<branch>]
|
'git checkout' [-q] [-f] [-m] [<branch>]
|
||||||
'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>]
|
'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>]
|
||||||
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
|
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
|
||||||
|
'git checkout' --patch [<tree-ish>] [--] [<paths>...]
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
|
@ -25,7 +26,7 @@ use the --track or --no-track options, which will be passed to `git
|
||||||
branch`. As a convenience, --track without `-b` implies branch
|
branch`. As a convenience, --track without `-b` implies branch
|
||||||
creation; see the description of --track below.
|
creation; see the description of --track below.
|
||||||
|
|
||||||
When <paths> are given, this command does *not* switch
|
When <paths> or --patch are given, this command does *not* switch
|
||||||
branches. It updates the named paths in the working tree from
|
branches. It updates the named paths in the working tree from
|
||||||
the index file, or from a named <tree-ish> (most often a commit). In
|
the index file, or from a named <tree-ish> (most often a commit). In
|
||||||
this case, the `-b` and `--track` options are meaningless and giving
|
this case, the `-b` and `--track` options are meaningless and giving
|
||||||
|
@ -115,6 +116,16 @@ the conflicted merge in the specified paths.
|
||||||
"merge" (default) and "diff3" (in addition to what is shown by
|
"merge" (default) and "diff3" (in addition to what is shown by
|
||||||
"merge" style, shows the original contents).
|
"merge" style, shows the original contents).
|
||||||
|
|
||||||
|
-p::
|
||||||
|
--patch::
|
||||||
|
Interactively select hunks in the difference between the
|
||||||
|
<tree-ish> (or the index, if unspecified) and the working
|
||||||
|
tree. The chosen hunks are then applied in reverse to the
|
||||||
|
working tree (and if a <tree-ish> was specified, the index).
|
||||||
|
+
|
||||||
|
This means that you can use `git checkout -p` to selectively discard
|
||||||
|
edits from your current working tree.
|
||||||
|
|
||||||
<branch>::
|
<branch>::
|
||||||
Branch to checkout; if it refers to a branch (i.e., a name that,
|
Branch to checkout; if it refers to a branch (i.e., a name that,
|
||||||
when prepended with "refs/heads/", is a valid ref), then that
|
when prepended with "refs/heads/", is a valid ref), then that
|
||||||
|
|
|
@ -10,6 +10,7 @@ SYNOPSIS
|
||||||
[verse]
|
[verse]
|
||||||
'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]
|
'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]
|
||||||
'git reset' [-q] [<commit>] [--] <paths>...
|
'git reset' [-q] [<commit>] [--] <paths>...
|
||||||
|
'git reset' --patch [<commit>] [--] [<paths>...]
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
|
@ -23,8 +24,9 @@ the undo in the history.
|
||||||
If you want to undo a commit other than the latest on a branch,
|
If you want to undo a commit other than the latest on a branch,
|
||||||
linkgit:git-revert[1] is your friend.
|
linkgit:git-revert[1] is your friend.
|
||||||
|
|
||||||
The second form with 'paths' is used to revert selected paths in
|
The second and third forms with 'paths' and/or --patch are used to
|
||||||
the index from a given commit, without moving HEAD.
|
revert selected paths in the index from a given commit, without moving
|
||||||
|
HEAD.
|
||||||
|
|
||||||
|
|
||||||
OPTIONS
|
OPTIONS
|
||||||
|
@ -50,6 +52,15 @@ OPTIONS
|
||||||
and updates the files that are different between the named commit
|
and updates the files that are different between the named commit
|
||||||
and the current commit in the working tree.
|
and the current commit in the working tree.
|
||||||
|
|
||||||
|
-p::
|
||||||
|
--patch::
|
||||||
|
Interactively select hunks in the difference between the index
|
||||||
|
and <commit> (defaults to HEAD). The chosen hunks are applied
|
||||||
|
in reverse to the index.
|
||||||
|
+
|
||||||
|
This means that `git reset -p` is the opposite of `git add -p` (see
|
||||||
|
linkgit:git-add[1]).
|
||||||
|
|
||||||
-q::
|
-q::
|
||||||
Be quiet, only report errors.
|
Be quiet, only report errors.
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ SYNOPSIS
|
||||||
'git stash' drop [-q|--quiet] [<stash>]
|
'git stash' drop [-q|--quiet] [<stash>]
|
||||||
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
|
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
|
||||||
'git stash' branch <branchname> [<stash>]
|
'git stash' branch <branchname> [<stash>]
|
||||||
'git stash' [save [--keep-index] [-q|--quiet] [<message>]]
|
'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
|
||||||
'git stash' clear
|
'git stash' clear
|
||||||
'git stash' create
|
'git stash' create
|
||||||
|
|
||||||
|
@ -42,15 +42,27 @@ is also possible).
|
||||||
OPTIONS
|
OPTIONS
|
||||||
-------
|
-------
|
||||||
|
|
||||||
save [--keep-index] [-q|--quiet] [<message>]::
|
save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
|
||||||
|
|
||||||
Save your local modifications to a new 'stash', and run `git reset
|
Save your local modifications to a new 'stash', and run `git reset
|
||||||
--hard` to revert them. This is the default action when no
|
--hard` to revert them. The <message> part is optional and gives
|
||||||
subcommand is given. The <message> part is optional and gives
|
the description along with the stashed state. For quickly making
|
||||||
the description along with the stashed state.
|
a snapshot, you can omit _both_ "save" and <message>, but giving
|
||||||
|
only <message> does not trigger this action to prevent a misspelled
|
||||||
|
subcommand from making an unwanted stash.
|
||||||
+
|
+
|
||||||
If the `--keep-index` option is used, all changes already added to the
|
If the `--keep-index` option is used, all changes already added to the
|
||||||
index are left intact.
|
index are left intact.
|
||||||
|
+
|
||||||
|
With `--patch`, you can interactively select hunks from in the diff
|
||||||
|
between HEAD and the working tree to be stashed. The stash entry is
|
||||||
|
constructed such that its index state is the same as the index state
|
||||||
|
of your repository, and its worktree contains only the changes you
|
||||||
|
selected interactively. The selected changes are then rolled back
|
||||||
|
from your worktree.
|
||||||
|
+
|
||||||
|
The `--patch` option implies `--keep-index`. You can use
|
||||||
|
`--no-keep-index` to override this.
|
||||||
|
|
||||||
list [<options>]::
|
list [<options>]::
|
||||||
|
|
||||||
|
|
|
@ -131,10 +131,37 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p
|
||||||
return pathspec;
|
return pathspec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int run_add_interactive(const char *revision, const char *patch_mode,
|
||||||
|
const char **pathspec)
|
||||||
|
{
|
||||||
|
int status, ac, pc = 0;
|
||||||
|
const char **args;
|
||||||
|
|
||||||
|
if (pathspec)
|
||||||
|
while (pathspec[pc])
|
||||||
|
pc++;
|
||||||
|
|
||||||
|
args = xcalloc(sizeof(const char *), (pc + 5));
|
||||||
|
ac = 0;
|
||||||
|
args[ac++] = "add--interactive";
|
||||||
|
if (patch_mode)
|
||||||
|
args[ac++] = patch_mode;
|
||||||
|
if (revision)
|
||||||
|
args[ac++] = revision;
|
||||||
|
args[ac++] = "--";
|
||||||
|
if (pc) {
|
||||||
|
memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);
|
||||||
|
ac += pc;
|
||||||
|
}
|
||||||
|
args[ac] = NULL;
|
||||||
|
|
||||||
|
status = run_command_v_opt(args, RUN_GIT_CMD);
|
||||||
|
free(args);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
int interactive_add(int argc, const char **argv, const char *prefix)
|
int interactive_add(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
int status, ac;
|
|
||||||
const char **args;
|
|
||||||
const char **pathspec = NULL;
|
const char **pathspec = NULL;
|
||||||
|
|
||||||
if (argc) {
|
if (argc) {
|
||||||
|
@ -143,21 +170,9 @@ int interactive_add(int argc, const char **argv, const char *prefix)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
args = xcalloc(sizeof(const char *), (argc + 4));
|
return run_add_interactive(NULL,
|
||||||
ac = 0;
|
patch_interactive ? "--patch" : NULL,
|
||||||
args[ac++] = "add--interactive";
|
pathspec);
|
||||||
if (patch_interactive)
|
|
||||||
args[ac++] = "--patch";
|
|
||||||
args[ac++] = "--";
|
|
||||||
if (argc) {
|
|
||||||
memcpy(&(args[ac]), pathspec, sizeof(const char *) * argc);
|
|
||||||
ac += argc;
|
|
||||||
}
|
|
||||||
args[ac] = NULL;
|
|
||||||
|
|
||||||
status = run_command_v_opt(args, RUN_GIT_CMD);
|
|
||||||
free(args);
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int edit_patch(int argc, const char **argv, const char *prefix)
|
static int edit_patch(int argc, const char **argv, const char *prefix)
|
||||||
|
|
|
@ -566,6 +566,13 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
|
||||||
return git_xmerge_config(var, value, cb);
|
return git_xmerge_config(var, value, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int interactive_checkout(const char *revision, const char **pathspec,
|
||||||
|
struct checkout_opts *opts)
|
||||||
|
{
|
||||||
|
return run_add_interactive(revision, "--patch=checkout", pathspec);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int cmd_checkout(int argc, const char **argv, const char *prefix)
|
int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
struct checkout_opts opts;
|
struct checkout_opts opts;
|
||||||
|
@ -574,6 +581,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||||
struct branch_info new;
|
struct branch_info new;
|
||||||
struct tree *source_tree = NULL;
|
struct tree *source_tree = NULL;
|
||||||
char *conflict_style = NULL;
|
char *conflict_style = NULL;
|
||||||
|
int patch_mode = 0;
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
OPT__QUIET(&opts.quiet),
|
OPT__QUIET(&opts.quiet),
|
||||||
OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
|
OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
|
||||||
|
@ -588,6 +596,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||||
OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
|
OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
|
||||||
OPT_STRING(0, "conflict", &conflict_style, "style",
|
OPT_STRING(0, "conflict", &conflict_style, "style",
|
||||||
"conflict style (merge or diff3)"),
|
"conflict style (merge or diff3)"),
|
||||||
|
OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
int has_dash_dash;
|
int has_dash_dash;
|
||||||
|
@ -602,6 +611,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||||
argc = parse_options(argc, argv, prefix, options, checkout_usage,
|
argc = parse_options(argc, argv, prefix, options, checkout_usage,
|
||||||
PARSE_OPT_KEEP_DASHDASH);
|
PARSE_OPT_KEEP_DASHDASH);
|
||||||
|
|
||||||
|
if (patch_mode && (opts.track > 0 || opts.new_branch
|
||||||
|
|| opts.new_branch_log || opts.merge || opts.force))
|
||||||
|
die ("--patch is incompatible with all other options");
|
||||||
|
|
||||||
/* --track without -b should DWIM */
|
/* --track without -b should DWIM */
|
||||||
if (0 < opts.track && !opts.new_branch) {
|
if (0 < opts.track && !opts.new_branch) {
|
||||||
const char *argv0 = argv[0];
|
const char *argv0 = argv[0];
|
||||||
|
@ -708,6 +721,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||||
if (!pathspec)
|
if (!pathspec)
|
||||||
die("invalid path specification");
|
die("invalid path specification");
|
||||||
|
|
||||||
|
if (patch_mode)
|
||||||
|
return interactive_checkout(new.name, pathspec, &opts);
|
||||||
|
|
||||||
/* Checkout paths */
|
/* Checkout paths */
|
||||||
if (opts.new_branch) {
|
if (opts.new_branch) {
|
||||||
if (argc == 1) {
|
if (argc == 1) {
|
||||||
|
@ -723,6 +739,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||||
return checkout_paths(source_tree, pathspec, &opts);
|
return checkout_paths(source_tree, pathspec, &opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (patch_mode)
|
||||||
|
return interactive_checkout(new.name, NULL, &opts);
|
||||||
|
|
||||||
if (opts.new_branch) {
|
if (opts.new_branch) {
|
||||||
struct strbuf buf = STRBUF_INIT;
|
struct strbuf buf = STRBUF_INIT;
|
||||||
if (strbuf_check_branch_ref(&buf, opts.new_branch))
|
if (strbuf_check_branch_ref(&buf, opts.new_branch))
|
||||||
|
|
|
@ -143,6 +143,17 @@ static void update_index_from_diff(struct diff_queue_struct *q,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int interactive_reset(const char *revision, const char **argv,
|
||||||
|
const char *prefix)
|
||||||
|
{
|
||||||
|
const char **pathspec = NULL;
|
||||||
|
|
||||||
|
if (*argv)
|
||||||
|
pathspec = get_pathspec(prefix, argv);
|
||||||
|
|
||||||
|
return run_add_interactive(revision, "--patch=reset", pathspec);
|
||||||
|
}
|
||||||
|
|
||||||
static int read_from_tree(const char *prefix, const char **argv,
|
static int read_from_tree(const char *prefix, const char **argv,
|
||||||
unsigned char *tree_sha1, int refresh_flags)
|
unsigned char *tree_sha1, int refresh_flags)
|
||||||
{
|
{
|
||||||
|
@ -184,6 +195,7 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)
|
||||||
int cmd_reset(int argc, const char **argv, const char *prefix)
|
int cmd_reset(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
|
int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
|
||||||
|
int patch_mode = 0;
|
||||||
const char *rev = "HEAD";
|
const char *rev = "HEAD";
|
||||||
unsigned char sha1[20], *orig = NULL, sha1_orig[20],
|
unsigned char sha1[20], *orig = NULL, sha1_orig[20],
|
||||||
*old_orig = NULL, sha1_old_orig[20];
|
*old_orig = NULL, sha1_old_orig[20];
|
||||||
|
@ -199,6 +211,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
|
||||||
"reset HEAD, index and working tree", MERGE),
|
"reset HEAD, index and working tree", MERGE),
|
||||||
OPT_BOOLEAN('q', NULL, &quiet,
|
OPT_BOOLEAN('q', NULL, &quiet,
|
||||||
"disable showing new HEAD in hard reset and progress message"),
|
"disable showing new HEAD in hard reset and progress message"),
|
||||||
|
OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
|
||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -252,6 +265,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
|
||||||
die("Could not parse object '%s'.", rev);
|
die("Could not parse object '%s'.", rev);
|
||||||
hashcpy(sha1, commit->object.sha1);
|
hashcpy(sha1, commit->object.sha1);
|
||||||
|
|
||||||
|
if (patch_mode) {
|
||||||
|
if (reset_type != NONE)
|
||||||
|
die("--patch is incompatible with --{hard,mixed,soft}");
|
||||||
|
return interactive_reset(rev, argv + i, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
/* git reset tree [--] paths... can be used to
|
/* git reset tree [--] paths... can be used to
|
||||||
* load chosen paths from the tree into the index without
|
* load chosen paths from the tree into the index without
|
||||||
* affecting the working tree nor HEAD. */
|
* affecting the working tree nor HEAD. */
|
||||||
|
|
2
commit.h
2
commit.h
|
@ -140,6 +140,8 @@ int is_descendant_of(struct commit *, struct commit_list *);
|
||||||
int in_merge_bases(struct commit *, struct commit **, int);
|
int in_merge_bases(struct commit *, struct commit **, int);
|
||||||
|
|
||||||
extern int interactive_add(int argc, const char **argv, const char *prefix);
|
extern int interactive_add(int argc, const char **argv, const char *prefix);
|
||||||
|
extern int run_add_interactive(const char *revision, const char *patch_mode,
|
||||||
|
const char **pathspec);
|
||||||
|
|
||||||
static inline int single_parent(struct commit *commit)
|
static inline int single_parent(struct commit *commit)
|
||||||
{
|
{
|
||||||
|
|
|
@ -72,6 +72,79 @@ sub colored {
|
||||||
|
|
||||||
# command line options
|
# command line options
|
||||||
my $patch_mode;
|
my $patch_mode;
|
||||||
|
my $patch_mode_revision;
|
||||||
|
|
||||||
|
sub apply_patch;
|
||||||
|
sub apply_patch_for_checkout_commit;
|
||||||
|
sub apply_patch_for_stash;
|
||||||
|
|
||||||
|
my %patch_modes = (
|
||||||
|
'stage' => {
|
||||||
|
DIFF => 'diff-files -p',
|
||||||
|
APPLY => sub { apply_patch 'apply --cached', @_; },
|
||||||
|
APPLY_CHECK => 'apply --cached',
|
||||||
|
VERB => 'Stage',
|
||||||
|
TARGET => '',
|
||||||
|
PARTICIPLE => 'staging',
|
||||||
|
FILTER => 'file-only',
|
||||||
|
},
|
||||||
|
'stash' => {
|
||||||
|
DIFF => 'diff-index -p HEAD',
|
||||||
|
APPLY => sub { apply_patch 'apply --cached', @_; },
|
||||||
|
APPLY_CHECK => 'apply --cached',
|
||||||
|
VERB => 'Stash',
|
||||||
|
TARGET => '',
|
||||||
|
PARTICIPLE => 'stashing',
|
||||||
|
FILTER => undef,
|
||||||
|
},
|
||||||
|
'reset_head' => {
|
||||||
|
DIFF => 'diff-index -p --cached',
|
||||||
|
APPLY => sub { apply_patch 'apply -R --cached', @_; },
|
||||||
|
APPLY_CHECK => 'apply -R --cached',
|
||||||
|
VERB => 'Unstage',
|
||||||
|
TARGET => '',
|
||||||
|
PARTICIPLE => 'unstaging',
|
||||||
|
FILTER => 'index-only',
|
||||||
|
},
|
||||||
|
'reset_nothead' => {
|
||||||
|
DIFF => 'diff-index -R -p --cached',
|
||||||
|
APPLY => sub { apply_patch 'apply --cached', @_; },
|
||||||
|
APPLY_CHECK => 'apply --cached',
|
||||||
|
VERB => 'Apply',
|
||||||
|
TARGET => ' to index',
|
||||||
|
PARTICIPLE => 'applying',
|
||||||
|
FILTER => 'index-only',
|
||||||
|
},
|
||||||
|
'checkout_index' => {
|
||||||
|
DIFF => 'diff-files -p',
|
||||||
|
APPLY => sub { apply_patch 'apply -R', @_; },
|
||||||
|
APPLY_CHECK => 'apply -R',
|
||||||
|
VERB => 'Discard',
|
||||||
|
TARGET => ' from worktree',
|
||||||
|
PARTICIPLE => 'discarding',
|
||||||
|
FILTER => 'file-only',
|
||||||
|
},
|
||||||
|
'checkout_head' => {
|
||||||
|
DIFF => 'diff-index -p',
|
||||||
|
APPLY => sub { apply_patch_for_checkout_commit '-R', @_ },
|
||||||
|
APPLY_CHECK => 'apply -R',
|
||||||
|
VERB => 'Discard',
|
||||||
|
TARGET => ' from index and worktree',
|
||||||
|
PARTICIPLE => 'discarding',
|
||||||
|
FILTER => undef,
|
||||||
|
},
|
||||||
|
'checkout_nothead' => {
|
||||||
|
DIFF => 'diff-index -R -p',
|
||||||
|
APPLY => sub { apply_patch_for_checkout_commit '', @_ },
|
||||||
|
APPLY_CHECK => 'apply',
|
||||||
|
VERB => 'Apply',
|
||||||
|
TARGET => ' to index and worktree',
|
||||||
|
PARTICIPLE => 'applying',
|
||||||
|
FILTER => undef,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
my %patch_mode_flavour = %{$patch_modes{stage}};
|
||||||
|
|
||||||
sub run_cmd_pipe {
|
sub run_cmd_pipe {
|
||||||
if ($^O eq 'MSWin32' || $^O eq 'msys') {
|
if ($^O eq 'MSWin32' || $^O eq 'msys') {
|
||||||
|
@ -190,7 +263,14 @@ sub list_modified {
|
||||||
return if (!@tracked);
|
return if (!@tracked);
|
||||||
}
|
}
|
||||||
|
|
||||||
my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
|
my $reference;
|
||||||
|
if (defined $patch_mode_revision and $patch_mode_revision ne 'HEAD') {
|
||||||
|
$reference = $patch_mode_revision;
|
||||||
|
} elsif (is_initial_commit()) {
|
||||||
|
$reference = get_empty_tree();
|
||||||
|
} else {
|
||||||
|
$reference = 'HEAD';
|
||||||
|
}
|
||||||
for (run_cmd_pipe(qw(git diff-index --cached
|
for (run_cmd_pipe(qw(git diff-index --cached
|
||||||
--numstat --summary), $reference,
|
--numstat --summary), $reference,
|
||||||
'--', @tracked)) {
|
'--', @tracked)) {
|
||||||
|
@ -613,12 +693,24 @@ sub add_untracked_cmd {
|
||||||
print "\n";
|
print "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub run_git_apply {
|
||||||
|
my $cmd = shift;
|
||||||
|
my $fh;
|
||||||
|
open $fh, '| git ' . $cmd;
|
||||||
|
print $fh @_;
|
||||||
|
return close $fh;
|
||||||
|
}
|
||||||
|
|
||||||
sub parse_diff {
|
sub parse_diff {
|
||||||
my ($path) = @_;
|
my ($path) = @_;
|
||||||
my @diff = run_cmd_pipe(qw(git diff-files -p --), $path);
|
my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
|
||||||
|
if (defined $patch_mode_revision) {
|
||||||
|
push @diff_cmd, $patch_mode_revision;
|
||||||
|
}
|
||||||
|
my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path);
|
||||||
my @colored = ();
|
my @colored = ();
|
||||||
if ($diff_use_color) {
|
if ($diff_use_color) {
|
||||||
@colored = run_cmd_pipe(qw(git diff-files -p --color --), $path);
|
@colored = run_cmd_pipe("git", @diff_cmd, qw(--color --), $path);
|
||||||
}
|
}
|
||||||
my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
|
my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
|
||||||
|
|
||||||
|
@ -881,6 +973,7 @@ sub edit_hunk_manually {
|
||||||
or die "failed to open hunk edit file for writing: " . $!;
|
or die "failed to open hunk edit file for writing: " . $!;
|
||||||
print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n";
|
print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n";
|
||||||
print $fh @$oldtext;
|
print $fh @$oldtext;
|
||||||
|
my $participle = $patch_mode_flavour{PARTICIPLE};
|
||||||
print $fh <<EOF;
|
print $fh <<EOF;
|
||||||
# ---
|
# ---
|
||||||
# To remove '-' lines, make them ' ' lines (context).
|
# To remove '-' lines, make them ' ' lines (context).
|
||||||
|
@ -888,7 +981,7 @@ sub edit_hunk_manually {
|
||||||
# Lines starting with # will be removed.
|
# Lines starting with # will be removed.
|
||||||
#
|
#
|
||||||
# If the patch applies cleanly, the edited hunk will immediately be
|
# If the patch applies cleanly, the edited hunk will immediately be
|
||||||
# marked for staging. If it does not apply cleanly, you will be given
|
# marked for $participle. If it does not apply cleanly, you will be given
|
||||||
# an opportunity to edit again. If all lines of the hunk are removed,
|
# an opportunity to edit again. If all lines of the hunk are removed,
|
||||||
# then the edit is aborted and the hunk is left unchanged.
|
# then the edit is aborted and the hunk is left unchanged.
|
||||||
EOF
|
EOF
|
||||||
|
@ -922,11 +1015,8 @@ sub edit_hunk_manually {
|
||||||
|
|
||||||
sub diff_applies {
|
sub diff_applies {
|
||||||
my $fh;
|
my $fh;
|
||||||
open $fh, '| git apply --recount --cached --check';
|
return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check',
|
||||||
for my $h (@_) {
|
map { @{$_->{TEXT}} } @_);
|
||||||
print $fh @{$h->{TEXT}};
|
|
||||||
}
|
|
||||||
return close $fh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _restore_terminal_and_die {
|
sub _restore_terminal_and_die {
|
||||||
|
@ -992,12 +1082,14 @@ sub edit_hunk_loop {
|
||||||
}
|
}
|
||||||
|
|
||||||
sub help_patch_cmd {
|
sub help_patch_cmd {
|
||||||
print colored $help_color, <<\EOF ;
|
my $verb = lc $patch_mode_flavour{VERB};
|
||||||
y - stage this hunk
|
my $target = $patch_mode_flavour{TARGET};
|
||||||
n - do not stage this hunk
|
print colored $help_color, <<EOF ;
|
||||||
q - quit, do not stage this hunk nor any of the remaining ones
|
y - $verb this hunk$target
|
||||||
a - stage this and all the remaining hunks in the file
|
n - do not $verb this hunk$target
|
||||||
d - do not stage this hunk nor any of the remaining hunks in the file
|
q - quit, do not $verb this hunk nor any of the remaining ones
|
||||||
|
a - $verb this and all the remaining hunks in the file
|
||||||
|
d - do not $verb this hunk nor any of the remaining hunks in the file
|
||||||
g - select a hunk to go to
|
g - select a hunk to go to
|
||||||
/ - search for a hunk matching the given regex
|
/ - search for a hunk matching the given regex
|
||||||
j - leave this hunk undecided, see next undecided hunk
|
j - leave this hunk undecided, see next undecided hunk
|
||||||
|
@ -1010,8 +1102,40 @@ sub help_patch_cmd {
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub apply_patch {
|
||||||
|
my $cmd = shift;
|
||||||
|
my $ret = run_git_apply $cmd . ' --recount', @_;
|
||||||
|
if (!$ret) {
|
||||||
|
print STDERR @_;
|
||||||
|
}
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub apply_patch_for_checkout_commit {
|
||||||
|
my $reverse = shift;
|
||||||
|
my $applies_index = run_git_apply 'apply '.$reverse.' --cached --recount --check', @_;
|
||||||
|
my $applies_worktree = run_git_apply 'apply '.$reverse.' --recount --check', @_;
|
||||||
|
|
||||||
|
if ($applies_worktree && $applies_index) {
|
||||||
|
run_git_apply 'apply '.$reverse.' --cached --recount', @_;
|
||||||
|
run_git_apply 'apply '.$reverse.' --recount', @_;
|
||||||
|
return 1;
|
||||||
|
} elsif (!$applies_index) {
|
||||||
|
print colored $error_color, "The selected hunks do not apply to the index!\n";
|
||||||
|
if (prompt_yesno "Apply them to the worktree anyway? ") {
|
||||||
|
return run_git_apply 'apply '.$reverse.' --recount', @_;
|
||||||
|
} else {
|
||||||
|
print colored $error_color, "Nothing was applied.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print STDERR @_;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub patch_update_cmd {
|
sub patch_update_cmd {
|
||||||
my @all_mods = list_modified('file-only');
|
my @all_mods = list_modified($patch_mode_flavour{FILTER});
|
||||||
my @mods = grep { !($_->{BINARY}) } @all_mods;
|
my @mods = grep { !($_->{BINARY}) } @all_mods;
|
||||||
my @them;
|
my @them;
|
||||||
|
|
||||||
|
@ -1142,8 +1266,9 @@ sub patch_update_file {
|
||||||
for (@{$hunk[$ix]{DISPLAY}}) {
|
for (@{$hunk[$ix]{DISPLAY}}) {
|
||||||
print;
|
print;
|
||||||
}
|
}
|
||||||
print colored $prompt_color, 'Stage ',
|
print colored $prompt_color, $patch_mode_flavour{VERB},
|
||||||
($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),
|
($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'),
|
||||||
|
$patch_mode_flavour{TARGET},
|
||||||
" [y,n,q,a,d,/$other,?]? ";
|
" [y,n,q,a,d,/$other,?]? ";
|
||||||
my $line = prompt_single_character;
|
my $line = prompt_single_character;
|
||||||
if ($line) {
|
if ($line) {
|
||||||
|
@ -1317,16 +1442,9 @@ sub patch_update_file {
|
||||||
|
|
||||||
if (@result) {
|
if (@result) {
|
||||||
my $fh;
|
my $fh;
|
||||||
|
my @patch = (@{$head->{TEXT}}, @result);
|
||||||
open $fh, '| git apply --cached --recount';
|
my $apply_routine = $patch_mode_flavour{APPLY};
|
||||||
for (@{$head->{TEXT}}, @result) {
|
&$apply_routine(@patch);
|
||||||
print $fh $_;
|
|
||||||
}
|
|
||||||
if (!close $fh) {
|
|
||||||
for (@{$head->{TEXT}}, @result) {
|
|
||||||
print STDERR $_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1367,11 +1485,41 @@ sub help_cmd {
|
||||||
sub process_args {
|
sub process_args {
|
||||||
return unless @ARGV;
|
return unless @ARGV;
|
||||||
my $arg = shift @ARGV;
|
my $arg = shift @ARGV;
|
||||||
if ($arg eq "--patch") {
|
if ($arg =~ /--patch(?:=(.*))?/) {
|
||||||
$patch_mode = 1;
|
if (defined $1) {
|
||||||
$arg = shift @ARGV or die "missing --";
|
if ($1 eq 'reset') {
|
||||||
|
$patch_mode = 'reset_head';
|
||||||
|
$patch_mode_revision = 'HEAD';
|
||||||
|
$arg = shift @ARGV or die "missing --";
|
||||||
|
if ($arg ne '--') {
|
||||||
|
$patch_mode_revision = $arg;
|
||||||
|
$patch_mode = ($arg eq 'HEAD' ?
|
||||||
|
'reset_head' : 'reset_nothead');
|
||||||
|
$arg = shift @ARGV or die "missing --";
|
||||||
|
}
|
||||||
|
} elsif ($1 eq 'checkout') {
|
||||||
|
$arg = shift @ARGV or die "missing --";
|
||||||
|
if ($arg eq '--') {
|
||||||
|
$patch_mode = 'checkout_index';
|
||||||
|
} else {
|
||||||
|
$patch_mode_revision = $arg;
|
||||||
|
$patch_mode = ($arg eq 'HEAD' ?
|
||||||
|
'checkout_head' : 'checkout_nothead');
|
||||||
|
$arg = shift @ARGV or die "missing --";
|
||||||
|
}
|
||||||
|
} elsif ($1 eq 'stage' or $1 eq 'stash') {
|
||||||
|
$patch_mode = $1;
|
||||||
|
$arg = shift @ARGV or die "missing --";
|
||||||
|
} else {
|
||||||
|
die "unknown --patch mode: $1";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$patch_mode = 'stage';
|
||||||
|
$arg = shift @ARGV or die "missing --";
|
||||||
|
}
|
||||||
die "invalid argument $arg, expecting --"
|
die "invalid argument $arg, expecting --"
|
||||||
unless $arg eq "--";
|
unless $arg eq "--";
|
||||||
|
%patch_mode_flavour = %{$patch_modes{$patch_mode}};
|
||||||
}
|
}
|
||||||
elsif ($arg ne "--") {
|
elsif ($arg ne "--") {
|
||||||
die "invalid argument $arg, expecting --";
|
die "invalid argument $arg, expecting --";
|
||||||
|
|
113
git-stash.sh
113
git-stash.sh
|
@ -7,7 +7,7 @@ USAGE="list [<options>]
|
||||||
or: $dashless drop [-q|--quiet] [<stash>]
|
or: $dashless drop [-q|--quiet] [<stash>]
|
||||||
or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
|
or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
|
||||||
or: $dashless branch <branchname> [<stash>]
|
or: $dashless branch <branchname> [<stash>]
|
||||||
or: $dashless [save [--keep-index] [-q|--quiet] [<message>]]
|
or: $dashless [save [-k|--keep-index] [-q|--quiet] [<message>]]
|
||||||
or: $dashless clear"
|
or: $dashless clear"
|
||||||
|
|
||||||
SUBDIRECTORY_OK=Yes
|
SUBDIRECTORY_OK=Yes
|
||||||
|
@ -21,6 +21,14 @@ trap 'rm -f "$TMP-*"' 0
|
||||||
|
|
||||||
ref_stash=refs/stash
|
ref_stash=refs/stash
|
||||||
|
|
||||||
|
if git config --get-colorbool color.interactive; then
|
||||||
|
help_color="$(git config --get-color color.interactive.help 'red bold')"
|
||||||
|
reset_color="$(git config --get-color '' reset)"
|
||||||
|
else
|
||||||
|
help_color=
|
||||||
|
reset_color=
|
||||||
|
fi
|
||||||
|
|
||||||
no_changes () {
|
no_changes () {
|
||||||
git diff-index --quiet --cached HEAD --ignore-submodules -- &&
|
git diff-index --quiet --cached HEAD --ignore-submodules -- &&
|
||||||
git diff-files --quiet --ignore-submodules
|
git diff-files --quiet --ignore-submodules
|
||||||
|
@ -68,19 +76,44 @@ create_stash () {
|
||||||
git commit-tree $i_tree -p $b_commit) ||
|
git commit-tree $i_tree -p $b_commit) ||
|
||||||
die "Cannot save the current index state"
|
die "Cannot save the current index state"
|
||||||
|
|
||||||
# state of the working tree
|
if test -z "$patch_mode"
|
||||||
w_tree=$( (
|
then
|
||||||
|
|
||||||
|
# state of the working tree
|
||||||
|
w_tree=$( (
|
||||||
|
rm -f "$TMP-index" &&
|
||||||
|
cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" &&
|
||||||
|
GIT_INDEX_FILE="$TMP-index" &&
|
||||||
|
export GIT_INDEX_FILE &&
|
||||||
|
git read-tree -m $i_tree &&
|
||||||
|
git add -u &&
|
||||||
|
git write-tree &&
|
||||||
|
rm -f "$TMP-index"
|
||||||
|
) ) ||
|
||||||
|
die "Cannot save the current worktree state"
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
rm -f "$TMP-index" &&
|
rm -f "$TMP-index" &&
|
||||||
cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" &&
|
GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&
|
||||||
GIT_INDEX_FILE="$TMP-index" &&
|
|
||||||
export GIT_INDEX_FILE &&
|
# find out what the user wants
|
||||||
git read-tree -m $i_tree &&
|
GIT_INDEX_FILE="$TMP-index" \
|
||||||
git add -u &&
|
git add--interactive --patch=stash -- &&
|
||||||
git write-tree &&
|
|
||||||
rm -f "$TMP-index"
|
# state of the working tree
|
||||||
) ) ||
|
w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
|
||||||
die "Cannot save the current worktree state"
|
die "Cannot save the current worktree state"
|
||||||
|
|
||||||
|
git diff-tree -p HEAD $w_tree > "$TMP-patch" &&
|
||||||
|
test -s "$TMP-patch" ||
|
||||||
|
die "No changes selected"
|
||||||
|
|
||||||
|
rm -f "$TMP-index" ||
|
||||||
|
die "Cannot remove temporary index (can't happen)"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
# create the stash
|
# create the stash
|
||||||
if test -z "$stash_msg"
|
if test -z "$stash_msg"
|
||||||
then
|
then
|
||||||
|
@ -95,15 +128,31 @@ create_stash () {
|
||||||
|
|
||||||
save_stash () {
|
save_stash () {
|
||||||
keep_index=
|
keep_index=
|
||||||
|
patch_mode=
|
||||||
while test $# != 0
|
while test $# != 0
|
||||||
do
|
do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--keep-index)
|
-k|--keep-index)
|
||||||
|
keep_index=t
|
||||||
|
;;
|
||||||
|
--no-keep-index)
|
||||||
|
keep_index=
|
||||||
|
;;
|
||||||
|
-p|--patch)
|
||||||
|
patch_mode=t
|
||||||
keep_index=t
|
keep_index=t
|
||||||
;;
|
;;
|
||||||
-q|--quiet)
|
-q|--quiet)
|
||||||
GIT_QUIET=t
|
GIT_QUIET=t
|
||||||
;;
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "error: unknown option for 'stash save': $1"
|
||||||
|
usage
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
break
|
break
|
||||||
;;
|
;;
|
||||||
|
@ -131,11 +180,22 @@ save_stash () {
|
||||||
die "Cannot save the current status"
|
die "Cannot save the current status"
|
||||||
say Saved working directory and index state "$stash_msg"
|
say Saved working directory and index state "$stash_msg"
|
||||||
|
|
||||||
git reset --hard ${GIT_QUIET:+-q}
|
if test -z "$patch_mode"
|
||||||
|
|
||||||
if test -n "$keep_index" && test -n $i_tree
|
|
||||||
then
|
then
|
||||||
git read-tree --reset -u $i_tree
|
git reset --hard ${GIT_QUIET:+-q}
|
||||||
|
|
||||||
|
if test -n "$keep_index" && test -n $i_tree
|
||||||
|
then
|
||||||
|
git read-tree --reset -u $i_tree
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
git apply -R < "$TMP-patch" ||
|
||||||
|
die "Cannot remove worktree changes"
|
||||||
|
|
||||||
|
if test -z "$keep_index"
|
||||||
|
then
|
||||||
|
git reset
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,6 +367,18 @@ apply_to_branch () {
|
||||||
drop_stash $stash
|
drop_stash $stash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The default command is "save" if nothing but options are given
|
||||||
|
seen_non_option=
|
||||||
|
for opt
|
||||||
|
do
|
||||||
|
case "$opt" in
|
||||||
|
-*) ;;
|
||||||
|
*) seen_non_option=t; break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
test -n "$seen_non_option" || set "save" "$@"
|
||||||
|
|
||||||
# Main command set
|
# Main command set
|
||||||
case "$1" in
|
case "$1" in
|
||||||
list)
|
list)
|
||||||
|
@ -358,12 +430,13 @@ branch)
|
||||||
apply_to_branch "$@"
|
apply_to_branch "$@"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
if test $# -eq 0
|
case $# in
|
||||||
then
|
0)
|
||||||
save_stash &&
|
save_stash &&
|
||||||
say '(To restore them type "git stash apply")'
|
say '(To restore them type "git stash apply")'
|
||||||
else
|
;;
|
||||||
|
*)
|
||||||
usage
|
usage
|
||||||
fi
|
esac
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
41
t/lib-patch-mode.sh
Executable file
41
t/lib-patch-mode.sh
Executable file
|
@ -0,0 +1,41 @@
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
if ! test_have_prereq PERL; then
|
||||||
|
say 'skipping --patch tests, perl not available'
|
||||||
|
test_done
|
||||||
|
fi
|
||||||
|
|
||||||
|
set_state () {
|
||||||
|
echo "$3" > "$1" &&
|
||||||
|
git add "$1" &&
|
||||||
|
echo "$2" > "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
save_state () {
|
||||||
|
noslash="$(echo "$1" | tr / _)" &&
|
||||||
|
cat "$1" > _worktree_"$noslash" &&
|
||||||
|
git show :"$1" > _index_"$noslash"
|
||||||
|
}
|
||||||
|
|
||||||
|
set_and_save_state () {
|
||||||
|
set_state "$@" &&
|
||||||
|
save_state "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_state () {
|
||||||
|
test "$(cat "$1")" = "$2" &&
|
||||||
|
test "$(git show :"$1")" = "$3"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_saved_state () {
|
||||||
|
noslash="$(echo "$1" | tr / _)" &&
|
||||||
|
verify_state "$1" "$(cat _worktree_"$noslash")" "$(cat _index_"$noslash")"
|
||||||
|
}
|
||||||
|
|
||||||
|
save_head () {
|
||||||
|
git rev-parse HEAD > _head
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_saved_head () {
|
||||||
|
test "$(cat _head)" = "$(git rev-parse HEAD)"
|
||||||
|
}
|
107
t/t2016-checkout-patch.sh
Executable file
107
t/t2016-checkout-patch.sh
Executable file
|
@ -0,0 +1,107 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='git checkout --patch'
|
||||||
|
|
||||||
|
. ./lib-patch-mode.sh
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
mkdir dir &&
|
||||||
|
echo parent > dir/foo &&
|
||||||
|
echo dummy > bar &&
|
||||||
|
git add bar dir/foo &&
|
||||||
|
git commit -m initial &&
|
||||||
|
test_tick &&
|
||||||
|
test_commit second dir/foo head &&
|
||||||
|
set_and_save_state bar bar_work bar_index &&
|
||||||
|
save_head
|
||||||
|
'
|
||||||
|
|
||||||
|
# note: bar sorts before dir/foo, so the first 'n' is always to skip 'bar'
|
||||||
|
|
||||||
|
test_expect_success 'saying "n" does nothing' '
|
||||||
|
set_and_save_state dir/foo work head &&
|
||||||
|
(echo n; echo n) | git checkout -p &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
verify_saved_state dir/foo
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git checkout -p' '
|
||||||
|
(echo n; echo y) | git checkout -p &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
verify_state dir/foo head head
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git checkout -p with staged changes' '
|
||||||
|
set_state dir/foo work index
|
||||||
|
(echo n; echo y) | git checkout -p &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
verify_state dir/foo index index
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git checkout -p HEAD with NO staged changes: abort' '
|
||||||
|
set_and_save_state dir/foo work head &&
|
||||||
|
(echo n; echo y; echo n) | git checkout -p HEAD &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
verify_saved_state dir/foo
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git checkout -p HEAD with NO staged changes: apply' '
|
||||||
|
(echo n; echo y; echo y) | git checkout -p HEAD &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
verify_state dir/foo head head
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git checkout -p HEAD with change already staged' '
|
||||||
|
set_state dir/foo index index
|
||||||
|
# the third n is to get out in case it mistakenly does not apply
|
||||||
|
(echo n; echo y; echo n) | git checkout -p HEAD &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
verify_state dir/foo head head
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git checkout -p HEAD^' '
|
||||||
|
# the third n is to get out in case it mistakenly does not apply
|
||||||
|
(echo n; echo y; echo n) | git checkout -p HEAD^ &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
verify_state dir/foo parent parent
|
||||||
|
'
|
||||||
|
|
||||||
|
# The idea in the rest is that bar sorts first, so we always say 'y'
|
||||||
|
# first and if the path limiter fails it'll apply to bar instead of
|
||||||
|
# dir/foo. There's always an extra 'n' to reject edits to dir/foo in
|
||||||
|
# the failure case (and thus get out of the loop).
|
||||||
|
|
||||||
|
test_expect_success 'path limiting works: dir' '
|
||||||
|
set_state dir/foo work head &&
|
||||||
|
(echo y; echo n) | git checkout -p dir &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
verify_state dir/foo head head
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'path limiting works: -- dir' '
|
||||||
|
set_state dir/foo work head &&
|
||||||
|
(echo y; echo n) | git checkout -p -- dir &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
verify_state dir/foo head head
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'path limiting works: HEAD^ -- dir' '
|
||||||
|
# the third n is to get out in case it mistakenly does not apply
|
||||||
|
(echo y; echo n; echo n) | git checkout -p HEAD^ -- dir &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
verify_state dir/foo parent parent
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'path limiting works: foo inside dir' '
|
||||||
|
set_state dir/foo work head &&
|
||||||
|
# the third n is to get out in case it mistakenly does not apply
|
||||||
|
(echo y; echo n; echo n) | (cd dir && git checkout -p foo) &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
verify_state dir/foo head head
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'none of this moved HEAD' '
|
||||||
|
verify_saved_head
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
|
@ -200,4 +200,23 @@ test_expect_success 'drop -q is quiet' '
|
||||||
test ! -s output.out
|
test ! -s output.out
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'stash -k' '
|
||||||
|
echo bar3 > file &&
|
||||||
|
echo bar4 > file2 &&
|
||||||
|
git add file2 &&
|
||||||
|
git stash -k &&
|
||||||
|
test bar,bar4 = $(cat file),$(cat file2)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'stash --invalid-option' '
|
||||||
|
echo bar5 > file &&
|
||||||
|
echo bar6 > file2 &&
|
||||||
|
git add file2 &&
|
||||||
|
test_must_fail git stash --invalid-option &&
|
||||||
|
test_must_fail git stash save --invalid-option &&
|
||||||
|
test bar5,bar6 = $(cat file),$(cat file2) &&
|
||||||
|
git stash -- -message-starting-with-dash &&
|
||||||
|
test bar,bar2 = $(cat file),$(cat file2)
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
55
t/t3904-stash-patch.sh
Executable file
55
t/t3904-stash-patch.sh
Executable file
|
@ -0,0 +1,55 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='git checkout --patch'
|
||||||
|
. ./lib-patch-mode.sh
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
mkdir dir &&
|
||||||
|
echo parent > dir/foo &&
|
||||||
|
echo dummy > bar &&
|
||||||
|
git add bar dir/foo &&
|
||||||
|
git commit -m initial &&
|
||||||
|
test_tick &&
|
||||||
|
test_commit second dir/foo head &&
|
||||||
|
echo index > dir/foo &&
|
||||||
|
git add dir/foo &&
|
||||||
|
set_and_save_state bar bar_work bar_index &&
|
||||||
|
save_head
|
||||||
|
'
|
||||||
|
|
||||||
|
# note: bar sorts before dir, so the first 'n' is always to skip 'bar'
|
||||||
|
|
||||||
|
test_expect_success 'saying "n" does nothing' '
|
||||||
|
set_state dir/foo work index
|
||||||
|
(echo n; echo n) | test_must_fail git stash save -p &&
|
||||||
|
verify_state dir/foo work index &&
|
||||||
|
verify_saved_state bar
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git stash -p' '
|
||||||
|
(echo n; echo y) | git stash save -p &&
|
||||||
|
verify_state dir/foo head index &&
|
||||||
|
verify_saved_state bar &&
|
||||||
|
git reset --hard &&
|
||||||
|
git stash apply &&
|
||||||
|
verify_state dir/foo work head &&
|
||||||
|
verify_state bar dummy dummy
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git stash -p --no-keep-index' '
|
||||||
|
set_state dir/foo work index &&
|
||||||
|
set_state bar bar_work bar_index &&
|
||||||
|
(echo n; echo y) | git stash save -p --no-keep-index &&
|
||||||
|
verify_state dir/foo head head &&
|
||||||
|
verify_state bar bar_work dummy &&
|
||||||
|
git reset --hard &&
|
||||||
|
git stash apply --index &&
|
||||||
|
verify_state dir/foo work index &&
|
||||||
|
verify_state bar dummy bar_index
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'none of this moved HEAD' '
|
||||||
|
verify_saved_head
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
69
t/t7105-reset-patch.sh
Executable file
69
t/t7105-reset-patch.sh
Executable file
|
@ -0,0 +1,69 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='git reset --patch'
|
||||||
|
. ./lib-patch-mode.sh
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
mkdir dir &&
|
||||||
|
echo parent > dir/foo &&
|
||||||
|
echo dummy > bar &&
|
||||||
|
git add dir &&
|
||||||
|
git commit -m initial &&
|
||||||
|
test_tick &&
|
||||||
|
test_commit second dir/foo head &&
|
||||||
|
set_and_save_state bar bar_work bar_index &&
|
||||||
|
save_head
|
||||||
|
'
|
||||||
|
|
||||||
|
# note: bar sorts before foo, so the first 'n' is always to skip 'bar'
|
||||||
|
|
||||||
|
test_expect_success 'saying "n" does nothing' '
|
||||||
|
set_and_save_state dir/foo work work
|
||||||
|
(echo n; echo n) | git reset -p &&
|
||||||
|
verify_saved_state dir/foo &&
|
||||||
|
verify_saved_state bar
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git reset -p' '
|
||||||
|
(echo n; echo y) | git reset -p &&
|
||||||
|
verify_state dir/foo work head &&
|
||||||
|
verify_saved_state bar
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git reset -p HEAD^' '
|
||||||
|
(echo n; echo y) | git reset -p HEAD^ &&
|
||||||
|
verify_state dir/foo work parent &&
|
||||||
|
verify_saved_state bar
|
||||||
|
'
|
||||||
|
|
||||||
|
# The idea in the rest is that bar sorts first, so we always say 'y'
|
||||||
|
# first and if the path limiter fails it'll apply to bar instead of
|
||||||
|
# dir/foo. There's always an extra 'n' to reject edits to dir/foo in
|
||||||
|
# the failure case (and thus get out of the loop).
|
||||||
|
|
||||||
|
test_expect_success 'git reset -p dir' '
|
||||||
|
set_state dir/foo work work
|
||||||
|
(echo y; echo n) | git reset -p dir &&
|
||||||
|
verify_state dir/foo work head &&
|
||||||
|
verify_saved_state bar
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git reset -p -- foo (inside dir)' '
|
||||||
|
set_state dir/foo work work
|
||||||
|
(echo y; echo n) | (cd dir && git reset -p -- foo) &&
|
||||||
|
verify_state dir/foo work head &&
|
||||||
|
verify_saved_state bar
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git reset -p HEAD^ -- dir' '
|
||||||
|
(echo y; echo n) | git reset -p HEAD^ -- dir &&
|
||||||
|
verify_state dir/foo work parent &&
|
||||||
|
verify_saved_state bar
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'none of this moved HEAD' '
|
||||||
|
verify_saved_head
|
||||||
|
'
|
||||||
|
|
||||||
|
|
||||||
|
test_done
|
Loading…
Reference in a new issue