]> git.ipfire.org Git - thirdparty/git.git/commitdiff
stash: implement '--staged' option for 'push' and 'save'
authorSergey Organov <sorganov@gmail.com>
Mon, 18 Oct 2021 16:09:06 +0000 (19:09 +0300)
committerJunio C Hamano <gitster@pobox.com>
Mon, 18 Oct 2021 20:09:21 +0000 (13:09 -0700)
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>
Documentation/git-stash.txt
builtin/stash.c
t/t3903-stash.sh

index be6084ccefbee6fba30cc4e9137abbc7da84e749..6e15f47525765ce3da66b793ab519abd1cd90784 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
 'git stash' drop [-q|--quiet] [<stash>]
 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<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>]
             [--pathspec-from-file=<file> [--pathspec-file-nul]]
             [--] [<pathspec>...]]
@@ -47,7 +47,7 @@ stash index (e.g. the integer `n` is equivalent to `stash@{n}`).
 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
        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,
 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
        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
 `--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>::
        This option is only valid for `push` command.
 +
@@ -341,6 +351,24 @@ $ edit/build/test 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::
 
 If you mistakenly drop or clear stash entries, they cannot be recovered
index a0ccc8654dff70bd3014dca9d804a75bdd4c3cbc..8d6f0e582ce04c40c0a631b309b9aab847e82680 100644 (file)
@@ -27,11 +27,11 @@ static const char * const git_stash_usage[] = {
        N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
        N_("git stash branch <branchname> [<stash>]"),
        "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"
           "          [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
           "          [--] [<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>]"),
        NULL
 };
@@ -1132,6 +1132,38 @@ done:
        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,
                       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,
-                          int include_untracked, int patch_mode,
+                          int include_untracked, int patch_mode, int only_staged,
                           struct stash_info *info, struct strbuf *patch,
                           int quiet)
 {
@@ -1337,6 +1369,16 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
                } else if (ret > 0) {
                        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 {
                if (stash_working_tree(info, ps)) {
                        if (!quiet)
@@ -1395,7 +1437,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)
        if (!check_changes_tracked_files(&ps))
                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);
        if (!ret)
                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,
-                        int keep_index, int patch_mode, int include_untracked)
+                        int keep_index, int patch_mode, int include_untracked, int only_staged)
 {
        int ret = 0;
        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;
        }
 
+       /* --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);
        if (!include_untracked && ps->nr) {
                int i;
@@ -1463,7 +1516,7 @@ 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,
+       if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,
                            &info, &patch, quiet)) {
                ret = -1;
                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"),
                          stash_msg_buf.buf);
 
-       if (!patch_mode) {
+       if (!(patch_mode || only_staged)) {
                if (include_untracked && !ps->nr) {
                        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 keep_index = -1;
+       int only_staged = 0;
        int patch_mode = 0;
        int include_untracked = 0;
        int quiet = 0;
@@ -1608,6 +1662,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,
        struct option options[] = {
                OPT_BOOL('k', "keep-index", &keep_index,
                         N_("keep index")),
+               OPT_BOOL('S', "staged", &only_staged,
+                        N_("stash staged changes only")),
                OPT_BOOL('p', "patch", &patch_mode,
                         N_("stash in patch 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)
                        die(_("--pathspec-from-file is incompatible with --patch"));
 
+               if (only_staged)
+                       die(_("--pathspec-from-file is incompatible with --staged"));
+
                if (ps.nr)
                        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,
-                            include_untracked);
+                            include_untracked, only_staged);
 }
 
 static int save_stash(int argc, const char **argv, const char *prefix)
 {
        int keep_index = -1;
+       int only_staged = 0;
        int patch_mode = 0;
        int include_untracked = 0;
        int quiet = 0;
@@ -1673,6 +1733,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)
        struct option options[] = {
                OPT_BOOL('k', "keep-index", &keep_index,
                         N_("keep index")),
+               OPT_BOOL('S', "staged", &only_staged,
+                        N_("stash staged changes only")),
                OPT_BOOL('p', "patch", &patch_mode,
                         N_("stash in patch 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));
        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);
        return ret;
index f0a82be9de7654a0956b1d9a4f24e161869e021e..2c66cfbc3b7fbadaa3f8c7a34a38cba2a6eac37f 100755 (executable)
@@ -288,6 +288,17 @@ test_expect_success 'stash --no-keep-index' '
        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_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