threads / bug / 57939

BUG? git stash and immediate git apply results in conflict

Subject: BUG? git stash and immediate git apply results in conflict

## tl;dr

4 messages between Jun 1, 2022 and Jun 2, 2022.

replies: 3people: 2as markdown or json

Akos Vandra-Meyer· Jun 1, 2022, 06:55 UTC · lore
Hello!

I believe there might be a bug in how a git stash is calculated with the -k flag. I would expect to always be able to stash and pop a set of changes, but if the working directory contains staged and unstaged changes for a file, this results in a conflict.

The stashed diff shows the changes from the last commit, rather than what is in the current working directory, so when the stash is applied it results in a conflict, because there are staged changes in the current working directory.

Steps to reproduce:
 mkdir test
 cd test
 git init .
 echo foo > a
 echo bar >> a
 echo baz >> a
 git add a
 git commit -m "initial"
 cat a | sed s/bar/foo2/ > a2; mv a2 a git status
 git add a
 cat a | sed s/foo2/xxx/ > a2; mv a2 a
 git status
 git stash -ku
 git stash pop
Thanks for looking into this,
  Akos Vandra-Meyer
Chris Torek· Jun 2, 2022, 06:32 UTC · re: Akos Vandra-Meyer · lore

Re: BUG? git stash and immediate git apply results in conflict

On Wed, Jun 1, 2022 at 2:11 PM Akos Vandra-Meyer <axos88@gmail.com> wrote:
>  git stash -ku
>  git stash pop
This is not a bug in `git stash` itself, but rather in the way you're using it.
There are two mistakes on your part here:

1: You are using `-k`, aka `--keep-index`. This flag is intended for usages that are not yours here.

2. You are *not* using `--index` in your `git pop`.  The `--index` flag is
intended for the kind of thing you are doing here.

It's a bit unfortunate (and perhaps worth some work in the documentation) that the `--keep-index` and `--index` flags sound so similar, and yet are so different. The documentation could use some examples here, I think.

Note that if you *do* want to use `--keep-index` during the `git stash` step, you will need a `git reset --hard` before your `git stash apply --index && git stash drop` step (aka `git stash pop --index`).

Chris
Akos Vandra-Meyer· Jun 2, 2022, 11:31 UTC · re: Chris Torek · lore

Re: BUG? git stash and immediate git apply results in conflict

Hi Chris,

Thanks for getting back to me on this. My use case is the following:

I made a bunch of (loosely related) changes to my code, but I have been asked to submit them as separate commits, so I need to separate the changes to dependent files relating to the first and second feature.

So I need to separate the changes in my workdir into two commits (let's call them two features). Turns out the first feature needs a dependent file changed in one way, and the second feature improves on it, so it needs it changed in another way - normally this would result in a conflict if they would have been done separately in different branches.

I start off by stashing everything except stuff directly relating to the first feature and stashing everything else. Check for build errors, unstage, and incrementally staging more stuff that need to be added in order to make the first feature build correctly. At some point I reach the file that has to be changed differently, so I will have a set of changes staged for that file, while another set of changes remain unstaged - the changes that will be required by the second feature.

At this point you pointed out that apparantly I'm misusing git, but I am confused on how to correctly stash the unstaged changes to check if the code would build with the staged changes only, and unstage them and add more stuff if not?

Thanks,
  Akos
On Thu, 2 Jun 2022 at 08:32, Chris Torek <chris.torek@gmail.com> wrote:
Show 24 quoted lines
>
> On Wed, Jun 1, 2022 at 2:11 PM Akos Vandra-Meyer <axos88@gmail.com> wrote:
> >  git stash -ku
> >  git stash pop
>
> This is not a bug in `git stash` itself, but rather in the way you're using it.
>
> There are two mistakes on your part here:
>
> 1: You are using `-k`, aka `--keep-index`. This flag is intended for usages
> that are not yours here.
>
> 2. You are *not* using `--index` in your `git pop`.  The `--index` flag is
> intended for the kind of thing you are doing here.
>
> It's a bit unfortunate (and perhaps worth some work in the documentation)
> that the `--keep-index` and `--index` flags sound so similar, and yet are so
> different.  The documentation could use some examples here, I think.
>
> Note that if you *do* want to use `--keep-index` during the `git stash` step,
> you will need a `git reset --hard` before your `git stash apply --index &&
> git stash drop` step (aka `git stash pop --index`).
>
> Chris
Chris Torek· Jun 2, 2022, 12:24 UTC · re: Akos Vandra-Meyer · lore

Re: BUG? git stash and immediate git apply results in conflict

On Thu, Jun 2, 2022 at 4:31 AM Akos Vandra-Meyer <axos88@gmail.com> wrote:
Show 9 quoted lines
> Hi Chris,
>
> Thanks for getting back to me on this.
> My use case is the following:
>
> I made a bunch of (loosely related) changes to my code, but I have
> been asked to submit them as separate commits, so I need to separate
> the changes to dependent files relating to the first and second
> feature.

I snipped the rest of this because I *dislike* and *recommend against* the use of `git stash` in the first place. If you want to use it that way, I am sure it is possible, but I recommend doing something else entirely. The rest of this message is all opinion! You seem to be looking for a recommended method, so I'm recommending mine.

Let's say for concreteness that your current branch is named "topic". Here is what I would do (though I might change strategies and use various short-cuts depending on various sub-scenarios; this is meant for illustrating how to do what you want, in a way that provides the most clarity):

git add (files as needed) git commit -m "final version, to be split up" git branch -m not-final-topic

Then:

# create a new topic branch git checkout -b new-topic <start-point>

# get a view of commits git log HEAD..topic

# copy some commits, up until some point git cherry-pick <some of those commits as desired>

# begin splitting a commit: git cherry-pick -n <hash>

# split it up: use a mix of the following:
git reset / git add -p / git reset -p
                # as needed here to take part of the commit)

# observe what's to be committed git diff --cached git commit # and write a commit message

git add -p ... # as needed again git diff --cached git commit # repeat until satisfied with broken up commit(s)

# make sure final result matches - optional: use the same # hash here as for the `git cherry-pick -n` command git diff HEAD <hash>

# repeat copying whole and partial commits:
git cherry-pick ...     # add more whole commits as desired
git cherry-pick -n ...  # add commit that is to be split up
                        # repeat the split-up process

# When finally done, ensure that new-topic and not-final-topic # have the same contents. If they do, the split-up was successful. git diff not-final-topic HEAD

Note that there is no longer any branch named "topic" at this point: we have instead "not-final-topic" and "new-topic". We can now rename "new-topic" to "topic" and use `git push -f` or whatever else is appropriate for whatever review system(s) you may be using.

The entire process above can often be done with a single `git rebase -i` command, but that obscures the fundamental nature of what you're really doing. The repeated cherry-pick, reset, add -p, and/or reset -p sequence is what you're really doing: you are taking some set of existing commits, which are *close* to the commits you'd like to have, and rebuilding them to make a new set of commits (with different hash IDs) that represent what you really do want.

In the end, you will have Git *find* your commits by name: the name "topic" will name the last commit in a chain of commits. The names here are up to you, and you can change any name at any time using `git branch -m`. Both Git and any review system you use is going to use the *commits*, with their scary looking hash IDs, but like you they will *find* these commits by starting from a branch name: so by changing the hash ID stored in the branch name, you change the set of commits they (and you) find.

What `git stash` does is make several commits that are not on *any* branch. These commits are a little weird, requiring the use of the `git stash` command to access them. Ordinary commits on ordinary branches are not weird and do not need any special access, so that's what I prefer. But the fact that the stash commits are on *no* branch makes it easy to slide them from one branch to another without having to think about hash IDs; that's one reason some people prefer `git stash`. I'm just not one of those people.

Chris

← back to recent threads