Short-lived Branches and Conflict Phobia
There’s much unfounded fear surrounding merge conflicts. In the non-CI, non TBD world, long-lived branches introduced conflicts which were difficult to resolve and led to the very mistaken view that to manage these situations, changes have to be isolated from each other carefully. In reality this raises even more problems.
On the other hand I’ve seen the notion that frequent merges are made to avoid conflicts. It’s actually the opposite. Just like real life, conflicts are a reality and unavoidable. The total impact of conflicts is not reduced by delays — that’s wishful thinking. It’s by meeting them head on early to before they get bigger.
What are Merge Conflicts?
During a merge, the working tree files are updated to reflect the result of the merge. Among the changes made to the common ancestor’s version, non-overlapping ones (that is, you changed an area of the file while the other side left that area intact, or vice versa) are incorporated in the final result verbatim. When both sides made changes to the same area, however, Git cannot randomly pick one side over the other, and asks you to resolve it by leaving what both sides did to that area.
In a merge that creates a commit, git looks at textual changes made by both branches since their last common ancestor and guesses how to combine them. That’s all.
Changes and Conflicts — Textual vs Semantic
Say we’re working on a story. In the current version Jake is introduced in the first chapter and Kelly the next. Both of us are working on this story in separate drafts. I’ve made Jake die at the end of chapter 1 and you’ve made Kelly meet Jake in chapter 2. Git will very naively merge both changes without any conflicts and produce a logical impossibility —Kelly cannot meet a dead John. No automated tool is able to detect semantic conflicts. There are changes in the meaning of the information that require human judgement.
Semantic conflicts can occur even if there are no merge conflicts and those are actually what we should really be concerned with. This point adds to the importance of automated tests which capture intended behaviour and constraints of code and can tell us of violations before/after merging conflicts. The same type of risk still applies to test code, but that is the whole point. They are executed, less abstract, expresses intended semantics and are more explicit.
Conflicts are Unavoidable
Git can raise merge conflicts even when the changes are intended and compatible. We actually want Jake to be hungry and his fondness for fries known before chapter 2, but these two changes are made in the same place. There is no way to avoid this. Definitely not by encouraging both writers to continue writing in isolation. The scope of conflicts will compound, and eventually nobody can have the confidence to resolve them.
Understanding how git merges leads to an aversion of long-lived branches and git-flow type feature branching. There’s no other logical conclusion. Do the CI — trunk-based development and automated tests. With continuous feedback, we can and should resolve the inevitable earlier. Pull from trunk extremely frequently. Run tests on every commit. Merge extremely frequently.
- Conflicts cannot be disappeared, the earlier we resolve them the better.
- Semantic conflicts cannot be captured by any diff tool. Write automated tests and exercise human judgement.
Video Mention: Sam Newman — Feature Branches and Toggles in a Post-GitHub World
https://www.youtube.com/watch?v=lqRQYEHAtpk from 29:00
A dark pattern exists where process is introduced to mitigate risks -> deltas get bigger -> risk gets bigger -> bad thing happens -> more process -> more delay -> bigger delta -> risk gets bigger.
Conflicts are just another type of risk. From this understanding that risks are better managed if changes are kept small and frequent, the same conclusions of small diffs, trunk-based development can be drawn.