gitgit-merge-conflictgit-am

Resolve conflicts from am session


I wanted to cherry pick multiple commits from one repository to another. I followed the instructions provided from this Stack Overflow post:

/path/to/2 $ git --git-dir=/path/to/1/.git format-patch --stdout sha1^..sha1 | git am -3

And a conflict arised:

Applying: commit-name-xxx
fatal: sha1 information is lacking or useless (path/to/conflicted/file).
error: could not build fake ancestor
Patch failed at 0001 commit-name-xxx
The copy of the patch that failed is found in: .git/rebase-apply/patch
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

I tried to resolve the conflict by running :

 git mergetool --tool=DiffMerge

But the response was:

No files need merging

I also ran git status but the response was:

On branch develop
You are in the middle of an am session.
  (fix conflicts and then run "git am --continue")
  (use "git am --skip" to skip this patch)
  (use "git am --abort" to restore the original branch)

nothing to commit, working directory clean

I'm not sure to understand what is going on here: my first command tell me their is a conflict and that it has been moved to .git/rebase-apply/patch but neither the git mergetoolor the git status is finding this conflict.


Solution

  • TL;DR

    Your Git does not have enough information to turn the patch into a three-way merge. Without an incomplete three-way merge sitting in your index, there is nothing for your git mergetool to bite into. You may have to manually apply the patch.

    (Remember, the index here is the thing also called the staging area and the cache, and it is used to resolve conflicts during three-way merges. In cases that don't involve three-way merges, the index stores only the files you are building up for the next git commit. During a conflicted merge, the index stores even more files.)

    If you git fetch from one repository (the one in 1) into the other (2), you may be able to git cherry-pick the commit in question, as max630 suggested in a comment. That is, while in repository 2, you can either add repository 1 as a remote:

    git remote add <name> <path-to-repo-1>
    

    and then git fetch from it as you would from any other remote, or you can use the old (Git 1.5 style) git fetch <path> syntax to temporarily acquire all reachable objects from repo-1 and then cherry-pick by hash ID.

    If this still does not work (but it will), or is inconvenient for some other reason, you will have to manually apply the patch. Consider using git apply --reject followed by manual cleanup.

    Long

    This error message tells us—well, OK, tells me—what is going on:

    fatal: sha1 information is lacking or useless (path/to/conflicted/file).

    You are using git format-patch and git am to transport one patch1 from one Git repository to another, the same way that people more typically use (or used, in the past) git format-patch to email patches between sites that have no other network connectivity. When Git makes such a patch, it includes within the change-set for the commit itself an index line above each file patch:

    diff --git a/Documentation/RelNotes/2.17.0.txt b/Documentation/RelNotes/2.17.0.txt
    index 7001dbbf8..c828d3734 100644
    

    This index line delivers—at least potentially—the information that Git needs to construct a full three-way merge, if that's possible. Adding --full-index to the format-patch options makes the index line longer:

    index 7001dbbf88b7ea5822eb0b798ac983505c57b3dc..c828d37345224550540a1665aaed2566d5bcb40e 100644
    

    Now the two hashes are significantly beefier; this can help in some cases. But what are they?

    These two hash IDs are the blob hash IDs of the files stored in the repository—the actual content of the "before" and "after" files. The diff hunks that follow this line give instructions: if you change these lines in the original blob (file), using these replacement lines, you will turn the original blob—the content with the left-side hash—into the new blob whose content is named by the right-side hash.

    When you feed this diff to git apply,2 it's possible that the file in HEAD no longer matches, or even resembles all that much in some parts, the "original blob" in the patch. In this case, the context lines won't match up and/or the "before" section will not appear anywhere in the file. A direct application of the patch becomes impossible.

    If you have supplied the --3way or -3 flag to git apply—and git am does so—Git can now use the information in the index line. Since the first hash is the blob hash of the actual file content in the repository that produced the change-set, your own Git can look in your own repository to see if you have a blob with that hash ID. If so, you have the original file already.3 Git can just extract that file and patch it in place, to produce the "after patch" version.

    Git now has all three versions of the file: the base version, obtained through the "before" hash ID and, fortuitously, found in your repository; the "theirs" version, obtained by applying the patch to the base version; and the "ours" version, which is the file in the current or HEAD commit. So Git can now stuff all three versions into your index, and now a three-way merge is possible.

    On the other hand, it's possible that the blob hash ID in the index line matches no object in your repository. In that case, you don't have the "before" version of the file. It's impossible to do a three way merge. Or, it's possible, however unlikely,4 that you have a shortened blob hash that matches more than one blob in your repository. In this case, you might have the "before" version of the file, but Git doesn't know for sure and will not attempt to identify whether any of those blobs are the correct one.

    In any case, because your Git does not have enough information to attempt a three-way merge, it doesn't bother trying, leaving you in this situation. Using git fetch and git cherry-pick you can get a true three-way merge after all. The histories need not even be related, as cherry-pick forces the merge base to be the parent of the commit being picked.


    1This also works for a set of patches, but the format-patch directives show that it's just one patch.

    2Note that git am is essentially just a wrapper that runs git apply on each patch, followed by git commit of the result.

    3Remember, Git is operating on the assumption that because you are feeding a patch to git am, you don't have a copy of the other repository. Someone else has emailed you a patch. Only they have that repository; you have only your repository. This is not true here—you have both repositories—but Git doesn't know that!

    4The chance depends on the number of blob objects in your repository, and the length of the shortened hash. Git now has code to automatically choose an appropriate abbreviated hash length, but this works based on the number of objects in the repository in which the diff is being generated, not on the number of objects in the receiving repository. If the receiving repository is significantly larger, the sender might not offer a long enough hash. Older versions of Git don't have this automatic computation either, and by default just use 28 bits of hash unconditionally; that may be too short.