When I run git revert
, it can happen, that a conflict occurs. Does git rely on the 3-way-merge, as it is depicted in the question merge internals (cf. table below) also for revert
?
What is the merge base for a revert? In What are the three files in a 3-way merge for interactive rebasing using git and meld? it is quite clear, but its hard to imagine this for a revert.
A - B - C - D - C^-1
(If I want to revert C
at the end.)
Yes, there is a base. (Side note: this code has changed a lot since I looked at it years ago. I picked up some of this for my recent cherry-pick answer, which you have linked here.)
Both git cherry-pick
and git revert
are implemented by the same source files (builtin/revert.c
and sequencer.c
).
As you say, the tricky part is deciding what to fake up for the merge base. In your example, we're undoing the B
-to-C
diffs. Here's the actual source code (in sequencer.c
), stripped down somewhat:
if (opts->action == REPLAY_REVERT) {
base = commit;
base_label = msg.label;
next = parent;
next_label = msg.parent_label;
strbuf_addstr(&msgbuf, "Revert \"");
strbuf_addstr(&msgbuf, msg.subject);
strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
strbuf_addstr(&msgbuf, oid_to_hex(&commit->object.oid));
if (commit->parents && commit->parents->next) {
strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
strbuf_addstr(&msgbuf, oid_to_hex(&parent->object.oid));
}
strbuf_addstr(&msgbuf, ".\n");
} else {
[this is the cherry-pick case, included just for completeness]
const char *p;
base = parent;
base_label = msg.parent_label;
next = commit;
next_label = msg.label;
When we enter here, commit
points to data for C
and parent
points to data for B
. The assignment to variable base
is what sets the merge base, and next
-vs-base
is what to bring in. For cherry-pick, the commit's parent (possibly chosen via -m
) is the merge base. For revert, the commit itself is the merge base and the parent (again possibly from -m
) is what-to-bring-in.
The other way to get the same effect (which is how this was done many years ago, and until recently, I thought this was still being used) is to reverse-apply a commit as produced by git format-patch
. In this case, the constructed base version is the second hash (the B
part from the A..B
part of a textual diff):
/*
* This represents a "patch" to a file, both metainfo changes
* such as creation/deletion, filemode and content changes represented
* as a series of fragments.
*/
struct patch {
[snip]
char old_sha1_prefix[41];
char new_sha1_prefix[41];
static void reverse_patches(struct patch *p)
{
[snip]
swap(p->old_sha1_prefix, p->new_sha1_prefix);
The reverse_patches
function is called after extracting the text into a series of patches, i.e., after the code that extracts the hashes from the index
lines, putting the A
and B
parts into the old and new prefix fields. Then (after reverse_patches
), when actually applying each patch, git uses the saved old and new sha1 values to fake a 3-way merge (if git am
is given --3way
). So by reverse-applying a text patch, we would get the new file as the base and the original as the target, just as with the sequencer.c
code.