Subject: [PATCH v2] stash: show correct entries count

From: "Miroma via GitGitGadget" <[email-masked]>
Date: Thu, 09 Oct 2025 11:03:03 +0000

From: Miroma <[email-masked]>

Currently, 'stash apply' internally calls 'status' to show a summary of
what the command did. This also happens with 'stash pop', which roughly
corresponds to 'apply' + 'drop'.

When the configuration 'status.showStash' is set, 'status' shows a count
of stashed entries. This leads to the following, confusing, output:

```sh
$ # assuming there are two stashed entries
$ git stash pop
...
Your stash currently has 2 entries
Dropped refs/stash@{0} (abc123...)
$ # when actually...
$ git status --show-stash
...
Your stash currently has 1 entry
```

This patch changes the output format to the following, no matter the
value of 'status.showStash':

```sh
$ # assuming there's a stashed entry
$ git stash pop
...
Dropped refs/stash@{0} (abc123...)
Your stash now has 0 entries
```

This new output is only shown if the stash count has changed, which can
happen with the following subcommands:
'drop', 'pop', 'branch', 'store', 'push', 'save'.

Helped-by: Karthik Nayak <[email-masked]>
Helped-by: Junio C Hamano <[email-masked]>
Signed-off-by: Miroma <[email-masked]>
---
    stash: show correct entries count

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2067%2Fits-miroma%2Fstash-no-status-showStash-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2067/its-miroma/stash-no-status-showStash-v2
Pull-Request: https://github.com/git/git/pull/2067

Range-diff vs v1:

 1:  32a45cf3d2 < -:  ---------- stash: don't show irrelevant entry count in status
 -:  ---------- > 1:  1118bd68ac stash: show correct entries count

 builtin/stash.c  | 31 ++++++++++++++++++--
 t/t3903-stash.sh | 75 ++++++++++++++++++++++++++++--------------------
 wt-status.c      |  2 +-
 wt-status.h      |  1 +
 4 files changed, 75 insertions(+), 34 deletions(-)

diff --git a/builtin/stash.c b/builtin/stash.c
index 1977e50df2..1bee391fb2 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -32,6 +32,7 @@
 #include "add-interactive.h"
 #include "oid-array.h"
 #include "commit.h"
+#include "wt-status.h"
 
 #define INCLUDE_ALL_FILES 2
 
@@ -585,6 +586,20 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)
 		die(_("could not write index"));
 }
 
+static int previous_stash_count = -1;
+
+static void print_stash_status(bool quiet)
+{
+	int stash_count;
+	stash_count = count_stash_entries();
+
+	if (quiet || stash_count == previous_stash_count || previous_stash_count == -1)
+		return;
+
+	printf_ln(Q_("Your stash now has %d entry",
+		"Your stash now has %d entries", stash_count), stash_count);
+}
+
 static int do_apply_stash(const char *prefix, struct stash_info *info,
 			  int index, int quiet)
 {
@@ -705,7 +720,7 @@ restore_untracked:
 			     absolute_path(repo_get_work_tree(the_repository)));
 		strvec_pushf(&cp.env, GIT_DIR_ENVIRONMENT"=%s",
 			     absolute_path(repo_get_git_dir(the_repository)));
-		strvec_push(&cp.args, "status");
+		strvec_pushl(&cp.args, "status", "--no-show-stash", NULL);
 		run_command(&cp);
 	}
 
@@ -756,6 +771,8 @@ static int reflog_is_empty(const char *refname)
 
 static int do_drop_stash(struct stash_info *info, int quiet)
 {
+	previous_stash_count = count_stash_entries();
+
 	if (!reflog_delete(info->revision.buf,
 			   EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_UPDATE_REF,
 			   0)) {
@@ -805,6 +822,8 @@ static int drop_stash(int argc, const char **argv, const char *prefix,
 		goto cleanup;
 
 	ret = do_drop_stash(&info, quiet);
+	print_stash_status(quiet);
+
 cleanup:
 	free_stash_info(&info);
 	return ret;
@@ -836,6 +855,8 @@ static int pop_stash(int argc, const char **argv, const char *prefix,
 	else
 		ret = do_drop_stash(&info, quiet);
 
+	print_stash_status(quiet);
+
 cleanup:
 	free_stash_info(&info);
 	return ret;
@@ -875,6 +896,8 @@ static int branch_stash(int argc, const char **argv, const char *prefix,
 	if (!ret && info.is_stash_ref)
 		ret = do_drop_stash(&info, 0);
 
+	print_stash_status(0);
+
 cleanup:
 	free_stash_info(&info);
 	return ret;
@@ -1062,6 +1085,7 @@ static int do_store_stash(const struct object_id *w_commit, const char *stash_ms
 	struct stash_info info;
 	char revision[GIT_MAX_HEXSZ];
 
+	previous_stash_count = count_stash_entries();
 	oid_to_hex_r(revision, w_commit);
 	assert_stash_like(&info, revision);
 
@@ -1119,6 +1143,7 @@ static int store_stash(int argc, const char **argv, const char *prefix,
 	}
 
 	ret = do_store_stash(&obj, stash_msg, quiet);
+	print_stash_status(quiet);
 
 out:
 	object_context_release(&dummy);
@@ -1914,6 +1939,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,
 
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
 			    &add_p_opt, include_untracked, only_staged);
+	print_stash_status(quiet);
 
 	clear_pathspec(&ps);
 	free(pathspec_from_file);
@@ -1982,6 +2008,7 @@ static int save_stash(int argc, const char **argv, const char *prefix,
 	ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
 			    patch_mode, &add_p_opt, include_untracked,
 			    only_staged);
+	print_stash_status(quiet);
 
 	strbuf_release(&stash_msg_buf);
 	return ret;
@@ -2291,7 +2318,7 @@ static int do_export_stash(struct repository *r,
 			.r = r, .items = iter,
 		};
 		if (refs_for_each_reflog_ent_reverse(get_main_ref_store(r),
-						     "refs/stash",
+						     ref_stash,
 						     collect_stash_entries,
 						     &cb_data) && cb_data.count)
 			goto out;
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 0bb4648e36..d3af50e404 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -49,7 +49,7 @@ diff_cmp () {
 	rm -f "$1.compare" "$2.compare"
 }
 
-setup_stash() {
+setup_stash () {
 	echo 1 >file &&
 	git add file &&
 	echo unrelated >other-file &&
@@ -60,13 +60,15 @@ setup_stash() {
 	git add file &&
 	echo 3 >file &&
 	test_tick &&
-	git stash &&
+	git stash | tail -n 1 >actual &&
 	git diff-files --quiet &&
 	git diff-index --cached --quiet HEAD
 }
 
-test_expect_success 'stash some dirty working directory' '
-	setup_stash
+test_expect_success 'stash reports stash count' '
+	setup_stash &&
+	echo "Your stash now has 1 entry" >expect &&
+	test_cmp expect actual
 '
 
 cat >expect <<EOF
@@ -145,12 +147,14 @@ test_expect_success 'stash drop complains of extra options' '
 
 test_expect_success 'drop top stash' '
 	git reset --hard &&
-	git stash list >expected &&
+	git stash list >expect &&
 	echo 7 >file &&
 	git stash &&
-	git stash drop &&
+	git stash drop | tail -n 1 >msg-actual &&
+	echo "Your stash now has 1 entry" >msg-expect &&
+	test_cmp msg-expect msg-actual &&
 	git stash list >actual &&
-	test_cmp expected actual &&
+	test_cmp expect actual &&
 	git stash apply &&
 	test 3 = $(cat file) &&
 	test 1 = $(git show :file) &&
@@ -236,14 +240,18 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
 
 test_expect_success 'stash pop' '
 	git reset --hard &&
-	git stash pop &&
+	git stash pop | tail -n 1 >actual &&
+	echo "Your stash now has 0 entries" >expect &&
+	test_cmp expect actual &&
 	test 3 = $(cat file) &&
 	test 1 = $(git show :file) &&
 	test 1 = $(git show HEAD:file) &&
 	test 0 = $(git stash list | wc -l)
 '
 
-cat >expect <<EOF
+echo "Your stash now has 0 entries" >expect
+
+cat >expect1 <<EOF
 diff --git a/file2 b/file2
 new file mode 100644
 index 0000000..1fe912c
@@ -253,7 +261,7 @@ index 0000000..1fe912c
 +bar2
 EOF
 
-cat >expect1 <<EOF
+cat >expect2 <<EOF
 diff --git a/file b/file
 index 257cc56..5716ca5 100644
 --- a/file
@@ -263,7 +271,7 @@ index 257cc56..5716ca5 100644
 +bar
 EOF
 
-cat >expect2 <<EOF
+cat >expect3 <<EOF
 diff --git a/file b/file
 index 7601807..5716ca5 100644
 --- a/file
@@ -289,17 +297,18 @@ test_expect_success 'stash branch' '
 	git stash &&
 	echo baz >file &&
 	git commit file -m second &&
-	git stash branch stashbranch &&
+	git stash branch stashbranch | tail -n 1 >actual &&
+	test_cmp expect actual &&
 	test refs/heads/stashbranch = $(git symbolic-ref HEAD) &&
 	test $(git rev-parse HEAD) = $(git rev-parse main^) &&
 	git diff --cached >output &&
-	diff_cmp expect output &&
-	git diff >output &&
 	diff_cmp expect1 output &&
+	git diff >output &&
+	diff_cmp expect2 output &&
 	git add file &&
 	git commit -m alternate\ second &&
 	git diff main..stashbranch >output &&
-	diff_cmp output expect2 &&
+	diff_cmp output expect3 &&
 	test 0 = $(git stash list | wc -l)
 '
 
@@ -427,7 +436,9 @@ test_expect_success 'stash an added file' '
 	git reset --hard &&
 	echo new >file3 &&
 	git add file3 &&
-	git stash save "added file" &&
+	git stash save "added file" | tail -n 1 >actual &&
+	echo "Your stash now has 6 entries" >expect &&
+	test_cmp expect actual &&
 	! test -r file3 &&
 	git stash apply &&
 	test new = "$(cat file3)"
@@ -683,12 +694,12 @@ test_expect_success 'stash show format defaults to --stat' '
 	echo bar >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
-	cat >expected <<-EOF &&
+	cat >expect <<-EOF &&
 	 file | 1 +
 	 1 file changed, 1 insertion(+)
 	EOF
 	git stash show ${STASH_ID} >actual &&
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'stash show - stashes on stack, stash-like argument' '
@@ -701,9 +712,9 @@ test_expect_success 'stash show - stashes on stack, stash-like argument' '
 	echo bar >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
-	echo "1	0	file" >expected &&
+	echo "1	0	file" >expect &&
 	git stash show --numstat ${STASH_ID} >actual &&
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'stash show -p - stashes on stack, stash-like argument' '
@@ -716,7 +727,7 @@ test_expect_success 'stash show -p - stashes on stack, stash-like argument' '
 	echo bar >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
-	cat >expected <<-EOF &&
+	cat >expect <<-EOF &&
 	diff --git a/file b/file
 	index 7601807..935fbd3 100644
 	--- a/file
@@ -726,7 +737,7 @@ test_expect_success 'stash show -p - stashes on stack, stash-like argument' '
 	+bar
 	EOF
 	git stash show -p ${STASH_ID} >actual &&
-	diff_cmp expected actual
+	diff_cmp expect actual
 '
 
 test_expect_success 'stash show - no stashes on stack, stash-like argument' '
@@ -736,9 +747,9 @@ test_expect_success 'stash show - no stashes on stack, stash-like argument' '
 	echo foo >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
-	echo "1	0	file" >expected &&
+	echo "1	0	file" >expect &&
 	git stash show --numstat ${STASH_ID} >actual &&
-	test_cmp expected actual
+	test_cmp expect actual
 '
 
 test_expect_success 'stash show -p - no stashes on stack, stash-like argument' '
@@ -748,7 +759,7 @@ test_expect_success 'stash show -p - no stashes on stack, stash-like argument' '
 	echo foo >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
-	cat >expected <<-EOF &&
+	cat >expect <<-EOF &&
 	diff --git a/file b/file
 	index 7601807..71b52c4 100644
 	--- a/file
@@ -758,7 +769,7 @@ test_expect_success 'stash show -p - no stashes on stack, stash-like argument' '
 	+foo
 	EOF
 	git stash show -p ${STASH_ID} >actual &&
-	diff_cmp expected actual
+	diff_cmp expect actual
 '
 
 test_expect_success 'stash show --patience shows diff' '
@@ -766,7 +777,7 @@ test_expect_success 'stash show --patience shows diff' '
 	echo foo >>file &&
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
-	cat >expected <<-EOF &&
+	cat >expect <<-EOF &&
 	diff --git a/file b/file
 	index 7601807..71b52c4 100644
 	--- a/file
@@ -776,7 +787,7 @@ test_expect_success 'stash show --patience shows diff' '
 	+foo
 	EOF
 	git stash show --patience ${STASH_ID} >actual &&
-	diff_cmp expected actual
+	diff_cmp expect actual
 '
 
 test_expect_success 'drop: fail early if specified stash is not a stash ref' '
@@ -915,7 +926,7 @@ test_expect_success 'apply: show same status as git status (relative to ./)' '
 		sane_unset GIT_MERGE_VERBOSITY &&
 		git stash apply
 	) |
-	sed -e 1d >actual && # drop "Saved..."
+	sed -e 1,2d >actual && # drop "Your stash now has 1 entry" and "Saved..."
 	test_cmp expect actual
 '
 
@@ -959,9 +970,11 @@ test_expect_success 'store updates stash ref and reflog' '
 	STASH_ID=$(git stash create) &&
 	git reset --hard &&
 	test_path_is_missing bazzy &&
-	git stash store -m quuxery $STASH_ID &&
+	git stash store -m quuxery $STASH_ID | tail -n 1 >actual &&
+	echo "Your stash now has 1 entry" >expect &&
+	test_cmp expect actual &&
 	test $(git rev-parse stash) = $STASH_ID &&
-	git reflog --format=%H stash| grep $STASH_ID &&
+	git reflog --format=%H stash | grep $STASH_ID &&
 	git stash pop &&
 	grep quux bazzy
 '
diff --git a/wt-status.c b/wt-status.c
index 454601afa1..6bdf71efee 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -983,7 +983,7 @@ static int stash_count_refs(struct object_id *ooid UNUSED,
 	return 0;
 }
 
-static int count_stash_entries(void)
+int count_stash_entries(void)
 {
 	int n = 0;
 	refs_for_each_reflog_ent(get_main_ref_store(the_repository),
diff --git a/wt-status.h b/wt-status.h
index 4e377ce62b..daccde0b6e 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -151,6 +151,7 @@ size_t wt_status_locate_end(const char *s, size_t len);
 void wt_status_append_cut_line(struct strbuf *buf);
 void wt_status_add_cut_line(struct wt_status *s);
 void wt_status_prepare(struct repository *r, struct wt_status *s);
+int count_stash_entries(void);
 void wt_status_print(struct wt_status *s);
 void wt_status_collect(struct wt_status *s);
 /*

base-commit: c44beea485f0f2feaf460e2ac87fdd5608d63cf0
-- 
gitgitgadget