GitHub merge queue is generally available
github.blog
github.blog
I scrolled down to the how does it work section where the first sentence is:
> Merge queue is designed for high-performance teams where multiple users regularly commit to a single branch
Half of the how does it work section is buzzwordy fluff.
* Normally, the big green button says "Merge pull request"
* Now, the big green button says "Merge when ready"
In a large project with lots of activity, a stampede of people pressing "Merge" at the same time will cause trouble. "Merge when ready" is supposed to solve this.
It seems to mean:
> "GH, please merge this, but take it slow. Re-run the tests a few extra times to be sure."
[1] https://docs.github.com/en/repositories/configuring-branches...
I’ve read docs several times and never found them very clear about the details.
> The result: your team can focus on the good stuff—write, submit, and commit. No tool sprawls here.
The good stuff? Tool sprawls? Is this written for teenagers?
> Merge queue is designed for high-performance teams where multiple users regularly commit to a single branch.
I think you meant "highly active". High performance means something else. But I can kind of see it emerging from your awful sales person brain.
Who, exactly, is it necessary for? The original commenter getting their rocks off on insulting someone else’s job? Others coming in and laughing at someone insulting someone else? Critically necessary.
It’s not like the original article author is going to come in, see this comment, and reflect deeply on themselves and their work.
So you sit next to them and therefore know what their assignment was and how well they executed on it?
There are many out there but I don't know about the "latest". GPT-4 itself says it has only a 10% chance of having been generated by a LLM.
These detectors are really unreliable. I've fed them content that I generated from GPT-4 and they never detect it as AI-generated.
I pity the students whose teachers will use them to detect plagiarism.
1. get a corpus of real text
2. generate a corpus of AI text
3. train a model until it can tell the difference
The problem is step 2 is semi-expensive and step 3 is really expensive, so everyone is trying to shortcut the process, and of course it doesn't work.
This is why we built merge queue. We’ve reduced the tension between branch stability and velocity. Merge queue takes care of making sure your pull request is compatible with other changes ahead of it and alerting you if something goes wrong. The result: your team can focus on the good stuff—write, submit, and commit. No tool sprawls here. This flow is still in the same place with the enablement of a modified merge button because GitHub remains your one-stop-shop for an integrated, enterprise-ready platform with the industry’s best collaboration tools.
Let's say you're an open source maintainer with 3 pending Pull Requests to merge: [1, 2, 3]. Each of which is based off `main`, has passed CI and has been approved.
If you merge all 3 at the same time, there is a chance to break the build: Your CI is testing `main <- 2`, but you're merging `main <- 1 <- 2`. A common example would be when (1) is a user-supplied change, and (2) is a dependency/localisation change, which don't cause merge conflicts but they do break the build/tests.
To do this safely, you need to re-run CI on (2) after merging (1), which is currently a manual process: you need to know that (2) is next to be merged, then rebase/pull + rerun CI for (2).
(There used to be a manual step of 'merge once CI is passed' here, GitHub has recently improved this workflow to allow automation)
Merge queues fully automate the safe approach: it merges (1), runs CI on (2) which fails, then runs CI on (3), which passes and gets merged.
This fixes that. It removes the race condition that exists because of the gap between testing a branch and merging it.
The solution is very simple - have a queue of PRs and automatically test & merge them one at a time.
There are some optimisations you can do to speed things up a bit, e.g. testing a bundle of PRs all at once, but that's the gist of it.
It is basically essential on any repo that has a high rate of PRs. I'm surprised so many people here haven't heard of it.
Gitlab has the same feature but they annoyingly called it something worse - merge trains, and it's only in Gitlab Premium.
Yes. The first useful line in the article is
"With GitHub’s merge queue, a temporary branch is created that contains"
and to reach there you have to skip fluff paragraphs halfway down the article.
As a very simple example, if your CI takes 10 minutes, your CI time budget is 6 merges per hour.
This is because if you merge two things in parallel without validating CI for the combined changes, your main branch could end up in a broken state.
Merge queues run CI for groups of PRs. If the group passes, all the PRs in the group land simultaneously. If it does not, the group is discarded while other group permutations are still running in parallel.
This way you can run more "sequential" CI validation runs than your CI time budget allows.
In our monorepo, we get a volume of 200-300 commits per day with CI SLO of 20 mins.
Without a queue, our best case scenario would be getting capped at ~72 commits per day before seeing regressions on main despite fully green CI (in real life, you'd see regressions a lot earlier though because throughput of PRs is spiky in nature)
That is a way of handling even higher volumes than GitHub is talking about, at the cost of a system that is a bit harder to think about. From the article:
With GitHub’s merge queue, a temporary branch is created that contains: the latest changes from the base branch, the changes from other pull requests already in the queue, and the changes from your pull request. CI then starts, with the expectation that all required status checks must pass before the branch (and the pull requests it represents) are merged.
Uber's[0] implementation, for example, does some more sophisticated speculation than just picking up whatever is sitting on the queue at the time.
Queues come with quirks, e.g. small PRs can get "blocked" behind a giant monorepo-wide codemod, for example. Naturally, one needs to consider the ROI of implementing techniques against aberrant cases vs their overall impact.
[0] https://www.uber.com/blog/research/keeping-master-green-at-s...
Beyond that, their API docs prior to the acquisition were some of the best in the industry, readable and concise. Now they are just a complicated mess.
Comms teams are really terrible in this regard. They insist on a singular 'voice', which means that every article is going to go through their review and get rewritten to their standard - that standard may involve removing technical content and instead making it more layman/ marketing friendly.
It's an incredible mistake that I see made everywhere after companies hit a certain size. It then becomes up to engineers to build their own engineering blog with less oversight and then guarding it from the comms teams, which most engineers aren't interested in doing.
The problem is that if you have multiple branches going into the same (mono)-repo, then they might all pass a localized CI-check, but fail if they are all merged. This is because the branches have an interaction between them. It can lead to a stall in commits and because everything hinges on the repo, work is going to stall as well.
So you serialize the branches, and impose an order on them: [x_1, x_2, x_3, ...]. Now, when running CI on one of these, x_j say, you do so in a temporary branch containing every branch x_i with i < j. This will avoid a stall up to branch x_j, if you started to merge the branches in order. If CI fails on branch x_j, you remove it from the list (queue) of branches to be merged and continue.
What I think it is: instead of you trying to merge into the main branch, you try to merge into a branch where all pull requests before you are already merged in.
That way any pull request before you can't cause any merge conflicts, because they are already taken into account.
At least that's what I deduct from all the marketing fluff. Maybe I'm completely wrong.
The problem is probably whoever wrote the blog post (who is likely not even the named author, depending on how their marketing team does things) tried to add a lot of high-level stuff to make it make sense to them without really needing to understand the details, and then dolled it up with a bunch of useless vapid quotes from customers and what not, because that is what marketing people think matters. Maybe it does make sense to have mealy-mouthed corporate speak for the overall product, since some executive is probably deciding whether to use GitHub as a whole and they might care if a big company uses it. I don't know that it makes much sense for specific features like this, especially in a fairly technical product like GitHub.
Merge queues address the problem of how to (1) merge in a lot of changes (2) while guaranteeing no breaking/conflicting changes are merged.
[1] https://docs.github.com/en/repositories/configuring-branches...
This explanation is actually a lot better
GitHub creates a temporary merge branch with the commit where you can run tests before the merge. So in the end it’s just one commit.
I'm a bit sad that the merge queue is not available for personal accounts. I was hoping have it replace Bors, which has been deprecated since May 1st, 2023.
I do agree MQ being only for Orgs is really really annoying.
That doesn’t sound very “Generally Available” to me, I really wish they’d stop this confusing feature differentiation. Just give everyone everything.
Why can’t I use merge queue or my personal repos. It’d be very useful for merging a bunch of dependabot PRs.
Though FWIW, you have to have a fairly large scale for merge queues to be relevant in practice.
I would love to be able to set different sets of checks that need to pass to add a PR to a merge group, and to the merge group itself to be merged, so we can better manage speed and cost.
The docs say there's a separate event for merge_group which I assume means you can configure different checks.
https://docs.github.com/en/repositories/configuring-branches...
In a GitHub Action, this is was very easy:
on:
pull_request:
merge_group:
jobs:
build:
name: Whatever...
if: github.event_name == 'merge_group'
Then the task "Whatever" can be added as a required status check, and "skipped" is good enough to allow it to function on the PR side while it actually executes on the merge queue.Actually getting to that point can be quite hard. But it's definitely possible. But also, if there's any change then you want to re-run anything that depends on the change. Limiting the rebuild to only things that have changed also take effort. But it's really worth it for the benefits you reap, both in CI and in development -- if your edit-compile-test cycle is long enough that an extra CI run is annoying, it's long enough to be detrimental in every day development.
Also, I don't think emojis belong in articles like this one. Maybe I'm getting old, but I don't like it.
Don't be ridiculous... Copilot did it.
That said, I can't imagine being in a position to need this. Right now we do 3-4 merges a day and it's really comfortable. If we got to 10 merges a day, I'd start asking why we are changing so much damn code in the same space/time. I know there are really good use cases for this tooling though.
It’s most important if the test suite is pretty slow though (e.g. minutes) as that makes it more likely you’ll get push races.
In this situation, we usually had a significant chance of waiting for another commit to go through. We also had a system that's similar to what Github offers to manage queued rebases. This was amplified by even the sanity test being about 30 minutes long and the requirement that each commit be tested (so, if you try to rebase a branch with 10 commits, you'd have to wait 5 hours for it to go through). However, such cases would be very rare and anyone who needed to rebase such a branch would typically send an email asking for a particular day to get their changes in. To my memory, there were two instances of something like this happening.
From what I could gather from the article and the comments, the system worked differently though. First of all, ours could try to rebase in parallel, so that if one rebase fails the test, another set of changes could be incorporated immediately. However, no attempt was made to see if the change set being submitted agrees with the changes in the pipeline. I think we might have tried that once, but later ruled this to be both confusing and in most cases catching very few issues anyways (it is in general rare that two developers work on the same exact files). The confusion part comes from detecting conflicts with the branch that doesn't get rebased due to the failed test, and then the developer who's notified about a conflict is left wondering about the real reason, because, usually, by the time they get to investigate it, the failed branch had been already changed by the branch owner.
I also worked in larger companies, but usually they try to split the repository along administrative boundaries, and the number of people pushing to the same repository at the same time is not so big. They pay for it by having to invest a lot into release management, version synchronization across multiple teams, much more integration testing, worse understanding of the product by individual developers / teams, and, in general, lower quality of the product. But, off-the-shelf VCSs don't allow for sharing large repositories easily, and, of course, the problem this merge queue is trying to address would grow more severe with the size.
Of course not since it’s not a problem it can solve.
The problem it tries to solve is having to rebase / merge by hand, racing for CI, and the risk of going “fuck it” and merging a change with does not conflict but is semantically incompatible with a previous change.
Most of the time your colleagues’ changes and yours don’t interfere, but on repos with lots of traffic losing the CI race and having to rebase, wait for CI again, rinse and repeat, gets old quick, when the test suite takes more than a few seconds.
https://github.com/orgs/community/discussions/36568
(This was an issue for us during the beta)
Source: https://docs.github.com/en/repositories/configuring-branches...
Merge when ready: PR with tests passing on a temporary branch (which includes all the PRs already queued up) gets merged when everything already queued up is merged.
It prevents merging in a situation where branch A passes tests, branch B passes tests, but main+A+B would break.
I really wish Microsoft would just pull the trigger on Old Yeller at this point, it's almost worse watching it suffer so much.
Regardless of the merge strategy (merge vs rebase), you will face the same problem with high-volume development relative to CI times.
Merge vs rebase just affects how it appears in Git, not anything about breakage guarantees.
In any case, many teams and groups prefer reducing the number of merges to zero, if at all possible, and rebasing all final commits on top of trunk, to make the history shorter and more manageable. It isn't really that unusual at all these days. I've been working this way for years. But merge queues/merge trains are more a matter of team size/velocity/commit rate than it is anything to do with the merge algorithm, ultimately.
For example, say A is a patch that renames the function foo() to foobar(), and then B is a patch that calls the function foo() at a completely new callsite. The original repository is green, and both A and B are individually green too. You merge A into main, then merge B without rebasing or re-merging main, and the build is broken. Each of these changes passed the tests in isolation, but together they will result in a repository that is broken. This has nothing to do with whether the codebase is a mess or not; it's just a simple rename of a function. B and A are simply mutually exclusive, and most be ordered concretely between each other.
To fix this case manually, you have to merge A to main, then rebase B onto main (or merge main into B), which would then result in "function foo() not found", or your tests failing or build exploding. Now that the CI has caught it, you can change B to use the new function foobar() and re-attempt a merge again. Except you also have patches C-through-Z written by 5 other developers that might also conflict. This kind of example is everywhere in a large codebase; imagine that you're renaming files, changing parameter types to a function, reworking test output from debug statements, etc. And now imagine every CI run is 15 minutes long. If anyone merges in that 15 minutes before you, you have to start all over again. This also applies to all 5 developers of all other 20+ patches, too.
The long and short is that there are a lot of cases where, to be safe, you need to just rebase your change on top of the latest tip first, before you can be 100% sure the build passes. Codebase cleanliness has nothing to do with it; that's just too pessimistic.
The Merge Queue solves it a different way. Just queue up A and queue up B to be merged in series. Actually, the order doesn't matter at all. Let's say B is up first, then A, then a new patch C that is totally unrelated. The build passes with B applied, because it worked originally so it gets merged. Next up is A. A now fails, because even though it applied the patch successfully, there's a new call to the old function named foo(). So it gets kicked out. Now C is up immediately after. It succeeds, so it gets merged. At this point, the author of A is now responsible for rebasing their change and fixing the build. At no point did the author of either B or C have to be responsible for re-merging or rebasing their changes on top of main, as they triggered the happy case.
The best way I can describe merge queue versus manually rebasing is this: the merge queue is optimistic locking, while manual rebasing is pessimistic locking. The time-to-merge a change is the latency. In an optimistic lock strategy, you always try to do the thing, but just detect if it fails and safely abort. The pessimistic case requires strict serialization of the operations to ensure no conflicts, but it needlessly holds up many concurrent writers. It has nothing to do with "messiness" of the data structures, to use an optimistic lock; you might just have a really writer-heavy system on your hands! If we keep putting it in latency/locking terms, this results in a much better "p90 time-to-merge latency", in other words.
In a large team of developers, working on a big codebase, the "optimistic locking" approach of the merge queue is very effective at getting PRs merged faster, and has very few downsides.
Why are you allowing commits to land that break interfaces?
If this is a public function it should go through a deprecation cycle.
If this is a private function then it shouldn't be accessed from outside its module/class/whatever in the first place. If it is, then you've got the "messy codebase" I referred to in my original comment.
I'm not sure if you're being intentionally obtuse here, it's a very simple scenario that has nothing to do with public interfaces or deprecation cycles.
It does, but you're now looking at an edge case that rarely arises in practice in a well-maintained codebase, and certainly not one to design your entire branching/merging/CI strategy around.
If your codebase is a spaghettified nonsense then the problem arises much more often, and so I can understand the use of merge trains/whatnot.
In this case, we often have conflicts in private modules. (And everything is private since we don’t provide any libraries to anyone.)