I prefer trunk-based development
trishagee.com
trishagee.com
When I work on a long lived (say, a week or two) branch, I just rebase it on main every morning or some times several times per day. It always contains 100% of everyone elses code. But their code contains 0% of my commits yet. Because that's the point of the branch. That none of it is visible to anyone else, until I deem it ready to. The point is that when I merge it, it looks like an atomic change on main, which takes it from one working state (100% passing tests and so on) to another working state. And the only way to ensure that is to make the validation take place IN my branch.
If I'm eager to "publish" my work, I can do that. I can make multiple branches with just one or two commits each on monday and tuesday with new functionality behind some feature flag. And then the final friday commit is basically just flipping the feature flag. But it doesn't change the fact that I do want to make sure main stays healthy so I still want to gate the commits. I don't think there is a pattern of development that works everywhere, and whether one thinks that feature branches is an overhead I think will vary.
People who are used to a project taking 3 months and having multiple builds per day, perhaps having their tests run in seconds via a commit hook, will think that the overhead of a PR is massive. But if validation builds/tests take multiple hours anyway then the overhead of making branches isn't really going to have an impact on productivity.
In the rare case you have 3 steps in that PR of 23 commits you want to keep, squash it in 3 pieces and merge them.
If it doesn't have to be testable in three pieces, then why break it down? For clarity when reviewing the changes perhaps.
> In the rare case you have 3 steps in that PR of 23 commits
I'd have 3 commits in this case.
It's not rare at all when you have code, charts, terrform, and scripts housed in a single repo. But even if you're just touching code some changes make sense to go out at the same time.
Personally, I feel that the use case for squash is when there are many WIP commits. Those could be avoided with the use of --amend, or --fixup, or cherry picking without a commit, etc.
I agree I think both have their pro's and con's. You can do trunk based development badly where your mainline is always broken, and you can do feature branching badly where you sit on a branch for months and then break the world when you merge.
I have not seen a lot of success with pull requests. Usually people just seem to say LGTM, then the original developer merges. That could just be the company I keep. Either way, I don't see them as a significant benefit of feature branches.
I'm not sure of the terminology as I've stepped away from development, but we used to work by the whole team working on a release branch, then merging to main at stable points. Maybe that gives the best of both worlds in terms of stable trunk and keeping continuous integration?
If we have a large feature with multiple people pushing code, we’ll create a “feature branch” that we then branch against. We run our PR flow into the “feature branch” then merge the feature branch into main when ready.
As part of smaller features, people rebase from main and work upstream changes back into the feature branch. Worst case, we spend and hour near the end of the project on a “merge party” where we work out all of the conflicts.
It contains no one else's code since everyone works on a branch. This is only holds if a single team uses a brach. That doesn't scale.
100% of everyone else's code that has made it to main.
If you need to coordinate more closely (i.e. different people working on related changes, which is likely to be common) then the branch doesn't have to be local and just for you, it can be shared, perhaps with individuals having their own local copy of that for quick check-ins¹ without publishing to others until ready². Whether this scales manageably will depend on the project and your team dynamics.
--
[1] to save work state before experimental changes, or just to have an off-machine last-known-good copy (assuming your “local” repo is covered by off-machine backups or is not actually local)
[2] though this indirection adds extra overhead – someone needs to rebase that from main then everyone may need to rebase to bring local copies in line
Branches vs. on-trunk misses the mark, IMO: the question is what's your practices like for how frequently changes get integrated. There's multiple dimensions of freedom in how you do things.
Branching is a nice tool to have.
Avoiding long-lived feature branches is best (particularly with a big subset of the team working in them). (But, dang, sometimes you need these abilities, too, so having a workflow that supports them well is good).
The amount of in progress work acceptable will differ if you work on a 20 year old desktop app with quarterly realeases like me vs a startup with 6 weeks of runway left.
I've had such headaches working across repos and having a "microrepo" organization scheme without thought to how these things are tested against each other.
It's a little more effort to get in the habit of, but it pays dividends.
I work on an open-source project using git that has both a main branch that's continuously-deployed, and stable branches for twice-yearly community releases.
We treat the main as a trunk: everyone makes small PRs to it and they're generally deployed day-of, if not within a couple of hours. If anyone's PR is too big, they're generally asked to split it up for incremental review+merge into main. PRs are generally rebased onto to main and tested via CI regularly, including right before merge. So, we enjoy all the "trunk-based" development benefits that the article describes. I get the impression that this workflow is common in the industry, especially for GitHub users.
That said, the branching features of git are still essential for us for creating, testing, and patching those stable community release branches. That's not to mention the offline development quality-of-life features that DVCSs give us. I couldn't imagine not using a DVCS!
I don't think that's the trunk based development that they're advocating for. I believe the version they're advocating for is where all developers commit and push changes directly to the main branch.
Anyway, agreed. Particularly, I wonder how any significant refactoring is approached.
Significant refactoring, done correctly, is a series of small refactorings, and they can be integrated every 30-60 min to minimize impact of changes.
You see what everyone has done and how it affects you before you can push.
(I'm not advocating for this, but I've worked in this style and it has pros and cons).
If there is a benefit to true trunk-based development over just trunk-oriented DVCSs usage, the article did not describe it.
Short lived feature branches that are merged back into trunk as soon as they are reasonably stable.
Deployment cadence is orthogonal, but CI ensures that it could be deployed at any point, even if it wouldn't or shouldn't be.
User acceptance testing and QA is done on the deployed main, and stable release candidates are branched from a point in main.
This might just sound like "branch based development" to many, but the key difference is that the feature branches are designed to only live within the context of a single sprint, for days or possibly hours, meaning there isn't time for "merge hell" to fully develop.
Good hygeine merging trunk into feature branches has a similar effect of course, which is why with good VCS hygeine there isn't that much difference.
But I was thinking OP meant something different, literally no feature branches. But maybe I misunderstood?
I think OP would benefit from more description of the actual process they are talking about, how you carry it out, what it looks like day to day, instead of just a list of benefits of a process it assumes you understand. I'm not sure we're all talking about the same things.
Atlassian: https://www.atlassian.com/continuous-delivery/continuous-int...
trunkbaseddevelopment.com: https://trunkbaseddevelopment.com/
Both of which describe having short lived branches. Atlassian is more aggressive with their timescale of "short lived", suggesting they should be closed out daily.
This contrasts with the "gitflow" model of development with long lived feature branches, which can live across multiple sprints.
It also doesn't make sense in a git based world, because there isn't much difference in concept or outcome between local and not-yet-pushed commits to local main vs a local branch.
The local branch just has a nice description and makes it slightly easier to organise the merge back to remote main later.
In both cases, you'd have the same (if any) merge conflicts, ultimately you have a situation where you need to merge, say, local commit 81f0f61e1 into remote shared history commit efe3c586c and, say, remote (latest) commit d224e5b7b.
In both cases I'd recommend merging remote main into the branch / local main to get a single commit on your local main/branch with both shared histories so there's no conflicts. IF there are conflicts trying to do that, resolve it with a local merge commit and manual conflict resolution.
That you happen to name your local commit at the point of divergence is a convenience for more easily controlling when to merge commits, and to enable switching back to a shared history more convenient. (git switch main is easier to remember than git checkout <hash>).
If OP is claiming to "not have branches" then they still do have a logical equivalent to branches, just without some of the tooling that branches make quicker.
Edit: Edited to avoid suggesting what "most" people think, because that is hard to judge.
PS C:\Users\eterm\Documents\code\aoc\adventclient> git rev-parse HEAD
a2e0c38327569533f2fc704bfe9617473011bf11
PS C:\Users\eterm\Documents\code\aoc\adventclient> git checkout -b test-branch
Switched to a new branch 'test-branch'
PS C:\Users\eterm\Documents\code\aoc\adventclient> git rev-parse HEAD
a2e0c38327569533f2fc704bfe9617473011bf11
PS C:\Users\eterm\Documents\code\aoc\adventclient> git status
On branch test-branch
nothing to commit, working tree clean
Both HEAD of main and HEAD of the branch point to the same commit until there is another commit.Is what you are calling trunk-based development simply avoiding long-lived branches?
But yes, trunk based development is about avoiding long lived branches, reducing the pain of merges.
Others think there is a contradiction there, that if they regularly merge from main to their long lived branches then they are avoiding merge hell, but they're not, because you can have the following situation:
1. Person A keeps long lived branch, "Feature X including refactor Frob"
2. Person B keeps long lived branch, "Refactor Y including Frob changes"
3. Person C makes regular commits to main.
Persons A and B regularly merge the changes from main, thinking they are avoiding merge conflicts, and on a literal sense they are, but what will happen is that when they realise they have incompatible changes, there will be an incentive for them to rush their feature branch into main first, so that they don't feel responsible for sorting the merge conflicts that will happen.
There will be a perverse incentive to dodge quality because they don't want the other side to get their branch in before them.
Also, because both branches A and B are long-running, their incompatibility and conflicting nature can be hidden for a long time.
But either way, yeah, what you say matches my experience and the consensus on best practice in communities/teams I have worked on already. I didn't really realize this was not universal, is perhaps my confusion! But I am still not sure if simply avoiding long-running branches is what OP was describing!
I’ve heard teams using Phabricator for “trunk based development” say they are reviewing commits not branches. If you’re doing any code review or CI before code lands on trunk, I’ve got a hard time seeing the difference between that and a “branch”.
Even in a CVS like SVN, your local code changes are still a “branch” that diverged from trunk until you commit it up to the server. And if you go 1-2 weeks without committing to trunk that’s just the same to others as going 1-2 weeks making hourly local git commits to a local branch…
And if I make a local git branch, commit 100 times to it over 6 hours, and then squash those local commits and push a single change to trunk, is that any different from trunk development?
At $JOB we use GitHub, try to make small Pull Requests that are usually merged within 24 hours of PR open, and those PRs tend to receive commits for between 6 hours and 4 days before we try to merge them into `main`. But you’re free to make as many commits as you want to your branch. We squash it down to a single commit rebased on main. Main is the only branch that lives indefinitely. Is that “branch” or “trunk” development? I think it’s “trunk” but maybe the author would differ.
I strongly disagree with this assertion. In my years of doing development with SVN/CVS the consistent pattern I would see is that developers didn't want to publish their work to trunk until they were certain it was good, and since with SVN you can't make local commits without publishing to trunk people delayed making commits until a feature was complete.
In other words, all the work that would be done on a branch with git was instead done on the developer's local machine without any revision control (and often without backups). Good developers would frequently `svn update` as they worked to get smaller conflicts, but code was merged to trunk at the same frequency as it is today with git[1].
In git, the equivalent would be make a branch, then commit --amend each change, fetch and rebase frequently, and then merge the single "squashed" commit.
[1] If anything code was merged less frequently because agile encourages smaller stories today but that is completely orthogonal to trunk vs branch based development.
The further you let your branch deviate from the trunk the worse it gets, sure, but you can alleviate that problem pretty much entirely by regularly merging develop into your feature branch. When it's time to merge your feature into develop there aren't many conflicts because you've already got all the changes in your branch.
Trunk based dev is still better in my opinion, mostly because it forces people to check their work properly, to write robust tests, or else they risk breaking the build. If you work on a team with people who aren't particularly careful then it's not worth the hassle though.
Trunk based development is better because it forces smaller commits. Everything else flows from that. Smaller commits are easier to test, and have less risk of breaking stuff. Smaller commits are easier to roll back if they break stuff (fewer "unmerge" conflicts, i.e conflicts when you merge the inverse of the patch). Smaller commits force you to develop a better approach to schema and data migrations. Smaller commits force you to add support for feature flags, because a whole feature can't be implemented in a single easily reviewable commit, and now you have tech you can leverage for canaries and so on.
This branching can be achieved in at least two ultimately equivalent ways:
- literal branches in your VCS of choice (or forks, clones, etc)
- literal runtime branches, via IF on feature flags
When a feature is ready to merge to prime time, the branches have to be merged, and unexpected interaction with other features can now be found. This is exactly as likely to happen when doing a git merge as when setting a feature flag to true.
Different features working in isolation but interacting badly is a fundamental aspect of software and not some incidental self-caused problem that can be prevented by a better development methodology.
To take a somewhat silly example - if one team is working on a feature to make error messages more readable and another team is working on a feature to make error messages opaque IDs looked up in a manual, these two features will obviously clash when they go live regardless of whether they were each behind a feature flag or on separate git branches. Hopefully few organizations would schedule such an obvious conflict of features, but more subtle versions happen all the time.
There's nothing intrinsic to trunk based dev that forces smaller commits. You'll just have a much worse time if you spend a lot of time making a lot of changes before pushing if you don't do smaller commits.
The problem is that you're constantly disrupting users with new features, changes to existing ones, and usually resetting or invalidating any open sessions.
I think CD is most useful to integrate into a 'staging' branch that is effectively the 'trunk' branch as far as developers are concerned. And that branch is them deployed to production on a schedule, say weekly, etc. Development should stil happen on feature branches though IMHO, just keep them small.
It's fair enough if you're going to switch on complete features for a subset of users, but if it's just to hide incomplete parts of a feature?
The practice seem like all risk for no gain.
We use this extensively at work, and wouldn't go back. You call it "just hiding incomplete parts of a feature", but for us that was a huge improvement in workflow. The real downside is that these flags tend to outlive their purpose; they don't get cleaned up as soon as they should.
But let's say you're part way through implemeting a new feature, and you need to alter a db table.
How do you know that won't affect existing code (apart from tests)? How do you put that behind a feature flag anyway?
As I said, deploying a change like this, without it being used for anything, seems like all risk and no gain.
Long-lived, all-encompassing feature branches are definitely an anti-pattern and they bit-rot unbelievably quickly, especially as team size grows. Some shops I've worked in still prefer them, though, because they demand zero dead code paths or half-baked features making it into the mainline (which are definitely their own risk of technical debt).
Due to git's 'multiple histories' it's quickly becoming a mess when many people are on the same branch, that's why I prefer to use a branch-model in git where only one person is on a short-lived branch, and a team uses PRs/MRs into a common target branch to create a clean, shared, linear history. This target branch doesn't have to be the main branch, but can itself be a (bigger) feature branch which then is eventually merged into the main branch (key is to not diverge too much from the main branch though, so that merging back doesn't become a hassle).
I wouldn't model how trunk-based development works based on experience with SVN.
For me short lived branches that get merged into main dependant on passing tests and maybe a PR is by far the easiest, most reliable, and most common approach.
For many of the reasons listed, I do use TBD for my side projects. Push it to main, tested, built and deployed. Easy. :)
Git doesn't have great support for trunk-based development. Gerrit tries to paper over the gaps, but the git client isn't great. git-branchless or another plugin is needed for better support.
> In the branching model, large and infrequent merges can introduce bugs that are hard to identify and resolve due to the sheer size of the changes.
You are not supposed to have branches that diverge too much, and if you do, what's stopping you in a centralized VCS from doing the same? I.e. having a large local changes that you did not upload back to the server. It actually makes the problem worse.
> In contrast, the branching model encourages developers to create separate branches for every feature, bug fix, or enhancement.
It seems the author suggesting not only a reversion the centralized VCS days but also to do it with just a trunk branch, where either each commit is made to the trunk right away (so it must be right on the first try) or you pile uncommitted changes locally.
Using a DVCS and committing to the local version of the `master` branch already counts as having a branched model, except more limited.
> In a trunk-based model, continuous integration becomes more straightforward because your code is committed frequently to trunk, and that's the branch your CI environment is running the build and tests on.
You can run CI and test in any branch you want, and you can rebase prior to running the tests. It is not a big deal.
One thing that occurs to me is that probably requires more intention and discipline into how your commits are divided up, what makes a commit? I guess you have to run tests and ensure green before committing, for one thing? But also I'd guess in general probably waiting to commit until it's a larger unit/feature than otherwise? But I'm guessing. I'm curious what people who use this method have to say about how they decide when to make a commit and what it should contain. And how that effects their experience.
If I'm understanding correctly what is being described.
You are conflating branch-based development with commit squashing. It is perfectly possible to revert a single commit even if it was developed on a separate branch and then merged.
What if it's the whole feature that broke stuff? What if we decide just before release that based on our release tests this particular feature isn't ready for production yet? Do you expect me to hunt through the entire commit log and identify and revert every single commit related to the feature?
Of course you wouldn't enable the feature all at once if at all possible. You'd do a canary first, sending a proportion of traffic. And your production system alerting would notice an elevated error rate on the canary and drain traffic to it, so you're not up in the middle of the night searching for a flag to flip or commit to roll back, at least not very often.
I am devoted to fridge based development. Whenever I have something harder to solve, I open the fridge and eat something. That seems to help my thought process.
It's hard for me to distinguish what is factual and what is "only" evangelism ... Additionally it's probably a lack of knowledge on my side, since I only worked with git. So examples would be very helpful to make this topic more accessable.
Objectively the continuous integration model wins in my opinion, but the feature branch thing we've ended up with seems to work well in many general cases. I think as with many programming related phenomena it seems to largely depends on human factors.
This is basically what I do on all of my projects and have put this into place in a couple of companies that I have worked at. Haven't encountered any serious issues and it has offloaded some big problems related to the gitflow madness that preceded it.
Branches are great. Pushing branches is great. People rebasing is a good thing. Committing as much as you need to is great, and lets me diff when I mess up.
If I couldn't commit till I was "done" because we're all working off main, like I had to do in the past, oh man were there gonna be merge conflicts. I've had projects where we spent a week on merge conflicts alone. I have never, in 14 years of using DVCS had anything even remotely close to that.
Every other set of rules about how/when to branch/merge/PR/rebase feels like an attempt to get around having to just actually talk to each other and know what's going on. By all means, have those guidelines. But first and foremost: talk to each other.
Depending on your scenario you might not need to scale past a handful of devs. In that case pretty much any process can be made to work well, including a completely linear commit graph.
I rather resolve merge conflicts than fix bugs.
Both a "regular" commit and a commit to a branch are just nodes on a DAG.
They lack a convenient name. You can go to any commit and "turn it into" a branch if you so wish by "promoting" it to a branch.
My point is that there's no such thing as "avoiding branches" in git. Or least, you can't avoid the "downsides" of branches as some perceive it.
Those issues are still present with commits, just without the convenience of good naming.
That is to say, and I should have made it more clear in my initial comment, that trying to avoid branches is pointless and indeed counterproductive. You still logically have branches, without the convenience.
There are specific version control systems that do some things better than git, but have huge downsides for other things.
If your company is focused on a specific thing (graphics or 3D modelling), there are options.
But usually "everyone" knows git well enough not to need hand-holding and that's what companies go with.
Hell - I used several before git (svn, sourcesafe, and something with an M at the start of the name that I think I've repressed so hard I can no longer recall).
My desire to continue using git has nothing to do with my inability to imagine a different system. It comes directly from having actually used those other systems.
I've done trunk development, I've dealt with file locks (and having to hunt down the fucking admin to unlock the file that one coworker locked before taking off on vacation).
I've seen the pain there. If anything, the vast majority of negative experiences with version control in my career came out of the first 5 years of it, before git took off everywhere. After that, the worst experiences are from the folks who absolutely refused to even try to learn git, and just bury their head in the sand of whatever tool they were using.
I don't really even like git, but unlike those other monstrosities... I can tolerate it. Is it "the best VCS"? Eh - probably not. It's fairly old, the concepts have trickled out to other systems, the cli tooling is obtuse, etc. But it's solid, widely used, and has none of the serious pain points those other tools caused.