gitgit-revert

Handling revert commits when merging feature branches in git


We had two feature branches targeting master: feature-a and feature-b. feature-b was temporarily merged into feature-a. That was subsequently undone with git revert -m1.

When feature-a was merged into master, Github recognized that all commits from feature-b were now present on master, and "helpfully" closed the outstanding PR for feature-b. That's not actually helpful though, because the code changes from feature-b were not present in master (because merging feature-a also merged the 'revert "merge feature-b into feature-a"' commit).

While not common, it can be useful to pull in another branch being worked on in parallel as that may influence how you develop your feature. How should such situation be handled?


Solution

  • Don't revert, rebase.

    Your repository looks like this.

    A - B - C - D - E - F -------------- M2 [master]
         \       \                      /
          \       Z - Y - M1 - X - R - W [feature-a]
           \             /
            1 - 2 - 3 --- [feature-b]
    

    R is the reversion. M are merge commits. master contains feature-a and feature-b by the connections between the commits; there's nothing special about a revert commit.

    You probably should just leave this as is now that feature-a has been merged into master, but for next time... let's roll back the clock to before the revert.

    A - B - C - D - E - F  [master]
         \       \
          \       Z - Y - M1 - X [feature-a]
           \             /
            1 - 2 - 3 --- [feature-b]
    

    Here feature-b has been merged into feature-a, and an additional commit has been added to feature-a.

    To fully undo a merge, do an interactive rebase to remove the merge commit entirely. We rebase feature-a onto commit Y.

    $ git switch feature-a
    $ git rebase -i Y
    

    At this point you'll get an editor which will list all the commits being rebased: X and M1. Simply delete the line with the merge commit. Git will rewrite commit X on top of Y and it will appear as if the merge never happened.

    A - B - C - D - E - F  [master]
         \       \
          \       Z - Y - XA [feature-a]
           \           \
            \           M - X
             \         /
              1 - 2 - 3 [feature-b]
    

    The one caveat is that because the parent of commit X changed, it must have a different commit ID, XA. If you already pushed feature-a, it will not push again because it has been rewritten. Use git push --force-with-lease to safely push the rewritten feature-a.

    If something goes wrong with the rebase, you can always go back. Note that the merge commit and original X commit are still there. Nothing refers to them and they'll eventually be garbage collected in a few weeks. To undo the rebase, move feature-a back to commit X. git reset allows you to move branch labels around as you like.

    $ git switch feature-a
    $ git reset --hard X
    A - B - C - D - E - F  [master]
         \       \
          \       Z - Y - XA 
           \           \
            \           M - X [feature-a]
             \         /
              1 - 2 - 3 [feature-b]
    

    See Rewriting History in Pro Git for more.