{"id":1544,"date":"2023-06-07T18:27:42","date_gmt":"2023-06-07T18:27:42","guid":{"rendered":"https:\/\/www.aviator.co\/blog\/?p=1544"},"modified":"2025-10-03T04:52:35","modified_gmt":"2025-10-03T04:52:35","slug":"merge-strategies-at-scale","status":"publish","type":"post","link":"https:\/\/www.aviator.co\/blog\/merge-strategies-at-scale\/","title":{"rendered":"How to Master Git Merging Strategies in Large Monorepos"},"content":{"rendered":"\n<figure class=\"wp-block-image aligncenter size-full\"><img fetchpriority=\"high\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2023\/06\/merge-strategy-banner.jpg\" alt=\"\" class=\"wp-image-4919\" srcset=\"https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2023\/06\/merge-strategy-banner.jpg 1024w, https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2023\/06\/merge-strategy-banner-300x169.jpg 300w, https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2023\/06\/merge-strategy-banner-768x432.jpg 768w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>How hard can merging be? There is a big green button you press and your changes are merged. That\u2019s true for small engineering teams or those working in polyrepo setups. But in a large\u00a0<a href=\"https:\/\/www.aviator.co\/blog\/what-is-a-monorepo-and-why-use-one\/\" target=\"_blank\" rel=\"noopener\" title=\"\">monorepo<\/a>\u00a0shared by dozens or hundreds of engineers, merging becomes one of the biggest bottlenecks in the software delivery pipeline.<\/p>\n\n\n\n<p>Without going deep into a&nbsp;<a href=\"https:\/\/www.aviator.co\/blog\/monorepo-vs-polyrepo\/?utm_source=tns&amp;utm_medium=content&amp;utm_campaign=q2-2025-tns-article-8-monorepo-blog&amp;utm_term=net-new&amp;utm_content=awareness\" target=\"_blank\" rel=\"noopener\" title=\"\">monorepo vs. polyrepo debate<\/a>, let\u2019s just say that monorepos have both benefits and challenges. On the benefits side, vulnerabilities are easily identified and fixed everywhere, easier refactoring, especially cross-project, standardization of tools and sharing the same libraries across different projects.<\/p>\n\n\n\n<p>On the challenges side, there are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Stale dependencies: Pull requests (PRs) based on older mainline branches<\/li>\n\n\n\n<li>Implicit conflicts due to\u00a0<a href=\"https:\/\/www.aviator.co\/blog\/how-not-to-do-code-reviews\/\" target=\"_blank\" rel=\"noopener\" title=\"\">developers working on a similar code base<\/a><\/li>\n\n\n\n<li>Infrastructure issues (timeouts, etc.)<\/li>\n\n\n\n<li>Internal and third-party dependencies.<\/li>\n\n\n\n<li>Flaky tests: Shared state and inconsistent test behavior<\/li>\n<\/ul>\n\n\n\n<p>And, as the engineering organization grows, the challenges increase exponentially. They may end up wasting hours of developers\u2019 time just waiting for the builds to become green.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\" https:\/\/docs.aviator.co\/mergequeue\" target=\"_blank\" rel=\" noreferrer noopener\"><img decoding=\"async\" width=\"1024\" height=\"264\" src=\"https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2024\/04\/aviator-mergequeue-blog-documentation-cta-photo-min-1-1024x264.png\" alt=\"MergeQueue CTA\" class=\"wp-image-4921\" srcset=\"https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2024\/04\/aviator-mergequeue-blog-documentation-cta-photo-min-1-1024x264.png 1024w, https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2024\/04\/aviator-mergequeue-blog-documentation-cta-photo-min-1-300x77.png 300w, https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2024\/04\/aviator-mergequeue-blog-documentation-cta-photo-min-1-768x198.png 768w, https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2024\/04\/aviator-mergequeue-blog-documentation-cta-photo-min-1-1536x396.png 1536w, https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2024\/04\/aviator-mergequeue-blog-documentation-cta-photo-min-1.png 1940w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Merge Queues to the Rescue<\/strong><\/h2>\n\n\n\n<p>Merge automation tools like GitHub Merge Queues,&nbsp;<a href=\"https:\/\/about.gitlab.com\/?utm_content=inline+mention\" target=\"_blank\" rel=\"noreferrer noopener\">GitLab<\/a>&nbsp;Merge Trains and&nbsp;<a href=\"https:\/\/www.aviator.co\/merge-queue?utm_source=tns&amp;utm_medium=content&amp;utm_campaign=q2-2025-tns-article-8-merge-queue&amp;utm_term=net-new&amp;utm_content=awareness\" target=\"_blank\" rel=\"noopener\" title=\"\">Aviator Merge Queue<\/a>&nbsp;change the game by introducing automation gates before merging to mainline.<a href=\"https:\/\/www.aviator.co\/?utm_content=sponsor+module\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>How they work:<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A developer marks a PR as ready.<\/li>\n\n\n\n<li>The system rebases this PR onto the latest mainline.<\/li>\n\n\n\n<li><a href=\"https:\/\/www.aviator.co\/blog\/what-is-ci-cd\/\" target=\"_blank\" rel=\"noopener\" title=\"\">CI runs<\/a>\u00a0in this updated context.<\/li>\n\n\n\n<li>If CI passes, the system merges it.<\/li>\n\n\n\n<li>If new PRs arrive while CI runs, they wait for their turn.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image aligncenter\"><a class=\"pswp-trigger no-caption\" href=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/b67d4396-visual1-merge-queue.png\"><img decoding=\"async\" src=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/b67d4396-visual1-merge-queue.png\" alt=\"\" class=\"wp-image-22797701\"\/><\/a><\/figure>\n\n\n\n<p>How much time does that take?<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter is-resized\"><a class=\"pswp-trigger no-caption\" href=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/3f9e2379-visual2-merge-queue_performance.png\"><img decoding=\"async\" src=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/3f9e2379-visual2-merge-queue_performance.png\" alt=\"\" class=\"wp-image-22797702\" style=\"width:750px;height:auto\"\/><\/a><\/figure>\n\n\n\n<p>Since we don\u2019t have 50 hours in a day, let\u2019s explore ways to optimize this process.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Batching PRs<\/strong><\/h3>\n\n\n\n<p>Instead of merging one PR at a time, several of them are&nbsp;<a href=\"https:\/\/docs.aviator.co\/mergequeue\/concepts\/batching\" target=\"_blank\" rel=\"noopener\" title=\"\">batched<\/a>&nbsp;before running the continuous integration (CI) process. This merging strategy reduces the number of CI runs and shortens the waiting time for merges.<\/p>\n\n\n\n<p>If the CI process for the batch succeeds, all pull requests in the batch are merged simultaneously, assuming that each ultimately passes the build. In case of a failure, the failing batches are bisected to identify which PR is causing the failure, and the rest are merged.<\/p>\n\n\n\n<p>This method introduces some delays. To illustrate, under ideal conditions with no failures and a batch size of four, the total merge time decreases dramatically: from approximately 50 hours to about 12.5 hours.<\/p>\n\n\n\n<p>In practical scenarios, failures occur. For instance, with a 10% failure rate, overall merge time can increase considerably, potentially extending to 24 hours for all pull requests to merge. Additionally, the total number of CI runs rises due to the repeated processing required to resolve failures.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter\"><a class=\"pswp-trigger no-caption\" href=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/b8f69fb5-visual3-batchedprs-performance.jpg\"><img decoding=\"async\" src=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/b8f69fb5-visual3-batchedprs-performance.jpg\" alt=\"\" class=\"wp-image-22797703\"\/><\/a><\/figure>\n\n\n\n<p>Can we optimize the process further?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Optimistic Queues<\/strong><\/h3>\n\n\n\n<p>Instead of thinking of merges happening in a serial world, let\u2019s think of them as&nbsp;<a href=\"https:\/\/docs.aviator.co\/mergequeue\/concepts\/parallel-mode\" target=\"_blank\" rel=\"noopener\" title=\"\">parallel universes.<\/a>&nbsp;We don\u2019t see mainline as a linear path, but as several potential futures that it can represent.<\/p>\n\n\n\n<p>Rather than waiting for the initial CI process to complete, the system can optimistically assume the first pull request will pass. Based on this assumption, an alternate mainline branch is created to begin the CI process for the second pull request immediately. Once the CI for the first pull request completes successfully, it is merged into the mainline branch. Similarly, when the CI for the second pull request passes, it is also merged.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter is-resized\"><a class=\"pswp-trigger no-caption\" href=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/66a9bd7f-visual4-optimistic_queues.jpg\"><img decoding=\"async\" src=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/66a9bd7f-visual4-optimistic_queues.jpg\" alt=\"\" class=\"wp-image-22797704\" style=\"object-fit:cover;width:750px;height:auto\"\/><\/a><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"1536\" height=\"783\" src=\"https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2023\/06\/316b9bed-visual4_optimistic-queues-1.gif\" alt=\"\" class=\"wp-image-4986\"\/><\/figure>\n\n\n\n<p>If the CI for the first one fails, the system will reject this alternate mainline and create a new alternate mainline without the first PR, run the validation and follow the same steps.<\/p>\n\n\n\n<p>Let\u2019s look at the numbers with optimistic queues:<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter\"><a class=\"pswp-trigger no-caption\" href=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/5d2ab6fa-visual5-optimistic-queue_performance.jpg\"><img decoding=\"async\" src=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/5d2ab6fa-visual5-optimistic-queue_performance.jpg\" alt=\"\" class=\"wp-image-22797705\"\/><\/a><\/figure>\n\n\n\n<p>Can we do better still?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Optimistic Batching<\/strong><\/h3>\n\n\n\n<p>We\u2019re combining the merging strategy of optimistic queues with batching, running a CI for batches of PRs and as they pass, they get merged. If they fail, they\u2019re split up to identify what caused the failure.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter is-resized\"><a class=\"pswp-trigger no-caption\" href=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/4aa75d6c-visual6-batching-performance-review.png\"><img decoding=\"async\" src=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/4aa75d6c-visual6-batching-performance-review.png\" alt=\"\" class=\"wp-image-22797706\" style=\"object-fit:cover;width:750px;height:auto\"\/><\/a><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Predictive Modeling<\/strong><\/h3>\n\n\n\n<p>What if we could predict which PRs are likely to fail?<\/p>\n\n\n\n<p>Instead of running CI on all possible combinations and scenarios for all of the PRs, we use predictive modeling. By calculating a score \u2014 based on lines of code and PR, types of files being modified, tests added or removed in a particular PR or a number of dependencies \u2014 we identify which paths are worth pursuing. This reduces CI cost but makes sure that the changes can be merged quickly.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter\"><a class=\"pswp-trigger no-caption\" href=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/b41fb4b0-visual7_predictive_modeling.jpg\"><img decoding=\"async\" src=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/b41fb4b0-visual7_predictive_modeling.jpg\" alt=\"\" class=\"wp-image-22797707\"\/><\/a><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Multi-Queues and Affected Targets<\/strong><\/h3>\n\n\n\n<p>Instead of thinking of the process as a single queue, we can treat it as multiple independent paths or disjoint queues that run in parallel. This idea is based on the concept of&nbsp;<a href=\"https:\/\/docs.aviator.co\/mergequeue\/concepts\/affected-targets\" target=\"_blank\" rel=\"noopener\" title=\"\">affected targets.&nbsp;<\/a><\/p>\n\n\n\n<p>Modern monorepo build tools like Bazel, Nx or Turborepo can identify which parts of the build are affected by a specific change. Using this information, we can group pull requests into separate queues, allowing builds that don\u2019t affect each other to run concurrently.<\/p>\n\n\n\n<p>Imagine a system that produces four different build types \u2014 A, B, C and D \u2014 and pull requests come in sequentially. Each PR might only involve some of these build types, so rather than waiting for one queue to process everything, we create separate queues for A, B, C and D and run them independently. This way, unrelated builds don\u2019t block each other, speeding up the overall process.<\/p>\n\n\n\n<h3 class=\"wp-block-heading has-text-align-center\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/cdn.thenewstack.io\/media\/2025\/08\/988107f6-visual8_affected-targets.jpeg\" alt=\"\" width=\"578\" height=\"640\"><\/h3>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>More Merging Strategies to Optimize Workflows<\/strong><\/h3>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Reordering Changes<\/strong><\/h4>\n\n\n\n<p>Prioritizing pull requests with a lower risk of failure or deemed high priority by placing them earlier in the queue minimizes the likelihood of cascading failures and reduces the risk of a chain reaction of failures. Larger or more complex changes, which carry higher uncertainty, are scheduled later in the sequence.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Fail Fast<\/strong><\/h4>\n\n\n\n<p>Reordering test execution prioritizes the tests most likely to fail to run first so failures can be detected early in the process. This ensures that problematic changes are identified and addressed quickly, reducing the number of possible failures.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Splitting Test Execution<\/strong><\/h2>\n\n\n\n<p>Running a set of fast, critical tests before merging helps catch common issues early. More extensive or slower tests, such as smoke or integration tests, are run after the merge. These post-merge tests are expected to fail very rarely, and in such cases, an automated rollback mechanism can mitigate the risk.<\/p>\n\n\n\n<p>Ultimately, the goal is to balance reliability with velocity, enabling teams to ship faster without compromising quality.&nbsp;<a href=\"https:\/\/www.aviator.co\/merge-queue?utm_source=tns&amp;utm_medium=content&amp;utm_campaign=q2-2025-tns-article-8-merge-queue&amp;utm_term=net-new&amp;utm_content=awareness\" target=\"_blank\" rel=\"noopener\" title=\"\">Merge automation<\/a>&nbsp;doesn\u2019t just save CI cycles, it saves developer time, release velocity, and engineering sanity.<\/p>\n\n\n\n<p>At\u00a0<a href=\"https:\/\/www.aviator.co\/?utm_source=tns&amp;utm_medium=content&amp;utm_campaign=q2-2025-tns-article-8-home&amp;utm_term=net-new&amp;utm_content=awareness\" target=\"_blank\" rel=\"noopener\" title=\"\">Aviator<\/a>, we care about developer experience and developer productivity. But we\u2019re taking the\u00a0<a href=\"https:\/\/www.aviator.co\/blog\/the-anti-developer-productivity-metrics\/\" target=\"_blank\" rel=\"noopener\" title=\"\">anti-metrics approach to productivity.\u00a0<\/a>Instead of building shiny dashboards, we are building\u00a0<a href=\"https:\/\/www.aviator.co\/?utm_source=tns&amp;utm_medium=content&amp;utm_campaign=q2-2025-tns-article-8-home&amp;utm_term=net-new&amp;utm_content=awareness\" target=\"_blank\" rel=\"noopener\" title=\"\">automated workflows<\/a>\u00a0across the entire software development life cycle: development, code reviews, builds, tests and deployments.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">FAQs<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">What Is A 3 Way Merge Strategy?<\/h3>\n\n\n\n<p>A 3-way merge strategy in Git uses three snapshots: the common ancestor, the source branch, and the target branch. Git compares these versions to automatically merge changes, only asking for manual input when conflicts occur.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Which Merge Strategy Is Best?<\/h3>\n\n\n\n<p>The best Git merge strategy depends on your project. For small, simple updates, a fast-forward merge is clean and efficient. For collaborative projects or monorepos, a 3-way merge or squash merge is usually preferred to preserve history and maintain stability.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What Are The Different Types Of Merges In Git?<\/h3>\n\n\n\n<p>Git supports several merge strategies: fast-forward, 3-way merge, squash merge, rebase and merge, and octopus merges (for multiple branches). Each has trade-offs in terms of history clarity, conflict handling, and collaboration flow.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How To Git Merge Correctly?<\/h3>\n\n\n\n<p>To merge correctly, always pull the latest changes, test your branch locally, and resolve conflicts carefully. Use meaningful commit messages and choose a merge strategy that balances clean history with build stability.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Merge automation doesn\u2019t just save CI cycles, it saves developer time, release velocity and engineering sanity.<\/p>\n","protected":false},"author":18,"featured_media":4919,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[78],"tags":[],"class_list":["post-1544","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-monorepo"],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":6}},"acf":[],"aioseo_notices":[],"jetpack_featured_media_url":"https:\/\/www.aviator.co\/blog\/wp-content\/uploads\/2023\/06\/merge-strategy-banner.jpg","post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/www.aviator.co\/blog\/wp-json\/wp\/v2\/posts\/1544","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.aviator.co\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.aviator.co\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.aviator.co\/blog\/wp-json\/wp\/v2\/users\/18"}],"replies":[{"embeddable":true,"href":"https:\/\/www.aviator.co\/blog\/wp-json\/wp\/v2\/comments?post=1544"}],"version-history":[{"count":12,"href":"https:\/\/www.aviator.co\/blog\/wp-json\/wp\/v2\/posts\/1544\/revisions"}],"predecessor-version":[{"id":5005,"href":"https:\/\/www.aviator.co\/blog\/wp-json\/wp\/v2\/posts\/1544\/revisions\/5005"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.aviator.co\/blog\/wp-json\/wp\/v2\/media\/4919"}],"wp:attachment":[{"href":"https:\/\/www.aviator.co\/blog\/wp-json\/wp\/v2\/media?parent=1544"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.aviator.co\/blog\/wp-json\/wp\/v2\/categories?post=1544"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.aviator.co\/blog\/wp-json\/wp\/v2\/tags?post=1544"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}