{"id":5267,"date":"2026-08-30T12:50:38","date_gmt":"2026-08-30T03:50:38","guid":{"rendered":"https:\/\/donguri3.net\/server-tech\/github-main-branch-protection-3way-merge-2\/"},"modified":"2026-08-30T12:50:39","modified_gmt":"2026-08-30T03:50:39","slug":"github-main-branch-protection-3way-merge","status":"publish","type":"post","link":"https:\/\/donguri3.net\/en\/server-tech\/development-programming-git\/github-main-branch-protection-3way-merge\/","title":{"rendered":"Protecting the GitHub main Branch: Minimum Branch Protection Settings and 3-Way Merge Workflow"},"content":{"rendered":"<p>At Nando Kobo, we manage the Minecraft Paper plugin &#8220;twbridge&#8221; in the <br \/>GitHub repository nando256\/twbridge.<\/p>\n<p>Until now, our workflow has been quite, shall we say, &#8220;hardcore&#8221;?<\/p>\n<ul>\n<li>Committing directly to the main branch<\/li>\n<li>Pushing all fixes and bugs straight to main<\/li>\n<\/ul>\n<p>However, as the codebase and feature set have grown, we started running into situations like:<\/p>\n<ul>\n<li>&#8220;Wait, what was the purpose of this commit again?&#8221;<\/li>\n<li>&#8220;Ah, I accidentally pushed a slightly risky change straight to main&#8230;&#8221;<\/li>\n<li>&#8220;I want to revert to a past state, but the history is such a mess that it&#8217;s painful to look through.&#8221;<\/li>\n<\/ul>\n<p>This made us realize that just because it&#8217;s a solo project doesn&#8217;t mean we should neglect the main branch.<\/p>\n<p>Our goal this time is simple:<\/p>\n<blockquote>\n<p>Implement branch protection on GitHub to prevent force pushes to the main branch.<br \/>Since we&#8217;re doing it anyway, we want to try adopting a standard 3-way merge workflow.<\/p>\n<\/blockquote>\n<h2>Target Workflow: No Direct Pushes to main + A Casual 3-Way Merge<\/h2>\n<p>The key here isn&#8217;t to tie ourselves down with overly strict rules, but rather to:<\/p>\n<ul>\n<li>Disallow direct pushes to main<\/li>\n<li>Create a separate branch for any work and push that<\/li>\n<li>Open a Pull Request on GitHub and merge it<\/li>\n<li>Use GitHub&#8217;s standard 3-way merge (the Merge pull request button)<\/li>\n<\/ul>\n<p>In other words, it&#8217;s a pretty relaxed setup that still enforces just the bare minimum necessary.<\/p>\n<p>We aren&#8217;t going as far as setting up mandatory reviews, required CIs, or CODEOWNERS\u2014<br \/>none of that &#8220;heavy-duty production set&#8221; for now.<\/p>\n<h2>What&#8217;s Inconvenient About the Current Way<\/h2>\n<p>When you&#8217;re working alone and pushing directly to main, you run into issues like:<\/p>\n<ul>\n<li>Commit units tend to get messy\n<ul>\n<li>Commits like &#8220;saving for now&#8221; pile up<\/li>\n<\/ul>\n<\/li>\n<li>Bugs slip in unnoticed\n<ul>\n<li>You only realize &#8220;uh oh&#8221; after it&#8217;s already live on main<\/li>\n<\/ul>\n<\/li>\n<li>History is hard to read when you want to roll back\n<ul>\n<li>It&#8217;s hard to tell at a glance where new features were added versus where bugs were fixed<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>And above all,<\/p>\n<ul>\n<li>The psychological pressure of &#8220;I can break main at any second.&#8221;<\/li>\n<\/ul>\n<p>Since the thought of pushing experimental code straight to main started feeling a bit scary,<br \/>we wanted to draw a clear line: &#8220;Only things that have been properly checked get merged into main.&#8221;<\/p>\n<h2>Applying &#8220;Bare Minimum&#8221; Protection to main on GitHub<\/h2>\n<p>The settings we applied are truly minimal.<br \/>Steps:<\/p>\n<ol>\n<li>Open https:\/\/github.com\/nando256\/twbridge<\/li>\n<li>Click on the Settings tab at the top<\/li>\n<li>Click on Code and automation \u2192 Branches in the left menu<\/li>\n<li>In the &#8220;Branch protection rules&#8221; section,<br \/>click Add branch ruleset<br \/>\n<a href=\"https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/fa785f46e27f3f1063c45f76a6ef8ceb.jpeg\"><img decoding=\"async\" class=\"aligncenter size-large wp-image-2433\" src=\"https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/fa785f46e27f3f1063c45f76a6ef8ceb-1024x658.jpeg\" alt=\"\" width=\"1024\" height=\"658\" srcset=\"https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/fa785f46e27f3f1063c45f76a6ef8ceb-1024x658.jpeg 1024w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/fa785f46e27f3f1063c45f76a6ef8ceb-300x193.jpeg 300w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/fa785f46e27f3f1063c45f76a6ef8ceb-768x493.jpeg 768w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/fa785f46e27f3f1063c45f76a6ef8ceb-530x340.jpeg 530w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/fa785f46e27f3f1063c45f76a6ef8ceb-565x363.jpeg 565w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/fa785f46e27f3f1063c45f76a6ef8ceb-710x456.jpeg 710w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/fa785f46e27f3f1063c45f76a6ef8ceb-725x466.jpeg 725w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/fa785f46e27f3f1063c45f76a6ef8ceb.jpeg 1459w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/li>\n<li>Configure the settings as follows:\n<ol>\n<li>Ruleset Name<br \/>\n3way merge (name it whatever you like)<\/li>\n<li>Enforcement status<br \/>\nActive (enables the ruleset)<\/li>\n<li>Target branches<br \/>\nAdd target &gt; Include default branch<\/li>\n<li>Require a pull request before merging<br \/>\nCheck the box<\/li>\n<li>Leave everything else as is<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<p><a href=\"https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/a03dc48e4c71d66b2dd67ef1d4a4432d.jpeg\"><img decoding=\"async\" class=\"aligncenter size-large wp-image-2432\" src=\"https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/a03dc48e4c71d66b2dd67ef1d4a4432d-749x1024.jpeg\" alt=\"\" width=\"749\" height=\"1024\" srcset=\"https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/a03dc48e4c71d66b2dd67ef1d4a4432d-749x1024.jpeg 749w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/a03dc48e4c71d66b2dd67ef1d4a4432d-220x300.jpeg 220w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/a03dc48e4c71d66b2dd67ef1d4a4432d-768x1050.jpeg 768w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/a03dc48e4c71d66b2dd67ef1d4a4432d-1124x1536.jpeg 1124w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/a03dc48e4c71d66b2dd67ef1d4a4432d-388x530.jpeg 388w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/a03dc48e4c71d66b2dd67ef1d4a4432d-413x565.jpeg 413w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/a03dc48e4c71d66b2dd67ef1d4a4432d-520x710.jpeg 520w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/a03dc48e4c71d66b2dd67ef1d4a4432d-531x725.jpeg 531w, https:\/\/donguri3.net\/wp-content\/uploads\/2025\/12\/a03dc48e4c71d66b2dd67ef1d4a4432d.jpeg 1405w\" sizes=\"(max-width: 749px) 100vw, 749px\" \/><\/a>And that&#8217;s it.<br \/>We haven&#8217;t touched mandatory reviews, required status checks, or CI at all this time.<\/p>\n<h2>About 3-Way Merges<\/h2>\n<p>GitHub&#8217;s default &#8220;Merge pull request&#8221; button performs a 3-way merge (including a merge commit), so:<\/p>\n<ul>\n<li>Repository Settings \u2192 General \u2192 Pull Requests\n<ul>\n<li>If Allow merge commits is ON, you can use 3-way merges.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>You can leave the Squash and Rebase settings as they are according to your preference.<br \/>(Without changing anything, it will perform a 3-way merge using &#8220;Merge pull request&#8221; just like before.)<\/p>\n<h2>Workflow Overview<\/h2>\n<p>1. Cut a branch from main<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">git checkout main\ngit pull origin main\ngit checkout -b feature\/xxx<\/pre>\n<p>2. Make changes \u2192 Commit \u2192 Push to GitHub<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">git add .\ngit commit -m &quot;Something&quot;\ngit push origin feature\/xxx<\/pre>\n<p>3. Create a Pull Request on GitHub<\/p>\n<p>4. Review the changes yourself \u2192 Click &#8220;Merge pull request&#8221; (3-way merge)<\/p>\n<p>That&#8217;s all there is to it.<br \/>Without any CI or enforced reviews, this gives you a minimal setup consisting of &#8220;cannot push directly to main &amp; 3-way merge via pull request.&#8221;<\/p>\n<h2>If You Want to Revert<\/h2>\n<p>If you ever find yourself thinking:<\/p>\n<ul>\n<li>&#8220;I actually want to push directly to main again.&#8221;<\/li>\n<li>&#8220;I want to loosen up some of the rules.&#8221;<\/li>\n<\/ul>\n<p>You can easily revert it by going to the Rules &gt; Rulesets screen and setting it to Disable.<\/p>\n<h2>Summary<\/h2>\n<p>What we did is extremely simple:<\/p>\n<ul>\n<li>Configured branch protection for the default branch (main)\n<ul>\n<li>target branches: default branch<\/li>\n<li>bypass: none<\/li>\n<li>Require a pull request before merging: on<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>With just this,<\/p>\n<ul>\n<li>Direct pushes to main are blocked<\/li>\n<li>The workflow naturally follows the pattern of branching out, opening a pull request, and doing a 3-way merge<\/li>\n<li>Deletions of main and force pushes are also prevented<\/li>\n<\/ul>\n<p>This gives us a &#8220;minimal yet effective guardrail.&#8221;<\/p>\n<p>Even in solo development, treating main carelessly will come back to haunt you later.<br \/>Instead of just &#8220;trying to be careful,&#8221; physically preventing direct pushes to main makes it much easier to keep your history clean and experiment freely on branches.<\/p>\n<p>For now, I plan to operate with this lightweight setup and add things like &#8220;required reviews&#8221; or &#8220;mandatory CI&#8221; whenever the need arises.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>At Nando Kobo, we manage the Minecraft Paper plugin &#8220;twbridge&#8221; in the GitHub repository nando256\/t [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":2434,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_locale":"en_US","_original_post":"https:\/\/donguri3.net\/?p=2423","footnotes":""},"categories":[1172],"tags":[51,430,52,968,936,103,179,331],"class_list":["post-5267","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development-programming-git","tag-git","tag-github","tag-https","tag-minecraft","tag-twbridge","tag-103","tag-179","tag-331","en-US"],"_links":{"self":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5267","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/comments?post=5267"}],"version-history":[{"count":1,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5267\/revisions"}],"predecessor-version":[{"id":5270,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5267\/revisions\/5270"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/media\/2434"}],"wp:attachment":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/media?parent=5267"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/categories?post=5267"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/tags?post=5267"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}