Search code examples
gitcherry-pickgit-merge-conflict

Cannot cherry-pick commit changing not-existing file


I'm wondering why git doesn't allow cherry-picking a commit that introduces changes to a file that doesn't exist. My understanding is that a commit represents a full snapshot of working directory. So cherry-picking a commit containing file that currently doesn't exist should simply create that file - but I can see that it's not the case.

Below is a couple of commands to reproduce the situation I'm talking about.

$ git init
Initialized empty Git repository in /home/mzakrze/workspace/tmp/.git/
$ echo "Hello world!" > first_file; git add first_file; git commit -m "Init commit"
[master (root-commit) 7f9478a] Init commit
 1 file changed, 1 insertion(+)
 create mode 100644 first_file
$ echo "Fox jumps over the layz dog" > test; git add test; git commit -m "Commit with a typo"
[master 776387b] Commit with a typo
 1 file changed, 1 insertion(+)
 create mode 100644 test
$ echo "Fox jumps over the lazy dog" > test; git add test; git commit -m "Fix typo"
[master 9ea19df] Fix typo
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log 
commit 9ea19dfe2597b28eb576e2682e745de3da74733f
Author: mzakrze <xxxxxx@gmail.com>
Date:   Sun Apr 11 17:01:13 2021 +0200

    Fix typo

commit 776387b563edba9df2a12c5d1a2fd5bffb10c643
Author: mzakrze <xxxxxx@gmail.com>
Date:   Sun Apr 11 17:00:53 2021 +0200

    Commit with a typo

commit 7f9478a83e3938bf57552c1b90ddc7322b1bf315
Author: mzakrze <xxxxxx@gmail.com>
Date:   Sun Apr 11 16:59:04 2021 +0200

    Init commit
$ git reset --hard 7f9478a83e3938bf57552c1b90ddc7322b1bf315
HEAD is now at 7f9478a Init commit
$ git cherry-pick 9ea19 # cherry-pick "Fix typo"
error: could not apply 9ea19df... Fix typo
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

INB4: I'm not looking for a "solution", I just want to understand why git thinks it's a conflict.


Solution

  • Remember that cherry-pick is technically a three-way merge. You run git cherry-pick C, for some commit C—often specified by a hash ID—and Git finds the parent P of commit C:

    • commit P is now the merge base;
    • the ours commit is the current (HEAD) commit; and
    • the theirs commit is commit C.

    The difference between P and HEAD is that the file in question has been deleted entirely. The difference between P and C is that the content of the file has been modified.

    For the standard merge algorithm (git-merge-recursive), this is a high level or tree level conflict: a modify/delete conflict. That's a merge conflict that cannot be resolved with -X ours or -X theirs. The merge therefore stops in the middle, leaving you with a merge conflict that you must resolve.

    Resolve the merge conflict and run git cherry-pick --continue to finish the cherry-pick. The way you resolve this merge conflict is up to you; the git checkout command in larsks answer is fine, or you can use git checkout --theirs test and git add test. Note that when using git checkout --ours (not applicable in this case) and git checkout --theirs, Git does not mark the conflict resolved, but when using git checkout CHERRY_PICK_HEAD (or a raw hash ID), Git does mark the conflict resolved. This is due to a quirk in the implementation of git checkout.