git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2] stash: expose untracked modes in create

From
KSKazumasa Shigeta <kazumasa.shigeta@kanamei.com>
Date
Oct 1, 2026, 04:21 UTC
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
Previous: Phillip WoodNext: Patrick Steinhardt
Message 5 of 20 in “stash: expose untracked modes in create”
  1. stash: expose untracked modes in createKazumasa Shigeta, Sep 29, 2026
  2. Phillip WoodSep 29, 2026
  3. 重田一聖Oct 1, 2026
  4. Phillip WoodOct 5, 2026
  5. stash: expose untracked modes in createKazumasa Shigeta, Oct 1, 2026
  6. Patrick SteinhardtOct 1, 2026
  7. 重田一聖Oct 1, 2026
  8. Junio C HamanoOct 1, 2026
  9. Junio C HamanoOct 1, 2026
  10. 重田一聖Oct 2, 2026
  11. 重田一聖Oct 2, 2026
  12. 重田一聖Oct 5, 2026
  13. Phillip WoodOct 5, 2026
  14. 重田一聖Oct 6, 2026
  15. Phillip WoodOct 6, 2026
  16. 重田一聖Oct 8, 2026
  17. Junio C HamanoOct 5, 2026
  18. 重田一聖Oct 8, 2026
  19. Junio C HamanoOct 9, 2026
  20. 重田一聖Oct 10, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.