Search code examples
gitgit-mergegit-stash

git stash unresolved merge conflicts


I was resolving merge conflicts and needed to get back to the old codebase to figure out how it worked before. I intuitively stashed my working copy, checked what I wanted to and applied the stash back. Now I'm in a lot of trouble because my code has been saved but I no longer see |MERGING next to the branch name in the terminal and I have lost all the commits form the other branch. For git it doesn't look like a merge commit any longer, but simply a new code change.

What have I done wrong and how can I avoid the same issue in the future?

NOTE: Committing it as is is not an option, because we are working on 2 different repos and we need to merge one into another from time to time. If we change the checksum during one of our merges, we will face a massive amount of conflicts, because on the next merge git will not recognise the previously merged code. Merging the same repo over and over again looks like merging the same branch several times. Checksums should stay the same.


Solution

  • Something does not quite add up, because git stash works by making commits. You said:

    I was resolving merge conflicts ... I intuitively stashed my working copy

    If you're in the middle of a conflicted merge, you cannot make commits, and therefore you cannot use git stash:

    ... [set up some conflicts; get onto branch b2, with b1 ready to conflict]
    
    $ git add README && git commit -m 'b2-readme'
    [b2 c548973] b2-readme
     1 file changed, 1 insertion(+)
    $ git merge b1
    Auto-merging README
    CONFLICT (content): Merge conflict in README
    Automatic merge failed; fix conflicts and then commit the result.
    $ git status
    On branch b2
    You have unmerged paths.
      (fix conflicts and run "git commit")
    
    Changes to be committed:
    
        new file:   file1
    
    Unmerged paths:
      (use "git add <file>..." to mark resolution)
    
        both modified:   README
    $ git stash
    README: needs merge
    README: needs merge
    README: unmerged (af8a7b1af61c4d6a71415c41d0f19a418d5a1e3c)
    README: unmerged (1eafc235a9f2cb59959d554ba9b9e11877c88349)
    README: unmerged (7616840fd1336b8bcae3f33e2c6c69890e563121)
    fatal: git-write-tree: error building trees
    Cannot save the current index state
    

    To make a stash actually happen, you must resolve the conflicts. Git doesn't care how you resolve them, of course:

    $ cat README 
    We put a dummy README file into the base,
    to get an initial commit.
    <<<<<<< HEAD
    conflicting b2 change
    ||||||| merged common ancestors
    =======
    conflicting change in b1
    >>>>>>> b1
    $ git add README    # Yuck! "resolve" conflicts by leaving markers
    $ git status --short
    M  README
    A  file1
    $ git stash
    Saved working directory and index state WIP on b2: c548973 b2-readme
    HEAD is now at c548973 b2-readme
    

    If this is what you did—perhaps while doing something sensible with the conflicts, rather than my silly example here—and then you did this:

    $ git stash pop   # NB: better to "git stash apply" really
    On branch b2
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)
    
        new file:   file1
    
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
        modified:   README
    
    Dropped refs/stash@{0} (8a947fcd34b3259fbb2fbf667bc0b99cc139a88c)
    

    you're sort-of in trouble, but not too much. The problem is that you now have, in your index and work-tree, the state you had when you did the (successful, this time) git stash, but you've lost the merging state files (.git/MERGE_HEAD and .git/MERGE_MSG).

    NOTE: Committing it as is is not an option, because we are working on 2 different repos and we need to merge one into another from time to time.

    Committing, like failure, is always an option! In this case, for instance, the trick is to commit the result now, and go back and restart the merge.

    Because of the confusing nature of branches in Git, it's possible to commit this right now on b2 and then make b2 (the name) no longer contain the commit. But it's just as easy, or even easier, and definitely less confusing, to create a new branch name first. Then you can make the commit there, not on b2.

    $ git checkout -b save-merge-result
    M   README
    A   file1
    Switched to a new branch 'save-merge-result'
    
    # NOTE: use "git status" (not shown here) to see what you
    # still need to "git add" (which is `README` in my case)
    
    $ git add README
    $ git commit -m 'save merge result on temp branch'
    [save-merge-result f8b2251] save merge result on temp branch
     Date: Tue Oct 18 05:58:14 2016 -0700
     2 files changed, 20 insertions(+)
     create mode 100644 file1
    

    Now you can go back to b2 and redo the git merge, which will of course fail with conflicts again:1

    $ git checkout b1
    Switched to branch 'b1'
    $ git merge b2
    Auto-merging README
    CONFLICT (content): Merge conflict in README
    Automatic merge failed; fix conflicts and then commit the result.
    

    Now you can extract results from the temporary merge. For instance:

    $ git checkout save-merge-result -- .
    $ git status
    On branch b1
    All conflicts fixed but you are still merging.
      (use "git commit" to conclude merge)
    
    Changes to be committed:
    
        modified:   README
        new file:   file2
    

    At this point you can modify and git add files more as needed/desired.

    Note that if you have not already done a git pop on the stash you made, you can use git stash branch to convert the stash to a branch of its own, all in one step. Since git stash save simply makes commits, you never need to use git stash: just make a branch and commit there. All that git stash does is give you commits that are remembered by the name stash, instead of by a branch name.

    (Incidentally, don't name a branch stash and start using git stash. Git won't have any trouble with this, but you will confuse the heck out of yourself. :-) )


    1Assuming you have not enabled git rerere.