Trunk-Based Development movement is often proposed as the alternative to Git Flow. I understand this distinction - managing streams of work through long-lived branches can be trouble. For those new to TBD, it can look like this means throwing away all branches and everyone commits literally to trunk. Typically this comes out as "feature branches are bad". But I've seen the opposite - it's not that feature branches are bad, but that long-lived branches are bad.
While there are plenty in the Agile/TBD movement that see this as true, I've never seen it as an either/or. First, why is TBD good and long-running branches bad? To see why TBD works in a lot of cases, we can see the reasons all the way back to Lean/Kanban principles.
When you have many commits in a branch, or lots of work in a branch, we're building up inventory of work. The more our inventory builds, the more difficult it becomes to move that work through the development pipeline. This is why when I was on teams that did 6-month deployments, our deployments were so incredibly painful. We had months of work to test, verify, validate, and ship across many different departments. We literally brought sleeping bags to work.
Most see limiting WIP strictly in terms of the development activities. Don't work on more than one ticket at a time. But what's missing is a visualization of the entire value stream, that goes from concept to production. When we have long-running branches, that's WIP piling up before production.
And if your development value stream doesn't include "deploy to production", you're missing the most valuable step in the process - shipping!
The more we can limit WIP across the entire pipeline, the lower our cycle time can get. For that, long-lived branches that hold WIP go directly against the Lean concept of limiting WIP.
Reducing batch size
Going along with reducing WIP is reducing the size of each item in our process. For software development, this means working in small chunks, and each small chunk represents a shippable item. We call these "features" or "stories", but the main goal is to reduce the size of these features/stories so that the items go through the system more quickly and with less variation.
Interestingly enough, the side effect of small features and common-sizing work means that estimation no longer becomes a value-add activity (if it ever was). The #NoEstimates movement captured this explicitly - that it's more valuable to measure the cycle time, lead time, and throughput as those serve as better predictors for future delivery than committed estimates (because that's what estimatese are - commitments).
With these two items together - limiting WIP and reducing batch size, it leads us to the conclusion that we want our "features" or "stories" to be small, and therefore no long-lived branches.
Bringing Pull Requests In
Finally, we come to pull requests. If our features are small, then feature branching by itself isn't bad. We've tackled the problem at its root - driving to smaller features instead of just saying "branches are bad".
For us, pull requests are a means of driving quality into the process. Inspections at the point in time mean we don't have defects further down the process that become more expensive the later we find it. Pull requests answer two questions - "is the code right?" and "is it the right code?". Checking to see that the feature meets the expectations and understanding before it hits production (or the next step even) can be critical on our projects.
An alternative is pair programming, but in my experience, pairing just isn't for every person or every task. We tend to pair when we need to, and work alone when we need to, with most work being alone.
So drive to small features first, limit WIP and reduce batch size, and the rest will follow.