Branch by Abstraction
Making a large change incrementally on the trunk by introducing an abstraction over the old implementation, building the new one behind it, then removing the abstraction.
The technique that makes large-scale change compatible with continuous integration, replacing the long-lived refactoring branch that diverges for three months and then merges catastrophically.
The sequence: introduce an abstraction layer over the component being replaced, and route all callers through it. Build the new implementation behind the same abstraction, incrementally, with both present in the codebase. Switch callers over — usually behind a flag, gradually, with the ability to switch back. Remove the old implementation. Remove the abstraction if it no longer earns its place.
Every step is committed to trunk, every step keeps the system working, and every step is releasable. The change may take months and never produces a merge event.
Where it is worth the ceremony: replacing a persistence layer, migrating between frameworks, swapping a third-party dependency, or extracting a module towards a service. All of these are otherwise tempting to do on a branch, and all of them go badly on a branch.
The cost to acknowledge: the abstraction is extra code, both implementations exist simultaneously which is a period of genuine duplication, and the temptation to stop after the switch — leaving the abstraction and the old code permanently — is strong. Finishing is part of the technique, and the half-completed version is a common source of confusing legacy code where two implementations exist and nobody knows which is live.