# [PATCH] stash: expose untracked modes in create

16 messages from 2026-09-29 to 2026-10-06. Participants: Kazumasa Shigeta, Phillip Wood, Patrick Steinhardt, 重田一聖, Junio C Hamano.
Thread: https://gitlist.dev/t/66418

## Kazumasa Shigeta, 2026-09-29 07:42

Subject: [PATCH] stash: expose untracked modes in create
Message-ID: <20260929074222.11942-1-kazumasa.shigeta@kanamei.com>

```
`git stash create` always passes zero for the include_untracked parameter
of do_create_stash(), even though that helper already supports untracked
and ignored files and stash push/save expose those modes as
-u/--include-untracked and -a/--all.

Teach create to accept the same options and pass the existing mode
through. Unlike push/save, create continues to only create objects: it
does not update refs/stash or modify the index or working tree.

When the selected mode finds no changes, do_create_stash() returns 1.
Translate that to success so create keeps its existing no-object, empty
output behavior.

Use normal parse-options semantics, so options may appear after message
arguments. A message that begins with a dash can be disambiguated with
--.

9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the
internal include-untracked path while intentionally leaving the user
interface for "git stash create" unchanged. Reuse that machinery and
the existing INCLUDE_ALL_FILES mode rather than adding a separate stash
creation path.

Add coverage for both short and long aliases, the untracked/ignored
boundary, option/message parsing, no-change behavior, and preservation
of refs/stash, the index, and the working tree.

Signed-off-by: Kazumasa Shigeta <kazumasa.shigeta@kanamei.com>
---
Related work:

I proposed adding both --include-untracked and --all to
"git stash create" in 2014:
  <1403856479-37421-1-git-send-email-shigeta@kanamei.co.jp>

I should also apologize for dropping that thread after receiving review.
I did not follow up on the comments at the time.  Thanks to those who
reviewed it then.

Separately, in 2017, Thomas Gummerer added an internal -u path while
refactoring stash_create in 9ca6326dff29 (stash: refactor stash_create).
That change explicitly kept the user interface of "git stash create"
unchanged.

When "stash create" was later converted to the builtin C implementation
in d4788af875cc (stash: convert create to builtin), the untracked-file
handling was carried into the new implementation and remains there today.

More recently, Shabbir Bhojani proposed exposing --include-untracked:
  <pull.1892.git.1774768580147.gitgitgadget@gmail.com>

This patch exposes both existing untracked modes, --include-untracked and
--all, to "git stash create".

 Documentation/git-stash.adoc | 18 ++++++----
 builtin/stash.c              | 36 ++++++++++++++-----
 t/t3903-stash.sh             | 70 ++++++++++++++++++++++++++++++++++++
 3 files changed, 109 insertions(+), 15 deletions(-)

diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc
index fc6a9a0..32f0fd5 100644
--- a/Documentation/git-stash.adoc
+++ b/Documentation/git-stash.adoc
@@ -21,7 +21,7 @@ git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | -
 git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]
            [-u | --include-untracked] [-a | --all] [<message>]
 git stash clear
-git stash create [<message>]
+git stash create [-u | --include-untracked] [-a | --all] [<message>]
 git stash store [(-m | --message) <message>] [-q | --quiet] <commit>
 git stash export (--print | --to-ref <ref>) [<stash>...]
 git stash import <commit>
@@ -138,10 +138,12 @@ with no conflicts.
 `drop [-q | --quiet] [<stash>]`::
 	Remove a single stash entry from the list of stash entries.
 
-`create`::
+`create [-u | --include-untracked] [-a | --all]`::
 	Create a stash entry (which is a regular commit object) and
 	return its object name, without storing it anywhere in the ref
-	namespace.
+	namespace.  The `--include-untracked` option includes untracked
+	files, while `--all` also includes ignored files, without modifying
+	the working tree.
 	This is intended to be useful for scripts.  It is probably not
 	the command you want to use; see "push" above.
 
@@ -167,10 +169,11 @@ OPTIONS
 -------
 `-a`::
 `--all`::
-	This option is only valid for `push` and `save` commands.
+	When used with the `push` and `save` commands, all ignored and
+	untracked files are also stashed and then cleaned up with `git clean`.
 +
-All ignored and untracked files are also stashed and then cleaned
-up with `git clean`.
+When used with the `create` command, ignored and untracked files are included
+in the stash entry without modifying the working tree.
 
 `-u`::
 `--include-untracked`::
@@ -179,6 +182,9 @@ up with `git clean`.
 	all untracked files are also stashed and then cleaned up with
 	`git clean`.
 +
+When used with the `create` command, untracked files are included in the
+stash entry without modifying the working tree.
++
 When used with the `show` command, show the untracked files in the stash
 entry as part of the diff.
 
diff --git a/builtin/stash.c b/builtin/stash.c
index 7a98434..57a4750 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -59,7 +59,7 @@
 	N_("git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n" \
 	   "          [-u | --include-untracked] [-a | --all] [<message>]")
 #define BUILTIN_STASH_CREATE_USAGE \
-	N_("git stash create [<message>]")
+	N_("git stash create [-u | --include-untracked] [-a | --all] [<message>]")
 #define BUILTIN_STASH_EXPORT_USAGE \
 	N_("git stash export (--print | --to-ref <ref>) [<stash>...]")
 #define BUILTIN_STASH_IMPORT_USAGE \
@@ -119,6 +119,11 @@ static const char * const git_stash_clear_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_create_usage[] = {
+	BUILTIN_STASH_CREATE_USAGE,
+	NULL
+};
+
 static const char * const git_stash_store_usage[] = {
 	BUILTIN_STASH_STORE_USAGE,
 	NULL
@@ -1643,26 +1648,39 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
 	return ret;
 }
 
-static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
+static int create_stash(int argc, const char **argv, const char *prefix,
 			struct repository *repo UNUSED)
 {
-	int ret;
+	int ret = 0;
+	int include_untracked = 0;
+	struct option options[] = {
+		OPT_BOOL('u', "include-untracked", &include_untracked,
+			 N_("include untracked files in stash")),
+		OPT_SET_INT('a', "all", &include_untracked,
+			    N_("include ignored files in stash"),
+			    INCLUDE_ALL_FILES),
+		OPT_END()
+	};
 	struct strbuf stash_msg_buf = STRBUF_INIT;
 	struct stash_info info = STASH_INFO_INIT;
 	struct pathspec ps;
 
-	/* Starting with argv[1], since argv[0] is "create" */
-	strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_create_usage, 0);
+	strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
-	if (!check_changes_tracked_files(&ps))
-		return 0;
+	if (!include_untracked && !check_changes_tracked_files(&ps))
+		goto done;
 
-	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,
-			      NULL, 0);
+	ret = do_create_stash(&ps, &stash_msg_buf, include_untracked, 0, NULL,
+			      0, &info, NULL, 0);
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
+	else if (ret == 1)
+		ret = 0;
 
+done:
 	free_stash_info(&info);
 	strbuf_release(&stash_msg_buf);
 	return ret;
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 7211586..fe34879 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -640,6 +640,76 @@ test_expect_success 'stash create - no changes' '
 	test_must_be_empty actual
 '
 
+# --all observes every untracked and ignored path in the worktree.  Use one
+# isolated repository for these checks so unrelated test state is not captured.
+test_expect_success 'stash create with untracked options' '
+	test_when_finished "rm -rf stash-create-options" &&
+	test_create_repo stash-create-options &&
+	(
+		cd stash-create-options &&
+		test_commit base tracked base &&
+		echo create-ignored >.gitignore &&
+		git add .gitignore &&
+		git commit -m ignore &&
+
+		git stash create -u >.git/actual &&
+		test_must_be_empty .git/actual &&
+		git stash create -a >.git/actual &&
+		test_must_be_empty .git/actual &&
+
+		echo untracked >create-untracked &&
+		git stash create "without untracked" >.git/actual &&
+		test_must_be_empty .git/actual &&
+		short=$(git stash create "create untracked" -u) &&
+		long=$(git stash create --include-untracked "create untracked") &&
+		test_cmp_rev "$short^3^{tree}" "$long^3^{tree}" &&
+		echo untracked >.git/expect &&
+		git show "$short^3:create-untracked" >.git/actual &&
+		test_cmp .git/expect .git/actual &&
+		branch=$(git symbolic-ref --short HEAD) &&
+		echo "On $branch: create untracked" >.git/expect &&
+		git show --pretty=%s -s "$short" >.git/actual &&
+		test_cmp .git/expect .git/actual &&
+		test_path_is_file create-untracked &&
+
+		echo ignored >create-ignored &&
+		with_untracked=$(git stash create -u "create options") &&
+		test_must_fail git cat-file -e "$with_untracked^3:create-ignored" &&
+		short=$(git stash create "create options" -a) &&
+		long=$(git stash create --all "create options") &&
+		test_cmp_rev "$short^3^{tree}" "$long^3^{tree}" &&
+		echo ignored >.git/expect &&
+		git show "$short^3:create-ignored" >.git/actual &&
+		test_cmp .git/expect .git/actual &&
+		test_path_is_file create-untracked &&
+		test_path_is_file create-ignored &&
+
+		echo staged >staged &&
+		git add staged &&
+		echo modified >>tracked &&
+		git diff >.git/before-worktree &&
+		git diff --cached >.git/before-index &&
+		git status --porcelain=v1 --ignored >.git/before-status &&
+		test_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&
+		STASH_ID=$(git stash create -a -- -create-message) &&
+		git diff >.git/after-worktree &&
+		git diff --cached >.git/after-index &&
+		git status --porcelain=v1 --ignored >.git/after-status &&
+		test_cmp .git/before-worktree .git/after-worktree &&
+		test_cmp .git/before-index .git/after-index &&
+		test_cmp .git/before-status .git/after-status &&
+		test_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&
+		echo "On $branch: -create-message" >.git/expect &&
+		git show --pretty=%s -s "$STASH_ID" >.git/actual &&
+		test_cmp .git/expect .git/actual
+	)
+'
+
+test_expect_success 'stash create rejects unknown options' '
+	test_expect_code 129 git stash create --unknown-option 2>err &&
+	test_grep "unknown option" err
+'
+
 test_expect_success 'stash branch - no stashes on stack, stash-like argument' '
 	git stash clear &&
 	test_when_finished "git reset --hard HEAD" &&
-- 
2.47.3


```

## Phillip Wood, 2026-09-29 16:08

Subject: Re: [PATCH] stash: expose untracked modes in create
Message-ID: <8453ebd1-77c1-4941-afbf-572f9e7b12c1@gmail.com>
In-Reply-To: <20260929074222.11942-1-kazumasa.shigeta@kanamei.com>

```
Hi Kazumasa

On 29/09/2026 08:42, Kazumasa Shigeta wrote:
> `git stash create` always passes zero for the include_untracked parameter
> of do_create_stash(), even though that helper already supports untracked
> and ignored files and stash push/save expose those modes as
> -u/--include-untracked and -a/--all.
> 
> Teach create to accept the same options and pass the existing mode
> through. Unlike push/save, create continues to only create objects: it
> does not update refs/stash or modify the index or working tree.
> 
> When the selected mode finds no changes, do_create_stash() returns 1.
> Translate that to success so create keeps its existing no-object, empty
> output behavior.

This doesn't seem to match the code changes. The code that prints the 
object id when the stash is successfully created is unchanged, as far as 
I can see what this patch does is change the exit status for "git stash 
create" when there are no changes to stash. Instead of exiting 1, it 
exits 0 even though it does not create a stash. That does not seem like 
a good idea.

> Use normal parse-options semantics, so options may appear after message
> arguments. A message that begins with a dash can be disambiguated with

As "git stash create" concatenates excess arguments to use as the stash 
message we should not be permuting options. "git stash create handle new 
-u flag" should continue to create a stash with the message "handle new 
-u flag" - it should not start stashing untracked files. You should pass 
PARSE_OPT_STOP_AT_NON_OPTION to parse_options() to prevent that.

> I proposed adding both --include-untracked and --all to
> "git stash create" in 2014:
>    <1403856479-37421-1-git-send-email-shigeta@kanamei.co.jp>
> 
> I should also apologize for dropping that thread after receiving review.
> I did not follow up on the comments at the time.  Thanks to those who
> reviewed it then.

Better late than never! I think the idea is fine, but the implementation 
could do with a couple of tweaks so it is as backward compatible as 
possible.

Thanks

Phillip

> Separately, in 2017, Thomas Gummerer added an internal -u path while
> refactoring stash_create in 9ca6326dff29 (stash: refactor stash_create).
> That change explicitly kept the user interface of "git stash create"
> unchanged.
> 
> When "stash create" was later converted to the builtin C implementation
> in d4788af875cc (stash: convert create to builtin), the untracked-file
> handling was carried into the new implementation and remains there today.
> 
> More recently, Shabbir Bhojani proposed exposing --include-untracked:
>    <pull.1892.git.1774768580147.gitgitgadget@gmail.com>
> 
> This patch exposes both existing untracked modes, --include-untracked and
> --all, to "git stash create".
> 
>   Documentation/git-stash.adoc | 18 ++++++----
>   builtin/stash.c              | 36 ++++++++++++++-----
>   t/t3903-stash.sh             | 70 ++++++++++++++++++++++++++++++++++++
>   3 files changed, 109 insertions(+), 15 deletions(-)
> 
> diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc
> index fc6a9a0..32f0fd5 100644
> --- a/Documentation/git-stash.adoc
> +++ b/Documentation/git-stash.adoc
> @@ -21,7 +21,7 @@ git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | -
>   git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]
>              [-u | --include-untracked] [-a | --all] [<message>]
>   git stash clear
> -git stash create [<message>]
> +git stash create [-u | --include-untracked] [-a | --all] [<message>]
>   git stash store [(-m | --message) <message>] [-q | --quiet] <commit>
>   git stash export (--print | --to-ref <ref>) [<stash>...]
>   git stash import <commit>
> @@ -138,10 +138,12 @@ with no conflicts.
>   `drop [-q | --quiet] [<stash>]`::
>   	Remove a single stash entry from the list of stash entries.
>   
> -`create`::
> +`create [-u | --include-untracked] [-a | --all]`::
>   	Create a stash entry (which is a regular commit object) and
>   	return its object name, without storing it anywhere in the ref
> -	namespace.
> +	namespace.  The `--include-untracked` option includes untracked
> +	files, while `--all` also includes ignored files, without modifying
> +	the working tree.
>   	This is intended to be useful for scripts.  It is probably not
>   	the command you want to use; see "push" above.
>   
> @@ -167,10 +169,11 @@ OPTIONS
>   -------
>   `-a`::
>   `--all`::
> -	This option is only valid for `push` and `save` commands.
> +	When used with the `push` and `save` commands, all ignored and
> +	untracked files are also stashed and then cleaned up with `git clean`.
>   +
> -All ignored and untracked files are also stashed and then cleaned
> -up with `git clean`.
> +When used with the `create` command, ignored and untracked files are included
> +in the stash entry without modifying the working tree.
>   
>   `-u`::
>   `--include-untracked`::
> @@ -179,6 +182,9 @@ up with `git clean`.
>   	all untracked files are also stashed and then cleaned up with
>   	`git clean`.
>   +
> +When used with the `create` command, untracked files are included in the
> +stash entry without modifying the working tree.
> ++
>   When used with the `show` command, show the untracked files in the stash
>   entry as part of the diff.
>   
> diff --git a/builtin/stash.c b/builtin/stash.c
> index 7a98434..57a4750 100644
> --- a/builtin/stash.c
> +++ b/builtin/stash.c
> @@ -59,7 +59,7 @@
>   	N_("git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n" \
>   	   "          [-u | --include-untracked] [-a | --all] [<message>]")
>   #define BUILTIN_STASH_CREATE_USAGE \
> -	N_("git stash create [<message>]")
> +	N_("git stash create [-u | --include-untracked] [-a | --all] [<message>]")
>   #define BUILTIN_STASH_EXPORT_USAGE \
>   	N_("git stash export (--print | --to-ref <ref>) [<stash>...]")
>   #define BUILTIN_STASH_IMPORT_USAGE \
> @@ -119,6 +119,11 @@ static const char * const git_stash_clear_usage[] = {
>   	NULL
>   };
>   
> +static const char * const git_stash_create_usage[] = {
> +	BUILTIN_STASH_CREATE_USAGE,
> +	NULL
> +};
> +
>   static const char * const git_stash_store_usage[] = {
>   	BUILTIN_STASH_STORE_USAGE,
>   	NULL
> @@ -1643,26 +1648,39 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
>   	return ret;
>   }
>   
> -static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
> +static int create_stash(int argc, const char **argv, const char *prefix,
>   			struct repository *repo UNUSED)
>   {
> -	int ret;
> +	int ret = 0;
> +	int include_untracked = 0;
> +	struct option options[] = {
> +		OPT_BOOL('u', "include-untracked", &include_untracked,
> +			 N_("include untracked files in stash")),
> +		OPT_SET_INT('a', "all", &include_untracked,
> +			    N_("include ignored files in stash"),
> +			    INCLUDE_ALL_FILES),
> +		OPT_END()
> +	};
>   	struct strbuf stash_msg_buf = STRBUF_INIT;
>   	struct stash_info info = STASH_INFO_INIT;
>   	struct pathspec ps;
>   
> -	/* Starting with argv[1], since argv[0] is "create" */
> -	strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');
> +	argc = parse_options(argc, argv, prefix, options,
> +			     git_stash_create_usage, 0);
> +	strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
>   
>   	memset(&ps, 0, sizeof(ps));
> -	if (!check_changes_tracked_files(&ps))
> -		return 0;
> +	if (!include_untracked && !check_changes_tracked_files(&ps))
> +		goto done;
>   
> -	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,
> -			      NULL, 0);
> +	ret = do_create_stash(&ps, &stash_msg_buf, include_untracked, 0, NULL,
> +			      0, &info, NULL, 0);
>   	if (!ret)
>   		printf_ln("%s", oid_to_hex(&info.w_commit));
> +	else if (ret == 1)
> +		ret = 0;
>   
> +done:
>   	free_stash_info(&info);
>   	strbuf_release(&stash_msg_buf);
>   	return ret;
> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
> index 7211586..fe34879 100755
> --- a/t/t3903-stash.sh
> +++ b/t/t3903-stash.sh
> @@ -640,6 +640,76 @@ test_expect_success 'stash create - no changes' '
>   	test_must_be_empty actual
>   '
>   
> +# --all observes every untracked and ignored path in the worktree.  Use one
> +# isolated repository for these checks so unrelated test state is not captured.
> +test_expect_success 'stash create with untracked options' '
> +	test_when_finished "rm -rf stash-create-options" &&
> +	test_create_repo stash-create-options &&
> +	(
> +		cd stash-create-options &&
> +		test_commit base tracked base &&
> +		echo create-ignored >.gitignore &&
> +		git add .gitignore &&
> +		git commit -m ignore &&
> +
> +		git stash create -u >.git/actual &&
> +		test_must_be_empty .git/actual &&
> +		git stash create -a >.git/actual &&
> +		test_must_be_empty .git/actual &&
> +
> +		echo untracked >create-untracked &&
> +		git stash create "without untracked" >.git/actual &&
> +		test_must_be_empty .git/actual &&
> +		short=$(git stash create "create untracked" -u) &&
> +		long=$(git stash create --include-untracked "create untracked") &&
> +		test_cmp_rev "$short^3^{tree}" "$long^3^{tree}" &&
> +		echo untracked >.git/expect &&
> +		git show "$short^3:create-untracked" >.git/actual &&
> +		test_cmp .git/expect .git/actual &&
> +		branch=$(git symbolic-ref --short HEAD) &&
> +		echo "On $branch: create untracked" >.git/expect &&
> +		git show --pretty=%s -s "$short" >.git/actual &&
> +		test_cmp .git/expect .git/actual &&
> +		test_path_is_file create-untracked &&
> +
> +		echo ignored >create-ignored &&
> +		with_untracked=$(git stash create -u "create options") &&
> +		test_must_fail git cat-file -e "$with_untracked^3:create-ignored" &&
> +		short=$(git stash create "create options" -a) &&
> +		long=$(git stash create --all "create options") &&
> +		test_cmp_rev "$short^3^{tree}" "$long^3^{tree}" &&
> +		echo ignored >.git/expect &&
> +		git show "$short^3:create-ignored" >.git/actual &&
> +		test_cmp .git/expect .git/actual &&
> +		test_path_is_file create-untracked &&
> +		test_path_is_file create-ignored &&
> +
> +		echo staged >staged &&
> +		git add staged &&
> +		echo modified >>tracked &&
> +		git diff >.git/before-worktree &&
> +		git diff --cached >.git/before-index &&
> +		git status --porcelain=v1 --ignored >.git/before-status &&
> +		test_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&
> +		STASH_ID=$(git stash create -a -- -create-message) &&
> +		git diff >.git/after-worktree &&
> +		git diff --cached >.git/after-index &&
> +		git status --porcelain=v1 --ignored >.git/after-status &&
> +		test_cmp .git/before-worktree .git/after-worktree &&
> +		test_cmp .git/before-index .git/after-index &&
> +		test_cmp .git/before-status .git/after-status &&
> +		test_must_fail git rev-parse --verify refs/stash >/dev/null 2>&1 &&
> +		echo "On $branch: -create-message" >.git/expect &&
> +		git show --pretty=%s -s "$STASH_ID" >.git/actual &&
> +		test_cmp .git/expect .git/actual
> +	)
> +'
> +
> +test_expect_success 'stash create rejects unknown options' '
> +	test_expect_code 129 git stash create --unknown-option 2>err &&
> +	test_grep "unknown option" err
> +'
> +
>   test_expect_success 'stash branch - no stashes on stack, stash-like argument' '
>   	git stash clear &&
>   	test_when_finished "git reset --hard HEAD" &&


```

## Kazumasa Shigeta, 2026-10-01 04:21

Subject: [PATCH v2] stash: expose untracked modes in create
Message-ID: <20261001042155.33303-1-kazumasa.shigeta@kanamei.com>
In-Reply-To: <20260929074222.11942-1-kazumasa.shigeta@kanamei.com>

```
`git stash create` always passes zero for the include_untracked parameter
of do_create_stash(), even though that helper already supports untracked
and ignored files and stash push/save expose those modes as
-u/--include-untracked and -a/--all.

Teach create to accept the same options and pass the existing mode
through. Unlike push/save, create continues to only create objects: it
does not update refs/stash, reset the index, or clean the working tree.

Use parse_options() for the new options and stop parsing at the first
non-option message word. This keeps option-like tokens after the message
as message text, while leading option-like arguments now follow Git's
normal option parsing. In particular, unknown or malformed leading
options are rejected instead of silently becoming a message, short
options may be combined, and `--` can be used when a message itself
begins with a dash.

Keep create's existing no-change behavior: detect the usual no-change
case before do_create_stash() refreshes and writes the index, and return
success without printing an object name. If do_create_stash() still
reports its internal "nothing to create" result, map that to create's
public success status.

This follows the stash subcommand exit-status convention established by
786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):
subcommands return 0 on success, negative values on failure, and status 1
when applying a stash results in conflicts. cmd_stash() maps negative
subcommand failures to 128.

9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the
internal include-untracked path while intentionally leaving the user
interface for "git stash create" unchanged. Reuse that machinery and
the existing INCLUDE_ALL_FILES mode rather than adding a separate stash
creation path.

Add coverage for short and long aliases, combined short options, the
untracked/ignored boundary including an ignored-only worktree, option
parsing and dash-leading messages, no-change behavior, and preservation
of refs/stash, the index state, and the working tree.

Signed-off-by: Kazumasa Shigeta <kazumasa.shigeta@kanamei.com>
---
 Documentation/git-stash.adoc | 19 ++++++---
 builtin/stash.c              | 48 +++++++++++++++++----
 t/t3903-stash.sh             | 83 ++++++++++++++++++++++++++++++++++++
 3 files changed, 135 insertions(+), 15 deletions(-)

diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc
index fc6a9a0..d343a75 100644
--- a/Documentation/git-stash.adoc
+++ b/Documentation/git-stash.adoc
@@ -21,7 +21,7 @@ git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | -
 git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]
            [-u | --include-untracked] [-a | --all] [<message>]
 git stash clear
-git stash create [<message>]
+git stash create [-u | --include-untracked] [-a | --all] [--] [<message>]
 git stash store [(-m | --message) <message>] [-q | --quiet] <commit>
 git stash export (--print | --to-ref <ref>) [<stash>...]
 git stash import <commit>
@@ -138,10 +138,13 @@ with no conflicts.
 `drop [-q | --quiet] [<stash>]`::
 	Remove a single stash entry from the list of stash entries.
 
-`create`::
+`create [-u | --include-untracked] [-a | --all] [--]`::
 	Create a stash entry (which is a regular commit object) and
 	return its object name, without storing it anywhere in the ref
-	namespace.
+	namespace.  The `--include-untracked` option includes untracked
+	files, while `--all` also includes ignored files, without modifying
+	the working tree.  If `<message>` begins with a dash, use `--` to
+	separate it from the options.
 	This is intended to be useful for scripts.  It is probably not
 	the command you want to use; see "push" above.
 
@@ -167,10 +170,11 @@ OPTIONS
 -------
 `-a`::
 `--all`::
-	This option is only valid for `push` and `save` commands.
+	When used with the `push` and `save` commands, all ignored and
+	untracked files are also stashed and then cleaned up with `git clean`.
 +
-All ignored and untracked files are also stashed and then cleaned
-up with `git clean`.
+When used with the `create` command, ignored and untracked files are included
+in the stash entry without modifying the working tree.
 
 `-u`::
 `--include-untracked`::
@@ -179,6 +183,9 @@ up with `git clean`.
 	all untracked files are also stashed and then cleaned up with
 	`git clean`.
 +
+When used with the `create` command, untracked files are included in the
+stash entry without modifying the working tree.
++
 When used with the `show` command, show the untracked files in the stash
 entry as part of the diff.
 
diff --git a/builtin/stash.c b/builtin/stash.c
index 7a98434..ec2b5e7 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -59,7 +59,7 @@
 	N_("git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n" \
 	   "          [-u | --include-untracked] [-a | --all] [<message>]")
 #define BUILTIN_STASH_CREATE_USAGE \
-	N_("git stash create [<message>]")
+	N_("git stash create [-u | --include-untracked] [-a | --all] [--] [<message>]")
 #define BUILTIN_STASH_EXPORT_USAGE \
 	N_("git stash export (--print | --to-ref <ref>) [<stash>...]")
 #define BUILTIN_STASH_IMPORT_USAGE \
@@ -119,6 +119,11 @@ static const char * const git_stash_clear_usage[] = {
 	NULL
 };
 
+static const char * const git_stash_create_usage[] = {
+	BUILTIN_STASH_CREATE_USAGE,
+	NULL
+};
+
 static const char * const git_stash_store_usage[] = {
 	BUILTIN_STASH_STORE_USAGE,
 	NULL
@@ -1643,26 +1648,51 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
 	return ret;
 }
 
-static int create_stash(int argc, const char **argv, const char *prefix UNUSED,
+static int create_stash(int argc, const char **argv, const char *prefix,
 			struct repository *repo UNUSED)
 {
-	int ret;
+	int ret = 0;
+	int include_untracked = 0;
+	struct option options[] = {
+		OPT_BOOL('u', "include-untracked", &include_untracked,
+			 N_("include untracked files in stash")),
+		OPT_SET_INT('a', "all", &include_untracked,
+			    N_("include ignored files in stash"),
+			    INCLUDE_ALL_FILES),
+		OPT_END()
+	};
 	struct strbuf stash_msg_buf = STRBUF_INIT;
+	struct strbuf untracked_files = STRBUF_INIT;
 	struct stash_info info = STASH_INFO_INIT;
 	struct pathspec ps;
 
-	/* Starting with argv[1], since argv[0] is "create" */
-	strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');
+	argc = parse_options(argc, argv, prefix, options,
+			     git_stash_create_usage,
+			     PARSE_OPT_STOP_AT_NON_OPTION);
+	strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
 
 	memset(&ps, 0, sizeof(ps));
-	if (!check_changes_tracked_files(&ps))
-		return 0;
+	/*
+	 * Preserve "stash create"'s successful no-change behavior before
+	 * do_create_stash() refreshes and writes the index.
+	 */
+	if (!check_changes(&ps, include_untracked, &untracked_files))
+		goto done;
 
-	ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info,
-			      NULL, 0);
+	ret = do_create_stash(&ps, &stash_msg_buf, include_untracked, 0, NULL,
+			      0, &info, NULL, 0);
+	/*
+	 * Status 1 is reserved for conflicts when applying a stash.
+	 * do_create_stash() uses it internally for "nothing to create", so
+	 * translate that sentinel to create's public success status.
+	 */
 	if (!ret)
 		printf_ln("%s", oid_to_hex(&info.w_commit));
+	else if (ret == 1)
+		ret = 0;
 
+done:
+	strbuf_release(&untracked_files);
 	free_stash_info(&info);
 	strbuf_release(&stash_msg_buf);
 	return ret;
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 7211586..1f660ca 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -1179,6 +1179,89 @@ test_expect_success 'create with multiple arguments for the message' '
 	test_cmp expect actual
 '
 
+test_expect_success 'create with untracked options' '
+	test_when_finished "rm -rf create-options" &&
+	git init create-options &&
+	test_commit -C create-options base tracked base &&
+	test_commit -C create-options ignore .gitignore ignored &&
+
+	git -C create-options stash create -u >actual &&
+	test_must_be_empty actual &&
+	git -C create-options stash create -a >actual &&
+	test_must_be_empty actual &&
+
+	echo untracked >create-options/untracked &&
+	echo ignored >create-options/ignored &&
+	git -C create-options diff >before-worktree &&
+	git -C create-options diff --cached >before-index &&
+	git -C create-options status --porcelain=v1 --ignored >before-status &&
+
+	short=$(git -C create-options stash create -u "create options") &&
+	long=$(git -C create-options stash create --include-untracked "create options") &&
+	test "$(git -C create-options rev-parse "$short^3^{tree}")" = "$(git -C create-options rev-parse "$long^3^{tree}")" &&
+	echo untracked >expect &&
+	git -C create-options show "$short^3:untracked" >actual &&
+	test_cmp expect actual &&
+	test_must_fail git -C create-options cat-file -e "$short^3:ignored" &&
+
+	short=$(git -C create-options stash create -a "create options") &&
+	long=$(git -C create-options stash create --all "create options") &&
+	cluster=$(git -C create-options stash create -ua "create options") &&
+	test "$(git -C create-options rev-parse "$short^3^{tree}")" = "$(git -C create-options rev-parse "$long^3^{tree}")" &&
+	test "$(git -C create-options rev-parse "$short^3^{tree}")" = "$(git -C create-options rev-parse "$cluster^3^{tree}")" &&
+	echo ignored >expect &&
+	git -C create-options show "$short^3:ignored" >actual &&
+	test_cmp expect actual &&
+
+	git -C create-options diff >after-worktree &&
+	git -C create-options diff --cached >after-index &&
+	git -C create-options status --porcelain=v1 --ignored >after-status &&
+	test_cmp before-worktree after-worktree &&
+	test_cmp before-index after-index &&
+	test_cmp before-status after-status &&
+	test_must_fail git -C create-options rev-parse --verify refs/stash >/dev/null 2>&1
+'
+
+test_expect_success 'create untracked modes with only ignored files' '
+	test_when_finished "rm -rf create-ignored-only" &&
+	git init create-ignored-only &&
+	test_commit -C create-ignored-only base tracked base &&
+	test_commit -C create-ignored-only ignore .gitignore ignored &&
+	echo ignored >create-ignored-only/ignored &&
+
+	git -C create-ignored-only stash create -u >actual &&
+	test_must_be_empty actual &&
+	stash=$(git -C create-ignored-only stash create -a) &&
+	test -n "$stash" &&
+	echo ignored >expect &&
+	git -C create-ignored-only show "$stash^3:ignored" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'create option parsing and dash-leading messages' '
+	test_when_finished "rm -rf create-message-options" &&
+	git init create-message-options &&
+	test_commit -C create-message-options base tracked base &&
+	echo modified >>create-message-options/tracked &&
+	echo untracked >create-message-options/untracked &&
+
+	stash=$(git -C create-message-options stash create handle new -u flag) &&
+	echo "On main: handle new -u flag" >expect &&
+	git -C create-message-options show --pretty=%s -s "$stash" >actual &&
+	test_cmp expect actual &&
+	test_must_fail git -C create-message-options cat-file -e "$stash^3^{commit}" &&
+
+	test_must_fail git -C create-message-options stash create -f >out 2>err &&
+	test_grep "unknown switch" err &&
+	stash=$(git -C create-message-options stash create -- -f) &&
+	echo "On main: -f" >expect &&
+	git -C create-message-options show --pretty=%s -s "$stash" >actual &&
+	test_cmp expect actual &&
+
+	test_must_fail git -C create-message-options stash create \
+		--include-untracked=yes >out 2>err
+'
+
 test_expect_success 'create in a detached state' '
 	test_when_finished "git checkout main" &&
 	git checkout HEAD~1 &&
-- 
2.47.3


```

## Patrick Steinhardt, 2026-10-01 11:32

Subject: Re: [PATCH v2] stash: expose untracked modes in create
Message-ID: <ar5EwwEt8-ADeLdr@pks.im>
In-Reply-To: <20261001042155.33303-1-kazumasa.shigeta@kanamei.com>

```
On Thu, Oct 01, 2026 at 01:21:55PM +0900, Kazumasa Shigeta wrote:

When sending a v2 in response to review feedback it's a good idea to
both:

  - Respond to the reviewer to acknowledge their feedback and/or engage
    in a discussion.

  - As part of v2, send a range-diff as well as some documentation what
    has changed between the two versions.

This ensures some netiquette in an age where we're increasingly only
talking with AI, either directly or via a meat proxy. And makes it
easier for the reviewer to see how exactly you have honored their
feedback.

Thanks!

Patrick

```

## 重田一聖, 2026-10-01 16:01

Subject: Re: [PATCH v2] stash: expose untracked modes in create
Message-ID: <CANUHOw3syiH81F_5q-S33Zmyfw5v_R8cS1hwgpPLqrBDQGu3FQ@mail.gmail.com>
In-Reply-To: <ar5EwwEt8-ADeLdr@pks.im>

```
Hi Patrick,

Thanks for pointing this out, and sorry I did not understand the
expected review process here and sent v2 before replying to Phillip.

I'll reply to Phillip first and follow the proper order.

Thanks,

Kazumasa Shigeta


On Thu, 1 Oct 2026 13:32:19 +0200, Patrick Steinhardt <ps@pks.im> wrote:
> On Thu, Oct 01, 2026 at 01:21:55PM +0900, Kazumasa Shigeta wrote:
>
> When sending a v2 in response to review feedback it's a good idea to
> both:
>
> - Respond to the reviewer to acknowledge their feedback and/or engage
> in a discussion.
>
> - As part of v2, send a range-diff as well as some documentation what
> has changed between the two versions.
>
> This ensures some netiquette in an age where we're increasingly only
> talking with AI, either directly or via a meat proxy. And makes it
> easier for the reviewer to see how exactly you have honored their
> feedback.
>
> Thanks!
>
> Patrick

```

## Junio C Hamano, 2026-10-01 16:36

Subject: Re: [PATCH v2] stash: expose untracked modes in create
Message-ID: <xmqqcxtt74ys.fsf@gitster.g>
In-Reply-To: <ar5EwwEt8-ADeLdr@pks.im>

```
Patrick Steinhardt <ps@pks.im> writes:

> On Thu, Oct 01, 2026 at 01:21:55PM +0900, Kazumasa Shigeta wrote:
>
> When sending a v2 in response to review feedback it's a good idea to
> both:
>
>   - Respond to the reviewer to acknowledge their feedback and/or engage
>     in a discussion.
>
>   - As part of v2, send a range-diff as well as some documentation what
>     has changed between the two versions.
>
> This ensures some netiquette in an age where we're increasingly only
> talking with AI, either directly or via a meat proxy. And makes it
> easier for the reviewer to see how exactly you have honored their
> feedback.
>
> Thanks!
>
> Patrick

Thanks for bringing this up.

A response to review on the first round should come _before_ sending
v2 round of patch(es).  Some people send them after v2, or
immediately sending before v2, but the right time to respond is
actually soon after receiving reviews on v1 and you had enough time
to understand the review comments, before starting to work on v2.
And then after working on v2, you would send patches.  So whenever I
see v1 responses come after v2 patches or soon before v2 patches, I
smell that something is fishy.


```

## Junio C Hamano, 2026-10-01 17:03

Subject: Re: [PATCH v2] stash: expose untracked modes in create
Message-ID: <xmqq7bk173qm.fsf@gitster.g>
In-Reply-To: <20261001042155.33303-1-kazumasa.shigeta@kanamei.com>

```
Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:

> `git stash create` always passes zero for the include_untracked parameter
> of do_create_stash(), even though that helper already supports untracked
> and ignored files and stash push/save expose those modes as
> -u/--include-untracked and -a/--all.

There may be no lies in what the above says, but we would prefer to
hear what the user visible implication of "passing 0" is more than
what mechanically is happening inside a program.  For example:

    "git stash create", "git stash push", and "git stash save" are
    commands that create a new stash entry.  The latter two are also
    responsible for storing the resulting stash entry to the reflog
    of the "refs/stash" ref, but have options to control what is
    included in the stash entry.  Among these options, "create" only
    supports the equivalent of "-m <message." to record in the stash
    entry.  Most notably, "-u" and "-a" options are missing.

> Teach create to accept the same options and pass the existing mode
> through. Unlike push/save, create continues to only create objects: it
> does not update refs/stash, reset the index, or clean the working tree.

Sure.  It is a very concise and good description of what we want to
do.

> Use parse_options() for the new options and stop parsing at the first
> non-option message word. This keeps option-like tokens after the message
> as message text, while leading option-like arguments now follow Git's
> normal option parsing. In particular, unknown or malformed leading
> options are rejected instead of silently becoming a message, short
> options may be combined, and `--` can be used when a message itself
> begins with a dash.

Why do we need to go into such a detail in the log message?  What is
the above paragraph designed to convey to the reader?  Again, it may
not be telling any lies, but it misses the point by being inconsiderate
to your readers.  What you need to tell them is _WHY_ you chose to
use parse_options() in such a way.  What were you trying to achieve?

I am guessing that something along this line ...

    "git stash create" traditionally treated the rest of the command
    line as a message.  For example, 

	$ git stash create adding -u option

    has always been a request to create a stash entry with the
    string "adding -u option" as its message.  We should not make it
    trigger the "-u" (include untracked) behavior for backward
    compatibility, by using parse_options() with stop-at-the-non-option
    mode to forbid it from reordering the command line arguments.

... was what you wanted to say, but I am not sure.

How much of all these verbiage was written by AI by the way?  You'd
need to spend effort to make it readable to humans.

> Keep create's existing no-change behavior: detect the usual no-change
> case before do_create_stash() refreshes and writes the index, and return
> success without printing an object name. If do_create_stash() still
> reports its internal "nothing to create" result, map that to create's
> public success status.

You already said that with "does not update, reset, or clean".

> This follows the stash subcommand exit-status convention established by
> 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):
> subcommands return 0 on success, negative values on failure, and status 1
> when applying a stash results in conflicts. cmd_stash() maps negative
> subcommand failures to 128.

Again, there may not be lies in here, but if you did not make a
breaking change to the established convention, is it worth saying?

> 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the
> internal include-untracked path while intentionally leaving the user
> interface for "git stash create" unchanged. Reuse that machinery and
> the existing INCLUDE_ALL_FILES mode rather than adding a separate stash
> creation path.
>
> Add coverage for short and long aliases, combined short options, the
> untracked/ignored boundary including an ignored-only worktree, option
> parsing and dash-leading messages, no-change behavior, and preservation
> of refs/stash, the index state, and the working tree.

Again, adding tests for comprehensive coverage is not something to
boast about.  Is it worth saying?

Aren't -p/-S/-k/-q and pathspec support all about the creating half
of "git stash push" that are not available to "git stash create",
not just "-u" and "-a"?  Why are we singling out only these two?  It
may be more worthwhile to explain the rationale behind such a design
decision.

```

## 重田一聖, 2026-10-01 17:44

Subject: Re: [PATCH] stash: expose untracked modes in create
Message-ID: <CANUHOw201hr2LgHb1ThcadiH8Y5k3zUArnhtj8g8SRVrvMsN-g@mail.gmail.com>
In-Reply-To: <8453ebd1-77c1-4941-afbf-572f9e7b12c1@gmail.com>

```
Hi Phillip,

Thanks for the review.

Sorry, I got a little carried away and sent v2 before replying.

> This doesn't seem to match the code changes.

For the no-change case, plain "git stash create" already checks for
tracked changes before calling do_create_stash(), and returns 0 with
empty output when there is nothing to create.

For -u and -a, I think we should follow that existing "create" behavior
as well, using check_changes() for the selected mode before calling
do_create_stash(), and returning 0 when it finds nothing to create.

I am also thinking of mapping do_create_stash()'s internal no-change
status of 1 to 0 in the unlikely case where the state changes between
these checks. That 1 is not STASH_APPLY_CONFLICT. Following 786fc390465f
("stash: reserve exit status 1 for conflicts"), I do not think it should
escape as public exit status 1, and would map it to 0 instead.

> You should pass PARSE_OPT_STOP_AT_NON_OPTION to parse_options()
> to prevent that.

I plan to use PARSE_OPT_STOP_AT_NON_OPTION as you suggested.

Thanks,

Kazumasa Shigeta


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

```

## 重田一聖, 2026-10-02 01:53

Subject: Re: [PATCH v2] stash: expose untracked modes in create
Message-ID: <CANUHOw2Q1Dg=e7zfAcRUBMyKt+cVW02V5MZV4x7vAyA6iJ=PWw@mail.gmail.com>
In-Reply-To: <xmqq7bk173qm.fsf@gitster.g>

```
Hi Junio,

Sorry for the crossed replies. As you also pointed out, I should have
replied to Phillip before sending v2. After Patrick raised that point, I
was writing my response to Phillip, and I did not notice that your
messages had arrived while I was doing so. I ended up sending my reply
to Phillip before seeing your comments.

Thank you for the detailed review. I will go through your comments
carefully before following up.

I am not very quick at writing these emails, so it takes me quite a
while to respond. Sorry about that. I will do my best to understand the
points properly and improve the next round.

Thanks,
Kazumasa Shigeta

On Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:
> Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:
>
> > `git stash create` always passes zero for the include_untracked parameter
> > of do_create_stash(), even though that helper already supports untracked
> > and ignored files and stash push/save expose those modes as
> > -u/--include-untracked and -a/--all.
>
> There may be no lies in what the above says, but we would prefer to
> hear what the user visible implication of "passing 0" is more than
> what mechanically is happening inside a program. For example:
>
> "git stash create", "git stash push", and "git stash save" are
> commands that create a new stash entry. The latter two are also
> responsible for storing the resulting stash entry to the reflog
> of the "refs/stash" ref, but have options to control what is
> included in the stash entry. Among these options, "create" only
> supports the equivalent of "-m <message." to record in the stash
> entry. Most notably, "-u" and "-a" options are missing.
>
> > Teach create to accept the same options and pass the existing mode
> > through. Unlike push/save, create continues to only create objects: it
> > does not update refs/stash, reset the index, or clean the working tree.
>
> Sure. It is a very concise and good description of what we want to
> do.
>
> > Use parse_options() for the new options and stop parsing at the first
> > non-option message word. This keeps option-like tokens after the message
> > as message text, while leading option-like arguments now follow Git's
> > normal option parsing. In particular, unknown or malformed leading
> > options are rejected instead of silently becoming a message, short
> > options may be combined, and `--` can be used when a message itself
> > begins with a dash.
>
> Why do we need to go into such a detail in the log message? What is
> the above paragraph designed to convey to the reader? Again, it may
> not be telling any lies, but it misses the point by being inconsiderate
> to your readers. What you need to tell them is _WHY_ you chose to
> use parse_options() in such a way. What were you trying to achieve?
>
> I am guessing that something along this line ...
>
> "git stash create" traditionally treated the rest of the command
> line as a message. For example,
>
> $ git stash create adding -u option
>
> has always been a request to create a stash entry with the
> string "adding -u option" as its message. We should not make it
> trigger the "-u" (include untracked) behavior for backward
> compatibility, by using parse_options() with stop-at-the-non-option
> mode to forbid it from reordering the command line arguments.
>
> ... was what you wanted to say, but I am not sure.
>
> How much of all these verbiage was written by AI by the way? You'd
> need to spend effort to make it readable to humans.
>
> > Keep create's existing no-change behavior: detect the usual no-change
> > case before do_create_stash() refreshes and writes the index, and return
> > success without printing an object name. If do_create_stash() still
> > reports its internal "nothing to create" result, map that to create's
> > public success status.
>
> You already said that with "does not update, reset, or clean".
>
> > This follows the stash subcommand exit-status convention established by
> > 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):
> > subcommands return 0 on success, negative values on failure, and status 1
> > when applying a stash results in conflicts. cmd_stash() maps negative
> > subcommand failures to 128.
>
> Again, there may not be lies in here, but if you did not make a
> breaking change to the established convention, is it worth saying?
>
> > 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the
> > internal include-untracked path while intentionally leaving the user
> > interface for "git stash create" unchanged. Reuse that machinery and
> > the existing INCLUDE_ALL_FILES mode rather than adding a separate stash
> > creation path.
> >
> > Add coverage for short and long aliases, combined short options, the
> > untracked/ignored boundary including an ignored-only worktree, option
> > parsing and dash-leading messages, no-change behavior, and preservation
> > of refs/stash, the index state, and the working tree.
>
> Again, adding tests for comprehensive coverage is not something to
> boast about. Is it worth saying?
>
> Aren't -p/-S/-k/-q and pathspec support all about the creating half
> of "git stash push" that are not available to "git stash create",
> not just "-u" and "-a"? Why are we singling out only these two? It
> may be more worthwhile to explain the rationale behind such a design
> decision.

```

## 重田一聖, 2026-10-02 09:04

Subject: Re: [PATCH v2] stash: expose untracked modes in create
Message-ID: <CANUHOw1eO0HNjU+-PYNDOz9kHhBZYYfhiKJSX4082YSC1NKxww@mail.gmail.com>
In-Reply-To: <xmqq7bk173qm.fsf@gitster.g>

```
Hi Junio,

> we would prefer to hear what the user visible implication of
> "passing 0" is more than what mechanically is happening inside a
> program.

The user-visible effect is that stash create cannot currently include
untracked or ignored files in the stash entry. If those are the only
changes, it creates no entry at all, while stash push and save can
include them with -u or -a as appropriate. I should have described that
difference directly instead of starting from the include_untracked
implementation detail.

> ... was what you wanted to say, but I am not sure.

Yes, exactly. I'll explain the backward-compatibility reason rather
than the mechanics of parse_options().

> You already said that with "does not update, reset, or clean".

I'll drop that paragraph.

> if you did not make a breaking change to the established convention,
> is it worth saying?

I don't think it adds anything here. I'll remove the exit-status
discussion from the commit message as well.

> adding tests for comprehensive coverage is not something to boast
> about. Is it worth saying?

I'll remove the test details from the commit message.

> Why are we singling out only these two?

I started by looking at the missing -u and -a support in create, and I
think that led me to focus too narrowly on those two when considering
the scope. I need to think more about whether this patch should remain
limited to those two.

Thanks,
Kazumasa Shigeta

On Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:
> Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:
>
> > `git stash create` always passes zero for the include_untracked parameter
> > of do_create_stash(), even though that helper already supports untracked
> > and ignored files and stash push/save expose those modes as
> > -u/--include-untracked and -a/--all.
>
> There may be no lies in what the above says, but we would prefer to
> hear what the user visible implication of "passing 0" is more than
> what mechanically is happening inside a program. For example:
>
> "git stash create", "git stash push", and "git stash save" are
> commands that create a new stash entry. The latter two are also
> responsible for storing the resulting stash entry to the reflog
> of the "refs/stash" ref, but have options to control what is
> included in the stash entry. Among these options, "create" only
> supports the equivalent of "-m <message." to record in the stash
> entry. Most notably, "-u" and "-a" options are missing.
>
> > Teach create to accept the same options and pass the existing mode
> > through. Unlike push/save, create continues to only create objects: it
> > does not update refs/stash, reset the index, or clean the working tree.
>
> Sure. It is a very concise and good description of what we want to
> do.
>
> > Use parse_options() for the new options and stop parsing at the first
> > non-option message word. This keeps option-like tokens after the message
> > as message text, while leading option-like arguments now follow Git's
> > normal option parsing. In particular, unknown or malformed leading
> > options are rejected instead of silently becoming a message, short
> > options may be combined, and `--` can be used when a message itself
> > begins with a dash.
>
> Why do we need to go into such a detail in the log message? What is
> the above paragraph designed to convey to the reader? Again, it may
> not be telling any lies, but it misses the point by being inconsiderate
> to your readers. What you need to tell them is _WHY_ you chose to
> use parse_options() in such a way. What were you trying to achieve?
>
> I am guessing that something along this line ...
>
> "git stash create" traditionally treated the rest of the command
> line as a message. For example,
>
> $ git stash create adding -u option
>
> has always been a request to create a stash entry with the
> string "adding -u option" as its message. We should not make it
> trigger the "-u" (include untracked) behavior for backward
> compatibility, by using parse_options() with stop-at-the-non-option
> mode to forbid it from reordering the command line arguments.
>
> ... was what you wanted to say, but I am not sure.
>
> How much of all these verbiage was written by AI by the way? You'd
> need to spend effort to make it readable to humans.
>
> > Keep create's existing no-change behavior: detect the usual no-change
> > case before do_create_stash() refreshes and writes the index, and return
> > success without printing an object name. If do_create_stash() still
> > reports its internal "nothing to create" result, map that to create's
> > public success status.
>
> You already said that with "does not update, reset, or clean".
>
> > This follows the stash subcommand exit-status convention established by
> > 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):
> > subcommands return 0 on success, negative values on failure, and status 1
> > when applying a stash results in conflicts. cmd_stash() maps negative
> > subcommand failures to 128.
>
> Again, there may not be lies in here, but if you did not make a
> breaking change to the established convention, is it worth saying?
>
> > 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the
> > internal include-untracked path while intentionally leaving the user
> > interface for "git stash create" unchanged. Reuse that machinery and
> > the existing INCLUDE_ALL_FILES mode rather than adding a separate stash
> > creation path.
> >
> > Add coverage for short and long aliases, combined short options, the
> > untracked/ignored boundary including an ignored-only worktree, option
> > parsing and dash-leading messages, no-change behavior, and preservation
> > of refs/stash, the index state, and the working tree.
>
> Again, adding tests for comprehensive coverage is not something to
> boast about. Is it worth saying?
>
> Aren't -p/-S/-k/-q and pathspec support all about the creating half
> of "git stash push" that are not available to "git stash create",
> not just "-u" and "-a"? Why are we singling out only these two? It
> may be more worthwhile to explain the rationale behind such a design
> decision.

```

## 重田一聖, 2026-10-05 05:55

Subject: Re: [PATCH v2] stash: expose untracked modes in create
Message-ID: <CANUHOw3gynMRGN7A-wOnL3PQtgFbMtsB2gyZxaq+0Z5bHp-H8A@mail.gmail.com>
In-Reply-To: <CANUHOw1eO0HNjU+-PYNDOz9kHhBZYYfhiKJSX4082YSC1NKxww@mail.gmail.com>

```
Hi Junio,

> Why are we singling out only these two?

After looking more carefully at both the history and the current stash
code, I don't think there is a good reason to single out only `-u` and
`-a`.

Your question made me realize that I had focused too narrowly on the
untracked modes. The larger issue is not simply that `do_create_stash()`
has capabilities that `git stash create` does not expose. The existing
`git stash create <message>` grammar is a long-standing compatibility
contract that has been deliberately preserved.

Making more of those capabilities available through `create` would
therefore mean either changing that contract or designing around it.
That is a much larger interface decision than I appreciated when I sent
the patch.

I moved too quickly here and sent the patch before understanding that
constraint well enough. I am sorry about that. Thanks to you, Phillip,
Patrick, and everyone else who took the time to review it. I should
have investigated this compatibility history first.

Even so, I still think it would be useful to make more of the existing
`do_create_stash()` capabilities available through the public command
line.

So I no longer think the question is simply which additional options
`stash create` should expose. The broader question is how to make those
capabilities available through a public interface while dealing
appropriately with the existing `git stash create <message>`
compatibility contract.

From that perspective, I can see three possible directions.

1. Keep extending `stash create`.

   We could expose more of the existing `do_create_stash()`
   functionality through `stash create`, following the conventions of
   `stash push` for the creation-related options they have in common.

   This seems implementable, but even with
   `PARSE_OPT_STOP_AT_NON_OPTION` it would change the handling of
   messages that begin with an option-like argument. Those would need
   explicit disambiguation, such as `--`.

   There is also the pathspec question. If positional arguments
   continue to be joined to form the message, pathspecs need some other
   way to be distinguished from that message.

2. Add a new stash subcommand for the creation functionality.

   This would leave the existing `stash create <message>` contract
   unchanged. Because the new command would not inherit `create`'s
   positional message grammar, its creation-related options and
   pathspec handling could follow conventions similar to `stash push`.

   This preserves the existing `create` grammar while avoiding the need
   to fit additional creation capabilities into it. The trade-off is
   adding another public stash subcommand and its long-term maintenance
   cost.

3. Add something like `--create-only` to `git stash push`.

   This would reuse the existing `push` option grammar without adding
   another subcommand.

   I also read the 2019 discussion around `git stash push --snapshot`.
   One concern there was that approximately the same end state could
   already be obtained with `git stash push && git stash apply`.

   I do not think that particular concern carries over directly here.
   `git stash create` already stops at object creation, but its public
   interface does not expose more of the creation capabilities already
   available in `do_create_stash()`. There is currently no public stash
   command that exposes those capabilities while retaining that
   create-only boundary.

   That does not mean a similar result cannot be constructed by other
   means. The missing piece is a public interface to the existing stash
   creation machinery at that boundary.

   Even so, there is still the separate question of whether `push` is
   the right place for a creation-only operation in the first place.
   The push-specific work around `do_create_stash()` would also need to
   be separated carefully.

All three seem substantially broader than the original `-u` / `-a`
patch.

If this is worth pursuing further, which of these directions seems the
most plausible? Also, is this the right thread to continue that design
discussion, or would it be better to discuss it separately?

Thanks again for the guidance,
Kazumasa Shigeta

On Fri, 2 Oct 2026 05:04:26 -0400, "重田一聖" <kazumasa.shigeta@kanamei.com> wrote:
> Hi Junio,
>
> > we would prefer to hear what the user visible implication of
> > "passing 0" is more than what mechanically is happening inside a
> > program.
>
> The user-visible effect is that stash create cannot currently include
> untracked or ignored files in the stash entry. If those are the only
> changes, it creates no entry at all, while stash push and save can
> include them with -u or -a as appropriate. I should have described that
> difference directly instead of starting from the include_untracked
> implementation detail.
>
> > ... was what you wanted to say, but I am not sure.
>
> Yes, exactly. I'll explain the backward-compatibility reason rather
> than the mechanics of parse_options().
>
> > You already said that with "does not update, reset, or clean".
>
> I'll drop that paragraph.
>
> > if you did not make a breaking change to the established convention,
> > is it worth saying?
>
> I don't think it adds anything here. I'll remove the exit-status
> discussion from the commit message as well.
>
> > adding tests for comprehensive coverage is not something to boast
> > about. Is it worth saying?
>
> I'll remove the test details from the commit message.
>
> > Why are we singling out only these two?
>
> I started by looking at the missing -u and -a support in create, and I
> think that led me to focus too narrowly on those two when considering
> the scope. I need to think more about whether this patch should remain
> limited to those two.
>
> Thanks,
> Kazumasa Shigeta
>
> On Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:
> > Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:
> >
> > > `git stash create` always passes zero for the include_untracked parameter
> > > of do_create_stash(), even though that helper already supports untracked
> > > and ignored files and stash push/save expose those modes as
> > > -u/--include-untracked and -a/--all.
> >
> > There may be no lies in what the above says, but we would prefer to
> > hear what the user visible implication of "passing 0" is more than
> > what mechanically is happening inside a program. For example:
> >
> > "git stash create", "git stash push", and "git stash save" are
> > commands that create a new stash entry. The latter two are also
> > responsible for storing the resulting stash entry to the reflog
> > of the "refs/stash" ref, but have options to control what is
> > included in the stash entry. Among these options, "create" only
> > supports the equivalent of "-m <message." to record in the stash
> > entry. Most notably, "-u" and "-a" options are missing.
> >
> > > Teach create to accept the same options and pass the existing mode
> > > through. Unlike push/save, create continues to only create objects: it
> > > does not update refs/stash, reset the index, or clean the working tree.
> >
> > Sure. It is a very concise and good description of what we want to
> > do.
> >
> > > Use parse_options() for the new options and stop parsing at the first
> > > non-option message word. This keeps option-like tokens after the message
> > > as message text, while leading option-like arguments now follow Git's
> > > normal option parsing. In particular, unknown or malformed leading
> > > options are rejected instead of silently becoming a message, short
> > > options may be combined, and `--` can be used when a message itself
> > > begins with a dash.
> >
> > Why do we need to go into such a detail in the log message? What is
> > the above paragraph designed to convey to the reader? Again, it may
> > not be telling any lies, but it misses the point by being inconsiderate
> > to your readers. What you need to tell them is _WHY_ you chose to
> > use parse_options() in such a way. What were you trying to achieve?
> >
> > I am guessing that something along this line ...
> >
> > "git stash create" traditionally treated the rest of the command
> > line as a message. For example,
> >
> > $ git stash create adding -u option
> >
> > has always been a request to create a stash entry with the
> > string "adding -u option" as its message. We should not make it
> > trigger the "-u" (include untracked) behavior for backward
> > compatibility, by using parse_options() with stop-at-the-non-option
> > mode to forbid it from reordering the command line arguments.
> >
> > ... was what you wanted to say, but I am not sure.
> >
> > How much of all these verbiage was written by AI by the way? You'd
> > need to spend effort to make it readable to humans.
> >
> > > Keep create's existing no-change behavior: detect the usual no-change
> > > case before do_create_stash() refreshes and writes the index, and return
> > > success without printing an object name. If do_create_stash() still
> > > reports its internal "nothing to create" result, map that to create's
> > > public success status.
> >
> > You already said that with "does not update, reset, or clean".
> >
> > > This follows the stash subcommand exit-status convention established by
> > > 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):
> > > subcommands return 0 on success, negative values on failure, and status 1
> > > when applying a stash results in conflicts. cmd_stash() maps negative
> > > subcommand failures to 128.
> >
> > Again, there may not be lies in here, but if you did not make a
> > breaking change to the established convention, is it worth saying?
> >
> > > 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the
> > > internal include-untracked path while intentionally leaving the user
> > > interface for "git stash create" unchanged. Reuse that machinery and
> > > the existing INCLUDE_ALL_FILES mode rather than adding a separate stash
> > > creation path.
> > >
> > > Add coverage for short and long aliases, combined short options, the
> > > untracked/ignored boundary including an ignored-only worktree, option
> > > parsing and dash-leading messages, no-change behavior, and preservation
> > > of refs/stash, the index state, and the working tree.
> >
> > Again, adding tests for comprehensive coverage is not something to
> > boast about. Is it worth saying?
> >
> > Aren't -p/-S/-k/-q and pathspec support all about the creating half
> > of "git stash push" that are not available to "git stash create",
> > not just "-u" and "-a"? Why are we singling out only these two? It
> > may be more worthwhile to explain the rationale behind such a design
> > decision.

```

## Phillip Wood, 2026-10-05 16:37

Subject: Re: [PATCH] stash: expose untracked modes in create
Message-ID: <632af360-5797-4794-82b7-02c7dd8f7bd4@gmail.com>
In-Reply-To: <CANUHOw201hr2LgHb1ThcadiH8Y5k3zUArnhtj8g8SRVrvMsN-g@mail.gmail.com>

```
Hi Kazumasa

On 01/10/2026 18:44, 重田一聖 wrote:
> Hi Phillip,
> > Thanks for the review.
> > Sorry, I got a little carried away and sent v2 before replying.
> >> This doesn't seem to match the code changes.
> > For the no-change case, plain "git stash create" already checks for
> tracked changes before calling do_create_stash(), and returns 0 with
> empty output when there is nothing to create.
> > For -u and -a, I think we should follow that existing "create" behavior
> as well, using check_changes() for the selected mode before calling
> do_create_stash(), and returning 0 when it finds nothing to create.

I'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.
I've sent a couple of patches [1] to fix that.

> I am also thinking of mapping do_create_stash()'s internal no-change
> status of 1 to 0 in the unlikely case where the state changes between
> these checks.

I think that is worth doing but it is not related to adding new options so should be a separate patch.
> That 1 is not STASH_APPLY_CONFLICT. Following 786fc390465f
> ("stash: reserve exit status 1 for conflicts"), I do not think it should
> escape as public exit status 1, and would map it to 0 instead.

I agree we should exit 0 in that case.
>> You should pass PARSE_OPT_STOP_AT_NON_OPTION to parse_options()
>> to prevent that.
> > I plan to use PARSE_OPT_STOP_AT_NON_OPTION as you suggested.

That's great

Thanks

Phillip

[1] https://lore.kernel.org/git/cover.1791218125.git.phillip.wood@dunelm.org.uk

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



```

## Phillip Wood, 2026-10-05 16:38

Subject: Re: [PATCH v2] stash: expose untracked modes in create
Message-ID: <1d1d2c76-9981-44ec-8ea9-8f886d49a742@gmail.com>
In-Reply-To: <CANUHOw3gynMRGN7A-wOnL3PQtgFbMtsB2gyZxaq+0Z5bHp-H8A@mail.gmail.com>

```
Hi Kazumasa

On 05/10/2026 06:55, 重田一聖 wrote:
> >  From that perspective, I can see three possible directions.
> > 1. Keep extending `stash create`.
> >     We could expose more of the existing `do_create_stash()`
>     functionality through `stash create`, following the conventions of
>     `stash push` for the creation-related options they have in common.
> >     This seems implementable, but even with
>     `PARSE_OPT_STOP_AT_NON_OPTION` it would change the handling of
>     messages that begin with an option-like argument. Those would need
>     explicit disambiguation, such as `--`.
> >     There is also the pathspec question. If positional arguments
>     continue to be joined to form the message, pathspecs need some other
>     way to be distinguished from that message.

It 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.

Thanks

Phillip

[1] This happens when a user edits a hunk that looks like
    @@ -1 +1,4 @@
    -A
    +a
    +b
    +c
    +d

    to

    @@ -1 +1,3 @@
    -A
    +a
    +b
    +d

    To clear the stashed changes, we apply the hunk in reverse, so we
    try to apply

    @@ -1,3 +1 @@
    -a
    -b
    -d
    +A

    to a file that looks like

    a
    b
    c
    d

    which fails because the '-' lines do not match the content of the
    file.

> > 2. Add a new stash subcommand for the creation functionality.
> >     This would leave the existing `stash create <message>` contract
>     unchanged. Because the new command would not inherit `create`'s
>     positional message grammar, its creation-related options and
>     pathspec handling could follow conventions similar to `stash push`.
> >     This preserves the existing `create` grammar while avoiding the need
>     to fit additional creation capabilities into it. The trade-off is
>     adding another public stash subcommand and its long-term maintenance
>     cost.
> > 3. Add something like `--create-only` to `git stash push`.
> >     This would reuse the existing `push` option grammar without adding
>     another subcommand.
> >     I also read the 2019 discussion around `git stash push --snapshot`.
>     One concern there was that approximately the same end state could
>     already be obtained with `git stash push && git stash apply`.
> >     I do not think that particular concern carries over directly here.
>     `git stash create` already stops at object creation, but its public
>     interface does not expose more of the creation capabilities already
>     available in `do_create_stash()`. There is currently no public stash
>     command that exposes those capabilities while retaining that
>     create-only boundary.
> >     That does not mean a similar result cannot be constructed by other
>     means. The missing piece is a public interface to the existing stash
>     creation machinery at that boundary.
> >     Even so, there is still the separate question of whether `push` is
>     the right place for a creation-only operation in the first place.
>     The push-specific work around `do_create_stash()` would also need to
>     be separated carefully.
> > All three seem substantially broader than the original `-u` / `-a`
> patch.
> > If this is worth pursuing further, which of these directions seems the
> most plausible? Also, is this the right thread to continue that design
> discussion, or would it be better to discuss it separately?
> > Thanks again for the guidance,
> Kazumasa Shigeta
> > On Fri, 2 Oct 2026 05:04:26 -0400, "重田一聖" <kazumasa.shigeta@kanamei.com> wrote:
>> Hi Junio,
>>
>>> we would prefer to hear what the user visible implication of
>>> "passing 0" is more than what mechanically is happening inside a
>>> program.
>>
>> The user-visible effect is that stash create cannot currently include
>> untracked or ignored files in the stash entry. If those are the only
>> changes, it creates no entry at all, while stash push and save can
>> include them with -u or -a as appropriate. I should have described that
>> difference directly instead of starting from the include_untracked
>> implementation detail.
>>
>>> ... was what you wanted to say, but I am not sure.
>>
>> Yes, exactly. I'll explain the backward-compatibility reason rather
>> than the mechanics of parse_options().
>>
>>> You already said that with "does not update, reset, or clean".
>>
>> I'll drop that paragraph.
>>
>>> if you did not make a breaking change to the established convention,
>>> is it worth saying?
>>
>> I don't think it adds anything here. I'll remove the exit-status
>> discussion from the commit message as well.
>>
>>> adding tests for comprehensive coverage is not something to boast
>>> about. Is it worth saying?
>>
>> I'll remove the test details from the commit message.
>>
>>> Why are we singling out only these two?
>>
>> I started by looking at the missing -u and -a support in create, and I
>> think that led me to focus too narrowly on those two when considering
>> the scope. I need to think more about whether this patch should remain
>> limited to those two.
>>
>> Thanks,
>> Kazumasa Shigeta
>>
>> On Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:
>>> Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:
>>>
>>>> `git stash create` always passes zero for the include_untracked parameter
>>>> of do_create_stash(), even though that helper already supports untracked
>>>> and ignored files and stash push/save expose those modes as
>>>> -u/--include-untracked and -a/--all.
>>>
>>> There may be no lies in what the above says, but we would prefer to
>>> hear what the user visible implication of "passing 0" is more than
>>> what mechanically is happening inside a program. For example:
>>>
>>> "git stash create", "git stash push", and "git stash save" are
>>> commands that create a new stash entry. The latter two are also
>>> responsible for storing the resulting stash entry to the reflog
>>> of the "refs/stash" ref, but have options to control what is
>>> included in the stash entry. Among these options, "create" only
>>> supports the equivalent of "-m <message." to record in the stash
>>> entry. Most notably, "-u" and "-a" options are missing.
>>>
>>>> Teach create to accept the same options and pass the existing mode
>>>> through. Unlike push/save, create continues to only create objects: it
>>>> does not update refs/stash, reset the index, or clean the working tree.
>>>
>>> Sure. It is a very concise and good description of what we want to
>>> do.
>>>
>>>> Use parse_options() for the new options and stop parsing at the first
>>>> non-option message word. This keeps option-like tokens after the message
>>>> as message text, while leading option-like arguments now follow Git's
>>>> normal option parsing. In particular, unknown or malformed leading
>>>> options are rejected instead of silently becoming a message, short
>>>> options may be combined, and `--` can be used when a message itself
>>>> begins with a dash.
>>>
>>> Why do we need to go into such a detail in the log message? What is
>>> the above paragraph designed to convey to the reader? Again, it may
>>> not be telling any lies, but it misses the point by being inconsiderate
>>> to your readers. What you need to tell them is _WHY_ you chose to
>>> use parse_options() in such a way. What were you trying to achieve?
>>>
>>> I am guessing that something along this line ...
>>>
>>> "git stash create" traditionally treated the rest of the command
>>> line as a message. For example,
>>>
>>> $ git stash create adding -u option
>>>
>>> has always been a request to create a stash entry with the
>>> string "adding -u option" as its message. We should not make it
>>> trigger the "-u" (include untracked) behavior for backward
>>> compatibility, by using parse_options() with stop-at-the-non-option
>>> mode to forbid it from reordering the command line arguments.
>>>
>>> ... was what you wanted to say, but I am not sure.
>>>
>>> How much of all these verbiage was written by AI by the way? You'd
>>> need to spend effort to make it readable to humans.
>>>
>>>> Keep create's existing no-change behavior: detect the usual no-change
>>>> case before do_create_stash() refreshes and writes the index, and return
>>>> success without printing an object name. If do_create_stash() still
>>>> reports its internal "nothing to create" result, map that to create's
>>>> public success status.
>>>
>>> You already said that with "does not update, reset, or clean".
>>>
>>>> This follows the stash subcommand exit-status convention established by
>>>> 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):
>>>> subcommands return 0 on success, negative values on failure, and status 1
>>>> when applying a stash results in conflicts. cmd_stash() maps negative
>>>> subcommand failures to 128.
>>>
>>> Again, there may not be lies in here, but if you did not make a
>>> breaking change to the established convention, is it worth saying?
>>>
>>>> 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the
>>>> internal include-untracked path while intentionally leaving the user
>>>> interface for "git stash create" unchanged. Reuse that machinery and
>>>> the existing INCLUDE_ALL_FILES mode rather than adding a separate stash
>>>> creation path.
>>>>
>>>> Add coverage for short and long aliases, combined short options, the
>>>> untracked/ignored boundary including an ignored-only worktree, option
>>>> parsing and dash-leading messages, no-change behavior, and preservation
>>>> of refs/stash, the index state, and the working tree.
>>>
>>> Again, adding tests for comprehensive coverage is not something to
>>> boast about. Is it worth saying?
>>>
>>> Aren't -p/-S/-k/-q and pathspec support all about the creating half
>>> of "git stash push" that are not available to "git stash create",
>>> not just "-u" and "-a"? Why are we singling out only these two? It
>>> may be more worthwhile to explain the rationale behind such a design
>>> decision.



```

## Junio C Hamano, 2026-10-05 16:43

Subject: Re: [PATCH v2] stash: expose untracked modes in create
Message-ID: <xmqq1pa4ksiw.fsf@gitster.g>
In-Reply-To: <CANUHOw3gynMRGN7A-wOnL3PQtgFbMtsB2gyZxaq+0Z5bHp-H8A@mail.gmail.com>

```
重田一聖 <kazumasa.shigeta@kanamei.com> writes:

> Your question made me realize that I had focused too narrowly on the
> untracked modes. The larger issue is not simply that `do_create_stash()`
> has capabilities that `git stash create` does not expose.

Brilliant.  I agree that is the right way to frame the issue.

> Making more of those capabilities available through `create` would
> therefore mean either changing that contract or designing around it.
> That is a much larger interface decision than I appreciated when I sent
> the patch.

Perhaps, but I do not think it is too huge a backward-compatibility
breakage to forbid giving a message lazily (i.e., all strings in
argv[] after 'git stash create' gets concatenated and becomes a
single message) that begins with "-", with an escape hatch that a
leading "-m" will take the next argv[] element as the message, for
example.

> 2. Add a new stash subcommand for the creation functionality.

This is essentially how 'git stash save' came about, to give us ways
to control how a new stash entry is created and how the working tree
is cleared with command line options.  In the beginning, you did not
even have to say 'save', because 'git stash <message>' was invented
as a way to say "the boss is here and tells me to work on something
unrelated. clear the slate with minimum number of keystrokes to
continue working on what I have been working on later."  And that
later became 'git stash push'.

> 3. Add something like `--create-only` to `git stash push`.

This also would work and sounds the safest.


```

## 重田一聖, 2026-10-06 09:25

Subject: Re: [PATCH v2] stash: expose untracked modes in create
Message-ID: <CANUHOw2OMHFJLKWkDkyDm7WtLDcYxMnNfkD8uV96GZdWBS53RA@mail.gmail.com>
In-Reply-To: <1d1d2c76-9981-44ec-8ea9-8f886d49a742@gmail.com>

```
Hi Phillip,

Thanks for the two patches removing the duplicate changes checks. I'll
wait for those to settle before revisiting the exit-status and no-change
handling.

> 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.

For `stash create`, I don't think the issue in [1] should apply, since
it does not remove the selected changes from the worktree. I still need
to think about whether `--patch` is worth supporting for `stash create`,
even though it is primarily aimed at scripts.

> I wonder if we really need pathspec support, or if we do is
> "--pathspec-from-file" sufficient?

I agree that positional pathspec support probably isn't necessary.
Since `create` is primarily aimed at scripts, `--pathspec-from-file`
seems sufficient. It also avoids giving positional arguments another
meaning while we are already dealing with the message ambiguity.

> 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.

Thanks for confirming those points.

Thanks,
Kazumasa

On Mon, 5 Oct 2026 17:38:07 +0100, Phillip Wood
<phillip.wood123@gmail.com> wrote:
> Hi Kazumasa
>
> On 05/10/2026 06:55, 重田一聖 wrote:
> >
> > From that perspective, I can see three possible directions.
> >
> > 1. Keep extending `stash create`.
> >
> > We could expose more of the existing `do_create_stash()`
> > functionality through `stash create`, following the conventions of
> > `stash push` for the creation-related options they have in common.
> >
> > This seems implementable, but even with
> > `PARSE_OPT_STOP_AT_NON_OPTION` it would change the handling of
> > messages that begin with an option-like argument. Those would need
> > explicit disambiguation, such as `--`.
> >
> > There is also the pathspec question. If positional arguments
> > continue to be joined to form the message, pathspecs need some other
> > way to be distinguished from that message.
>
> It 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.
>
> Thanks
>
> Phillip
>
> [1] This happens when a user edits a hunk that looks like
> @@ -1 +1,4 @@
> -A
> +a
> +b
> +c
> +d
>
> to
>
> @@ -1 +1,3 @@
> -A
> +a
> +b
> +d
>
> To clear the stashed changes, we apply the hunk in reverse, so we
> try to apply
>
> @@ -1,3 +1 @@
> -a
> -b
> -d
> +A
>
> to a file that looks like
>
> a
> b
> c
> d
>
> which fails because the '-' lines do not match the content of the
> file.
>
> >
> > 2. Add a new stash subcommand for the creation functionality.
> >
> > This would leave the existing `stash create <message>` contract
> > unchanged. Because the new command would not inherit `create`'s
> > positional message grammar, its creation-related options and
> > pathspec handling could follow conventions similar to `stash push`.
> >
> > This preserves the existing `create` grammar while avoiding the need
> > to fit additional creation capabilities into it. The trade-off is
> > adding another public stash subcommand and its long-term maintenance
> > cost.
> >
> > 3. Add something like `--create-only` to `git stash push`.
> >
> > This would reuse the existing `push` option grammar without adding
> > another subcommand.
> >
> > I also read the 2019 discussion around `git stash push --snapshot`.
> > One concern there was that approximately the same end state could
> > already be obtained with `git stash push && git stash apply`.
> >
> > I do not think that particular concern carries over directly here.
> > `git stash create` already stops at object creation, but its public
> > interface does not expose more of the creation capabilities already
> > available in `do_create_stash()`. There is currently no public stash
> > command that exposes those capabilities while retaining that
> > create-only boundary.
> >
> > That does not mean a similar result cannot be constructed by other
> > means. The missing piece is a public interface to the existing stash
> > creation machinery at that boundary.
> >
> > Even so, there is still the separate question of whether `push` is
> > the right place for a creation-only operation in the first place.
> > The push-specific work around `do_create_stash()` would also need to
> > be separated carefully.
> >
> > All three seem substantially broader than the original `-u` / `-a`
> > patch.
> >
> > If this is worth pursuing further, which of these directions seems the
> > most plausible? Also, is this the right thread to continue that design
> > discussion, or would it be better to discuss it separately?
> >
> > Thanks again for the guidance,
> > Kazumasa Shigeta
> >
> > On Fri, 2 Oct 2026 05:04:26 -0400, "重田一聖" <kazumasa.shigeta@kanamei.com> wrote:
> >> Hi Junio,
> >>
> >>> we would prefer to hear what the user visible implication of
> >>> "passing 0" is more than what mechanically is happening inside a
> >>> program.
> >>
> >> The user-visible effect is that stash create cannot currently include
> >> untracked or ignored files in the stash entry. If those are the only
> >> changes, it creates no entry at all, while stash push and save can
> >> include them with -u or -a as appropriate. I should have described that
> >> difference directly instead of starting from the include_untracked
> >> implementation detail.
> >>
> >>> ... was what you wanted to say, but I am not sure.
> >>
> >> Yes, exactly. I'll explain the backward-compatibility reason rather
> >> than the mechanics of parse_options().
> >>
> >>> You already said that with "does not update, reset, or clean".
> >>
> >> I'll drop that paragraph.
> >>
> >>> if you did not make a breaking change to the established convention,
> >>> is it worth saying?
> >>
> >> I don't think it adds anything here. I'll remove the exit-status
> >> discussion from the commit message as well.
> >>
> >>> adding tests for comprehensive coverage is not something to boast
> >>> about. Is it worth saying?
> >>
> >> I'll remove the test details from the commit message.
> >>
> >>> Why are we singling out only these two?
> >>
> >> I started by looking at the missing -u and -a support in create, and I
> >> think that led me to focus too narrowly on those two when considering
> >> the scope. I need to think more about whether this patch should remain
> >> limited to those two.
> >>
> >> Thanks,
> >> Kazumasa Shigeta
> >>
> >> On Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:
> >>> Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:
> >>>
> >>>> `git stash create` always passes zero for the include_untracked parameter
> >>>> of do_create_stash(), even though that helper already supports untracked
> >>>> and ignored files and stash push/save expose those modes as
> >>>> -u/--include-untracked and -a/--all.
> >>>
> >>> There may be no lies in what the above says, but we would prefer to
> >>> hear what the user visible implication of "passing 0" is more than
> >>> what mechanically is happening inside a program. For example:
> >>>
> >>> "git stash create", "git stash push", and "git stash save" are
> >>> commands that create a new stash entry. The latter two are also
> >>> responsible for storing the resulting stash entry to the reflog
> >>> of the "refs/stash" ref, but have options to control what is
> >>> included in the stash entry. Among these options, "create" only
> >>> supports the equivalent of "-m <message." to record in the stash
> >>> entry. Most notably, "-u" and "-a" options are missing.
> >>>
> >>>> Teach create to accept the same options and pass the existing mode
> >>>> through. Unlike push/save, create continues to only create objects: it
> >>>> does not update refs/stash, reset the index, or clean the working tree.
> >>>
> >>> Sure. It is a very concise and good description of what we want to
> >>> do.
> >>>
> >>>> Use parse_options() for the new options and stop parsing at the first
> >>>> non-option message word. This keeps option-like tokens after the message
> >>>> as message text, while leading option-like arguments now follow Git's
> >>>> normal option parsing. In particular, unknown or malformed leading
> >>>> options are rejected instead of silently becoming a message, short
> >>>> options may be combined, and `--` can be used when a message itself
> >>>> begins with a dash.
> >>>
> >>> Why do we need to go into such a detail in the log message? What is
> >>> the above paragraph designed to convey to the reader? Again, it may
> >>> not be telling any lies, but it misses the point by being inconsiderate
> >>> to your readers. What you need to tell them is _WHY_ you chose to
> >>> use parse_options() in such a way. What were you trying to achieve?
> >>>
> >>> I am guessing that something along this line ...
> >>>
> >>> "git stash create" traditionally treated the rest of the command
> >>> line as a message. For example,
> >>>
> >>> $ git stash create adding -u option
> >>>
> >>> has always been a request to create a stash entry with the
> >>> string "adding -u option" as its message. We should not make it
> >>> trigger the "-u" (include untracked) behavior for backward
> >>> compatibility, by using parse_options() with stop-at-the-non-option
> >>> mode to forbid it from reordering the command line arguments.
> >>>
> >>> ... was what you wanted to say, but I am not sure.
> >>>
> >>> How much of all these verbiage was written by AI by the way? You'd
> >>> need to spend effort to make it readable to humans.
> >>>
> >>>> Keep create's existing no-change behavior: detect the usual no-change
> >>>> case before do_create_stash() refreshes and writes the index, and return
> >>>> success without printing an object name. If do_create_stash() still
> >>>> reports its internal "nothing to create" result, map that to create's
> >>>> public success status.
> >>>
> >>> You already said that with "does not update, reset, or clean".
> >>>
> >>>> This follows the stash subcommand exit-status convention established by
> >>>> 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):
> >>>> subcommands return 0 on success, negative values on failure, and status 1
> >>>> when applying a stash results in conflicts. cmd_stash() maps negative
> >>>> subcommand failures to 128.
> >>>
> >>> Again, there may not be lies in here, but if you did not make a
> >>> breaking change to the established convention, is it worth saying?
> >>>
> >>>> 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the
> >>>> internal include-untracked path while intentionally leaving the user
> >>>> interface for "git stash create" unchanged. Reuse that machinery and
> >>>> the existing INCLUDE_ALL_FILES mode rather than adding a separate stash
> >>>> creation path.
> >>>>
> >>>> Add coverage for short and long aliases, combined short options, the
> >>>> untracked/ignored boundary including an ignored-only worktree, option
> >>>> parsing and dash-leading messages, no-change behavior, and preservation
> >>>> of refs/stash, the index state, and the working tree.
> >>>
> >>> Again, adding tests for comprehensive coverage is not something to
> >>> boast about. Is it worth saying?
> >>>
> >>> Aren't -p/-S/-k/-q and pathspec support all about the creating half
> >>> of "git stash push" that are not available to "git stash create",
> >>> not just "-u" and "-a"? Why are we singling out only these two? It
> >>> may be more worthwhile to explain the rationale behind such a design
> >>> decision.


```

## Phillip Wood, 2026-10-06 09:57

Subject: Re: [PATCH v2] stash: expose untracked modes in create
Message-ID: <7af72eb3-9a61-43c8-a9c0-faaff1817949@gmail.com>
In-Reply-To: <CANUHOw2OMHFJLKWkDkyDm7WtLDcYxMnNfkD8uV96GZdWBS53RA@mail.gmail.com>

```
Hi Kazumasa

On 06/10/2026 10:25, 重田一聖 wrote:
> Hi Phillip,
> > Thanks for the two patches removing the duplicate changes checks. I'll
> wait for those to settle before revisiting the exit-status and no-change
> handling.
> >> 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.
> > For `stash create`, I don't think the issue in [1] should apply, since
> 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.

Thanks

Phillip
> I still need
> to think about whether `--patch` is worth supporting for `stash create`,
> even though it is primarily aimed at scripts.
> >> I wonder if we really need pathspec support, or if we do is
>> "--pathspec-from-file" sufficient?
> > I agree that positional pathspec support probably isn't necessary.
> Since `create` is primarily aimed at scripts, `--pathspec-from-file`
> seems sufficient. It also avoids giving positional arguments another
> meaning while we are already dealing with the message ambiguity.
> >> 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.
> > Thanks for confirming those points.
> > Thanks,
> Kazumasa
> > On Mon, 5 Oct 2026 17:38:07 +0100, Phillip Wood
> <phillip.wood123@gmail.com> wrote:
>> Hi Kazumasa
>>
>> On 05/10/2026 06:55, 重田一聖 wrote:
>>>
>>>  From that perspective, I can see three possible directions.
>>>
>>> 1. Keep extending `stash create`.
>>>
>>> We could expose more of the existing `do_create_stash()`
>>> functionality through `stash create`, following the conventions of
>>> `stash push` for the creation-related options they have in common.
>>>
>>> This seems implementable, but even with
>>> `PARSE_OPT_STOP_AT_NON_OPTION` it would change the handling of
>>> messages that begin with an option-like argument. Those would need
>>> explicit disambiguation, such as `--`.
>>>
>>> There is also the pathspec question. If positional arguments
>>> continue to be joined to form the message, pathspecs need some other
>>> way to be distinguished from that message.
>>
>> It 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.
>>
>> Thanks
>>
>> Phillip
>>
>> [1] This happens when a user edits a hunk that looks like
>> @@ -1 +1,4 @@
>> -A
>> +a
>> +b
>> +c
>> +d
>>
>> to
>>
>> @@ -1 +1,3 @@
>> -A
>> +a
>> +b
>> +d
>>
>> To clear the stashed changes, we apply the hunk in reverse, so we
>> try to apply
>>
>> @@ -1,3 +1 @@
>> -a
>> -b
>> -d
>> +A
>>
>> to a file that looks like
>>
>> a
>> b
>> c
>> d
>>
>> which fails because the '-' lines do not match the content of the
>> file.
>>
>>>
>>> 2. Add a new stash subcommand for the creation functionality.
>>>
>>> This would leave the existing `stash create <message>` contract
>>> unchanged. Because the new command would not inherit `create`'s
>>> positional message grammar, its creation-related options and
>>> pathspec handling could follow conventions similar to `stash push`.
>>>
>>> This preserves the existing `create` grammar while avoiding the need
>>> to fit additional creation capabilities into it. The trade-off is
>>> adding another public stash subcommand and its long-term maintenance
>>> cost.
>>>
>>> 3. Add something like `--create-only` to `git stash push`.
>>>
>>> This would reuse the existing `push` option grammar without adding
>>> another subcommand.
>>>
>>> I also read the 2019 discussion around `git stash push --snapshot`.
>>> One concern there was that approximately the same end state could
>>> already be obtained with `git stash push && git stash apply`.
>>>
>>> I do not think that particular concern carries over directly here.
>>> `git stash create` already stops at object creation, but its public
>>> interface does not expose more of the creation capabilities already
>>> available in `do_create_stash()`. There is currently no public stash
>>> command that exposes those capabilities while retaining that
>>> create-only boundary.
>>>
>>> That does not mean a similar result cannot be constructed by other
>>> means. The missing piece is a public interface to the existing stash
>>> creation machinery at that boundary.
>>>
>>> Even so, there is still the separate question of whether `push` is
>>> the right place for a creation-only operation in the first place.
>>> The push-specific work around `do_create_stash()` would also need to
>>> be separated carefully.
>>>
>>> All three seem substantially broader than the original `-u` / `-a`
>>> patch.
>>>
>>> If this is worth pursuing further, which of these directions seems the
>>> most plausible? Also, is this the right thread to continue that design
>>> discussion, or would it be better to discuss it separately?
>>>
>>> Thanks again for the guidance,
>>> Kazumasa Shigeta
>>>
>>> On Fri, 2 Oct 2026 05:04:26 -0400, "重田一聖" <kazumasa.shigeta@kanamei.com> wrote:
>>>> Hi Junio,
>>>>
>>>>> we would prefer to hear what the user visible implication of
>>>>> "passing 0" is more than what mechanically is happening inside a
>>>>> program.
>>>>
>>>> The user-visible effect is that stash create cannot currently include
>>>> untracked or ignored files in the stash entry. If those are the only
>>>> changes, it creates no entry at all, while stash push and save can
>>>> include them with -u or -a as appropriate. I should have described that
>>>> difference directly instead of starting from the include_untracked
>>>> implementation detail.
>>>>
>>>>> ... was what you wanted to say, but I am not sure.
>>>>
>>>> Yes, exactly. I'll explain the backward-compatibility reason rather
>>>> than the mechanics of parse_options().
>>>>
>>>>> You already said that with "does not update, reset, or clean".
>>>>
>>>> I'll drop that paragraph.
>>>>
>>>>> if you did not make a breaking change to the established convention,
>>>>> is it worth saying?
>>>>
>>>> I don't think it adds anything here. I'll remove the exit-status
>>>> discussion from the commit message as well.
>>>>
>>>>> adding tests for comprehensive coverage is not something to boast
>>>>> about. Is it worth saying?
>>>>
>>>> I'll remove the test details from the commit message.
>>>>
>>>>> Why are we singling out only these two?
>>>>
>>>> I started by looking at the missing -u and -a support in create, and I
>>>> think that led me to focus too narrowly on those two when considering
>>>> the scope. I need to think more about whether this patch should remain
>>>> limited to those two.
>>>>
>>>> Thanks,
>>>> Kazumasa Shigeta
>>>>
>>>> On Thu, 01 Oct 2026 10:03:13 -0700, Junio C Hamano <gitster@pobox.com> wrote:
>>>>> Kazumasa Shigeta <kazumasa.shigeta@kanamei.com> writes:
>>>>>
>>>>>> `git stash create` always passes zero for the include_untracked parameter
>>>>>> of do_create_stash(), even though that helper already supports untracked
>>>>>> and ignored files and stash push/save expose those modes as
>>>>>> -u/--include-untracked and -a/--all.
>>>>>
>>>>> There may be no lies in what the above says, but we would prefer to
>>>>> hear what the user visible implication of "passing 0" is more than
>>>>> what mechanically is happening inside a program. For example:
>>>>>
>>>>> "git stash create", "git stash push", and "git stash save" are
>>>>> commands that create a new stash entry. The latter two are also
>>>>> responsible for storing the resulting stash entry to the reflog
>>>>> of the "refs/stash" ref, but have options to control what is
>>>>> included in the stash entry. Among these options, "create" only
>>>>> supports the equivalent of "-m <message." to record in the stash
>>>>> entry. Most notably, "-u" and "-a" options are missing.
>>>>>
>>>>>> Teach create to accept the same options and pass the existing mode
>>>>>> through. Unlike push/save, create continues to only create objects: it
>>>>>> does not update refs/stash, reset the index, or clean the working tree.
>>>>>
>>>>> Sure. It is a very concise and good description of what we want to
>>>>> do.
>>>>>
>>>>>> Use parse_options() for the new options and stop parsing at the first
>>>>>> non-option message word. This keeps option-like tokens after the message
>>>>>> as message text, while leading option-like arguments now follow Git's
>>>>>> normal option parsing. In particular, unknown or malformed leading
>>>>>> options are rejected instead of silently becoming a message, short
>>>>>> options may be combined, and `--` can be used when a message itself
>>>>>> begins with a dash.
>>>>>
>>>>> Why do we need to go into such a detail in the log message? What is
>>>>> the above paragraph designed to convey to the reader? Again, it may
>>>>> not be telling any lies, but it misses the point by being inconsiderate
>>>>> to your readers. What you need to tell them is _WHY_ you chose to
>>>>> use parse_options() in such a way. What were you trying to achieve?
>>>>>
>>>>> I am guessing that something along this line ...
>>>>>
>>>>> "git stash create" traditionally treated the rest of the command
>>>>> line as a message. For example,
>>>>>
>>>>> $ git stash create adding -u option
>>>>>
>>>>> has always been a request to create a stash entry with the
>>>>> string "adding -u option" as its message. We should not make it
>>>>> trigger the "-u" (include untracked) behavior for backward
>>>>> compatibility, by using parse_options() with stop-at-the-non-option
>>>>> mode to forbid it from reordering the command line arguments.
>>>>>
>>>>> ... was what you wanted to say, but I am not sure.
>>>>>
>>>>> How much of all these verbiage was written by AI by the way? You'd
>>>>> need to spend effort to make it readable to humans.
>>>>>
>>>>>> Keep create's existing no-change behavior: detect the usual no-change
>>>>>> case before do_create_stash() refreshes and writes the index, and return
>>>>>> success without printing an object name. If do_create_stash() still
>>>>>> reports its internal "nothing to create" result, map that to create's
>>>>>> public success status.
>>>>>
>>>>> You already said that with "does not update, reset, or clean".
>>>>>
>>>>>> This follows the stash subcommand exit-status convention established by
>>>>>> 786fc390465f (stash: reserve exit status 1 for conflicts, 2026-09-03):
>>>>>> subcommands return 0 on success, negative values on failure, and status 1
>>>>>> when applying a stash results in conflicts. cmd_stash() maps negative
>>>>>> subcommand failures to 128.
>>>>>
>>>>> Again, there may not be lies in here, but if you did not make a
>>>>> breaking change to the established convention, is it worth saying?
>>>>>
>>>>>> 9ca6326dff29 (stash: refactor stash_create, 2017-02-19) added the
>>>>>> internal include-untracked path while intentionally leaving the user
>>>>>> interface for "git stash create" unchanged. Reuse that machinery and
>>>>>> the existing INCLUDE_ALL_FILES mode rather than adding a separate stash
>>>>>> creation path.
>>>>>>
>>>>>> Add coverage for short and long aliases, combined short options, the
>>>>>> untracked/ignored boundary including an ignored-only worktree, option
>>>>>> parsing and dash-leading messages, no-change behavior, and preservation
>>>>>> of refs/stash, the index state, and the working tree.
>>>>>
>>>>> Again, adding tests for comprehensive coverage is not something to
>>>>> boast about. Is it worth saying?
>>>>>
>>>>> Aren't -p/-S/-k/-q and pathspec support all about the creating half
>>>>> of "git stash push" that are not available to "git stash create",
>>>>> not just "-u" and "-a"? Why are we singling out only these two? It
>>>>> may be more worthwhile to explain the rationale behind such a design
>>>>> decision.



```
