At high level, it looks like that this approach should have worked.
My guess is that you think git merge rollback replays the two commits unique to rollback onto the current branch (main). If git did that, the first commit would do nothing since main already has that change. The second commit would restore the properties file to its old state. This is what you expected. But this is not how git merge works.
git merge does not replay commits
What git merge does by default is called a three-way merge. As explained in an answer to another question:
You might be best off looking for a description of a 3-way merge algorithm. A high-level description would go something like this:
- Find a suitable merge base
B - a version of the file that is an ancestor of both of the new versions (X and Y), and usually the most recent such base (although there are cases where it will have to go back further, which is one of the features of gits default recursive merge)
- Perform diffs of
X with B and Y with B.
- Walk through the change blocks identified in the two diffs. If both sides introduce the same change in the same spot, accept either one; if one introduces a change and the other leaves that region alone, introduce the change in the final; if both introduce changes in a spot, but they don't match, mark a conflict to be resolved manually.
The git merge docs has a less detailed explanation, but explains exactly the behavior that is confusing you:
With the strategies that use 3-way merge (including the default, ort), if a change is made on both branches, but later reverted on one of the branches, that change will be present in the merged result; some people find this behavior confusing. It occurs because only the heads and the merge base are considered when performing a merge, not the individual commits. The merge algorithm therefore considers the reverted change as no change at all, and substitutes the changed version instead.
I believe the reason does this is for a few reasons:
- 99% of the time, merging is about parallel lines of development, working on different things, and you want to merge both those things. Thus you don't want to, by default, give priority to changes made on one branch over the other. If
rollback were a feature branch, you wouldn't want it to just override the changes made by impl.
- 99.9% when a developer one branch makes a change and subsequently undoes it, the intent is "Pretend I never did that". So the behavior that confuses some people most of the time does what people expect.
- You can achieve the other behavior by specifying a different merge strategy, e.g.
git merge -s ort -X theirs rollaback , or by using other commands like cherry-pick or rebase, depending on what you exactly want.
solutions #1: git revert
Nix the rollback branch and just use git revert:
git checkout main
git revert main..IMPL
This is simplest.
solutions #2: branch rollback from impl
If for some reason you must do it via a PR, or for whatever reason must have a rollback branch, then branch rollback off of impl, not master:
git checkout impl
git checkout -b rollback
git revert main..IMPL
It will look like this:
1 main
\
2 impl
\
3 rollback
merging impl to deploy:
1---4 main
\ /
2 impl
\
3 rollback
merging rollback to rollback:
1---4-------5 main
\ / /
2 impl /
\ /
3---- rollback
Not only will app.properties be reverted as you expected, the git history is simpler AND a better representation of what happened. Compare it to the result of your current approach, which has two identical, redundant commits (2 and 3), and misleadingly splits into three branches at the beginning.
2 rollout
/ \
1---5---6 main
\ /
3---4 rollback
It looks even better if you allow fast-forward merges.
1 main
\
2 impl
\
3 rollback
Deploy ends up like this:
1---2 main, impl
\
3 rollback
and rollback like this:
impl
1---2---3 main, rollback