Explanation:
I make a heavy use of Git staging area to keep track of the changes that I'm already sure of while the working directory is often a mess of untested solutions, TODOs and a code that is generally very WIP. Loosing the distinction between the index and the working directory is a significant setback because I have to reevaluate all my changes (where often half of a line should be staged and half is a TODO comment).
Now, there is a recurring situation when I realize that for the my current changes require something else to work first.
I'm a big fan of staging so what I do in that case is to git stash push and after the other change is committed and the working directory clean again git stash pop --index.
However, it is common that there are some conflicts between my stash entry and the new HEAD (usually very minor ones which is doubly annoying).
This locks off the option --index and forces me to drop my cache and manually rebuild it from scratch after resolving the conflicts.
Is there a way to keep/restore the index after the conflicts are resolved? It doesn't matter to me if the conflicts will also be resolved in the staging area or these files remain exactly as they were in the stash.
I would be most happy with a way to just pop the stash without index, resolve the conflicts and slap the old index back on it but if I have to resolve conflicts 2 times (separately for the index), this is also fine.
TL;DR:
I need a way to keep the index when popping stash that conflicts with the current HEAD.
Example:
Here is a simple shell script that creates a new repository and reproduces this situation:
mkdir example && cd example || exit
git init
printf 'first line\nlast line\n' >foo
git add foo
git commit -m 'initial commit'
sed -i '2i a good line that should be staged' foo
git add foo
sed -i '3i a WIP line that should NOT be staged' foo
git stash push -m 'the stash with index'
sed -i '2i some conflicting change' foo
git commit -a -m 'a new HEAD conflicting with stash'
git stash pop --index # this doesn't work




It doesn't look like there is a out-of-the-box way to resolve conflicts and keep the index at the same time. During conflict resolution, Git uses the staging area for its own purposes, which effectively erases the data there.
However, a stash entry is just a few commits in the repository. The command
git stashis provided for our convenience to manage those commits but we don't have to use it. You can instead pop the stash manually in a way that preserves the index.The key is to merge the stash into the current
HEADin 2 steps: first only the index and later the rest. (You can use multiple commits to keep track of which files are from where and make sure that solving conflicts won't remove any information.)First, you need to convert the commit structure of the stash entry into something sane. The normal stash entry comprises of 2 or 3 commits woven into a bizarre web of merges. This is not only pointlessly complicated but also hard to work with. Instead, you could have just two linear commits: first with indexed changes and the second with the non-index ones.
First we move
HEADto the commit that stores the indexed files from the 1st stash entry.stash@{N}is the top commit of the stash entry numberNandstash@{N}^2is its second parent. (Stash entry always has at least 2 parent commits: the base commit at which the entry was created and a commit storing stashed index.) You can use the option--detachbecause these commits will be temporary and there is no use for a branch.For the second commit, you should convert the tip of the stash entry from a merge commit into a normal commit, using
git merge --squash. The code below additionally checks if that merge has a 3rd parent that stores untracked files. If this is the case, they are also added.At this point the Git repository should look like follows:
Ais the initial commit where the changes were pushed to stash andBis the new commit where you want to apply the changes. (Btw, the original stash entry is not drawn here but it still exists. It isn't lost it or anything.)The second step is to just rebase the simplified stash entry onto the branch where you want to apply it.
It's just one command:
At this stage you will have to resolve the conflicts that blocked you from applying the stash before.
After it's all finished, the repository should look like this:
The third step is to remove the commits, without losing any changes or the contents of the index.
It is as simple as:
The forth and the last step is just some cleanup.
Currently you are in a detached
HEADstate and you most likely started the whole operation at a top of some branch like a sane Git user. You need to switch back to your branch:You can also remove the stash entry if it's no longer needed:
An entire script
Popping stash in this way takes a lot of commands and is quite error-prone. A much better idea is to create a script that can do it automatically and also provides some rudimentary idiot-proofing.
This script works similarly to the
git rebasein the sense that it will exit on conflicts and it needs to be restarted with a flag--continueafter they are fixed.For the initial run, you can pass an optional argument that specifies the stash entry to pop.