git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* stashing only unstaged changes?
@ 2022-06-21 19:26 Tim Chase
  2022-06-24  8:16 ` Reto
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Tim Chase @ 2022-06-21 19:26 UTC (permalink / raw)
  To: git

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)

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: stashing only unstaged changes?
  2022-06-21 19:26 stashing only unstaged changes? Tim Chase
@ 2022-06-24  8:16 ` Reto
  2022-06-24 20:53   ` Tim Chase
  2022-06-24  9:55 ` Konstantin Khomoutov
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 10+ messages in thread
From: Reto @ 2022-06-24  8:16 UTC (permalink / raw)
  To: Tim Chase; +Cc: git

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

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: stashing only unstaged changes?
  2022-06-21 19:26 stashing only unstaged changes? Tim Chase
  2022-06-24  8:16 ` Reto
@ 2022-06-24  9:55 ` Konstantin Khomoutov
  2022-06-24 11:23 ` Erik Cervin Edin
  2022-06-24 13:09 ` Ævar Arnfjörð Bjarmason
  3 siblings, 0 replies; 10+ messages in thread
From: Konstantin Khomoutov @ 2022-06-24  9:55 UTC (permalink / raw)
  To: Tim Chase; +Cc: git

On Tue, Jun 21, 2022 at 02:26:18PM -0500, Tim Chase wrote:

> 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.


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: stashing only unstaged changes?
  2022-06-21 19:26 stashing only unstaged changes? Tim Chase
  2022-06-24  8:16 ` Reto
  2022-06-24  9:55 ` Konstantin Khomoutov
@ 2022-06-24 11:23 ` Erik Cervin Edin
  2022-06-24 20:38   ` Tim Chase
  2022-06-24 13:09 ` Ævar Arnfjörð Bjarmason
  3 siblings, 1 reply; 10+ messages in thread
From: Erik Cervin Edin @ 2022-06-24 11:23 UTC (permalink / raw)
  To: Tim Chase; +Cc: git

My $0.02

On Tue, Jun 21, 2022 at 9:57 PM Tim Chase <git@tim.thechases.com> wrote:
>
> 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.

> 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

> 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.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: stashing only unstaged changes?
  2022-06-21 19:26 stashing only unstaged changes? Tim Chase
                   ` (2 preceding siblings ...)
  2022-06-24 11:23 ` Erik Cervin Edin
@ 2022-06-24 13:09 ` Ævar Arnfjörð Bjarmason
  2022-06-24 13:49   ` Erik Cervin Edin
  3 siblings, 1 reply; 10+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-06-24 13:09 UTC (permalink / raw)
  To: Tim Chase; +Cc: git, Daryl Van Den Brink


On Tue, Jun 21 2022, Tim Chase wrote:

> 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/

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: stashing only unstaged changes?
  2022-06-24 13:09 ` Ævar Arnfjörð Bjarmason
@ 2022-06-24 13:49   ` Erik Cervin Edin
  2022-06-24 15:32     ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 10+ messages in thread
From: Erik Cervin Edin @ 2022-06-24 13:49 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Tim Chase, git, Daryl Van Den Brink

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.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: stashing only unstaged changes?
  2022-06-24 13:49   ` Erik Cervin Edin
@ 2022-06-24 15:32     ` Ævar Arnfjörð Bjarmason
  2022-06-24 19:47       ` Junio C Hamano
  0 siblings, 1 reply; 10+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-06-24 15:32 UTC (permalink / raw)
  To: Erik Cervin Edin; +Cc: Tim Chase, git, Daryl Van Den Brink


On Fri, Jun 24 2022, Erik Cervin Edin wrote:

> 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.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: stashing only unstaged changes?
  2022-06-24 15:32     ` Ævar Arnfjörð Bjarmason
@ 2022-06-24 19:47       ` Junio C Hamano
  0 siblings, 0 replies; 10+ messages in thread
From: Junio C Hamano @ 2022-06-24 19:47 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Erik Cervin Edin, Tim Chase, git, Daryl Van Den Brink

Æ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.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: stashing only unstaged changes?
  2022-06-24 11:23 ` Erik Cervin Edin
@ 2022-06-24 20:38   ` Tim Chase
  0 siblings, 0 replies; 10+ messages in thread
From: Tim Chase @ 2022-06-24 20:38 UTC (permalink / raw)
  To: git

On 2022-06-24 13:23, Erik Cervin Edin wrote:
> > 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.

> 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.

> 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.

> > 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.

> > 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.

> > 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






^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: stashing only unstaged changes?
  2022-06-24  8:16 ` Reto
@ 2022-06-24 20:53   ` Tim Chase
  0 siblings, 0 replies; 10+ messages in thread
From: Tim Chase @ 2022-06-24 20:53 UTC (permalink / raw)
  To: git

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


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2022-06-24 21:17 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-06-21 19:26 stashing only unstaged changes? Tim Chase
2022-06-24  8:16 ` Reto
2022-06-24 20:53   ` Tim Chase
2022-06-24  9:55 ` Konstantin Khomoutov
2022-06-24 11:23 ` Erik Cervin Edin
2022-06-24 20:38   ` Tim Chase
2022-06-24 13:09 ` Ævar Arnfjörð Bjarmason
2022-06-24 13:49   ` Erik Cervin Edin
2022-06-24 15:32     ` Ævar Arnfjörð Bjarmason
2022-06-24 19:47       ` Junio C Hamano

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).