Search code examples
gitmerge

git lost commit after revert a commit and cherrypick it again


I have two branches A/B:

  1. Make a commit named a in branch A
  2. Check out branch B, and merge A
  3. Revert commit a (until now, B doesn't have commit a but A does)
  4. Check out A, revert commit a, but re-commit a with git cherry-pick a (now A has commit a')
  5. Check out B, merge A to B again (the commit a or a' is lost)

I think finally, branch B will have the commit a or a' , but it lost. What's going on here?

I have checked the git docs, but it just mentions three way merge without any detail.

From https://git-scm.com/docs/git-merge#_description:

Then "git merge topic" will replay the changes made on the topic branch since it diverged from master (i.e., E) until its current commit (C) on top of master, and record the result in a new commit along with the names of the two parent commits and a log message from the user describing the changes. Before the operation, ORIG_HEAD is set to the tip of the current branch (C).

If it is an actual replay , the commit a should be on branch B.


Solution

  • You have this history:

    --a--a_rev1--a'      <-- branch A
       \          \
    ----M--a_rev2--M'    <-- branch B
    

    You are observing that the changes of a' (which are identical to a) are missing from branch B.

    This is expected and not an error.

    When Git merges changes, it only considers the two project states at tips that are merge, a' on branch A and a_rev2 on branch B, and the merge base, which is a. From the point of view of branch B (the "merge target"), only the changes between the merge base a and the other branch tip, a' are considered and only those changes are integrated into the "merge target". Since a_rev1 and a' are changes that cancel each other out, there are actually no changes to integrate, therefore, the merge M' does not introduce any changes compared to the ancestor a_rev2.

    You can also read the story like this:

    1. On branch A, the developer first said "I need to revert a, so let's make a_rev1!" But then later, they said "No, wait, I do need a, so let's cherry-pick it, a'!" The effect is that we do not need any change.

    2. On branch B, the developer said "We definitely need to revert a. Let's make a_rev2.

    3. The branches are now merged. One side said, that no changes are needed, but the other said that some changes, a_rev2, are needed, so that is what remains.