stash: implement '--staged' option for 'push' and 'save'
Stash only the changes that are staged. This mode allows to easily stash-out for later reuse some changes unrelated to the current work in progress. Unlike 'stash push --patch', --staged supports use of any tool to select the changes to stash-out, including, but not limited to 'git add --interactive'. Signed-off-by: Sergey Organov <sorganov@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
parent
f443b226ca
commit
41a28eb6c1
|
@ -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' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
|
'git stash' [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]
|
||||||
[-u|--include-untracked] [-a|--all] [-m|--message <message>]
|
[-u|--include-untracked] [-a|--all] [-m|--message <message>]
|
||||||
[--pathspec-from-file=<file> [--pathspec-file-nul]]
|
[--pathspec-from-file=<file> [--pathspec-file-nul]]
|
||||||
[--] [<pathspec>...]]
|
[--] [<pathspec>...]]
|
||||||
|
@ -47,7 +47,7 @@ stash index (e.g. the integer `n` is equivalent to `stash@{n}`).
|
||||||
COMMANDS
|
COMMANDS
|
||||||
--------
|
--------
|
||||||
|
|
||||||
push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--pathspec-from-file=<file> [--pathspec-file-nul]] [--] [<pathspec>...]::
|
push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--pathspec-from-file=<file> [--pathspec-file-nul]] [--] [<pathspec>...]::
|
||||||
|
|
||||||
Save your local modifications to a new 'stash entry' and roll them
|
Save your local modifications to a new 'stash entry' and roll them
|
||||||
back to HEAD (in the working tree and in the index).
|
back to HEAD (in the working tree and in the index).
|
||||||
|
@ -60,7 +60,7 @@ subcommand from making an unwanted stash entry. The two exceptions to this
|
||||||
are `stash -p` which acts as alias for `stash push -p` and pathspec elements,
|
are `stash -p` which acts as alias for `stash push -p` and pathspec elements,
|
||||||
which are allowed after a double hyphen `--` for disambiguation.
|
which are allowed after a double hyphen `--` for disambiguation.
|
||||||
|
|
||||||
save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
|
save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
|
||||||
|
|
||||||
This option is deprecated in favour of 'git stash push'. It
|
This option is deprecated in favour of 'git stash push'. It
|
||||||
differs from "stash push" in that it cannot take pathspec.
|
differs from "stash push" in that it cannot take pathspec.
|
||||||
|
@ -205,6 +205,16 @@ to learn how to operate the `--patch` mode.
|
||||||
The `--patch` option implies `--keep-index`. You can use
|
The `--patch` option implies `--keep-index`. You can use
|
||||||
`--no-keep-index` to override this.
|
`--no-keep-index` to override this.
|
||||||
|
|
||||||
|
-S::
|
||||||
|
--staged::
|
||||||
|
This option is only valid for `push` and `save` commands.
|
||||||
|
+
|
||||||
|
Stash only the changes that are currently staged. This is similar to
|
||||||
|
basic `git commit` except the state is committed to the stash instead
|
||||||
|
of current branch.
|
||||||
|
+
|
||||||
|
The `--patch` option has priority over this one.
|
||||||
|
|
||||||
--pathspec-from-file=<file>::
|
--pathspec-from-file=<file>::
|
||||||
This option is only valid for `push` command.
|
This option is only valid for `push` command.
|
||||||
+
|
+
|
||||||
|
@ -341,6 +351,24 @@ $ edit/build/test remaining parts
|
||||||
$ git commit foo -m 'Remaining parts'
|
$ git commit foo -m 'Remaining parts'
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
Saving unrelated changes for future use::
|
||||||
|
|
||||||
|
When you are in the middle of massive changes and you find some
|
||||||
|
unrelated issue that you don't want to forget to fix, you can do the
|
||||||
|
change(s), stage them, and use `git stash push --staged` to stash them
|
||||||
|
out for future use. This is similar to committing the staged changes,
|
||||||
|
only the commit ends-up being in the stash and not on the current branch.
|
||||||
|
+
|
||||||
|
----------------------------------------------------------------
|
||||||
|
# ... hack hack hack ...
|
||||||
|
$ git add --patch foo # add unrelated changes to the index
|
||||||
|
$ git stash push --staged # save these changes to the stash
|
||||||
|
# ... hack hack hack, finish curent changes ...
|
||||||
|
$ git commit -m 'Massive' # commit fully tested changes
|
||||||
|
$ git switch fixup-branch # switch to another branch
|
||||||
|
$ git stash pop # to finish work on the saved changes
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
Recovering stash entries that were cleared/dropped erroneously::
|
Recovering stash entries that were cleared/dropped erroneously::
|
||||||
|
|
||||||
If you mistakenly drop or clear stash entries, they cannot be recovered
|
If you mistakenly drop or clear stash entries, they cannot be recovered
|
||||||
|
|
|
@ -27,11 +27,11 @@ static const char * const git_stash_usage[] = {
|
||||||
N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
|
N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
|
||||||
N_("git stash branch <branchname> [<stash>]"),
|
N_("git stash branch <branchname> [<stash>]"),
|
||||||
"git stash clear",
|
"git stash clear",
|
||||||
N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
|
N_("git stash [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\n"
|
||||||
" [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
|
" [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
|
||||||
" [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
|
" [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
|
||||||
" [--] [<pathspec>...]]"),
|
" [--] [<pathspec>...]]"),
|
||||||
N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
|
N_("git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\n"
|
||||||
" [-u|--include-untracked] [-a|--all] [<message>]"),
|
" [-u|--include-untracked] [-a|--all] [<message>]"),
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
@ -1132,6 +1132,38 @@ done:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int stash_staged(struct stash_info *info, const struct pathspec *ps,
|
||||||
|
struct strbuf *out_patch, int quiet)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
struct child_process cp_diff_tree = CHILD_PROCESS_INIT;
|
||||||
|
struct index_state istate = { NULL };
|
||||||
|
|
||||||
|
if (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file,
|
||||||
|
0, NULL)) {
|
||||||
|
ret = -1;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
cp_diff_tree.git_cmd = 1;
|
||||||
|
strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD",
|
||||||
|
oid_to_hex(&info->w_tree), "--", NULL);
|
||||||
|
if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
|
||||||
|
ret = -1;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!out_patch->len) {
|
||||||
|
if (!quiet)
|
||||||
|
fprintf_ln(stderr, _("No staged changes"));
|
||||||
|
ret = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
discard_index(&istate);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int stash_patch(struct stash_info *info, const struct pathspec *ps,
|
static int stash_patch(struct stash_info *info, const struct pathspec *ps,
|
||||||
struct strbuf *out_patch, int quiet)
|
struct strbuf *out_patch, int quiet)
|
||||||
{
|
{
|
||||||
|
@ -1258,7 +1290,7 @@ done:
|
||||||
}
|
}
|
||||||
|
|
||||||
static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,
|
static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,
|
||||||
int include_untracked, int patch_mode,
|
int include_untracked, int patch_mode, int only_staged,
|
||||||
struct stash_info *info, struct strbuf *patch,
|
struct stash_info *info, struct strbuf *patch,
|
||||||
int quiet)
|
int quiet)
|
||||||
{
|
{
|
||||||
|
@ -1337,6 +1369,16 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
|
||||||
} else if (ret > 0) {
|
} else if (ret > 0) {
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
} else if (only_staged) {
|
||||||
|
ret = stash_staged(info, ps, patch, quiet);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (!quiet)
|
||||||
|
fprintf_ln(stderr, _("Cannot save the current "
|
||||||
|
"staged state"));
|
||||||
|
goto done;
|
||||||
|
} else if (ret > 0) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (stash_working_tree(info, ps)) {
|
if (stash_working_tree(info, ps)) {
|
||||||
if (!quiet)
|
if (!quiet)
|
||||||
|
@ -1395,7 +1437,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)
|
||||||
if (!check_changes_tracked_files(&ps))
|
if (!check_changes_tracked_files(&ps))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,
|
ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,
|
||||||
NULL, 0);
|
NULL, 0);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
printf_ln("%s", oid_to_hex(&info.w_commit));
|
printf_ln("%s", oid_to_hex(&info.w_commit));
|
||||||
|
@ -1405,7 +1447,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,
|
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 keep_index, int patch_mode, int include_untracked, int only_staged)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
struct stash_info info;
|
struct stash_info info;
|
||||||
|
@ -1423,6 +1465,17 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --patch overrides --staged */
|
||||||
|
if (patch_mode)
|
||||||
|
only_staged = 0;
|
||||||
|
|
||||||
|
if (only_staged && include_untracked) {
|
||||||
|
fprintf_ln(stderr, _("Can't use --staged and --include-untracked"
|
||||||
|
" or --all at the same time"));
|
||||||
|
ret = -1;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
read_cache_preload(NULL);
|
read_cache_preload(NULL);
|
||||||
if (!include_untracked && ps->nr) {
|
if (!include_untracked && ps->nr) {
|
||||||
int i;
|
int i;
|
||||||
|
@ -1463,7 +1516,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
|
||||||
|
|
||||||
if (stash_msg)
|
if (stash_msg)
|
||||||
strbuf_addstr(&stash_msg_buf, stash_msg);
|
strbuf_addstr(&stash_msg_buf, stash_msg);
|
||||||
if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
|
if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,
|
||||||
&info, &patch, quiet)) {
|
&info, &patch, quiet)) {
|
||||||
ret = -1;
|
ret = -1;
|
||||||
goto done;
|
goto done;
|
||||||
|
@ -1480,7 +1533,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
|
||||||
printf_ln(_("Saved working directory and index state %s"),
|
printf_ln(_("Saved working directory and index state %s"),
|
||||||
stash_msg_buf.buf);
|
stash_msg_buf.buf);
|
||||||
|
|
||||||
if (!patch_mode) {
|
if (!(patch_mode || only_staged)) {
|
||||||
if (include_untracked && !ps->nr) {
|
if (include_untracked && !ps->nr) {
|
||||||
struct child_process cp = CHILD_PROCESS_INIT;
|
struct child_process cp = CHILD_PROCESS_INIT;
|
||||||
|
|
||||||
|
@ -1598,6 +1651,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,
|
||||||
{
|
{
|
||||||
int force_assume = 0;
|
int force_assume = 0;
|
||||||
int keep_index = -1;
|
int keep_index = -1;
|
||||||
|
int only_staged = 0;
|
||||||
int patch_mode = 0;
|
int patch_mode = 0;
|
||||||
int include_untracked = 0;
|
int include_untracked = 0;
|
||||||
int quiet = 0;
|
int quiet = 0;
|
||||||
|
@ -1608,6 +1662,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
OPT_BOOL('k', "keep-index", &keep_index,
|
OPT_BOOL('k', "keep-index", &keep_index,
|
||||||
N_("keep index")),
|
N_("keep index")),
|
||||||
|
OPT_BOOL('S', "staged", &only_staged,
|
||||||
|
N_("stash staged changes only")),
|
||||||
OPT_BOOL('p', "patch", &patch_mode,
|
OPT_BOOL('p', "patch", &patch_mode,
|
||||||
N_("stash in patch mode")),
|
N_("stash in patch mode")),
|
||||||
OPT__QUIET(&quiet, N_("quiet mode")),
|
OPT__QUIET(&quiet, N_("quiet mode")),
|
||||||
|
@ -1646,6 +1702,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,
|
||||||
if (patch_mode)
|
if (patch_mode)
|
||||||
die(_("--pathspec-from-file is incompatible with --patch"));
|
die(_("--pathspec-from-file is incompatible with --patch"));
|
||||||
|
|
||||||
|
if (only_staged)
|
||||||
|
die(_("--pathspec-from-file is incompatible with --staged"));
|
||||||
|
|
||||||
if (ps.nr)
|
if (ps.nr)
|
||||||
die(_("--pathspec-from-file is incompatible with pathspec arguments"));
|
die(_("--pathspec-from-file is incompatible with pathspec arguments"));
|
||||||
|
|
||||||
|
@ -1657,12 +1716,13 @@ static int push_stash(int argc, const char **argv, const char *prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
|
return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
|
||||||
include_untracked);
|
include_untracked, only_staged);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int save_stash(int argc, const char **argv, const char *prefix)
|
static int save_stash(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
int keep_index = -1;
|
int keep_index = -1;
|
||||||
|
int only_staged = 0;
|
||||||
int patch_mode = 0;
|
int patch_mode = 0;
|
||||||
int include_untracked = 0;
|
int include_untracked = 0;
|
||||||
int quiet = 0;
|
int quiet = 0;
|
||||||
|
@ -1673,6 +1733,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
OPT_BOOL('k', "keep-index", &keep_index,
|
OPT_BOOL('k', "keep-index", &keep_index,
|
||||||
N_("keep index")),
|
N_("keep index")),
|
||||||
|
OPT_BOOL('S', "staged", &only_staged,
|
||||||
|
N_("stash staged changes only")),
|
||||||
OPT_BOOL('p', "patch", &patch_mode,
|
OPT_BOOL('p', "patch", &patch_mode,
|
||||||
N_("stash in patch mode")),
|
N_("stash in patch mode")),
|
||||||
OPT__QUIET(&quiet, N_("quiet mode")),
|
OPT__QUIET(&quiet, N_("quiet mode")),
|
||||||
|
@ -1694,7 +1756,7 @@ static int save_stash(int argc, const char **argv, const char *prefix)
|
||||||
|
|
||||||
memset(&ps, 0, sizeof(ps));
|
memset(&ps, 0, sizeof(ps));
|
||||||
ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
|
ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
|
||||||
patch_mode, include_untracked);
|
patch_mode, include_untracked, only_staged);
|
||||||
|
|
||||||
strbuf_release(&stash_msg_buf);
|
strbuf_release(&stash_msg_buf);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -288,6 +288,17 @@ test_expect_success 'stash --no-keep-index' '
|
||||||
test bar,bar2 = $(cat file),$(cat file2)
|
test bar,bar2 = $(cat file),$(cat file2)
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'stash --staged' '
|
||||||
|
echo bar3 >file &&
|
||||||
|
echo bar4 >file2 &&
|
||||||
|
git add file2 &&
|
||||||
|
git stash --staged &&
|
||||||
|
test bar3,bar2 = $(cat file),$(cat file2) &&
|
||||||
|
git reset --hard &&
|
||||||
|
git stash pop &&
|
||||||
|
test bar,bar4 = $(cat file),$(cat file2)
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'dont assume push with non-option args' '
|
test_expect_success 'dont assume push with non-option args' '
|
||||||
test_must_fail git stash -q drop 2>err &&
|
test_must_fail git stash -q drop 2>err &&
|
||||||
test_i18ngrep -e "subcommand wasn'\''t specified; '\''push'\'' can'\''t be assumed due to unexpected token '\''drop'\''" err
|
test_i18ngrep -e "subcommand wasn'\''t specified; '\''push'\'' can'\''t be assumed due to unexpected token '\''drop'\''" err
|
||||||
|
|
Loading…
Reference in New Issue