Fernando Gonzalez Sanchez Fernando Gonzalez Sanchez - 11 days ago 5
Git Question

Git apply 3way error "repository lacks the necessary blob to fall back on 3-way merge."

I have a Git diff created with

git diff --full-index --ignore-submodules > mypatch.diff


And when trying to apply it to another git repo (same remote branch, more advanced revision) I get the error in the title

git apply --3way mypatch.diff
...
error: patch failed: dir/file:189
error: repository lacks the necessary blob to fall back on 3-way merge.
error: dir/file: patch does not apply


This happens for several files.

The docs at https://git-scm.com/docs/git-apply state


When the patch does not apply cleanly, fall back on 3-way merge if the patch records the identity of blobs it is supposed to apply to, and we have those blobs available locally, possibly leaving the conflict markers in the files in the working tree for the user to resolve.


The interesting thing in my case is that the files (blobs in Git terminology) are in the destination repository (not even renamed), so they must be found by "git apply".

NOTE: The sequence of commands has worked several times in the past.

Answer

You probably need to run git fetch (and you may need to tweak your fetch refspecs, and/or use --unshallow).

What's happening

The blob(s) that Git needs are specific versions of the files (pathnames are irrelevant at this point).

For instance, consider this bit of patch:

diff --git a/fmt.py b/fmt.py
index 2069319..0c8a68f 100755
--- a/fmt.py
+++ b/fmt.py
@@ -207,7 +207,8 @@ def main():

Let's take a look at commit 7124b135...'s version of fmt.py, and then its parent's version:

$ git rev-parse 7124b135:fmt.py
0c8a68f9dc05a7399d06693c0b2761fb43ed0b58
$ git rev-parse 7124b135^:fmt.py
2069319dae4bd87cb6e1b7c18d86ebededeb00c8

Now look back at that index ... line:

index 2069319..0c8a68f 100755

There are those blob IDs. Git is comparing blob 2069319... against blob 0c8a68f..., which are the versions in the parent of the commit being shown, and the commit itself.

If I were to send this patch to you, and you asked your Git to apply it and it did not apply cleanly, Git's "fall back to 3-way merge" requires that it find or construct three versions of the file: your current one, the one identified by 2069319, and my version.

Clearly your Git has your current version, so there's no problem there. Clearly it does not have my version, so it must construct it. The way it can construct it is to find version 2069319.

The complaint you are seeing is because your Git cannot find, in your repository, anything identified by the SHA-1 ID on the left of the .. in the index <sha1>..<sha1> <mode> line.

(If your Git could find that blob, it would extract it to a temporary file, apply the patch to that temporary file, and hence have my version; and then it would have the three versions it needs to do a 3-way merge.)

Where will Git get that blob?

If the patch is against a commit that is currently in a repository that you can git fetch from, you should be able to run git fetch and obtain that commit, which will obtain all the trees and blobs associated with that commit.

This git fetch needs to pick up that commit. The commits that git fetch normally picks up are all the ones in refs/heads/* on the remote, as controlled by the fetch = line(s) associated with that particular remote, that you don't already have.1

It's possible that the commit(s) in the remote that retain this blob there are not under refs/heads/* (e.g., if it's only in a stash now, or attached to an outdated tag or a branch that was rebased). In this case, git fetch probably won't bring over the necessary blob. There is a way to get it if you can log in on the remote: just make a branch or tag that points to a commit that leads to the blob in question. (Finding such a commit can be hard, unless your patch has the commit ID right in it, then it's easy.) Then direct your Git to fetch that branch or tag from the other Git, and you'll get the necessary blob.


1Note that if you've made a shallow clone, Git won't proceed past your existing commits. Your commits end at some cut-off point, which is what makes your clone shallow. If the missing blob is on an ordinary branch after all, but is "below" a cutoff, you will need to deepen your repository, either with a deeper --depth= argument, or by adding --unshallow to your fetch command, so as to deepen it "all the way".

Computing the correct depth is impossible on the shallow-clone side. (The count is: "How many commits down do I have to dig from a branch tip, to get to the target commit?" What you have in your repository stops before we get that far, so we don't know how far it is. We could count how far down all of your branches go, and pick a number bigger than that, which would definitely deepen your repository—but we don't know if that's the right number.)

It is possible on the other repository, but if you have direct access to that repository, you don't have to count, you can just make a branch or tag name pointing to the commit.

What this means in the end that the easiest thing, by far, is to just use git fetch --unshallow.

Comments