Search code examples
gitgit-merge-conflict

Force checkout of the file after conflict from stash


I did git stash save then git pull git stash pop and now I have conflicts and I want to reset one file to be the same as from pull to remove changes from stash (I have other changes in other files that I wan to keep).

I've tried:

git checkout -f inst/app/server.R

but got warning:

warning: path 'inst/app/server.R' is unmerged

and the file is unchanged, I still have conflict markers inside. How can I checkout that file from last commit?


Solution

  • TL;DR: It sounds like you want git checkout --ours inst/app/server.R.

    You are in the middle of a conflicted merge, so there are two "last commit"s. One is your HEAD (current) commit. Note that your current commit is the one you were on when you ran your command, unless your command is git rebase, in which case things get swapped around: see What is the precise meaning of "ours" and "theirs" in git? Since you say the last command, the one that failed with a merge conflict, was git stash apply,1 your current commit is the one you made after your git pull ran a successful git merge for you,2 and the other commit is one of the several commits in your current git stash stash-bag under the name stash@{0} (aka just stash).

    (There is a third commit involved in any conflicted merge, which is the merge base. It's probably less relevant for stashes, which are a bit weird, but it is worth mentioning. Note that if you set merge.conflictStyle to diff3 in your Git configuration, you will get the merge-base version with an extra set of conflict markers, |||||||, between the <<<<<<< and >>>>>>> sections showing you the "ours" and "theirs" versions. I like to keep diff3 set, as it shows me what was there before I-and-they both changed the file in conflicting ways.)

    When you run git checkout without either naming a specific commit, or using the --ours or --theirs option, Git tries to get the version from the index. (Remember, the index—also called the staging area and sometimes the cache—is where you are building the next commit you will make.) But a conflicted merge leaves three versions of each conflicted file in the index. Git doesn't know which one to get, so it fails with that error. Using --ours tells git checkout to copy the "ours" (i.e., HEAD) version of the file from the index to the work-tree; using --theirs tells git checkout to copy the "other" (i.e., MERGE_HEAD) version of the file.3

    When you do extract one of these version from the index, that leaves the other two versions in the index, i.e., leaves the file in unresolved-merge state. Inspect the contents carefully to be sure you have the correct version, then git add the file to resolve the conflict. This will wipe out the three separate index versions, putting the work-tree version into the "resolved" index slot (slot zero).

    If you need to re-create the merge conflict, you can use git checkout -m -- path (the -- is only required if the path resembles an option, but it is a good habit to get into). This works up until you run git commit to finish the failing merge.

    Once everything is resolved the way you want, use git commit to finish the merge.


    1You said git stash pop, but git stash pop is just a shortcut meaning "Run git stash apply and then, if and only if that succeeds, run git stash drop." Since the apply step failed, you will have to run git stash drop manually, once you are satisfied that the stash has applied cleanly and been committed.

    2I recommend avoiding git pull. All it does is run two other Git commands for you: git fetch, then either git merge or git rebase depending on what you tell Git in advance. It's better to run the two commands separately, so that:

    • you know what is failing, when one fails; and
    • you can choose merge or rebase after inspecting what git fetch fetched.

    There's nothing fundamentally wrong with using git pull, just remember that it's really git fetch && git something, and you have to decide the "something" part ahead of time. If you don't pick one, Git will pick git merge, which is probably the wrong default, but is what Git first did, way back in 2005, and therefore cannot be changed. :-)

    3There is no --base option, but you can use gitrevisions syntax to access it from the index: git checkout -- :1:path. You can use this same syntax to get at the --ours version, which is index entry #2, and the --theirs version, which is #3, but it's easier to remember --ours and --theirs anyway.

    The "ours" version is sometimes called the local version, and the "theirs" version is sometimes called the remote version, but I hate these names. "Remote" in particular is much too easy to confuse with remotes like origin and remote-tracking branch names like origin/master.