Nikita LeshenkoAug 25, 2026, 08:55 UTC on loreMake "git am --3way" succeed in an edge case where it currently fails.
First, some background about the case:
Say we have a patch with two commits, A and B, and both of them change the same file. We apply them to a different repo on a different version of the file using --3way.
If both patches apply cleanly, we are done.
If A does not apply cleanly, git am falls back to 3-way merge. To merge, Git uses the preimage hash from the patch:
A: index 83b2a16..cccad2b 100644
B: index cccad2b..0ce2f98 100644git am looks up 83b2a16, applies A to it, and merges that result with the current version of the file. As an important side effect, applying A to 83b2a16 also stores A's postimage, cccad2b, in the repository. This means that if B also doesn't apply cleanly, cccad2b (which is now B's preimage) exists in the repository so we can merge against it as well.
Let's assume instead that A applies cleanly and B fails. Because git am didn't have to 3-way merge A, nothing created cccad2b this time. What we have after applying A is our file plus A's change, which is a different hash. When B doesn't apply cleanly, git am fails because it doesn't know what cccad2b is:
Applying: A
Applying: B
error: sha1 information is lacking or useless (file).
error: could not build fake ancestorHowever, technically we have the information to build the fake ancestor! We have 83b2a16 in the repository, and we have A, so if we apply A we'll get that hash.
So do exactly that: if the user requested --3way, apply the patch on the fake ancestor even after a patch applies cleanly, in order to produce intermediate hashes for later commits. If the preimage is missing, or the patch does not apply, nothing is recorded and git am behaves as it does today.
This does not change the behavior of how patches apply, but when the user requested --3way it does cost one extra "git apply --build-fake-ancestor" process and one extra apply per clean patch.
Signed-off-by: Nikita Leshenko <nikita@island.io>
---
builtin/am.c | 58 ++++++++++++++++++++++++++++++++++++++++++++-------
t/t4150-am.sh | 28 +++++++++++++++++++++++++
2 files changed, 79 insertions(+), 7 deletions(-)
Show changes to 2 files +79 −7
builtin/am.c, t/t4150-am.sh
diff --git a/builtin/am.c b/builtin/am.c
index e9623b8307..37569fed65 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1488,7 +1488,8 @@ static int parse_mail_rebase(struct am_state *state, const char *mail)
* Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If
* `index_file` is not NULL, the patch will be applied to that index.
*/
-static int run_apply(const struct am_state *state, const char *index_file)
+static int run_apply(const struct am_state *state, const char *index_file,
+ int quiet)
{
struct strvec apply_paths = STRVEC_INIT;
struct strvec apply_opts = STRVEC_INIT;
@@ -1528,7 +1529,7 @@ static int run_apply(const struct am_state *state, const char *index_file)
* If we are allowed to fall back on 3-way merge, don't give false
* errors during the initial attempt.
*/
- if (state->threeway && !index_file)
+ if (quiet || (state->threeway && !index_file))
apply_state.apply_verbosity = verbosity_silent;
if (check_apply_state(&apply_state, force_apply))
@@ -1559,11 +1560,13 @@ static int run_apply(const struct am_state *state, const char *index_file)
/**
* Builds an index that contains just the blobs needed for a 3way merge.
*/
-static int build_fake_ancestor(const struct am_state *state, const char *index_file)
+static int build_fake_ancestor(const struct am_state *state,
+ const char *index_file, int quiet)
{
struct child_process cp = CHILD_PROCESS_INIT;
cp.git_cmd = 1;
+ cp.no_stderr = quiet;
strvec_push(&cp.args, "apply");
strvec_pushv(&cp.args, state->git_apply_opts.v);
strvec_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
@@ -1589,7 +1592,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
if (repo_get_oid(the_repository, "HEAD", &our_tree) < 0)
oidcpy(&our_tree, the_hash_algo->empty_tree);
- if (build_fake_ancestor(state, index_path))
+ if (build_fake_ancestor(state, index_path, 0))
return error("could not build fake ancestor");
discard_index(the_repository->index);
@@ -1617,7 +1620,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
release_revisions(&rev_info);
}
- if (run_apply(state, index_path))
+ if (run_apply(state, index_path, 0))
return error(_("Did you hand edit your patch?\n"
"It does not apply to blobs recorded in its index."));
@@ -1658,6 +1661,39 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
return 0;
}
+/**
+ * Applies the patch on the fake ancestor and stores the postimage in the
+ * repository for future patches to reference. Best effort, fails quietly.
+ *
+ * Motivation: Say a patch file has two commits, A and B, that both change the
+ * same file. We apply it with --3way. If A applies cleanly, nothing stores
+ * A's postimage. If B then does not apply cleanly, git am cannot 3-way merge
+ * it, because B's preimage is A's postimage.
+ */
+static void try_record_patch_postimage(const struct am_state *state)
+{
+ struct strbuf index_path = STRBUF_INIT;
+
+ strbuf_addstr(&index_path, am_path(state, "patch-postimage-index"));
+
+ if (build_fake_ancestor(state, index_path.buf, 1))
+ goto done;
+
+ /*
+ * Discard index because run_apply() reads `index_path` only if no index
+ * is in core.
+ */
+ discard_index(the_repository->index);
+ run_apply(state, index_path.buf, 1);
+
+ discard_index(the_repository->index);
+ repo_read_index(the_repository);
+
+done:
+ unlink(index_path.buf);
+ strbuf_release(&index_path);
+}
+
/**
* Commits the current index with state->msg as the commit message and
* state->author_name, state->author_email and state->author_date as the author
@@ -1886,9 +1922,17 @@ static void am_run(struct am_state *state, int resume)
say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
- apply_status = run_apply(state, NULL);
+ apply_status = run_apply(state, NULL, 0);
- if (apply_status && state->threeway) {
+ if (!apply_status && state->threeway && state->cur < state->last) {
+ /*
+ * The patch applied cleanly, so no 3-way was performed.
+ * A patch later in the series may reference postimage
+ * hashes this patch would have produced, so record them
+ * while we still can.
+ */
+ try_record_patch_postimage(state);
+ } else if (apply_status && state->threeway) {
struct strbuf sb = STRBUF_INIT;
strbuf_addstr(&sb, am_path(state, "patch-merge-index"));
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index ee96223668..e5ed666c2f 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -641,6 +641,34 @@ test_expect_success 'am with config am.threeWay overridden by --no-3way' '
test_path_is_dir .git/rebase-apply
'
+test_expect_success 'am -3 records blobs a later patch needs' '
+ test_when_finished "rm -rf 3way-source 3way-target" &&
+
+ # Two patches touching the same file, the first of which applies
+ # cleanly to the target while the second one does not.
+ git init 3way-source &&
+ test_write_lines 1 2 3 4 5 6 7 8 9 >3way-source/file &&
+ git -C 3way-source add file &&
+ git -C 3way-source commit -m base &&
+ test_write_lines 11 2 3 4 5 6 7 8 9 >3way-source/file &&
+ git -C 3way-source commit -am first &&
+ test_write_lines 11 2 3 4 5 66 7 8 9 >3way-source/file &&
+ git -C 3way-source commit -am second &&
+ git -C 3way-source format-patch --stdout -2 >3way-two.patches &&
+
+ git init 3way-target &&
+ test_write_lines 1 2 3 4 5 6 7 8 9 >3way-target/file &&
+ git -C 3way-target add file &&
+ git -C 3way-target commit -m base &&
+ test_write_lines 1 2 3 4 5 6 7 8 9XXX >3way-target/file &&
+ git -C 3way-target commit -am "change outside the first patch" &&
+
+ git -C 3way-target am -3 ../3way-two.patches &&
+ test_path_is_missing 3way-target/.git/rebase-apply &&
+ test_write_lines 11 2 3 4 5 66 7 8 9XXX >expect &&
+ test_cmp expect 3way-target/file
+'
+
test_expect_success 'am can rename a file' '
test_grep "^rename from" rename.patch &&
rm -fr .git/rebase-apply &&
--
2.55.0
Re: [PATCH] am: record blobs of cleanly applied patches when using --3way
Nikita Leshenko <nikita@island.io> writes:
Show 46 quoted lines
> Make "git am --3way" succeed in an edge case where it currently fails.
>
> First, some background about the case:
>
> Say we have a patch with two commits, A and B, and both of them change the
> same file. We apply them to a different repo on a different version of the
> file using --3way.
>
> If both patches apply cleanly, we are done.
>
> If A does not apply cleanly, git am falls back to 3-way merge. To merge,
> Git uses the preimage hash from the patch:
>
> A: index 83b2a16..cccad2b 100644
> B: index cccad2b..0ce2f98 100644
>
> git am looks up 83b2a16, applies A to it, and merges that result with the
> current version of the file. As an important side effect, applying A to
> 83b2a16 also stores A's postimage, cccad2b, in the repository. This means
> that if B also doesn't apply cleanly, cccad2b (which is now B's preimage)
> exists in the repository so we can merge against it as well.
>
> Let's assume instead that A applies cleanly and B fails. Because git am
> didn't have to 3-way merge A, nothing created cccad2b this time. What we
> have after applying A is our file plus A's change, which is a different
> hash. When B doesn't apply cleanly, git am fails because it doesn't know
> what cccad2b is:
>
> Applying: A
> Applying: B
> error: sha1 information is lacking or useless (file).
> error: could not build fake ancestor
>
> However, technically we have the information to build the fake ancestor! We
> have 83b2a16 in the repository, and we have A, so if we apply A we'll get
> that hash.
>
> So do exactly that: if the user requested --3way, apply the patch on the
> fake ancestor even after a patch applies cleanly, in order to produce
> intermediate hashes for later commits. If the preimage is missing, or the
> patch does not apply, nothing is recorded and git am behaves as it does
> today.
>
> This does not change the behavior of how patches apply, but when the user
> requested --3way it does cost one extra "git apply --build-fake-ancestor"
> process and one extra apply per clean patch.
If you have a 50-patch series that cleanly applies, we would incur overhead to spawn 49 extra "git apply --build-fake-ancestor" subprocesses, to write and unlink 49 temporary index files, and to perform 49 in-core patch applications, generating unneeded loose objects in the object database, and loading and unloading the index file one extra time per step. That is simply unacceptable.
Can't you do the equivalent lazily inside fall_back_threeway() instead? A rough outline may go like so:
* Imagine that, after applying patches 1..(N-1) successfully, you
are applying patch N.
- First try direct application of the patch, and it fails.
- You call fall_back_threeway().
- build_fake_ancestor() is called for patch N; if the preimage
blob exists, you are done, but the case you want to address is
what to do when the preimage is missing. And in that case (and
in that case only), can't you reconstruct the image chain
lazily? Instead of returning error("could not build fake ancestor"): - You inspect patches in .git/rebase-apply/ for 1..(N-1)
patches (i.e., those you have applied already) to find the
relevant blob objects involved in reconstructing the
preimage blob necessary to apply patch N. Some of the
blobs may already exist in the object database (83b2a16
in your example). - Apply these previous patches in-core to arrive at the
preimage recorded in these earlier patches (applying patch 1
to 83b2a16 would now give you cccad2b), until you see the
preimage blob recorded in patch N. Write out that blob
object (and not the blobs that the chain may have
produced as a result of intermediate patches). - If the lazy reconstruction yielded the necessary blobs, try the
build_fake_ancestor() call again, which should succeed. If
not, you can return error("could not build fake ancestor"). - And after patch N succeeds with 3-way fallback this way, you
would also have the postimage blob recorded in the patch in
your object database, which may help when you apply patch
(N+1).When the patches cleanly apply, or if 3-way finds necessary blobs already, there is no additional overhead with the above approach.
Hmm?
Re: [PATCH] am: record blobs of cleanly applied patches when using --3way
On Tue, Aug 25, 2026 at 8:14 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 13 quoted lines
>
> Nikita Leshenko <nikita@island.io> writes:
>
> > This does not change the behavior of how patches apply, but when the user
> > requested --3way it does cost one extra "git apply --build-fake-ancestor"
> > process and one extra apply per clean patch.
>
> If you have a 50-patch series that cleanly applies, we would incur
> overhead to spawn 49 extra "git apply --build-fake-ancestor"
> subprocesses, to write and unlink 49 temporary index files, and to
> perform 49 in-core patch applications, generating unneeded loose
> objects in the object database, and loading and unloading the index
> file one extra time per step. That is simply unacceptable.
Show 46 quoted lines
>
> Can't you do the equivalent lazily inside fall_back_threeway()
> instead? A rough outline may go like so:
>
> * Imagine that, after applying patches 1..(N-1) successfully, you
> are applying patch N.
>
> - First try direct application of the patch, and it fails.
>
> - You call fall_back_threeway().
>
> - build_fake_ancestor() is called for patch N; if the preimage
> blob exists, you are done, but the case you want to address is
> what to do when the preimage is missing. And in that case (and
> in that case only), can't you reconstruct the image chain
> lazily?
>
> Instead of returning error("could not build fake ancestor"):
>
> - You inspect patches in .git/rebase-apply/ for 1..(N-1)
> patches (i.e., those you have applied already) to find the
> relevant blob objects involved in reconstructing the
> preimage blob necessary to apply patch N. Some of the
> blobs may already exist in the object database (83b2a16
> in your example).
>
> - Apply these previous patches in-core to arrive at the
> preimage recorded in these earlier patches (applying patch 1
> to 83b2a16 would now give you cccad2b), until you see the
> preimage blob recorded in patch N. Write out that blob
> object (and not the blobs that the chain may have
> produced as a result of intermediate patches).
>
> - If the lazy reconstruction yielded the necessary blobs, try the
> build_fake_ancestor() call again, which should succeed. If
> not, you can return error("could not build fake ancestor").
>
> - And after patch N succeeds with 3-way fallback this way, you
> would also have the postimage blob recorded in the patch in
> your object database, which may help when you apply patch
> (N+1).
>
> When the patches cleanly apply, or if 3-way finds necessary blobs
> already, there is no additional overhead with the above approach.
>
> Hmm?I'm concerned about the complexity of creating such lazy reconstruction logic, especially for cases where multiple blobs from the patch are missing, and their preimages were modified in different previous commits (which could in turn have multiple blobs missing from other commits). You mentioned writing only the strictly necessary blobs to the database so IIUC I'll need pretty elaborate scanning logic to surgically perform the minimal number of applies given a list of missing hashes.
This can be done, but IMO such complexity isn't warranted for this relatively niche problem. (I haven't seen online discussion about this exact flavor of the issue, even though I encounter it from time to time.)
How about this:
- If build_fake_ancestor() fails due to useless sha1 information, try to
apply ALL 1..(N-1) patches on their fake ancestors. This will build a
lot of unrelated blobs but will build the missing blob. This will allow
us to build fake ancestor and apply N on it. - Record in am_state .git/rebase-apply called "postimage-attempted" (WIP
name) that we tried to apply on fake ancestors all the way to N. So if
patch M > N later fails due to missing sha1, we apply ALL (N+1)..(M-1)
patches on their fake ancestors. - Optional optimization: if patch N was applied on its fake ancestor and
"postimage-attempted" is N-1, bump it to N.This is less optimized than you suggested but it doesn't hurt the clean path, and this logic kicks in only when patch application would have otherwise failed.