{"thread":{"id":"66418","subject":"[PATCH] stash: expose untracked modes in create","startedAt":"2026-09-29T07:42:31Z","lastAt":"2026-10-06T09:57:58Z","messageCount":16,"participants":["Kazumasa Shigeta","Phillip Wood","Patrick Steinhardt","重田一聖","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"553555","messageId":"20260929074222.11942-1-kazumasa.shigeta@kanamei.com","threadId":"66418","inReplyTo":null,"subject":"[PATCH] stash: expose untracked modes in create","fromName":"Kazumasa Shigeta","fromEmail":"kazumasa.shigeta@kanamei.com","sentAt":"2026-09-29T07:42:22Z","receivedAt":"2026-09-29T07:42:31Z","isPatch":true,"body":"`git stash create` always passes zero for the include_untracked parameter\nof do_create_stash(), even though that helper already supports untracked\nand ignored files and stash push/save expose those modes as\n-u/--include-untracked and -a/--all.\n\nTeach create to accept the same options and pass the existing mode\nthrough. Unlike push/save, create continues to only create objects: it\ndoes not update refs/stash or modify the index or working tree.\n\nWhen the selected mode finds no changes, do_create_stash() returns 1.\nTranslate that to success so create keeps its existing no-object, empty\noutput behavior.\n\nUse normal parse-options semantics, so options may appear after message\narguments. A message that begins with a dash can be disambiguated with\n--.\n\n9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the\ninternal include-untracked path while intentionally leaving the user\ninterface for \"git stash create\" unchanged. Reuse that machinery and\nthe existing INCLUDE_ALL_FILES mode rather than adding a separate stash\ncreation path.\n\nAdd coverage for both short and long aliases, the untracked/ignored\nboundary, option/message parsing, no-change behavior, and preservation\nof refs/stash, the index, and the working tree.\n\nSigned-off-by: Kazumasa Shigeta <kazumasa.shigeta@kanamei.com>\n---\nRelated work:\n\nI proposed adding both --include-untracked and --all to\n\"git stash create\" in 2014:\n  <1403856479-37421-1-git-send-email-shigeta@kanamei.co.jp>\n\nI should also apologize for dropping that thread after receiving review.\nI did not follow up on the comments at the time.  Thanks to those who\nreviewed it then.\n\nSeparately, in 2017, Thomas Gummerer added an internal -u path while\nrefactoring stash_create in 9ca6326dff29 (stash: refactor stash_create).\nThat change explicitly kept the user interface of \"git stash create\"\nunchanged.\n\nWhen \"stash create\" was later converted to the builtin C implementation\nin d4788af875cc (stash: convert create to builtin), the untracked-file\nhandling was carried into the new implementation and remains there today.\n\nMore recently, Shabbir Bhojani proposed exposing --include-untracked:\n  <pull.1892.git.1774768580147.gitgitgadget@gmail.com>\n\nThis patch exposes both existing untracked modes, --include-untracked and\n--all, to \"git stash create\".\n\n Documentation/git-stash.adoc | 18 ++++++----\n builtin/stash.c              | 36 ++++++++++++++-----\n t/t3903-stash.sh             | 70 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 109 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex fc6a9a0..32f0fd5 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -21,7 +21,7 @@ git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | -\n git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n            [-u | --include-untracked] [-a | --all] [<message>]\n git stash clear\n-git stash create [<message>]\n+git stash create [-u | --include-untracked] [-a | --all] [<message>]\n git stash store [(-m | --message) <message>] [-q | --quiet] <commit>\n git stash export (--print | --to-ref <ref>) [<stash>...]\n git stash import <commit>\n@@ -138,10 +138,12 @@ with no conflicts.\n `drop [-q | --quiet] [<stash>]`::\n \tRemove a single stash entry from the list of stash entries.\n \n-`create`::\n+`create [-u | --include-untracked] [-a | --all]`::\n \tCreate a stash entry (which is a regular commit object) and\n \treturn its object name, without storing it anywhere in the ref\n-\tnamespace.\n+\tnamespace.  The `--include-untracked` option includes untracked\n+\tfiles, while `--all` also includes ignored files, without modifying\n+\tthe working tree.\n \tThis is intended to be useful for scripts.  It is probably not\n \tthe command you want to use; see \"push\" above.\n \n@@ -167,10 +169,11 @@ OPTIONS\n -------\n `-a`::\n `--all`::\n-\tThis option is only valid for `push` and `save` commands.\n+\tWhen used with the `push` and `save` commands, all ignored and\n+\tuntracked files are also stashed and then cleaned up with `git clean`.\n +\n-All ignored and untracked files are also stashed and then cleaned\n-up with `git clean`.\n+When used with the `create` command, ignored and untracked files are included\n+in the stash entry without modifying the working tree.\n \n `-u`::\n `--include-untracked`::\n@@ -179,6 +182,9 @@ up with `git clean`.\n \tall untracked files are also stashed and then cleaned up with\n \t`git clean`.\n +\n+When used with the `create` command, untracked files are included in the\n+stash entry without modifying the working tree.\n++\n When used with the `show` command, show the untracked files in the stash\n entry as part of the diff.\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 7a98434..57a4750 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -59,7 +59,7 @@\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 #define BUILTIN_STASH_CREATE_USAGE \\\n-\tN_(\"git stash create [<message>]\")\n+\tN_(\"git stash create [-u | --include-untracked] [-a | --all] [<message>]\")\n #define BUILTIN_STASH_EXPORT_USAGE \\\n \tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\")\n #define BUILTIN_STASH_IMPORT_USAGE \\\n@@ -119,6 +119,11 @@ static const char * const git_stash_clear_usage[] = {\n \tNULL\n };\n \n+static const char * const git_stash_create_usage[] = {\n+\tBUILTIN_STASH_CREATE_USAGE,\n+\tNULL\n+};\n+\n static const char * const git_stash_store_usage[] = {\n \tBUILTIN_STASH_STORE_USAGE,\n \tNULL\n@@ -1643,26 +1648,39 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \treturn ret;\n }\n \n-static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n+static int create_stash(int argc, const char **argv, const char *prefix,\n \t\t\tstruct repository *repo UNUSED)\n {\n-\tint ret;\n+\tint ret = 0;\n+\tint include_untracked = 0;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n+\t\t\t N_(\"include untracked files in stash\")),\n+\t\tOPT_SET_INT('a', \"all\", &include_untracked,\n+\t\t\t    N_(\"include ignored files in stash\"),\n+\t\t\t    INCLUDE_ALL_FILES),\n+\t\tOPT_END()\n+\t};\n \tstruct strbuf stash_msg_buf = STRBUF_INIT;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct pathspec ps;\n \n-\t/* Starting with argv[1], since argv[0] is \"create\" */\n-\tstrbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_stash_create_usage, 0);\n+\tstrbuf_join_argv(&stash_msg_buf, argc, argv, ' ');\n \n \tmemset(&ps, 0, sizeof(ps));\n-\tif (!check_changes_tracked_files(&ps))\n-\t\treturn 0;\n+\tif (!include_untracked && !check_changes_tracked_files(&ps))\n+\t\tgoto done;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,\n-\t\t\t      NULL, 0);\n+\tret = do_create_stash(&ps, &stash_msg_buf, include_untracked, 0, NULL,\n+\t\t\t      0, &info, NULL, 0);\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n+\telse if (ret == 1)\n+\t\tret = 0;\n \n+done:\n \tfree_stash_info(&info);\n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 7211586..fe34879 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -640,6 +640,76 @@ test_expect_success 'stash create - no changes' '\n \ttest_must_be_empty actual\n '\n \n+# --all observes every untracked and ignored path in the worktree.  Use one\n+# isolated repository for these checks so unrelated test state is not captured.\n+test_expect_success 'stash create with untracked options' '\n+\ttest_when_finished \"rm -rf stash-create-options\" &&\n+\ttest_create_repo stash-create-options &&\n+\t(\n+\t\tcd stash-create-options &&\n+\t\ttest_commit base tracked base &&\n+\t\techo create-ignored >.gitignore &&\n+\t\tgit add .gitignore &&\n+\t\tgit commit -m ignore &&\n+\n+\t\tgit stash create -u >.git/actual &&\n+\t\ttest_must_be_empty .git/actual &&\n+\t\tgit stash create -a >.git/actual &&\n+\t\ttest_must_be_empty .git/actual &&\n+\n+\t\techo untracked >create-untracked &&\n+\t\tgit stash create \"without untracked\" >.git/actual &&\n+\t\ttest_must_be_empty .git/actual &&\n+\t\tshort=$(git stash create \"create untracked\" -u) &&\n+\t\tlong=$(git stash create --include-untracked \"create untracked\") &&\n+\t\ttest_cmp_rev \"$short^3^{tree}\" \"$long^3^{tree}\" &&\n+\t\techo untracked >.git/expect &&\n+\t\tgit show \"$short^3:create-untracked\" >.git/actual &&\n+\t\ttest_cmp .git/expect .git/actual &&\n+\t\tbranch=$(git symbolic-ref --short HEAD) &&\n+\t\techo \"On $branch: create untracked\" >.git/expect &&\n+\t\tgit show --pretty=%s -s \"$short\" >.git/actual &&\n+\t\ttest_cmp .git/expect .git/actual &&\n+\t\ttest_path_is_file create-untracked &&\n+\n+\t\techo ignored >create-ignored &&\n+\t\twith_untracked=$(git stash create -u \"create options\") &&\n+\t\ttest_must_fail git cat-file -e \"$with_untracked^3:create-ignored\" &&\n+\t\tshort=$(git stash create \"create options\" -a) &&\n+\t\tlong=$(git stash create --all \"create options\") &&\n+\t\ttest_cmp_rev \"$short^3^{tree}\" \"$long^3^{tree}\" &&\n+\t\techo ignored >.git/expect &&\n+\t\tgit show \"$short^3:create-ignored\" >.git/actual &&\n+\t\ttest_cmp .git/expect .git/actual &&\n+\t\ttest_path_is_file create-untracked &&\n+\t\ttest_path_is_file create-ignored &&\n+\n+\t\techo staged >staged &&\n+\t\tgit add staged &&\n+\t\techo modified >>tracked &&\n+\t\tgit diff >.git/before-worktree &&\n+\t\tgit diff --cached >.git/before-index &&\n+\t\tgit status --porcelain=v1 --ignored >.git/before-status &&\n+\t\ttest_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&\n+\t\tSTASH_ID=$(git stash create -a -- -create-message) &&\n+\t\tgit diff >.git/after-worktree &&\n+\t\tgit diff --cached >.git/after-index &&\n+\t\tgit status --porcelain=v1 --ignored >.git/after-status &&\n+\t\ttest_cmp .git/before-worktree .git/after-worktree &&\n+\t\ttest_cmp .git/before-index .git/after-index &&\n+\t\ttest_cmp .git/before-status .git/after-status &&\n+\t\ttest_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&\n+\t\techo \"On $branch: -create-message\" >.git/expect &&\n+\t\tgit show --pretty=%s -s \"$STASH_ID\" >.git/actual &&\n+\t\ttest_cmp .git/expect .git/actual\n+\t)\n+'\n+\n+test_expect_success 'stash create rejects unknown options' '\n+\ttest_expect_code 129 git stash create --unknown-option 2>err &&\n+\ttest_grep \"unknown option\" err\n+'\n+\n test_expect_success 'stash branch - no stashes on stack, stash-like argument' '\n \tgit stash clear &&\n \ttest_when_finished \"git reset --hard HEAD\" &&\n-- \n2.47.3\n\n"},{"id":"553613","messageId":"8453ebd1-77c1-4941-afbf-572f9e7b12c1@gmail.com","threadId":"66418","inReplyTo":"20260929074222.11942-1-kazumasa.shigeta@kanamei.com","subject":"Re: [PATCH] stash: expose untracked modes in create","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-09-29T16:08:08Z","receivedAt":"2026-09-29T16:08:11Z","isPatch":true,"body":"Hi Kazumasa\n\nOn 29/09/2026 08:42, Kazumasa Shigeta wrote:\n> `git stash create` always passes zero for the include_untracked parameter\n> of do_create_stash(), even though that helper already supports untracked\n> and ignored files and stash push/save expose those modes as\n> -u/--include-untracked and -a/--all.\n> \n> Teach create to accept the same options and pass the existing mode\n> through. Unlike push/save, create continues to only create objects: it\n> does not update refs/stash or modify the index or working tree.\n> \n> When the selected mode finds no changes, do_create_stash() returns 1.\n> Translate that to success so create keeps its existing no-object, empty\n> output behavior.\n\nThis doesn't seem to match the code changes. The code that prints the \nobject id when the stash is successfully created is unchanged, as far as \nI can see what this patch does is change the exit status for \"git stash \ncreate\" when there are no changes to stash. Instead of exiting 1, it \nexits 0 even though it does not create a stash. That does not seem like \na good idea.\n\n> Use normal parse-options semantics, so options may appear after message\n> arguments. A message that begins with a dash can be disambiguated with\n\nAs \"git stash create\" concatenates excess arguments to use as the stash \nmessage we should not be permuting options. \"git stash create handle new \n-u flag\" should continue to create a stash with the message \"handle new \n-u flag\" - it should not start stashing untracked files. You should pass \nPARSE_OPT_STOP_AT_NON_OPTION to parse_options() to prevent that.\n\n> I proposed adding both --include-untracked and --all to\n> \"git stash create\" in 2014:\n>    <1403856479-37421-1-git-send-email-shigeta@kanamei.co.jp>\n> \n> I should also apologize for dropping that thread after receiving review.\n> I did not follow up on the comments at the time.  Thanks to those who\n> reviewed it then.\n\nBetter late than never! I think the idea is fine, but the implementation \ncould do with a couple of tweaks so it is as backward compatible as \npossible.\n\nThanks\n\nPhillip\n\n> Separately, in 2017, Thomas Gummerer added an internal -u path while\n> refactoring stash_create in 9ca6326dff29 (stash: refactor stash_create).\n> That change explicitly kept the user interface of \"git stash create\"\n> unchanged.\n> \n> When \"stash create\" was later converted to the builtin C implementation\n> in d4788af875cc (stash: convert create to builtin), the untracked-file\n> handling was carried into the new implementation and remains there today.\n> \n> More recently, Shabbir Bhojani proposed exposing --include-untracked:\n>    <pull.1892.git.1774768580147.gitgitgadget@gmail.com>\n> \n> This patch exposes both existing untracked modes, --include-untracked and\n> --all, to \"git stash create\".\n> \n>   Documentation/git-stash.adoc | 18 ++++++----\n>   builtin/stash.c              | 36 ++++++++++++++-----\n>   t/t3903-stash.sh             | 70 ++++++++++++++++++++++++++++++++++++\n>   3 files changed, 109 insertions(+), 15 deletions(-)\n> \n> diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\n> index fc6a9a0..32f0fd5 100644\n> --- a/Documentation/git-stash.adoc\n> +++ b/Documentation/git-stash.adoc\n> @@ -21,7 +21,7 @@ git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | -\n>   git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n>              [-u | --include-untracked] [-a | --all] [<message>]\n>   git stash clear\n> -git stash create [<message>]\n> +git stash create [-u | --include-untracked] [-a | --all] [<message>]\n>   git stash store [(-m | --message) <message>] [-q | --quiet] <commit>\n>   git stash export (--print | --to-ref <ref>) [<stash>...]\n>   git stash import <commit>\n> @@ -138,10 +138,12 @@ with no conflicts.\n>   `drop [-q | --quiet] [<stash>]`::\n>   \tRemove a single stash entry from the list of stash entries.\n>   \n> -`create`::\n> +`create [-u | --include-untracked] [-a | --all]`::\n>   \tCreate a stash entry (which is a regular commit object) and\n>   \treturn its object name, without storing it anywhere in the ref\n> -\tnamespace.\n> +\tnamespace.  The `--include-untracked` option includes untracked\n> +\tfiles, while `--all` also includes ignored files, without modifying\n> +\tthe working tree.\n>   \tThis is intended to be useful for scripts.  It is probably not\n>   \tthe command you want to use; see \"push\" above.\n>   \n> @@ -167,10 +169,11 @@ OPTIONS\n>   -------\n>   `-a`::\n>   `--all`::\n> -\tThis option is only valid for `push` and `save` commands.\n> +\tWhen used with the `push` and `save` commands, all ignored and\n> +\tuntracked files are also stashed and then cleaned up with `git clean`.\n>   +\n> -All ignored and untracked files are also stashed and then cleaned\n> -up with `git clean`.\n> +When used with the `create` command, ignored and untracked files are included\n> +in the stash entry without modifying the working tree.\n>   \n>   `-u`::\n>   `--include-untracked`::\n> @@ -179,6 +182,9 @@ up with `git clean`.\n>   \tall untracked files are also stashed and then cleaned up with\n>   \t`git clean`.\n>   +\n> +When used with the `create` command, untracked files are included in the\n> +stash entry without modifying the working tree.\n> ++\n>   When used with the `show` command, show the untracked files in the stash\n>   entry as part of the diff.\n>   \n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index 7a98434..57a4750 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -59,7 +59,7 @@\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>   #define BUILTIN_STASH_CREATE_USAGE \\\n> -\tN_(\"git stash create [<message>]\")\n> +\tN_(\"git stash create [-u | --include-untracked] [-a | --all] [<message>]\")\n>   #define BUILTIN_STASH_EXPORT_USAGE \\\n>   \tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\")\n>   #define BUILTIN_STASH_IMPORT_USAGE \\\n> @@ -119,6 +119,11 @@ static const char * const git_stash_clear_usage[] = {\n>   \tNULL\n>   };\n>   \n> +static const char * const git_stash_create_usage[] = {\n> +\tBUILTIN_STASH_CREATE_USAGE,\n> +\tNULL\n> +};\n> +\n>   static const char * const git_stash_store_usage[] = {\n>   \tBUILTIN_STASH_STORE_USAGE,\n>   \tNULL\n> @@ -1643,26 +1648,39 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n>   \treturn ret;\n>   }\n>   \n> -static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n> +static int create_stash(int argc, const char **argv, const char *prefix,\n>   \t\t\tstruct repository *repo UNUSED)\n>   {\n> -\tint ret;\n> +\tint ret = 0;\n> +\tint include_untracked = 0;\n> +\tstruct option options[] = {\n> +\t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n> +\t\t\t N_(\"include untracked files in stash\")),\n> +\t\tOPT_SET_INT('a', \"all\", &include_untracked,\n> +\t\t\t    N_(\"include ignored files in stash\"),\n> +\t\t\t    INCLUDE_ALL_FILES),\n> +\t\tOPT_END()\n> +\t};\n>   \tstruct strbuf stash_msg_buf = STRBUF_INIT;\n>   \tstruct stash_info info = STASH_INFO_INIT;\n>   \tstruct pathspec ps;\n>   \n> -\t/* Starting with argv[1], since argv[0] is \"create\" */\n> -\tstrbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');\n> +\targc = parse_options(argc, argv, prefix, options,\n> +\t\t\t     git_stash_create_usage, 0);\n> +\tstrbuf_join_argv(&stash_msg_buf, argc, argv, ' ');\n>   \n>   \tmemset(&ps, 0, sizeof(ps));\n> -\tif (!check_changes_tracked_files(&ps))\n> -\t\treturn 0;\n> +\tif (!include_untracked && !check_changes_tracked_files(&ps))\n> +\t\tgoto done;\n>   \n> -\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,\n> -\t\t\t      NULL, 0);\n> +\tret = do_create_stash(&ps, &stash_msg_buf, include_untracked, 0, NULL,\n> +\t\t\t      0, &info, NULL, 0);\n>   \tif (!ret)\n>   \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n> +\telse if (ret == 1)\n> +\t\tret = 0;\n>   \n> +done:\n>   \tfree_stash_info(&info);\n>   \tstrbuf_release(&stash_msg_buf);\n>   \treturn ret;\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index 7211586..fe34879 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -640,6 +640,76 @@ test_expect_success 'stash create - no changes' '\n>   \ttest_must_be_empty actual\n>   '\n>   \n> +# --all observes every untracked and ignored path in the worktree.  Use one\n> +# isolated repository for these checks so unrelated test state is not captured.\n> +test_expect_success 'stash create with untracked options' '\n> +\ttest_when_finished \"rm -rf stash-create-options\" &&\n> +\ttest_create_repo stash-create-options &&\n> +\t(\n> +\t\tcd stash-create-options &&\n> +\t\ttest_commit base tracked base &&\n> +\t\techo create-ignored >.gitignore &&\n> +\t\tgit add .gitignore &&\n> +\t\tgit commit -m ignore &&\n> +\n> +\t\tgit stash create -u >.git/actual &&\n> +\t\ttest_must_be_empty .git/actual &&\n> +\t\tgit stash create -a >.git/actual &&\n> +\t\ttest_must_be_empty .git/actual &&\n> +\n> +\t\techo untracked >create-untracked &&\n> +\t\tgit stash create \"without untracked\" >.git/actual &&\n> +\t\ttest_must_be_empty .git/actual &&\n> +\t\tshort=$(git stash create \"create untracked\" -u) &&\n> +\t\tlong=$(git stash create --include-untracked \"create untracked\") &&\n> +\t\ttest_cmp_rev \"$short^3^{tree}\" \"$long^3^{tree}\" &&\n> +\t\techo untracked >.git/expect &&\n> +\t\tgit show \"$short^3:create-untracked\" >.git/actual &&\n> +\t\ttest_cmp .git/expect .git/actual &&\n> +\t\tbranch=$(git symbolic-ref --short HEAD) &&\n> +\t\techo \"On $branch: create untracked\" >.git/expect &&\n> +\t\tgit show --pretty=%s -s \"$short\" >.git/actual &&\n> +\t\ttest_cmp .git/expect .git/actual &&\n> +\t\ttest_path_is_file create-untracked &&\n> +\n> +\t\techo ignored >create-ignored &&\n> +\t\twith_untracked=$(git stash create -u \"create options\") &&\n> +\t\ttest_must_fail git cat-file -e \"$with_untracked^3:create-ignored\" &&\n> +\t\tshort=$(git stash create \"create options\" -a) &&\n> +\t\tlong=$(git stash create --all \"create options\") &&\n> +\t\ttest_cmp_rev \"$short^3^{tree}\" \"$long^3^{tree}\" &&\n> +\t\techo ignored >.git/expect &&\n> +\t\tgit show \"$short^3:create-ignored\" >.git/actual &&\n> +\t\ttest_cmp .git/expect .git/actual &&\n> +\t\ttest_path_is_file create-untracked &&\n> +\t\ttest_path_is_file create-ignored &&\n> +\n> +\t\techo staged >staged &&\n> +\t\tgit add staged &&\n> +\t\techo modified >>tracked &&\n> +\t\tgit diff >.git/before-worktree &&\n> +\t\tgit diff --cached >.git/before-index &&\n> +\t\tgit status --porcelain=v1 --ignored >.git/before-status &&\n> +\t\ttest_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&\n> +\t\tSTASH_ID=$(git stash create -a -- -create-message) &&\n> +\t\tgit diff >.git/after-worktree &&\n> +\t\tgit diff --cached >.git/after-index &&\n> +\t\tgit status --porcelain=v1 --ignored >.git/after-status &&\n> +\t\ttest_cmp .git/before-worktree .git/after-worktree &&\n> +\t\ttest_cmp .git/before-index .git/after-index &&\n> +\t\ttest_cmp .git/before-status .git/after-status &&\n> +\t\ttest_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&\n> +\t\techo \"On $branch: -create-message\" >.git/expect &&\n> +\t\tgit show --pretty=%s -s \"$STASH_ID\" >.git/actual &&\n> +\t\ttest_cmp .git/expect .git/actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'stash create rejects unknown options' '\n> +\ttest_expect_code 129 git stash create --unknown-option 2>err &&\n> +\ttest_grep \"unknown option\" err\n> +'\n> +\n>   test_expect_success 'stash branch - no stashes on stack, stash-like argument' '\n>   \tgit stash clear &&\n>   \ttest_when_finished \"git reset --hard HEAD\" &&\n\n"},{"id":"553803","messageId":"20261001042155.33303-1-kazumasa.shigeta@kanamei.com","threadId":"66418","inReplyTo":"20260929074222.11942-1-kazumasa.shigeta@kanamei.com","subject":"[PATCH v2] stash: expose untracked modes in create","fromName":"Kazumasa Shigeta","fromEmail":"kazumasa.shigeta@kanamei.com","sentAt":"2026-10-01T04:21:55Z","receivedAt":"2026-10-01T04:22:02Z","isPatch":true,"body":"`git stash create` always passes zero for the include_untracked parameter\nof do_create_stash(), even though that helper already supports untracked\nand ignored files and stash push/save expose those modes as\n-u/--include-untracked and -a/--all.\n\nTeach create to accept the same options and pass the existing mode\nthrough. Unlike push/save, create continues to only create objects: it\ndoes not update refs/stash, reset the index, or clean the working tree.\n\nUse parse_options() for the new options and stop parsing at the first\nnon-option message word. This keeps option-like tokens after the message\nas message text, while leading option-like arguments now follow Git's\nnormal option parsing. In particular, unknown or malformed leading\noptions are rejected instead of silently becoming a message, short\noptions may be combined, and `--` can be used when a message itself\nbegins with a dash.\n\nKeep create's existing no-change behavior: detect the usual no-change\ncase before do_create_stash() refreshes and writes the index, and return\nsuccess without printing an object name. If do_create_stash() still\nreports its internal \"nothing to create\" result, map that to create's\npublic success status.\n\nThis follows the stash subcommand exit-status convention established by\n786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):\nsubcommands return 0 on success, negative values on failure, and status 1\nwhen applying a stash results in conflicts. cmd_stash() maps negative\nsubcommand failures to 128.\n\n9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the\ninternal include-untracked path while intentionally leaving the user\ninterface for \"git stash create\" unchanged. Reuse that machinery and\nthe existing INCLUDE_ALL_FILES mode rather than adding a separate stash\ncreation path.\n\nAdd coverage for short and long aliases, combined short options, the\nuntracked/ignored boundary including an ignored-only worktree, option\nparsing and dash-leading messages, no-change behavior, and preservation\nof refs/stash, the index state, and the working tree.\n\nSigned-off-by: Kazumasa Shigeta <kazumasa.shigeta@kanamei.com>\n---\n Documentation/git-stash.adoc | 19 ++++++---\n builtin/stash.c              | 48 +++++++++++++++++----\n t/t3903-stash.sh             | 83 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 135 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex fc6a9a0..d343a75 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -21,7 +21,7 @@ git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | -\n git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n            [-u | --include-untracked] [-a | --all] [<message>]\n git stash clear\n-git stash create [<message>]\n+git stash create [-u | --include-untracked] [-a | --all] [--] [<message>]\n git stash store [(-m | --message) <message>] [-q | --quiet] <commit>\n git stash export (--print | --to-ref <ref>) [<stash>...]\n git stash import <commit>\n@@ -138,10 +138,13 @@ with no conflicts.\n `drop [-q | --quiet] [<stash>]`::\n \tRemove a single stash entry from the list of stash entries.\n \n-`create`::\n+`create [-u | --include-untracked] [-a | --all] [--]`::\n \tCreate a stash entry (which is a regular commit object) and\n \treturn its object name, without storing it anywhere in the ref\n-\tnamespace.\n+\tnamespace.  The `--include-untracked` option includes untracked\n+\tfiles, while `--all` also includes ignored files, without modifying\n+\tthe working tree.  If `<message>` begins with a dash, use `--` to\n+\tseparate it from the options.\n \tThis is intended to be useful for scripts.  It is probably not\n \tthe command you want to use; see \"push\" above.\n \n@@ -167,10 +170,11 @@ OPTIONS\n -------\n `-a`::\n `--all`::\n-\tThis option is only valid for `push` and `save` commands.\n+\tWhen used with the `push` and `save` commands, all ignored and\n+\tuntracked files are also stashed and then cleaned up with `git clean`.\n +\n-All ignored and untracked files are also stashed and then cleaned\n-up with `git clean`.\n+When used with the `create` command, ignored and untracked files are included\n+in the stash entry without modifying the working tree.\n \n `-u`::\n `--include-untracked`::\n@@ -179,6 +183,9 @@ up with `git clean`.\n \tall untracked files are also stashed and then cleaned up with\n \t`git clean`.\n +\n+When used with the `create` command, untracked files are included in the\n+stash entry without modifying the working tree.\n++\n When used with the `show` command, show the untracked files in the stash\n entry as part of the diff.\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 7a98434..ec2b5e7 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -59,7 +59,7 @@\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 #define BUILTIN_STASH_CREATE_USAGE \\\n-\tN_(\"git stash create [<message>]\")\n+\tN_(\"git stash create [-u | --include-untracked] [-a | --all] [--] [<message>]\")\n #define BUILTIN_STASH_EXPORT_USAGE \\\n \tN_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\")\n #define BUILTIN_STASH_IMPORT_USAGE \\\n@@ -119,6 +119,11 @@ static const char * const git_stash_clear_usage[] = {\n \tNULL\n };\n \n+static const char * const git_stash_create_usage[] = {\n+\tBUILTIN_STASH_CREATE_USAGE,\n+\tNULL\n+};\n+\n static const char * const git_stash_store_usage[] = {\n \tBUILTIN_STASH_STORE_USAGE,\n \tNULL\n@@ -1643,26 +1648,51 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \treturn ret;\n }\n \n-static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n+static int create_stash(int argc, const char **argv, const char *prefix,\n \t\t\tstruct repository *repo UNUSED)\n {\n-\tint ret;\n+\tint ret = 0;\n+\tint include_untracked = 0;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL('u', \"include-untracked\", &include_untracked,\n+\t\t\t N_(\"include untracked files in stash\")),\n+\t\tOPT_SET_INT('a', \"all\", &include_untracked,\n+\t\t\t    N_(\"include ignored files in stash\"),\n+\t\t\t    INCLUDE_ALL_FILES),\n+\t\tOPT_END()\n+\t};\n \tstruct strbuf stash_msg_buf = STRBUF_INIT;\n+\tstruct strbuf untracked_files = STRBUF_INIT;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct pathspec ps;\n \n-\t/* Starting with argv[1], since argv[0] is \"create\" */\n-\tstrbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_stash_create_usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\tstrbuf_join_argv(&stash_msg_buf, argc, argv, ' ');\n \n \tmemset(&ps, 0, sizeof(ps));\n-\tif (!check_changes_tracked_files(&ps))\n-\t\treturn 0;\n+\t/*\n+\t * Preserve \"stash create\"'s successful no-change behavior before\n+\t * do_create_stash() refreshes and writes the index.\n+\t */\n+\tif (!check_changes(&ps, include_untracked, &untracked_files))\n+\t\tgoto done;\n \n-\tret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,\n-\t\t\t      NULL, 0);\n+\tret = do_create_stash(&ps, &stash_msg_buf, include_untracked, 0, NULL,\n+\t\t\t      0, &info, NULL, 0);\n+\t/*\n+\t * Status 1 is reserved for conflicts when applying a stash.\n+\t * do_create_stash() uses it internally for \"nothing to create\", so\n+\t * translate that sentinel to create's public success status.\n+\t */\n \tif (!ret)\n \t\tprintf_ln(\"%s\", oid_to_hex(&info.w_commit));\n+\telse if (ret == 1)\n+\t\tret = 0;\n \n+done:\n+\tstrbuf_release(&untracked_files);\n \tfree_stash_info(&info);\n \tstrbuf_release(&stash_msg_buf);\n \treturn ret;\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 7211586..1f660ca 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1179,6 +1179,89 @@ test_expect_success 'create with multiple arguments for the message' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'create with untracked options' '\n+\ttest_when_finished \"rm -rf create-options\" &&\n+\tgit init create-options &&\n+\ttest_commit -C create-options base tracked base &&\n+\ttest_commit -C create-options ignore .gitignore ignored &&\n+\n+\tgit -C create-options stash create -u >actual &&\n+\ttest_must_be_empty actual &&\n+\tgit -C create-options stash create -a >actual &&\n+\ttest_must_be_empty actual &&\n+\n+\techo untracked >create-options/untracked &&\n+\techo ignored >create-options/ignored &&\n+\tgit -C create-options diff >before-worktree &&\n+\tgit -C create-options diff --cached >before-index &&\n+\tgit -C create-options status --porcelain=v1 --ignored >before-status &&\n+\n+\tshort=$(git -C create-options stash create -u \"create options\") &&\n+\tlong=$(git -C create-options stash create --include-untracked \"create options\") &&\n+\ttest \"$(git -C create-options rev-parse \"$short^3^{tree}\")\" = \"$(git -C create-options rev-parse \"$long^3^{tree}\")\" &&\n+\techo untracked >expect &&\n+\tgit -C create-options show \"$short^3:untracked\" >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git -C create-options cat-file -e \"$short^3:ignored\" &&\n+\n+\tshort=$(git -C create-options stash create -a \"create options\") &&\n+\tlong=$(git -C create-options stash create --all \"create options\") &&\n+\tcluster=$(git -C create-options stash create -ua \"create options\") &&\n+\ttest \"$(git -C create-options rev-parse \"$short^3^{tree}\")\" = \"$(git -C create-options rev-parse \"$long^3^{tree}\")\" &&\n+\ttest \"$(git -C create-options rev-parse \"$short^3^{tree}\")\" = \"$(git -C create-options rev-parse \"$cluster^3^{tree}\")\" &&\n+\techo ignored >expect &&\n+\tgit -C create-options show \"$short^3:ignored\" >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tgit -C create-options diff >after-worktree &&\n+\tgit -C create-options diff --cached >after-index &&\n+\tgit -C create-options status --porcelain=v1 --ignored >after-status &&\n+\ttest_cmp before-worktree after-worktree &&\n+\ttest_cmp before-index after-index &&\n+\ttest_cmp before-status after-status &&\n+\ttest_must_fail git -C create-options rev-parse --verify refs/stash >/dev/null 2>&1\n+'\n+\n+test_expect_success 'create untracked modes with only ignored files' '\n+\ttest_when_finished \"rm -rf create-ignored-only\" &&\n+\tgit init create-ignored-only &&\n+\ttest_commit -C create-ignored-only base tracked base &&\n+\ttest_commit -C create-ignored-only ignore .gitignore ignored &&\n+\techo ignored >create-ignored-only/ignored &&\n+\n+\tgit -C create-ignored-only stash create -u >actual &&\n+\ttest_must_be_empty actual &&\n+\tstash=$(git -C create-ignored-only stash create -a) &&\n+\ttest -n \"$stash\" &&\n+\techo ignored >expect &&\n+\tgit -C create-ignored-only show \"$stash^3:ignored\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create option parsing and dash-leading messages' '\n+\ttest_when_finished \"rm -rf create-message-options\" &&\n+\tgit init create-message-options &&\n+\ttest_commit -C create-message-options base tracked base &&\n+\techo modified >>create-message-options/tracked &&\n+\techo untracked >create-message-options/untracked &&\n+\n+\tstash=$(git -C create-message-options stash create handle new -u flag) &&\n+\techo \"On main: handle new -u flag\" >expect &&\n+\tgit -C create-message-options show --pretty=%s -s \"$stash\" >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git -C create-message-options cat-file -e \"$stash^3^{commit}\" &&\n+\n+\ttest_must_fail git -C create-message-options stash create -f >out 2>err &&\n+\ttest_grep \"unknown switch\" err &&\n+\tstash=$(git -C create-message-options stash create -- -f) &&\n+\techo \"On main: -f\" >expect &&\n+\tgit -C create-message-options show --pretty=%s -s \"$stash\" >actual &&\n+\ttest_cmp expect actual &&\n+\n+\ttest_must_fail git -C create-message-options stash create \\\n+\t\t--include-untracked=yes >out 2>err\n+'\n+\n test_expect_success 'create in a detached state' '\n \ttest_when_finished \"git checkout main\" &&\n \tgit checkout HEAD~1 &&\n-- \n2.47.3\n\n"},{"id":"553831","messageId":"ar5EwwEt8-ADeLdr@pks.im","threadId":"66418","inReplyTo":"20261001042155.33303-1-kazumasa.shigeta@kanamei.com","subject":"Re: [PATCH v2] stash: expose untracked modes in create","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-01T11:32:19Z","receivedAt":"2026-10-01T11:32:29Z","isPatch":true,"body":"On Thu, Oct 01, 2026 at 01:21:55PM +0900, Kazumasa Shigeta wrote:\n\nWhen sending a v2 in response to review feedback it's a good idea to\nboth:\n\n  - Respond to the reviewer to acknowledge their feedback and/or engage\n    in a discussion.\n\n  - As part of v2, send a range-diff as well as some documentation what\n    has changed between the two versions.\n\nThis ensures some netiquette in an age where we're increasingly only\ntalking with AI, either directly or via a meat proxy. And makes it\neasier for the reviewer to see how exactly you have honored their\nfeedback.\n\nThanks!\n\nPatrick\n"},{"id":"553844","messageId":"CANUHOw3syiH81F_5q-S33Zmyfw5v_R8cS1hwgpPLqrBDQGu3FQ@mail.gmail.com","threadId":"66418","inReplyTo":"ar5EwwEt8-ADeLdr@pks.im","subject":"Re: [PATCH v2] stash: expose untracked modes in create","fromName":"重田一聖","fromEmail":"kazumasa.shigeta@kanamei.com","sentAt":"2026-10-01T16:01:04Z","receivedAt":"2026-10-01T16:01:08Z","isPatch":true,"body":"Hi Patrick,\n\nThanks for pointing this out, and sorry I did not understand the\nexpected review process here and sent v2 before replying to Phillip.\n\nI'll reply to Phillip first and follow the proper order.\n\nThanks,\n\nKazumasa Shigeta\n\n\nOn Thu, 1 Oct 2026 13:32:19 +0200, Patrick Steinhardt <ps@pks.im> wrote:\n> On Thu, Oct 01, 2026 at 01:21:55PM +0900, Kazumasa Shigeta wrote:\n>\n> When sending a v2 in response to review feedback it's a good idea to\n> both:\n>\n> - Respond to the reviewer to acknowledge their feedback and/or engage\n> in a discussion.\n>\n> - As part of v2, send a range-diff as well as some documentation what\n> has changed between the two versions.\n>\n> This ensures some netiquette in an age where we're increasingly only\n> talking with AI, either directly or via a meat proxy. And makes it\n> easier for the reviewer to see how exactly you have honored their\n> feedback.\n>\n> Thanks!\n>\n> Patrick\n"},{"id":"553847","messageId":"xmqqcxtt74ys.fsf@gitster.g","threadId":"66418","inReplyTo":"ar5EwwEt8-ADeLdr@pks.im","subject":"Re: [PATCH v2] stash: expose untracked modes in create","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-10-01T16:36:43Z","receivedAt":"2026-10-01T16:36:45Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Thu, Oct 01, 2026 at 01:21:55PM +0900, Kazumasa Shigeta wrote:\n>\n> When sending a v2 in response to review feedback it's a good idea to\n> both:\n>\n>   - Respond to the reviewer to acknowledge their feedback and/or engage\n>     in a discussion.\n>\n>   - As part of v2, send a range-diff as well as some documentation what\n>     has changed between the two versions.\n>\n> This ensures some netiquette in an age where we're increasingly only\n> talking with AI, either directly or via a meat proxy. And makes it\n> easier for the reviewer to see how exactly you have honored their\n> feedback.\n>\n> Thanks!\n>\n> Patrick\n\nThanks for bringing this up.\n\nA response to review on the first round should come _before_ sending\nv2 round of patch(es).  Some people send them after v2, or\nimmediately sending before v2, but the right time to respond is\nactually soon after receiving reviews on v1 and you had enough time\nto understand the review comments, before starting to work on v2.\nAnd then after working on v2, you would send patches.  So whenever I\nsee v1 responses come after v2 patches or soon before v2 patches, I\nsmell that something is fishy.\n\n"},{"id":"553849","messageId":"xmqq7bk173qm.fsf@gitster.g","threadId":"66418","inReplyTo":"20261001042155.33303-1-kazumasa.shigeta@kanamei.com","subject":"Re: [PATCH v2] stash: expose untracked modes in create","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-10-01T17:03:13Z","receivedAt":"2026-10-01T17:03:20Z","isPatch":true,"body":"Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:\n\n> `git stash create` always passes zero for the include_untracked parameter\n> of do_create_stash(), even though that helper already supports untracked\n> and ignored files and stash push/save expose those modes as\n> -u/--include-untracked and -a/--all.\n\nThere may be no lies in what the above says, but we would prefer to\nhear what the user visible implication of \"passing 0\" is more than\nwhat mechanically is happening inside a program.  For example:\n\n    \"git stash create\", \"git stash push\", and \"git stash save\" are\n    commands that create a new stash entry.  The latter two are also\n    responsible for storing the resulting stash entry to the reflog\n    of the \"refs/stash\" ref, but have options to control what is\n    included in the stash entry.  Among these options, \"create\" only\n    supports the equivalent of \"-m <message.\" to record in the stash\n    entry.  Most notably, \"-u\" and \"-a\" options are missing.\n\n> Teach create to accept the same options and pass the existing mode\n> through. Unlike push/save, create continues to only create objects: it\n> does not update refs/stash, reset the index, or clean the working tree.\n\nSure.  It is a very concise and good description of what we want to\ndo.\n\n> Use parse_options() for the new options and stop parsing at the first\n> non-option message word. This keeps option-like tokens after the message\n> as message text, while leading option-like arguments now follow Git's\n> normal option parsing. In particular, unknown or malformed leading\n> options are rejected instead of silently becoming a message, short\n> options may be combined, and `--` can be used when a message itself\n> begins with a dash.\n\nWhy do we need to go into such a detail in the log message?  What is\nthe above paragraph designed to convey to the reader?  Again, it may\nnot be telling any lies, but it misses the point by being inconsiderate\nto your readers.  What you need to tell them is _WHY_ you chose to\nuse parse_options() in such a way.  What were you trying to achieve?\n\nI am guessing that something along this line ...\n\n    \"git stash create\" traditionally treated the rest of the command\n    line as a message.  For example, \n\n\t$ git stash create adding -u option\n\n    has always been a request to create a stash entry with the\n    string \"adding -u option\" as its message.  We should not make it\n    trigger the \"-u\" (include untracked) behavior for backward\n    compatibility, by using parse_options() with stop-at-the-non-option\n    mode to forbid it from reordering the command line arguments.\n\n... was what you wanted to say, but I am not sure.\n\nHow much of all these verbiage was written by AI by the way?  You'd\nneed to spend effort to make it readable to humans.\n\n> Keep create's existing no-change behavior: detect the usual no-change\n> case before do_create_stash() refreshes and writes the index, and return\n> success without printing an object name. If do_create_stash() still\n> reports its internal \"nothing to create\" result, map that to create's\n> public success status.\n\nYou already said that with \"does not update, reset, or clean\".\n\n> This follows the stash subcommand exit-status convention established by\n> 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):\n> subcommands return 0 on success, negative values on failure, and status 1\n> when applying a stash results in conflicts. cmd_stash() maps negative\n> subcommand failures to 128.\n\nAgain, there may not be lies in here, but if you did not make a\nbreaking change to the established convention, is it worth saying?\n\n> 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the\n> internal include-untracked path while intentionally leaving the user\n> interface for \"git stash create\" unchanged. Reuse that machinery and\n> the existing INCLUDE_ALL_FILES mode rather than adding a separate stash\n> creation path.\n>\n> Add coverage for short and long aliases, combined short options, the\n> untracked/ignored boundary including an ignored-only worktree, option\n> parsing and dash-leading messages, no-change behavior, and preservation\n> of refs/stash, the index state, and the working tree.\n\nAgain, adding tests for comprehensive coverage is not something to\nboast about.  Is it worth saying?\n\nAren't -p/-S/-k/-q and pathspec support all about the creating half\nof \"git stash push\" that are not available to \"git stash create\",\nnot just \"-u\" and \"-a\"?  Why are we singling out only these two?  It\nmay be more worthwhile to explain the rationale behind such a design\ndecision.\n"},{"id":"553855","messageId":"CANUHOw201hr2LgHb1ThcadiH8Y5k3zUArnhtj8g8SRVrvMsN-g@mail.gmail.com","threadId":"66418","inReplyTo":"8453ebd1-77c1-4941-afbf-572f9e7b12c1@gmail.com","subject":"Re: [PATCH] stash: expose untracked modes in create","fromName":"重田一聖","fromEmail":"kazumasa.shigeta@kanamei.com","sentAt":"2026-10-01T17:44:00Z","receivedAt":"2026-10-01T17:44:07Z","isPatch":true,"body":"Hi Phillip,\n\nThanks for the review.\n\nSorry, I got a little carried away and sent v2 before replying.\n\n> This doesn't seem to match the code changes.\n\nFor the no-change case, plain \"git stash create\" already checks for\ntracked changes before calling do_create_stash(), and returns 0 with\nempty output when there is nothing to create.\n\nFor -u and -a, I think we should follow that existing \"create\" behavior\nas well, using check_changes() for the selected mode before calling\ndo_create_stash(), and returning 0 when it finds nothing to create.\n\nI am also thinking of mapping do_create_stash()'s internal no-change\nstatus of 1 to 0 in the unlikely case where the state changes between\nthese checks. That 1 is not STASH_APPLY_CONFLICT. Following 786fc390465f\n(\"stash: reserve exit status 1 for conflicts\"), I do not think it should\nescape as public exit status 1, and would map it to 0 instead.\n\n> You should pass PARSE_OPT_STOP_AT_NON_OPTION to parse_options()\n> to prevent that.\n\nI plan to use PARSE_OPT_STOP_AT_NON_OPTION as you suggested.\n\nThanks,\n\nKazumasa Shigeta\n\n\nOn Tue, 29 Sep 2026 17:08:08 +0100, Phillip Wood\n<phillip.wood123@gmail.com> wrote:\n> Hi Kazumasa\n>\n> On 29/09/2026 08:42, Kazumasa Shigeta wrote:\n> > `git stash create` always passes zero for the include_untracked parameter\n> > of do_create_stash(), even though that helper already supports untracked\n> > and ignored files and stash push/save expose those modes as\n> > -u/--include-untracked and -a/--all.\n> >\n> > Teach create to accept the same options and pass the existing mode\n> > through. Unlike push/save, create continues to only create objects: it\n> > does not update refs/stash or modify the index or working tree.\n> >\n> > When the selected mode finds no changes, do_create_stash() returns 1.\n> > Translate that to success so create keeps its existing no-object, empty\n> > output behavior.\n>\n> This doesn't seem to match the code changes. The code that prints the\n> object id when the stash is successfully created is unchanged, as far as\n> I can see what this patch does is change the exit status for \"git stash\n> create\" when there are no changes to stash. Instead of exiting 1, it\n> exits 0 even though it does not create a stash. That does not seem like\n> a good idea.\n>\n> > Use normal parse-options semantics, so options may appear after message\n> > arguments. A message that begins with a dash can be disambiguated with\n>\n> As \"git stash create\" concatenates excess arguments to use as the stash\n> message we should not be permuting options. \"git stash create handle new\n> -u flag\" should continue to create a stash with the message \"handle new\n> -u flag\" - it should not start stashing untracked files. You should pass\n> PARSE_OPT_STOP_AT_NON_OPTION to parse_options() to prevent that.\n>\n> > I proposed adding both --include-untracked and --all to\n> > \"git stash create\" in 2014:\n> > <1403856479-37421-1-git-send-email-shigeta@kanamei.co.jp>\n> >\n> > I should also apologize for dropping that thread after receiving review.\n> > I did not follow up on the comments at the time. Thanks to those who\n> > reviewed it then.\n>\n> Better late than never! I think the idea is fine, but the implementation\n> could do with a couple of tweaks so it is as backward compatible as\n> possible.\n>\n> Thanks\n>\n> Phillip\n>\n> > Separately, in 2017, Thomas Gummerer added an internal -u path while\n> > refactoring stash_create in 9ca6326dff29 (stash: refactor stash_create).\n> > That change explicitly kept the user interface of \"git stash create\"\n> > unchanged.\n> >\n> > When \"stash create\" was later converted to the builtin C implementation\n> > in d4788af875cc (stash: convert create to builtin), the untracked-file\n> > handling was carried into the new implementation and remains there today.\n> >\n> > More recently, Shabbir Bhojani proposed exposing --include-untracked:\n> > <pull.1892.git.1774768580147.gitgitgadget@gmail.com>\n> >\n> > This patch exposes both existing untracked modes, --include-untracked and\n> > --all, to \"git stash create\".\n> >\n> > Documentation/git-stash.adoc | 18 ++++++----\n> > builtin/stash.c | 36 ++++++++++++++-----\n> > t/t3903-stash.sh | 70 ++++++++++++++++++++++++++++++++++++\n> > 3 files changed, 109 insertions(+), 15 deletions(-)\n> >\n> > diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\n> > index fc6a9a0..32f0fd5 100644\n> > --- a/Documentation/git-stash.adoc\n> > +++ b/Documentation/git-stash.adoc\n> > @@ -21,7 +21,7 @@ git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | -\n> > git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n> > [-u | --include-untracked] [-a | --all] [<message>]\n> > git stash clear\n> > -git stash create [<message>]\n> > +git stash create [-u | --include-untracked] [-a | --all] [<message>]\n> > git stash store [(-m | --message) <message>] [-q | --quiet] <commit>\n> > git stash export (--print | --to-ref <ref>) [<stash>...]\n> > git stash import <commit>\n> > @@ -138,10 +138,12 @@ with no conflicts.\n> > `drop [-q | --quiet] [<stash>]`::\n> > Remove a single stash entry from the list of stash entries.\n> >\n> > -`create`::\n> > +`create [-u | --include-untracked] [-a | --all]`::\n> > Create a stash entry (which is a regular commit object) and\n> > return its object name, without storing it anywhere in the ref\n> > - namespace.\n> > + namespace. The `--include-untracked` option includes untracked\n> > + files, while `--all` also includes ignored files, without modifying\n> > + the working tree.\n> > This is intended to be useful for scripts. It is probably not\n> > the command you want to use; see \"push\" above.\n> >\n> > @@ -167,10 +169,11 @@ OPTIONS\n> > -------\n> > `-a`::\n> > `--all`::\n> > - This option is only valid for `push` and `save` commands.\n> > + When used with the `push` and `save` commands, all ignored and\n> > + untracked files are also stashed and then cleaned up with `git clean`.\n> > +\n> > -All ignored and untracked files are also stashed and then cleaned\n> > -up with `git clean`.\n> > +When used with the `create` command, ignored and untracked files are included\n> > +in the stash entry without modifying the working tree.\n> >\n> > `-u`::\n> > `--include-untracked`::\n> > @@ -179,6 +182,9 @@ up with `git clean`.\n> > all untracked files are also stashed and then cleaned up with\n> > `git clean`.\n> > +\n> > +When used with the `create` command, untracked files are included in the\n> > +stash entry without modifying the working tree.\n> > ++\n> > When used with the `show` command, show the untracked files in the stash\n> > entry as part of the diff.\n> >\n> > diff --git a/builtin/stash.c b/builtin/stash.c\n> > index 7a98434..57a4750 100644\n> > --- a/builtin/stash.c\n> > +++ b/builtin/stash.c\n> > @@ -59,7 +59,7 @@\n> > N_(\"git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\\n\" \\\n> > \" [-u | --include-untracked] [-a | --all] [<message>]\")\n> > #define BUILTIN_STASH_CREATE_USAGE \\\n> > - N_(\"git stash create [<message>]\")\n> > + N_(\"git stash create [-u | --include-untracked] [-a | --all] [<message>]\")\n> > #define BUILTIN_STASH_EXPORT_USAGE \\\n> > N_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\")\n> > #define BUILTIN_STASH_IMPORT_USAGE \\\n> > @@ -119,6 +119,11 @@ static const char * const git_stash_clear_usage[] = {\n> > NULL\n> > };\n> >\n> > +static const char * const git_stash_create_usage[] = {\n> > + BUILTIN_STASH_CREATE_USAGE,\n> > + NULL\n> > +};\n> > +\n> > static const char * const git_stash_store_usage[] = {\n> > BUILTIN_STASH_STORE_USAGE,\n> > NULL\n> > @@ -1643,26 +1648,39 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n> > return ret;\n> > }\n> >\n> > -static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n> > +static int create_stash(int argc, const char **argv, const char *prefix,\n> > struct repository *repo UNUSED)\n> > {\n> > - int ret;\n> > + int ret = 0;\n> > + int include_untracked = 0;\n> > + struct option options[] = {\n> > + OPT_BOOL('u', \"include-untracked\", &include_untracked,\n> > + N_(\"include untracked files in stash\")),\n> > + OPT_SET_INT('a', \"all\", &include_untracked,\n> > + N_(\"include ignored files in stash\"),\n> > + INCLUDE_ALL_FILES),\n> > + OPT_END()\n> > + };\n> > struct strbuf stash_msg_buf = STRBUF_INIT;\n> > struct stash_info info = STASH_INFO_INIT;\n> > struct pathspec ps;\n> >\n> > - /* Starting with argv[1], since argv[0] is \"create\" */\n> > - strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');\n> > + argc = parse_options(argc, argv, prefix, options,\n> > + git_stash_create_usage, 0);\n> > + strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');\n> >\n> > memset(&ps, 0, sizeof(ps));\n> > - if (!check_changes_tracked_files(&ps))\n> > - return 0;\n> > + if (!include_untracked && !check_changes_tracked_files(&ps))\n> > + goto done;\n> >\n> > - ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,\n> > - NULL, 0);\n> > + ret = do_create_stash(&ps, &stash_msg_buf, include_untracked, 0, NULL,\n> > + 0, &info, NULL, 0);\n> > if (!ret)\n> > printf_ln(\"%s\", oid_to_hex(&info.w_commit));\n> > + else if (ret == 1)\n> > + ret = 0;\n> >\n> > +done:\n> > free_stash_info(&info);\n> > strbuf_release(&stash_msg_buf);\n> > return ret;\n> > diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> > index 7211586..fe34879 100755\n> > --- a/t/t3903-stash.sh\n> > +++ b/t/t3903-stash.sh\n> > @@ -640,6 +640,76 @@ test_expect_success 'stash create - no changes' '\n> > test_must_be_empty actual\n> > '\n> >\n> > +# --all observes every untracked and ignored path in the worktree. Use one\n> > +# isolated repository for these checks so unrelated test state is not captured.\n> > +test_expect_success 'stash create with untracked options' '\n> > + test_when_finished \"rm -rf stash-create-options\" &&\n> > + test_create_repo stash-create-options &&\n> > + (\n> > + cd stash-create-options &&\n> > + test_commit base tracked base &&\n> > + echo create-ignored >.gitignore &&\n> > + git add .gitignore &&\n> > + git commit -m ignore &&\n> > +\n> > + git stash create -u >.git/actual &&\n> > + test_must_be_empty .git/actual &&\n> > + git stash create -a >.git/actual &&\n> > + test_must_be_empty .git/actual &&\n> > +\n> > + echo untracked >create-untracked &&\n> > + git stash create \"without untracked\" >.git/actual &&\n> > + test_must_be_empty .git/actual &&\n> > + short=$(git stash create \"create untracked\" -u) &&\n> > + long=$(git stash create --include-untracked \"create untracked\") &&\n> > + test_cmp_rev \"$short^3^{tree}\" \"$long^3^{tree}\" &&\n> > + echo untracked >.git/expect &&\n> > + git show \"$short^3:create-untracked\" >.git/actual &&\n> > + test_cmp .git/expect .git/actual &&\n> > + branch=$(git symbolic-ref --short HEAD) &&\n> > + echo \"On $branch: create untracked\" >.git/expect &&\n> > + git show --pretty=%s -s \"$short\" >.git/actual &&\n> > + test_cmp .git/expect .git/actual &&\n> > + test_path_is_file create-untracked &&\n> > +\n> > + echo ignored >create-ignored &&\n> > + with_untracked=$(git stash create -u \"create options\") &&\n> > + test_must_fail git cat-file -e \"$with_untracked^3:create-ignored\" &&\n> > + short=$(git stash create \"create options\" -a) &&\n> > + long=$(git stash create --all \"create options\") &&\n> > + test_cmp_rev \"$short^3^{tree}\" \"$long^3^{tree}\" &&\n> > + echo ignored >.git/expect &&\n> > + git show \"$short^3:create-ignored\" >.git/actual &&\n> > + test_cmp .git/expect .git/actual &&\n> > + test_path_is_file create-untracked &&\n> > + test_path_is_file create-ignored &&\n> > +\n> > + echo staged >staged &&\n> > + git add staged &&\n> > + echo modified >>tracked &&\n> > + git diff >.git/before-worktree &&\n> > + git diff --cached >.git/before-index &&\n> > + git status --porcelain=v1 --ignored >.git/before-status &&\n> > + test_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&\n> > + STASH_ID=$(git stash create -a -- -create-message) &&\n> > + git diff >.git/after-worktree &&\n> > + git diff --cached >.git/after-index &&\n> > + git status --porcelain=v1 --ignored >.git/after-status &&\n> > + test_cmp .git/before-worktree .git/after-worktree &&\n> > + test_cmp .git/before-index .git/after-index &&\n> > + test_cmp .git/before-status .git/after-status &&\n> > + test_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&\n> > + echo \"On $branch: -create-message\" >.git/expect &&\n> > + git show --pretty=%s -s \"$STASH_ID\" >.git/actual &&\n> > + test_cmp .git/expect .git/actual\n> > + )\n> > +'\n> > +\n> > +test_expect_success 'stash create rejects unknown options' '\n> > + test_expect_code 129 git stash create --unknown-option 2>err &&\n> > + test_grep \"unknown option\" err\n> > +'\n> > +\n> > test_expect_success 'stash branch - no stashes on stack, stash-like argument' '\n> > git stash clear &&\n> > test_when_finished \"git reset --hard HEAD\" &&\n"},{"id":"553883","messageId":"CANUHOw2Q1Dg=e7zfAcRUBMyKt+cVW02V5MZV4x7vAyA6iJ=PWw@mail.gmail.com","threadId":"66418","inReplyTo":"xmqq7bk173qm.fsf@gitster.g","subject":"Re: [PATCH v2] stash: expose untracked modes in create","fromName":"重田一聖","fromEmail":"kazumasa.shigeta@kanamei.com","sentAt":"2026-10-02T01:53:04Z","receivedAt":"2026-10-02T01:53:06Z","isPatch":true,"body":"Hi Junio,\n\nSorry for the crossed replies. As you also pointed out, I should have\nreplied to Phillip before sending v2. After Patrick raised that point, I\nwas writing my response to Phillip, and I did not notice that your\nmessages had arrived while I was doing so. I ended up sending my reply\nto Phillip before seeing your comments.\n\nThank you for the detailed review. I will go through your comments\ncarefully before following up.\n\nI am not very quick at writing these emails, so it takes me quite a\nwhile to respond. Sorry about that. I will do my best to understand the\npoints properly and improve the next round.\n\nThanks,\nKazumasa Shigeta\n\nOn Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:\n>\n> > `git stash create` always passes zero for the include_untracked parameter\n> > of do_create_stash(), even though that helper already supports untracked\n> > and ignored files and stash push/save expose those modes as\n> > -u/--include-untracked and -a/--all.\n>\n> There may be no lies in what the above says, but we would prefer to\n> hear what the user visible implication of \"passing 0\" is more than\n> what mechanically is happening inside a program. For example:\n>\n> \"git stash create\", \"git stash push\", and \"git stash save\" are\n> commands that create a new stash entry. The latter two are also\n> responsible for storing the resulting stash entry to the reflog\n> of the \"refs/stash\" ref, but have options to control what is\n> included in the stash entry. Among these options, \"create\" only\n> supports the equivalent of \"-m <message.\" to record in the stash\n> entry. Most notably, \"-u\" and \"-a\" options are missing.\n>\n> > Teach create to accept the same options and pass the existing mode\n> > through. Unlike push/save, create continues to only create objects: it\n> > does not update refs/stash, reset the index, or clean the working tree.\n>\n> Sure. It is a very concise and good description of what we want to\n> do.\n>\n> > Use parse_options() for the new options and stop parsing at the first\n> > non-option message word. This keeps option-like tokens after the message\n> > as message text, while leading option-like arguments now follow Git's\n> > normal option parsing. In particular, unknown or malformed leading\n> > options are rejected instead of silently becoming a message, short\n> > options may be combined, and `--` can be used when a message itself\n> > begins with a dash.\n>\n> Why do we need to go into such a detail in the log message? What is\n> the above paragraph designed to convey to the reader? Again, it may\n> not be telling any lies, but it misses the point by being inconsiderate\n> to your readers. What you need to tell them is _WHY_ you chose to\n> use parse_options() in such a way. What were you trying to achieve?\n>\n> I am guessing that something along this line ...\n>\n> \"git stash create\" traditionally treated the rest of the command\n> line as a message. For example,\n>\n> $ git stash create adding -u option\n>\n> has always been a request to create a stash entry with the\n> string \"adding -u option\" as its message. We should not make it\n> trigger the \"-u\" (include untracked) behavior for backward\n> compatibility, by using parse_options() with stop-at-the-non-option\n> mode to forbid it from reordering the command line arguments.\n>\n> ... was what you wanted to say, but I am not sure.\n>\n> How much of all these verbiage was written by AI by the way? You'd\n> need to spend effort to make it readable to humans.\n>\n> > Keep create's existing no-change behavior: detect the usual no-change\n> > case before do_create_stash() refreshes and writes the index, and return\n> > success without printing an object name. If do_create_stash() still\n> > reports its internal \"nothing to create\" result, map that to create's\n> > public success status.\n>\n> You already said that with \"does not update, reset, or clean\".\n>\n> > This follows the stash subcommand exit-status convention established by\n> > 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):\n> > subcommands return 0 on success, negative values on failure, and status 1\n> > when applying a stash results in conflicts. cmd_stash() maps negative\n> > subcommand failures to 128.\n>\n> Again, there may not be lies in here, but if you did not make a\n> breaking change to the established convention, is it worth saying?\n>\n> > 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the\n> > internal include-untracked path while intentionally leaving the user\n> > interface for \"git stash create\" unchanged. Reuse that machinery and\n> > the existing INCLUDE_ALL_FILES mode rather than adding a separate stash\n> > creation path.\n> >\n> > Add coverage for short and long aliases, combined short options, the\n> > untracked/ignored boundary including an ignored-only worktree, option\n> > parsing and dash-leading messages, no-change behavior, and preservation\n> > of refs/stash, the index state, and the working tree.\n>\n> Again, adding tests for comprehensive coverage is not something to\n> boast about. Is it worth saying?\n>\n> Aren't -p/-S/-k/-q and pathspec support all about the creating half\n> of \"git stash push\" that are not available to \"git stash create\",\n> not just \"-u\" and \"-a\"? Why are we singling out only these two? It\n> may be more worthwhile to explain the rationale behind such a design\n> decision.\n"},{"id":"553930","messageId":"CANUHOw1eO0HNjU+-PYNDOz9kHhBZYYfhiKJSX4082YSC1NKxww@mail.gmail.com","threadId":"66418","inReplyTo":"xmqq7bk173qm.fsf@gitster.g","subject":"Re: [PATCH v2] stash: expose untracked modes in create","fromName":"重田一聖","fromEmail":"kazumasa.shigeta@kanamei.com","sentAt":"2026-10-02T09:04:26Z","receivedAt":"2026-10-02T09:04:29Z","isPatch":true,"body":"Hi Junio,\n\n> we would prefer to hear what the user visible implication of\n> \"passing 0\" is more than what mechanically is happening inside a\n> program.\n\nThe user-visible effect is that stash create cannot currently include\nuntracked or ignored files in the stash entry. If those are the only\nchanges, it creates no entry at all, while stash push and save can\ninclude them with -u or -a as appropriate. I should have described that\ndifference directly instead of starting from the include_untracked\nimplementation detail.\n\n> ... was what you wanted to say, but I am not sure.\n\nYes, exactly. I'll explain the backward-compatibility reason rather\nthan the mechanics of parse_options().\n\n> You already said that with \"does not update, reset, or clean\".\n\nI'll drop that paragraph.\n\n> if you did not make a breaking change to the established convention,\n> is it worth saying?\n\nI don't think it adds anything here. I'll remove the exit-status\ndiscussion from the commit message as well.\n\n> adding tests for comprehensive coverage is not something to boast\n> about. Is it worth saying?\n\nI'll remove the test details from the commit message.\n\n> Why are we singling out only these two?\n\nI started by looking at the missing -u and -a support in create, and I\nthink that led me to focus too narrowly on those two when considering\nthe scope. I need to think more about whether this patch should remain\nlimited to those two.\n\nThanks,\nKazumasa Shigeta\n\nOn Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:\n>\n> > `git stash create` always passes zero for the include_untracked parameter\n> > of do_create_stash(), even though that helper already supports untracked\n> > and ignored files and stash push/save expose those modes as\n> > -u/--include-untracked and -a/--all.\n>\n> There may be no lies in what the above says, but we would prefer to\n> hear what the user visible implication of \"passing 0\" is more than\n> what mechanically is happening inside a program. For example:\n>\n> \"git stash create\", \"git stash push\", and \"git stash save\" are\n> commands that create a new stash entry. The latter two are also\n> responsible for storing the resulting stash entry to the reflog\n> of the \"refs/stash\" ref, but have options to control what is\n> included in the stash entry. Among these options, \"create\" only\n> supports the equivalent of \"-m <message.\" to record in the stash\n> entry. Most notably, \"-u\" and \"-a\" options are missing.\n>\n> > Teach create to accept the same options and pass the existing mode\n> > through. Unlike push/save, create continues to only create objects: it\n> > does not update refs/stash, reset the index, or clean the working tree.\n>\n> Sure. It is a very concise and good description of what we want to\n> do.\n>\n> > Use parse_options() for the new options and stop parsing at the first\n> > non-option message word. This keeps option-like tokens after the message\n> > as message text, while leading option-like arguments now follow Git's\n> > normal option parsing. In particular, unknown or malformed leading\n> > options are rejected instead of silently becoming a message, short\n> > options may be combined, and `--` can be used when a message itself\n> > begins with a dash.\n>\n> Why do we need to go into such a detail in the log message? What is\n> the above paragraph designed to convey to the reader? Again, it may\n> not be telling any lies, but it misses the point by being inconsiderate\n> to your readers. What you need to tell them is _WHY_ you chose to\n> use parse_options() in such a way. What were you trying to achieve?\n>\n> I am guessing that something along this line ...\n>\n> \"git stash create\" traditionally treated the rest of the command\n> line as a message. For example,\n>\n> $ git stash create adding -u option\n>\n> has always been a request to create a stash entry with the\n> string \"adding -u option\" as its message. We should not make it\n> trigger the \"-u\" (include untracked) behavior for backward\n> compatibility, by using parse_options() with stop-at-the-non-option\n> mode to forbid it from reordering the command line arguments.\n>\n> ... was what you wanted to say, but I am not sure.\n>\n> How much of all these verbiage was written by AI by the way? You'd\n> need to spend effort to make it readable to humans.\n>\n> > Keep create's existing no-change behavior: detect the usual no-change\n> > case before do_create_stash() refreshes and writes the index, and return\n> > success without printing an object name. If do_create_stash() still\n> > reports its internal \"nothing to create\" result, map that to create's\n> > public success status.\n>\n> You already said that with \"does not update, reset, or clean\".\n>\n> > This follows the stash subcommand exit-status convention established by\n> > 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):\n> > subcommands return 0 on success, negative values on failure, and status 1\n> > when applying a stash results in conflicts. cmd_stash() maps negative\n> > subcommand failures to 128.\n>\n> Again, there may not be lies in here, but if you did not make a\n> breaking change to the established convention, is it worth saying?\n>\n> > 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the\n> > internal include-untracked path while intentionally leaving the user\n> > interface for \"git stash create\" unchanged. Reuse that machinery and\n> > the existing INCLUDE_ALL_FILES mode rather than adding a separate stash\n> > creation path.\n> >\n> > Add coverage for short and long aliases, combined short options, the\n> > untracked/ignored boundary including an ignored-only worktree, option\n> > parsing and dash-leading messages, no-change behavior, and preservation\n> > of refs/stash, the index state, and the working tree.\n>\n> Again, adding tests for comprehensive coverage is not something to\n> boast about. Is it worth saying?\n>\n> Aren't -p/-S/-k/-q and pathspec support all about the creating half\n> of \"git stash push\" that are not available to \"git stash create\",\n> not just \"-u\" and \"-a\"? Why are we singling out only these two? It\n> may be more worthwhile to explain the rationale behind such a design\n> decision.\n"},{"id":"554143","messageId":"CANUHOw3gynMRGN7A-wOnL3PQtgFbMtsB2gyZxaq+0Z5bHp-H8A@mail.gmail.com","threadId":"66418","inReplyTo":"CANUHOw1eO0HNjU+-PYNDOz9kHhBZYYfhiKJSX4082YSC1NKxww@mail.gmail.com","subject":"Re: [PATCH v2] stash: expose untracked modes in create","fromName":"重田一聖","fromEmail":"kazumasa.shigeta@kanamei.com","sentAt":"2026-10-05T05:55:18Z","receivedAt":"2026-10-05T05:55:19Z","isPatch":true,"body":"Hi Junio,\n\n> Why are we singling out only these two?\n\nAfter looking more carefully at both the history and the current stash\ncode, I don't think there is a good reason to single out only `-u` and\n`-a`.\n\nYour question made me realize that I had focused too narrowly on the\nuntracked modes. The larger issue is not simply that `do_create_stash()`\nhas capabilities that `git stash create` does not expose. The existing\n`git stash create <message>` grammar is a long-standing compatibility\ncontract that has been deliberately preserved.\n\nMaking more of those capabilities available through `create` would\ntherefore mean either changing that contract or designing around it.\nThat is a much larger interface decision than I appreciated when I sent\nthe patch.\n\nI moved too quickly here and sent the patch before understanding that\nconstraint well enough. I am sorry about that. Thanks to you, Phillip,\nPatrick, and everyone else who took the time to review it. I should\nhave investigated this compatibility history first.\n\nEven so, I still think it would be useful to make more of the existing\n`do_create_stash()` capabilities available through the public command\nline.\n\nSo I no longer think the question is simply which additional options\n`stash create` should expose. The broader question is how to make those\ncapabilities available through a public interface while dealing\nappropriately with the existing `git stash create <message>`\ncompatibility contract.\n\nFrom that perspective, I can see three possible directions.\n\n1. Keep extending `stash create`.\n\n   We could expose more of the existing `do_create_stash()`\n   functionality through `stash create`, following the conventions of\n   `stash push` for the creation-related options they have in common.\n\n   This seems implementable, but even with\n   `PARSE_OPT_STOP_AT_NON_OPTION` it would change the handling of\n   messages that begin with an option-like argument. Those would need\n   explicit disambiguation, such as `--`.\n\n   There is also the pathspec question. If positional arguments\n   continue to be joined to form the message, pathspecs need some other\n   way to be distinguished from that message.\n\n2. Add a new stash subcommand for the creation functionality.\n\n   This would leave the existing `stash create <message>` contract\n   unchanged. Because the new command would not inherit `create`'s\n   positional message grammar, its creation-related options and\n   pathspec handling could follow conventions similar to `stash push`.\n\n   This preserves the existing `create` grammar while avoiding the need\n   to fit additional creation capabilities into it. The trade-off is\n   adding another public stash subcommand and its long-term maintenance\n   cost.\n\n3. Add something like `--create-only` to `git stash push`.\n\n   This would reuse the existing `push` option grammar without adding\n   another subcommand.\n\n   I also read the 2019 discussion around `git stash push --snapshot`.\n   One concern there was that approximately the same end state could\n   already be obtained with `git stash push && git stash apply`.\n\n   I do not think that particular concern carries over directly here.\n   `git stash create` already stops at object creation, but its public\n   interface does not expose more of the creation capabilities already\n   available in `do_create_stash()`. There is currently no public stash\n   command that exposes those capabilities while retaining that\n   create-only boundary.\n\n   That does not mean a similar result cannot be constructed by other\n   means. The missing piece is a public interface to the existing stash\n   creation machinery at that boundary.\n\n   Even so, there is still the separate question of whether `push` is\n   the right place for a creation-only operation in the first place.\n   The push-specific work around `do_create_stash()` would also need to\n   be separated carefully.\n\nAll three seem substantially broader than the original `-u` / `-a`\npatch.\n\nIf this is worth pursuing further, which of these directions seems the\nmost plausible? Also, is this the right thread to continue that design\ndiscussion, or would it be better to discuss it separately?\n\nThanks again for the guidance,\nKazumasa Shigeta\n\nOn Fri, 2 Oct 2026 05:04:26 -0400, \"重田一聖\" <kazumasa.shigeta@kanamei.com> wrote:\n> Hi Junio,\n>\n> > we would prefer to hear what the user visible implication of\n> > \"passing 0\" is more than what mechanically is happening inside a\n> > program.\n>\n> The user-visible effect is that stash create cannot currently include\n> untracked or ignored files in the stash entry. If those are the only\n> changes, it creates no entry at all, while stash push and save can\n> include them with -u or -a as appropriate. I should have described that\n> difference directly instead of starting from the include_untracked\n> implementation detail.\n>\n> > ... was what you wanted to say, but I am not sure.\n>\n> Yes, exactly. I'll explain the backward-compatibility reason rather\n> than the mechanics of parse_options().\n>\n> > You already said that with \"does not update, reset, or clean\".\n>\n> I'll drop that paragraph.\n>\n> > if you did not make a breaking change to the established convention,\n> > is it worth saying?\n>\n> I don't think it adds anything here. I'll remove the exit-status\n> discussion from the commit message as well.\n>\n> > adding tests for comprehensive coverage is not something to boast\n> > about. Is it worth saying?\n>\n> I'll remove the test details from the commit message.\n>\n> > Why are we singling out only these two?\n>\n> I started by looking at the missing -u and -a support in create, and I\n> think that led me to focus too narrowly on those two when considering\n> the scope. I need to think more about whether this patch should remain\n> limited to those two.\n>\n> Thanks,\n> Kazumasa Shigeta\n>\n> On Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> > Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:\n> >\n> > > `git stash create` always passes zero for the include_untracked parameter\n> > > of do_create_stash(), even though that helper already supports untracked\n> > > and ignored files and stash push/save expose those modes as\n> > > -u/--include-untracked and -a/--all.\n> >\n> > There may be no lies in what the above says, but we would prefer to\n> > hear what the user visible implication of \"passing 0\" is more than\n> > what mechanically is happening inside a program. For example:\n> >\n> > \"git stash create\", \"git stash push\", and \"git stash save\" are\n> > commands that create a new stash entry. The latter two are also\n> > responsible for storing the resulting stash entry to the reflog\n> > of the \"refs/stash\" ref, but have options to control what is\n> > included in the stash entry. Among these options, \"create\" only\n> > supports the equivalent of \"-m <message.\" to record in the stash\n> > entry. Most notably, \"-u\" and \"-a\" options are missing.\n> >\n> > > Teach create to accept the same options and pass the existing mode\n> > > through. Unlike push/save, create continues to only create objects: it\n> > > does not update refs/stash, reset the index, or clean the working tree.\n> >\n> > Sure. It is a very concise and good description of what we want to\n> > do.\n> >\n> > > Use parse_options() for the new options and stop parsing at the first\n> > > non-option message word. This keeps option-like tokens after the message\n> > > as message text, while leading option-like arguments now follow Git's\n> > > normal option parsing. In particular, unknown or malformed leading\n> > > options are rejected instead of silently becoming a message, short\n> > > options may be combined, and `--` can be used when a message itself\n> > > begins with a dash.\n> >\n> > Why do we need to go into such a detail in the log message? What is\n> > the above paragraph designed to convey to the reader? Again, it may\n> > not be telling any lies, but it misses the point by being inconsiderate\n> > to your readers. What you need to tell them is _WHY_ you chose to\n> > use parse_options() in such a way. What were you trying to achieve?\n> >\n> > I am guessing that something along this line ...\n> >\n> > \"git stash create\" traditionally treated the rest of the command\n> > line as a message. For example,\n> >\n> > $ git stash create adding -u option\n> >\n> > has always been a request to create a stash entry with the\n> > string \"adding -u option\" as its message. We should not make it\n> > trigger the \"-u\" (include untracked) behavior for backward\n> > compatibility, by using parse_options() with stop-at-the-non-option\n> > mode to forbid it from reordering the command line arguments.\n> >\n> > ... was what you wanted to say, but I am not sure.\n> >\n> > How much of all these verbiage was written by AI by the way? You'd\n> > need to spend effort to make it readable to humans.\n> >\n> > > Keep create's existing no-change behavior: detect the usual no-change\n> > > case before do_create_stash() refreshes and writes the index, and return\n> > > success without printing an object name. If do_create_stash() still\n> > > reports its internal \"nothing to create\" result, map that to create's\n> > > public success status.\n> >\n> > You already said that with \"does not update, reset, or clean\".\n> >\n> > > This follows the stash subcommand exit-status convention established by\n> > > 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):\n> > > subcommands return 0 on success, negative values on failure, and status 1\n> > > when applying a stash results in conflicts. cmd_stash() maps negative\n> > > subcommand failures to 128.\n> >\n> > Again, there may not be lies in here, but if you did not make a\n> > breaking change to the established convention, is it worth saying?\n> >\n> > > 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the\n> > > internal include-untracked path while intentionally leaving the user\n> > > interface for \"git stash create\" unchanged. Reuse that machinery and\n> > > the existing INCLUDE_ALL_FILES mode rather than adding a separate stash\n> > > creation path.\n> > >\n> > > Add coverage for short and long aliases, combined short options, the\n> > > untracked/ignored boundary including an ignored-only worktree, option\n> > > parsing and dash-leading messages, no-change behavior, and preservation\n> > > of refs/stash, the index state, and the working tree.\n> >\n> > Again, adding tests for comprehensive coverage is not something to\n> > boast about. Is it worth saying?\n> >\n> > Aren't -p/-S/-k/-q and pathspec support all about the creating half\n> > of \"git stash push\" that are not available to \"git stash create\",\n> > not just \"-u\" and \"-a\"? Why are we singling out only these two? It\n> > may be more worthwhile to explain the rationale behind such a design\n> > decision.\n"},{"id":"554197","messageId":"632af360-5797-4794-82b7-02c7dd8f7bd4@gmail.com","threadId":"66418","inReplyTo":"CANUHOw201hr2LgHb1ThcadiH8Y5k3zUArnhtj8g8SRVrvMsN-g@mail.gmail.com","subject":"Re: [PATCH] stash: expose untracked modes in create","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-10-05T16:37:53Z","receivedAt":"2026-10-05T16:37:53Z","isPatch":true,"body":"Hi Kazumasa\n\nOn 01/10/2026 18:44, 重田一聖 wrote:\n> Hi Phillip,\n> > Thanks for the review.\n> > Sorry, I got a little carried away and sent v2 before replying.\n> >> This doesn't seem to match the code changes.\n> > For the no-change case, plain \"git stash create\" already checks for\n> tracked changes before calling do_create_stash(), and returns 0 with\n> empty output when there is nothing to create.\n> > For -u and -a, I think we should follow that existing \"create\" behavior\n> as well, using check_changes() for the selected mode before calling\n> do_create_stash(), and returning 0 when it finds nothing to create.\n\nI'm not really sure why that call to check_changes_tracked_files() is there in create_stash() as do_create_stash() repeats the same check.\nI've sent a couple of patches [1] to fix that.\n\n> I am also thinking of mapping do_create_stash()'s internal no-change\n> status of 1 to 0 in the unlikely case where the state changes between\n> these checks.\n\nI think that is worth doing but it is not related to adding new options so should be a separate patch.\n> That 1 is not STASH_APPLY_CONFLICT. Following 786fc390465f\n> (\"stash: reserve exit status 1 for conflicts\"), I do not think it should\n> escape as public exit status 1, and would map it to 0 instead.\n\nI agree we should exit 0 in that case.\n>> You should pass PARSE_OPT_STOP_AT_NON_OPTION to parse_options()\n>> to prevent that.\n> > I plan to use PARSE_OPT_STOP_AT_NON_OPTION as you suggested.\n\nThat's great\n\nThanks\n\nPhillip\n\n[1] https://lore.kernel.org/git/cover.1791218125.git.phillip.wood@dunelm.org.uk\n\n> Thanks,\n> > Kazumasa Shigeta\n> > > On Tue, 29 Sep 2026 17:08:08 +0100, Phillip Wood\n> <phillip.wood123@gmail.com> wrote:\n>> Hi Kazumasa\n>>\n>> On 29/09/2026 08:42, Kazumasa Shigeta wrote:\n>>> `git stash create` always passes zero for the include_untracked parameter\n>>> of do_create_stash(), even though that helper already supports untracked\n>>> and ignored files and stash push/save expose those modes as\n>>> -u/--include-untracked and -a/--all.\n>>>\n>>> Teach create to accept the same options and pass the existing mode\n>>> through. Unlike push/save, create continues to only create objects: it\n>>> does not update refs/stash or modify the index or working tree.\n>>>\n>>> When the selected mode finds no changes, do_create_stash() returns 1.\n>>> Translate that to success so create keeps its existing no-object, empty\n>>> output behavior.\n>>\n>> This doesn't seem to match the code changes. The code that prints the\n>> object id when the stash is successfully created is unchanged, as far as\n>> I can see what this patch does is change the exit status for \"git stash\n>> create\" when there are no changes to stash. Instead of exiting 1, it\n>> exits 0 even though it does not create a stash. That does not seem like\n>> a good idea.\n>>\n>>> Use normal parse-options semantics, so options may appear after message\n>>> arguments. A message that begins with a dash can be disambiguated with\n>>\n>> As \"git stash create\" concatenates excess arguments to use as the stash\n>> message we should not be permuting options. \"git stash create handle new\n>> -u flag\" should continue to create a stash with the message \"handle new\n>> -u flag\" - it should not start stashing untracked files. You should pass\n>> PARSE_OPT_STOP_AT_NON_OPTION to parse_options() to prevent that.\n>>\n>>> I proposed adding both --include-untracked and --all to\n>>> \"git stash create\" in 2014:\n>>> <1403856479-37421-1-git-send-email-shigeta@kanamei.co.jp>\n>>>\n>>> I should also apologize for dropping that thread after receiving review.\n>>> I did not follow up on the comments at the time. Thanks to those who\n>>> reviewed it then.\n>>\n>> Better late than never! I think the idea is fine, but the implementation\n>> could do with a couple of tweaks so it is as backward compatible as\n>> possible.\n>>\n>> Thanks\n>>\n>> Phillip\n>>\n>>> Separately, in 2017, Thomas Gummerer added an internal -u path while\n>>> refactoring stash_create in 9ca6326dff29 (stash: refactor stash_create).\n>>> That change explicitly kept the user interface of \"git stash create\"\n>>> unchanged.\n>>>\n>>> When \"stash create\" was later converted to the builtin C implementation\n>>> in d4788af875cc (stash: convert create to builtin), the untracked-file\n>>> handling was carried into the new implementation and remains there today.\n>>>\n>>> More recently, Shabbir Bhojani proposed exposing --include-untracked:\n>>> <pull.1892.git.1774768580147.gitgitgadget@gmail.com>\n>>>\n>>> This patch exposes both existing untracked modes, --include-untracked and\n>>> --all, to \"git stash create\".\n>>>\n>>> Documentation/git-stash.adoc | 18 ++++++----\n>>> builtin/stash.c | 36 ++++++++++++++-----\n>>> t/t3903-stash.sh | 70 ++++++++++++++++++++++++++++++++++++\n>>> 3 files changed, 109 insertions(+), 15 deletions(-)\n>>>\n>>> diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\n>>> index fc6a9a0..32f0fd5 100644\n>>> --- a/Documentation/git-stash.adoc\n>>> +++ b/Documentation/git-stash.adoc\n>>> @@ -21,7 +21,7 @@ git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | -\n>>> git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n>>> [-u | --include-untracked] [-a | --all] [<message>]\n>>> git stash clear\n>>> -git stash create [<message>]\n>>> +git stash create [-u | --include-untracked] [-a | --all] [<message>]\n>>> git stash store [(-m | --message) <message>] [-q | --quiet] <commit>\n>>> git stash export (--print | --to-ref <ref>) [<stash>...]\n>>> git stash import <commit>\n>>> @@ -138,10 +138,12 @@ with no conflicts.\n>>> `drop [-q | --quiet] [<stash>]`::\n>>> Remove a single stash entry from the list of stash entries.\n>>>\n>>> -`create`::\n>>> +`create [-u | --include-untracked] [-a | --all]`::\n>>> Create a stash entry (which is a regular commit object) and\n>>> return its object name, without storing it anywhere in the ref\n>>> - namespace.\n>>> + namespace. The `--include-untracked` option includes untracked\n>>> + files, while `--all` also includes ignored files, without modifying\n>>> + the working tree.\n>>> This is intended to be useful for scripts. It is probably not\n>>> the command you want to use; see \"push\" above.\n>>>\n>>> @@ -167,10 +169,11 @@ OPTIONS\n>>> -------\n>>> `-a`::\n>>> `--all`::\n>>> - This option is only valid for `push` and `save` commands.\n>>> + When used with the `push` and `save` commands, all ignored and\n>>> + untracked files are also stashed and then cleaned up with `git clean`.\n>>> +\n>>> -All ignored and untracked files are also stashed and then cleaned\n>>> -up with `git clean`.\n>>> +When used with the `create` command, ignored and untracked files are included\n>>> +in the stash entry without modifying the working tree.\n>>>\n>>> `-u`::\n>>> `--include-untracked`::\n>>> @@ -179,6 +182,9 @@ up with `git clean`.\n>>> all untracked files are also stashed and then cleaned up with\n>>> `git clean`.\n>>> +\n>>> +When used with the `create` command, untracked files are included in the\n>>> +stash entry without modifying the working tree.\n>>> ++\n>>> When used with the `show` command, show the untracked files in the stash\n>>> entry as part of the diff.\n>>>\n>>> diff --git a/builtin/stash.c b/builtin/stash.c\n>>> index 7a98434..57a4750 100644\n>>> --- a/builtin/stash.c\n>>> +++ b/builtin/stash.c\n>>> @@ -59,7 +59,7 @@\n>>> N_(\"git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\\n\" \\\n>>> \" [-u | --include-untracked] [-a | --all] [<message>]\")\n>>> #define BUILTIN_STASH_CREATE_USAGE \\\n>>> - N_(\"git stash create [<message>]\")\n>>> + N_(\"git stash create [-u | --include-untracked] [-a | --all] [<message>]\")\n>>> #define BUILTIN_STASH_EXPORT_USAGE \\\n>>> N_(\"git stash export (--print | --to-ref <ref>) [<stash>...]\")\n>>> #define BUILTIN_STASH_IMPORT_USAGE \\\n>>> @@ -119,6 +119,11 @@ static const char * const git_stash_clear_usage[] = {\n>>> NULL\n>>> };\n>>>\n>>> +static const char * const git_stash_create_usage[] = {\n>>> + BUILTIN_STASH_CREATE_USAGE,\n>>> + NULL\n>>> +};\n>>> +\n>>> static const char * const git_stash_store_usage[] = {\n>>> BUILTIN_STASH_STORE_USAGE,\n>>> NULL\n>>> @@ -1643,26 +1648,39 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n>>> return ret;\n>>> }\n>>>\n>>> -static int create_stash(int argc, const char **argv, const char *prefix UNUSED,\n>>> +static int create_stash(int argc, const char **argv, const char *prefix,\n>>> struct repository *repo UNUSED)\n>>> {\n>>> - int ret;\n>>> + int ret = 0;\n>>> + int include_untracked = 0;\n>>> + struct option options[] = {\n>>> + OPT_BOOL('u', \"include-untracked\", &include_untracked,\n>>> + N_(\"include untracked files in stash\")),\n>>> + OPT_SET_INT('a', \"all\", &include_untracked,\n>>> + N_(\"include ignored files in stash\"),\n>>> + INCLUDE_ALL_FILES),\n>>> + OPT_END()\n>>> + };\n>>> struct strbuf stash_msg_buf = STRBUF_INIT;\n>>> struct stash_info info = STASH_INFO_INIT;\n>>> struct pathspec ps;\n>>>\n>>> - /* Starting with argv[1], since argv[0] is \"create\" */\n>>> - strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');\n>>> + argc = parse_options(argc, argv, prefix, options,\n>>> + git_stash_create_usage, 0);\n>>> + strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');\n>>>\n>>> memset(&ps, 0, sizeof(ps));\n>>> - if (!check_changes_tracked_files(&ps))\n>>> - return 0;\n>>> + if (!include_untracked && !check_changes_tracked_files(&ps))\n>>> + goto done;\n>>>\n>>> - ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,\n>>> - NULL, 0);\n>>> + ret = do_create_stash(&ps, &stash_msg_buf, include_untracked, 0, NULL,\n>>> + 0, &info, NULL, 0);\n>>> if (!ret)\n>>> printf_ln(\"%s\", oid_to_hex(&info.w_commit));\n>>> + else if (ret == 1)\n>>> + ret = 0;\n>>>\n>>> +done:\n>>> free_stash_info(&info);\n>>> strbuf_release(&stash_msg_buf);\n>>> return ret;\n>>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n>>> index 7211586..fe34879 100755\n>>> --- a/t/t3903-stash.sh\n>>> +++ b/t/t3903-stash.sh\n>>> @@ -640,6 +640,76 @@ test_expect_success 'stash create - no changes' '\n>>> test_must_be_empty actual\n>>> '\n>>>\n>>> +# --all observes every untracked and ignored path in the worktree. Use one\n>>> +# isolated repository for these checks so unrelated test state is not captured.\n>>> +test_expect_success 'stash create with untracked options' '\n>>> + test_when_finished \"rm -rf stash-create-options\" &&\n>>> + test_create_repo stash-create-options &&\n>>> + (\n>>> + cd stash-create-options &&\n>>> + test_commit base tracked base &&\n>>> + echo create-ignored >.gitignore &&\n>>> + git add .gitignore &&\n>>> + git commit -m ignore &&\n>>> +\n>>> + git stash create -u >.git/actual &&\n>>> + test_must_be_empty .git/actual &&\n>>> + git stash create -a >.git/actual &&\n>>> + test_must_be_empty .git/actual &&\n>>> +\n>>> + echo untracked >create-untracked &&\n>>> + git stash create \"without untracked\" >.git/actual &&\n>>> + test_must_be_empty .git/actual &&\n>>> + short=$(git stash create \"create untracked\" -u) &&\n>>> + long=$(git stash create --include-untracked \"create untracked\") &&\n>>> + test_cmp_rev \"$short^3^{tree}\" \"$long^3^{tree}\" &&\n>>> + echo untracked >.git/expect &&\n>>> + git show \"$short^3:create-untracked\" >.git/actual &&\n>>> + test_cmp .git/expect .git/actual &&\n>>> + branch=$(git symbolic-ref --short HEAD) &&\n>>> + echo \"On $branch: create untracked\" >.git/expect &&\n>>> + git show --pretty=%s -s \"$short\" >.git/actual &&\n>>> + test_cmp .git/expect .git/actual &&\n>>> + test_path_is_file create-untracked &&\n>>> +\n>>> + echo ignored >create-ignored &&\n>>> + with_untracked=$(git stash create -u \"create options\") &&\n>>> + test_must_fail git cat-file -e \"$with_untracked^3:create-ignored\" &&\n>>> + short=$(git stash create \"create options\" -a) &&\n>>> + long=$(git stash create --all \"create options\") &&\n>>> + test_cmp_rev \"$short^3^{tree}\" \"$long^3^{tree}\" &&\n>>> + echo ignored >.git/expect &&\n>>> + git show \"$short^3:create-ignored\" >.git/actual &&\n>>> + test_cmp .git/expect .git/actual &&\n>>> + test_path_is_file create-untracked &&\n>>> + test_path_is_file create-ignored &&\n>>> +\n>>> + echo staged >staged &&\n>>> + git add staged &&\n>>> + echo modified >>tracked &&\n>>> + git diff >.git/before-worktree &&\n>>> + git diff --cached >.git/before-index &&\n>>> + git status --porcelain=v1 --ignored >.git/before-status &&\n>>> + test_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&\n>>> + STASH_ID=$(git stash create -a -- -create-message) &&\n>>> + git diff >.git/after-worktree &&\n>>> + git diff --cached >.git/after-index &&\n>>> + git status --porcelain=v1 --ignored >.git/after-status &&\n>>> + test_cmp .git/before-worktree .git/after-worktree &&\n>>> + test_cmp .git/before-index .git/after-index &&\n>>> + test_cmp .git/before-status .git/after-status &&\n>>> + test_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&\n>>> + echo \"On $branch: -create-message\" >.git/expect &&\n>>> + git show --pretty=%s -s \"$STASH_ID\" >.git/actual &&\n>>> + test_cmp .git/expect .git/actual\n>>> + )\n>>> +'\n>>> +\n>>> +test_expect_success 'stash create rejects unknown options' '\n>>> + test_expect_code 129 git stash create --unknown-option 2>err &&\n>>> + test_grep \"unknown option\" err\n>>> +'\n>>> +\n>>> test_expect_success 'stash branch - no stashes on stack, stash-like argument' '\n>>> git stash clear &&\n>>> test_when_finished \"git reset --hard HEAD\" &&\n\n\n"},{"id":"554198","messageId":"1d1d2c76-9981-44ec-8ea9-8f886d49a742@gmail.com","threadId":"66418","inReplyTo":"CANUHOw3gynMRGN7A-wOnL3PQtgFbMtsB2gyZxaq+0Z5bHp-H8A@mail.gmail.com","subject":"Re: [PATCH v2] stash: expose untracked modes in create","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-10-05T16:38:07Z","receivedAt":"2026-10-05T16:38:07Z","isPatch":true,"body":"Hi Kazumasa\n\nOn 05/10/2026 06:55, 重田一聖 wrote:\n> >  From that perspective, I can see three possible directions.\n> > 1. Keep extending `stash create`.\n> >     We could expose more of the existing `do_create_stash()`\n>     functionality through `stash create`, following the conventions of\n>     `stash push` for the creation-related options they have in common.\n> >     This seems implementable, but even with\n>     `PARSE_OPT_STOP_AT_NON_OPTION` it would change the handling of\n>     messages that begin with an option-like argument. Those would need\n>     explicit disambiguation, such as `--`.\n> >     There is also the pathspec question. If positional arguments\n>     continue to be joined to form the message, pathspecs need some other\n>     way to be distinguished from that message.\n\nIt is worth thinking about which options from \"push\" make sense with \"create\" as the latter is really aimed at scripts rather than users. I can see a script wanting to stash untracked files, but it may not make sense to add interactive options like \"--patch\" which sometimes [1] fails to clear the stashed changes from the worktree, that would be problematic for scripts. I wonder if we really need pathspec support, or if we do is \"--pathspec-from-file\" sufficient? I think it is fairly unlikely that the message is going to start with '-' so using PARSE_OPT_STOP_AT_NON_OPTION seems like a reasonable way forward to me. Adding \"-m/--message\" to match other commands that take a message would certainly make sense.\n\nThanks\n\nPhillip\n\n[1] This happens when a user edits a hunk that looks like\n    @@ -1 +1,4 @@\n    -A\n    +a\n    +b\n    +c\n    +d\n\n    to\n\n    @@ -1 +1,3 @@\n    -A\n    +a\n    +b\n    +d\n\n    To clear the stashed changes, we apply the hunk in reverse, so we\n    try to apply\n\n    @@ -1,3 +1 @@\n    -a\n    -b\n    -d\n    +A\n\n    to a file that looks like\n\n    a\n    b\n    c\n    d\n\n    which fails because the '-' lines do not match the content of the\n    file.\n\n> > 2. Add a new stash subcommand for the creation functionality.\n> >     This would leave the existing `stash create <message>` contract\n>     unchanged. Because the new command would not inherit `create`'s\n>     positional message grammar, its creation-related options and\n>     pathspec handling could follow conventions similar to `stash push`.\n> >     This preserves the existing `create` grammar while avoiding the need\n>     to fit additional creation capabilities into it. The trade-off is\n>     adding another public stash subcommand and its long-term maintenance\n>     cost.\n> > 3. Add something like `--create-only` to `git stash push`.\n> >     This would reuse the existing `push` option grammar without adding\n>     another subcommand.\n> >     I also read the 2019 discussion around `git stash push --snapshot`.\n>     One concern there was that approximately the same end state could\n>     already be obtained with `git stash push && git stash apply`.\n> >     I do not think that particular concern carries over directly here.\n>     `git stash create` already stops at object creation, but its public\n>     interface does not expose more of the creation capabilities already\n>     available in `do_create_stash()`. There is currently no public stash\n>     command that exposes those capabilities while retaining that\n>     create-only boundary.\n> >     That does not mean a similar result cannot be constructed by other\n>     means. The missing piece is a public interface to the existing stash\n>     creation machinery at that boundary.\n> >     Even so, there is still the separate question of whether `push` is\n>     the right place for a creation-only operation in the first place.\n>     The push-specific work around `do_create_stash()` would also need to\n>     be separated carefully.\n> > All three seem substantially broader than the original `-u` / `-a`\n> patch.\n> > If this is worth pursuing further, which of these directions seems the\n> most plausible? Also, is this the right thread to continue that design\n> discussion, or would it be better to discuss it separately?\n> > Thanks again for the guidance,\n> Kazumasa Shigeta\n> > On Fri, 2 Oct 2026 05:04:26 -0400, \"重田一聖\" <kazumasa.shigeta@kanamei.com> wrote:\n>> Hi Junio,\n>>\n>>> we would prefer to hear what the user visible implication of\n>>> \"passing 0\" is more than what mechanically is happening inside a\n>>> program.\n>>\n>> The user-visible effect is that stash create cannot currently include\n>> untracked or ignored files in the stash entry. If those are the only\n>> changes, it creates no entry at all, while stash push and save can\n>> include them with -u or -a as appropriate. I should have described that\n>> difference directly instead of starting from the include_untracked\n>> implementation detail.\n>>\n>>> ... was what you wanted to say, but I am not sure.\n>>\n>> Yes, exactly. I'll explain the backward-compatibility reason rather\n>> than the mechanics of parse_options().\n>>\n>>> You already said that with \"does not update, reset, or clean\".\n>>\n>> I'll drop that paragraph.\n>>\n>>> if you did not make a breaking change to the established convention,\n>>> is it worth saying?\n>>\n>> I don't think it adds anything here. I'll remove the exit-status\n>> discussion from the commit message as well.\n>>\n>>> adding tests for comprehensive coverage is not something to boast\n>>> about. Is it worth saying?\n>>\n>> I'll remove the test details from the commit message.\n>>\n>>> Why are we singling out only these two?\n>>\n>> I started by looking at the missing -u and -a support in create, and I\n>> think that led me to focus too narrowly on those two when considering\n>> the scope. I need to think more about whether this patch should remain\n>> limited to those two.\n>>\n>> Thanks,\n>> Kazumasa Shigeta\n>>\n>> On Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:\n>>> Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:\n>>>\n>>>> `git stash create` always passes zero for the include_untracked parameter\n>>>> of do_create_stash(), even though that helper already supports untracked\n>>>> and ignored files and stash push/save expose those modes as\n>>>> -u/--include-untracked and -a/--all.\n>>>\n>>> There may be no lies in what the above says, but we would prefer to\n>>> hear what the user visible implication of \"passing 0\" is more than\n>>> what mechanically is happening inside a program. For example:\n>>>\n>>> \"git stash create\", \"git stash push\", and \"git stash save\" are\n>>> commands that create a new stash entry. The latter two are also\n>>> responsible for storing the resulting stash entry to the reflog\n>>> of the \"refs/stash\" ref, but have options to control what is\n>>> included in the stash entry. Among these options, \"create\" only\n>>> supports the equivalent of \"-m <message.\" to record in the stash\n>>> entry. Most notably, \"-u\" and \"-a\" options are missing.\n>>>\n>>>> Teach create to accept the same options and pass the existing mode\n>>>> through. Unlike push/save, create continues to only create objects: it\n>>>> does not update refs/stash, reset the index, or clean the working tree.\n>>>\n>>> Sure. It is a very concise and good description of what we want to\n>>> do.\n>>>\n>>>> Use parse_options() for the new options and stop parsing at the first\n>>>> non-option message word. This keeps option-like tokens after the message\n>>>> as message text, while leading option-like arguments now follow Git's\n>>>> normal option parsing. In particular, unknown or malformed leading\n>>>> options are rejected instead of silently becoming a message, short\n>>>> options may be combined, and `--` can be used when a message itself\n>>>> begins with a dash.\n>>>\n>>> Why do we need to go into such a detail in the log message? What is\n>>> the above paragraph designed to convey to the reader? Again, it may\n>>> not be telling any lies, but it misses the point by being inconsiderate\n>>> to your readers. What you need to tell them is _WHY_ you chose to\n>>> use parse_options() in such a way. What were you trying to achieve?\n>>>\n>>> I am guessing that something along this line ...\n>>>\n>>> \"git stash create\" traditionally treated the rest of the command\n>>> line as a message. For example,\n>>>\n>>> $ git stash create adding -u option\n>>>\n>>> has always been a request to create a stash entry with the\n>>> string \"adding -u option\" as its message. We should not make it\n>>> trigger the \"-u\" (include untracked) behavior for backward\n>>> compatibility, by using parse_options() with stop-at-the-non-option\n>>> mode to forbid it from reordering the command line arguments.\n>>>\n>>> ... was what you wanted to say, but I am not sure.\n>>>\n>>> How much of all these verbiage was written by AI by the way? You'd\n>>> need to spend effort to make it readable to humans.\n>>>\n>>>> Keep create's existing no-change behavior: detect the usual no-change\n>>>> case before do_create_stash() refreshes and writes the index, and return\n>>>> success without printing an object name. If do_create_stash() still\n>>>> reports its internal \"nothing to create\" result, map that to create's\n>>>> public success status.\n>>>\n>>> You already said that with \"does not update, reset, or clean\".\n>>>\n>>>> This follows the stash subcommand exit-status convention established by\n>>>> 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):\n>>>> subcommands return 0 on success, negative values on failure, and status 1\n>>>> when applying a stash results in conflicts. cmd_stash() maps negative\n>>>> subcommand failures to 128.\n>>>\n>>> Again, there may not be lies in here, but if you did not make a\n>>> breaking change to the established convention, is it worth saying?\n>>>\n>>>> 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the\n>>>> internal include-untracked path while intentionally leaving the user\n>>>> interface for \"git stash create\" unchanged. Reuse that machinery and\n>>>> the existing INCLUDE_ALL_FILES mode rather than adding a separate stash\n>>>> creation path.\n>>>>\n>>>> Add coverage for short and long aliases, combined short options, the\n>>>> untracked/ignored boundary including an ignored-only worktree, option\n>>>> parsing and dash-leading messages, no-change behavior, and preservation\n>>>> of refs/stash, the index state, and the working tree.\n>>>\n>>> Again, adding tests for comprehensive coverage is not something to\n>>> boast about. Is it worth saying?\n>>>\n>>> Aren't -p/-S/-k/-q and pathspec support all about the creating half\n>>> of \"git stash push\" that are not available to \"git stash create\",\n>>> not just \"-u\" and \"-a\"? Why are we singling out only these two? It\n>>> may be more worthwhile to explain the rationale behind such a design\n>>> decision.\n\n\n"},{"id":"554200","messageId":"xmqq1pa4ksiw.fsf@gitster.g","threadId":"66418","inReplyTo":"CANUHOw3gynMRGN7A-wOnL3PQtgFbMtsB2gyZxaq+0Z5bHp-H8A@mail.gmail.com","subject":"Re: [PATCH v2] stash: expose untracked modes in create","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-10-05T16:43:03Z","receivedAt":"2026-10-05T16:43:03Z","isPatch":true,"body":"重田一聖 <kazumasa.shigeta@kanamei.com> writes:\n\n> Your question made me realize that I had focused too narrowly on the\n> untracked modes. The larger issue is not simply that `do_create_stash()`\n> has capabilities that `git stash create` does not expose.\n\nBrilliant.  I agree that is the right way to frame the issue.\n\n> Making more of those capabilities available through `create` would\n> therefore mean either changing that contract or designing around it.\n> That is a much larger interface decision than I appreciated when I sent\n> the patch.\n\nPerhaps, but I do not think it is too huge a backward-compatibility\nbreakage to forbid giving a message lazily (i.e., all strings in\nargv[] after 'git stash create' gets concatenated and becomes a\nsingle message) that begins with \"-\", with an escape hatch that a\nleading \"-m\" will take the next argv[] element as the message, for\nexample.\n\n> 2. Add a new stash subcommand for the creation functionality.\n\nThis is essentially how 'git stash save' came about, to give us ways\nto control how a new stash entry is created and how the working tree\nis cleared with command line options.  In the beginning, you did not\neven have to say 'save', because 'git stash <message>' was invented\nas a way to say \"the boss is here and tells me to work on something\nunrelated. clear the slate with minimum number of keystrokes to\ncontinue working on what I have been working on later.\"  And that\nlater became 'git stash push'.\n\n> 3. Add something like `--create-only` to `git stash push`.\n\nThis also would work and sounds the safest.\n\n"},{"id":"554253","messageId":"CANUHOw2OMHFJLKWkDkyDm7WtLDcYxMnNfkD8uV96GZdWBS53RA@mail.gmail.com","threadId":"66418","inReplyTo":"1d1d2c76-9981-44ec-8ea9-8f886d49a742@gmail.com","subject":"Re: [PATCH v2] stash: expose untracked modes in create","fromName":"重田一聖","fromEmail":"kazumasa.shigeta@kanamei.com","sentAt":"2026-10-06T09:25:13Z","receivedAt":"2026-10-06T09:25:13Z","isPatch":true,"body":"Hi Phillip,\n\nThanks for the two patches removing the duplicate changes checks. I'll\nwait for those to settle before revisiting the exit-status and no-change\nhandling.\n\n> I can see a script wanting to stash untracked files, but it may not make\n> sense to add interactive options like \"--patch\" which sometimes [1]\n> fails to clear the stashed changes from the worktree, that would be\n> problematic for scripts.\n\nFor `stash create`, I don't think the issue in [1] should apply, since\nit does not remove the selected changes from the worktree. I still need\nto think about whether `--patch` is worth supporting for `stash create`,\neven though it is primarily aimed at scripts.\n\n> I wonder if we really need pathspec support, or if we do is\n> \"--pathspec-from-file\" sufficient?\n\nI agree that positional pathspec support probably isn't necessary.\nSince `create` is primarily aimed at scripts, `--pathspec-from-file`\nseems sufficient. It also avoids giving positional arguments another\nmeaning while we are already dealing with the message ambiguity.\n\n> I think it is fairly unlikely that the message is going to start with\n> '-' so using PARSE_OPT_STOP_AT_NON_OPTION seems like a reasonable way\n> forward to me. Adding \"-m/--message\" to match other commands that take\n> a message would certainly make sense.\n\nThanks for confirming those points.\n\nThanks,\nKazumasa\n\nOn Mon, 5 Oct 2026 17:38:07 +0100, Phillip Wood\n<phillip.wood123@gmail.com> wrote:\n> Hi Kazumasa\n>\n> On 05/10/2026 06:55, 重田一聖 wrote:\n> >\n> > From that perspective, I can see three possible directions.\n> >\n> > 1. Keep extending `stash create`.\n> >\n> > We could expose more of the existing `do_create_stash()`\n> > functionality through `stash create`, following the conventions of\n> > `stash push` for the creation-related options they have in common.\n> >\n> > This seems implementable, but even with\n> > `PARSE_OPT_STOP_AT_NON_OPTION` it would change the handling of\n> > messages that begin with an option-like argument. Those would need\n> > explicit disambiguation, such as `--`.\n> >\n> > There is also the pathspec question. If positional arguments\n> > continue to be joined to form the message, pathspecs need some other\n> > way to be distinguished from that message.\n>\n> It is worth thinking about which options from \"push\" make sense with\n> \"create\" as the latter is really aimed at scripts rather than users. I\n> can see a script wanting to stash untracked files, but it may not make\n> sense to add interactive options like \"--patch\" which sometimes [1]\n> fails to clear the stashed changes from the worktree, that would be\n> problematic for scripts. I wonder if we really need pathspec support, or\n> if we do is \"--pathspec-from-file\" sufficient? I think it is fairly\n> unlikely that the message is going to start with '-' so using\n> PARSE_OPT_STOP_AT_NON_OPTION seems like a reasonable way forward to me.\n> Adding \"-m/--message\" to match other commands that take a message would\n> certainly make sense.\n>\n> Thanks\n>\n> Phillip\n>\n> [1] This happens when a user edits a hunk that looks like\n> @@ -1 +1,4 @@\n> -A\n> +a\n> +b\n> +c\n> +d\n>\n> to\n>\n> @@ -1 +1,3 @@\n> -A\n> +a\n> +b\n> +d\n>\n> To clear the stashed changes, we apply the hunk in reverse, so we\n> try to apply\n>\n> @@ -1,3 +1 @@\n> -a\n> -b\n> -d\n> +A\n>\n> to a file that looks like\n>\n> a\n> b\n> c\n> d\n>\n> which fails because the '-' lines do not match the content of the\n> file.\n>\n> >\n> > 2. Add a new stash subcommand for the creation functionality.\n> >\n> > This would leave the existing `stash create <message>` contract\n> > unchanged. Because the new command would not inherit `create`'s\n> > positional message grammar, its creation-related options and\n> > pathspec handling could follow conventions similar to `stash push`.\n> >\n> > This preserves the existing `create` grammar while avoiding the need\n> > to fit additional creation capabilities into it. The trade-off is\n> > adding another public stash subcommand and its long-term maintenance\n> > cost.\n> >\n> > 3. Add something like `--create-only` to `git stash push`.\n> >\n> > This would reuse the existing `push` option grammar without adding\n> > another subcommand.\n> >\n> > I also read the 2019 discussion around `git stash push --snapshot`.\n> > One concern there was that approximately the same end state could\n> > already be obtained with `git stash push && git stash apply`.\n> >\n> > I do not think that particular concern carries over directly here.\n> > `git stash create` already stops at object creation, but its public\n> > interface does not expose more of the creation capabilities already\n> > available in `do_create_stash()`. There is currently no public stash\n> > command that exposes those capabilities while retaining that\n> > create-only boundary.\n> >\n> > That does not mean a similar result cannot be constructed by other\n> > means. The missing piece is a public interface to the existing stash\n> > creation machinery at that boundary.\n> >\n> > Even so, there is still the separate question of whether `push` is\n> > the right place for a creation-only operation in the first place.\n> > The push-specific work around `do_create_stash()` would also need to\n> > be separated carefully.\n> >\n> > All three seem substantially broader than the original `-u` / `-a`\n> > patch.\n> >\n> > If this is worth pursuing further, which of these directions seems the\n> > most plausible? Also, is this the right thread to continue that design\n> > discussion, or would it be better to discuss it separately?\n> >\n> > Thanks again for the guidance,\n> > Kazumasa Shigeta\n> >\n> > On Fri, 2 Oct 2026 05:04:26 -0400, \"重田一聖\" <kazumasa.shigeta@kanamei.com> wrote:\n> >> Hi Junio,\n> >>\n> >>> we would prefer to hear what the user visible implication of\n> >>> \"passing 0\" is more than what mechanically is happening inside a\n> >>> program.\n> >>\n> >> The user-visible effect is that stash create cannot currently include\n> >> untracked or ignored files in the stash entry. If those are the only\n> >> changes, it creates no entry at all, while stash push and save can\n> >> include them with -u or -a as appropriate. I should have described that\n> >> difference directly instead of starting from the include_untracked\n> >> implementation detail.\n> >>\n> >>> ... was what you wanted to say, but I am not sure.\n> >>\n> >> Yes, exactly. I'll explain the backward-compatibility reason rather\n> >> than the mechanics of parse_options().\n> >>\n> >>> You already said that with \"does not update, reset, or clean\".\n> >>\n> >> I'll drop that paragraph.\n> >>\n> >>> if you did not make a breaking change to the established convention,\n> >>> is it worth saying?\n> >>\n> >> I don't think it adds anything here. I'll remove the exit-status\n> >> discussion from the commit message as well.\n> >>\n> >>> adding tests for comprehensive coverage is not something to boast\n> >>> about. Is it worth saying?\n> >>\n> >> I'll remove the test details from the commit message.\n> >>\n> >>> Why are we singling out only these two?\n> >>\n> >> I started by looking at the missing -u and -a support in create, and I\n> >> think that led me to focus too narrowly on those two when considering\n> >> the scope. I need to think more about whether this patch should remain\n> >> limited to those two.\n> >>\n> >> Thanks,\n> >> Kazumasa Shigeta\n> >>\n> >> On Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> >>> Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:\n> >>>\n> >>>> `git stash create` always passes zero for the include_untracked parameter\n> >>>> of do_create_stash(), even though that helper already supports untracked\n> >>>> and ignored files and stash push/save expose those modes as\n> >>>> -u/--include-untracked and -a/--all.\n> >>>\n> >>> There may be no lies in what the above says, but we would prefer to\n> >>> hear what the user visible implication of \"passing 0\" is more than\n> >>> what mechanically is happening inside a program. For example:\n> >>>\n> >>> \"git stash create\", \"git stash push\", and \"git stash save\" are\n> >>> commands that create a new stash entry. The latter two are also\n> >>> responsible for storing the resulting stash entry to the reflog\n> >>> of the \"refs/stash\" ref, but have options to control what is\n> >>> included in the stash entry. Among these options, \"create\" only\n> >>> supports the equivalent of \"-m <message.\" to record in the stash\n> >>> entry. Most notably, \"-u\" and \"-a\" options are missing.\n> >>>\n> >>>> Teach create to accept the same options and pass the existing mode\n> >>>> through. Unlike push/save, create continues to only create objects: it\n> >>>> does not update refs/stash, reset the index, or clean the working tree.\n> >>>\n> >>> Sure. It is a very concise and good description of what we want to\n> >>> do.\n> >>>\n> >>>> Use parse_options() for the new options and stop parsing at the first\n> >>>> non-option message word. This keeps option-like tokens after the message\n> >>>> as message text, while leading option-like arguments now follow Git's\n> >>>> normal option parsing. In particular, unknown or malformed leading\n> >>>> options are rejected instead of silently becoming a message, short\n> >>>> options may be combined, and `--` can be used when a message itself\n> >>>> begins with a dash.\n> >>>\n> >>> Why do we need to go into such a detail in the log message? What is\n> >>> the above paragraph designed to convey to the reader? Again, it may\n> >>> not be telling any lies, but it misses the point by being inconsiderate\n> >>> to your readers. What you need to tell them is _WHY_ you chose to\n> >>> use parse_options() in such a way. What were you trying to achieve?\n> >>>\n> >>> I am guessing that something along this line ...\n> >>>\n> >>> \"git stash create\" traditionally treated the rest of the command\n> >>> line as a message. For example,\n> >>>\n> >>> $ git stash create adding -u option\n> >>>\n> >>> has always been a request to create a stash entry with the\n> >>> string \"adding -u option\" as its message. We should not make it\n> >>> trigger the \"-u\" (include untracked) behavior for backward\n> >>> compatibility, by using parse_options() with stop-at-the-non-option\n> >>> mode to forbid it from reordering the command line arguments.\n> >>>\n> >>> ... was what you wanted to say, but I am not sure.\n> >>>\n> >>> How much of all these verbiage was written by AI by the way? You'd\n> >>> need to spend effort to make it readable to humans.\n> >>>\n> >>>> Keep create's existing no-change behavior: detect the usual no-change\n> >>>> case before do_create_stash() refreshes and writes the index, and return\n> >>>> success without printing an object name. If do_create_stash() still\n> >>>> reports its internal \"nothing to create\" result, map that to create's\n> >>>> public success status.\n> >>>\n> >>> You already said that with \"does not update, reset, or clean\".\n> >>>\n> >>>> This follows the stash subcommand exit-status convention established by\n> >>>> 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):\n> >>>> subcommands return 0 on success, negative values on failure, and status 1\n> >>>> when applying a stash results in conflicts. cmd_stash() maps negative\n> >>>> subcommand failures to 128.\n> >>>\n> >>> Again, there may not be lies in here, but if you did not make a\n> >>> breaking change to the established convention, is it worth saying?\n> >>>\n> >>>> 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the\n> >>>> internal include-untracked path while intentionally leaving the user\n> >>>> interface for \"git stash create\" unchanged. Reuse that machinery and\n> >>>> the existing INCLUDE_ALL_FILES mode rather than adding a separate stash\n> >>>> creation path.\n> >>>>\n> >>>> Add coverage for short and long aliases, combined short options, the\n> >>>> untracked/ignored boundary including an ignored-only worktree, option\n> >>>> parsing and dash-leading messages, no-change behavior, and preservation\n> >>>> of refs/stash, the index state, and the working tree.\n> >>>\n> >>> Again, adding tests for comprehensive coverage is not something to\n> >>> boast about. Is it worth saying?\n> >>>\n> >>> Aren't -p/-S/-k/-q and pathspec support all about the creating half\n> >>> of \"git stash push\" that are not available to \"git stash create\",\n> >>> not just \"-u\" and \"-a\"? Why are we singling out only these two? It\n> >>> may be more worthwhile to explain the rationale behind such a design\n> >>> decision.\n\n"},{"id":"554259","messageId":"7af72eb3-9a61-43c8-a9c0-faaff1817949@gmail.com","threadId":"66418","inReplyTo":"CANUHOw2OMHFJLKWkDkyDm7WtLDcYxMnNfkD8uV96GZdWBS53RA@mail.gmail.com","subject":"Re: [PATCH v2] stash: expose untracked modes in create","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-10-06T09:57:58Z","receivedAt":"2026-10-06T09:57:58Z","isPatch":true,"body":"Hi Kazumasa\n\nOn 06/10/2026 10:25, 重田一聖 wrote:\n> Hi Phillip,\n> > Thanks for the two patches removing the duplicate changes checks. I'll\n> wait for those to settle before revisiting the exit-status and no-change\n> handling.\n> >> I can see a script wanting to stash untracked files, but it may not make\n>> sense to add interactive options like \"--patch\" which sometimes [1]\n>> fails to clear the stashed changes from the worktree, that would be\n>> problematic for scripts.\n> > For `stash create`, I don't think the issue in [1] should apply, since\n> it does not remove the selected changes from the worktree. Oh, good point, I'd completely forgotten that when I was writing yesterday. If the script wants to remove the changes from the worktree it still faces the same problem. As \"git stash create\" does not remove the stashed changes from the worktree it would probably be simplest to not support \"--patch\" or \"pathspecs\" so that the script can easily remove the stashed changes with \"git read-tree -m -u HEAD\" (together with \"git clean\" if it is stashing untracked files). If someone has a use for pathspec support we can think about adding it but \"git stash create\" has existed for 20 years without anyone requesting it.\n\nThanks\n\nPhillip\n> I still need\n> to think about whether `--patch` is worth supporting for `stash create`,\n> even though it is primarily aimed at scripts.\n> >> I wonder if we really need pathspec support, or if we do is\n>> \"--pathspec-from-file\" sufficient?\n> > I agree that positional pathspec support probably isn't necessary.\n> Since `create` is primarily aimed at scripts, `--pathspec-from-file`\n> seems sufficient. It also avoids giving positional arguments another\n> meaning while we are already dealing with the message ambiguity.\n> >> I think it is fairly unlikely that the message is going to start with\n>> '-' so using PARSE_OPT_STOP_AT_NON_OPTION seems like a reasonable way\n>> forward to me. Adding \"-m/--message\" to match other commands that take\n>> a message would certainly make sense.\n> > Thanks for confirming those points.\n> > Thanks,\n> Kazumasa\n> > On Mon, 5 Oct 2026 17:38:07 +0100, Phillip Wood\n> <phillip.wood123@gmail.com> wrote:\n>> Hi Kazumasa\n>>\n>> On 05/10/2026 06:55, 重田一聖 wrote:\n>>>\n>>>  From that perspective, I can see three possible directions.\n>>>\n>>> 1. Keep extending `stash create`.\n>>>\n>>> We could expose more of the existing `do_create_stash()`\n>>> functionality through `stash create`, following the conventions of\n>>> `stash push` for the creation-related options they have in common.\n>>>\n>>> This seems implementable, but even with\n>>> `PARSE_OPT_STOP_AT_NON_OPTION` it would change the handling of\n>>> messages that begin with an option-like argument. Those would need\n>>> explicit disambiguation, such as `--`.\n>>>\n>>> There is also the pathspec question. If positional arguments\n>>> continue to be joined to form the message, pathspecs need some other\n>>> way to be distinguished from that message.\n>>\n>> It is worth thinking about which options from \"push\" make sense with\n>> \"create\" as the latter is really aimed at scripts rather than users. I\n>> can see a script wanting to stash untracked files, but it may not make\n>> sense to add interactive options like \"--patch\" which sometimes [1]\n>> fails to clear the stashed changes from the worktree, that would be\n>> problematic for scripts. I wonder if we really need pathspec support, or\n>> if we do is \"--pathspec-from-file\" sufficient? I think it is fairly\n>> unlikely that the message is going to start with '-' so using\n>> PARSE_OPT_STOP_AT_NON_OPTION seems like a reasonable way forward to me.\n>> Adding \"-m/--message\" to match other commands that take a message would\n>> certainly make sense.\n>>\n>> Thanks\n>>\n>> Phillip\n>>\n>> [1] This happens when a user edits a hunk that looks like\n>> @@ -1 +1,4 @@\n>> -A\n>> +a\n>> +b\n>> +c\n>> +d\n>>\n>> to\n>>\n>> @@ -1 +1,3 @@\n>> -A\n>> +a\n>> +b\n>> +d\n>>\n>> To clear the stashed changes, we apply the hunk in reverse, so we\n>> try to apply\n>>\n>> @@ -1,3 +1 @@\n>> -a\n>> -b\n>> -d\n>> +A\n>>\n>> to a file that looks like\n>>\n>> a\n>> b\n>> c\n>> d\n>>\n>> which fails because the '-' lines do not match the content of the\n>> file.\n>>\n>>>\n>>> 2. Add a new stash subcommand for the creation functionality.\n>>>\n>>> This would leave the existing `stash create <message>` contract\n>>> unchanged. Because the new command would not inherit `create`'s\n>>> positional message grammar, its creation-related options and\n>>> pathspec handling could follow conventions similar to `stash push`.\n>>>\n>>> This preserves the existing `create` grammar while avoiding the need\n>>> to fit additional creation capabilities into it. The trade-off is\n>>> adding another public stash subcommand and its long-term maintenance\n>>> cost.\n>>>\n>>> 3. Add something like `--create-only` to `git stash push`.\n>>>\n>>> This would reuse the existing `push` option grammar without adding\n>>> another subcommand.\n>>>\n>>> I also read the 2019 discussion around `git stash push --snapshot`.\n>>> One concern there was that approximately the same end state could\n>>> already be obtained with `git stash push && git stash apply`.\n>>>\n>>> I do not think that particular concern carries over directly here.\n>>> `git stash create` already stops at object creation, but its public\n>>> interface does not expose more of the creation capabilities already\n>>> available in `do_create_stash()`. There is currently no public stash\n>>> command that exposes those capabilities while retaining that\n>>> create-only boundary.\n>>>\n>>> That does not mean a similar result cannot be constructed by other\n>>> means. The missing piece is a public interface to the existing stash\n>>> creation machinery at that boundary.\n>>>\n>>> Even so, there is still the separate question of whether `push` is\n>>> the right place for a creation-only operation in the first place.\n>>> The push-specific work around `do_create_stash()` would also need to\n>>> be separated carefully.\n>>>\n>>> All three seem substantially broader than the original `-u` / `-a`\n>>> patch.\n>>>\n>>> If this is worth pursuing further, which of these directions seems the\n>>> most plausible? Also, is this the right thread to continue that design\n>>> discussion, or would it be better to discuss it separately?\n>>>\n>>> Thanks again for the guidance,\n>>> Kazumasa Shigeta\n>>>\n>>> On Fri, 2 Oct 2026 05:04:26 -0400, \"重田一聖\" <kazumasa.shigeta@kanamei.com> wrote:\n>>>> Hi Junio,\n>>>>\n>>>>> we would prefer to hear what the user visible implication of\n>>>>> \"passing 0\" is more than what mechanically is happening inside a\n>>>>> program.\n>>>>\n>>>> The user-visible effect is that stash create cannot currently include\n>>>> untracked or ignored files in the stash entry. If those are the only\n>>>> changes, it creates no entry at all, while stash push and save can\n>>>> include them with -u or -a as appropriate. I should have described that\n>>>> difference directly instead of starting from the include_untracked\n>>>> implementation detail.\n>>>>\n>>>>> ... was what you wanted to say, but I am not sure.\n>>>>\n>>>> Yes, exactly. I'll explain the backward-compatibility reason rather\n>>>> than the mechanics of parse_options().\n>>>>\n>>>>> You already said that with \"does not update, reset, or clean\".\n>>>>\n>>>> I'll drop that paragraph.\n>>>>\n>>>>> if you did not make a breaking change to the established convention,\n>>>>> is it worth saying?\n>>>>\n>>>> I don't think it adds anything here. I'll remove the exit-status\n>>>> discussion from the commit message as well.\n>>>>\n>>>>> adding tests for comprehensive coverage is not something to boast\n>>>>> about. Is it worth saying?\n>>>>\n>>>> I'll remove the test details from the commit message.\n>>>>\n>>>>> Why are we singling out only these two?\n>>>>\n>>>> I started by looking at the missing -u and -a support in create, and I\n>>>> think that led me to focus too narrowly on those two when considering\n>>>> the scope. I need to think more about whether this patch should remain\n>>>> limited to those two.\n>>>>\n>>>> Thanks,\n>>>> Kazumasa Shigeta\n>>>>\n>>>> On Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:\n>>>>> Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:\n>>>>>\n>>>>>> `git stash create` always passes zero for the include_untracked parameter\n>>>>>> of do_create_stash(), even though that helper already supports untracked\n>>>>>> and ignored files and stash push/save expose those modes as\n>>>>>> -u/--include-untracked and -a/--all.\n>>>>>\n>>>>> There may be no lies in what the above says, but we would prefer to\n>>>>> hear what the user visible implication of \"passing 0\" is more than\n>>>>> what mechanically is happening inside a program. For example:\n>>>>>\n>>>>> \"git stash create\", \"git stash push\", and \"git stash save\" are\n>>>>> commands that create a new stash entry. The latter two are also\n>>>>> responsible for storing the resulting stash entry to the reflog\n>>>>> of the \"refs/stash\" ref, but have options to control what is\n>>>>> included in the stash entry. Among these options, \"create\" only\n>>>>> supports the equivalent of \"-m <message.\" to record in the stash\n>>>>> entry. Most notably, \"-u\" and \"-a\" options are missing.\n>>>>>\n>>>>>> Teach create to accept the same options and pass the existing mode\n>>>>>> through. Unlike push/save, create continues to only create objects: it\n>>>>>> does not update refs/stash, reset the index, or clean the working tree.\n>>>>>\n>>>>> Sure. It is a very concise and good description of what we want to\n>>>>> do.\n>>>>>\n>>>>>> Use parse_options() for the new options and stop parsing at the first\n>>>>>> non-option message word. This keeps option-like tokens after the message\n>>>>>> as message text, while leading option-like arguments now follow Git's\n>>>>>> normal option parsing. In particular, unknown or malformed leading\n>>>>>> options are rejected instead of silently becoming a message, short\n>>>>>> options may be combined, and `--` can be used when a message itself\n>>>>>> begins with a dash.\n>>>>>\n>>>>> Why do we need to go into such a detail in the log message? What is\n>>>>> the above paragraph designed to convey to the reader? Again, it may\n>>>>> not be telling any lies, but it misses the point by being inconsiderate\n>>>>> to your readers. What you need to tell them is _WHY_ you chose to\n>>>>> use parse_options() in such a way. What were you trying to achieve?\n>>>>>\n>>>>> I am guessing that something along this line ...\n>>>>>\n>>>>> \"git stash create\" traditionally treated the rest of the command\n>>>>> line as a message. For example,\n>>>>>\n>>>>> $ git stash create adding -u option\n>>>>>\n>>>>> has always been a request to create a stash entry with the\n>>>>> string \"adding -u option\" as its message. We should not make it\n>>>>> trigger the \"-u\" (include untracked) behavior for backward\n>>>>> compatibility, by using parse_options() with stop-at-the-non-option\n>>>>> mode to forbid it from reordering the command line arguments.\n>>>>>\n>>>>> ... was what you wanted to say, but I am not sure.\n>>>>>\n>>>>> How much of all these verbiage was written by AI by the way? You'd\n>>>>> need to spend effort to make it readable to humans.\n>>>>>\n>>>>>> Keep create's existing no-change behavior: detect the usual no-change\n>>>>>> case before do_create_stash() refreshes and writes the index, and return\n>>>>>> success without printing an object name. If do_create_stash() still\n>>>>>> reports its internal \"nothing to create\" result, map that to create's\n>>>>>> public success status.\n>>>>>\n>>>>> You already said that with \"does not update, reset, or clean\".\n>>>>>\n>>>>>> This follows the stash subcommand exit-status convention established by\n>>>>>> 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):\n>>>>>> subcommands return 0 on success, negative values on failure, and status 1\n>>>>>> when applying a stash results in conflicts. cmd_stash() maps negative\n>>>>>> subcommand failures to 128.\n>>>>>\n>>>>> Again, there may not be lies in here, but if you did not make a\n>>>>> breaking change to the established convention, is it worth saying?\n>>>>>\n>>>>>> 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the\n>>>>>> internal include-untracked path while intentionally leaving the user\n>>>>>> interface for \"git stash create\" unchanged. Reuse that machinery and\n>>>>>> the existing INCLUDE_ALL_FILES mode rather than adding a separate stash\n>>>>>> creation path.\n>>>>>>\n>>>>>> Add coverage for short and long aliases, combined short options, the\n>>>>>> untracked/ignored boundary including an ignored-only worktree, option\n>>>>>> parsing and dash-leading messages, no-change behavior, and preservation\n>>>>>> of refs/stash, the index state, and the working tree.\n>>>>>\n>>>>> Again, adding tests for comprehensive coverage is not something to\n>>>>> boast about. Is it worth saying?\n>>>>>\n>>>>> Aren't -p/-S/-k/-q and pathspec support all about the creating half\n>>>>> of \"git stash push\" that are not available to \"git stash create\",\n>>>>> not just \"-u\" and \"-a\"? Why are we singling out only these two? It\n>>>>> may be more worthwhile to explain the rationale behind such a design\n>>>>> decision.\n\n\n"}]}