* [PATCH 0/2] stash: stop checking for changes twice
@ 2026-10-05 16:35 Phillip Wood
2026-10-05 16:35 ` [PATCH 1/2] stash create: remove duplicate changes detection Phillip Wood
` (2 more replies)
0 siblings, 3 replies; 20+ messages in thread
From: Phillip Wood @ 2026-10-05 16:35 UTC (permalink / raw)
To: git; +Cc: 重田一聖, Phillip Wood
"git stash push" and "git stash create" check if there are
any unstaged or uncommitted changes at startup, and then again
when they try to create the stash. This short series removes
that duplication of effort which I spotted while looking at
<20260929074222.11942-1-kazumasa.shigeta@kanamei.com>.
base-commit: c46c1e37724f0478939de636ab8ea5a89086d532
Published-As: https://github.com/phillipwood/git/releases/tag/pw%2Fstash-optimize-check_changes-calls%2Fv1
View-Changes-At: https://github.com/phillipwood/git/compare/c46c1e377...95b7d582a
Fetch-It-Via: git fetch https://github.com/phillipwood/git pw/stash-optimize-check_changes-calls/v1
Phillip Wood (2):
stash create: remove duplicate changes detection
stash push: remove duplicate changes detection
builtin/stash.c | 27 +++++++++++++++------------
1 file changed, 15 insertions(+), 12 deletions(-)
--
2.56.0.134.g299a3c16181
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH 1/2] stash create: remove duplicate changes detection 2026-10-05 16:35 [PATCH 0/2] stash: stop checking for changes twice Phillip Wood @ 2026-10-05 16:35 ` Phillip Wood 2026-10-06 12:44 ` Junio C Hamano 2026-10-05 16:35 ` [PATCH 2/2] stash push: " Phillip Wood 2026-10-14 15:40 ` [PATCH v2 0/7] stash creation: avoid unecessary index loads and refreshes Phillip Wood 2 siblings, 1 reply; 20+ messages in thread From: Phillip Wood @ 2026-10-05 16:35 UTC (permalink / raw) To: git; +Cc: 重田一聖, Phillip Wood From: Phillip Wood <phillip.wood@dunelm.org.uk> Before it creates a stash, git checks if there are any unstaged, or uncommitted changes. If there isn't anything to stash it bails out. Since ef0f0b4509 (stash: optimize `get_untracked_files()` and `check_changes()`, 2019-02-25) "git stash store" has checked for changes twice, once in create_stash() before we refresh the index and then again in do_create_stash() after the index has been refreshed. That commit claims it is an optimization but it is not clear what it is trying to optimize by checking for changes twice, especially as checking for changes before refreshing the index is unreliable (the scripted version of "git stash store", called "git update-index -q --refresh" before looking for any changes). Avoid checking for changes twice by removing the call to check_changes_tracked_files() from store_stash() and restore the return code handling in store_stash() that was removed by ef0f0b4509 so that we continue to exit 0 when there are no changes to stash. In principle we could remove the call to check_changes() from do_store_stash() instead, but then we'd need to pass in the list of untracked files. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> --- builtin/stash.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index 7a9843413b1..9a5006e3d92 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1655,8 +1655,6 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED, strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' '); memset(&ps, 0, sizeof(ps)); - if (!check_changes_tracked_files(&ps)) - return 0; ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info, NULL, 0); @@ -1665,7 +1663,11 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED, free_stash_info(&info); strbuf_release(&stash_msg_buf); - return ret; + /* + * ret is 1 if there were no changes. In this case, we should + * not error out. + */ + return ret < 0; } static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet, -- 2.56.0.134.g299a3c16181 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 1/2] stash create: remove duplicate changes detection 2026-10-05 16:35 ` [PATCH 1/2] stash create: remove duplicate changes detection Phillip Wood @ 2026-10-06 12:44 ` Junio C Hamano 2026-10-07 13:49 ` Phillip Wood 0 siblings, 1 reply; 20+ messages in thread From: Junio C Hamano @ 2026-10-06 12:44 UTC (permalink / raw) To: Phillip Wood; +Cc: git, 重田一聖 Phillip Wood <phillip.wood123@gmail.com> writes: > From: Phillip Wood <phillip.wood@dunelm.org.uk> > > Before it creates a stash, git checks if there are any unstaged, > or uncommitted changes. If there isn't anything to stash it bails > out. Since ef0f0b4509 (stash: optimize `get_untracked_files()` > and `check_changes()`, 2019-02-25) "git stash store" has checked "store"? Aren't we talking about "create"? > unreliable (the scripted version of "git stash store", called "git > update-index -q --refresh" before looking for any changes). Ditto. > Avoid checking for changes twice by removing the call to > check_changes_tracked_files() from store_stash() and restore the return > code handling in store_stash() that was removed by ef0f0b4509 so that > we continue to exit 0 when there are no changes to stash. In principle > we could remove the call to check_changes() from do_store_stash() > instead, but then we'd need to pass in the list of untracked files. Again "(do_)?store" -> "\1create"? ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/2] stash create: remove duplicate changes detection 2026-10-06 12:44 ` Junio C Hamano @ 2026-10-07 13:49 ` Phillip Wood 0 siblings, 0 replies; 20+ messages in thread From: Phillip Wood @ 2026-10-07 13:49 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, 重田一聖 On 06/10/2026 13:44, Junio C Hamano wrote: > Phillip Wood <phillip.wood123@gmail.com> writes: > >> From: Phillip Wood <phillip.wood@dunelm.org.uk> >> >> Before it creates a stash, git checks if there are any unstaged, >> or uncommitted changes. If there isn't anything to stash it bails >> out. Since ef0f0b4509 (stash: optimize `get_untracked_files()` >> and `check_changes()`, 2019-02-25) "git stash store" has checked > > "store"? Aren't we talking about "create"? Sorry, it looks like I managed to confuse "create" with "store" when I wrote the message. It should be stash create: remove duplicate changes detection Before it creates a stash, git checks if there are any unstaged, or uncommitted changes. If there isn't anything to stash it bails out. Since ef0f0b4509 (stash: optimize `get_untracked_files()` and `check_changes()`, 2019-02-25) "git stash create" has checked for changes twice, once in create_stash() before we refresh the index and then again in do_create_stash() after the index has been refreshed. That commit claims it is an optimization but it is not clear what it is trying to optimize by checking for changes twice, especially as checking for changes before refreshing the index is unreliable (the scripted version of "git stash create", called "git update-index -q --refresh" before looking for any changes). Avoid checking for changes twice by removing the call to check_changes_tracked_files() from create_stash() and restore the return code handling in create_stash() that was removed by ef0f0b4509 so that we continue to exit 0 when there are no changes to stash. In principle we could remove the call to check_changes() from do_create_stash() instead, but then we'd need to pass in the list of untracked files. Thanks Phillip > >> unreliable (the scripted version of "git stash store", called "git >> update-index -q --refresh" before looking for any changes). > > Ditto. > >> Avoid checking for changes twice by removing the call to >> check_changes_tracked_files() from store_stash() and restore the return >> code handling in store_stash() that was removed by ef0f0b4509 so that >> we continue to exit 0 when there are no changes to stash. In principle >> we could remove the call to check_changes() from do_store_stash() >> instead, but then we'd need to pass in the list of untracked files. > > Again "(do_)?store" -> "\1create"? ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 2/2] stash push: remove duplicate changes detection 2026-10-05 16:35 [PATCH 0/2] stash: stop checking for changes twice Phillip Wood 2026-10-05 16:35 ` [PATCH 1/2] stash create: remove duplicate changes detection Phillip Wood @ 2026-10-05 16:35 ` Phillip Wood 2026-10-06 14:27 ` Junio C Hamano 2026-10-14 15:40 ` [PATCH v2 0/7] stash creation: avoid unecessary index loads and refreshes Phillip Wood 2 siblings, 1 reply; 20+ messages in thread From: Phillip Wood @ 2026-10-05 16:35 UTC (permalink / raw) To: git; +Cc: 重田一聖, Phillip Wood From: Phillip Wood <phillip.wood@dunelm.org.uk> Before it creates a stash, git checks if there are any unstaged, or uncommitted changes. If there isn't anything to stash it bails out. "git stash push" checks for changes twice, once in do_push_stash() and then again in do_create_stash(). Avoid that by removing the call to check_changes() from do_push_stash() and checking the return value of do_create_stash() to see if there were any changes to stash. If check_changes() finds there are no changes do_create_stash() now returns 2 rather than 1. This enables us to distinguish between there being no changes and there being nothing stashed so that "git stash push --patch" when nothing is select, and "git stash push --staged" when the index matches HEAD still exit 1 rather than 0. There is still one small change in behavior as, if there is nothing to stash, we'll try now to create the reflog for stashes before we realize that there is nothing to stash. I don't think that should matter in practice. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> --- builtin/stash.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index 9a5006e3d92..79fdfff09a2 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1538,7 +1538,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b } if (!check_changes(ps, include_untracked, &untracked_files)) { - ret = 1; + ret = 2; goto done; } @@ -1664,8 +1664,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED, free_stash_info(&info); strbuf_release(&stash_msg_buf); /* - * ret is 1 if there were no changes. In this case, we should - * not error out. + * ret is greater than zero if there were no changes. In this case, + * we should not error out. */ return ret < 0; } @@ -1728,12 +1728,6 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q goto done; } - if (!check_changes(ps, include_untracked, &untracked_files)) { - if (!quiet) - printf_ln(_("No local changes to save")); - goto done; - } - if (!refs_reflog_exists(get_main_ref_store(the_repository), ref_stash) && do_clear_stash()) { ret = -1; if (!quiet) @@ -1743,8 +1737,15 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q if (stash_msg) strbuf_addstr(&stash_msg_buf, stash_msg); - if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, - interactive_opts, only_staged, &info, &patch, quiet)) { + ret = do_create_stash(ps, &stash_msg_buf, include_untracked, + patch_mode, interactive_opts, only_staged, &info, + &patch, quiet); + if (ret == 2) { + if (!quiet) + printf_ln(_("No local changes to save")); + ret = 0; + goto done; + } else if (ret) { ret = -1; goto done; } -- 2.56.0.134.g299a3c16181 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 2/2] stash push: remove duplicate changes detection 2026-10-05 16:35 ` [PATCH 2/2] stash push: " Phillip Wood @ 2026-10-06 14:27 ` Junio C Hamano 2026-10-07 13:46 ` Phillip Wood 0 siblings, 1 reply; 20+ messages in thread From: Junio C Hamano @ 2026-10-06 14:27 UTC (permalink / raw) To: Phillip Wood; +Cc: git, 重田一聖 Phillip Wood <phillip.wood123@gmail.com> writes: > diff --git a/builtin/stash.c b/builtin/stash.c > index 9a5006e3d92..79fdfff09a2 100644 > --- a/builtin/stash.c > +++ b/builtin/stash.c > @@ -1538,7 +1538,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b > } > > if (!check_changes(ps, include_untracked, &untracked_files)) { > - ret = 1; > + ret = 2; > goto done; > } It may be time for us to introduce symbolic constants once we have three choices instead of two. > @@ -1664,8 +1664,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED, > free_stash_info(&info); > strbuf_release(&stash_msg_buf); > /* > - * ret is 1 if there were no changes. In this case, we should > - * not error out. > + * ret is greater than zero if there were no changes. In this case, > + * we should not error out. > */ > return ret < 0; > } > @@ -1728,12 +1728,6 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q > goto done; > } > > - if (!check_changes(ps, include_untracked, &untracked_files)) { > - if (!quiet) > - printf_ln(_("No local changes to save")); > - goto done; > - } > - > if (!refs_reflog_exists(get_main_ref_store(the_repository), ref_stash) && do_clear_stash()) { > ret = -1; > if (!quiet) Before the precontext of this hunk, repo_refresh_and_write_index() is called to refresh the index. We used to leave early when check_changes() saw no need to save. We no longer do so, and instead keep going. > @@ -1743,8 +1737,15 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q > > if (stash_msg) > strbuf_addstr(&stash_msg_buf, stash_msg); > - if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, > - interactive_opts, only_staged, &info, &patch, quiet)) { > + ret = do_create_stash(ps, &stash_msg_buf, include_untracked, > + patch_mode, interactive_opts, only_staged, &info, > + &patch, quiet); And we call do_create_stash(). The first thing it does is to call repo_read_index_preload() and repo_refresh_and_write_index(). Are we refreshing the index twice now, even though we know nothing has changed in between, when we run "git stash push"? do_create_stash() does call check_changes() to return early without creating stash, so we did save the cost of check_changes() with this patch, though. > + if (ret == 2) { > + if (!quiet) > + printf_ln(_("No local changes to save")); > + ret = 0; > + goto done; > + } else if (ret) { > ret = -1; > goto done; > } ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 2/2] stash push: remove duplicate changes detection 2026-10-06 14:27 ` Junio C Hamano @ 2026-10-07 13:46 ` Phillip Wood 2026-10-07 17:15 ` Junio C Hamano 0 siblings, 1 reply; 20+ messages in thread From: Phillip Wood @ 2026-10-07 13:46 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, 重田一聖 On 06/10/2026 15:27, Junio C Hamano wrote: > Phillip Wood <phillip.wood123@gmail.com> writes: > >> diff --git a/builtin/stash.c b/builtin/stash.c >> index 9a5006e3d92..79fdfff09a2 100644 >> --- a/builtin/stash.c >> +++ b/builtin/stash.c >> @@ -1538,7 +1538,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b >> } >> >> if (!check_changes(ps, include_untracked, &untracked_files)) { >> - ret = 1; >> + ret = 2; >> goto done; >> } > > It may be time for us to introduce symbolic constants once we have > three choices instead of two. That makes sense >> @@ -1728,12 +1728,6 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q >> goto done; >> } >> >> - if (!check_changes(ps, include_untracked, &untracked_files)) { >> - if (!quiet) >> - printf_ln(_("No local changes to save")); >> - goto done; >> - } >> - >> if (!refs_reflog_exists(get_main_ref_store(the_repository), ref_stash) && do_clear_stash()) { >> ret = -1; >> if (!quiet) > > Before the precontext of this hunk, repo_refresh_and_write_index() > is called to refresh the index. We used to leave early when > check_changes() saw no need to save. We no longer do so, and > instead keep going. > >> @@ -1743,8 +1737,15 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q >> >> if (stash_msg) >> strbuf_addstr(&stash_msg_buf, stash_msg); >> - if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, >> - interactive_opts, only_staged, &info, &patch, quiet)) { >> + ret = do_create_stash(ps, &stash_msg_buf, include_untracked, >> + patch_mode, interactive_opts, only_staged, &info, >> + &patch, quiet); > > And we call do_create_stash(). The first thing it does is to call > repo_read_index_preload() and repo_refresh_and_write_index(). > > Are we refreshing the index twice now, even though we know nothing > has changed in between, when we run "git stash push"? We've always been doing that when there is something to stash (which is the normal case). To avoid that we need to make the callers of do_create_stash() responsible for refreshing the index. At the moment create_stash() calls do_create_stash() without evening reading the index, but do_push_stash() needs to read the index to check if it the pathspec contains paths that don't match the index. That would also avoid calling preload_index() multiple times. > do_create_stash() does call check_changes() to return early without > creating stash, so we did save the cost of check_changes() with this > patch, though. Yes, lets add another patch to avoid unnecessarily refreshing the index as well. Thanks Phillip >> + if (ret == 2) { >> + if (!quiet) >> + printf_ln(_("No local changes to save")); >> + ret = 0; >> + goto done; >> + } else if (ret) { >> ret = -1; >> goto done; >> } ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 2/2] stash push: remove duplicate changes detection 2026-10-07 13:46 ` Phillip Wood @ 2026-10-07 17:15 ` Junio C Hamano 2026-10-08 13:35 ` Phillip Wood 0 siblings, 1 reply; 20+ messages in thread From: Junio C Hamano @ 2026-10-07 17:15 UTC (permalink / raw) To: Phillip Wood; +Cc: git, 重田一聖 Phillip Wood <phillip.wood123@gmail.com> writes: >> Before the precontext of this hunk, repo_refresh_and_write_index() >> is called to refresh the index. We used to leave early when >> check_changes() saw no need to save. We no longer do so, and >> instead keep going. >> >>> @@ -1743,8 +1737,15 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q >>> >>> if (stash_msg) >>> strbuf_addstr(&stash_msg_buf, stash_msg); >>> - if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, >>> - interactive_opts, only_staged, &info, &patch, quiet)) { >>> + ret = do_create_stash(ps, &stash_msg_buf, include_untracked, >>> + patch_mode, interactive_opts, only_staged, &info, >>> + &patch, quiet); >> >> And we call do_create_stash(). The first thing it does is to call >> repo_read_index_preload() and repo_refresh_and_write_index(). >> >> Are we refreshing the index twice now, even though we know nothing >> has changed in between, when we run "git stash push"? > > We've always been doing that when there is something to stash (which is > the normal case). So when there is nothing to stash, we only refreshed once but now refreshing twice is not a regression? > To avoid that we need to make the callers of do_create_stash() > responsible for refreshing the index. At the moment > create_stash() calls do_create_stash() without evening reading the > index, but do_push_stash() needs to read the index to check if it > the pathspec contains paths that don't match the index. That would > also avoid calling preload_index() multiple times. Yes, that exactly is the right direction. Then we can reduce the number of check_changes() without increasing the number of refreshes, right? Thanks. ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 2/2] stash push: remove duplicate changes detection 2026-10-07 17:15 ` Junio C Hamano @ 2026-10-08 13:35 ` Phillip Wood 0 siblings, 0 replies; 20+ messages in thread From: Phillip Wood @ 2026-10-08 13:35 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, 重田一聖 On 07/10/2026 18:15, Junio C Hamano wrote: > Phillip Wood <phillip.wood123@gmail.com> writes: > >>> Before the precontext of this hunk, repo_refresh_and_write_index() >>> is called to refresh the index. We used to leave early when >>> check_changes() saw no need to save. We no longer do so, and >>> instead keep going. >>> >>>> @@ -1743,8 +1737,15 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q >>>> >>>> if (stash_msg) >>>> strbuf_addstr(&stash_msg_buf, stash_msg); >>>> - if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, >>>> - interactive_opts, only_staged, &info, &patch, quiet)) { >>>> + ret = do_create_stash(ps, &stash_msg_buf, include_untracked, >>>> + patch_mode, interactive_opts, only_staged, &info, >>>> + &patch, quiet); >>> >>> And we call do_create_stash(). The first thing it does is to call >>> repo_read_index_preload() and repo_refresh_and_write_index(). >>> >>> Are we refreshing the index twice now, even though we know nothing >>> has changed in between, when we run "git stash push"? >> >> We've always been doing that when there is something to stash (which is >> the normal case). > > So when there is nothing to stash, we only refreshed once but now > refreshing twice is not a regression? If there are no changes then the first refresh will mark all the index entries as up-to-date, which means the second refresh wont stat anything so I'm not sure there is a noticeable change. To me the more important problem is that we're lstat()ing each changed file half a dozen times before we call check_changes() when we should be lstat()ing them twice. >> To avoid that we need to make the callers of do_create_stash() >> responsible for refreshing the index. At the moment >> create_stash() calls do_create_stash() without evening reading the >> index, but do_push_stash() needs to read the index to check if it >> the pathspec contains paths that don't match the index. That would >> also avoid calling preload_index() multiple times. > > Yes, that exactly is the right direction. Then we can reduce the > number of check_changes() without increasing the number of > refreshes, right? Yes and improve things for the case where we actually have something to stash. Thanks Phillip ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 0/7] stash creation: avoid unecessary index loads and refreshes 2026-10-05 16:35 [PATCH 0/2] stash: stop checking for changes twice Phillip Wood 2026-10-05 16:35 ` [PATCH 1/2] stash create: remove duplicate changes detection Phillip Wood 2026-10-05 16:35 ` [PATCH 2/2] stash push: " Phillip Wood @ 2026-10-14 15:40 ` Phillip Wood 2026-10-14 15:40 ` [PATCH v2 1/7] stash: use an enum for create status Phillip Wood ` (6 more replies) 2 siblings, 7 replies; 20+ messages in thread From: Phillip Wood @ 2026-10-14 15:40 UTC (permalink / raw) To: git; +Cc: 重田一聖, Phillip Wood Thanks to Junio for his comments on V1, this series has expanded in scope to avoid unecessary index refreshes. "git stash push" and "git stash create" load and refresh the index multiple times when they only need to do it once. This series removes a number of calls to repo_read_preload_index() and refresh_index() which reduces the number of lstat() calls on each changed path by four for "git stash create" and eight for "git stash push". It also removes a duplicate call to check_changes() so that when stashing untracked files we only walk them once. Changes since V1: - Added an enum for the various return values from do_create_stash() - Fixed typos in the commit message of patch 2 (was patch 1) - Removed an unnecessary index refresh and several unnecessary index preloads Cover letter for V1: "git stash push" and "git stash create" check if there are any unstaged or uncommitted changes at startup, and then again when they try to create the stash. This short series removes that duplication of effort which I spotted while looking at <20260929074222.11942-1-kazumasa.shigeta@kanamei.com>. base-commit: c46c1e37724f0478939de636ab8ea5a89086d532 Published-As: https://github.com/phillipwood/git/releases/tag/pw%2Fstash-optimize-check_changes-calls%2Fv2 View-Changes-At: https://github.com/phillipwood/git/compare/c46c1e377...db53bf3b5 Fetch-It-Via: git fetch https://github.com/phillipwood/git pw/stash-optimize-check_changes-calls/v2 Phillip Wood (7): stash: use an enum for create status stash create: remove duplicate changes detection stash push: remove duplicate changes detection stash push: avoid duplicate calls to preload_index() stash create: rework temporary index creation stash create: remove unnecessary index load and refresh stash: simplify reset_tree() function signature builtin/stash.c | 137 ++++++++++++++++++++++-------------------------- 1 file changed, 62 insertions(+), 75 deletions(-) Range-diff against v1: -: ---------- > 1: 9e27bcd676 stash: use an enum for create status 1: 1617d92942 ! 2: d56b7c6f67 stash create: remove duplicate changes detection @@ Commit message Before it creates a stash, git checks if there are any unstaged, or uncommitted changes. If there isn't anything to stash it bails out. Since ef0f0b4509 (stash: optimize `get_untracked_files()` - and `check_changes()`, 2019-02-25) "git stash store" has checked + and `check_changes()`, 2019-02-25) "git stash create" has checked for changes twice, once in create_stash() before we refresh the index and then again in do_create_stash() after the index has been refreshed. That commit claims it is an optimization but it is not clear what it is trying to optimize by checking for changes twice, especially as checking for changes before refreshing the index is - unreliable (the scripted version of "git stash store", called "git + unreliable (the scripted version of "git stash create", called "git update-index -q --refresh" before looking for any changes). Avoid checking for changes twice by removing the call to - check_changes_tracked_files() from store_stash() and restore the return - code handling in store_stash() that was removed by ef0f0b4509 so that - we continue to exit 0 when there are no changes to stash. In principle - we could remove the call to check_changes() from do_store_stash() - instead, but then we'd need to pass in the list of untracked files. + check_changes_tracked_files() from create_stash(). In principle we + could remove the call to check_changes() from do_create_stash() + instead, but it is more convenient to keep it there, rather than + require each caller to call it instead. + + The remaining callers of check_changes_tracked_files() sensibly read + and refresh the index before calling it, so it no-longer needs to + call to repo_read_index(). Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> ## builtin/stash.c ## +@@ builtin/stash.c: static int check_changes_tracked_files(const struct pathspec *ps) + if (repo_get_oid(the_repository, "HEAD", &dummy)) + return -1; + +- if (repo_read_index(the_repository) < 0) +- return -1; +- + repo_init_revisions(the_repository, &rev, NULL); + copy_pathspec(&rev.prune_data, ps); + @@ builtin/stash.c: static int create_stash(int argc, const char **argv, const char *prefix UNUSED, strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' '); @@ builtin/stash.c: static int create_stash(int argc, const char **argv, const char ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info, NULL, 0); -@@ builtin/stash.c: static int create_stash(int argc, const char **argv, const char *prefix UNUSED, - - free_stash_info(&info); - strbuf_release(&stash_msg_buf); -- return ret; -+ /* -+ * ret is 1 if there were no changes. In this case, we should -+ * not error out. -+ */ -+ return ret < 0; - } - - static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet, 2: 95b7d582a2 ! 3: 36fc0eb913 stash push: remove duplicate changes detection @@ Metadata ## Commit message ## stash push: remove duplicate changes detection - Before it creates a stash, git checks if there are any unstaged, - or uncommitted changes. If there isn't anything to stash it bails - out. "git stash push" checks for changes twice, once in do_push_stash() - and then again in do_create_stash(). Avoid that by removing the call - to check_changes() from do_push_stash() and checking the return value - of do_create_stash() to see if there were any changes to stash. If - check_changes() finds there are no changes do_create_stash() now - returns 2 rather than 1. This enables us to distinguish between there - being no changes and there being nothing stashed so that "git stash - push --patch" when nothing is select, and "git stash push --staged" - when the index matches HEAD still exit 1 rather than 0. + Before it creates a stash, git checks if there are any unstaged, or + uncommitted changes. If there isn't anything to stash it bails out. "git + stash push" checks for changes twice, once in do_push_stash() and then + again in do_create_stash(). While duplicating the checks for staged and + unstaged charges is fairly cheap because they stop after finding the + first changed path, if we're stashing untracked files then we visit + them all twice. Avoid that by removing the call to check_changes() + from do_push_stash() and checking the return value of do_create_stash() + to see if there were any changes to stash. If check_changes() finds + there are no changes do_create_stash() now returns CREATE_NO_CHANGES + rather than CREATE_EMPTY. This enables us to distinguish between there + being no changes and there being nothing stashed, so that "git stash + push --patch" when nothing is selected, and "git stash push --staged" + when the index matches HEAD, still exit 1 rather than 0. + + As we're not calling check_changes() in do_push_stash() anymore, we do + not need to refresh the index there either because do_create_stash() + refreshes it again before calling check_changes(). Avoiding the extra + index refresh saves us two lstat() calls per changed path. There is still one small change in behavior as, if there is nothing - to stash, we'll try now to create the reflog for stashes before we + to stash, we'll now try to create the reflog for stashes before we realize that there is nothing to stash. I don't think that should matter in practice. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> ## builtin/stash.c ## -@@ builtin/stash.c: static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b +@@ builtin/stash.c: enum create_result { + CREATE_SUCCESS, + /* No changes were stashed */ + CREATE_EMPTY, ++ /* There were no changes to stash */ ++ CREATE_NO_CHANGES, + }; + + static enum create_result stash_staged(struct stash_info *info, +@@ builtin/stash.c: static enum create_result do_create_stash(const struct pathspec *ps, } if (!check_changes(ps, include_untracked, &untracked_files)) { -- ret = 1; -+ ret = 2; +- ret = CREATE_EMPTY; ++ ret = CREATE_NO_CHANGES; goto done; } -@@ builtin/stash.c: static int create_stash(int argc, const char **argv, const char *prefix UNUSED, - free_stash_info(&info); - strbuf_release(&stash_msg_buf); - /* -- * ret is 1 if there were no changes. In this case, we should -- * not error out. -+ * ret is greater than zero if there were no changes. In this case, -+ * we should not error out. - */ - return ret < 0; - } @@ builtin/stash.c: static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q - goto done; + free(ps_matched); } +- if (repo_refresh_and_write_index(the_repository, REFRESH_QUIET, 0, 0, +- NULL, NULL, NULL)) { +- ret = error(_("could not write index")); +- goto done; +- } +- - if (!check_changes(ps, include_untracked, &untracked_files)) { - if (!quiet) - printf_ln(_("No local changes to save")); @@ builtin/stash.c: static int do_push_stash(const struct pathspec *ps, const char ret = -1; if (!quiet) @@ builtin/stash.c: static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q - - if (stash_msg) - strbuf_addstr(&stash_msg_buf, stash_msg); -- if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, -- interactive_opts, only_staged, &info, &patch, quiet)) { -+ ret = do_create_stash(ps, &stash_msg_buf, include_untracked, -+ patch_mode, interactive_opts, only_staged, &info, -+ &patch, quiet); -+ if (ret == 2) { + create_res = do_create_stash(ps, &stash_msg_buf, include_untracked, + patch_mode, interactive_opts, only_staged, + &info, &patch, quiet); +- if (create_res != CREATE_SUCCESS) { ++ if (create_res == CREATE_NO_CHANGES) { + if (!quiet) + printf_ln(_("No local changes to save")); + ret = 0; + goto done; -+ } else if (ret) { ++ } else if (create_res != CREATE_SUCCESS) { ret = -1; goto done; } -: ---------- > 4: 6ca7e1c567 stash push: avoid duplicate calls to preload_index() -: ---------- > 5: f799d86f52 stash create: rework temporary index creation -: ---------- > 6: 768d6bc7e5 stash create: remove unnecessary index load and refresh -: ---------- > 7: db53bf3b57 stash: simplify reset_tree() function signature -- 2.56.0.134.g299a3c16181 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 1/7] stash: use an enum for create status 2026-10-14 15:40 ` [PATCH v2 0/7] stash creation: avoid unecessary index loads and refreshes Phillip Wood @ 2026-10-14 15:40 ` Phillip Wood 2026-10-14 15:40 ` [PATCH v2 2/7] stash create: remove duplicate changes detection Phillip Wood ` (5 subsequent siblings) 6 siblings, 0 replies; 20+ messages in thread From: Phillip Wood @ 2026-10-14 15:40 UTC (permalink / raw) To: git; +Cc: 重田一聖, Phillip Wood From: Phillip Wood <phillip.wood@dunelm.org.uk> Change the return type of do_create_stash() to an enum in preparation for adding a separate value for "there are no changes to stash" in a future commit. There is a small change to behavior here which fixes an anomaly with the exit status of create_stash(). That function calls check_changes() without refreshing the index and exits 0 if there are no changes. However, because it does not refresh the index, that check is unreliable which means do_create_stash() can return 1 when there aren't any changes, and then create_stash() exits 1 rather than 0. This commit fixes that anomaly. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> --- builtin/stash.c | 83 ++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index 7a9843413b..3e50ccfb40 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1336,16 +1336,23 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg, return ret; } -static int stash_staged(struct stash_info *info, struct strbuf *out_patch, - int quiet) +enum create_result { + CREATE_ERROR = -1, + CREATE_SUCCESS, + /* No changes were stashed */ + CREATE_EMPTY, +}; + +static enum create_result stash_staged(struct stash_info *info, + struct strbuf *out_patch, int quiet) { - int ret = 0; + enum create_result ret = 0; struct child_process cp_diff_tree = CHILD_PROCESS_INIT; struct index_state istate = INDEX_STATE_INIT(the_repository); if (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file, 0, NULL)) { - ret = -1; + ret = CREATE_ERROR; goto done; } @@ -1354,26 +1361,26 @@ static int stash_staged(struct stash_info *info, struct strbuf *out_patch, "--no-color", "-U1", "HEAD", oid_to_hex(&info->w_tree), "--", NULL); if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { - ret = -1; + ret = CREATE_ERROR; goto done; } if (!out_patch->len) { if (!quiet) fprintf_ln(stderr, _("No staged changes")); - ret = 1; + ret = CREATE_EMPTY; } done: release_index(&istate); return ret; } -static int stash_patch(struct stash_info *info, const struct pathspec *ps, - struct strbuf *out_patch, int quiet, - struct interactive_options *interactive_opts) +static enum create_result stash_patch(struct stash_info *info, + const struct pathspec *ps, struct strbuf *out_patch, int quiet, + struct interactive_options *interactive_opts) { - int ret = 0; + enum create_result ret = 0; struct child_process cp_diff_tree = CHILD_PROCESS_INIT; struct commit *head_commit; const struct object_id *head_tree; @@ -1384,17 +1391,17 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, head_commit = lookup_commit(the_repository, &info->b_commit); if (!head_commit || repo_parse_commit(the_repository, head_commit)) { - ret = -1; + ret = CREATE_ERROR; goto done; } head_tree = get_commit_tree_oid(head_commit); if (!head_tree) { - ret = -1; + ret = CREATE_ERROR; goto done; } if (create_index_from_tree(head_tree, stash_index_path.buf)) { - ret = -1; + ret = CREATE_ERROR; goto done; } @@ -1416,7 +1423,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, /* State of the working tree. */ if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, NULL)) { - ret = -1; + ret = CREATE_ERROR; goto done; } @@ -1425,14 +1432,14 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, "--no-color", oid_to_hex(&info->w_tree), "--", NULL); if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { - ret = -1; + ret = CREATE_ERROR; goto done; } if (!out_patch->len) { if (!quiet) fprintf_ln(stderr, _("No changes selected")); - ret = 1; + ret = CREATE_EMPTY; } done: @@ -1499,13 +1506,13 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps return ret; } -static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf, - int include_untracked, int patch_mode, - struct interactive_options *interactive_opts, - int only_staged, struct stash_info *info, struct strbuf *patch, - int quiet) +static enum create_result do_create_stash(const struct pathspec *ps, + struct strbuf *stash_msg_buf, int include_untracked, + int patch_mode, struct interactive_options *interactive_opts, + int only_staged, struct stash_info *info, struct strbuf *patch, + int quiet) { - int ret = 0; + enum create_result ret = 0; int flags = 0; int untracked_commit_option = 0; const char *head_short_sha1 = NULL; @@ -1531,14 +1538,14 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b if (!quiet) fprintf_ln(stderr, _("You do not have " "the initial commit yet")); - ret = -1; + ret = CREATE_ERROR; goto done; } else { head_commit = lookup_commit(the_repository, &info->b_commit); } if (!check_changes(ps, include_untracked, &untracked_files)) { - ret = 1; + ret = CREATE_EMPTY; goto done; } @@ -1565,7 +1572,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b if (!quiet) fprintf_ln(stderr, _("Cannot save the current " "index state")); - ret = -1; + ret = CREATE_ERROR; goto done; } @@ -1577,37 +1584,37 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b if (!quiet) fprintf_ln(stderr, _("Cannot save " "the untracked files")); - ret = -1; + ret = CREATE_ERROR; goto done; } untracked_commit_option = 1; } if (patch_mode) { ret = stash_patch(info, ps, patch, quiet, interactive_opts); - if (ret < 0) { + if (ret == CREATE_ERROR) { if (!quiet) fprintf_ln(stderr, _("Cannot save the current " "worktree state")); goto done; - } else if (ret > 0) { + } else if (ret != CREATE_SUCCESS) { goto done; } } else if (only_staged) { ret = stash_staged(info, patch, quiet); - if (ret < 0) { + if (ret == CREATE_ERROR) { if (!quiet) fprintf_ln(stderr, _("Cannot save the current " "staged state")); goto done; - } else if (ret > 0) { + } else if (ret != CREATE_SUCCESS) { goto done; } } else { if (stash_working_tree(info, ps)) { if (!quiet) fprintf_ln(stderr, _("Cannot save the current " "worktree state")); - ret = -1; + ret = CREATE_ERROR; goto done; } } @@ -1630,7 +1637,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b if (!quiet) fprintf_ln(stderr, _("Cannot record " "working tree state")); - ret = -1; + ret = CREATE_ERROR; goto done; } @@ -1660,12 +1667,13 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED, ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info, NULL, 0); - if (!ret) + if (ret == CREATE_SUCCESS) printf_ln("%s", oid_to_hex(&info.w_commit)); free_stash_info(&info); strbuf_release(&stash_msg_buf); - return ret; + /* Do not return an error if there are no changes */ + return ret == CREATE_ERROR ? -1 : 0; } static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet, @@ -1674,6 +1682,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q int include_untracked, int only_staged) { int ret = 0; + enum create_result create_res; struct stash_info info = STASH_INFO_INIT; struct strbuf patch = STRBUF_INIT; struct strbuf stash_msg_buf = STRBUF_INIT; @@ -1741,8 +1750,10 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q if (stash_msg) strbuf_addstr(&stash_msg_buf, stash_msg); - if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, - interactive_opts, only_staged, &info, &patch, quiet)) { + create_res = do_create_stash(ps, &stash_msg_buf, include_untracked, + patch_mode, interactive_opts, only_staged, + &info, &patch, quiet); + if (create_res != CREATE_SUCCESS) { ret = -1; goto done; } -- 2.56.0.134.g299a3c16181 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v2 2/7] stash create: remove duplicate changes detection 2026-10-14 15:40 ` [PATCH v2 0/7] stash creation: avoid unecessary index loads and refreshes Phillip Wood 2026-10-14 15:40 ` [PATCH v2 1/7] stash: use an enum for create status Phillip Wood @ 2026-10-14 15:40 ` Phillip Wood 2026-10-14 15:40 ` [PATCH v2 3/7] stash push: " Phillip Wood ` (4 subsequent siblings) 6 siblings, 0 replies; 20+ messages in thread From: Phillip Wood @ 2026-10-14 15:40 UTC (permalink / raw) To: git; +Cc: 重田一聖, Phillip Wood From: Phillip Wood <phillip.wood@dunelm.org.uk> Before it creates a stash, git checks if there are any unstaged, or uncommitted changes. If there isn't anything to stash it bails out. Since ef0f0b4509 (stash: optimize `get_untracked_files()` and `check_changes()`, 2019-02-25) "git stash create" has checked for changes twice, once in create_stash() before we refresh the index and then again in do_create_stash() after the index has been refreshed. That commit claims it is an optimization but it is not clear what it is trying to optimize by checking for changes twice, especially as checking for changes before refreshing the index is unreliable (the scripted version of "git stash create", called "git update-index -q --refresh" before looking for any changes). Avoid checking for changes twice by removing the call to check_changes_tracked_files() from create_stash(). In principle we could remove the call to check_changes() from do_create_stash() instead, but it is more convenient to keep it there, rather than require each caller to call it instead. The remaining callers of check_changes_tracked_files() sensibly read and refresh the index before calling it, so it no-longer needs to call to repo_read_index(). Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> --- builtin/stash.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index 3e50ccfb40..f461f002e5 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1248,9 +1248,6 @@ static int check_changes_tracked_files(const struct pathspec *ps) if (repo_get_oid(the_repository, "HEAD", &dummy)) return -1; - if (repo_read_index(the_repository) < 0) - return -1; - repo_init_revisions(the_repository, &rev, NULL); copy_pathspec(&rev.prune_data, ps); @@ -1662,8 +1659,6 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED, strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' '); memset(&ps, 0, sizeof(ps)); - if (!check_changes_tracked_files(&ps)) - return 0; ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info, NULL, 0); -- 2.56.0.134.g299a3c16181 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v2 3/7] stash push: remove duplicate changes detection 2026-10-14 15:40 ` [PATCH v2 0/7] stash creation: avoid unecessary index loads and refreshes Phillip Wood 2026-10-14 15:40 ` [PATCH v2 1/7] stash: use an enum for create status Phillip Wood 2026-10-14 15:40 ` [PATCH v2 2/7] stash create: remove duplicate changes detection Phillip Wood @ 2026-10-14 15:40 ` Phillip Wood 2026-10-14 15:40 ` [PATCH v2 4/7] stash push: avoid duplicate calls to preload_index() Phillip Wood ` (3 subsequent siblings) 6 siblings, 0 replies; 20+ messages in thread From: Phillip Wood @ 2026-10-14 15:40 UTC (permalink / raw) To: git; +Cc: 重田一聖, Phillip Wood From: Phillip Wood <phillip.wood@dunelm.org.uk> Before it creates a stash, git checks if there are any unstaged, or uncommitted changes. If there isn't anything to stash it bails out. "git stash push" checks for changes twice, once in do_push_stash() and then again in do_create_stash(). While duplicating the checks for staged and unstaged charges is fairly cheap because they stop after finding the first changed path, if we're stashing untracked files then we visit them all twice. Avoid that by removing the call to check_changes() from do_push_stash() and checking the return value of do_create_stash() to see if there were any changes to stash. If check_changes() finds there are no changes do_create_stash() now returns CREATE_NO_CHANGES rather than CREATE_EMPTY. This enables us to distinguish between there being no changes and there being nothing stashed, so that "git stash push --patch" when nothing is selected, and "git stash push --staged" when the index matches HEAD, still exit 1 rather than 0. As we're not calling check_changes() in do_push_stash() anymore, we do not need to refresh the index there either because do_create_stash() refreshes it again before calling check_changes(). Avoiding the extra index refresh saves us two lstat() calls per changed path. There is still one small change in behavior as, if there is nothing to stash, we'll now try to create the reflog for stashes before we realize that there is nothing to stash. I don't think that should matter in practice. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> --- builtin/stash.c | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index f461f002e5..aec79ed473 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1338,6 +1338,8 @@ enum create_result { CREATE_SUCCESS, /* No changes were stashed */ CREATE_EMPTY, + /* There were no changes to stash */ + CREATE_NO_CHANGES, }; static enum create_result stash_staged(struct stash_info *info, @@ -1542,7 +1544,7 @@ static enum create_result do_create_stash(const struct pathspec *ps, } if (!check_changes(ps, include_untracked, &untracked_files)) { - ret = CREATE_EMPTY; + ret = CREATE_NO_CHANGES; goto done; } @@ -1724,18 +1726,6 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q free(ps_matched); } - if (repo_refresh_and_write_index(the_repository, REFRESH_QUIET, 0, 0, - NULL, NULL, NULL)) { - ret = error(_("could not write index")); - goto done; - } - - if (!check_changes(ps, include_untracked, &untracked_files)) { - if (!quiet) - printf_ln(_("No local changes to save")); - goto done; - } - if (!refs_reflog_exists(get_main_ref_store(the_repository), ref_stash) && do_clear_stash()) { ret = -1; if (!quiet) @@ -1748,7 +1738,12 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q create_res = do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, interactive_opts, only_staged, &info, &patch, quiet); - if (create_res != CREATE_SUCCESS) { + if (create_res == CREATE_NO_CHANGES) { + if (!quiet) + printf_ln(_("No local changes to save")); + ret = 0; + goto done; + } else if (create_res != CREATE_SUCCESS) { ret = -1; goto done; } -- 2.56.0.134.g299a3c16181 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v2 4/7] stash push: avoid duplicate calls to preload_index() 2026-10-14 15:40 ` [PATCH v2 0/7] stash creation: avoid unecessary index loads and refreshes Phillip Wood ` (2 preceding siblings ...) 2026-10-14 15:40 ` [PATCH v2 3/7] stash push: " Phillip Wood @ 2026-10-14 15:40 ` Phillip Wood 2026-10-14 18:09 ` Junio C Hamano 2026-10-14 15:40 ` [PATCH v2 5/7] stash create: rework temporary index creation Phillip Wood ` (2 subsequent siblings) 6 siblings, 1 reply; 20+ messages in thread From: Phillip Wood @ 2026-10-14 15:40 UTC (permalink / raw) To: git; +Cc: 重田一聖, Phillip Wood From: Phillip Wood <phillip.wood@dunelm.org.uk> do_stash_push() needs to load the index so it can check that any pathspec argument refers to tracked files. To do that it calls repo_read_index_preload() which does not just load the index, it also calls preload_index() which lstat()s all the files to see which ones are up-to-date. do_create_stash() then calls repo_read_index_preload() for a second time which lstat()s all the changed paths again and then calls repo_refresh_and_write_index() which invokes preload_index() for a third time. So we're preloading the index three times when we only need to do it once. Avoid this by making the callers of do_create_stash() responsible for reading the index and use repo_read_index() rather than repo_read_index_preload(). This saves two lstat() calls per changed path. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> --- builtin/stash.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index aec79ed473..b4fe7465e4 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1526,7 +1526,6 @@ static enum create_result do_create_stash(const struct pathspec *ps, prepare_fallback_ident("git stash", "git@stash"); - repo_read_index_preload(the_repository, NULL, 0); if (repo_refresh_and_write_index(the_repository, REFRESH_QUIET, 0, 0, NULL, NULL, NULL) < 0) { ret = error(_("could not write index")); @@ -1662,6 +1661,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED, memset(&ps, 0, sizeof(ps)); + repo_read_index(the_repository); ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info, NULL, 0); if (ret == CREATE_SUCCESS) @@ -1707,7 +1707,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q goto done; } - repo_read_index_preload(the_repository, NULL, 0); + repo_read_index(the_repository); if (!include_untracked && ps->nr) { char *ps_matched = xcalloc(ps->nr, 1); -- 2.56.0.134.g299a3c16181 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v2 4/7] stash push: avoid duplicate calls to preload_index() 2026-10-14 15:40 ` [PATCH v2 4/7] stash push: avoid duplicate calls to preload_index() Phillip Wood @ 2026-10-14 18:09 ` Junio C Hamano 0 siblings, 0 replies; 20+ messages in thread From: Junio C Hamano @ 2026-10-14 18:09 UTC (permalink / raw) To: Phillip Wood; +Cc: git, 重田一聖 Phillip Wood <phillip.wood123@gmail.com> writes: > From: Phillip Wood <phillip.wood@dunelm.org.uk> > > do_stash_push() needs to load the index so it can check that any Perhaps you meant do_push_stash()? > pathspec argument refers to tracked files. To do that it calls > repo_read_index_preload() which does not just load the index, it also > calls preload_index() which lstat()s all the files to see which ones > are up-to-date. do_create_stash() then calls repo_read_index_preload() > for a second time which lstat()s all the changed paths again and then > calls repo_refresh_and_write_index() which invokes preload_index() for a > third time. So we're preloading the index three times when we only need > to do it once. Avoid this by making the callers of do_create_stash() > responsible for reading the index and use repo_read_index() rather > than repo_read_index_preload(). This saves two lstat() calls per > changed path. The other caller of do_create_stash() is create_stash(). And it does not need to call the preload version for the same reason. > Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> > --- > builtin/stash.c | 4 ++-- > 1 file changed, 2 insertions(+), 2 deletions(-) > > diff --git a/builtin/stash.c b/builtin/stash.c > index aec79ed473..b4fe7465e4 100644 > --- a/builtin/stash.c > +++ b/builtin/stash.c > @@ -1526,7 +1526,6 @@ static enum create_result do_create_stash(const struct pathspec *ps, > > prepare_fallback_ident("git stash", "git@stash"); > > - repo_read_index_preload(the_repository, NULL, 0); > if (repo_refresh_and_write_index(the_repository, REFRESH_QUIET, 0, 0, > NULL, NULL, NULL) < 0) { > ret = error(_("could not write index")); > @@ -1662,6 +1661,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED, > > memset(&ps, 0, sizeof(ps)); > > + repo_read_index(the_repository); > ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info, > NULL, 0); > if (ret == CREATE_SUCCESS) > @@ -1707,7 +1707,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q > goto done; > } > > - repo_read_index_preload(the_repository, NULL, 0); > + repo_read_index(the_repository); > if (!include_untracked && ps->nr) { > char *ps_matched = xcalloc(ps->nr, 1); ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 5/7] stash create: rework temporary index creation 2026-10-14 15:40 ` [PATCH v2 0/7] stash creation: avoid unecessary index loads and refreshes Phillip Wood ` (3 preceding siblings ...) 2026-10-14 15:40 ` [PATCH v2 4/7] stash push: avoid duplicate calls to preload_index() Phillip Wood @ 2026-10-14 15:40 ` Phillip Wood 2026-10-14 18:16 ` Junio C Hamano 2026-10-14 15:40 ` [PATCH v2 6/7] stash create: remove unnecessary index load and refresh Phillip Wood 2026-10-14 15:40 ` [PATCH v2 7/7] stash: simplify reset_tree() function signature Phillip Wood 6 siblings, 1 reply; 20+ messages in thread From: Phillip Wood @ 2026-10-14 15:40 UTC (permalink / raw) To: git; +Cc: 重田一聖, Phillip Wood From: Phillip Wood <phillip.wood@dunelm.org.uk> Use create_index_from_tree() instead of reset_tree() when we create a temporary index file to stash the working tree changes. This matches what we do for "git stash push -p" and means we can avoid messing with set_alternate_index_output(). Add a new parameter to control whether the index is reset, as we need to merge the index rather than reset it so that we still detect intent-to-add entries. We do not alter the in-memory index so we can remove the call to repo_read_index_preload() which saves one lstat() call per changed path. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> --- builtin/stash.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index b4fe7465e4..ddeb5a5d2a 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -374,7 +374,7 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) } static int create_index_from_tree(const struct object_id *tree_id, - const char *index_path) + const char *index_path, bool reset) { int nr_trees = 1; int ret = 0; @@ -403,7 +403,7 @@ static int create_index_from_tree(const struct object_id *tree_id, opts.src_index = the_repository->index; opts.dst_index = &dst_istate; opts.merge = 1; - opts.reset = UNPACK_RESET_PROTECT_UNTRACKED; + opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0; opts.fn = oneway_merge; if (unpack_trees(nr_trees, t, &opts)) { @@ -1399,7 +1399,7 @@ static enum create_result stash_patch(struct stash_info *info, goto done; } - if (create_index_from_tree(head_tree, stash_index_path.buf)) { + if (create_index_from_tree(head_tree, stash_index_path.buf, true)) { ret = CREATE_ERROR; goto done; } @@ -1458,22 +1458,15 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps repo_init_revisions(the_repository, &rev, NULL); copy_pathspec(&rev.prune_data, ps); - set_alternate_index_output(stash_index_path.buf); - if (reset_tree(&info->i_tree, 0, 0)) { + if (create_index_from_tree(&info->i_tree, stash_index_path.buf, false)) { ret = -1; goto done; } - set_alternate_index_output(NULL); rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = add_diff_to_buf; rev.diffopt.format_callback_data = &diff_output; - if (repo_read_index_preload(the_repository, &rev.diffopt.pathspec, 0) < 0) { - ret = -1; - goto done; - } - add_pending_object(&rev, parse_object(the_repository, &info->b_commit), ""); run_diff_index(&rev, 0); -- 2.56.0.134.g299a3c16181 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v2 5/7] stash create: rework temporary index creation 2026-10-14 15:40 ` [PATCH v2 5/7] stash create: rework temporary index creation Phillip Wood @ 2026-10-14 18:16 ` Junio C Hamano 0 siblings, 0 replies; 20+ messages in thread From: Junio C Hamano @ 2026-10-14 18:16 UTC (permalink / raw) To: Phillip Wood; +Cc: git, 重田一聖 Phillip Wood <phillip.wood123@gmail.com> writes: > From: Phillip Wood <phillip.wood@dunelm.org.uk> > > Use create_index_from_tree() instead of reset_tree() when we create a > temporary index file to stash the working tree changes. This matches > what we do for "git stash push -p" and means we can avoid messing with > set_alternate_index_output(). Add a new parameter to control whether > the index is reset, as we need to merge the index rather than reset > it so that we still detect intent-to-add entries. We do not alter the > in-memory index so we can remove the call to repo_read_index_preload() > which saves one lstat() call per changed path. It is unclear what problem we are trying to solve. Obviously we can enhance create_index_from_tree() and replace reset_tree() just fine, but the proposed log message should make it clear why we are doing so. Please follow the usual pattern to state "observation" (e.g., "we use reset_tree() when we do X"), "problem" (e.g., "use of reset_tree() is undesirable because ..."), and "solution" (e.g, "we can improve by replacing reset_tree() with create_index_from_tree() because the latter has these desirable characteristics") in this order, perhaps in separate paragraphs for extra readability. > Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> > --- > builtin/stash.c | 15 ++++----------- > 1 file changed, 4 insertions(+), 11 deletions(-) > > diff --git a/builtin/stash.c b/builtin/stash.c > index b4fe7465e4..ddeb5a5d2a 100644 > --- a/builtin/stash.c > +++ b/builtin/stash.c > @@ -374,7 +374,7 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) > } > > static int create_index_from_tree(const struct object_id *tree_id, > - const char *index_path) > + const char *index_path, bool reset) > { > int nr_trees = 1; > int ret = 0; > @@ -403,7 +403,7 @@ static int create_index_from_tree(const struct object_id *tree_id, > opts.src_index = the_repository->index; > opts.dst_index = &dst_istate; > opts.merge = 1; > - opts.reset = UNPACK_RESET_PROTECT_UNTRACKED; > + opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0; > opts.fn = oneway_merge; > > if (unpack_trees(nr_trees, t, &opts)) { > @@ -1399,7 +1399,7 @@ static enum create_result stash_patch(struct stash_info *info, > goto done; > } > > - if (create_index_from_tree(head_tree, stash_index_path.buf)) { > + if (create_index_from_tree(head_tree, stash_index_path.buf, true)) { > ret = CREATE_ERROR; > goto done; > } > @@ -1458,22 +1458,15 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps > repo_init_revisions(the_repository, &rev, NULL); > copy_pathspec(&rev.prune_data, ps); > > - set_alternate_index_output(stash_index_path.buf); > - if (reset_tree(&info->i_tree, 0, 0)) { > + if (create_index_from_tree(&info->i_tree, stash_index_path.buf, false)) { > ret = -1; > goto done; > } > - set_alternate_index_output(NULL); > > rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; > rev.diffopt.format_callback = add_diff_to_buf; > rev.diffopt.format_callback_data = &diff_output; > > - if (repo_read_index_preload(the_repository, &rev.diffopt.pathspec, 0) < 0) { > - ret = -1; > - goto done; > - } > - > add_pending_object(&rev, parse_object(the_repository, &info->b_commit), > ""); > run_diff_index(&rev, 0); ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 6/7] stash create: remove unnecessary index load and refresh 2026-10-14 15:40 ` [PATCH v2 0/7] stash creation: avoid unecessary index loads and refreshes Phillip Wood ` (4 preceding siblings ...) 2026-10-14 15:40 ` [PATCH v2 5/7] stash create: rework temporary index creation Phillip Wood @ 2026-10-14 15:40 ` Phillip Wood 2026-10-14 18:21 ` Junio C Hamano 2026-10-14 15:40 ` [PATCH v2 7/7] stash: simplify reset_tree() function signature Phillip Wood 6 siblings, 1 reply; 20+ messages in thread From: Phillip Wood @ 2026-10-14 15:40 UTC (permalink / raw) To: git; +Cc: 重田一聖, Phillip Wood From: Phillip Wood <phillip.wood@dunelm.org.uk> create_index_from_tree() is called either directly or indirectly from do_create_stash() after it has loaded and refreshed the index, so we do not need to refresh it again here. This saves three lstat() calls per changed path. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> --- builtin/stash.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index ddeb5a5d2a..55916259c3 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -384,9 +384,6 @@ static int create_index_from_tree(const struct object_id *tree_id, struct index_state dst_istate = INDEX_STATE_INIT(the_repository); struct lock_file lock_file = LOCK_INIT; - repo_read_index_preload(the_repository, NULL, 0); - refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); - hold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR); memset(&opts, 0, sizeof(opts)); -- 2.56.0.134.g299a3c16181 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v2 6/7] stash create: remove unnecessary index load and refresh 2026-10-14 15:40 ` [PATCH v2 6/7] stash create: remove unnecessary index load and refresh Phillip Wood @ 2026-10-14 18:21 ` Junio C Hamano 0 siblings, 0 replies; 20+ messages in thread From: Junio C Hamano @ 2026-10-14 18:21 UTC (permalink / raw) To: Phillip Wood; +Cc: git, 重田一聖 Phillip Wood <phillip.wood123@gmail.com> writes: > From: Phillip Wood <phillip.wood@dunelm.org.uk> > > create_index_from_tree() is called either directly or indirectly > from do_create_stash() after it has loaded and refreshed the index, > so we do not need to refresh it again here. This saves three lstat() > calls per changed path. > > Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> > --- > builtin/stash.c | 3 --- > 1 file changed, 3 deletions(-) > > diff --git a/builtin/stash.c b/builtin/stash.c > index ddeb5a5d2a..55916259c3 100644 > --- a/builtin/stash.c > +++ b/builtin/stash.c > @@ -384,9 +384,6 @@ static int create_index_from_tree(const struct object_id *tree_id, > struct index_state dst_istate = INDEX_STATE_INIT(the_repository); > struct lock_file lock_file = LOCK_INIT; > > - repo_read_index_preload(the_repository, NULL, 0); > - refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); > - Oh, it is emrarrasingly stupid follow read_index_preload() with refresh_index(), since the reason former is not read_index() is because we want it refreshed. The original is doubly bad. > hold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR); > > memset(&opts, 0, sizeof(opts)); ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 7/7] stash: simplify reset_tree() function signature 2026-10-14 15:40 ` [PATCH v2 0/7] stash creation: avoid unecessary index loads and refreshes Phillip Wood ` (5 preceding siblings ...) 2026-10-14 15:40 ` [PATCH v2 6/7] stash create: remove unnecessary index load and refresh Phillip Wood @ 2026-10-14 15:40 ` Phillip Wood 6 siblings, 0 replies; 20+ messages in thread From: Phillip Wood @ 2026-10-14 15:40 UTC (permalink / raw) To: git; +Cc: 重田一聖, Phillip Wood From: Phillip Wood <phillip.wood@dunelm.org.uk> There is only one caller and it does not want to update the worktree or reset the index so remove those unused parameters. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> --- builtin/stash.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index 55916259c3..bc1252790b 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -332,7 +332,7 @@ static int clear_stash(int argc, const char **argv, const char *prefix, return do_clear_stash(); } -static int reset_tree(struct object_id *i_tree, int update, int reset) +static int reset_tree(struct object_id *i_tree) { int nr_trees = 1; struct unpack_trees_options opts; @@ -358,10 +358,6 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) opts.src_index = the_repository->index; opts.dst_index = the_repository->index; opts.merge = 1; - opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0; - opts.update = update; - if (update) - opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ opts.fn = oneway_merge; if (unpack_trees(nr_trees, t, &opts)) @@ -738,7 +734,7 @@ static enum stash_apply_result do_apply_stash(const char *prefix, } if (has_index) { - if (reset_tree(&index_tree, 0, 0)) + if (reset_tree(&index_tree)) ret = -1; } else { unstage_changes_unless_new(&c_tree); -- 2.56.0.134.g299a3c16181 ^ permalink raw reply related [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-10-14 18:21 UTC | newest] Thread overview: 20+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-05 16:35 [PATCH 0/2] stash: stop checking for changes twice Phillip Wood 2026-10-05 16:35 ` [PATCH 1/2] stash create: remove duplicate changes detection Phillip Wood 2026-10-06 12:44 ` Junio C Hamano 2026-10-07 13:49 ` Phillip Wood 2026-10-05 16:35 ` [PATCH 2/2] stash push: " Phillip Wood 2026-10-06 14:27 ` Junio C Hamano 2026-10-07 13:46 ` Phillip Wood 2026-10-07 17:15 ` Junio C Hamano 2026-10-08 13:35 ` Phillip Wood 2026-10-14 15:40 ` [PATCH v2 0/7] stash creation: avoid unecessary index loads and refreshes Phillip Wood 2026-10-14 15:40 ` [PATCH v2 1/7] stash: use an enum for create status Phillip Wood 2026-10-14 15:40 ` [PATCH v2 2/7] stash create: remove duplicate changes detection Phillip Wood 2026-10-14 15:40 ` [PATCH v2 3/7] stash push: " Phillip Wood 2026-10-14 15:40 ` [PATCH v2 4/7] stash push: avoid duplicate calls to preload_index() Phillip Wood 2026-10-14 18:09 ` Junio C Hamano 2026-10-14 15:40 ` [PATCH v2 5/7] stash create: rework temporary index creation Phillip Wood 2026-10-14 18:16 ` Junio C Hamano 2026-10-14 15:40 ` [PATCH v2 6/7] stash create: remove unnecessary index load and refresh Phillip Wood 2026-10-14 18:21 ` Junio C Hamano 2026-10-14 15:40 ` [PATCH v2 7/7] stash: simplify reset_tree() function signature Phillip Wood
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox