Undo Published Merge and Re-Apply Original Changes Without History Rewrite

Viewed 116

Good evening,

we've ran into the following git issues on one of our intern projects. We've got two branches, main and feat/smth and both have diverged during development. There was a pull request created from feat/smth to main which showed those conflicts. Those conflicts were resolved using the graphical tool of GitHub, basically performing a merge from main to feat/smth in order to fix the conflicts. This merge was faulty and resulted in corrupted files which were not noticed for now.

Therefore, the feat/smth branch was merged (using GitHubs Pull Request) into main where the errors were found. In order to revert the changes, the students ran a git revert on the merge commit on main and pushed all these changes. Now, the issue is that we would like have the changes from our feat/smth branch and merge them "again" into main. Obviously, that does not work because the commits are already merged and therefore present on main.

Let's assume that we don't want to force push because a lot of other developers already checked out the code and continued working. My first thought was to revert both merges locally using git reset -m <commit_sha> and re-perform the merge. During the merge, I've "avoided" the errors from the very first merge attempt. I've stashed the files, pulled the feat/smth branch and applied my stash. Using this approach, we've got the correct changes from our merge, but all other changes on the branch aren't applied anymore, as they are still reverted on the main branch. The commits of our feat/smth branch are already present on main and were reverted. The only change we introduce using this attempt are the correct changes from the merge.

And that's where I'm basically stuck. What is a proper way in order to undo a merge pushed to main and re-apply the original changes from the branch? My first thoughts were cherry picks, although that might be cumbersome with a lot of commits (and might result into a lot of merge conflicts?) or to take a "hard copy" of the correct version of the branch and just copy paste them onto the main branch. However, this might result in non-wanted changes, as well. The later approach might get tricky in case that changes were made to main already. The author has to carefully check all the changes and distinguish between those from the feat/smth branch and the ones from the main branch. In addition, copy paste doesn't feel like it's the proper "git way". Any ideas? I really don't know how to solve that the "git way".

I've made a drawing of the git history, as well for illustration purposes.

Drawing of git history

Thanks a lot! I'm curious about the approaches.

1 Answers

Update: I originally misread your question to assume you wanted to add a commit to fix the merge, but in fact it appears you need to remove one from the original branch. In this case, I would simply rebase your feature branch (4 commits as shown in the diagram without the merge commit) onto main and resolve the conflicts at that time. Then you won't need a merge commit before merging into main. In fact, that's what I would have done in the first place instead of merging in main to resolve. (And one of the reasons I prefer rebase there instead of merge is exactly because of what happened; most people find it easier to detect bad merge artifacts in the commits themselves, than in a merge commit.)

That being said, if you don't like a rebase workflow, and wish to retain the original merge-base for those 4 commits and redo the merge, then you'll want to do the rewrite described below in #2. Start by checking out your feature branch and resetting to the commit just before the merge commit (to the right of #1 marker in your diagram.) Then rebase --no-ff, then merge in main, then create your PR.

Note: in your specific case it doesn't make sense for you to do #1 below.

Original Answer: The more general case is when you later decide you still want all of the commits in the original branch. In that scenario you typically have two options:

  1. Revert the reverted merge commit. This way you can then merge your commits on the feature branch back in again. I suggest whenever you revert any commit, and especially when you revert a revert, in the details of the commit message you explain why you are doing it, and also include the commit ID of the commit being reverted.
  2. Rewrite your feature branch (not master), by using git rebase --no-ff [merge-base-commit]. (Note the merge base commit ID is git merge-base main feat/smth, which is the second commit from the bottom of main in your diagram.) Maybe you've heard of --no-ff for merges, but the same option exists for rebase too. Similar to what it means for merging, for rebasing, it just means: "rewrite the commits even if you can fast forward". Once the commit IDs are changed you'll be able to merge all of those commits back in, along with the new one containing the fix.

By the way, option 2 is pretty much what you were thinking with cherry-picking a list of commits in order, except that the rebase is automated and less prone to error. It shouldn't be possible to have merge conflicts with the cherry-picks or the rebase, as long as you don't change the parent commit ID.

As for which way to go, I'd say it's a matter of taste. A revert of a revert is kind of confusing if you don't understand what's going on. But I think I'd probably lean towards reverting a revert if that was the only change (e.g. you decided you didn't want the merge, then changed your mind without adding new commits). If you have new commits which is often the case, I might prefer rebase --no-ff if I didn't have too many commits that would be duplicated. Perhaps I'd rewrite 5 but not 50.

Related