Hacker News new | ask | show | jobs
by choeger 127 days ago
I am working with main/master for years now, and there's one problem you don't have with develop: Whenever you merge something into master, it kind of blocks the next release until its (non-continuous) QA is done. If your changes are somewhat independent, you can cherry-pick them from develop into master in an arbitrary order and call that a release whenever you want to.
5 comments

> Whenever you merge something into master, it kind of blocks the next release until its (non-continuous) QA is done.

That's what tags are for, QA tests the tagged release, then that gets released. Master can continue changing up until the next tag, then QA has another thing to test.

Can I tag a bugfix that goes in after a feature was already merged into main? Basically out of order. Or do I need to tag the bugfix branch, in which case the main branch is no longer the release, so we need to ensure the bugfix ends up in the remote main branch as well as the release. Seems like it could cause further conflicts.
git doesn't care what order or from which branch you tag things in. If you need to hotfix a previous release you branch from that previous release's tag, make your bugfix and tag that bugfix then merge the whole thing back to main.

Presumably you are maintaining the ordering of these releases with your naming scheme for tags. For instance, using semver tags with your main release being v1.2.0 and your hotfix tag being v1.2.1, even while you've got features in flight for v1.3.0 or v1.4.0 or v2.0.0. Keeping track of the order of versions is part of semver's job.

Perhaps the distinction is that v1.2.0 and v1.2.1 are still separate releases. A bug fix is a different binary output (for compiled languages) and should have its own release tag. Even if you aren't using a compiled language but are using a lot of manual QA, different releases have different QA steps and tracking that with different version numbers is helpful there, too.

I'm not sure what you mean, what does "tag a bugfix", "tag the bugfix branch" or "ensure the bugfix ends up in the remote main branch as well as the release" even mean?

What are you trying to achieve here, or what's the crux? I'm not 100% sure, but it seems you're asking about how to apply a bug fix while QA is testing a tag, that you'd like to be a part of the eventual release, but not on top of other features? Or is about something else?

I think one misconception I can see already, is that tags don't belong to branches, they're on commits. If you have branch A and branch B, with branch B having one extra commit and that commit has tag A, once you merge branch B into branch A, the tag is still pointing to the same commit, and the tag has nothing to do with branches at all. Not that you'd use this workflow for QA/releases, but should at least get the point across.

It means you need a bugfix on your release and you don't want to carry in any other features that have been applied to master in the meantime.
In that case one can just branch off a stable-x.y branch from the respective X.Y release tag as needed.

It really depends on the whole development workflow, but in my experience it was always easier and less hassle to develop on the main/master branch and create stable release or fix branch as needed. With that one also prioritizes on fixing on master first and cherry-pick that fix then directly to the stable branch with potential adaptions relevant for the potential older code state there.

With branching of stable branches as needed the git history gets less messy and stays more linear, making it easier to follow and feels more like a "only pay for what you actually use" model.

"with potential adaptions relevant for the potential older code state there"

And there it is. Not "potential adaptations", they will be a 100% necessity for some applications. There are industries outside webdev where the ideals of semver ("we do NOT break userland", "we do NOT break existing customer workflows", https://xkcd.com/1172/) are strongly applied and cherry-picking backports is not a simple process. Especially with the pace of development that TBD/develop-on-main usually implies, the "potential older code state" is a matter of fact, and eliding the backport process into "just cherry-pick it" as you did is simply not viable.

Usually what I've seen is one of two solutions, the former (usually) being slightly favored: A) hide any new feature behind feature flags, separate "what's in the code" from "how the application works" essentially or B) have two branches, one for development (master) and one for production. The production branch is what QA and releasers work with, master is what developers work with, cherry-picking stuff and backporting becomes relatively trivial.
We've been using feature flags but mostly for controlling when things get released. But feature flags carry their own issues, they complicate the code, introduce parallel code paths, and if not maintained properly it gets difficult to introduce new features and have everything working together seamlessly. Usually you want to remove the flag soon after release, otherwise it festers. The production branch is also ok, but committing out of order can break references if commits are not in the same order as master, and patching something directly to prod can cause issues with promoting changes from master to prod, it requires some foresight to not break builds.
With (B) you've just reconstructed the part of git-flow that was questioned at the start of this thread. Just switch the two branches from master/production to develop/master.
The second one you described is basically GitFlow, just substitute "master branch" for "production branch" and "dev branch" for "master branch". I mean, you literally said "master is what developers work with", so why not call it the "development branch"?
B is basically Gitflow with different branch names - “one for development” is called develop, “one for production” is called main.
and Git Flow and similar day "that's what merges to main or for". GF and TDB really are way more similar than anyone wants to admit. It's basically "branch for release" vs "merge for release". There are benefits and downsides to both. IE: fully continuous and non-blocking QA/testing is non-trivial, and GF can help with keeping development on "the next thing" moving along without having the dreaded potential huge rebase looming overhead if QA comes back with lots of changes needed. Or just if some requirement changes come down from proect management.

For smaller projects with tests, something like TBD is great: easy to reason about, branches are free, tags are great. For bigger things with many teams working on overlapping features, keeping a TBD setup "flowing" (pun intended) can require a bit more fore-thought and planning. Release engineering, in other words. TBD vs GF is kind of just "do you want your release engineering at the beginning or at the end"?

I worked at a place that had Gitlab review apps set up. Where the QA people could just click a button and it would create an instance of the app with just that PR on it. Then they could test, approve, and kill the instance.

Then you can merge to master and it's immediately ready to go.

Yeah same. The idea that you'd be merging code to `main` that isn't ready to deploy is crazy to me, but that doesn't mean you need a `develop` and `prod` branch. The main + 1-layer of branches has generally been totally sufficient. We either deploy to branch-preview environment or we just test it locally.
What's the difference between what you describe, and continuously merging things into main and cutting releases from a branch called stable?
They're the same strategy with different branch names.
Are you using feature flags in your workflow pattern? These can be used to gate releases into your production environment while still allowing development work to be continuously integrated to trunk without blocking.

This also means that the release to prod happens post-integration by means of turning the feature flag on. Which is arguably a higher quality code review than pre-integration.

Yes, you have to include QA in the continuous integration process for it to work. That means at any time you can just tag the top of the master branch to cut a release, or do continuous delivery if it makes sense (so no tags at all).

It sounds like you are doing a monorepo type thing. Git does work best and was designed for multiple/independent repos.

Even in a monorepo you can tag releases independently in git. git doesn't proscribe any particular version tag naming scheme and stores tags similarly to refs in a folder structure that many (but not all) UIs pay attention to. You can tag `project-a/v1.2.0` and `project-b/v1.2.0` as different commits at different points in the repo as each project is independently versioned.

It makes using `git describe` a little bit more complicated, but not that much more complicated. You just need to `--match project-a/` or `--match project-b/` when you want `git describe` for a specific project.

That's true, but git also doesn't have tags that apply to a subset of the repository tree. You can easily check out `project-b/v1.2.0` and build project-a from that tree. Of course, the answer to that is "don't do that", but you still have the weird situation that the source control implementation doesn't match the release workflow; your `git describe` example is but one of the issues you will face fighting the source control system -- the same applies to `git log` and `git diff`, which will also happily give you information from all other projects that you're not interested in.

For me, the scope of a tag should match the scope of the release. That means that a monorepo is only useful if the entire source tree is built and released at the same time. If you're using a monorepo but then do partial releases from a subtree, you're using the wrong solution: different repo's with a common core dependency would better match that workflow. The common core can either be built separately and imported as a library, or imported as a git submodule. But that's still miles ahead of any solution that muddles the developers' daily git operations.

I understand the low level details of why tags don't work that way and why git leaves that "partial release" or "subtree release" as a higher level concept for whoever is making the tags in how they want to name them.

I know there are monorepo tools out there that do things like automate partial releases include building the git tag names and helping you you get release trees, logs, and diffs when you need them.

I think a lot of monorepo work is using more domain specific release management tools on top of just git.

Also, yeah, my personal preference is to avoid monorepos, but I know a lot of teams like them and so I try my best to at least know the tools to getting what I can out of monorepos.

Do you have any examples of tooling like that, providing the monorepo tiling on top of git's porcelain so to speak? I had assumed that most of such tooling is bespoke, internal to each company. But if there's generic tooling out there, then I agree, it's useful to know such.
That's absolutely an issue that a lot of it is bespoke and proprietary.

I found someone else's list of well known open source tools (in the middle of a big marketing page advertising monorepos as an ideal): https://monorepo.tools/#monorepo-tools

That list includes several I was aware and several I'd not yet heard of. It's the cross-over between monorepo management tool and build tool is. It's also interesting how many of the open source stacks are purely for or at least heavily specialized for Typescript monorepos.

I don't have any recommendations on which tools work well, just vaguely trying to keep up on the big names in case I need to learn one for a job, or choose one to better organize an existing repo.