threads / discuss / 58044

stashing only unstaged changes?

Subject: stashing only unstaged changes?

## tl;dr

10 messages between Jun 21, 2022 and Jun 24, 2022.

replies: 9people: 6as markdown or json

Tim Chase· Jun 21, 2022, 19:26 UTC · lore

I recently had composed a commit with some `git add -p` leaving some portions unstaged. I wanted to stash the unstaged changes to make sure that the staged code ran as expected, so I did a `git stash` only to find that it unstaged my staged changes and stashed *everything*.

Using `git stash --saved` does the opposite of what I want (stashing the index, not the difference between the index and the working-copy)

So I carefully re-`git add -p`'ed everything and tried `git stash --keep-index` which sounded promising (my index remained the same), but popping my stash ended up causing conflicts because it had stashed the diff of HEAD..working-copy, not INDEX..working-copy. A `git stash show -p` confirmed that the stash included things that I had already staged.

So I carefully re-`git add -p`ed everything yet again, but then got stuck trying to convince `stash` to save a snapshot of only the diff in my working directory. To work around it, I did a `git diff > temp.patch` to obtain the stuff I'd wanted to stash, a `git reset --staged` to clear out those changes, ran my code to verify (eventually committing it), and then applied the `temp.patch` back on top of my changes. It worked, but felt convoluted.

I did see the `git stash -p` option, to manually choose the inverse bits, but for what I was doing, it was more sensible to `git add -p` and try to stash the rest.

So is there some option I've missed to tell `git stash` to stash only the delta between the uncommitted-index and the working-copy?

Thanks,
-Tim
(I'd posted this on /r/git
https://www.reddit.com/r/git/comments/vchu83/stashing_only_unstaged_changes/
but figured I'd try my hand here in the hope of more answers)
Reto· Jun 24, 2022, 08:16 UTC · re: Tim Chase · lore

Re: stashing only unstaged changes?

You should be using proper branches, not stash.
Here's an email with some background from Junio:
https://lore.kernel.org/git/xmqq5ylior3l.fsf@gitster.g/
Some quotes:
> The intended use of "stash" is to clear the deck as quickly as
> possible to deal with "emergencies"
> Users are better off doing any large scale "I made a mess in the
> working tree with mixed changes, and I want to take time to separate
> them out" on separate (possibly temporary) branches, instead of
> using "stash save" + "stash pop"
In other words, you are using the wrong tool for the job probably.

Cheers, Reto

Tim Chase· Jun 24, 2022, 20:53 UTC · re: Reto · lore

Re: stashing only unstaged changes?

On 2022-06-24 10:16, Reto wrote:
> https://lore.kernel.org/git/xmqq5ylior3l.fsf@gitster.g/
>   
> > The intended use of "stash" is to clear the deck as quickly as
> > possible to deal with "emergencies"    

Understandable. Just in my case, the deck I wanted to clear to quickly was the contents of the index, not the HEAD commit.

-Tim
Konstantin Khomoutov· Jun 24, 2022, 09:55 UTC · re: Tim Chase · lore

Re: stashing only unstaged changes?

On Tue, Jun 21, 2022 at 02:26:18PM -0500, Tim Chase wrote:
Show 5 quoted lines
> I recently had composed a commit with some `git add -p` leaving some
> portions unstaged. I wanted to stash the unstaged changes to make
> sure that the staged code ran as expected, so I did  a `git stash`
> only to find that it unstaged my staged changes and stashed
> *everything*.

I tend to rely on reflogs for this. Basically, I roll like this:

 1. Stage the necessary changes, commit.
 
 2. Stage the remaining changes - what you've left unstaged in your case, -
    commit.
 
 3. Go to the commit I need to test; in this simple case that'd be
      $ git checkout HEAD~
    Then test the changes.
 4. Go back to the previous state using the HEAD's reflog:
     $ git checkout HEAD@{1}

If you now need to have the situation where the changes committed on step 2 are left only in the work tree, run

  $ git reset HEAD~

so that you have the HEAD and the index reset to the commit recorded on step 1 with the changes from the commit 2 left in the work tree.

Having said that, I'd note that I tend to do work on the detached HEAD but you could apply the same logic to working on a a branch.

Erik Cervin Edin· Jun 24, 2022, 11:23 UTC · re: Tim Chase · lore

Re: stashing only unstaged changes?

My $0.02
On Tue, Jun 21, 2022 at 9:57 PM Tim Chase <git@tim.thechases.com> wrote:
Show 6 quoted lines
>
> I recently had composed a commit with some `git add -p` leaving some
> portions unstaged. I wanted to stash the unstaged changes to make
> sure that the staged code ran as expected, so I did  a `git stash`
> only to find that it unstaged my staged changes and stashed
> *everything*.
What you wanted to do was
  git stash --keep-index
which creates a stash with the staged and unstaged changes but leaves
the staged ones in the working tree.
If you forget to do this, what you do is try
  git stash pop --index
and then
  git stash --keep-index
> Using `git stash --saved` does the opposite of what I want (stashing
> the index, not the difference between the index and the working-copy)
I'm unaware of a --saved option

My understanding (which may be incorrect) is that a shash is always of the staged/unstaged changes and there's no way to stash only one or the other in a single stash operation.

Show 6 quoted lines
> So I carefully re-`git add -p`'ed everything and tried `git stash
> --keep-index` which sounded promising (my index remained the same),
> but popping my stash ended up causing conflicts because it had
> stashed the diff of HEAD..working-copy, not INDEX..working-copy.  A
> `git stash show -p` confirmed that the stash included things that I
> had already staged.
Such conflicts are usually trivially be resolved by taking "theirs"
I have a helper script that does this and it's basically
  git ls-files --unmerged -z |\
    xargs -0 sed -i -e '/^<\{7\}/,/^=\{7\}/d' --e '/^>\{7\}/d' &&
    git ls-files --unmerged -z | xargs -0 git add --
though, unfortunately, it also stages the content as a part of marking
resolution.
> So I carefully re-`git add -p`ed everything yet again, but then got
> stuck trying to convince `stash` to save a snapshot of only the diff
> in my working directory.
A stash is always both staged and unstaged changes of the files.
To stash only staged you may do
  git stash --keep-index
  git stash
The first stash will include staged/unstaged and the second only staged
To create a stash of only unstaged
  git commit -m tmp # create temporary commit w staged
  git stash # stash unstaged
  git reset HEAD~ &&  git stash # stash the previous staged as
unstaged (optionally git add  in the middle)
  git stash apply/pop stash@{1} # get the "unstaged" stash
As you noted such a stash is still based on a tree that may have
contained staged changes (ORIG_HEAD).
Ie. if you staged line 1 but not 2-3 the "unstaged" stash will also
contain line 1
This is doesn't happen if the staged/unstaged contain different files
Show 5 quoted lines
> To work around it, I did a `git diff >
> temp.patch` to obtain the stuff I'd wanted to stash, a `git reset
> --staged` to clear out those changes, ran my code to verify
> (eventually committing it), and then applied the `temp.patch` back on
> top of my changes. It worked, but felt convoluted.

That's basically what you have to do if you only want certain changes. (and also what --patch does under the hood)

> I did see the `git stash -p` option, to manually choose the inverse
> bits, but for what I was doing, it was more sensible to `git add -p`
> and try to stash the rest.
git stash --patch is MUCH slower than git add -p, so I personally never use it.
In my workflow I find it better to either
  git add -p
and then
  git stash --keep-index
or creating regular temporary commits, and fiddling with those,
perhaps using rebase and friends.
> So is there some option I've missed to tell `git stash` to stash only
> the delta between the uncommitted-index and the working-copy?
No, there is none.

In my experience, using regular add/commit/reset/branch/checkout/rebase is superior to using the stash for separating changes into discrete commits.

Tim Chase· Jun 24, 2022, 20:38 UTC · re: Erik Cervin Edin · lore

Re: stashing only unstaged changes?

On 2022-06-24 13:23, Erik Cervin Edin wrote:
Show 5 quoted lines
> > Using `git stash --saved` does the opposite of what I want
> > (stashing the index, not the difference between the index and the
> > working-copy)  
> 
> I'm unaware of a --saved option
Derp, meant to type "--keep-index" there.
> My understanding (which may be incorrect) is that a shash is always
> of the staged/unstaged changes and there's no way to stash only one
> or the other in a single stash operation.
That seems to be what I'm experiencing.
Show 7 quoted lines
> A stash is always both staged and unstaged changes of the files.
> 
> To stash only staged you may do
>   git stash --keep-index
>   git stash
> The first stash will include staged/unstaged and the second only
> staged
Right, which is what I'd tried.  Except
  git stash

effectively takes a diff of HEAD..{working copy} and stashes that, while --keep-index also takes note of what was in the index.

My hope had been for an option to have git-stash use the index as its baseline rather than use HEAD.

Show 12 quoted lines
> To create a stash of only unstaged
>   git commit -m tmp # create temporary commit w staged
>   git stash # stash unstaged
>   git reset HEAD~ &&  git stash # stash the previous staged as
> unstaged (optionally git add  in the middle)
>   git stash apply/pop stash@{1} # get the "unstaged" stash
> As you noted such a stash is still based on a tree that may have
> contained staged changes (ORIG_HEAD).
> Ie. if you staged line 1 but not 2-3 the "unstaged" stash will also
> contain line 1
> This is doesn't happen if the staged/unstaged contain different
> files

Yeah, those are among the issues I was bumping against. With different files, it's a little less troublesome. But when there might be some overlap, became problematic.

Show 8 quoted lines
> > To work around it, I did a `git diff >
> > temp.patch` to obtain the stuff I'd wanted to stash, a `git reset
> > --staged` to clear out those changes, ran my code to verify
> > (eventually committing it), and then applied the `temp.patch`
> > back on top of my changes. It worked, but felt convoluted.  
> 
> That's basically what you have to do if you only want certain
> changes. (and also what --patch does under the hood)

Okay, based on the other replies, it sounds like it might be the most practical route to go since I'm not missing some existing functionality of git-stash.

Show 6 quoted lines
> > I did see the `git stash -p` option, to manually choose the
> > inverse bits, but for what I was doing, it was more sensible to
> > `git add -p` and try to stash the rest.  
> 
> git stash --patch is MUCH slower than git add -p, so I personally
> never use it.

I don't find the speed an issue as much as I have trouble with any of the --patch variants that aren't `git add -p` because I'm never 100% positive which direction + vs - means. Which is partly why I wanted to stick with `add -p` and stash the non-staged stuff.

Show 5 quoted lines
> > So is there some option I've missed to tell `git stash` to stash
> > only the delta between the uncommitted-index and the
> > working-copy?  
> 
> No, there is none.

Thanks! That's pretty much what I'm getting from the rest of the replies, too.

-Tim
Ævar Arnfjörð Bjarmason· Jun 24, 2022, 13:09 UTC · re: Tim Chase · lore

Re: stashing only unstaged changes?

On Tue, Jun 21 2022, Tim Chase wrote:
Show 30 quoted lines
> I recently had composed a commit with some `git add -p` leaving some
> portions unstaged. I wanted to stash the unstaged changes to make
> sure that the staged code ran as expected, so I did  a `git stash`
> only to find that it unstaged my staged changes and stashed
> *everything*.
>
> Using `git stash --saved` does the opposite of what I want (stashing
> the index, not the difference between the index and the working-copy)
>
> So I carefully re-`git add -p`'ed everything and tried `git stash
> --keep-index` which sounded promising (my index remained the same),
> but popping my stash ended up causing conflicts because it had
> stashed the diff of HEAD..working-copy, not INDEX..working-copy.  A
> `git stash show -p` confirmed that the stash included things that I
> had already staged.
>
> So I carefully re-`git add -p`ed everything yet again, but then got
> stuck trying to convince `stash` to save a snapshot of only the diff
> in my working directory. To work around it, I did a `git diff >
> temp.patch` to obtain the stuff I'd wanted to stash, a `git reset
> --staged` to clear out those changes, ran my code to verify
> (eventually committing it), and then applied the `temp.patch` back on
> top of my changes.  It worked, but felt convoluted.
>
> I did see the `git stash -p` option, to manually choose the inverse
> bits, but for what I was doing, it was more sensible to `git add -p`
> and try to stash the rest.
>
> So is there some option I've missed to tell `git stash` to stash only
> the delta between the uncommitted-index and the working-copy?
Is what you want equivalent to:
    # save the "git add -p"'d chunks
    git stash push --staged
    # save the "uncommitted"
    git stash push
    # pop the previously staged
    git stash pop --index stash@{1}
?

I.e. this (ab)uses the stash itself to juggle the two around. I don't think there's a way to do this in one step, but I'm not very familiar with git-stash.

If that is what you want (and we don't have a way to do it) perhaps we should have a a:

    git stash push --unstaged

Which could start out as an alias for the above sequence, with e.g. an optional "--include-untracked" being passed to the second "git stash push" command above.

I also found this past thread (CC'd the author, in case it helps), which seems to be asking the same question: https://lore.kernel.org/git/CAC4jX8GEg5=9BPepYLntGRG7n_84ju7rTSYO82SQyuiiff0UcQ@mail.gmail.com/

Erik Cervin Edin· Jun 24, 2022, 13:49 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: stashing only unstaged changes?

On Fri, Jun 24, 2022 at 3:20 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:

Show 11 quoted lines
>
> Is what you want equivalent to:
>
>     # save the "git add -p"'d chunks
>     git stash push --staged
>     # save the "uncommitted"
>     git stash push
>     # pop the previously staged
>     git stash pop --index stash@{1}
>
> ?
Never noticed --staged before.
I just tried to
  seq 1 3 >> bar
then git add -p to stage with 2 and 3
followed by a git stash push --staged but that gave me the error
> error: patch failed: bar:2
> error: bar: patch does not apply
> Cannot remove worktree changes

But I'm guessing the command (and approach) work fine if the staged/unstaged are in different files.

Ævar Arnfjörð Bjarmason· Jun 24, 2022, 15:32 UTC · re: Erik Cervin Edin · lore

Re: stashing only unstaged changes?

On Fri, Jun 24 2022, Erik Cervin Edin wrote:
Show 26 quoted lines
> On Fri, Jun 24, 2022 at 3:20 PM Ævar Arnfjörð Bjarmason
> <avarab@gmail.com> wrote:
>>
>> Is what you want equivalent to:
>>
>>     # save the "git add -p"'d chunks
>>     git stash push --staged
>>     # save the "uncommitted"
>>     git stash push
>>     # pop the previously staged
>>     git stash pop --index stash@{1}
>>
>> ?
>
> Never noticed --staged before.
> I just tried to
>   seq 1 3 >> bar
> then git add -p to stage with 2 and 3
> followed by a git stash push --staged but that gave me the error
>
>> error: patch failed: bar:2
>> error: bar: patch does not apply
>> Cannot remove worktree changes
>
> But I'm guessing the command (and approach) work fine if the
> staged/unstaged are in different files.
Huh!

No, it does work when you stage chunks in the same file, I tested this by modifying the top & bottom of our README.md.

But we do indeed have that error (which looks like an unrelated bug) if you try to "git stash pust --staged" a hunk when that hunk overlaps with changes that are unstaged.

Junio C Hamano· Jun 24, 2022, 19:47 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: stashing only unstaged changes?

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
> No, it does work when you stage chunks in the same file, I tested this
> by modifying the top & bottom of our README.md.

This is primarily because of the three-way merge magic. If you consider what "git stash pop" does after "git stash push -k" followed by a "git commit", you can compare the difference between recorded HEAD and recorded working tree (which is replayed on top of the result of the "git commit" step) and what is in the working tree after "git commit". We do replay both the changes already committed (and is already in the working tree) and leftover ones (removed from the working tree when "push -k" was run), and the former is *often* resolved cleanly as "both sides (meaning: the "stash" and the human user who did "git commit") made the same change", while the latter is resolved cleanly because only the "stash" side.

Here, *often* is a key phrase. If you did a tricky "add -p" that edited the patch, such a three-way merge may not resolve itself cleanly.

In addition, if you did something after the "git commit", the former may get conflicts because what the "working tree" side did may not match what was in "stash", hence "both sides made the same change" no longer applies.

The story is very similar when the testing after "git stash push -k" turned out to be unsuccessful and the user decides not to commit. "git reset --hard HEAD && git stash pop" is how you would go back to the state before "git stash push -k" in such a case, but if you edit between these two operations, you can make "both sides did the same change" rule not to apply any more.

← back to recent threads