Does git revert also use the 3-way-merge?

前端 未结 1 741
长发绾君心
长发绾君心 2021-01-27 03:11

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)

相关标签:
1条回答
  • 2021-01-27 03:49

    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.

    0 讨论(0)
提交回复
热议问题