{"thread":{"id":"56624","subject":"[PATCH RFC] stash: implement '--staged' option for 'push' and 'save'","startedAt":"2021-10-01T22:12:20Z","lastAt":"2021-10-28T21:17:10Z","messageCount":26,"participants":["Sergey Organov","Eric Sunshine","Ævar Arnfjörð Bjarmason","Junio C Hamano","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"437739","messageId":"875yugcs3l.fsf@osv.gnss.ru","threadId":"56624","inReplyTo":null,"subject":"[PATCH RFC] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-01T22:12:14Z","receivedAt":"2021-10-01T22:12:20Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"\nStash only the changes that are staged.\n\nThis mode allows to easily stash-out for later reuse some changes\nunrelated to the current work in progress.\n\nUnlike 'stash push --patch', --staged supports using of any tool to\nselect the changes to stash-out, including, but not limited to 'git add\n--interactive'.\n\nThis operation that is essentially just a 'git commit', but to the\nstash, is somehow missed, complicating the task that is otherwise simple\nand natural. For example, see discussions here:\n\nhttps://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible\n\nNOTE: I'm entirely unfamiliar with the code, and the implementation\nbelow is the first thing that came to my mind, without much thought.\n\nSigned-off-by: Sergey Organov <sorganov@gmail.com>\n---\n Documentation/git-stash.txt | 32 +++++++++++++++--\n builtin/stash.c             | 72 ++++++++++++++++++++++++-------------\n 2 files changed, 77 insertions(+), 27 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex be6084ccefbe..90d83839d282 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n 'git stash' drop [-q|--quiet] [<stash>]\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n-'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n+'git stash' [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\n \t     [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n \t     [--pathspec-from-file=<file> [--pathspec-file-nul]]\n \t     [--] [<pathspec>...]]\n@@ -47,7 +47,7 @@ stash index (e.g. the integer `n` is equivalent to `stash@{n}`).\n COMMANDS\n --------\n \n-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>...]::\n+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>...]::\n \n \tSave your local modifications to a new 'stash entry' and roll them\n \tback to HEAD (in the working tree and in the index).\n@@ -60,7 +60,7 @@ subcommand from making an unwanted stash entry.  The two exceptions to this\n are `stash -p` which acts as alias for `stash push -p` and pathspec elements,\n which are allowed after a double hyphen `--` for disambiguation.\n \n-save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::\n+save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::\n \n \tThis option is deprecated in favour of 'git stash push'.  It\n \tdiffers from \"stash push\" in that it cannot take pathspec.\n@@ -205,6 +205,14 @@ to learn how to operate the `--patch` mode.\n The `--patch` option implies `--keep-index`.  You can use\n `--no-keep-index` to override this.\n \n+-S::\n+--staged::\n+\tThis option is only valid for `push` and `save` commands.\n++\n+Stash only the changes that are currently staged in the index.\n++\n+The `--patch` option has priority over this one.\n+\n --pathspec-from-file=<file>::\n \tThis option is only valid for `push` command.\n +\n@@ -341,6 +349,24 @@ $ edit/build/test remaining parts\n $ git commit foo -m 'Remaining parts'\n ----------------------------------------------------------------\n \n+Saving unrelated changes for future use::\n+\n+When you are in the middle of massive changes and you find some\n+unrelated issue that you don't want to forget to fix, you can do the\n+change(s), stage them, and use `git stash push --staged` to stash them\n+out for future use. This is similar to committing the staged changes,\n+only the commit ends-up being in the stash and not on the current branch.\n++\n+----------------------------------------------------------------\n+# ... hack hack hack ...\n+$ git add --patch foo           # add unrelated changes to the index\n+$ git stash push --staged       # save these changes to the stash\n+# ... hack hack hack, finish curent changes ...\n+$ git commit -m 'Massive'       # commit fully tested changes\n+$ git switch fixup-branch       # switch to another branch\n+$ git stash pop                 # to finish work on the saved changes\n+----------------------------------------------------------------\n+\n Recovering stash entries that were cleared/dropped erroneously::\n \n If you mistakenly drop or clear stash entries, they cannot be recovered\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 8f42360ca913..82d97b0c7e42 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -27,11 +27,11 @@ static const char * const git_stash_usage[] = {\n \tN_(\"git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]\"),\n \tN_(\"git stash branch <branchname> [<stash>]\"),\n \t\"git stash clear\",\n-\tN_(\"git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n+\tN_(\"git stash [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\\n\"\n \t   \"          [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n \t   \"          [--] [<pathspec>...]]\"),\n-\tN_(\"git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n+\tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n \tNULL\n };\n@@ -1117,7 +1117,7 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,\n }\n \n static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n-\t\t       struct strbuf *out_patch, int quiet)\n+\t\t       struct strbuf *out_patch, int only_staged, int quiet)\n {\n \tint ret = 0;\n \tstruct child_process cp_read_tree = CHILD_PROCESS_INIT;\n@@ -1136,24 +1136,27 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n \t\tgoto done;\n \t}\n \n-\t/* Find out what the user wants. */\n-\told_repo_index_file = the_repository->index_file;\n-\tthe_repository->index_file = stash_index_path.buf;\n-\told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n-\tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n+\tif (!only_staged) {\n+\t\t/* Find out what the user wants. */\n+\t\told_repo_index_file = the_repository->index_file;\n+\t\tthe_repository->index_file = stash_index_path.buf;\n+\t\told_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));\n+\t\tsetenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);\n \n-\tret = run_add_interactive(NULL, \"--patch=stash\", ps);\n+\t\tret = run_add_interactive(NULL, \"--patch=stash\", ps);\n \n-\tthe_repository->index_file = old_repo_index_file;\n-\tif (old_index_env && *old_index_env)\n-\t\tsetenv(INDEX_ENVIRONMENT, old_index_env, 1);\n-\telse\n-\t\tunsetenv(INDEX_ENVIRONMENT);\n-\tFREE_AND_NULL(old_index_env);\n+\t\tthe_repository->index_file = old_repo_index_file;\n+\t\tif (old_index_env && *old_index_env)\n+\t\t\tsetenv(INDEX_ENVIRONMENT, old_index_env, 1);\n+\t\telse\n+\t\t\tunsetenv(INDEX_ENVIRONMENT);\n+\t\tFREE_AND_NULL(old_index_env);\n+\t}\n \n \t/* State of the working tree. */\n-\tif (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0,\n-\t\t\t\tNULL)) {\n+\tif (write_index_as_tree(&info->w_tree, &istate,\n+\t\t\t\t(only_staged ? the_repository->index_file : stash_index_path.buf),\n+\t\t\t\t0, NULL)) {\n \t\tret = -1;\n \t\tgoto done;\n \t}\n@@ -1242,7 +1245,7 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps\n }\n \n static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,\n-\t\t\t   int include_untracked, int patch_mode,\n+\t\t\t   int include_untracked, int patch_mode, int only_staged,\n \t\t\t   struct stash_info *info, struct strbuf *patch,\n \t\t\t   int quiet)\n {\n@@ -1312,7 +1315,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\tuntracked_commit_option = 1;\n \t}\n \tif (patch_mode) {\n-\t\tret = stash_patch(info, ps, patch, quiet);\n+\t\tret = stash_patch(info, ps, patch, only_staged, quiet);\n \t\tif (ret < 0) {\n \t\t\tif (!quiet)\n \t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n@@ -1379,7 +1382,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n \tif (!check_changes_tracked_files(&ps))\n \t\treturn 0;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,\n+\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n \t\t\t      NULL, 0);\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n@@ -1389,7 +1392,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n }\n \n static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,\n-\t\t\t int keep_index, int patch_mode, int include_untracked)\n+\t\t\t int keep_index, int patch_mode, int include_untracked, int only_staged)\n {\n \tint ret = 0;\n \tstruct stash_info info;\n@@ -1407,6 +1410,21 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \t\tgoto done;\n \t}\n \n+\t/* --patch overrides --staged */\n+\tif (patch_mode)\n+\t\tonly_staged = 0;\n+\n+\t/* --staged reuses 'patch_mode' for implementation */\n+\tif (only_staged)\n+\t\tpatch_mode = 1;\n+\n+\tif (only_staged && include_untracked) {\n+\t\tfprintf_ln(stderr, _(\"Can't use --staged and --include-untracked\"\n+\t\t\t\t     \" or --all at the same time\"));\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n \tread_cache_preload(NULL);\n \tif (!include_untracked && ps->nr) {\n \t\tint i;\n@@ -1447,7 +1465,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \n \tif (stash_msg)\n \t\tstrbuf_addstr(&stash_msg_buf, stash_msg);\n-\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,\n+\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,\n \t\t\t    &info, &patch, quiet)) {\n \t\tret = -1;\n \t\tgoto done;\n@@ -1581,6 +1599,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n {\n \tint force_assume = 0;\n \tint keep_index = -1;\n+\tint only_staged = 0;\n \tint patch_mode = 0;\n \tint include_untracked = 0;\n \tint quiet = 0;\n@@ -1591,6 +1610,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n+\t\tOPT_BOOL('S', \"staged\", &only_staged,\n+\t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n@@ -1640,12 +1661,13 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t}\n \n \treturn do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,\n-\t\t\t     include_untracked);\n+\t\t\t     include_untracked, only_staged);\n }\n \n static int save_stash(int argc, const char **argv, const char *prefix)\n {\n \tint keep_index = -1;\n+\tint only_staged = 0;\n \tint patch_mode = 0;\n \tint include_untracked = 0;\n \tint quiet = 0;\n@@ -1656,6 +1678,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n+\t\tOPT_BOOL('S', \"staged\", &only_staged,\n+\t\t\t N_(\"stash in patch mode\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n@@ -1677,7 +1701,7 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \n \tmemset(&ps, 0, sizeof(ps));\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index,\n-\t\t\t    patch_mode, include_untracked);\n+\t\t\t    patch_mode, include_untracked, only_staged);\n \n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\n-- \n2.25.1\n"},{"id":"438491","messageId":"87lf2zz59w.fsf@osv.gnss.ru","threadId":"56624","inReplyTo":"875yugcs3l.fsf@osv.gnss.ru","subject":"[PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-11T20:16:11Z","receivedAt":"2021-10-11T20:16:16Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Stash only the changes that are staged.\n\nThis mode allows to easily stash-out for later reuse some changes\nunrelated to the current work in progress.\n\nUnlike 'stash push --patch', --staged supports using of any tool to\nselect the changes to stash-out, including, but not limited to 'git\nadd --interactive'.\n\nSigned-off-by: Sergey Organov <sorganov@gmail.com>\n---\n\nThis operation that is essentially just a 'git commit', but to the stash\nrather than to the current branch, is somehow missed, complicating the\ntask that is otherwise simple and natural. For example, see discussions\nhere:\n\nhttps://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible\n\nChanges in v1:\n\n  * Implement separate stash_staged() instead of re-using and changing\n    stash_patch()\n\n  * Add test\n\n  * Minor documentation cleanup\n\n Documentation/git-stash.txt | 34 ++++++++++++++--\n builtin/stash.c             | 80 ++++++++++++++++++++++++++++++++-----\n t/t3903-stash.sh            | 11 +++++\n 3 files changed, 113 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex be6084ccefbe..6e15f4752576 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n 'git stash' drop [-q|--quiet] [<stash>]\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n-'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n+'git stash' [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\n \t     [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n \t     [--pathspec-from-file=<file> [--pathspec-file-nul]]\n \t     [--] [<pathspec>...]]\n@@ -47,7 +47,7 @@ stash index (e.g. the integer `n` is equivalent to `stash@{n}`).\n COMMANDS\n --------\n \n-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>...]::\n+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>...]::\n \n \tSave your local modifications to a new 'stash entry' and roll them\n \tback to HEAD (in the working tree and in the index).\n@@ -60,7 +60,7 @@ subcommand from making an unwanted stash entry.  The two exceptions to this\n are `stash -p` which acts as alias for `stash push -p` and pathspec elements,\n which are allowed after a double hyphen `--` for disambiguation.\n \n-save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::\n+save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::\n \n \tThis option is deprecated in favour of 'git stash push'.  It\n \tdiffers from \"stash push\" in that it cannot take pathspec.\n@@ -205,6 +205,16 @@ to learn how to operate the `--patch` mode.\n The `--patch` option implies `--keep-index`.  You can use\n `--no-keep-index` to override this.\n \n+-S::\n+--staged::\n+\tThis option is only valid for `push` and `save` commands.\n++\n+Stash only the changes that are currently staged. This is similar to\n+basic `git commit` except the state is committed to the stash instead\n+of current branch.\n++\n+The `--patch` option has priority over this one.\n+\n --pathspec-from-file=<file>::\n \tThis option is only valid for `push` command.\n +\n@@ -341,6 +351,24 @@ $ edit/build/test remaining parts\n $ git commit foo -m 'Remaining parts'\n ----------------------------------------------------------------\n \n+Saving unrelated changes for future use::\n+\n+When you are in the middle of massive changes and you find some\n+unrelated issue that you don't want to forget to fix, you can do the\n+change(s), stage them, and use `git stash push --staged` to stash them\n+out for future use. This is similar to committing the staged changes,\n+only the commit ends-up being in the stash and not on the current branch.\n++\n+----------------------------------------------------------------\n+# ... hack hack hack ...\n+$ git add --patch foo           # add unrelated changes to the index\n+$ git stash push --staged       # save these changes to the stash\n+# ... hack hack hack, finish curent changes ...\n+$ git commit -m 'Massive'       # commit fully tested changes\n+$ git switch fixup-branch       # switch to another branch\n+$ git stash pop                 # to finish work on the saved changes\n+----------------------------------------------------------------\n+\n Recovering stash entries that were cleared/dropped erroneously::\n \n If you mistakenly drop or clear stash entries, they cannot be recovered\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 8f42360ca913..97bf274627d8 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -27,11 +27,11 @@ static const char * const git_stash_usage[] = {\n \tN_(\"git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]\"),\n \tN_(\"git stash branch <branchname> [<stash>]\"),\n \t\"git stash clear\",\n-\tN_(\"git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n+\tN_(\"git stash [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\\n\"\n \t   \"          [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n \t   \"          [--] [<pathspec>...]]\"),\n-\tN_(\"git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n+\tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n \tNULL\n };\n@@ -1116,6 +1116,38 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,\n \treturn ret;\n }\n \n+static int stash_staged(struct stash_info *info, const struct pathspec *ps,\n+\t\t       struct strbuf *out_patch, int quiet)\n+{\n+\tint ret = 0;\n+\tstruct child_process cp_diff_tree = CHILD_PROCESS_INIT;\n+\tstruct index_state istate = { NULL };\n+\n+\tif (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file,\n+\t\t\t\t0, NULL)) {\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n+\tcp_diff_tree.git_cmd = 1;\n+\tstrvec_pushl(&cp_diff_tree.args, \"diff-tree\", \"-p\", \"-U1\", \"HEAD\",\n+\t\t     oid_to_hex(&info->w_tree), \"--\", NULL);\n+\tif (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n+\tif (!out_patch->len) {\n+\t\tif (!quiet)\n+\t\t\tfprintf_ln(stderr, _(\"No changes selected\"));\n+\t\tret = 1;\n+\t}\n+\n+done:\n+\tdiscard_index(&istate);\n+\treturn ret;\n+}\n+\n static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n \t\t       struct strbuf *out_patch, int quiet)\n {\n@@ -1242,7 +1274,7 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps\n }\n \n static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,\n-\t\t\t   int include_untracked, int patch_mode,\n+\t\t\t   int include_untracked, int patch_mode, int only_staged,\n \t\t\t   struct stash_info *info, struct strbuf *patch,\n \t\t\t   int quiet)\n {\n@@ -1321,6 +1353,16 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\t} else if (ret > 0) {\n \t\t\tgoto done;\n \t\t}\n+\t} else if (only_staged) {\n+\t\tret = stash_staged(info, ps, patch, quiet);\n+\t\tif (ret < 0) {\n+\t\t\tif (!quiet)\n+\t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n+\t\t\t\t\t\t     \"staged state\"));\n+\t\t\tgoto done;\n+\t\t} else if (ret > 0) {\n+\t\t\tgoto done;\n+\t\t}\n \t} else {\n \t\tif (stash_working_tree(info, ps)) {\n \t\t\tif (!quiet)\n@@ -1379,7 +1421,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n \tif (!check_changes_tracked_files(&ps))\n \t\treturn 0;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,\n+\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n \t\t\t      NULL, 0);\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n@@ -1389,7 +1431,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n }\n \n static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,\n-\t\t\t int keep_index, int patch_mode, int include_untracked)\n+\t\t\t int keep_index, int patch_mode, int include_untracked, int only_staged)\n {\n \tint ret = 0;\n \tstruct stash_info info;\n@@ -1407,6 +1449,17 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \t\tgoto done;\n \t}\n \n+\t/* --patch overrides --staged */\n+\tif (patch_mode)\n+\t\tonly_staged = 0;\n+\n+\tif (only_staged && include_untracked) {\n+\t\tfprintf_ln(stderr, _(\"Can't use --staged and --include-untracked\"\n+\t\t\t\t     \" or --all at the same time\"));\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n \tread_cache_preload(NULL);\n \tif (!include_untracked && ps->nr) {\n \t\tint i;\n@@ -1447,7 +1500,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \n \tif (stash_msg)\n \t\tstrbuf_addstr(&stash_msg_buf, stash_msg);\n-\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,\n+\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,\n \t\t\t    &info, &patch, quiet)) {\n \t\tret = -1;\n \t\tgoto done;\n@@ -1464,7 +1517,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \t\tprintf_ln(_(\"Saved working directory and index state %s\"),\n \t\t\t  stash_msg_buf.buf);\n \n-\tif (!patch_mode) {\n+\tif (!(patch_mode || only_staged)) {\n \t\tif (include_untracked && !ps->nr) {\n \t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n \n@@ -1581,6 +1634,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n {\n \tint force_assume = 0;\n \tint keep_index = -1;\n+\tint only_staged = 0;\n \tint patch_mode = 0;\n \tint include_untracked = 0;\n \tint quiet = 0;\n@@ -1591,6 +1645,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n+\t\tOPT_BOOL('S', \"staged\", &only_staged,\n+\t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n@@ -1629,6 +1685,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\tif (patch_mode)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with --patch\"));\n \n+\t\tif (only_staged)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --staged\"));\n+\n \t\tif (ps.nr)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n \n@@ -1640,12 +1699,13 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t}\n \n \treturn do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,\n-\t\t\t     include_untracked);\n+\t\t\t     include_untracked, only_staged);\n }\n \n static int save_stash(int argc, const char **argv, const char *prefix)\n {\n \tint keep_index = -1;\n+\tint only_staged = 0;\n \tint patch_mode = 0;\n \tint include_untracked = 0;\n \tint quiet = 0;\n@@ -1656,6 +1716,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n+\t\tOPT_BOOL('S', \"staged\", &only_staged,\n+\t\t\t N_(\"stash in patch mode\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n@@ -1677,7 +1739,7 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \n \tmemset(&ps, 0, sizeof(ps));\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index,\n-\t\t\t    patch_mode, include_untracked);\n+\t\t\t    patch_mode, include_untracked, only_staged);\n \n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 873aa56e359d..18ea885412b8 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -288,6 +288,17 @@ test_expect_success 'stash --no-keep-index' '\n \ttest bar,bar2 = $(cat file),$(cat file2)\n '\n \n+test_expect_success 'stash --staged' '\n+\techo bar3 >file &&\n+\techo bar4 >file2 &&\n+\tgit add file2 &&\n+\tgit stash --staged &&\n+\ttest bar3,bar2 = $(cat file),$(cat file2) &&\n+\tgit reset --hard &&\n+\tgit stash pop &&\n+\ttest bar,bar4 = $(cat file),$(cat file2)\n+'\n+\n test_expect_success 'dont assume push with non-option args' '\n \ttest_must_fail git stash -q drop 2>err &&\n \ttest_i18ngrep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''drop'\\''\" err\n-- \n2.25.1\n"},{"id":"438506","messageId":"CAPig+cQgZX5U0KThgSKjmgvOvEZWxPF_BovYYe=VfjDvUE5c9Q@mail.gmail.com","threadId":"56624","inReplyTo":"87lf2zz59w.fsf@osv.gnss.ru","subject":"Re: [PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-10-11T21:21:54Z","receivedAt":"2021-10-11T21:22:14Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"/On Mon, Oct 11, 2021 at 4:17 PM Sergey Organov <sorganov@gmail.com> wrote:\n> Stash only the changes that are staged.\n>\n> This mode allows to easily stash-out for later reuse some changes\n> unrelated to the current work in progress.\n>\n> Unlike 'stash push --patch', --staged supports using of any tool to\n> select the changes to stash-out, including, but not limited to 'git\n> add --interactive'.\n\ns/using of any/use of any/\n...or...\ns/using of any/using any/\n\n> Signed-off-by: Sergey Organov <sorganov@gmail.com>\n> ---\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> @@ -1656,6 +1716,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n> +               OPT_BOOL('S', \"staged\", &only_staged,\n> +                        N_(\"stash in patch mode\")),\n>                 OPT_BOOL('p', \"patch\", &patch_mode,\n>                          N_(\"stash in patch mode\")),\n>                 OPT__QUIET(&quiet, N_(\"quiet mode\")),\n\nCopy/paste error in new help/description string?\n"},{"id":"438509","messageId":"87fst7z0oa.fsf@osv.gnss.ru","threadId":"56624","inReplyTo":"CAPig+cQgZX5U0KThgSKjmgvOvEZWxPF_BovYYe=VfjDvUE5c9Q@mail.gmail.com","subject":"Re: [PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-11T21:55:33Z","receivedAt":"2021-10-11T21:55:38Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> /On Mon, Oct 11, 2021 at 4:17 PM Sergey Organov <sorganov@gmail.com> wrote:\n>> Stash only the changes that are staged.\n>>\n>> This mode allows to easily stash-out for later reuse some changes\n>> unrelated to the current work in progress.\n>>\n>> Unlike 'stash push --patch', --staged supports using of any tool to\n>> select the changes to stash-out, including, but not limited to 'git\n>> add --interactive'.\n>\n> s/using of any/use of any/\n> ...or...\n> s/using of any/using any/\n\nWill fix, thanks!\n\n>\n>> Signed-off-by: Sergey Organov <sorganov@gmail.com>\n>> ---\n>> diff --git a/builtin/stash.c b/builtin/stash.c\n>> @@ -1656,6 +1716,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n>> +               OPT_BOOL('S', \"staged\", &only_staged,\n>> +                        N_(\"stash in patch mode\")),\n>>                 OPT_BOOL('p', \"patch\", &patch_mode,\n>>                          N_(\"stash in patch mode\")),\n>>                 OPT__QUIET(&quiet, N_(\"quiet mode\")),\n>\n> Copy/paste error in new help/description string?\n\nYep.\n\nThanks,\n\n-- Sergey Organov\n"},{"id":"438526","messageId":"87y26yk35r.fsf@evledraar.gmail.com","threadId":"56624","inReplyTo":"87fst7z0oa.fsf@osv.gnss.ru","subject":"Re: [PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-12T09:18:38Z","receivedAt":"2021-10-12T09:25:10Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Oct 12 2021, Sergey Organov wrote:\n\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n>\n>> /On Mon, Oct 11, 2021 at 4:17 PM Sergey Organov <sorganov@gmail.com> wrote:\n>>> Stash only the changes that are staged.\n>>>\n>>> This mode allows to easily stash-out for later reuse some changes\n>>> unrelated to the current work in progress.\n>>>\n>>> Unlike 'stash push --patch', --staged supports using of any tool to\n>>> select the changes to stash-out, including, but not limited to 'git\n>>> add --interactive'.\n>>\n>> s/using of any/use of any/\n>> ...or...\n>> s/using of any/using any/\n>\n> Will fix, thanks!\n>\n>>\n>>> Signed-off-by: Sergey Organov <sorganov@gmail.com>\n>>> ---\n>>> diff --git a/builtin/stash.c b/builtin/stash.c\n>>> @@ -1656,6 +1716,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n>>> +               OPT_BOOL('S', \"staged\", &only_staged,\n>>> +                        N_(\"stash in patch mode\")),\n>>>                 OPT_BOOL('p', \"patch\", &patch_mode,\n>>>                          N_(\"stash in patch mode\")),\n>>>                 OPT__QUIET(&quiet, N_(\"quiet mode\")),\n>>\n>> Copy/paste error in new help/description string?\n>\n> Yep.\n>\n> Thanks,\n>\n\nI very much like this option, I've sometimes missed it in \"git stash\",\nand was always going to dig into if there was some way to do it.\n\nThe one thing I'm a bit iffy on is if this is consistent with the\n--staged options in other commands (with some taking --cached and/or\n--staged), I think so, and this is a good name.\n\nBut is the -S option used as a shorthand for --staged somewhere else?\n*Checks*, ah yes, for \"git restore\", then we use \"stage\" for\ncheckout-index/ls-files, the latter of which has a 's' (not\ncapital-letter 'S') shorthand.\n\nI *think* that just -s/--stage would make more sense here, but I've only\nlooked at it briefly, but getting options consistent if possible is in\ngeneral quite nice for users, so we should think about it...\n"},{"id":"438532","messageId":"8735p67an1.fsf@osv.gnss.ru","threadId":"56624","inReplyTo":"87y26yk35r.fsf@evledraar.gmail.com","subject":"Re: [PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-12T11:20:02Z","receivedAt":"2021-10-12T11:20:09Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Tue, Oct 12 2021, Sergey Organov wrote:\n>\n>> Eric Sunshine <sunshine@sunshineco.com> writes:\n>>\n>>> /On Mon, Oct 11, 2021 at 4:17 PM Sergey Organov <sorganov@gmail.com> wrote:\n>>>> Stash only the changes that are staged.\n>>>>\n>>>> This mode allows to easily stash-out for later reuse some changes\n>>>> unrelated to the current work in progress.\n>>>>\n>>>> Unlike 'stash push --patch', --staged supports using of any tool to\n>>>> select the changes to stash-out, including, but not limited to 'git\n>>>> add --interactive'.\n>>>\n>>> s/using of any/use of any/\n>>> ...or...\n>>> s/using of any/using any/\n>>\n>> Will fix, thanks!\n>>\n>>>\n>>>> Signed-off-by: Sergey Organov <sorganov@gmail.com>\n>>>> ---\n>>>> diff --git a/builtin/stash.c b/builtin/stash.c\n>>>> @@ -1656,6 +1716,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n>>>> +               OPT_BOOL('S', \"staged\", &only_staged,\n>>>> +                        N_(\"stash in patch mode\")),\n>>>>                 OPT_BOOL('p', \"patch\", &patch_mode,\n>>>>                          N_(\"stash in patch mode\")),\n>>>>                 OPT__QUIET(&quiet, N_(\"quiet mode\")),\n>>>\n>>> Copy/paste error in new help/description string?\n>>\n>> Yep.\n>>\n>> Thanks,\n>>\n>\n> I very much like this option, I've sometimes missed it in \"git stash\",\n> and was always going to dig into if there was some way to do it.\n>\n> The one thing I'm a bit iffy on is if this is consistent with the\n> --staged options in other commands (with some taking --cached and/or\n> --staged), I think so, and this is a good name.\n>\n> But is the -S option used as a shorthand for --staged somewhere else?\n> *Checks*, ah yes, for \"git restore\", then we use \"stage\" for\n> checkout-index/ls-files, the latter of which has a 's' (not\n> capital-letter 'S') shorthand.\n>\n> I *think* that just -s/--stage would make more sense here, but I've only\n> looked at it briefly, but getting options consistent if possible is in\n> general quite nice for users, so we should think about it...\n\nI grepped the Documentation/ and found -S,--staged in the\n\"git-restore.txt\":\n\n-S::\n--staged::\n        Specify the restore location.\n\n, so I used the same shorthand, even though I personally would use -s as\nwell.\n\nIn addition, the problem with -s is that it's used in a lot of places\nfor entirely unrelated option(s).\n\nThanks,\n-- Sergey Organov\n"},{"id":"438533","messageId":"xmqqa6je8n5c.fsf@gitster.g","threadId":"56624","inReplyTo":"87y26yk35r.fsf@evledraar.gmail.com","subject":"Re: [PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-12T12:04:31Z","receivedAt":"2021-10-12T12:04:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> The one thing I'm a bit iffy on is if this is consistent with the\n> --staged options in other commands (with some taking --cached and/or\n> --staged), I think so, and this is a good name.\n\nWe clearly define contrasts between \"--cached\" and \"--index\", but\nthe \"--staged\", which is a confusing synonym for nothing, does not\nget in the contrast between the two, so I do not think you need to\nworry about \"which one is it?\" in this case.\n\nIf something works only on the contents in the index, then it should\nuse \"--cached\".  If it works both on the index and the working tree,\nthen it should use \"--index\".  If you call it \"--staged\", it is\nwhatever it means ;-)\n\nMore importantly...\n\nWhenever I think about a new \"feature\", I try to come up with a\nstory in which the feature effectively improves the end-user's life,\nhow it fits in the larger picture, and enables something that is\nhard to do by combining other tools.\n\nThe kind of \"story\" I would aim for is like this.  Suppose we were\nselling not \"git stash -S\" but \"git stash -k\".  The story would go\nlike this:\n\n  Imagine that the change you have been working on started to take\n  shape, and you estimate it would be a three-patch series in the\n  end.  You also guess that so far you have enough to finish the\n  first step fully, perhaps 40% of the second step and a little bit\n  of the third step, all mixed together.\n\n  You started to sifting the changes into the first step material\n  and the rest, by using \"add -p\" etc., and you are reasonably sure\n  that what you have in the index is in a good shape for the first\n  commit.  But it is not easy to be sure, because what you can test\n  is only in the working tree, so a mistake like having all the code\n  already added in the index but forgetting to add to the index a\n  declaration for a variable the code uses that is in the working\n  tree is easy to make.  With \"git stash -k\", you can materialize\n  only what is in the index to the working tree, while stashing away\n  the changes in the working tree that haven't been added yet.  By\n  checking the resulting working tree, you can be sure.\n\n  - If the resulting working tree after \"git stash -k\" tests out\n    OK, then you can make a commit and the \"git stash pop\" will\n    give you the material for the second and the third step.\n    You'd work to produce the remaining 60% of the second step and\n    do \"git stash -k\" dance again before recording it.\n\n  - It is possible that the resulting working tree does not work\n    OK.  You may find that you forgot to \"add -p\" the declaration\n    of a variable you used in the code that you already \"add\"ed.\n    After \"stash -k\", the former is stashed away while the latter\n    appears in the working tree, and the compiler complains.  In\n    such a case, you can \"git commit\" the slightly broken state,\n    \"git stash pop\" to recover the missed declaration, together\n    with the material for the second and third step, into the\n    working tree, use \"add -p\" and \"stash -k\" to prepare and\n    verify the \"fixup\" commit for the first step.  Later you can\n    \"rebase -i\" the first step into shape.\n\nI unfortunately am coming up empty for \"git stash -S\".  And I do not\nsee a beginning of a good story at the stackoverflow entry you had\nthe URL for, either.  If we had one to support this feature, that\nwould help very much.\n\nWhile I failed to come up with a good story for this new feature, I\nhowever did come up with possible confusion and frustration that\nend-users may feel while trying to use it:\n\n - I thought the result of \"git add -p\" was good, so I did \"git\n   stash -S\", then after working further on, did another \"git stash\n   -S\" a few times.  Now I have a handful of stash entries, but\n   because all I can do is \"git stash pop\" them one by one,\n\n   - I need to make commits for real, and \n\n   - because I wasn't given a chance to, these stash entries do not\n     record material to write good log messages and I forgot why I\n     did some of the changes in the way I did so.\n\n   - My \"add -p\" seem to have missed some stuff that should have\n     been added, but it is too late to correct, especially given\n     that these stash entries cannot be \"rebase -i\"ed or \"commit\n     --amend\"ed.\n\n - Also, how would I reorder these steps?  If I made real commits,\n   instead of \"stash -S\", I am familiar with \"rebase -i\" to reorder,\n   combine or split them, but because these are not real commits, I\n   cannot use \"rebase -i\".\n\n - After making these \"stash -S\" entries, I popped a wrong one.  If\n   I recorded them as real commits on a temporary work branch, its\n   reflog would have helped me to recover from such a mistake, but\n   because stash does not mix well with reflog, I am lost.\n\nAnd I do not want to see us respond to these future end-user gripes\nwith \"don't worry, we'll extend 'git stash save [-S]' with the '-e'\noption to let you describe the change in detail, and enhance 'git\nstash pop' with the '--commit' option to directly create a commit\nusing the message you wrote when you created the stash\", or \"don't\nworry, we'll enhance 'rebase -i' to be capable of working on series\nof stash entries\".  These all look like complexity that only became\nnecessary because we added \"git stash -S\"---if the user committed\nincrementally on a temporary work branch, none of the complication\nwould have been needed.\n\nI think it is very possible that an answer to the above possible\nend-user gripes is \"no, this feature is not about sifting a big and\nmixed changes in the working tree into multiple steps recorded in\nthe stash entries (instead of a series of commits on a branch), so\nall the above end-user gripes are the result of using the tool for a\nwrong job\", and that is why I wanted to come up with a story in\nwhich this feature effectively improves the end-user experience.\nIOW, with the \"frustration\" story in the previous paragraph, I might\nhave been trying to drive screws into wood with this new feature,\nwhich is a hammer and not a screwdriver.  If that is the case, then\nI would want to see a story where the hammer is used to drive nails\ninstead, and nails do something good that screws don't.\n\nI have a suspicion that this _might_ be coming from a hesitancy to\ncommit (e.g. a draconian commit hook that always pushes things out\nimmediately a commit is made), and somehow creating stashes is used\nas a way to sidestep the real source of the problem (e.g. in Git,\ncommits on temporary branches are designed to be useful lightweight\ntools to help advance the history recorded in the real branch, but\nmisguided hooks and policies prevent your branches to be used as\nsuch).  I dunno.\n"},{"id":"438534","messageId":"xmqqo87u777d.fsf@gitster.g","threadId":"56624","inReplyTo":"xmqqa6je8n5c.fsf@gitster.g","subject":"Re: [PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-12T12:34:14Z","receivedAt":"2021-10-12T12:34:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> More importantly...\n>\n> Whenever I think about a new \"feature\", I try to come up with a\n> story in which the feature effectively improves the end-user's life,\n> how it fits in the larger picture, and enables something that is\n> hard to do by combining other tools.\n>\n> The kind of \"story\" I would aim for is like this.  Suppose we were\n> selling not \"git stash -S\" but \"git stash -k\". ...\n\nTo answer my previous \"question\", I guess this is usable in the same\nscenario where \"git stash -k\" is useful.  After creating a bunch of\nstash entries created by \"git stash -S\", if you want to test any of\nthem (because what is in these stash entries did not exist without\nother working tree changes, and couldn't have been tested in the\nworking tree standalone by definition), you can \"git stash pop\" such\na stash entry created by \"git stash -S\" and then \"git stash -k\" to\nmaterialize what was in the stash alone in the working tree to test\n_later_ (as opposed to testing _first_; in the \"git stash -k\"\nworkflow, you'd collect \"good bits\" in the index with \"add -p\"\nfirst, then \"clear the remaining cruft\" with \"git stash -k\" to test\nit first, and take the cruft back with \"git stash pop\").\n\nSo in short, I do not think I am strongly opposed to \"git stash -S\"\nexisting, since I did find one use case story that it could be used,\nbut I do think it is redundant and unnecessary.\n\nIOW, \"git stash -k\" followed by \"git stash\" and \"git stash pop\" the\none created with \"git stash -k\" would be an equivalent operation to\nthis new \"git stash -S\".  But the price of being able to combine\nthese three operations into one is that the user cannot have the\nstate after \"stash -k\" in the working tree to inspect, and I cannot\nshake the feeling that this new \"feature\" is like a tail wagging a\ndog.  If the \"goal\" is to \"create a stash entry out of what is in\nthe index\", then \"stash -S\" is a one-step handy tool that directly\nachieves that \"goal\", but that \"goal\" does not smell like a useful\n\"goal\" in the first place.  To \"create a commit by sifting mixed\nchanges in the working tree with 'add -p' and then gaining a chance\nto do a clean and final testing\" would be the \"goal\" of \"stash -k\",\nand that I can see a clear benefit.  Contrasting to that, I am not\nso sure about \"stash -S\".  It would be another way to eventually do\nthe same thing but along a more roundabout route.\n\nSo, I dunno.\n"},{"id":"438567","messageId":"87k0ii446g.fsf@osv.gnss.ru","threadId":"56624","inReplyTo":"xmqqo87u777d.fsf@gitster.g","subject":"Re: [PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-12T16:07:51Z","receivedAt":"2021-10-12T16:07:57Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> More importantly...\n>>\n>> Whenever I think about a new \"feature\", I try to come up with a\n>> story in which the feature effectively improves the end-user's life,\n>> how it fits in the larger picture, and enables something that is\n>> hard to do by combining other tools.\n>>\n>> The kind of \"story\" I would aim for is like this.  Suppose we were\n>> selling not \"git stash -S\" but \"git stash -k\". ...\n>\n\n[...]\n\n> So in short, I do not think I am strongly opposed to \"git stash -S\"\n> existing, since I did find one use case story that it could be used,\n> but I do think it is redundant and unnecessary.\n\nRedundant? Yes. Unnecessary? Yes. Useful? Yes. ;-)\n\nI took the steps to propose the new feature after yet another round of\n\"how do I quickly store this tiny bit of changes I just figured I need\nfor later, out of bunch of VIWIP changes?\"\n\n  git stash --staged\n\nis exactly the (currently missing) answer for me, as I have pretty\ninteractive tool to stage diff chunks always handy.\n\nWhat's your answer, I wonder?\n\nThat said, I'm also curious what story, if any, do you have for 'git\nstash --patch', as exactly the same story should be applicable to\nproposed 'git stash --staged', as far as I can see.\n\nThanks,\n-- Sergey Organov\n"},{"id":"438572","messageId":"xmqqzgre40ff.fsf@gitster.g","threadId":"56624","inReplyTo":"87k0ii446g.fsf@osv.gnss.ru","subject":"Re: [PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-12T17:28:52Z","receivedAt":"2021-10-12T17:29:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sergey Organov <sorganov@gmail.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Junio C Hamano <gitster@pobox.com> writes:\n>>\n>>> More importantly...\n>>>\n>>> Whenever I think about a new \"feature\", I try to come up with a\n>>> story in which the feature effectively improves the end-user's life,\n>>> how it fits in the larger picture, and enables something that is\n>>> hard to do by combining other tools.\n>>>\n>>> The kind of \"story\" I would aim for is like this.  Suppose we were\n>>> selling not \"git stash -S\" but \"git stash -k\". ...\n>>\n>\n> [...]\n>\n>> So in short, I do not think I am strongly opposed to \"git stash -S\"\n>> existing, since I did find one use case story that it could be used,\n>> but I do think it is redundant and unnecessary.\n>\n> Redundant? Yes. Unnecessary? Yes. Useful? Yes. ;-)\n>\n> I took the steps to propose the new feature after yet another round of\n> \"how do I quickly store this tiny bit of changes I just figured I need\n> for later, out of bunch of VIWIP changes?\"\n>\n>   git stash --staged\n>\n> is exactly the (currently missing) answer for me, as I have pretty\n> interactive tool to stage diff chunks always handy.\n>\n> What's your answer, I wonder?\n\nI am the one who questions the usefulness of \"stash --staged\" and\nthinks \"add -p\", \"stash -k\", test, \"commit\" is a much better way to\nsolve the \"we have a messy working tree and we want to create a\nclean multi-step end result out of it\" problem.\n\nI consider \"stash --staged\" as a solution in search of a problem, so\nyou'd need to ask somebody else for a problem that \"stash --staged\"\nis suitable for.\n\nAnd \"I want to stash away this tiny bit\" is better solved by *not*\ndoing \"git add\" it to the index and then stashing.  Rather, I'd just\ndo \"commit\" so that I can \"rebase -i\" to reorganize these bits\nlater.  Of course, to test the \"tiny bit\" standalone, I may use\n\"stash -k\" first, but do not see such a senario shows the merit of\nusing \"stash --staged\" over other tools.\n\n> That said, I'm also curious what story, if any, do you have for 'git\n> stash --patch', as exactly the same story should be applicable to\n> proposed 'git stash --staged', as far as I can see.\n\n\"stash --patch\" is also \"Meh\" from my point of view.  I do not\nstrongly object to its existence, it may be a OK tool for a small\nscale use, but I suspect it would be more frustrating than helpful\nto users when applied in a larger workflow story, just like I view\n\"git stash --staged\".\n\nThanks.\n"},{"id":"438582","messageId":"87lf2yqevj.fsf@osv.gnss.ru","threadId":"56624","inReplyTo":"xmqqzgre40ff.fsf@gitster.g","subject":"Re: [PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-12T18:25:52Z","receivedAt":"2021-10-12T18:25:58Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Sergey Organov <sorganov@gmail.com> writes:\n>\n>> Junio C Hamano <gitster@pobox.com> writes:\n>>\n>>> Junio C Hamano <gitster@pobox.com> writes:\n>>>\n>>>> More importantly...\n>>>>\n>>>> Whenever I think about a new \"feature\", I try to come up with a\n>>>> story in which the feature effectively improves the end-user's life,\n>>>> how it fits in the larger picture, and enables something that is\n>>>> hard to do by combining other tools.\n>>>>\n>>>> The kind of \"story\" I would aim for is like this.  Suppose we were\n>>>> selling not \"git stash -S\" but \"git stash -k\". ...\n>>>\n>>\n>> [...]\n>>\n>>> So in short, I do not think I am strongly opposed to \"git stash -S\"\n>>> existing, since I did find one use case story that it could be used,\n>>> but I do think it is redundant and unnecessary.\n>>\n>> Redundant? Yes. Unnecessary? Yes. Useful? Yes. ;-)\n>>\n>> I took the steps to propose the new feature after yet another round of\n>> \"how do I quickly store this tiny bit of changes I just figured I need\n>> for later, out of bunch of VIWIP changes?\"\n>>\n>>   git stash --staged\n>>\n>> is exactly the (currently missing) answer for me, as I have pretty\n>> interactive tool to stage diff chunks always handy.\n>>\n>> What's your answer, I wonder?\n>\n> I am the one who questions the usefulness of \"stash --staged\" and\n> thinks \"add -p\", \"stash -k\", test, \"commit\" is a much better way to\n> solve the \"we have a messy working tree and we want to create a\n> clean multi-step end result out of it\" problem\n\nI don't want to create a multi-step result out of it, if it means a\nseries of commits. The question is about a change that is *unrelated* to\nthe series I'm supposedly doing.\n\n>\n> I consider \"stash --staged\" as a solution in search of a problem, so\n> you'd need to ask somebody else for a problem that \"stash --staged\"\n> is suitable for.\n\nI didn't ask you what --staged is suitable for, sorry. I asked how do\nyou solve the problem of saving an *entirely unrelated* subset of\nchanges for future use?\n\nIf the answer is \"I don't have such problem\", it's OK with me, but my\npoint is that I, and at least a few others, seem to have such a problem\nfrequently enough to justify introduction of the --staged option.\n\n>\n> And \"I want to stash away this tiny bit\" is better solved by *not*\n> doing \"git add\" it to the index and then stashing.  Rather, I'd just\n> do \"commit\" so that I can \"rebase -i\" to reorganize these bits\n> later.  Of course, to test the \"tiny bit\" standalone, I may use\n> \"stash -k\" first, but do not see such a senario shows the merit of\n> using \"stash --staged\" over other tools.\n\nThat is a good solution for *different* problem. The changes I want to\nstash-out supposedly don't belong to the series of changes currently\nbeing worked on *at all*, and I don't want to test them right now as I'm\nworking on entirely unrelated set of problems and don't want to get\nside-tracked.\n\nSo, the analog here is not using \"stage -k\"->test->commit cycle, it's\nrather temporary switching to another branch and committing there, like\nthis:\n\n <hack, hack, hack...>\n <notice unrelated problem, give it a quick fix and stage it>\n $ git checkout -b tmp-fix-bla-bla\n $ git commit -m \"Will have to look at bla-bla later\"\n $ git checkout -\n <hack continues, probably using stash -k and rebasing as needed>\n <... time passes... >\n $ git switch some-branch\n $ git cherry-pick -n tmp-fix-bla-bla\n <... continue to work on the bla-bla fix ...>\n\nSee? But now, we already have such a wonderful place for temporary\nstates called \"stash\". Why should it be so hard to \"commit\" right to the\nstash instead of stomping around and then house-keeping of these\ntemporary non-branches? That's what \"stash --staged\" is suitable for,\nnot for creating clean sequence of commits out of a mess, where \"stash\n-k\" indeed shines.\n\n>\n>> That said, I'm also curious what story, if any, do you have for 'git\n>> stash --patch', as exactly the same story should be applicable to\n>> proposed 'git stash --staged', as far as I can see.\n>\n> \"stash --patch\" is also \"Meh\" from my point of view.  I do not\n> strongly object to its existence, it may be a OK tool for a small\n> scale use, but I suspect it would be more frustrating than helpful\n> to users when applied in a larger workflow story, just like I view\n> \"git stash --staged\".\n\nI see, thank you for clarification.\n\nThanks,\n-- Sergey Organov\n"},{"id":"438628","messageId":"xmqqo87til8b.fsf@gitster.g","threadId":"56624","inReplyTo":"87lf2yqevj.fsf@osv.gnss.ru","subject":"Re: [PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-13T04:48:04Z","receivedAt":"2021-10-13T04:48:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sergey Organov <sorganov@gmail.com> writes:\n\n> I didn't ask you what --staged is suitable for, sorry. I asked how do\n> you solve the problem of saving an *entirely unrelated* subset of\n> changes for future use?\n\nAh, OK.  I do not think I would \"git add\" unrelated pieces in the\nfirst place, so \"add -p && stash --staged\" as a way to stash away\nsuch a change would be quite foreign concept in my workflow.\n\nIOW, I'd concentrate on finishing the step I am working on, leaving\nunrelated changes in the working tree, and when I came to a good\nstopping point, I'd do the \"stash -k && test && commit\" dance,\nfollowed by \"stash pop\".  At that point,the working tree would have\nonly unrelated changes that I can stash away with \"stash save\".\n\nSo I guess perhaps your \"no such problem for me\" is the closest?\n\nThanks.\n"},{"id":"438659","messageId":"871r4pujkc.fsf@osv.gnss.ru","threadId":"56624","inReplyTo":"xmqqo87til8b.fsf@gitster.g","subject":"Re: [PATCH RFC v1] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-13T13:43:15Z","receivedAt":"2021-10-13T13:43:19Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Sergey Organov <sorganov@gmail.com> writes:\n>\n>> I didn't ask you what --staged is suitable for, sorry. I asked how do\n>> you solve the problem of saving an *entirely unrelated* subset of\n>> changes for future use?\n>\n> Ah, OK.  I do not think I would \"git add\" unrelated pieces in the\n> first place, so \"add -p && stash --staged\" as a way to stash away\n> such a change would be quite foreign concept in my workflow.\n>\n> IOW, I'd concentrate on finishing the step I am working on, leaving\n> unrelated changes in the working tree, and when I came to a good\n> stopping point, I'd do the \"stash -k && test && commit\" dance,\n> followed by \"stash pop\".  At that point,the working tree would have\n> only unrelated changes that I can stash away with \"stash save\".\n>\n> So I guess perhaps your \"no such problem for me\" is the closest?\n\nYep, looks like it is.\n\nFor me the problem with the approach you've adopted is that these\nunrelated changes distract my attention every time I select what is to\nbe in the next clean commit, and it gets annoying over time.\n\nThanks,\n-- Sergey Organov\n"},{"id":"438854","messageId":"87fst2gwia.fsf_-_@osv.gnss.ru","threadId":"56624","inReplyTo":"87lf2zz59w.fsf@osv.gnss.ru","subject":"[PATCH v2] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-15T15:04:13Z","receivedAt":"2021-10-15T15:04:20Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"\nStash only the changes that are staged.\n\nThis mode allows to easily stash-out for later reuse some changes\nunrelated to the current work in progress.\n\nUnlike 'stash push --patch', --staged supports use of any tool to\nselect the changes to stash-out, including, but not limited to 'git\nadd --interactive'.\n\nSigned-off-by: Sergey Organov <sorganov@gmail.com>\n---\n\nThis operation that is essentially just a 'git commit', but to the stash\nrather than to the current branch, is somehow missed, complicating the\ntask that is otherwise simple and natural. For example, see discussions\nhere:\n\nhttps://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible\n\nChanges in v2:\n\n  * Fixed English grammar in commit message\n\n  * Fixed copy-paste error in help description\n\nChanges in v1:\n\n  * Implement separate stash_staged() instead of re-using and changing\n    stash_patch()\n\n  * Add test\n\n  * Minor documentation cleanup\n\n Documentation/git-stash.txt | 34 ++++++++++++++--\n builtin/stash.c             | 80 ++++++++++++++++++++++++++++++++-----\n t/t3903-stash.sh            | 11 +++++\n 3 files changed, 113 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex be6084ccefbe..6e15f4752576 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n 'git stash' drop [-q|--quiet] [<stash>]\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n-'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n+'git stash' [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\n \t     [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n \t     [--pathspec-from-file=<file> [--pathspec-file-nul]]\n \t     [--] [<pathspec>...]]\n@@ -47,7 +47,7 @@ stash index (e.g. the integer `n` is equivalent to `stash@{n}`).\n COMMANDS\n --------\n \n-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>...]::\n+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>...]::\n \n \tSave your local modifications to a new 'stash entry' and roll them\n \tback to HEAD (in the working tree and in the index).\n@@ -60,7 +60,7 @@ subcommand from making an unwanted stash entry.  The two exceptions to this\n are `stash -p` which acts as alias for `stash push -p` and pathspec elements,\n which are allowed after a double hyphen `--` for disambiguation.\n \n-save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::\n+save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::\n \n \tThis option is deprecated in favour of 'git stash push'.  It\n \tdiffers from \"stash push\" in that it cannot take pathspec.\n@@ -205,6 +205,16 @@ to learn how to operate the `--patch` mode.\n The `--patch` option implies `--keep-index`.  You can use\n `--no-keep-index` to override this.\n \n+-S::\n+--staged::\n+\tThis option is only valid for `push` and `save` commands.\n++\n+Stash only the changes that are currently staged. This is similar to\n+basic `git commit` except the state is committed to the stash instead\n+of current branch.\n++\n+The `--patch` option has priority over this one.\n+\n --pathspec-from-file=<file>::\n \tThis option is only valid for `push` command.\n +\n@@ -341,6 +351,24 @@ $ edit/build/test remaining parts\n $ git commit foo -m 'Remaining parts'\n ----------------------------------------------------------------\n \n+Saving unrelated changes for future use::\n+\n+When you are in the middle of massive changes and you find some\n+unrelated issue that you don't want to forget to fix, you can do the\n+change(s), stage them, and use `git stash push --staged` to stash them\n+out for future use. This is similar to committing the staged changes,\n+only the commit ends-up being in the stash and not on the current branch.\n++\n+----------------------------------------------------------------\n+# ... hack hack hack ...\n+$ git add --patch foo           # add unrelated changes to the index\n+$ git stash push --staged       # save these changes to the stash\n+# ... hack hack hack, finish curent changes ...\n+$ git commit -m 'Massive'       # commit fully tested changes\n+$ git switch fixup-branch       # switch to another branch\n+$ git stash pop                 # to finish work on the saved changes\n+----------------------------------------------------------------\n+\n Recovering stash entries that were cleared/dropped erroneously::\n \n If you mistakenly drop or clear stash entries, they cannot be recovered\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 8f42360ca913..cdc142f16602 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -27,11 +27,11 @@ static const char * const git_stash_usage[] = {\n \tN_(\"git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]\"),\n \tN_(\"git stash branch <branchname> [<stash>]\"),\n \t\"git stash clear\",\n-\tN_(\"git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n+\tN_(\"git stash [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\\n\"\n \t   \"          [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n \t   \"          [--] [<pathspec>...]]\"),\n-\tN_(\"git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n+\tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n \tNULL\n };\n@@ -1116,6 +1116,38 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,\n \treturn ret;\n }\n \n+static int stash_staged(struct stash_info *info, const struct pathspec *ps,\n+\t\t       struct strbuf *out_patch, int quiet)\n+{\n+\tint ret = 0;\n+\tstruct child_process cp_diff_tree = CHILD_PROCESS_INIT;\n+\tstruct index_state istate = { NULL };\n+\n+\tif (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file,\n+\t\t\t\t0, NULL)) {\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n+\tcp_diff_tree.git_cmd = 1;\n+\tstrvec_pushl(&cp_diff_tree.args, \"diff-tree\", \"-p\", \"-U1\", \"HEAD\",\n+\t\t     oid_to_hex(&info->w_tree), \"--\", NULL);\n+\tif (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n+\tif (!out_patch->len) {\n+\t\tif (!quiet)\n+\t\t\tfprintf_ln(stderr, _(\"No changes selected\"));\n+\t\tret = 1;\n+\t}\n+\n+done:\n+\tdiscard_index(&istate);\n+\treturn ret;\n+}\n+\n static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n \t\t       struct strbuf *out_patch, int quiet)\n {\n@@ -1242,7 +1274,7 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps\n }\n \n static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,\n-\t\t\t   int include_untracked, int patch_mode,\n+\t\t\t   int include_untracked, int patch_mode, int only_staged,\n \t\t\t   struct stash_info *info, struct strbuf *patch,\n \t\t\t   int quiet)\n {\n@@ -1321,6 +1353,16 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\t} else if (ret > 0) {\n \t\t\tgoto done;\n \t\t}\n+\t} else if (only_staged) {\n+\t\tret = stash_staged(info, ps, patch, quiet);\n+\t\tif (ret < 0) {\n+\t\t\tif (!quiet)\n+\t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n+\t\t\t\t\t\t     \"staged state\"));\n+\t\t\tgoto done;\n+\t\t} else if (ret > 0) {\n+\t\t\tgoto done;\n+\t\t}\n \t} else {\n \t\tif (stash_working_tree(info, ps)) {\n \t\t\tif (!quiet)\n@@ -1379,7 +1421,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n \tif (!check_changes_tracked_files(&ps))\n \t\treturn 0;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,\n+\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n \t\t\t      NULL, 0);\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n@@ -1389,7 +1431,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n }\n \n static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,\n-\t\t\t int keep_index, int patch_mode, int include_untracked)\n+\t\t\t int keep_index, int patch_mode, int include_untracked, int only_staged)\n {\n \tint ret = 0;\n \tstruct stash_info info;\n@@ -1407,6 +1449,17 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \t\tgoto done;\n \t}\n \n+\t/* --patch overrides --staged */\n+\tif (patch_mode)\n+\t\tonly_staged = 0;\n+\n+\tif (only_staged && include_untracked) {\n+\t\tfprintf_ln(stderr, _(\"Can't use --staged and --include-untracked\"\n+\t\t\t\t     \" or --all at the same time\"));\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n \tread_cache_preload(NULL);\n \tif (!include_untracked && ps->nr) {\n \t\tint i;\n@@ -1447,7 +1500,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \n \tif (stash_msg)\n \t\tstrbuf_addstr(&stash_msg_buf, stash_msg);\n-\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,\n+\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,\n \t\t\t    &info, &patch, quiet)) {\n \t\tret = -1;\n \t\tgoto done;\n@@ -1464,7 +1517,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \t\tprintf_ln(_(\"Saved working directory and index state %s\"),\n \t\t\t  stash_msg_buf.buf);\n \n-\tif (!patch_mode) {\n+\tif (!(patch_mode || only_staged)) {\n \t\tif (include_untracked && !ps->nr) {\n \t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n \n@@ -1581,6 +1634,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n {\n \tint force_assume = 0;\n \tint keep_index = -1;\n+\tint only_staged = 0;\n \tint patch_mode = 0;\n \tint include_untracked = 0;\n \tint quiet = 0;\n@@ -1591,6 +1645,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n+\t\tOPT_BOOL('S', \"staged\", &only_staged,\n+\t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n@@ -1629,6 +1685,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\tif (patch_mode)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with --patch\"));\n \n+\t\tif (only_staged)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --staged\"));\n+\n \t\tif (ps.nr)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n \n@@ -1640,12 +1699,13 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t}\n \n \treturn do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,\n-\t\t\t     include_untracked);\n+\t\t\t     include_untracked, only_staged);\n }\n \n static int save_stash(int argc, const char **argv, const char *prefix)\n {\n \tint keep_index = -1;\n+\tint only_staged = 0;\n \tint patch_mode = 0;\n \tint include_untracked = 0;\n \tint quiet = 0;\n@@ -1656,6 +1716,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n+\t\tOPT_BOOL('S', \"staged\", &only_staged,\n+\t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n@@ -1677,7 +1739,7 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \n \tmemset(&ps, 0, sizeof(ps));\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index,\n-\t\t\t    patch_mode, include_untracked);\n+\t\t\t    patch_mode, include_untracked, only_staged);\n \n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 873aa56e359d..18ea885412b8 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -288,6 +288,17 @@ test_expect_success 'stash --no-keep-index' '\n \ttest bar,bar2 = $(cat file),$(cat file2)\n '\n \n+test_expect_success 'stash --staged' '\n+\techo bar3 >file &&\n+\techo bar4 >file2 &&\n+\tgit add file2 &&\n+\tgit stash --staged &&\n+\ttest bar3,bar2 = $(cat file),$(cat file2) &&\n+\tgit reset --hard &&\n+\tgit stash pop &&\n+\ttest bar,bar4 = $(cat file),$(cat file2)\n+'\n+\n test_expect_success 'dont assume push with non-option args' '\n \ttest_must_fail git stash -q drop 2>err &&\n \ttest_i18ngrep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''drop'\\''\" err\n-- \n2.25.1\n\n"},{"id":"438888","messageId":"xmqq5yty6uh1.fsf@gitster.g","threadId":"56624","inReplyTo":"87fst2gwia.fsf_-_@osv.gnss.ru","subject":"Re: [PATCH v2] stash: implement '--staged' option for 'push' and 'save'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-15T17:58:18Z","receivedAt":"2021-10-15T17:58:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sergey Organov <sorganov@gmail.com> writes:\n\n> @@ -205,6 +205,16 @@ to learn how to operate the `--patch` mode.\n>  The `--patch` option implies `--keep-index`.  You can use\n>  `--no-keep-index` to override this.\n>  \n> +-S::\n> +--staged::\n> +\tThis option is only valid for `push` and `save` commands.\n> ++\n> +Stash only the changes that are currently staged. This is similar to\n> +basic `git commit` except the state is committed to the stash instead\n> +of current branch.\n> ++\n> +The `--patch` option has priority over this one.\n> +\n>  --pathspec-from-file=<file>::\n>  \tThis option is only valid for `push` command.\n>  +\n> @@ -341,6 +351,24 @@ $ edit/build/test remaining parts\n>  $ git commit foo -m 'Remaining parts'\n>  ----------------------------------------------------------------\n>  \n> +Saving unrelated changes for future use::\n> +\n> +When you are in the middle of massive changes and you find some\n> +unrelated issue that you don't want to forget to fix, you can do the\n> +change(s), stage them, and use `git stash push --staged` to stash them\n> +out for future use. This is similar to committing the staged changes,\n> +only the commit ends-up being in the stash and not on the current branch.\n> ++\n> +----------------------------------------------------------------\n> +# ... hack hack hack ...\n> +$ git add --patch foo           # add unrelated changes to the index\n> +$ git stash push --staged       # save these changes to the stash\n> +# ... hack hack hack, finish curent changes ...\n> +$ git commit -m 'Massive'       # commit fully tested changes\n> +$ git switch fixup-branch       # switch to another branch\n> +$ git stash pop                 # to finish work on the saved changes\n> +----------------------------------------------------------------\n> +\n\nThe last step would more like \"to start working on top of the saved\nchanges\", I would think, as the user did not want to bother thinking\nabout them earlier while working on the other theme.  Otherwise, the\nuser would have done\n\n    git checkout -b fixup-branch\n    git add -p\n    git commit -m '[WIP] mostly done' -e\n    git checkout -\n\nto remember that the fixup is not quite but mostly done and what was\ndone so far.\n\nBut I'd agree that the new mode would fit in such a workflow.\n\n\n> +static int stash_staged(struct stash_info *info, const struct pathspec *ps,\n> +\t\t       struct strbuf *out_patch, int quiet)\n> +{\n> +\tint ret = 0;\n> +\tstruct child_process cp_diff_tree = CHILD_PROCESS_INIT;\n> +\tstruct index_state istate = { NULL };\n> +\n> +\tif (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file,\n> +\t\t\t\t0, NULL)) {\n> +\t\tret = -1;\n> +\t\tgoto done;\n> +\t}\n\nOK.  So what is currently in the index becomes the w-tree.\n\n> +\tcp_diff_tree.git_cmd = 1;\n> +\tstrvec_pushl(&cp_diff_tree.args, \"diff-tree\", \"-p\", \"-U1\", \"HEAD\",\n> +\t\t     oid_to_hex(&info->w_tree), \"--\", NULL);\n> +\tif (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {\n> +\t\tret = -1;\n> +\t\tgoto done;\n> +\t}\n> +\n> +\tif (!out_patch->len) {\n> +\t\tif (!quiet)\n> +\t\t\tfprintf_ln(stderr, _(\"No changes selected\"));\n> +\t\tret = 1;\n> +\t}\n\nThis seems to have been taken from the \"stash_patch()\" flow, but\nunlike the \"stash -p\" that goes interactive to let the user pick\nhunks, in which context \"oh, no, you did not SELECT anything\" makes\nperfect sense as an error message, this message would be confusing\nto users who weren't offered a chance to select.\n\n> +done:\n> +\tdiscard_index(&istate);\n> +\treturn ret;\n> +}\n> +\n\nAlso, as stash_staged() and stash_patch() are _so_ close, I suspect\nthat we might want to make a common helper out of the original\nstash_patch() and make stash_patch() a thin wrapper around it in the\nstep #1 of the series, and then add stash_staged() as a second\ncaller to the common helper.  That might result in a cleaner end\nresult.  And optionally the \"do we really need to spawn diff-tree\"\noptimization can be done on top after the dust settles [*].\n\n    [Side note] As we already have the tree object for the stashed\n    state, I wonder if it is overkill to run \"diff-tree\" here,\n    though.  Wouldn't it be a matter of comparing that tree object\n    name with the tree object name of the HEAD?  Something like\n\n            get_oid_treeish(\"HEAD:\", &head_tree);\n            if (oideq(&head_tree, &info->w_tree)) {\n                    \"Nothing staged\";\n                    ret -1;\n            }\n\n    may be a good starting point, perhaps?\n\n    But as I hinted above, such an optimization is outside the scope\n    of this topic.  As long as we do not duplicate the code to spawn\n    diff-tree from stash_patch(), but use a shared helper between\n    the two codepath, such an optimization is easily doable later.\n\n>  static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,\n> -\t\t\t   int include_untracked, int patch_mode,\n> +\t\t\t   int include_untracked, int patch_mode, int only_staged,\n\nLet's not keep adding a parameter to represent just a bit (or less).\nCan't we at least treat this as an auxiliary bit in the \"patch_mode\"\nflag?  The traditional patch_mode may be variant #1 while the one\nthat does the same thing but skips the interactive hunk selection\n(i.e. only-staged) becomes the variant #2, or something?\n\n> @@ -1321,6 +1353,16 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n>  \t\t} else if (ret > 0) {\n>  \t\t\tgoto done;\n>  \t\t}\n> +\t} else if (only_staged) {\n> +\t\tret = stash_staged(info, ps, patch, quiet);\n> +\t\tif (ret < 0) {\n> +\t\t\tif (!quiet)\n> +\t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n> +\t\t\t\t\t\t     \"staged state\"));\n> +\t\t\tgoto done;\n> +\t\t} else if (ret > 0) {\n> +\t\t\tgoto done;\n> +\t\t}\n\n... which would reduce the need to add yet another else-if to this\ncascade.\n\n> @@ -1379,7 +1421,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n>  \tif (!check_changes_tracked_files(&ps))\n>  \t\treturn 0;\n>  \n> -\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,\n> +\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n>  \t\t\t      NULL, 0);\n\nand no need to touch this hunk.\n\n> @@ -1389,7 +1431,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n>  }\n>  \n>  static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,\n> -\t\t\t int keep_index, int patch_mode, int include_untracked)\n> +\t\t\t int keep_index, int patch_mode, int include_untracked, int only_staged)\n>  {\n\nnor this one.\n\nI think I can agree with the motivation now (thanks for the\ndiscussion); the code may want a bit more cleaning up.\n\nThanks.\n"},{"id":"438894","messageId":"87pms6cdnk.fsf@osv.gnss.ru","threadId":"56624","inReplyTo":"xmqq5yty6uh1.fsf@gitster.g","subject":"Re: [PATCH v2] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-15T19:05:03Z","receivedAt":"2021-10-15T19:05:08Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Sergey Organov <sorganov@gmail.com> writes:\n>\n>> @@ -205,6 +205,16 @@ to learn how to operate the `--patch` mode.\n>>  The `--patch` option implies `--keep-index`.  You can use\n>>  `--no-keep-index` to override this.\n>>  \n>> +-S::\n>> +--staged::\n>> +\tThis option is only valid for `push` and `save` commands.\n>> ++\n>> +Stash only the changes that are currently staged. This is similar to\n>> +basic `git commit` except the state is committed to the stash instead\n>> +of current branch.\n>> ++\n>> +The `--patch` option has priority over this one.\n>> +\n>>  --pathspec-from-file=<file>::\n>>  \tThis option is only valid for `push` command.\n>>  +\n>> @@ -341,6 +351,24 @@ $ edit/build/test remaining parts\n>>  $ git commit foo -m 'Remaining parts'\n>>  ----------------------------------------------------------------\n>>  \n>> +Saving unrelated changes for future use::\n>> +\n>> +When you are in the middle of massive changes and you find some\n>> +unrelated issue that you don't want to forget to fix, you can do the\n>> +change(s), stage them, and use `git stash push --staged` to stash them\n>> +out for future use. This is similar to committing the staged changes,\n>> +only the commit ends-up being in the stash and not on the current branch.\n>> ++\n>> +----------------------------------------------------------------\n>> +# ... hack hack hack ...\n>> +$ git add --patch foo           # add unrelated changes to the index\n>> +$ git stash push --staged       # save these changes to the stash\n>> +# ... hack hack hack, finish curent changes ...\n>> +$ git commit -m 'Massive'       # commit fully tested changes\n>> +$ git switch fixup-branch       # switch to another branch\n>> +$ git stash pop                 # to finish work on the saved changes\n>> +----------------------------------------------------------------\n>> +\n>\n> The last step would more like \"to start working on top of the saved\n> changes\", I would think, as the user did not want to bother thinking\n> about them earlier while working on the other theme.  Otherwise, the\n> user would have done\n>\n>     git checkout -b fixup-branch\n>     git add -p\n>     git commit -m '[WIP] mostly done' -e\n>     git checkout -\n>\n> to remember that the fixup is not quite but mostly done and what was\n> done so far.\n\nYep, that's why I said: \"to finish work on the saved changes\" in the\ncomments. If it's not clear enough, I'm open for suggestions for\nclarifications.\n\n>\n> But I'd agree that the new mode would fit in such a workflow.\n>\n>\n>> +static int stash_staged(struct stash_info *info, const struct pathspec *ps,\n>> +\t\t       struct strbuf *out_patch, int quiet)\n>> +{\n>> +\tint ret = 0;\n>> +\tstruct child_process cp_diff_tree = CHILD_PROCESS_INIT;\n>> +\tstruct index_state istate = { NULL };\n>> +\n>> + if (write_index_as_tree(&info->w_tree, &istate,\n>> the_repository->index_file,\n>> +\t\t\t\t0, NULL)) {\n>> +\t\tret = -1;\n>> +\t\tgoto done;\n>> +\t}\n>\n> OK.  So what is currently in the index becomes the w-tree.\n>\n>> +\tcp_diff_tree.git_cmd = 1;\n>> +\tstrvec_pushl(&cp_diff_tree.args, \"diff-tree\", \"-p\", \"-U1\", \"HEAD\",\n>> +\t\t     oid_to_hex(&info->w_tree), \"--\", NULL);\n>> +\tif (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {\n>> +\t\tret = -1;\n>> +\t\tgoto done;\n>> +\t}\n>> +\n>> +\tif (!out_patch->len) {\n>> +\t\tif (!quiet)\n>> +\t\t\tfprintf_ln(stderr, _(\"No changes selected\"));\n>> +\t\tret = 1;\n>> +\t}\n>\n> This seems to have been taken from the \"stash_patch()\" flow,\n\nYep, exactly. I'm not familiar enough with the code to easily write it\nby myself, so it's basically a copy-paste of stash_patch() and getting\nrid of all the unneeded stuff.\n\n> but\n> unlike the \"stash -p\" that goes interactive to let the user pick\n> hunks, in which context \"oh, no, you did not SELECT anything\" makes\n> perfect sense as an error message, this message would be confusing\n> to users who weren't offered a chance to select.\n\nIt seems to me that it makes sense to leave this warning as is, in case\nthe user invoked \"stash --staged\" without anything staged. I'm OK to\nchange this if you have something better in mind.\n\n>\n>> +done:\n>> +\tdiscard_index(&istate);\n>> +\treturn ret;\n>> +}\n>> +\n>\n> Also, as stash_staged() and stash_patch() are _so_ close, I suspect\n> that we might want to make a common helper out of the original\n> stash_patch() and make stash_patch() a thin wrapper around it in the\n> step #1 of the series, and then add stash_staged() as a second\n> caller to the common helper.  That might result in a cleaner end\n> result.  And optionally the \"do we really need to spawn diff-tree\"\n> optimization can be done on top after the dust settles [*].\n\nI do see a few of opportunities to improve code quality of \"git stash\",\neven before this patch, but that is was not an aim of this patch.\n\nWhat I aimed for is to keep all the existing code as intact as possible\nto minimize probability of unintended breakage.\n\n[...]\n\n>>  static int do_create_stash(const struct pathspec *ps, struct strbuf\n>> *stash_msg_buf,\n>> -\t\t\t   int include_untracked, int patch_mode,\n>> + int include_untracked, int patch_mode, int only_staged,\n>\n> Let's not keep adding a parameter to represent just a bit (or less).\n> Can't we at least treat this as an auxiliary bit in the \"patch_mode\"\n> flag?  The traditional patch_mode may be variant #1 while the one\n> that does the same thing but skips the interactive hunk selection\n> (i.e. only-staged) becomes the variant #2, or something?\n\nI don't want to mix --patch and --staged, as I believe --patch, after\nintroduction of --staged, becomes almost useless. One can instead use\n\"git add --interactive\" directly and then run \"stash --staged\" on the\nresult.\n\nThat said, to get rid of these function arguments bloating, the code\nshould better be rewritten to use structure to hold all the parameters,\nbut that's again not the material of this patch.\n\n>\n>> @@ -1321,6 +1353,16 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n>>  \t\t} else if (ret > 0) {\n>>  \t\t\tgoto done;\n>>  \t\t}\n>> +\t} else if (only_staged) {\n>> +\t\tret = stash_staged(info, ps, patch, quiet);\n>> +\t\tif (ret < 0) {\n>> +\t\t\tif (!quiet)\n>> +\t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n>> +\t\t\t\t\t\t     \"staged state\"));\n>> +\t\t\tgoto done;\n>> +\t\t} else if (ret > 0) {\n>> +\t\t\tgoto done;\n>> +\t\t}\n>\n> ... which would reduce the need to add yet another else-if to this\n> cascade.\n\nThese else-if cascades could *all* be reduced. The code does need actual\nre-factoring, independently of this patch.\n\n>\n>> @@ -1379,7 +1421,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n>>  \tif (!check_changes_tracked_files(&ps))\n>>  \t\treturn 0;\n>>  \n>> -\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,\n>> +\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n>>  \t\t\t      NULL, 0);\n>\n> and no need to touch this hunk.\n\nYep, if they all were in parameters structure in the first place, it'd\nbe unneeded.\n\n>\n>> @@ -1389,7 +1431,7 @@ static int create_stash(int argc, const char\n>> **argv, const char *prefix)\n>>  }\n>>  \n>>  static int do_push_stash(const struct pathspec *ps, const char\n>> *stash_msg, int quiet,\n>> -\t\t\t int keep_index, int patch_mode, int include_untracked)\n>> + int keep_index, int patch_mode, int include_untracked, int\n>> only_staged)\n>>  {\n>\n> nor this one.\n\nYep, but that's the generic problem with the code. Mixing \"only_staged\"\ninto \"patch_mode\" would only worsen the code.\n\n>\n> I think I can agree with the motivation now (thanks for the\n> discussion); the code may want a bit more cleaning up.\n\nI do thing the code of \"git stash\" needs cleanup. Independently of the\npatch. If somebody gets time to cleanup the original code, I'll happily\nadapt the patch to the changes.\n\nOtherwise, maybe I'll find time later to refactor the resulting code of\nthe \"git stash\" myself, but I'd like the patch to get its place first.\n\nIf you insist, I can provide a follow-up patch that factors-out minor\nbit of common code from --patch and --staged, but I really don't want to\nmix \"only_staged\" flag into the \"patch_mode\" flag, sorry.\n\nThanks,\n-- Sergey Organov\n"},{"id":"438895","messageId":"xmqqsfx25c09.fsf@gitster.g","threadId":"56624","inReplyTo":"87pms6cdnk.fsf@osv.gnss.ru","subject":"Re: [PATCH v2] stash: implement '--staged' option for 'push' and 'save'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-15T19:22:30Z","receivedAt":"2021-10-15T19:22:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sergey Organov <sorganov@gmail.com> writes:\n\n>> but\n>> unlike the \"stash -p\" that goes interactive to let the user pick\n>> hunks, in which context \"oh, no, you did not SELECT anything\" makes\n>> perfect sense as an error message, this message would be confusing\n>> to users who weren't offered a chance to select.\n>\n> It seems to me that it makes sense to leave this warning as is, in case\n> the user invoked \"stash --staged\" without anything staged. I'm OK to\n> change this if you have something better in mind.\n\nI am not questioning the presense of the warning.  It is just the\nphrasing of the warning---\"You have nothing staged\" would make a\ngood message, but \"You didn't select anything\", when we do not offer\nthem a chance to select in the first place, would not work well.\n"},{"id":"438904","messageId":"87lf2uavum.fsf@osv.gnss.ru","threadId":"56624","inReplyTo":"xmqqsfx25c09.fsf@gitster.g","subject":"Re: [PATCH v2] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-15T20:14:57Z","receivedAt":"2021-10-15T20:15:03Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Sergey Organov <sorganov@gmail.com> writes:\n>\n>>> but\n>>> unlike the \"stash -p\" that goes interactive to let the user pick\n>>> hunks, in which context \"oh, no, you did not SELECT anything\" makes\n>>> perfect sense as an error message, this message would be confusing\n>>> to users who weren't offered a chance to select.\n>>\n>> It seems to me that it makes sense to leave this warning as is, in case\n>> the user invoked \"stash --staged\" without anything staged. I'm OK to\n>> change this if you have something better in mind.\n>\n> I am not questioning the presense of the warning.  It is just the\n> phrasing of the warning---\"You have nothing staged\" would make a\n> good message, but \"You didn't select anything\", when we do not offer\n> them a chance to select in the first place, would not work well.\n\nAh, I agree, your phrasing is much better, -- will fix.\n\nThanks,\n-- Sergey Organov\n"},{"id":"438909","messageId":"87ee8mavk3.fsf@osv.gnss.ru","threadId":"56624","inReplyTo":"87lf2uavum.fsf@osv.gnss.ru","subject":"Re: [PATCH v2] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-15T20:21:16Z","receivedAt":"2021-10-15T20:21:22Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Sergey Organov <sorganov@gmail.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Sergey Organov <sorganov@gmail.com> writes:\n>>\n>>>> but\n>>>> unlike the \"stash -p\" that goes interactive to let the user pick\n>>>> hunks, in which context \"oh, no, you did not SELECT anything\" makes\n>>>> perfect sense as an error message, this message would be confusing\n>>>> to users who weren't offered a chance to select.\n>>>\n>>> It seems to me that it makes sense to leave this warning as is, in case\n>>> the user invoked \"stash --staged\" without anything staged. I'm OK to\n>>> change this if you have something better in mind.\n>>\n>> I am not questioning the presense of the warning.  It is just the\n>> phrasing of the warning---\"You have nothing staged\" would make a\n>> good message, but \"You didn't select anything\", when we do not offer\n>> them a chance to select in the first place, would not work well.\n>\n> Ah, I agree, your phrasing is much better, -- will fix.\n\nChanged to \"No staged changes\" for the next re-roll. Looks OK?\n\nThanks,\n-- Sergey Organov\n"},{"id":"438987","messageId":"87pms2mi1p.fsf_-_@osv.gnss.ru","threadId":"56624","inReplyTo":"87fst2gwia.fsf_-_@osv.gnss.ru","subject":"[PATCH v3] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-18T16:09:06Z","receivedAt":"2021-10-18T16:09:16Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Stash only the changes that are staged.\n\nThis mode allows to easily stash-out for later reuse some changes\nunrelated to the current work in progress.\n\nUnlike 'stash push --patch', --staged supports use of any tool to\nselect the changes to stash-out, including, but not limited to 'git\nadd --interactive'.\n\nSigned-off-by: Sergey Organov <sorganov@gmail.com>\n---\n\nThis operation that is essentially just a 'git commit', but to the stash\nrather than to the current branch, is somehow missed, complicating the\ntask that is otherwise simple and natural. For example, see discussions\nhere:\n\nhttps://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible\n\nChanges in v3:\n\n  * Fixed copy-pasted warning to better \"No staged changes\"\n\nChanges in v2:\n\n  * Fixed English grammar in commit message\n\n  * Fixed copy-paste error in help description\n\nChanges in v1:\n\n  * Implement separate stash_staged() instead of re-using and changing\n    stash_patch()\n\n  * Add test\n\n  * Minor documentation cleanup\n\n Documentation/git-stash.txt | 34 ++++++++++++++--\n builtin/stash.c             | 80 ++++++++++++++++++++++++++++++++-----\n t/t3903-stash.sh            | 11 +++++\n 3 files changed, 113 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex be6084ccefbe..6e15f4752576 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n 'git stash' drop [-q|--quiet] [<stash>]\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n-'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n+'git stash' [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\n \t     [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n \t     [--pathspec-from-file=<file> [--pathspec-file-nul]]\n \t     [--] [<pathspec>...]]\n@@ -47,7 +47,7 @@ stash index (e.g. the integer `n` is equivalent to `stash@{n}`).\n COMMANDS\n --------\n \n-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>...]::\n+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>...]::\n \n \tSave your local modifications to a new 'stash entry' and roll them\n \tback to HEAD (in the working tree and in the index).\n@@ -60,7 +60,7 @@ subcommand from making an unwanted stash entry.  The two exceptions to this\n are `stash -p` which acts as alias for `stash push -p` and pathspec elements,\n which are allowed after a double hyphen `--` for disambiguation.\n \n-save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::\n+save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::\n \n \tThis option is deprecated in favour of 'git stash push'.  It\n \tdiffers from \"stash push\" in that it cannot take pathspec.\n@@ -205,6 +205,16 @@ to learn how to operate the `--patch` mode.\n The `--patch` option implies `--keep-index`.  You can use\n `--no-keep-index` to override this.\n \n+-S::\n+--staged::\n+\tThis option is only valid for `push` and `save` commands.\n++\n+Stash only the changes that are currently staged. This is similar to\n+basic `git commit` except the state is committed to the stash instead\n+of current branch.\n++\n+The `--patch` option has priority over this one.\n+\n --pathspec-from-file=<file>::\n \tThis option is only valid for `push` command.\n +\n@@ -341,6 +351,24 @@ $ edit/build/test remaining parts\n $ git commit foo -m 'Remaining parts'\n ----------------------------------------------------------------\n \n+Saving unrelated changes for future use::\n+\n+When you are in the middle of massive changes and you find some\n+unrelated issue that you don't want to forget to fix, you can do the\n+change(s), stage them, and use `git stash push --staged` to stash them\n+out for future use. This is similar to committing the staged changes,\n+only the commit ends-up being in the stash and not on the current branch.\n++\n+----------------------------------------------------------------\n+# ... hack hack hack ...\n+$ git add --patch foo           # add unrelated changes to the index\n+$ git stash push --staged       # save these changes to the stash\n+# ... hack hack hack, finish curent changes ...\n+$ git commit -m 'Massive'       # commit fully tested changes\n+$ git switch fixup-branch       # switch to another branch\n+$ git stash pop                 # to finish work on the saved changes\n+----------------------------------------------------------------\n+\n Recovering stash entries that were cleared/dropped erroneously::\n \n If you mistakenly drop or clear stash entries, they cannot be recovered\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 8f42360ca913..49e7b748e334 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -27,11 +27,11 @@ static const char * const git_stash_usage[] = {\n \tN_(\"git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]\"),\n \tN_(\"git stash branch <branchname> [<stash>]\"),\n \t\"git stash clear\",\n-\tN_(\"git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n+\tN_(\"git stash [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\\n\"\n \t   \"          [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n \t   \"          [--] [<pathspec>...]]\"),\n-\tN_(\"git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n+\tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n \tNULL\n };\n@@ -1116,6 +1116,38 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,\n \treturn ret;\n }\n \n+static int stash_staged(struct stash_info *info, const struct pathspec *ps,\n+\t\t\tstruct strbuf *out_patch, int quiet)\n+{\n+\tint ret = 0;\n+\tstruct child_process cp_diff_tree = CHILD_PROCESS_INIT;\n+\tstruct index_state istate = { NULL };\n+\n+\tif (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file,\n+\t\t\t\t0, NULL)) {\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n+\tcp_diff_tree.git_cmd = 1;\n+\tstrvec_pushl(&cp_diff_tree.args, \"diff-tree\", \"-p\", \"-U1\", \"HEAD\",\n+\t\t     oid_to_hex(&info->w_tree), \"--\", NULL);\n+\tif (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n+\tif (!out_patch->len) {\n+\t\tif (!quiet)\n+\t\t\tfprintf_ln(stderr, _(\"No staged changes\"));\n+\t\tret = 1;\n+\t}\n+\n+done:\n+\tdiscard_index(&istate);\n+\treturn ret;\n+}\n+\n static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n \t\t       struct strbuf *out_patch, int quiet)\n {\n@@ -1242,7 +1274,7 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps\n }\n \n static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,\n-\t\t\t   int include_untracked, int patch_mode,\n+\t\t\t   int include_untracked, int patch_mode, int only_staged,\n \t\t\t   struct stash_info *info, struct strbuf *patch,\n \t\t\t   int quiet)\n {\n@@ -1321,6 +1353,16 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\t} else if (ret > 0) {\n \t\t\tgoto done;\n \t\t}\n+\t} else if (only_staged) {\n+\t\tret = stash_staged(info, ps, patch, quiet);\n+\t\tif (ret < 0) {\n+\t\t\tif (!quiet)\n+\t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n+\t\t\t\t\t\t     \"staged state\"));\n+\t\t\tgoto done;\n+\t\t} else if (ret > 0) {\n+\t\t\tgoto done;\n+\t\t}\n \t} else {\n \t\tif (stash_working_tree(info, ps)) {\n \t\t\tif (!quiet)\n@@ -1379,7 +1421,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n \tif (!check_changes_tracked_files(&ps))\n \t\treturn 0;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,\n+\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n \t\t\t      NULL, 0);\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n@@ -1389,7 +1431,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n }\n \n static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,\n-\t\t\t int keep_index, int patch_mode, int include_untracked)\n+\t\t\t int keep_index, int patch_mode, int include_untracked, int only_staged)\n {\n \tint ret = 0;\n \tstruct stash_info info;\n@@ -1407,6 +1449,17 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \t\tgoto done;\n \t}\n \n+\t/* --patch overrides --staged */\n+\tif (patch_mode)\n+\t\tonly_staged = 0;\n+\n+\tif (only_staged && include_untracked) {\n+\t\tfprintf_ln(stderr, _(\"Can't use --staged and --include-untracked\"\n+\t\t\t\t     \" or --all at the same time\"));\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n \tread_cache_preload(NULL);\n \tif (!include_untracked && ps->nr) {\n \t\tint i;\n@@ -1447,7 +1500,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \n \tif (stash_msg)\n \t\tstrbuf_addstr(&stash_msg_buf, stash_msg);\n-\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,\n+\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,\n \t\t\t    &info, &patch, quiet)) {\n \t\tret = -1;\n \t\tgoto done;\n@@ -1464,7 +1517,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \t\tprintf_ln(_(\"Saved working directory and index state %s\"),\n \t\t\t  stash_msg_buf.buf);\n \n-\tif (!patch_mode) {\n+\tif (!(patch_mode || only_staged)) {\n \t\tif (include_untracked && !ps->nr) {\n \t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n \n@@ -1581,6 +1634,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n {\n \tint force_assume = 0;\n \tint keep_index = -1;\n+\tint only_staged = 0;\n \tint patch_mode = 0;\n \tint include_untracked = 0;\n \tint quiet = 0;\n@@ -1591,6 +1645,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n+\t\tOPT_BOOL('S', \"staged\", &only_staged,\n+\t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n@@ -1629,6 +1685,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\tif (patch_mode)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with --patch\"));\n \n+\t\tif (only_staged)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --staged\"));\n+\n \t\tif (ps.nr)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n \n@@ -1640,12 +1699,13 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t}\n \n \treturn do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,\n-\t\t\t     include_untracked);\n+\t\t\t     include_untracked, only_staged);\n }\n \n static int save_stash(int argc, const char **argv, const char *prefix)\n {\n \tint keep_index = -1;\n+\tint only_staged = 0;\n \tint patch_mode = 0;\n \tint include_untracked = 0;\n \tint quiet = 0;\n@@ -1656,6 +1716,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n+\t\tOPT_BOOL('S', \"staged\", &only_staged,\n+\t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n@@ -1677,7 +1739,7 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \n \tmemset(&ps, 0, sizeof(ps));\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index,\n-\t\t\t    patch_mode, include_untracked);\n+\t\t\t    patch_mode, include_untracked, only_staged);\n \n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 873aa56e359d..18ea885412b8 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -288,6 +288,17 @@ test_expect_success 'stash --no-keep-index' '\n \ttest bar,bar2 = $(cat file),$(cat file2)\n '\n \n+test_expect_success 'stash --staged' '\n+\techo bar3 >file &&\n+\techo bar4 >file2 &&\n+\tgit add file2 &&\n+\tgit stash --staged &&\n+\ttest bar3,bar2 = $(cat file),$(cat file2) &&\n+\tgit reset --hard &&\n+\tgit stash pop &&\n+\ttest bar,bar4 = $(cat file),$(cat file2)\n+'\n+\n test_expect_success 'dont assume push with non-option args' '\n \ttest_must_fail git stash -q drop 2>err &&\n \ttest_i18ngrep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''drop'\\''\" err\n-- \n2.25.1\n\n"},{"id":"439624","messageId":"YXeMkC/jPxjzNgWF@coredump.intra.peff.net","threadId":"56624","inReplyTo":"87pms2mi1p.fsf_-_@osv.gnss.ru","subject":"Re: [PATCH v3] stash: implement '--staged' option for 'push' and 'save'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-10-26T05:05:20Z","receivedAt":"2021-10-26T05:05:28Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Oct 18, 2021 at 07:09:06PM +0300, Sergey Organov wrote:\n\n> +static int stash_staged(struct stash_info *info, const struct pathspec *ps,\n> +\t\t\tstruct strbuf *out_patch, int quiet)\n> +{\n> +\tint ret = 0;\n> +\tstruct child_process cp_diff_tree = CHILD_PROCESS_INIT;\n> +\tstruct index_state istate = { NULL };\n> +\n> +\tif (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file,\n> +\t\t\t\t0, NULL)) {\n> +\t\tret = -1;\n> +\t\tgoto done;\n> +\t}\n> +\n> +\tcp_diff_tree.git_cmd = 1;\n> +\tstrvec_pushl(&cp_diff_tree.args, \"diff-tree\", \"-p\", \"-U1\", \"HEAD\",\n> +\t\t     oid_to_hex(&info->w_tree), \"--\", NULL);\n> +\tif (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {\n> +\t\tret = -1;\n> +\t\tgoto done;\n> +\t}\n> +\n> +\tif (!out_patch->len) {\n> +\t\tif (!quiet)\n> +\t\t\tfprintf_ln(stderr, _(\"No staged changes\"));\n> +\t\tret = 1;\n> +\t}\n> +\n> +done:\n> +\tdiscard_index(&istate);\n> +\treturn ret;\n> +}\n\nThis function doesn't look at its \"struct pathspec\" parameter at all.\nI'm not sure if that's a bug (i.e., it should be restricting the diff\nhere) or if it was just pulled unnecessarily over from stash_patch().\n\n-Peff\n"},{"id":"439789","messageId":"8735ombizs.fsf@osv.gnss.ru","threadId":"56624","inReplyTo":"YXeMkC/jPxjzNgWF@coredump.intra.peff.net","subject":"Re: [PATCH v3] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-27T15:11:19Z","receivedAt":"2021-10-27T15:11:25Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Mon, Oct 18, 2021 at 07:09:06PM +0300, Sergey Organov wrote:\n>\n>> +static int stash_staged(struct stash_info *info, const struct pathspec *ps,\n>> +\t\t\tstruct strbuf *out_patch, int quiet)\n>> +{\n>> +\tint ret = 0;\n>> +\tstruct child_process cp_diff_tree = CHILD_PROCESS_INIT;\n>> +\tstruct index_state istate = { NULL };\n>> +\n>> + if (write_index_as_tree(&info->w_tree, &istate,\n>> the_repository->index_file,\n>> +\t\t\t\t0, NULL)) {\n>> +\t\tret = -1;\n>> +\t\tgoto done;\n>> +\t}\n>> +\n>> +\tcp_diff_tree.git_cmd = 1;\n>> +\tstrvec_pushl(&cp_diff_tree.args, \"diff-tree\", \"-p\", \"-U1\", \"HEAD\",\n>> +\t\t     oid_to_hex(&info->w_tree), \"--\", NULL);\n>> +\tif (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {\n>> +\t\tret = -1;\n>> +\t\tgoto done;\n>> +\t}\n>> +\n>> +\tif (!out_patch->len) {\n>> +\t\tif (!quiet)\n>> +\t\t\tfprintf_ln(stderr, _(\"No staged changes\"));\n>> +\t\tret = 1;\n>> +\t}\n>> +\n>> +done:\n>> +\tdiscard_index(&istate);\n>> +\treturn ret;\n>> +}\n>\n> This function doesn't look at its \"struct pathspec\" parameter at all.\n> I'm not sure if that's a bug (i.e., it should be restricting the diff\n> here) or if it was just pulled unnecessarily over from stash_patch().\n\nYep, it's a remnant from copy-paste of stash_patch(). I'm used to\ngetting a warning from compiler for such cases and didn't pay enough\nattention. The warning belongs to -Wextra though and is not turned on\nfor Git compilation.\n\nThanks for catching!\n\n-- Sergey Organov\n"},{"id":"439790","messageId":"87wnlya40k.fsf_-_@osv.gnss.ru","threadId":"56624","inReplyTo":"87pms2mi1p.fsf_-_@osv.gnss.ru","subject":"[PATCH v4] stash: implement '--staged' option for 'push' and 'save'","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-27T15:20:11Z","receivedAt":"2021-10-27T15:20:16Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Stash only the changes that are staged.\n\nThis mode allows to easily stash-out for later reuse some changes\nunrelated to the current work in progress.\n\nUnlike 'stash push --patch', --staged supports use of any tool to\nselect the changes to stash-out, including, but not limited to 'git\nadd --interactive'.\n\nSigned-off-by: Sergey Organov <sorganov@gmail.com>\n---\n\nThis operation that is essentially just a 'git commit', but to the stash\nrather than to the current branch, is somehow missed, complicating the\ntask that is otherwise simple and natural. For example, see discussions\nhere:\n\nhttps://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible\n\nChanges in v4:\n\n  * Removed unused \"ps\" argument in stash_staged()\n\nChanges in v3:\n\n  * Fixed copy-pasted warning to better \"No staged changes\"\n\nChanges in v2:\n\n  * Fixed English grammar in commit message\n\n  * Fixed copy-paste error in help description\n\nChanges in v1:\n\n  * Implement separate stash_staged() instead of re-using and changing\n    stash_patch()\n\n  * Add test\n\n  * Minor documentation cleanup\n\n Documentation/git-stash.txt | 34 ++++++++++++++--\n builtin/stash.c             | 80 ++++++++++++++++++++++++++++++++-----\n t/t3903-stash.sh            | 11 +++++\n 3 files changed, 113 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex be6084ccefbe..6e15f4752576 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n 'git stash' drop [-q|--quiet] [<stash>]\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n-'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n+'git stash' [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\n \t     [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n \t     [--pathspec-from-file=<file> [--pathspec-file-nul]]\n \t     [--] [<pathspec>...]]\n@@ -47,7 +47,7 @@ stash index (e.g. the integer `n` is equivalent to `stash@{n}`).\n COMMANDS\n --------\n \n-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>...]::\n+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>...]::\n \n \tSave your local modifications to a new 'stash entry' and roll them\n \tback to HEAD (in the working tree and in the index).\n@@ -60,7 +60,7 @@ subcommand from making an unwanted stash entry.  The two exceptions to this\n are `stash -p` which acts as alias for `stash push -p` and pathspec elements,\n which are allowed after a double hyphen `--` for disambiguation.\n \n-save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::\n+save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::\n \n \tThis option is deprecated in favour of 'git stash push'.  It\n \tdiffers from \"stash push\" in that it cannot take pathspec.\n@@ -205,6 +205,16 @@ to learn how to operate the `--patch` mode.\n The `--patch` option implies `--keep-index`.  You can use\n `--no-keep-index` to override this.\n \n+-S::\n+--staged::\n+\tThis option is only valid for `push` and `save` commands.\n++\n+Stash only the changes that are currently staged. This is similar to\n+basic `git commit` except the state is committed to the stash instead\n+of current branch.\n++\n+The `--patch` option has priority over this one.\n+\n --pathspec-from-file=<file>::\n \tThis option is only valid for `push` command.\n +\n@@ -341,6 +351,24 @@ $ edit/build/test remaining parts\n $ git commit foo -m 'Remaining parts'\n ----------------------------------------------------------------\n \n+Saving unrelated changes for future use::\n+\n+When you are in the middle of massive changes and you find some\n+unrelated issue that you don't want to forget to fix, you can do the\n+change(s), stage them, and use `git stash push --staged` to stash them\n+out for future use. This is similar to committing the staged changes,\n+only the commit ends-up being in the stash and not on the current branch.\n++\n+----------------------------------------------------------------\n+# ... hack hack hack ...\n+$ git add --patch foo           # add unrelated changes to the index\n+$ git stash push --staged       # save these changes to the stash\n+# ... hack hack hack, finish curent changes ...\n+$ git commit -m 'Massive'       # commit fully tested changes\n+$ git switch fixup-branch       # switch to another branch\n+$ git stash pop                 # to finish work on the saved changes\n+----------------------------------------------------------------\n+\n Recovering stash entries that were cleared/dropped erroneously::\n \n If you mistakenly drop or clear stash entries, they cannot be recovered\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 8f42360ca913..900c1006ad37 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -27,11 +27,11 @@ static const char * const git_stash_usage[] = {\n \tN_(\"git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]\"),\n \tN_(\"git stash branch <branchname> [<stash>]\"),\n \t\"git stash clear\",\n-\tN_(\"git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n+\tN_(\"git stash [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [-m|--message <message>]\\n\"\n \t   \"          [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n \t   \"          [--] [<pathspec>...]]\"),\n-\tN_(\"git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n+\tN_(\"git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\\n\"\n \t   \"          [-u|--include-untracked] [-a|--all] [<message>]\"),\n \tNULL\n };\n@@ -1116,6 +1116,38 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,\n \treturn ret;\n }\n \n+static int stash_staged(struct stash_info *info, struct strbuf *out_patch,\n+\t\t\tint quiet)\n+{\n+\tint ret = 0;\n+\tstruct child_process cp_diff_tree = CHILD_PROCESS_INIT;\n+\tstruct index_state istate = { NULL };\n+\n+\tif (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file,\n+\t\t\t\t0, NULL)) {\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n+\tcp_diff_tree.git_cmd = 1;\n+\tstrvec_pushl(&cp_diff_tree.args, \"diff-tree\", \"-p\", \"-U1\", \"HEAD\",\n+\t\t     oid_to_hex(&info->w_tree), \"--\", NULL);\n+\tif (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n+\tif (!out_patch->len) {\n+\t\tif (!quiet)\n+\t\t\tfprintf_ln(stderr, _(\"No staged changes\"));\n+\t\tret = 1;\n+\t}\n+\n+done:\n+\tdiscard_index(&istate);\n+\treturn ret;\n+}\n+\n static int stash_patch(struct stash_info *info, const struct pathspec *ps,\n \t\t       struct strbuf *out_patch, int quiet)\n {\n@@ -1242,7 +1274,7 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps\n }\n \n static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,\n-\t\t\t   int include_untracked, int patch_mode,\n+\t\t\t   int include_untracked, int patch_mode, int only_staged,\n \t\t\t   struct stash_info *info, struct strbuf *patch,\n \t\t\t   int quiet)\n {\n@@ -1321,6 +1353,16 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\t} else if (ret > 0) {\n \t\t\tgoto done;\n \t\t}\n+\t} else if (only_staged) {\n+\t\tret = stash_staged(info, patch, quiet);\n+\t\tif (ret < 0) {\n+\t\t\tif (!quiet)\n+\t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n+\t\t\t\t\t\t     \"staged state\"));\n+\t\t\tgoto done;\n+\t\t} else if (ret > 0) {\n+\t\t\tgoto done;\n+\t\t}\n \t} else {\n \t\tif (stash_working_tree(info, ps)) {\n \t\t\tif (!quiet)\n@@ -1379,7 +1421,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n \tif (!check_changes_tracked_files(&ps))\n \t\treturn 0;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,\n+\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,\n \t\t\t      NULL, 0);\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n@@ -1389,7 +1431,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)\n }\n \n static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,\n-\t\t\t int keep_index, int patch_mode, int include_untracked)\n+\t\t\t int keep_index, int patch_mode, int include_untracked, int only_staged)\n {\n \tint ret = 0;\n \tstruct stash_info info;\n@@ -1407,6 +1449,17 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \t\tgoto done;\n \t}\n \n+\t/* --patch overrides --staged */\n+\tif (patch_mode)\n+\t\tonly_staged = 0;\n+\n+\tif (only_staged && include_untracked) {\n+\t\tfprintf_ln(stderr, _(\"Can't use --staged and --include-untracked\"\n+\t\t\t\t     \" or --all at the same time\"));\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n \tread_cache_preload(NULL);\n \tif (!include_untracked && ps->nr) {\n \t\tint i;\n@@ -1447,7 +1500,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \n \tif (stash_msg)\n \t\tstrbuf_addstr(&stash_msg_buf, stash_msg);\n-\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,\n+\tif (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,\n \t\t\t    &info, &patch, quiet)) {\n \t\tret = -1;\n \t\tgoto done;\n@@ -1464,7 +1517,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n \t\tprintf_ln(_(\"Saved working directory and index state %s\"),\n \t\t\t  stash_msg_buf.buf);\n \n-\tif (!patch_mode) {\n+\tif (!(patch_mode || only_staged)) {\n \t\tif (include_untracked && !ps->nr) {\n \t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n \n@@ -1581,6 +1634,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n {\n \tint force_assume = 0;\n \tint keep_index = -1;\n+\tint only_staged = 0;\n \tint patch_mode = 0;\n \tint include_untracked = 0;\n \tint quiet = 0;\n@@ -1591,6 +1645,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n+\t\tOPT_BOOL('S', \"staged\", &only_staged,\n+\t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n@@ -1629,6 +1685,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\tif (patch_mode)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with --patch\"));\n \n+\t\tif (only_staged)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --staged\"));\n+\n \t\tif (ps.nr)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n \n@@ -1640,12 +1699,13 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t}\n \n \treturn do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,\n-\t\t\t     include_untracked);\n+\t\t\t     include_untracked, only_staged);\n }\n \n static int save_stash(int argc, const char **argv, const char *prefix)\n {\n \tint keep_index = -1;\n+\tint only_staged = 0;\n \tint patch_mode = 0;\n \tint include_untracked = 0;\n \tint quiet = 0;\n@@ -1656,6 +1716,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_BOOL('k', \"keep-index\", &keep_index,\n \t\t\t N_(\"keep index\")),\n+\t\tOPT_BOOL('S', \"staged\", &only_staged,\n+\t\t\t N_(\"stash staged changes only\")),\n \t\tOPT_BOOL('p', \"patch\", &patch_mode,\n \t\t\t N_(\"stash in patch mode\")),\n \t\tOPT__QUIET(&quiet, N_(\"quiet mode\")),\n@@ -1677,7 +1739,7 @@ static int save_stash(int argc, const char **argv, const char *prefix)\n \n \tmemset(&ps, 0, sizeof(ps));\n \tret = do_push_stash(&ps, stash_msg, quiet, keep_index,\n-\t\t\t    patch_mode, include_untracked);\n+\t\t\t    patch_mode, include_untracked, only_staged);\n \n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 873aa56e359d..18ea885412b8 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -288,6 +288,17 @@ test_expect_success 'stash --no-keep-index' '\n \ttest bar,bar2 = $(cat file),$(cat file2)\n '\n \n+test_expect_success 'stash --staged' '\n+\techo bar3 >file &&\n+\techo bar4 >file2 &&\n+\tgit add file2 &&\n+\tgit stash --staged &&\n+\ttest bar3,bar2 = $(cat file),$(cat file2) &&\n+\tgit reset --hard &&\n+\tgit stash pop &&\n+\ttest bar,bar4 = $(cat file),$(cat file2)\n+'\n+\n test_expect_success 'dont assume push with non-option args' '\n \ttest_must_fail git stash -q drop 2>err &&\n \ttest_i18ngrep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''drop'\\''\" err\n-- \n2.25.1\n"},{"id":"439835","messageId":"xmqqfssmupvq.fsf@gitster.g","threadId":"56624","inReplyTo":"87wnlya40k.fsf_-_@osv.gnss.ru","subject":"Re: [PATCH v4] stash: implement '--staged' option for 'push' and 'save'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-27T21:19:53Z","receivedAt":"2021-10-27T21:21:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sergey Organov <sorganov@gmail.com> writes:\n\n> Stash only the changes that are staged.\n\nAs the topic already is in 'next', let's have this in a form of\nincremental \"the earlier one missed this, so let's fix it\" patch to\nbe applied on top.\n\nThanks.\n"},{"id":"439863","messageId":"87k0hx5z81.fsf_-_@osv.gnss.ru","threadId":"56624","inReplyTo":"xmqqfssmupvq.fsf@gitster.g","subject":"[PATCH] stash: get rid of unused argument in stash_staged()","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2021-10-28T08:29:34Z","receivedAt":"2021-10-28T08:29:41Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Unused 'ps' argument was a left-over from original copy-paste of\nstash_patch(). Removed.\n\nSigned-off-by: Sergey Organov <sorganov@gmail.com>\n---\n builtin/stash.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 8d6f0e582ce0..18c812bbe032 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1132,8 +1132,8 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,\n \treturn ret;\n }\n \n-static int stash_staged(struct stash_info *info, const struct pathspec *ps,\n-\t\t\tstruct strbuf *out_patch, int quiet)\n+static int stash_staged(struct stash_info *info, struct strbuf *out_patch,\n+\t\t\tint quiet)\n {\n \tint ret = 0;\n \tstruct child_process cp_diff_tree = CHILD_PROCESS_INIT;\n@@ -1370,7 +1370,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\t\tgoto done;\n \t\t}\n \t} else if (only_staged) {\n-\t\tret = stash_staged(info, ps, patch, quiet);\n+\t\tret = stash_staged(info, patch, quiet);\n \t\tif (ret < 0) {\n \t\t\tif (!quiet)\n \t\t\t\tfprintf_ln(stderr, _(\"Cannot save the current \"\n-- \n2.25.1\n"},{"id":"439949","messageId":"xmqqwnlwq27j.fsf@gitster.g","threadId":"56624","inReplyTo":"87k0hx5z81.fsf_-_@osv.gnss.ru","subject":"Re: [PATCH] stash: get rid of unused argument in stash_staged()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-28T21:17:04Z","receivedAt":"2021-10-28T21:17:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sergey Organov <sorganov@gmail.com> writes:\n\n> Unused 'ps' argument was a left-over from original copy-paste of\n> stash_patch(). Removed.\n>\n> Signed-off-by: Sergey Organov <sorganov@gmail.com>\n> ---\n\nThanks.  Will queue.\n"}]}