`git stash create` always passes zero for the include_untracked parameter of do_create_stash(), even though that helper already supports untracked and ignored files and stash push/save expose those modes as -u/--include-untracked and -a/--all.
Teach create to accept the same options and pass the existing mode through. Unlike push/save, create continues to only create objects: it does not update refs/stash or modify the index or working tree.
When the selected mode finds no changes, do_create_stash() returns 1. Translate that to success so create keeps its existing no-object, empty output behavior.
Use normal parse-options semantics, so options may appear after message arguments. A message that begins with a dash can be disambiguated with --.
9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the internal include-untracked path while intentionally leaving the user interface for "git stash create" unchanged. Reuse that machinery and the existing INCLUDE_ALL_FILES mode rather than adding a separate stash creation path.
Add coverage for both short and long aliases, the untracked/ignored boundary, option/message parsing, no-change behavior, and preservation of refs/stash, the index, and the working tree.
Signed-off-by: Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> --- Related work:
I proposed adding both --include-untracked and --all to "git stash create" in 2014: <1403856479-37421-1-git-send-email-shigeta@kanamei.co.jp>
I should also apologize for dropping that thread after receiving review. I did not follow up on the comments at the time. Thanks to those who reviewed it then.
Separately, in 2017, Thomas Gummerer added an internal -u path while refactoring stash_create in 9ca6326dff29 (stash: refactor stash_create). That change explicitly kept the user interface of "git stash create" unchanged.
When "stash create" was later converted to the builtin C implementation in d4788af875cc (stash: convert create to builtin), the untracked-file handling was carried into the new implementation and remains there today.
More recently, Shabbir Bhojani proposed exposing --include-untracked: <pull.1892.git.1774768580147.gitgitgadget@gmail.com>
This patch exposes both existing untracked modes, --include-untracked and --all, to "git stash create".
Documentation/git-stash.adoc | 18 ++++++---- builtin/stash.c | 36 ++++++++++++++----- t/t3903-stash.sh | 70 ++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 15 deletions(-)
Show changes to 3 files +109 −15
Documentation/git-stash.adoc, builtin/stash.c, t/t3903-stash.sh
diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc index fc6a9a0..32f0fd5 100644 --- a/Documentation/git-stash.adoc +++ b/Documentation/git-stash.adoc @@ -21,7 +21,7 @@ git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | - git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet] [-u | --include-untracked] [-a | --all] [<message>] git stash clear -git stash create [<message>] +git stash create [-u | --include-untracked] [-a | --all] [<message>] git stash store [(-m | --message) <message>] [-q | --quiet] <commit> git stash export (--print | --to-ref <ref>) [<stash>...] git stash import <commit> @@ -138,10 +138,12 @@ with no conflicts. `drop [-q | --quiet] [<stash>]`:: Remove a single stash entry from the list of stash entries. -`create`:: +`create [-u | --include-untracked] [-a | --all]`:: Create a stash entry (which is a regular commit object) and return its object name, without storing it anywhere in the ref - namespace. + namespace. The `--include-untracked` option includes untracked + files, while `--all` also includes ignored files, without modifying + the working tree. This is intended to be useful for scripts. It is probably not the command you want to use; see "push" above. @@ -167,10 +169,11 @@ OPTIONS ------- `-a`:: `--all`:: - This option is only valid for `push` and `save` commands. + When used with the `push` and `save` commands, all ignored and + untracked files are also stashed and then cleaned up with `git clean`. + -All ignored and untracked files are also stashed and then cleaned -up with `git clean`. +When used with the `create` command, ignored and untracked files are included +in the stash entry without modifying the working tree. `-u`:: `--include-untracked`:: @@ -179,6 +182,9 @@ up with `git clean`. all untracked files are also stashed and then cleaned up with `git clean`. + +When used with the `create` command, untracked files are included in the +stash entry without modifying the working tree. ++ When used with the `show` command, show the untracked files in the stash entry as part of the diff. diff --git a/builtin/stash.c b/builtin/stash.c index 7a98434..57a4750 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -59,7 +59,7 @@ N_("git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n" \ " [-u | --include-untracked] [-a | --all] [<message>]") #define BUILTIN_STASH_CREATE_USAGE \ - N_("git stash create [<message>]") + N_("git stash create [-u | --include-untracked] [-a | --all] [<message>]") #define BUILTIN_STASH_EXPORT_USAGE \ N_("git stash export (--print | --to-ref <ref>) [<stash>...]") #define BUILTIN_STASH_IMPORT_USAGE \ @@ -119,6 +119,11 @@ static const char * const git_stash_clear_usage[] = { NULL }; +static const char * const git_stash_create_usage[] = { + BUILTIN_STASH_CREATE_USAGE, + NULL +}; + static const char * const git_stash_store_usage[] = { BUILTIN_STASH_STORE_USAGE, NULL @@ -1643,26 +1648,39 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b return ret; } -static int create_stash(int argc, const char **argv, const char *prefix UNUSED, +static int create_stash(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { - int ret; + int ret = 0; + int include_untracked = 0; + struct option options[] = { + OPT_BOOL('u', "include-untracked", &include_untracked, + N_("include untracked files in stash")), + OPT_SET_INT('a', "all", &include_untracked, + N_("include ignored files in stash"), + INCLUDE_ALL_FILES), + OPT_END() + }; struct strbuf stash_msg_buf = STRBUF_INIT; struct stash_info info = STASH_INFO_INIT; struct pathspec ps; - /* Starting with argv[1], since argv[0] is "create" */ - strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' '); + argc = parse_options(argc, argv, prefix, options, + git_stash_create_usage, 0); + strbuf_join_argv(&stash_msg_buf, argc, argv, ' '); memset(&ps, 0, sizeof(ps)); - if (!check_changes_tracked_files(&ps)) - return 0; + if (!include_untracked && !check_changes_tracked_files(&ps)) + goto done; - ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info, - NULL, 0); + ret = do_create_stash(&ps, &stash_msg_buf, include_untracked, 0, NULL, + 0, &info, NULL, 0); if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); + else if (ret == 1) + ret = 0; +done: free_stash_info(&info); strbuf_release(&stash_msg_buf); return ret; diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 7211586..fe34879 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -640,6 +640,76 @@ test_expect_success 'stash create - no changes' ' test_must_be_empty actual ' +# --all observes every untracked and ignored path in the worktree. Use one +# isolated repository for these checks so unrelated test state is not captured. +test_expect_success 'stash create with untracked options' ' + test_when_finished "rm -rf stash-create-options" && + test_create_repo stash-create-options && + ( + cd stash-create-options && + test_commit base tracked base && + echo create-ignored >.gitignore && + git add .gitignore && + git commit -m ignore && + + git stash create -u >.git/actual && + test_must_be_empty .git/actual && + git stash create -a >.git/actual && + test_must_be_empty .git/actual && + + echo untracked >create-untracked && + git stash create "without untracked" >.git/actual && + test_must_be_empty .git/actual && + short=$(git stash create "create untracked" -u) && + long=$(git stash create --include-untracked "create untracked") && + test_cmp_rev "$short^3^{tree}" "$long^3^{tree}" && + echo untracked >.git/expect && + git show "$short^3:create-untracked" >.git/actual && + test_cmp .git/expect .git/actual && + branch=$(git symbolic-ref --short HEAD) && + echo "On $branch: create untracked" >.git/expect && + git show --pretty=%s -s "$short" >.git/actual && + test_cmp .git/expect .git/actual && + test_path_is_file create-untracked && + + echo ignored >create-ignored && + with_untracked=$(git stash create -u "create options") && + test_must_fail git cat-file -e "$with_untracked^3:create-ignored" && + short=$(git stash create "create options" -a) && + long=$(git stash create --all "create options") && + test_cmp_rev "$short^3^{tree}" "$long^3^{tree}" && + echo ignored >.git/expect && + git show "$short^3:create-ignored" >.git/actual && + test_cmp .git/expect .git/actual && + test_path_is_file create-untracked && + test_path_is_file create-ignored && + + echo staged >staged && + git add staged && + echo modified >>tracked && + git diff >.git/before-worktree && + git diff --cached >.git/before-index && + git status --porcelain=v1 --ignored >.git/before-status && + test_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 && + STASH_ID=$(git stash create -a -- -create-message) && + git diff >.git/after-worktree && + git diff --cached >.git/after-index && + git status --porcelain=v1 --ignored >.git/after-status && + test_cmp .git/before-worktree .git/after-worktree && + test_cmp .git/before-index .git/after-index && + test_cmp .git/before-status .git/after-status && + test_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 && + echo "On $branch: -create-message" >.git/expect && + git show --pretty=%s -s "$STASH_ID" >.git/actual && + test_cmp .git/expect .git/actual + ) +' + +test_expect_success 'stash create rejects unknown options' ' + test_expect_code 129 git stash create --unknown-option 2>err && + test_grep "unknown option" err +' + test_expect_success 'stash branch - no stashes on stack, stash-like argument' ' git stash clear && test_when_finished "git reset --hard HEAD" &&
-- 2.47.3