threads / discuss / 63952

[FEATURE] Proposal: git stash --only-unstaged

Subject: [FEATURE] Proposal: git stash --only-unstaged

## tl;dr

11 messages between Aug 13, 2025 and Sep 16, 2025.

replies: 10people: 4as markdown or json

J. Dettweiler· Aug 13, 2025, 08:51 UTC · lore
Hi all,

I’ve run into a recurring workflow problem when splitting commits during an interactive rebase, and I think Git could benefit from an option to stash *only* the working tree (unstaged) changes, without saving or restoring the index (staged changes) at all.

---
**Scenario:**
- I have a commit that needs to be split.
- I stage the part of the changes that will remain in the earlier commit 
(this becomes the new, fixed commit).
- The rest of the changes (which belong in a later commit) remain 
unstaged in the working tree.
- I want to test the staged commit in isolation before actually 
committing it, without losing or committing the later changes.
- After testing, I want to bring back the unstaged changes exactly as 
they were.
---
**Current limitations:**
- `git stash --keep-index` still saves the index in the stash object.
- When I later `git stash pop`, Git tries to restore those staged 
changes, often causing merge conflicts if I’ve modified them during the 
test.
- `git stash -p` and `git diff`+`git apply` can work as workarounds, but 
they are clunky and error-prone in longer rebases.
- The goal is essentially:
   > “stash the working tree only, leave the index untouched and 
unrecorded in the stash.”
---

**Proposed feature:** A new option, for example: git stash push --only-unstaged

---
This would:
- Save only the unstaged working tree changes to the stash.
- Leave the index both in the working directory and completely absent 
from the stash object.
- Make `stash pop` safe even if the index has changed in the meantime.
---
**Benefits:**
- Cleaner workflows for splitting commits during interactive rebases.
- Safer testing of staged changes in isolation.
- Avoids unnecessary conflicts on stash pop.
---

I have not found an existing Git command that provides this exact behavior, nor an option to `git stash` that prevents the index from being stored in the stash object.

Has this been considered before? Would there be interest in adding such an option?

Thanks for your time and for Git!
J. Dettweiler
Junio C Hamano· Aug 13, 2025, 15:30 UTC · re: J. Dettweiler · lore

Re: [FEATURE] Proposal: git stash --only-unstaged

"J. Dettweiler" <git.vger.kernel.org@dettweb.de> writes:
Show 19 quoted lines
> **Proposed feature:**
> A new option, for example: git stash push --only-unstaged
>
> ---
>
> This would:
> - Save only the unstaged working tree changes to the stash.
> - Leave the index both in the working directory and completely absent
>   from the stash object.
> - Make `stash pop` safe even if the index has changed in the meantime.
>
> ---
>
> **Benefits:**
> - Cleaner workflows for splitting commits during interactive rebases.
> - Safer testing of staged changes in isolation.
> - Avoids unnecessary conflicts on stash pop.
>
> ---

What is the downside? If the users of "git stash --keep" can keep using the same workflow by switching to this new option, you wouldn't be proposing it as a separate option---rather you would be proposing an improvement to the "--keep" option. So there must be something that the users would have to do differently if they switch to this new "feature", but it is unclear what that is.

D. Ben Knoble· Aug 13, 2025, 17:02 UTC · re: J. Dettweiler · lore

Re: [FEATURE] Proposal: git stash --only-unstaged

On Wed, Aug 13, 2025 at 5:00 AM J. Dettweiler <git.vger.kernel.org@dettweb.de> wrote:

Show 33 quoted lines
>
> Hi all,
>
> I’ve run into a recurring workflow problem when splitting commits during
> an interactive rebase, and I think Git could benefit from an option to
> stash *only* the working tree (unstaged) changes, without saving or
> restoring the index (staged changes) at all.
>
> ---
>
> **Scenario:**
> - I have a commit that needs to be split.
> - I stage the part of the changes that will remain in the earlier commit
> (this becomes the new, fixed commit).
> - The rest of the changes (which belong in a later commit) remain
> unstaged in the working tree.
> - I want to test the staged commit in isolation before actually
> committing it, without losing or committing the later changes.
> - After testing, I want to bring back the unstaged changes exactly as
> they were.
>
> ---
>
> **Current limitations:**
> - `git stash --keep-index` still saves the index in the stash object.
> - When I later `git stash pop`, Git tries to restore those staged
> changes, often causing merge conflicts if I’ve modified them during the
> test.
> - `git stash -p` and `git diff`+`git apply` can work as workarounds, but
> they are clunky and error-prone in longer rebases.
> - The goal is essentially:
>    > “stash the working tree only, leave the index untouched and
> unrecorded in the stash.”
I can reproduce (2.50.1):
    git init
    echo a >a && echo b >b && git add .
    git commit -m.
    echo c >>a && echo d >>b && git add b
    git stash --keep-index
    echo f >>b && git add . && git commit -m.

Here, “git stash pop” creates conflicts for b, even with “--no-index”. “git stash branch foo” is slightly more sensible, but also includes the stashed index changes for b, which I thought were normally dropped when applying stashes [1,2]. This also seems like exactly the use case that “Testing partial commits” in “git help stash“ is supposed to cover.

Taking a closer look, though, the conflicts aren’t from the index? The stash tree for the “W” commit (aka stash@{0}^{tree}) has the changes in a and has b in the state before more modifications were made (since the version in the index and working tree were the same). Try “git diff @^{tree} stash@{0}^{tree}”. Now, the I commit in the stash has the same changes for b (git diff @^{tree} stash@{0}^2^{tree}), so that explains why it seems like the conflicts are about the index!

I don’t see how to avoid this in general: “git restore -W -s stash@{0} a” does what I want, but wouldn’t scale to many files easily. If I do a “git restore -Ws @ b” in the recipe above _before_ stashing, then I get no conflicts on “git stash pop” (see tree diffs above for why), but that seems hard to remember to do (and isn’t documented in “git help stash” as being necessary)?

[1]: https://lore.kernel.org/git/20250510183358.36806-1-ben.knoble+github@gmail.com/ [2]: https://lore.kernel.org/git/20250510183358.36806-4-ben.knoble+github@gmail.com/

Phillip Wood· Aug 16, 2025, 16:12 UTC · re: J. Dettweiler · lore

Re: [FEATURE] Proposal: git stash --only-unstaged

On 13/08/2025 09:51, J. Dettweiler wrote:
Show 6 quoted lines
> 
> **Current limitations:**
> - `git stash --keep-index` still saves the index in the stash object.
> - When I later `git stash pop`, Git tries to restore those staged 
> changes, often causing merge conflicts if I’ve modified them during the 
> test.

As the index and the worktree are saved in the stash we can restore the unstaged changes with a three-way merge between the stashed index, HEAD and the stashed worktree. The (lightly tested) script below does that and updates the worktree without changing the index unless there are conflicts. There will only be conflicts when a staged line that is adjacent to an unstaged line is changed. Changes to staged lines that are not next to unstaged lines should not create conflicts as they are only modified on one side of the merge.

To use it create your stash with "git stash push --keep-index" and then use the script to pop the unstaged changes rather than using "git stash pop". If the script proves to be useful then perhaps we could add an "--unstaged" option to "git stash pop"

Thanks
Phillip

---- 8< ---- #!/bin/sh

USAGE="${0##*/} [<stash>]" SUBDIRECTORY_OK=1 . "$(git --exec-path)/git-sh-setup" cd_to_toplevel require_clean_work_tree stash-pop-unstaged

LF='
'
stash="${1:-stash@{0}}"
if ! stash_oid="$(git rev-parse --verify --quiet "$stash")" ||
    ! oid1="$(git rev-parse --verify --quiet $stash_oid^1)" ||
    ! oid2="$(git rev-parse $stash_oid^2^@ 2>/dev/null)" ||
    test "$oid1" != "$oid2"
then
     die "error: '$stash' does not look like a stash commit"
fi
merge_output="$(git merge-tree --merge-base=$stash_oid^2: HEAD: $stash_oid:)"
status=$?
if test $status = 0
then
     tree=$merge_output
     conflict_info=
     messages=
elif test $status = 1
then
     merge_output="$merge_output$LF"
     tree="${merge_output%%$LF*}"
     conflict_info="${merge_output%%$LF$LF*}"
     conflict_info="${conflict_info#*$LF}"
     messages="${merge_output#*$LF$LF}"
else
     exit 128
fi
git read-tree --index-output="$GIT_DIR/stash-pop-index" -m -u HEAD $tree &&
rm "$GIT_DIR/stash-pop-index" &&
if test -n "$conflict_info"
then
     printf '%s' "$messages" >&2
     printf '%s\n' "$conflict_info" | git update-index --index-info
else
     git stash drop "$stash"
fi
Junio C Hamano· Aug 17, 2025, 16:08 UTC · re: Phillip Wood · lore

Re: [FEATURE] Proposal: git stash --only-unstaged

Phillip Wood <phillip.wood123@gmail.com> writes:
> To use it create your stash with "git stash push --keep-index" and then
> use the script to pop the unstaged changes rather than using "git stash
> pop". If the script proves to be useful then perhaps we could add an
> "--unstaged" option to "git stash pop"

Hmph, would the behaviour useful enough that it should be always enabled, without any new option? I strongly suspect that those who worked on adding "--keep-index" option did not expect the user to be mucking with the working tree files while "testing the staged stuff by updating the working tree files to match it and nothing else", and as long as the end-user stays within that originally designed use case, nothing changes for them, no?

Thanks.
(the script left for reference without comments below).
Show 45 quoted lines
> ---- 8< ----
> #!/bin/sh
>
> USAGE="${0##*/} [<stash>]"
> SUBDIRECTORY_OK=1
> . "$(git --exec-path)/git-sh-setup"
> cd_to_toplevel
> require_clean_work_tree stash-pop-unstaged
>
> LF='
> '
> stash="${1:-stash@{0}}"
> if ! stash_oid="$(git rev-parse --verify --quiet "$stash")" ||
>    ! oid1="$(git rev-parse --verify --quiet $stash_oid^1)" ||
>    ! oid2="$(git rev-parse $stash_oid^2^@ 2>/dev/null)" ||
>    test "$oid1" != "$oid2"
> then
>     die "error: '$stash' does not look like a stash commit"
> fi
> merge_output="$(git merge-tree --merge-base=$stash_oid^2: HEAD: $stash_oid:)"
> status=$?
> if test $status = 0
> then
>     tree=$merge_output
>     conflict_info=
>     messages=
> elif test $status = 1
> then
>     merge_output="$merge_output$LF"
>     tree="${merge_output%%$LF*}"
>     conflict_info="${merge_output%%$LF$LF*}"
>     conflict_info="${conflict_info#*$LF}"
>     messages="${merge_output#*$LF$LF}"
> else
>     exit 128
> fi
> git read-tree --index-output="$GIT_DIR/stash-pop-index" -m -u HEAD $tree &&
> rm "$GIT_DIR/stash-pop-index" &&
> if test -n "$conflict_info"
> then
>     printf '%s' "$messages" >&2
>     printf '%s\n' "$conflict_info" | git update-index --index-info
> else
>     git stash drop "$stash"
> fi
Phillip Wood· Aug 18, 2025, 15:14 UTC · re: Junio C Hamano · lore

Re: [FEATURE] Proposal: git stash --only-unstaged

On 17/08/2025 17:08, Junio C Hamano wrote:
Show 14 quoted lines
> Phillip Wood <phillip.wood123@gmail.com> writes:
> 
>> To use it create your stash with "git stash push --keep-index" and then
>> use the script to pop the unstaged changes rather than using "git stash
>> pop". If the script proves to be useful then perhaps we could add an
>> "--unstaged" option to "git stash pop"
> 
> Hmph, would the behaviour useful enough that it should be always
> enabled, without any new option?  I strongly suspect that those who
> worked on adding "--keep-index" option did not expect the user to be
> mucking with the working tree files while "testing the staged stuff
> by updating the working tree files to match it and nothing else",
> and as long as the end-user stays within that originally designed
> use case, nothing changes for them, no?

Yes it probably would make sense if we knew that the stash had been created with "--keep-index". At the moment I don't think there is anywhere to tell from looking at the stash if that's the case but we could add a trailer to the commit message when we create the stash to record that it was created with "--keep-index" and apply only the unstaged changes when we see that trailer.

Thanks
Phillip
Show 49 quoted lines
> Thanks.
> 
> (the script left for reference without comments below).
> 
>> ---- 8< ----
>> #!/bin/sh
>>
>> USAGE="${0##*/} [<stash>]"
>> SUBDIRECTORY_OK=1
>> . "$(git --exec-path)/git-sh-setup"
>> cd_to_toplevel
>> require_clean_work_tree stash-pop-unstaged
>>
>> LF='
>> '
>> stash="${1:-stash@{0}}"
>> if ! stash_oid="$(git rev-parse --verify --quiet "$stash")" ||
>>     ! oid1="$(git rev-parse --verify --quiet $stash_oid^1)" ||
>>     ! oid2="$(git rev-parse $stash_oid^2^@ 2>/dev/null)" ||
>>     test "$oid1" != "$oid2"
>> then
>>      die "error: '$stash' does not look like a stash commit"
>> fi
>> merge_output="$(git merge-tree --merge-base=$stash_oid^2: HEAD: $stash_oid:)"
>> status=$?
>> if test $status = 0
>> then
>>      tree=$merge_output
>>      conflict_info=
>>      messages=
>> elif test $status = 1
>> then
>>      merge_output="$merge_output$LF"
>>      tree="${merge_output%%$LF*}"
>>      conflict_info="${merge_output%%$LF$LF*}"
>>      conflict_info="${conflict_info#*$LF}"
>>      messages="${merge_output#*$LF$LF}"
>> else
>>      exit 128
>> fi
>> git read-tree --index-output="$GIT_DIR/stash-pop-index" -m -u HEAD $tree &&
>> rm "$GIT_DIR/stash-pop-index" &&
>> if test -n "$conflict_info"
>> then
>>      printf '%s' "$messages" >&2
>>      printf '%s\n' "$conflict_info" | git update-index --index-info
>> else
>>      git stash drop "$stash"
>> fi
Ben Knoble· Aug 18, 2025, 23:41 UTC · re: Phillip Wood · lore

Re: [FEATURE] Proposal: git stash --only-unstaged

Show 15 quoted lines
> Le 18 août 2025 à 11:14, Phillip Wood <phillip.wood123@gmail.com> a écrit :
> 
> On 17/08/2025 17:08, Junio C Hamano wrote:
>> Phillip Wood <phillip.wood123@gmail.com> writes:
>>> To use it create your stash with "git stash push --keep-index" and then
>>> use the script to pop the unstaged changes rather than using "git stash
>>> pop". If the script proves to be useful then perhaps we could add an
>>> "--unstaged" option to "git stash pop"
>> Hmph, would the behaviour useful enough that it should be always
>> enabled, without any new option?  I strongly suspect that those who
>> worked on adding "--keep-index" option did not expect the user to be
>> mucking with the working tree files while "testing the staged stuff
>> by updating the working tree files to match it and nothing else",
>> and as long as the end-user stays within that originally designed
>> use case, nothing changes for them, no?
I can’t particularly comment on the script, but I do think the example in the docs should either just work or be adjusted to work.
Show 5 quoted lines
> Yes it probably would make sense if we knew that the stash had been created with "--keep-index". At the moment I don't think there is anywhere to tell from looking at the stash if that's the case but we could add a trailer to the commit message when we create the stash to record that it was created with "--keep-index" and apply only the unstaged changes when we see that trailer.
> 
> Thanks
> 
> Phillip
This seems reasonable; I wonder if there would be any interaction with the stash import/export features? But perhaps those omitted the index anyway, I cannot recall.
Phillip Wood· Aug 29, 2025, 13:06 UTC · re: Ben Knoble · lore

Re: [FEATURE] Proposal: git stash --only-unstaged

Hi Ben
On 19/08/2025 00:41, Ben Knoble wrote:
Show 18 quoted lines
> 
>> Le 18 août 2025 à 11:14, Phillip Wood <phillip.wood123@gmail.com> a écrit :
>>
>> On 17/08/2025 17:08, Junio C Hamano wrote:
>>> Phillip Wood <phillip.wood123@gmail.com> writes:
>>>> To use it create your stash with "git stash push --keep-index" and then
>>>> use the script to pop the unstaged changes rather than using "git stash
>>>> pop". If the script proves to be useful then perhaps we could add an
>>>> "--unstaged" option to "git stash pop"
>>> Hmph, would the behaviour useful enough that it should be always
>>> enabled, without any new option?  I strongly suspect that those who
>>> worked on adding "--keep-index" option did not expect the user to be
>>> mucking with the working tree files while "testing the staged stuff
>>> by updating the working tree files to match it and nothing else",
>>> and as long as the end-user stays within that originally designed
>>> use case, nothing changes for them, no?
> 
> I can’t particularly comment on the script, but I do think the example
 > in the docs should either just work or be adjusted to work.

I think the example works but may generate conflicts when the stash is popped. One can argue that the conflicts are unnecessary because they could be avoided by popping the unstaged changes but I don't think the example is broken as such.

>> Yes it probably would make sense if we knew that the stash had been
>> created with "--keep-index". At the moment I don't think there is
 >> anywhere to tell from looking at the stash if that's the case but we
 >> could add a trailer to the commit message when we create the stash to
 >> record that it was created with "--keep-index" and apply only the
 >> unstaged changes when we see that trailer.>
> This seems reasonable; I wonder if there would be any interaction 
 > with the stash import/export features? But perhaps those omitted the
 > index anyway, I cannot recall.
It exports the stash commits as-is so it should be fine I think
Thanks
Phillip
D. Ben Knoble· Aug 29, 2025, 13:30 UTC · re: Phillip Wood · lore

Re: [FEATURE] Proposal: git stash --only-unstaged

On Fri, Aug 29, 2025 at 9:06 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 28 quoted lines
>
> Hi Ben
>
> On 19/08/2025 00:41, Ben Knoble wrote:
> >
> >> Le 18 août 2025 à 11:14, Phillip Wood <phillip.wood123@gmail.com> a écrit :
> >>
> >> On 17/08/2025 17:08, Junio C Hamano wrote:
> >>> Phillip Wood <phillip.wood123@gmail.com> writes:
> >>>> To use it create your stash with "git stash push --keep-index" and then
> >>>> use the script to pop the unstaged changes rather than using "git stash
> >>>> pop". If the script proves to be useful then perhaps we could add an
> >>>> "--unstaged" option to "git stash pop"
> >>> Hmph, would the behaviour useful enough that it should be always
> >>> enabled, without any new option?  I strongly suspect that those who
> >>> worked on adding "--keep-index" option did not expect the user to be
> >>> mucking with the working tree files while "testing the staged stuff
> >>> by updating the working tree files to match it and nothing else",
> >>> and as long as the end-user stays within that originally designed
> >>> use case, nothing changes for them, no?
> >
> > I can’t particularly comment on the script, but I do think the example
>  > in the docs should either just work or be adjusted to work.
>
> I think the example works but may generate conflicts when the stash is
> popped. One can argue that the conflicts are unnecessary because they
> could be avoided by popping the unstaged changes but I don't think the
> example is broken as such.

Thanks, let me try to rephrase: the example makes no mention of conflicts appearing or having to adjust them. It seems to heavily imply to me that no such conflicts are expected, though as we discussed upthread it seems unlikely you _won't_ get conflicts if you do

               # ... hack hack hack ...
               $ git add --patch foo            # add just first part
to the index
               $ git stash push --keep-index    # save all other
changes to the stash
               $ edit/build/test first part
               $ git commit -m 'First part'     # commit fully tested change
               $ git stash pop                  # prepare to work on
all other changes
               # ... repeat above five steps until one commit remains ...
               $ edit/build/test remaining parts
               $ git commit foo -m 'Remaining parts'

since we explicitly say "edit[…] first part," since it seems natural to me for that to include editing "foo." Perhaps this is where Junio's suspicion (reproduced below) falls afoul of the language used in the example?

Show 6 quoted lines
> I strongly suspect that those who
> worked on adding "--keep-index" option did not expect the user to be
> mucking with the working tree files while "testing the staged stuff
> by updating the working tree files to match it and nothing else",
> and as long as the end-user stays within that originally designed
> use case, nothing changes for them, no?
I suppose my main complaint is nothing about the example makes it
clear that's the intended use case to me? Hence
- we could change the example to mention conflicts and/or use case
(smaller patch, punts on the problem)
- we could change the code to accommodate the example as written
(using ideas from your script; harder but bigger win IMO?)
Show 11 quoted lines
> >> Yes it probably would make sense if we knew that the stash had been
> >> created with "--keep-index". At the moment I don't think there is
>  >> anywhere to tell from looking at the stash if that's the case but we
>  >> could add a trailer to the commit message when we create the stash to
>  >> record that it was created with "--keep-index" and apply only the
>  >> unstaged changes when we see that trailer.>
> > This seems reasonable; I wonder if there would be any interaction
>  > with the stash import/export features? But perhaps those omitted the
>  > index anyway, I cannot recall.
>
> It exports the stash commits as-is so it should be fine I think
Great!
> Thanks
>
> Phillip
Thank _you_ for taking a look and providing something to work from :)
-- 
D. Ben Knoble
Phillip Wood· Sep 16, 2025, 11:03 UTC · re: D. Ben Knoble · lore

Re: [FEATURE] Proposal: git stash --only-unstaged

Hi Ben
On 29/08/2025 14:30, D. Ben Knoble wrote:
Show 12 quoted lines
> On Fri, Aug 29, 2025 at 9:06 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>
>> I think the example works but may generate conflicts when the stash is
>> popped. One can argue that the conflicts are unnecessary because they
>> could be avoided by popping the unstaged changes but I don't think the
>> example is broken as such.
> 
> Thanks, let me try to rephrase: the example makes no mention of
> conflicts appearing or having to adjust them. It seems to heavily
> imply to me that no such conflicts are expected, though as we
> discussed upthread it seems unlikely you _won't_ get conflicts if you
> do

Yes, I think if you edit any staged changes (that is the lines that differed between the index and HEAD when "git stash" was run) you'll end up with conflicts. If you edit a line where the index, HEAD and the worktree matched when the stash was created then I don't think you will see a conflict. Overall conflicts seem pretty likely, so maybe we should mention them in the documentation.

Show 7 quoted lines
> [...]
> I suppose my main complaint is nothing about the example makes it
> clear that's the intended use case to me? Hence
> - we could change the example to mention conflicts and/or use case
> (smaller patch, punts on the problem)
> - we could change the code to accommodate the example as written
> (using ideas from your script; harder but bigger win IMO?)

I was hoping that we'd hear back from J. Dettweiler as to whether the ideas in the script were useful. It would certainly be better to update the command to avoid conflicts if we can.

Thanks
Phillip
D. Ben Knoble· Sep 16, 2025, 17:10 UTC · re: Phillip Wood · lore

Re: [FEATURE] Proposal: git stash --only-unstaged

On Tue, Sep 16, 2025 at 7:03 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 23 quoted lines
>
> Hi Ben
>
> On 29/08/2025 14:30, D. Ben Knoble wrote:
> > On Fri, Aug 29, 2025 at 9:06 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> >>
> >> I think the example works but may generate conflicts when the stash is
> >> popped. One can argue that the conflicts are unnecessary because they
> >> could be avoided by popping the unstaged changes but I don't think the
> >> example is broken as such.
> >
> > Thanks, let me try to rephrase: the example makes no mention of
> > conflicts appearing or having to adjust them. It seems to heavily
> > imply to me that no such conflicts are expected, though as we
> > discussed upthread it seems unlikely you _won't_ get conflicts if you
> > do
>
> Yes, I think if you edit any staged changes (that is the lines that
> differed between the index and HEAD when "git stash" was run) you'll end
> up with conflicts. If you edit a line where the index, HEAD and the
> worktree matched when the stash was created then I don't think you will
> see a conflict. Overall conflicts seem pretty likely, so maybe we should
> mention them in the documentation.

Thanks—I realize we were talking across each other, so I'm glad we were able to get on the same page.

Show 11 quoted lines
> > [...]
> > I suppose my main complaint is nothing about the example makes it
> > clear that's the intended use case to me? Hence
> > - we could change the example to mention conflicts and/or use case
> > (smaller patch, punts on the problem)
> > - we could change the code to accommodate the example as written
> > (using ideas from your script; harder but bigger win IMO?)
>
> I was hoping that we'd hear back from J. Dettweiler as to whether the
> ideas in the script were useful. It would certainly be better to update
> the command to avoid conflicts if we can.
Sounds like a good idea.
-- 
D. Ben Knoble

← back to recent threads