46 pointsby handfuloflight6 hours ago7 comments
  • jascha_eng5 hours ago
    I've rarely seen the first description of it where people actually commit directly to main. Except in very early stage projects. But it does always feel the fastest if you only review code "on-demand" in PRs/MRs instead of enforcing it for every change.

    I think in a team with good ownership, enforcing formal reviews slows down a lot. But of course in a larger code base where no single engineer can understand all the effects a change might have, having a bit of knowledge sharing and 4 eyes enforced is often the better approach than yolo-ing it.

    Then again I did build an SQL review tool for database access because I felt like "yolo-ing" production access was also not the way it should be. It's an interesting slider between full autonomy and strict review processes where each team needs to find their sweet spot: https://github.com/kviklet/kviklet/

    • roggenilsson4 hours ago
      In my previous job we worked like this. We had one dev branch that everyone pushed directly to and the dev branch was eventually branched to an RC branch which in turn was merged to master once the release was complete.

      The team was small, around 6 people, and the codebase was maybe medium sized (~500k LOC). There was no formal review process, instead it was up to each team member to ensure the quality of their own and others code. In practice I would read through all commits that came in the previous day while having my morning coffee. If there was some egregious I would talk to whoever made to commit to make discuss if something should change, but this was fairly rare.

      Formal PR reviews were only ever really used for new members or for bigger/sketchy changes where someone wanted more eyes on it.

      Because I ended up reading most commits, I ended up knowing how pretty much the entire codebase worked. It takes a while for this to develop, but the more you do it the better you get at it, especially in the context of a single codebase.

    • dxdm4 hours ago
      As I understand it, trunk based development does not call for committing directly to main. It says to avoid long-lived branches for releases, whole features, etc.

      There's nothing wrong with small, short-lived branches that can be quickly reviewed and merged into main.

      That being said, I've been in a small team where the blessed style was to commit directly to main and do reviews "on demand". It quickly gets features deployed in a way that builds a lot of rot and debt into your project that people quickly lose a good understanding of. Then you just keep piling.

      There's probably a way to get this done properly with a tiny team of very experienced and well-aligned developers; but that's usually not what you have in an environment that pushes this kind of extreme no-review-interpretation of trunk-based development.

      Slow down, do reviews, avoid keeping branches open for more than a day.

      • jascha_eng4 hours ago
        > well-aligned developers

        I think this is very key, if the development style and the direction of the project is clear, much less review and alignment is necessary.

        And also

        > avoid keeping branches open for more than a day

        Big +1 on that, fast reviews are extremely key. Most teams I have seen often took days or even weeks to merge branches though, often because you end up waiting too long for reviews in the first place. Or because of good old bike-shedding. But also because these code reviews often uncovered uncertainties that needed longer discussions.

        However usually code is easy to change, so defaulting to "just merge it" and creating followup tasks is often the cheaper approach than infinite review cycles.

        • dxdm4 hours ago
          I think it's still worth-while to do reviews. A second pair of eyes does wonders, and it spreads knowledge of what things exist and how they work. If changes are small, reviews can be quick. It's possible to keep building on top of code being reviewed, and even easy when using modern VCS tooling like jujutsu.

          Once the code is merged, chances are it will not get changed Those follow-up tasks will be displaced by more pressing work that will keep piling onto a slightly unstable foundation, increasing the tilt over time.

          There is an excluded middle between "no reviews" and "infinite review cycles": proper, timely and efficient reviews. They are worth investing the time to get right. They will start paying dividends months down the line, and boy will they keep paying.

          This is not about trying to get things perfect from the get go, but to get them done right while you're there. "We'll fix it later" is not gonna happen, and is much more expensive than it initially seems.

        • KronisLV4 hours ago
          > However usually code is easy to change, so defaulting to "just merge it" and creating followup tasks is often the cheaper approach than infinite review cycles.

          I wish this was the "default" mindset everywhere, especially in those cases where you have that one colleague that loves to nitpick everything and doesn't see an issue with holding up both releases and wasting your time over menial pedantic stuff. It would be so much easier to merge working code and let it work, and keep those TODOs in the backlog (e.g. trash).

          In a sane world, code review would be like:

            1. Will this work and not break anything? We checked it, it's okay. There are no apparent critical or serious issues here.
            2. Here's a list of stuff that you can change if you wish, here's why it might be an improvement.
            3. Okay, we have some left-over nice to haves, let's keep track of those for later (or not) and merge.
          
          It gets infinitely worse if you're working on 3 projects in parallel and the person wants long winded calls or starts nitpicking about naming, or wants things done exactly their way as if it's the only way (doubly worse if their way is actually worse by most metrics and tastes).
      • reverius424 hours ago
        > "Scaled Trunk-Based Development"

        > There's nothing wrong with small, short-lived branches that can be quickly reviewed and merged into main.

        I would have called this "branch based development", personally.

    • maccard3 hours ago
      I work in games, and we do commit directly to main. On a smaller team you can get away with pre submit review, post submit checks. On a bigger team you need pre submit checks but honestly the point where you need this is much much later than you think. One of my previous projects had 100+ people committing directly to main with no pre submit checks and jt broke once or twice a day. The builds took longer than that to go through so you just always sync to “last known good”
    • div3rs35 hours ago
      We're organized in small teams around specific products. That leads to mob programming being a good fit, so code reviews and knowledge sharing is organic. This gives us an opportunity to commit to main and do direct deploys to production. Treating every commit as deployable is key, but it drives good practices.
  • fjfaase4 hours ago
    Working without branches, except for releases, is the most effective way of working, using rebase instead of merge to get a single line of commits. Even release branches can be avoided with continuous deployment.
    • locknitpicker4 hours ago
      > Working without branches, except for releases, is the most effective way of working, using rebase instead of merge to get a single line of commits.

      I think you're confusing workflows with commit history.

      You can work with feature branches all you want, rebase them as you feel like it, and then do squash merges to main.

      The likes of GitHub even have a button for this.

      • ahartmetz3 hours ago
        Why in the world would you do squash merges? ...except to clean up messy mini-branches written by total noobs. I don't do separate commits for funzies. If you want separate commits for ease of review, why not for later reading of the code.

        Assumption: above mentioned total noobs don't use git rebase -i or equivalent, everyone else does

        • aidos3 hours ago
          It’s pretty hard to keep the commits in a working branch in a good legible state - certainly it takes work to do it.

          In 25 years of professional development I’ve never really had a situation where the commits on a branch would have helped me understand what was going on a year ago when the work was done. That includes pretty big bits of project work.

          I’d much rather have a trunk with commits at the granularity of features.

          • rsaarsoo3 hours ago
            I on the other hand have never come across a scenario where I run git bisect to find a commit that broke something, discover a small commit as a culprit and wish I had instead found a commit that's hundreds of lines long.

            What has happened a whole lot though is the exact opposite.

            • Normal_gaussian3 hours ago
              It might be better to view a commit as a natural unit of working code. There are a lot of units of working code which would be tedious to be introduced as a only a few lines.

              As such, a new codebase is likely to grow by large unwieldy commits and a mature one by targetted small commits.

          • sodapopcan3 hours ago
            `git log --merges --first-parent` gives you both.

            I've had separate commits come in handy several times when `git blame`ing when working with people who actually described what changes were about in their commits (which, unlike comments, don't go out of date).

          • groestl3 hours ago
            100%. I don't want to know how the sausage was made. It's similar to research papers, or history books, where the way we arrive at results or outcomes in the real world is often quite different from the way it's presented in the final form.
            • ahartmetz2 hours ago
              A good commit history is more like a well-written sausage recipe than like a TV documentary about scandalous sanitary conditions at Foo sausage factory ;)
          • barrkel3 hours ago
            I'd much rather reduce the risk of mutation to the trunk, by having small easily reviewable commits direct to trunk.

            It's less about reviewing commits from a year ago, than making change low-risk today. And small commits can easily be rolled back. The bigger the commit, the more likely rollback will be entangled.

            It better to have partial features committed and in production and gated behind a feature flag, than risk living in some long-lived branch.

            • locknitpicker3 hours ago
              > I'd much rather reduce the risk of mutation to the trunk, by having small easily reviewable commits direct to trunk.

              You're not addressing the problem. You're just wishing that the problem wouldn't happen as frequently as it does.

              But that's like wishing that race conditions don't happen by making your allocations at a higher frequency.

              • barrkel2 hours ago
                I'm describing how Google works with teams with high release cadence, fwiw.

                Also, your comment reads a little bit as a non sequitur.

          • lloeki3 hours ago
            In 25 years of professional development I have several counter examples where some bit was either a trivial git revert of a single commit - among multiple ones in a branch - away, or an absolute pain because the squash-merge commit had flattened too many concerns together, concerns that were perfectly split in the topic branch but that branch was long gone by virtue of being auto-deleted on PR merge.

            Coincidentally, every single squash-merge commit advocate I've had the unfortunate debate with was a regular practitioner of public tmp / tmp / try again / linter / tmp / fix / fix / haaaaaands commits.

            Note that I'm not against squashing/history rewriting e.g rebase -i and stuff (which I'm a heavy user of so as to present sensible code aggregation reviewable per-commits), only squash-merge.

            • homebrewer2 hours ago
              I take it you haven't had the pleasure of working with your average ("dark matter" as they're called here) developers. I wouldn't call myself an "advocate" of squashes, but it's often the only practical way of keeping git history somewhat usable when working with people who refuse to learn their VCS properly.

              I chunk my changes into tiny commits ("linter"/"tmp"/"wip"), but then rebase aggressively, turning it into a set of logical changes with well-formed commit messages. git bisect/revert work great with history written in this way even years layer.

              But: most of the people I've been interacting with also produce lots of "wip"/"tmp", but then skip the rebase. I can only offer my help with learning git rebase for so long before it starts taking too much time from the actual work. So squash it is: at least it produces coherent history without adding thousands of commits into `--ignore-revs-file`.

              • skydhashan hour ago
                And sometimes, a patch is just that big. especially in UI works where a single change can cascade down to multiple layers.

                > I chunk my changes into tiny commits ("linter"/"tmp"/"wip"), but then rebase aggressively, turning it into a set of logical changes with well-formed commit messages. git bisect/revert work great with history written in this way even years layer.

                In a PR based workflow, it has become easier to have the PR be a logical unit than to `rebase -i` all the time on my end.

            • skydhash3 hours ago
              If you work with a ticket system, squash-merge gives you the same granularity, where a commit would refer to a single ticket.

              A ticket should be atomic describing a single change request. PR in this case are the working room. It can be as messy or as clean as you want. But the goal is to produce a patch that introduces one change. Because if you would rebase -i at the end, you would have a single commit too in the PR.

              • ahartmetz2 hours ago
                No, you wouldn't. git rebase -i is to remove noise, which is about merging commits that, well, make more sense together than apart. Which is mostly about summarizing trivialities (e.g. several typo fixes) and squashing fixups into commits that introduced a problem in the same branch.

                A typical bugfix branch might look like this after rebase -i:

                Move property to a more appropriate place

                Improve documentation of feature Foo

                Fix accidental O(n^2) in feature Bar

                Fix interaction of Foo with Bar

                • skydhash2 hours ago
                  Those looks more like noise to me. A squashed merge (or a final squash before PR) would be:

                    TN 43 - Fix mismatched interface between Foo and Bar
                  
                    We've moved the X property to a more appropriate place and
                    improved the documentation for Feature Foo. We've also found and fix
                    an O(n^2) implementation in feature Bar.
                  
                  The the ticket TN-43 will have all the details that have lead to the PR being made: Bug reports, investigations, alternative solutions,...

                  The commit message is what's more important. I don't think I've ever needed what is in a merged branch. But I've always wanted the commit at one point to have tests passing and a good description of the patch. And all the talk in the engineering team are always about ticket. It does makes sense to align those.

                  • sodapopcan25 minutes ago
                    They aren't noise at all and have found them useful a bunch in the past when I worked at a place that didn't squash. Commits at this level act as immutable comments that don't get out of date. Provided you do --no-fast-forward merges, the merge commit is the feature commit and you can get the "clean" feature history with `git log --merges --first-parent`. Best of both worlds! Being able to `git blame` and get a granular message about why something was done can be really handy, especially when looking unfamiliar code.
          • VorpalWay3 hours ago
            Each commit should be small, have a descriptive commit message and be stand alone. I consider the Linux kernel a good example of how to do commits and messages right. Often the commit message is longer than the code change.

            I strive to do that when making commits for work too, and that helps when going back in history and looking at history to motivate why a change was made.

            While working I rebase all the time to move changes into the relevant commit, I don't find that particularly hard or time consuming. Doing this upfront is easy, splitting commits after the fact is not.

            I consider this standard practice, at least in the sector I work in (industrial equipment control software, some of which is considered human safety critical).

        • xen02 hours ago
          I want every commit to represent a buildable state in which I have confidence automated tests pass. Bisecting is made unnecessarily difficult otherwise, and it's nice to be able checkout any commit and just build something for testing.

          This constraint is usually enforced by code review.

          On Github, the unit of code review is the PR.

          Therefore, I prefer squashing.

          • ahartmetz2 hours ago
            >On Github, the unit of code review is the PR.

            It is, and that is some bullshit. The only sensible way to work with that is to break up larger features into several PRs - which is often positive anyway, but sometimes it doesn't fit the nature of the change.

            • xen023 minutes ago
              I am not a fan of Github's interface.

              But my point is, is that I believe the important thing to preserve in history is whatever your unit of review is. If you could stack PRs and each were subject to the individual review, I would not combine and squash those (just the individual commits within each PR).

        • locknitpicker3 hours ago
          > Why in the world would you do squash merges?

          Why would you not want to squash merges? It's one of three options offered by GitHub in their PR merge buttons.

          They create a linear commit history, which is what you want when you have to audit changes.

          > except to clean up messy mini-branches written by total noobs.

          Nonsense. You get a messed up commit history as easily as when you create a PR in GitHub, and after team members merge their commits, you click on GitHub's "update branch" button.

          You also get a messed up commit history if you merge a PR after someone else merged theirs.

          You will always mess up your pristine commit history if you have more than one person posting and merging branches. With one notable exception: squashed commits.

          • ahartmetz2 hours ago
            Rebasing fixes all of the problems for which you present squash merges as the only solution.
  • swiftcoder4 hours ago
    stacked PRs. stacked PRs!

    Seriously wish the stacked PR workflow would gain more traction outside of FAANG. Apart from the (somewhat pricey) Graphite offering, there's no standard UI for managing stacked PRs in the wild.

    • amabito2 hours ago
      The tooling gap exists partly because git's data model has no native concept of "this branch's upstream is another feature branch" — each PR is independent from the forge's perspective, so rebasing one layer in the stack requires manually re-targeting every PR below it. FAANG-internal tools solve this by storing the stack relationship in a metadata layer outside git itself, then regenerating the PR graph after each rebase. Without that layer, the bookkeeping falls on the developer, which is why most teams abandon the workflow after two or three levels deep regardless of how disciplined they are.
    • reywilliams4 hours ago
      GitHub[1] is working through this now, hopefully it leads to more adoption.

      [1]: https://x.com/jaredpalmer/status/2019817235163074881?s=20

    • ahartmetz3 hours ago
      I think Gerrit has something like stacked PRs - and incidentally it's used for Chromium and was created for running its own public open source projects by Google. It's also used by the Qt project, which is where I sometimes use it.

      Gerrit looks ugly and is not very easy to use at first, but at least it's not slow like GitLab and GitHub.

    • Hamuko3 hours ago
      What's the benefit of stacked PRs?
      • swiftcoder3 hours ago
        It's basically automatic rebasing of a set of PRs built on top of each other. Makes it easy to split large changes into manageable pieces that are asymmetrical for your colleagues to review individually, and makes it easy to maintain feature branches long enough to get the feature working end-to-end
  • 4pkjai3 hours ago
    I worked in a team of four between 2017 and 2020 this way. I really enjoyed it. After that I joined a company that worked with PRs. Felt like such a waste of time.
  • SuperJanne3 hours ago
    Trunk-based becomes even more critical when AI agents are writing code. Long-lived branches create merge hell when both humans and agents are committing. We've found keeping everything on trunk with feature flags is the only sane way to coordinate human + AI contributions.
  • ngalaiko3 hours ago
    trunk based is the way to go, especially for small teams building web / backend services

    especially combined with monorepo

    amount of time people spend updating dependencies between internal services and libraries in a pursuit of semver for now reason is just absurd

  • alfiedotwtf4 hours ago
    The thing missing with a lot of these branch management posts is release management… because it’s lovely to live in an ideal happy-path world, but what happens when main is tagged for release, only some customers update, main moves of with multiple breaking changes, and only then do some customers require fixes to their releases (who could all be on different i.e even older tags)?

    Do you take their tagged release, fix it there, and then send them that branch release with the fix, or do you send them a fix on current main - you know, the main that is now a million releases ahead with multiple breaking changes? And what about all the intermediate release tags? Do you fix each one there too if they have the problem, or do you only update when customers on those releases start having that issue too?

    And if you fix to their old tagged release which is incompatible from main, does this mean you have to do this fix twice i.e on their tagged release and also fix it for main? But what if this fix affects other people who are all on different branches too? Now… times this by 20 different customers all running different hardware and different branches with different problems :(

    Maybe my comments are off topic, and don’t get me wrong - I prefer “trunk is releasable” motto, but I think maybe as an industry we should all come up with an Acid Test (like the only CSS Acid Tests) so we can through all these branching strategies into the ring

    • Longwelwind2 hours ago
      Trunk-based development fits nicely when you have a single deployment product like a SaaS and you don't need to maintain old versions of your software. You only have one prod environment.

      If you build a software that you distribute so people can deploy it themselves (a library, a self-hostable solution, ...), then you most likely semantic versioning. In that case, the best model is to use what semantic release offers.

    • F-W-M4 hours ago
      If you need to support multiple versions at the same time, you need to extend TBD in some way.

      We just cherry-picked stuff back to release branches, if we needed a fix.

      • Zardoz843 hours ago
        we have a "release" branch and a "develop" branch. The release is trunked on the last released version and (in theory) only gets fixes. If we need to fix a more older version, we create a temporary branch on that version to fix it, and we cherry pick the fixes (or merge to) to release branch and then to develop branch.

        The triple mortal loop, comes that we have two versions of the product. One with the old no responsive frontend and other with a modern responsive frontend. And we need to release and develop the two versions for sometime, before the direction decides to kill the old no responsive version. So we end with 4 branches: release, release_rwd, develop and develop_rwd. If we fix something in release, we need to do a diamond merge : release to release_rwd, release to develop, release_rwd to develop_rwd and develop to develop_rwd