{"id":291,"date":"2026-11-04T14:55:23","date_gmt":"2026-11-04T14:55:23","guid":{"rendered":"https:\/\/devdojo.co.in\/?p=291"},"modified":"2026-11-04T14:55:23","modified_gmt":"2026-11-04T14:55:23","slug":"devops-series-part-3-git-trunk-based-development","status":"publish","type":"post","link":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/","title":{"rendered":"DevOps CI\/CD Part 3: Git for Teams: Branches, Pull Requests and Trunk-Based Development"},"content":{"rendered":"\n<div class=\"dd-post\">\n<style>\n.single article:has(.dd-hero) .entry-media{display:none}\n.dd-post{--navy:#0b1020;--blue:#3178c6;--violet:#7c5cff;--ink:#1f2433;--muted:#5b6275;--line:#e3e6ef;font-size:17px;line-height:1.75;color:var(--ink)}\n.dd-post h2{margin:2.2em 0 .6em;font-size:1.6em;line-height:1.3;scroll-margin-top:90px}\n.dd-post h3{margin:1.6em 0 .5em;font-size:1.22em;scroll-margin-top:90px}\n.dd-post a{color:var(--blue);text-decoration:underline;text-underline-offset:3px}\n.dd-post pre{background:#0f1430;color:#e6e9f5;padding:18px 20px;border-radius:12px;overflow-x:auto;line-height:1.6;border:1px solid #232a52;margin:1.2em 0;font-size:14.5px !important}\n.dd-post pre code{font-size:1em !important;background:none;color:inherit;padding:0;font-family:\"JetBrains Mono\",\"Fira Code\",Consolas,Menlo,monospace;white-space:pre}\n.dd-post :not(pre)>code{background:#eef1fb;color:#2b3a8f;padding:2px 6px;border-radius:5px;font-size:.9em}\n.dd-post .dd-file{display:inline-block;background:#151b3b;color:#c9d1ff;font:600 13px\/1 \"JetBrains Mono\",Consolas,monospace;padding:7px 12px;border-radius:8px 8px 0 0;margin:1em 0 -1.2em;position:relative}\n.dd-post .dd-hero{margin:0 0 1.6em;border-radius:16px;overflow:hidden;box-shadow:0 10px 30px rgba(11,16,32,.25)}\n.dd-post .dd-hero svg{display:block;width:100%;height:auto}\n.dd-post .dd-hero .node{transition:transform .25s,filter .25s;transform-box:fill-box;transform-origin:center;cursor:pointer}\n.dd-post .dd-hero .node:hover{transform:scale(1.08);filter:drop-shadow(0 0 10px #7c5cff)}\n.dd-post .dd-series{display:inline-flex;align-items:center;gap:8px;background:#151b3b;color:#dfe3ff;border-radius:999px;padding:6px 14px;font-size:.88em;font-weight:600;margin:0 0 .8em}\n.dd-post .dd-series span{background:var(--violet);color:#fff;border-radius:999px;padding:2px 10px}\n.dd-post .dd-prereq{font-size:.96em;color:var(--muted);border-left:3px solid var(--line);padding-left:12px;margin:.6em 0 1.2em}\n.dd-post .dd-summary{background:linear-gradient(135deg,#eef3ff,#f4efff);border:1px solid #d6dcf5;border-left:5px solid var(--violet);border-radius:12px;padding:18px 22px;margin:1.4em 0}\n.dd-post .dd-summary strong.lbl{display:block;font-size:1.05em;margin-bottom:6px;color:#3a2fa0}\n.dd-post .dd-summary ul{margin:.3em 0 0 1.1em}\n.dd-post .dd-toc{background:#f8f9fc;border:1px solid var(--line);border-radius:12px;padding:16px 22px;margin:1.4em 0}\n.dd-post .dd-toc ol{margin:.4em 0 0 1.2em;columns:2;column-gap:28px}\n.dd-post .dd-toc li{break-inside:avoid;margin:.15em 0}\n@media(max-width:640px){.dd-post .dd-toc ol{columns:1}}\n.dd-post .dd-tip,.dd-post .dd-note,.dd-post .dd-warn{border-radius:10px;padding:14px 18px;margin:1.3em 0}\n.dd-post .dd-tip{background:#ecfbf3;border-left:4px solid #1f9d61}\n.dd-post .dd-note{background:#eef4ff;border-left:4px solid var(--blue)}\n.dd-post .dd-warn{background:#fff6e8;border-left:4px solid #e08a00}\n.dd-post .dd-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:14px;margin:1.3em 0}\n.dd-post .dd-card{border:1px solid var(--line);border-radius:12px;padding:14px 16px;background:#fff;box-shadow:0 2px 8px rgba(20,30,70,.05)}\n.dd-post .dd-card b{display:block;color:#3a2fa0;margin-bottom:4px}\n.dd-post table{width:100%;border-collapse:collapse;margin:1.3em 0;font-size:.95em}\n.dd-post th,.dd-post td{border:1px solid var(--line);padding:10px 12px;text-align:left;vertical-align:top}\n.dd-post th{background:#151b3b;color:#fff}\n.dd-post tr:nth-child(even) td{background:#f8f9fc}\n.dd-post .dd-tablewrap{overflow-x:auto}\n.dd-post figure{margin:1.6em 0;text-align:center}\n.dd-post figure svg{width:100%;height:auto;border-radius:12px}\n.dd-post figcaption{font-size:.9em;color:var(--muted);margin-top:8px}\n.dd-post details{border:1px solid var(--line);border-radius:10px;padding:12px 16px;margin:.7em 0;background:#fff}\n.dd-post details summary{cursor:pointer;font-weight:600}\n.dd-post details[open] summary{margin-bottom:8px;color:#3a2fa0}\n.dd-post .dd-roadmap{border:2px solid #d6dcf5;background:#fbfbff}\n.dd-post .dd-roadmap h4{margin:1em 0 .3em;color:#151b3b;font-size:1em;text-transform:uppercase;letter-spacing:.04em}\n.dd-post .dd-roadmap ol{margin:.2em 0 .6em 1.4em;padding:0}\n.dd-post .dd-roadmap li{margin:.2em 0}\n.dd-post .dd-roadmap .here{background:var(--violet);color:#fff;border-radius:999px;padding:1px 9px;font-size:.8em;font-weight:700;margin-left:6px}\n.dd-post .dd-roadmap .soon{color:#8a90a6;font-size:.82em;margin-left:6px}\n.dd-post .dd-tabs{border:1px solid var(--line);border-radius:12px;padding:6px 16px 16px;margin:1.4em 0;background:#fbfbfe}\n.dd-post .dd-tabs>input{position:absolute;opacity:0;pointer-events:none}\n.dd-post .dd-tabs .labels label{display:inline-block;padding:8px 16px;margin:10px 6px 0 0;border-radius:999px;border:1px solid #cfd5ea;cursor:pointer;font-weight:600;font-size:.95em;background:#fff;transition:background .2s}\n.dd-post .dd-tabs .labels label:hover{border-color:var(--violet)}\n.dd-post .dd-tabs .panel{display:none;margin-top:12px}\n.dd-post .dd-tabs #ti1:checked~.labels label[for=ti1],.dd-post .dd-tabs #ti2:checked~.labels label[for=ti2],.dd-post .dd-tabs #ti3:checked~.labels label[for=ti3],.dd-post .dd-tabs #ti4:checked~.labels label[for=ti4]{background:var(--violet);color:#fff;border-color:var(--violet)}\n.dd-post .dd-tabs #ti1:checked~.p1,.dd-post .dd-tabs #ti2:checked~.p2,.dd-post .dd-tabs #ti3:checked~.p3,.dd-post .dd-tabs #ti4:checked~.p4{display:block}\n.dd-post .dd-tabs #ti1:focus-visible~.labels label[for=ti1],.dd-post .dd-tabs #ti2:focus-visible~.labels label[for=ti2],.dd-post .dd-tabs #ti3:focus-visible~.labels label[for=ti3],.dd-post .dd-tabs #ti4:focus-visible~.labels label[for=ti4]{outline:2px solid var(--blue);outline-offset:2px}\n.dd-post .dd-cta{background:linear-gradient(135deg,#0b1020,#151b3b);color:#e8ebff;border-radius:16px;padding:24px 26px;margin:2em 0;text-align:center}\n.dd-post .dd-cta h3{color:#fff;margin-top:0}\n.dd-post .dd-cta a{color:#a99bff}\n.dd-post .dd-vsc{position:relative;margin:1.5em 0;border-radius:12px;overflow:hidden;background:#1e1e1e;border:1px solid #2b2b2b;box-shadow:0 14px 34px rgba(0,0,0,.28);font-family:\"Segoe UI\",system-ui,sans-serif}\n.dd-post .dd-vsc>input{position:absolute;opacity:0;pointer-events:none}\n.dd-post .dd-vsc-bar{display:flex;align-items:center;gap:12px;background:#323233;color:#cccccc;font-size:12.5px;padding:8px 12px}\n.dd-post .dd-dots{display:inline-flex;gap:7px}.dd-post .dd-dots i{width:12px;height:12px;border-radius:50%;background:#ff5f57;display:block}.dd-post .dd-dots i:nth-child(2){background:#febc2e}.dd-post .dd-dots i:nth-child(3){background:#28c840}\n.dd-post .dd-vsc-title{flex:1;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:50px;opacity:.85}\n.dd-post .dd-vsc-tabs{display:flex;align-items:stretch;background:#252526;border-bottom:1px solid #1e1e1e;overflow-x:auto}\n.dd-post .dd-vsc-tabs label{display:flex;align-items:center;gap:7px;padding:9px 16px;color:#969696;font-size:13px;cursor:pointer;background:#2d2d2d;border-right:1px solid #252526;border-top:1px solid transparent;white-space:nowrap;margin:0;transition:color .15s}\n.dd-post .dd-vsc-tabs label:hover{color:#e0e0e0}\n.dd-post .dd-vsc-tabs .ic{font:700 9.5px\/1 Consolas,monospace;padding:3px 4px;border-radius:3px;color:#1e1e1e}\n.dd-post .ic.py{background:#ffd43b}.dd-post .ic.js{background:#f1e05a}.dd-post .ic.sh{background:#89e051}\n.dd-post .dd-vsc-tabs label.run{margin-left:auto;background:transparent;border:0;color:#89d185;font-weight:600;display:none}\n.dd-post .dd-vsc-tabs label.run:hover{color:#b5f0b0;background:#2a2d2e}\n.dd-post .dd-vsc-pane{display:none}\n.dd-post .dd-vsc>.f1:checked~.dd-vsc-tabs .t1,.dd-post .dd-vsc>.f2:checked~.dd-vsc-tabs .t2,.dd-post .dd-vsc>.f3:checked~.dd-vsc-tabs .t3{background:#1e1e1e;color:#ffffff;border-top-color:#3178c6}\n.dd-post .dd-vsc>.f1:checked~.p1,.dd-post .dd-vsc>.f2:checked~.p2,.dd-post .dd-vsc>.f3:checked~.p3{display:block}\n.dd-post .dd-vsc>.f1:checked~.dd-vsc-tabs .run1,.dd-post .dd-vsc>.f2:checked~.dd-vsc-tabs .run2,.dd-post .dd-vsc>.f3:checked~.dd-vsc-tabs .run3{display:flex}\n.dd-post .dd-vsc>.f1:focus-visible~.dd-vsc-tabs .t1,.dd-post .dd-vsc>.f2:focus-visible~.dd-vsc-tabs .t2,.dd-post .dd-vsc>.f3:focus-visible~.dd-vsc-tabs .t3,.dd-post .dd-vsc>input[type=checkbox]:focus-visible~.dd-vsc-tabs .run{outline:2px solid #3178c6;outline-offset:-2px}\n.dd-post .dd-vsc-crumbs{background:#1e1e1e;color:#a9a9a9;font-size:12px;padding:5px 16px 2px}\n.dd-post .dd-vsc pre.dd-vsc-code{background:#1e1e1e;color:#d4d4d4;border:0;border-radius:0;margin:0;padding:8px 0 14px;counter-reset:ln;line-height:1.62;overflow-x:auto}\n.dd-post .dd-vsc pre code{font-family:Consolas,\"JetBrains Mono\",\"Fira Code\",Menlo,monospace;color:inherit;background:none;white-space:pre}\n.dd-post .dd-vsc .l{display:inline-block;min-width:100%;padding-right:18px;box-sizing:border-box}\n.dd-post .dd-vsc .l::before{counter-increment:ln;content:counter(ln);display:inline-block;width:3.2em;padding-right:1.4em;text-align:right;color:#6e7681;user-select:none;-webkit-user-select:none}\n.dd-post .dd-vsc .l:hover{background:#2a2d2e}.dd-post .dd-vsc .l:hover::before{color:#c6c6c6}\n.dd-post .dd-vsc .k,.dd-post .dd-vsc .kn{color:#c586c0}.dd-post .dd-vsc .kc{color:#569cd6}.dd-post .dd-vsc .s{color:#ce9178}.dd-post .dd-vsc .c{color:#6a9955}.dd-post .dd-vsc .m{color:#b5cea8}.dd-post .dd-vsc .nf{color:#dcdcaa}.dd-post .dd-vsc .nc{color:#4ec9b0}.dd-post .dd-vsc .nb{color:#4ec9b0}.dd-post .dd-vsc .nd{color:#dcdcaa}.dd-post .dd-vsc .n{color:#9cdcfe}.dd-post .dd-vsc .o{color:#d4d4d4}.dd-post .dd-vsc .gp{color:#89d185;user-select:none;-webkit-user-select:none}\n.dd-post .dd-vsc-term{display:none;border-top:1px solid #3c3c3c;background:#181818}\n.dd-post .dd-vsc>.r1:checked~.p1 .dd-vsc-term,.dd-post .dd-vsc>.r2:checked~.p2 .dd-vsc-term,.dd-post .dd-vsc>.r3:checked~.p3 .dd-vsc-term{display:block;animation:ddterm .35s ease-out}\n@keyframes ddterm{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}\n.dd-post .dd-term-head{display:flex;gap:18px;align-items:center;font-size:11px;letter-spacing:.06em;color:#8b8b8b;padding:7px 16px 0}\n.dd-post .dd-term-head span:first-child{color:#e7e7e7;border-bottom:1px solid #e7e7e7;padding-bottom:4px}\n.dd-post .dd-term-head .dd-term-note{margin-left:auto;letter-spacing:0;text-transform:none;color:#d7ba7d;border:0;font-size:11.5px}\n.dd-post .dd-vsc-term pre{background:#181818;color:#cccccc;border:0;margin:0;padding:10px 16px 14px;border-radius:0}\n.dd-post .dd-vsc-status{display:flex;flex-wrap:wrap;gap:4px 14px;background:#007acc;color:#fff;font-size:11.5px;padding:4px 12px}\n.dd-post .dd-vsc-status .sp{flex:1}\n.dd-post .dd-vsc-termonly pre.dd-vsc-code{background:#181818;padding:12px 16px}\n@media(max-width:640px){.dd-post .dd-vsc-title{padding-right:0;text-align:left}.dd-post .dd-vsc-status span:nth-child(n+4){display:none}.dd-post .dd-vsc .l::before{width:2.2em;padding-right:1em}}\n.dd-post .dd-vsc-hint{font-size:.93em;color:var(--muted);margin-top:-.6em}\n.dd-post .dd-next{border:1px dashed var(--violet);border-radius:12px;padding:16px 20px;margin:1.6em 0;background:#f7f4ff}\n.dd-post .ic.yml{background:#cb3837;color:#fff}\n.dd-post .ic.md{background:#519aba;color:#fff}.dd-post .ic.txt{background:#c5c5c5}\n<\/style>\n\n<div class=\"dd-hero\">\n<svg viewBox=\"0 0 1200 520\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-label=\"DevOps CI\/CD series Part 3 banner: a Git graph where short feature branches leave main, pass a pull request with green CI checks and merge back into main\">\n<defs>\n<linearGradient id=\"ddbg\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"><stop offset=\"0\" stop-color=\"#0b1020\"\/><stop offset=\"1\" stop-color=\"#151b3b\"\/><\/linearGradient>\n<linearGradient id=\"ddac\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\"><stop offset=\"0\" stop-color=\"#3178c6\"\/><stop offset=\"1\" stop-color=\"#7c5cff\"\/><\/linearGradient>\n<radialGradient id=\"ddglow\" cx=\".5\" cy=\".5\" r=\".5\"><stop offset=\"0\" stop-color=\"#7c5cff\" stop-opacity=\".45\"\/><stop offset=\"1\" stop-color=\"#7c5cff\" stop-opacity=\"0\"\/><\/radialGradient>\n<\/defs>\n<rect x=\"0\" y=\"-60\" width=\"1200\" height=\"640\" fill=\"url(#ddbg)\"\/>\n<circle cx=\"900\" cy=\"270\" r=\"280\" fill=\"url(#ddglow)\"\/>\n<g opacity=\".12\" stroke=\"#9fb2ff\"><path d=\"M0 100H1200M0 200H1200M0 300H1200M0 400H1200M200 0V520M400 0V520M600 0V520M800 0V520M1000 0V520\"\/><\/g>\n<rect x=\"60\" y=\"46\" width=\"128\" height=\"38\" rx=\"19\" fill=\"url(#ddac)\"\/>\n<text x=\"124\" y=\"71\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"18\" font-weight=\"700\" fill=\"#fff\">PART 3<\/text>\n<text x=\"204\" y=\"71\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"18\" fill=\"#aab4e8\">DevOps CI\/CD Series \u00b7 DevDojo<\/text>\n<text x=\"60\" y=\"150\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"54\" font-weight=\"800\" fill=\"#ffffff\">Git for Teams<\/text>\n<text x=\"60\" y=\"212\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"50\" font-weight=\"800\" fill=\"url(#ddac)\">Branches &amp; Pull Requests<\/text>\n<text x=\"60\" y=\"258\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"23\" fill=\"#c9d1ff\">Merge strategies, conflicts and<\/text>\n<text x=\"60\" y=\"290\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"23\" fill=\"#c9d1ff\">trunk-based development for CI\/CD<\/text>\n<g font-family=\"Consolas,Menlo,monospace\" font-size=\"17\">\n<rect x=\"60\" y=\"330\" width=\"470\" height=\"140\" rx=\"12\" fill=\"#11162e\" stroke=\"#2a3366\"\/>\n<circle cx=\"82\" cy=\"350\" r=\"6\" fill=\"#ff5f57\"\/><circle cx=\"102\" cy=\"350\" r=\"6\" fill=\"#febc2e\"\/><circle cx=\"122\" cy=\"350\" r=\"6\" fill=\"#28c840\"\/>\n<text x=\"80\" y=\"385\" fill=\"#89d185\">$ <tspan fill=\"#e6e9f5\">git switch -c feature\/cart<\/tspan><\/text>\n<text x=\"80\" y=\"413\" fill=\"#89d185\">$ <tspan fill=\"#e6e9f5\">gh pr create &#8211;fill<\/tspan><\/text>\n<text x=\"80\" y=\"441\" fill=\"#c9d1ff\">\u2713 checks passed \u00b7 ready to merge<tspan fill=\"#89d185\"> \u258c<animate attributeName=\"opacity\" values=\"1;0;1\" dur=\"1.1s\" repeatCount=\"indefinite\"\/><\/tspan><\/text>\n<\/g>\n<path d=\"M640 300H1150\" stroke=\"#3178c6\" stroke-width=\"6\" stroke-linecap=\"round\"\/>\n<path id=\"ddb1\" d=\"M700 300 C 740 300, 740 190, 790 190 L 880 190 C 930 190, 930 300, 970 300\" fill=\"none\" stroke=\"#7c5cff\" stroke-width=\"5\" stroke-linecap=\"round\"\/>\n<path id=\"ddb2\" d=\"M880 300 C 920 300, 920 410, 960 410 L 1030 410 C 1075 410, 1075 300, 1110 300\" fill=\"none\" stroke=\"#61dafb\" stroke-width=\"5\" stroke-linecap=\"round\"\/>\n<circle r=\"7\" fill=\"#fff\"><animateMotion dur=\"5s\" repeatCount=\"indefinite\"><mpath href=\"#ddb1\"\/><\/animateMotion><\/circle>\n<circle r=\"7\" fill=\"#fff\"><animateMotion dur=\"5s\" begin=\"-2.5s\" repeatCount=\"indefinite\"><mpath href=\"#ddb2\"\/><\/animateMotion><\/circle>\n<circle r=\"5\" fill=\"#7c5cff\"><animateMotion dur=\"7s\" repeatCount=\"indefinite\" path=\"M640 300H1150\"\/><\/circle>\n<g class=\"node\"><circle cx=\"700\" cy=\"300\" r=\"16\" fill=\"#1b2250\" stroke=\"#3178c6\" stroke-width=\"4\"\/><\/g>\n<g class=\"node\"><circle cx=\"880\" cy=\"300\" r=\"16\" fill=\"#1b2250\" stroke=\"#3178c6\" stroke-width=\"4\"\/><\/g>\n<g class=\"node\"><circle cx=\"970\" cy=\"300\" r=\"18\" fill=\"#3178c6\" stroke=\"#fff\" stroke-width=\"3\"\/><\/g>\n<g class=\"node\"><circle cx=\"1110\" cy=\"300\" r=\"18\" fill=\"#3178c6\" stroke=\"#fff\" stroke-width=\"3\"\/><\/g>\n<g class=\"node\"><circle cx=\"790\" cy=\"190\" r=\"14\" fill=\"#1b2250\" stroke=\"#7c5cff\" stroke-width=\"4\"\/><\/g>\n<g class=\"node\"><circle cx=\"880\" cy=\"190\" r=\"14\" fill=\"#1b2250\" stroke=\"#7c5cff\" stroke-width=\"4\"\/><\/g>\n<g class=\"node\"><circle cx=\"995\" cy=\"410\" r=\"14\" fill=\"#1b2250\" stroke=\"#61dafb\" stroke-width=\"4\"\/><\/g>\n<text x=\"640\" y=\"336\" font-family=\"Consolas,Menlo,monospace\" font-size=\"17\" font-weight=\"700\" fill=\"#9fc2ff\">main<\/text>\n<text x=\"770\" y=\"160\" font-family=\"Consolas,Menlo,monospace\" font-size=\"16\" fill=\"#c9b8ff\">feature\/cart<\/text>\n<text x=\"930\" y=\"452\" font-family=\"Consolas,Menlo,monospace\" font-size=\"16\" fill=\"#a8ecff\">fix\/login<\/text>\n<g class=\"node\"><rect x=\"880\" y=\"96\" width=\"170\" height=\"40\" rx=\"20\" fill=\"#1b2250\" stroke=\"url(#ddac)\" stroke-width=\"2\"\/><text x=\"965\" y=\"122\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">PR #7 \u00b7 CI \u2713<\/text><\/g>\n<g class=\"node\"><rect x=\"1040\" y=\"350\" width=\"130\" height=\"40\" rx=\"20\" fill=\"#1b2250\" stroke=\"#28c840\" stroke-width=\"2\"\/><text x=\"1105\" y=\"376\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#fff\">Reviewed<\/text><\/g>\n<text x=\"1140\" y=\"500\" text-anchor=\"end\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"16\" fill=\"#8f9bd6\">devdojo.co.in<\/text>\n<\/svg>\n<\/div>\n\n<div class=\"dd-series\">Series: DevOps CI\/CD <span>Part 3 of 30<\/span><\/div>\n<p class=\"dd-prereq\"><strong>What you should know first:<\/strong> read <a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/21\/devops-series-part-1-what-is-devops-ci-cd\/\">Part 1: What Is DevOps and How Does CI\/CD Work?<\/a> and <a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/\">Part 2: Linux and Shell Basics<\/a>. You need Git installed (<code>git --version<\/code>) and a terminal. Knowing <code>git add<\/code> and <code>git commit<\/code> helps, but we recap them.<\/p>\n\n<p>Every CI\/CD pipeline starts with a Git event: a push, a pull request or a merge. So before we write bigger pipelines, we need a team workflow that keeps <code>main<\/code> healthy while many people change the code at the same time. In this part you will learn <strong>Git for teams<\/strong>: feature branches, pull requests, code review, the three ways to merge (merge commit, squash and rebase), how to fix merge conflicts, how to protect <code>main<\/code> with required CI checks, and why fast teams use <strong>trunk-based development<\/strong> with short-lived branches and feature flags. Every command below was run for real, so the outputs you see are what Git prints.<\/p>\n\n<div class=\"dd-summary\">\n<strong class=\"lbl\">Quick Summary<\/strong>\n<ul>\n<li>A <strong>branch<\/strong> is a cheap, movable label on a commit. Teams use one short branch per change and merge it back into <code>main<\/code>.<\/li>\n<li>A <strong>pull request (PR)<\/strong> asks to merge a branch. CI runs the tests on it and a teammate reviews it before it can be merged.<\/li>\n<li>There are three ways to merge: <strong>merge commit<\/strong> (keeps all history), <strong>squash<\/strong> (one clean commit per PR) and <strong>rebase<\/strong> (straight line, no merge commit).<\/li>\n<li><strong>Trunk-based development<\/strong> = tiny branches merged into <code>main<\/code> at least once a day, with unfinished features hidden behind <strong>feature flags<\/strong>.<\/li>\n<li>Protect <code>main<\/code> with branch rules: require a PR, passing CI checks and an approval. Never push directly to it.<\/li>\n<li>Write clear commit messages with <strong>Conventional Commits<\/strong> (<code>feat:<\/code>, <code>fix:<\/code>, <code>docs:<\/code>\u2026) \u2013 later in the series we will use them to create releases automatically.<\/li>\n<\/ul>\n<\/div>\n\n<details class=\"dd-roadmap\">\n<summary>Full DevOps CI\/CD roadmap (30 parts)<\/summary>\n<h4>Beginner<\/h4>\n<ol start=\"1\">\n<li><a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/21\/devops-series-part-1-what-is-devops-ci-cd\/\">What Is DevOps and How Does CI\/CD Work?<\/a><\/li>\n<li><a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/30\/devops-series-part-2-linux-shell-basics\/\">Linux and Shell Basics Every DevOps Engineer Needs<\/a><\/li>\n<li>Git for Teams: Branches, Pull Requests and Trunk-Based Development <span class=\"here\">You are here<\/span><\/li>\n<li>Automated Testing for Pipelines: Unit, Integration and End-to-End <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Your First CI Pipeline with GitHub Actions <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Docker Basics: Images, Containers and Dockerfiles <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Writing Small, Secure, Production-Ready Dockerfiles <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Container Registries and Image Versioning <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Build Artifacts, Versioning and Semantic Releases <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Continuous Delivery vs Continuous Deployment: Your First Release Pipeline <span class=\"soon\">Coming soon<\/span><\/li>\n<\/ol>\n<h4>Intermediate<\/h4>\n<ol start=\"11\">\n<li>Environments, Configuration and Secrets Management <span class=\"soon\">Coming soon<\/span><\/li>\n<li>GitHub Actions Deep Dive: Matrix Builds, Caching and Reusable Workflows <span class=\"soon\">Coming soon<\/span><\/li>\n<li>GitLab CI\/CD and Jenkins: How Other CI Tools Work <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Infrastructure as Code with Terraform <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Configuration Management with Ansible <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Kubernetes Essentials for CI\/CD <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Packaging Apps for Kubernetes with Helm and Kustomize <span class=\"soon\">Coming soon<\/span><\/li>\n<li>GitOps with Argo CD <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Deployment Strategies: Rolling, Blue-Green and Canary <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Feature Flags and Progressive Delivery <span class=\"soon\">Coming soon<\/span><\/li>\n<\/ol>\n<h4>Advanced<\/h4>\n<ol start=\"21\">\n<li>DevSecOps: Code, Dependency and Container Scanning in CI <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Software Supply Chain Security: SBOMs, Signing and SLSA <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Observability: Metrics, Logs and Traces with Prometheus, Grafana and OpenTelemetry <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Alerts, SLOs and Incident Response <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Fast CI for Monorepos: Affected Builds and Remote Caching <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Self-Hosted Runners and Scaling CI Cost-Effectively <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Platform Engineering and Internal Developer Platforms <span class=\"soon\">Coming soon<\/span><\/li>\n<li>AI in DevOps: AI Code Review and Agentic Pipelines <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Measuring DevOps with DORA Metrics <span class=\"soon\">Coming soon<\/span><\/li>\n<li>Final Project: A Complete Pipeline from Commit to Production <span class=\"soon\">Coming soon<\/span><\/li>\n<\/ol>\n<\/details>\n\n<div class=\"dd-toc\"><strong>Table of contents<\/strong>\n<nav aria-label=\"Table of contents\"><ol>\n<li><a href=\"#why-git\">Why Git matters for CI\/CD<\/a><\/li>\n<li><a href=\"#basics\">Git basics: commits, branches, HEAD<\/a><\/li>\n<li><a href=\"#pull-requests\">Feature branches and pull requests<\/a><\/li>\n<li><a href=\"#strategies\">Git branching strategies<\/a><\/li>\n<li><a href=\"#trunk-based\">Trunk-based development in practice<\/a><\/li>\n<li><a href=\"#merge-options\">Merge, squash or rebase?<\/a><\/li>\n<li><a href=\"#conflicts\">Fixing merge conflicts<\/a><\/li>\n<li><a href=\"#protect-main\">Protecting the main branch<\/a><\/li>\n<li><a href=\"#commits\">Good commit messages<\/a><\/li>\n<li><a href=\"#errors\">Common errors and fixes<\/a><\/li>\n<li><a href=\"#best-practices\">Best practices<\/a><\/li>\n<li><a href=\"#practice\">Practice task<\/a><\/li>\n<li><a href=\"#faq\">FAQ<\/a><\/li>\n<\/ol><\/nav>\n<\/div>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"why-git\">Why Git matters for CI\/CD<\/h2>\n<p>In Part 1 our pipeline started with <code>on: push<\/code>. That one line says it all: <strong>Git is the trigger of CI\/CD<\/strong>. When you push a branch, CI builds and tests it. When you open a pull request, CI posts a green tick or a red cross on it. When a pull request is merged into <code>main<\/code>, the deploy pipeline starts. If your team uses Git in a messy way \u2013 huge branches, direct pushes to <code>main<\/code>, unclear history \u2013 your pipeline is messy too.<\/p>\n<figure>\n<svg viewBox=\"0 0 1000 300\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-label=\"Diagram: a git push or pull request starts the CI checks, then code review, merge to main and the deploy pipeline\">\n<rect width=\"1000\" height=\"300\" fill=\"#0f1430\"\/>\n<defs><marker id=\"ar3\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto\"><path d=\"M0 0L10 5L0 10z\" fill=\"#7c5cff\"\/><\/marker><\/defs>\n<text x=\"500\" y=\"40\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"22\" font-weight=\"700\" fill=\"#fff\">Every pipeline starts with a Git event<\/text>\n<g font-family=\"Segoe UI,Arial,sans-serif\" text-anchor=\"middle\">\n<rect x=\"20\" y=\"85\" width=\"170\" height=\"120\" rx=\"14\" fill=\"#1b2250\" stroke=\"#3178c6\" stroke-width=\"2\"\/>\n<text x=\"105\" y=\"125\" font-size=\"18\" font-weight=\"700\" fill=\"#fff\">git push<\/text><text x=\"105\" y=\"153\" font-size=\"14\" fill=\"#c9d1ff\">to a feature<\/text><text x=\"105\" y=\"173\" font-size=\"14\" fill=\"#c9d1ff\">branch<\/text>\n<rect x=\"215\" y=\"85\" width=\"170\" height=\"120\" rx=\"14\" fill=\"#1b2250\" stroke=\"#3178c6\" stroke-width=\"2\"\/>\n<text x=\"300\" y=\"125\" font-size=\"18\" font-weight=\"700\" fill=\"#fff\">Pull request<\/text><text x=\"300\" y=\"153\" font-size=\"14\" fill=\"#c9d1ff\">&#8220;please merge<\/text><text x=\"300\" y=\"173\" font-size=\"14\" fill=\"#c9d1ff\">my branch&#8221;<\/text>\n<rect x=\"410\" y=\"85\" width=\"170\" height=\"120\" rx=\"14\" fill=\"#1b2250\" stroke=\"#7c5cff\" stroke-width=\"2\"\/>\n<text x=\"495\" y=\"125\" font-size=\"18\" font-weight=\"700\" fill=\"#fff\">CI checks<\/text><text x=\"495\" y=\"153\" font-size=\"14\" fill=\"#c9d1ff\">build \u00b7 lint \u00b7 test<\/text><text x=\"495\" y=\"173\" font-size=\"14\" fill=\"#89d185\">\u2713 all green<\/text>\n<rect x=\"605\" y=\"85\" width=\"170\" height=\"120\" rx=\"14\" fill=\"#1b2250\" stroke=\"#7c5cff\" stroke-width=\"2\"\/>\n<text x=\"690\" y=\"125\" font-size=\"18\" font-weight=\"700\" fill=\"#fff\">Code review<\/text><text x=\"690\" y=\"153\" font-size=\"14\" fill=\"#c9d1ff\">a teammate<\/text><text x=\"690\" y=\"173\" font-size=\"14\" fill=\"#c9d1ff\">approves<\/text>\n<rect x=\"800\" y=\"85\" width=\"180\" height=\"120\" rx=\"14\" fill=\"#3178c6\"\/>\n<text x=\"890\" y=\"125\" font-size=\"18\" font-weight=\"700\" fill=\"#fff\">Merge to main<\/text><text x=\"890\" y=\"153\" font-size=\"14\" fill=\"#e8ebff\">starts the deploy<\/text><text x=\"890\" y=\"173\" font-size=\"14\" fill=\"#e8ebff\">pipeline<\/text>\n<\/g>\n<g stroke=\"#7c5cff\" stroke-width=\"3\" marker-end=\"url(#ar3)\"><path d=\"M192 145H211\"\/><path d=\"M387 145H406\"\/><path d=\"M582 145H601\"\/><path d=\"M777 145H796\"\/><\/g>\n<path d=\"M495 210 C 495 262, 300 262, 300 212\" fill=\"none\" stroke=\"#e08a00\" stroke-width=\"2.5\" stroke-dasharray=\"6 6\" marker-end=\"url(#ar3)\"\/>\n<text x=\"400\" y=\"282\" text-anchor=\"middle\" font-family=\"Segoe UI,Arial,sans-serif\" font-size=\"14\" fill=\"#f5c46a\">red check? push a fix to the same branch and CI runs again<\/text>\n<\/svg>\n<figcaption>Git events (push, pull request, merge) are the triggers of your CI\/CD pipeline. Nothing reaches <code>main<\/code> without passing checks and a review.<\/figcaption>\n<\/figure>\n<p>A good team Git workflow gives you three things: <strong>safety<\/strong> (broken code is stopped before it reaches <code>main<\/code>), <strong>speed<\/strong> (small changes are reviewed and merged quickly) and <strong>traceability<\/strong> (you can see who changed what, why, and roll it back).<\/p>\n\n<h2 id=\"basics\">Git basics in 5 minutes: commits, branches and HEAD<\/h2>\n<p>Let&#8217;s refresh the four ideas everything else is built on:<\/p>\n<div class=\"dd-cards\">\n<div class=\"dd-card\"><b>Commit<\/b>A saved snapshot of your files, with a message, an author and a unique ID (a hash like <code>92fc825<\/code>).<\/div>\n<div class=\"dd-card\"><b>Branch<\/b>A movable label that points to a commit. Creating one is instant and costs almost nothing.<\/div>\n<div class=\"dd-card\"><b>HEAD<\/b>&#8220;Where you are now&#8221; \u2013 usually the branch you have checked out with <code>git switch<\/code>.<\/div>\n<div class=\"dd-card\"><b>Remote<\/b>The shared copy of the repo, usually called <code>origin<\/code>, on GitHub, GitLab or Bitbucket.<\/div>\n<\/div>\n<p>This script creates a tiny repo, does some work on a branch and merges it back \u2013 the whole team workflow in miniature:<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: branch-basics.sh\">\n<input type=\"radio\" name=\"v1\" id=\"v1f1\" class=\"f1\" checked>\n<input type=\"checkbox\" id=\"v1r1\" class=\"r1\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">branch-basics.sh \u2014 git-basics \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v1f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>branch-basics.sh<\/label>\n<label for=\"v1r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">git-basics \u203a branch-basics.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># Create a repo with one commit on main<\/span><\/span>\n<span class=\"l\">git init -q -b main shop-api &amp;&amp; <span class=\"nb\">cd<\/span> shop-api<\/span>\n<span class=\"l\">git config user.name <span class=\"s\">\"Asha\"<\/span><\/span>\n<span class=\"l\">git config user.email <span class=\"s\">\"asha@example.com\"<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"# Shop API\"<\/span> &gt; README.md<\/span>\n<span class=\"l\">git add README.md<\/span>\n<span class=\"l\">git commit -q -m <span class=\"s\">\"chore: initial commit\"<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># Work on a separate branch<\/span><\/span>\n<span class=\"l\">git switch -c feature\/health-check<\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">'export const health = () =&gt; \"ok\";'<\/span> &gt; health.js<\/span>\n<span class=\"l\">git add health.js<\/span>\n<span class=\"l\">git commit -m <span class=\"s\">\"feat: add health check\"<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># Bring the work back into main<\/span><\/span>\n<span class=\"l\">git switch main<\/span>\n<span class=\"l\">git merge --no-ff feature\/health-check -m <span class=\"s\">\"Merge pull request #1 from feature\/health-check\"<\/span><\/span>\n<span class=\"l\">git log --oneline --graph<\/span>\n<span class=\"l\">git branch -d feature\/health-check<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (git 2.43, bash 5.2, Ubuntu)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> git init -q -b main shop-api &amp;&amp; cd shop-api\n<span class=\"gp\">$<\/span> git config user.name \"Asha\"\n<span class=\"gp\">$<\/span> git config user.email \"asha@example.com\"\n<span class=\"gp\">$<\/span> echo \"# Shop API\" &gt; README.md\n<span class=\"gp\">$<\/span> git add README.md\n<span class=\"gp\">$<\/span> git commit -q -m \"chore: initial commit\"\n<span class=\"gp\">$<\/span> git switch -c feature\/health-check\nSwitched to a new branch 'feature\/health-check'\n<span class=\"gp\">$<\/span> echo 'export const health = () =&gt; \"ok\";' &gt; health.js\n<span class=\"gp\">$<\/span> git add health.js\n<span class=\"gp\">$<\/span> git commit -m \"feat: add health check\"\n[feature\/health-check 92fc825] feat: add health check\n 1 file changed, 1 insertion(+)\n create mode 100644 health.js\n<span class=\"gp\">$<\/span> git switch main\nSwitched to branch 'main'\n<span class=\"gp\">$<\/span> git merge --no-ff feature\/health-check -m \"Merge pull request #1 from feature\/health-check\"\nMerge made by the 'ort' strategy.\n health.js | 1 +\n 1 file changed, 1 insertion(+)\n create mode 100644 health.js\n<span class=\"gp\">$<\/span> git log --oneline --graph\n*   fd9b1d7 Merge pull request #1 from feature\/health-check\n|\\  \n| * 92fc825 feat: add health check\n|\/  \n* 09035ab chore: initial commit\n<span class=\"gp\">$<\/span> git branch -d feature\/health-check\nDeleted branch feature\/health-check (was 92fc825).<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 17, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Click \u25b6 Run to open the terminal panel. Commit IDs like <code>92fc825<\/code> will be different on your machine \u2013 they come from the content and the time of each commit.<\/p>\n<p>Read the <code>git log --graph<\/code> output from bottom to top: the first commit, then a side line for the branch, then the <strong>merge commit<\/strong> that joins them. We use <code>git switch<\/code> instead of the older <code>git checkout<\/code> because it only does one job (changing branches), so it is harder to misuse.<\/p>\n\n<h2 id=\"pull-requests\">Feature branches and pull requests<\/h2>\n<p>On a team you never commit straight to <code>main<\/code>. Instead, every change follows the same loop: <strong>branch \u2192 commit \u2192 push \u2192 pull request \u2192 CI + review \u2192 merge<\/strong>. A <strong>pull request<\/strong> (GitLab calls it a <em>merge request<\/em>) is a page where you say &#8220;here is my branch, please merge it&#8221;. It shows the diff, runs the CI checks and collects review comments.<\/p>\n<p>Here is the daily workflow from the terminal. The last three commands use the GitHub CLI (<code>gh<\/code>), but you can do the same steps on the GitHub website:<\/p>\n<div class=\"dd-vsc dd-vsc-termonly\" role=\"group\" aria-label=\"Terminal: daily pull request workflow\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">Terminal \u2014 daily pull request workflow<\/span><\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># 1. Start from the latest main<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git switch main<\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git pull --rebase<\/span>\n<span class=\"l\"><span class=\"c\"># 2. Create a short-lived branch for one small change<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git switch -c feature\/cart-discount<\/span>\n<span class=\"l\"><span class=\"c\"># 3. Commit in small steps (git add -p lets you pick changes)<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git add -p<\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git commit -m \"feat(cart): add discount codes\"<\/span>\n<span class=\"l\"><span class=\"c\"># 4. Push the branch and open a pull request<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git push -u origin feature\/cart-discount<\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> gh pr create --fill --base main<\/span>\n<span class=\"l\"><span class=\"c\"># 5. After review and green CI checks: squash-merge and clean up<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> gh pr merge --squash --delete-branch<\/span><\/code><\/pre>\n<\/div>\n<p class=\"dd-vsc-hint\">These commands talk to a real GitHub remote, so there is no Run button here. Try them on your own practice repo.<\/p>\n<h3>What makes a good pull request?<\/h3>\n<ul>\n<li><strong>Small:<\/strong> one idea per PR. Small PRs get reviewed faster and more carefully; under ~400 changed lines is a good target.<\/li>\n<li><strong>Clear title and description:<\/strong> what changed, why, and how you tested it. A PR template (shown later) makes this automatic.<\/li>\n<li><strong>Green CI:<\/strong> fix failing checks before asking for review \u2013 nobody wants to review broken code.<\/li>\n<li><strong>Fast turnaround:<\/strong> good teams review PRs within a few hours. A PR that waits three days goes stale and gets merge conflicts.<\/li>\n<\/ul>\n<div class=\"dd-tip\"><strong>Branch naming tip:<\/strong> use a prefix and a short description, like <code>feature\/cart-discount<\/code>, <code>fix\/login-timeout<\/code> or <code>chore\/update-deps<\/code>. Some teams add the ticket number: <code>feature\/SHOP-142-cart-discount<\/code>.<\/div>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"strategies\">Git branching strategies: Git Flow vs GitHub Flow vs trunk-based development<\/h2>\n<p>A <strong>branching strategy<\/strong> is the set of rules your team follows for creating and merging branches. There are three popular ones, and the big difference between them is <strong>how long branches live<\/strong>.<\/p>\n<figure>\n<svg viewBox=\"0 0 1000 470\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-label=\"Diagram comparing three Git branching strategies: Git Flow with long-lived develop and release branches, GitHub Flow with feature branches that live a few days, and trunk-based development with tiny branches that merge into main within hours\">\n<rect width=\"1000\" height=\"470\" fill=\"#0f1430\"\/>\n<g font-family=\"Segoe UI,Arial,sans-serif\">\n<text x=\"500\" y=\"36\" text-anchor=\"middle\" font-size=\"22\" font-weight=\"700\" fill=\"#fff\">Three Git branching strategies<\/text>\n<text x=\"30\" y=\"78\" font-size=\"17\" font-weight=\"800\" fill=\"#fff\">1. Git Flow<\/text><text x=\"150\" y=\"78\" font-size=\"14\" fill=\"#aab4e8\">long-lived develop + release branches, many weeks between releases<\/text>\n<path d=\"M150 110H960\" stroke=\"#3178c6\" stroke-width=\"5\" stroke-linecap=\"round\"\/><text x=\"30\" y=\"115\" font-size=\"15\" font-weight=\"700\" fill=\"#3178c6\">main<\/text>\n<path d=\"M150 160H960\" stroke=\"#7c5cff\" stroke-width=\"5\" stroke-linecap=\"round\"\/><text x=\"30\" y=\"165\" font-size=\"15\" font-weight=\"700\" fill=\"#7c5cff\">develop<\/text>\n<path d=\"M220 160 C 250 160, 240 200, 270 200 L 420 200 C 450 200, 450 160, 470 160\" fill=\"none\" stroke=\"#c9b8ff\" stroke-width=\"3.5\"\/>\n<path d=\"M500 160 C 530 160, 520 200, 550 200 L 620 200 C 650 200, 650 160, 670 160\" fill=\"none\" stroke=\"#c9b8ff\" stroke-width=\"3.5\"\/>\n<path d=\"M700 160 C 730 160, 720 135, 750 135 L 830 135 C 860 135, 860 110, 880 110\" fill=\"none\" stroke=\"#e08a00\" stroke-width=\"3.5\"\/>\n<circle cx=\"220\" cy=\"160\" r=\"8\" fill=\"#0f1430\" stroke=\"#7c5cff\" stroke-width=\"3\"\/>\n<circle cx=\"470\" cy=\"160\" r=\"8\" fill=\"#0f1430\" stroke=\"#7c5cff\" stroke-width=\"3\"\/>\n<circle cx=\"500\" cy=\"160\" r=\"8\" fill=\"#0f1430\" stroke=\"#7c5cff\" stroke-width=\"3\"\/>\n<circle cx=\"670\" cy=\"160\" r=\"8\" fill=\"#0f1430\" stroke=\"#7c5cff\" stroke-width=\"3\"\/>\n<circle cx=\"700\" cy=\"160\" r=\"8\" fill=\"#0f1430\" stroke=\"#7c5cff\" stroke-width=\"3\"\/>\n<circle cx=\"880\" cy=\"110\" r=\"10\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<text x=\"290\" y=\"223\" font-size=\"13\" fill=\"#c9b8ff\">feature (2 weeks)<\/text><text x=\"760\" y=\"128\" font-size=\"13\" fill=\"#f5c46a\">release<\/text>\n<text x=\"30\" y=\"270\" font-size=\"17\" font-weight=\"800\" fill=\"#fff\">2. GitHub Flow<\/text><text x=\"180\" y=\"270\" font-size=\"14\" fill=\"#aab4e8\">one main branch, feature branches live a few days, merged by pull request<\/text>\n<path d=\"M150 300H960\" stroke=\"#3178c6\" stroke-width=\"5\" stroke-linecap=\"round\"\/><text x=\"30\" y=\"305\" font-size=\"15\" font-weight=\"700\" fill=\"#3178c6\">main<\/text>\n<path d=\"M200 300 C 230 300, 220 340, 250 340 L 330 340 C 360 340, 360 300, 380 300\" fill=\"none\" stroke=\"#61dafb\" stroke-width=\"3.5\"\/>\n<circle cx=\"200\" cy=\"300\" r=\"8\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<circle cx=\"380\" cy=\"300\" r=\"10\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<path d=\"M420 300 C 450 300, 440 340, 470 340 L 560 340 C 590 340, 590 300, 610 300\" fill=\"none\" stroke=\"#61dafb\" stroke-width=\"3.5\"\/>\n<circle cx=\"420\" cy=\"300\" r=\"8\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<circle cx=\"610\" cy=\"300\" r=\"10\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<path d=\"M650 300 C 680 300, 670 340, 700 340 L 790 340 C 820 340, 820 300, 840 300\" fill=\"none\" stroke=\"#61dafb\" stroke-width=\"3.5\"\/>\n<circle cx=\"650\" cy=\"300\" r=\"8\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<circle cx=\"840\" cy=\"300\" r=\"10\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<text x=\"260\" y=\"364\" font-size=\"13\" fill=\"#a8ecff\">PR \u00b7 2-3 days<\/text>\n<text x=\"30\" y=\"405\" font-size=\"17\" font-weight=\"800\" fill=\"#fff\">3. Trunk-based<\/text><text x=\"180\" y=\"405\" font-size=\"14\" fill=\"#aab4e8\">tiny branches merged within hours, unfinished work hidden behind feature flags<\/text>\n<path d=\"M150 430H960\" stroke=\"#3178c6\" stroke-width=\"5\" stroke-linecap=\"round\"\/><text x=\"30\" y=\"435\" font-size=\"15\" font-weight=\"700\" fill=\"#3178c6\">main<\/text>\n<path d=\"M190 430 C 202 430, 200 452, 215 452 C 230 452, 228 430, 242 430\" fill=\"none\" stroke=\"#89d185\" stroke-width=\"3\"\/>\n<circle cx=\"242\" cy=\"430\" r=\"7\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<path d=\"M272 430 C 284 430, 282 452, 297 452 C 312 452, 310 430, 324 430\" fill=\"none\" stroke=\"#89d185\" stroke-width=\"3\"\/>\n<circle cx=\"324\" cy=\"430\" r=\"7\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<path d=\"M354 430 C 366 430, 364 452, 379 452 C 394 452, 392 430, 406 430\" fill=\"none\" stroke=\"#89d185\" stroke-width=\"3\"\/>\n<circle cx=\"406\" cy=\"430\" r=\"7\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<path d=\"M436 430 C 448 430, 446 452, 461 452 C 476 452, 474 430, 488 430\" fill=\"none\" stroke=\"#89d185\" stroke-width=\"3\"\/>\n<circle cx=\"488\" cy=\"430\" r=\"7\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<path d=\"M518 430 C 530 430, 528 452, 543 452 C 558 452, 556 430, 570 430\" fill=\"none\" stroke=\"#89d185\" stroke-width=\"3\"\/>\n<circle cx=\"570\" cy=\"430\" r=\"7\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<path d=\"M600 430 C 612 430, 610 452, 625 452 C 640 452, 638 430, 652 430\" fill=\"none\" stroke=\"#89d185\" stroke-width=\"3\"\/>\n<circle cx=\"652\" cy=\"430\" r=\"7\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<path d=\"M682 430 C 694 430, 692 452, 707 452 C 722 452, 720 430, 734 430\" fill=\"none\" stroke=\"#89d185\" stroke-width=\"3\"\/>\n<circle cx=\"734\" cy=\"430\" r=\"7\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<path d=\"M764 430 C 776 430, 774 452, 789 452 C 804 452, 802 430, 816 430\" fill=\"none\" stroke=\"#89d185\" stroke-width=\"3\"\/>\n<circle cx=\"816\" cy=\"430\" r=\"7\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<path d=\"M846 430 C 858 430, 856 452, 871 452 C 886 452, 884 430, 898 430\" fill=\"none\" stroke=\"#89d185\" stroke-width=\"3\"\/>\n<circle cx=\"898\" cy=\"430\" r=\"7\" fill=\"#0f1430\" stroke=\"#3178c6\" stroke-width=\"3\"\/>\n<\/g><\/svg>\n<figcaption>The shorter a branch lives, the smaller the merge and the faster the feedback from CI. Trunk-based development takes this idea the furthest.<\/figcaption>\n<\/figure>\n<p>Click a tab to compare them:<\/p>\n<div class=\"dd-tabs\" role=\"group\" aria-label=\"Compare Git branching strategies\">\n<input type=\"radio\" name=\"ddt\" id=\"ti1\" checked><input type=\"radio\" name=\"ddt\" id=\"ti2\"><input type=\"radio\" name=\"ddt\" id=\"ti3\"><input type=\"radio\" name=\"ddt\" id=\"ti4\">\n<div class=\"labels\"><label for=\"ti1\">Git Flow<\/label><label for=\"ti2\">GitHub Flow<\/label><label for=\"ti3\">Trunk-based<\/label><label for=\"ti4\">Which one should I pick?<\/label><\/div>\n<div class=\"panel p1\"><p><strong>Git Flow<\/strong> uses two long-lived branches: <code>main<\/code> (what is in production) and <code>develop<\/code> (the next release). Features branch off <code>develop<\/code>, a <code>release\/1.4<\/code> branch is cut to stabilise a version, and urgent fixes go into <code>hotfix\/*<\/code> branches.<\/p><p><strong>Good for:<\/strong> software you ship as numbered versions (mobile apps, desktop apps, libraries) and must support several versions at once.<br><strong>Weak point:<\/strong> branches live for weeks, so merges are big and CI feedback arrives late. Even its author now suggests simpler flows for web apps.<\/p><\/div>\n<div class=\"panel p2\"><p><strong>GitHub Flow<\/strong> has one rule: <code>main<\/code> is always deployable. You create a branch for each change, open a pull request, let CI and a reviewer check it, merge it and deploy.<\/p><p><strong>Good for:<\/strong> most web apps and APIs, small and medium teams, and anyone new to CI\/CD.<br><strong>Weak point:<\/strong> if people keep branches open for a week or more, you get the same big, painful merges as Git Flow.<\/p><\/div>\n<div class=\"panel p3\"><p><strong>Trunk-based development<\/strong> means everyone integrates into <code>main<\/code> (the &#8220;trunk&#8221;) at least once a day. Branches are tiny and live for hours, not days. Work that is not ready for users is merged anyway, but hidden behind a <strong>feature flag<\/strong>.<\/p><p><strong>Good for:<\/strong> teams that deploy often (daily or many times a day) and have good automated tests. Big tech companies such as Google work this way, and the DORA research links it with faster, more reliable delivery.<br><strong>Weak point:<\/strong> it needs strong CI, fast reviews and discipline about small changes.<\/p><\/div>\n<div class=\"panel p4\"><p><strong>Simple rule:<\/strong> start with <strong>GitHub Flow<\/strong> plus small pull requests. As your tests get better and you deploy more often, shrink branches until they live less than a day \u2013 at that point you are doing <strong>trunk-based development<\/strong>. Choose <strong>Git Flow<\/strong> only if you really ship numbered versions and must patch old ones.<\/p><\/div>\n<\/div>\n<div class=\"dd-tablewrap\"><table>\n<tr><th><\/th><th>Git Flow<\/th><th>GitHub Flow<\/th><th>Trunk-based<\/th><\/tr>\n<tr><td>Long-lived branches<\/td><td><code>main<\/code> + <code>develop<\/code><\/td><td><code>main<\/code> only<\/td><td><code>main<\/code> only<\/td><\/tr>\n<tr><td>Feature branch life<\/td><td>Days to weeks<\/td><td>A few days<\/td><td>Hours (under a day)<\/td><\/tr>\n<tr><td>Release style<\/td><td>Planned versions<\/td><td>Deploy after each merge<\/td><td>Deploy many times a day<\/td><\/tr>\n<tr><td>Unfinished work<\/td><td>Stays on its branch<\/td><td>Stays on its branch<\/td><td>Merged, hidden by a feature flag<\/td><\/tr>\n<tr><td>Merge conflicts<\/td><td>Frequent and big<\/td><td>Sometimes<\/td><td>Rare and tiny<\/td><\/tr>\n<tr><td>Fits CI\/CD best?<\/td><td>Not really<\/td><td>Yes<\/td><td>Yes \u2013 the best fit<\/td><\/tr>\n<\/table><\/div>\n\n<h2 id=\"trunk-based\">How trunk-based development works in practice<\/h2>\n<p><strong>Trunk-based development<\/strong> sounds scary (&#8220;everyone commits to main?!&#8221;) but in practice it is just GitHub Flow with very short branches. The <a href=\"https:\/\/trunkbaseddevelopment.com\/\" rel=\"noopener\" target=\"_blank\">trunkbaseddevelopment.com<\/a> guide and the DORA research both link it with faster and safer delivery. The rules:<\/p>\n<ol>\n<li><strong>Merge into <code>main<\/code> at least once a day.<\/strong> Split big features into small, safe steps.<\/li>\n<li><strong>Keep <code>main<\/code> releasable.<\/strong> Every merge goes through CI; if <code>main<\/code> breaks, fixing it is the team&#8217;s top priority.<\/li>\n<li><strong>Hide unfinished work behind feature flags<\/strong> instead of keeping it on a long branch.<\/li>\n<li><strong>Review fast.<\/strong> Small PRs make a 10-minute review possible.<\/li>\n<\/ol>\n<p>A <strong>feature flag<\/strong> is just an <code>if<\/code> that you can switch from outside the code (an environment variable, a config file or a flag service). Here is the smallest useful version in Node.js:<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: checkout.mjs, flags.mjs\">\n<input type=\"radio\" name=\"v3\" id=\"v3f1\" class=\"f1\" checked>\n<input type=\"radio\" name=\"v3\" id=\"v3f2\" class=\"f2\">\n<input type=\"checkbox\" id=\"v3r1\" class=\"r1\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">checkout.mjs \u2014 trunk-demo \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v3f1\" class=\"t1\"><b class=\"ic js\">JS<\/b>checkout.mjs<\/label>\n<label for=\"v3f2\" class=\"t2\"><b class=\"ic js\">JS<\/b>flags.mjs<\/label>\n<label for=\"v3r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">trunk-demo \u203a checkout.mjs<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\">\/\/ checkout.mjs \u2013 unfinished code can live safely on main<\/span><\/span>\n<span class=\"l\"><span class=\"k\">import<\/span> { <span class=\"n\">isEnabled<\/span> } <span class=\"k\">from<\/span> <span class=\"s\">\".\/flags.mjs\"<\/span>;<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"k\">function<\/span> <span class=\"n\">oldCheckout<\/span>(<span class=\"n\">cart<\/span>) {<\/span>\n<span class=\"l\">  <span class=\"k\">return<\/span> <span class=\"s\">`Old checkout: ${<\/span><span class=\"n\">cart<\/span>.<span class=\"n\">length<\/span><span class=\"s\">} items, pay on next page`<\/span>;<\/span>\n<span class=\"l\">}<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"k\">function<\/span> <span class=\"n\">newCheckout<\/span>(<span class=\"n\">cart<\/span>) {<\/span>\n<span class=\"l\">  <span class=\"k\">return<\/span> <span class=\"s\">`New one-click checkout: ${<\/span><span class=\"n\">cart<\/span>.<span class=\"n\">length<\/span><span class=\"s\">} items, paid!`<\/span>;<\/span>\n<span class=\"l\">}<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"k\">const<\/span> <span class=\"n\">cart<\/span> = [<span class=\"s\">\"keyboard\"<\/span>, <span class=\"s\">\"mouse\"<\/span>];<\/span>\n<span class=\"l\"><span class=\"k\">const<\/span> <span class=\"n\">result<\/span> = <span class=\"n\">isEnabled<\/span>(<span class=\"s\">\"newCheckout\"<\/span>) ? <span class=\"n\">newCheckout<\/span>(<span class=\"n\">cart<\/span>) : <span class=\"n\">oldCheckout<\/span>(<span class=\"n\">cart<\/span>);<\/span>\n<span class=\"l\"><span class=\"n\">console<\/span>.<span class=\"n\">log<\/span>(<span class=\"n\">result<\/span>);<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (Node.js 22)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> node checkout.mjs\nOld checkout: 2 items, pay on next page\n<span class=\"gp\">$<\/span> FLAG_NEW_CHECKOUT=true node checkout.mjs\nNew one-click checkout: 2 items, paid!<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 14, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>JavaScript<\/span><\/div>\n<\/div>\n<div class=\"dd-vsc-pane p2\">\n<div class=\"dd-vsc-crumbs\">trunk-demo \u203a flags.mjs<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\">\/\/ flags.mjs \u2013 a tiny feature flag helper<\/span><\/span>\n<span class=\"l\"><span class=\"c\">\/\/ Flags come from environment variables, so you can switch a<\/span><\/span>\n<span class=\"l\"><span class=\"c\">\/\/ feature on or off without changing (or redeploying) the code.<\/span><\/span>\n<span class=\"l\"><span class=\"k\">const<\/span> <span class=\"n\">flags<\/span> = {<\/span>\n<span class=\"l\">  <span class=\"n\">newCheckout<\/span>: <span class=\"n\">process<\/span>.<span class=\"n\">env<\/span>.<span class=\"n\">FLAG_NEW_CHECKOUT<\/span> === <span class=\"s\">\"true\"<\/span>,<\/span>\n<span class=\"l\">};<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"k\">export<\/span> <span class=\"k\">function<\/span> <span class=\"n\">isEnabled<\/span>(<span class=\"n\">name<\/span>) {<\/span>\n<span class=\"l\">  <span class=\"k\">return<\/span> <span class=\"n\">flags<\/span>[<span class=\"n\">name<\/span>] ?? <span class=\"kc\">false<\/span>;<\/span>\n<span class=\"l\">}<\/span><\/code><\/pre>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 10, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>JavaScript<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Two files: switch tabs to see the flag helper. Click \u25b6 Run on <code>checkout.mjs<\/code> to see the same code behave differently with the flag off and on.<\/p>\n<p>The new checkout can now be merged into <code>main<\/code> and even deployed to production while it is still being built \u2013 users only see it when you set <code>FLAG_NEW_CHECKOUT=true<\/code>. This is what lets trunk-based teams merge every day. We will go much deeper into flags and gradual rollouts in Part 20 of this series.<\/p>\n<div class=\"dd-warn\"><strong>Clean up your flags:<\/strong> once a feature is fully released, delete the flag and the old code path. Old flags pile up and make code hard to read.<\/div>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"merge-options\">Merge, squash or rebase? Three ways to finish a pull request<\/h2>\n<p>When a PR is approved, GitHub offers three buttons: <strong>Create a merge commit<\/strong>, <strong>Squash and merge<\/strong> and <strong>Rebase and merge<\/strong>. They all bring the same code into <code>main<\/code>, but they leave very different history. Let&#8217;s try all three on the same repo. First, this script builds the starting point:<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: setup.sh\">\n<input type=\"radio\" name=\"v4\" id=\"v4f1\" class=\"f1\" checked>\n<input type=\"checkbox\" id=\"v4r1\" class=\"r1\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">setup.sh \u2014 merge-lab \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v4f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>setup.sh<\/label>\n<label for=\"v4r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">merge-lab \u203a setup.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\">#!\/usr\/bin\/env bash<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># Run in an empty folder. Builds a tiny repo where main moved on<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># while feature\/cart was waiting for review.<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">set<\/span> -euo pipefail<\/span>\n<span class=\"l\">git init -q -b main shop &amp;&amp; <span class=\"nb\">cd<\/span> shop<\/span>\n<span class=\"l\">git config user.name <span class=\"s\">\"Asha\"<\/span><\/span>\n<span class=\"l\">git config user.email <span class=\"s\">\"asha@example.com\"<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"# Shop\"<\/span> &gt; README.md &amp;&amp; git add . &amp;&amp; git commit -qm <span class=\"s\">\"chore: initial commit\"<\/span><\/span>\n<span class=\"l\">git switch -qc feature\/cart<\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"cart\"<\/span> &gt; cart.js &amp;&amp; git add . &amp;&amp; git commit -qm <span class=\"s\">\"feat: add cart\"<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"total\"<\/span> &gt;&gt; cart.js &amp;&amp; git commit -qam <span class=\"s\">\"fix: typo in cart\"<\/span><\/span>\n<span class=\"l\">git switch -q main<\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"MIT\"<\/span> &gt; LICENSE &amp;&amp; git add . &amp;&amp; git commit -qm <span class=\"s\">\"docs: add license\"<\/span><\/span>\n<span class=\"l\">git log --oneline --graph --all<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (git 2.43, bash 5.2, Ubuntu)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> bash setup.sh\n* 8070f45 fix: typo in cart\n* ca115d3 feat: add cart\n| * 194d1e1 docs: add license\n|\/  \n* b5f8f6b chore: initial commit<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 14, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Run it to see the starting point: <code>feature\/cart<\/code> has two commits and <code>main<\/code> got a new commit in the meantime.<\/p>\n<p>Now finish the &#8220;pull request&#8221; in three different ways. Each script starts again from a fresh copy of the repo above:<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: merge.sh, squash.sh, rebase.sh\">\n<input type=\"radio\" name=\"v5\" id=\"v5f1\" class=\"f1\" checked>\n<input type=\"radio\" name=\"v5\" id=\"v5f2\" class=\"f2\">\n<input type=\"radio\" name=\"v5\" id=\"v5f3\" class=\"f3\">\n<input type=\"checkbox\" id=\"v5r1\" class=\"r1\">\n<input type=\"checkbox\" id=\"v5r2\" class=\"r2\">\n<input type=\"checkbox\" id=\"v5r3\" class=\"r3\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">merge.sh \u2014 merge-lab \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v5f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>merge.sh<\/label>\n<label for=\"v5f2\" class=\"t2\"><b class=\"ic sh\">$_<\/b>squash.sh<\/label>\n<label for=\"v5f3\" class=\"t3\"><b class=\"ic sh\">$_<\/b>rebase.sh<\/label>\n<label for=\"v5r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<label for=\"v5r2\" class=\"run run2\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<label for=\"v5r3\" class=\"run run3\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">merge-lab \u203a merge.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># Option 1: merge commit (keeps every commit + a merge commit)<\/span><\/span>\n<span class=\"l\">bash setup.sh &gt; \/dev\/null &amp;&amp; <span class=\"nb\">cd<\/span> shop<\/span>\n<span class=\"l\">git merge --no-ff feature\/cart -m <span class=\"s\">\"Merge pull request #7 from feature\/cart\"<\/span><\/span>\n<span class=\"l\">git log --oneline --graph<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (git 2.43, bash 5.2, Ubuntu)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> bash setup.sh &gt; \/dev\/null &amp;&amp; cd shop\n<span class=\"gp\">$<\/span> git merge --no-ff feature\/cart -m \"Merge pull request #7 from feature\/cart\"\nMerge made by the 'ort' strategy.\n cart.js | 2 ++\n 1 file changed, 2 insertions(+)\n create mode 100644 cart.js\n<span class=\"gp\">$<\/span> git log --oneline --graph\n*   5080bb8 Merge pull request #7 from feature\/cart\n|\\  \n| * 8070f45 fix: typo in cart\n| * ca115d3 feat: add cart\n* | 194d1e1 docs: add license\n|\/  \n* b5f8f6b chore: initial commit<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 4, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<div class=\"dd-vsc-pane p2\">\n<div class=\"dd-vsc-crumbs\">merge-lab \u203a squash.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># Option 2: squash merge (all branch commits become ONE commit)<\/span><\/span>\n<span class=\"l\">bash setup.sh &gt; \/dev\/null &amp;&amp; <span class=\"nb\">cd<\/span> shop<\/span>\n<span class=\"l\">git merge --squash feature\/cart<\/span>\n<span class=\"l\">git commit -qm <span class=\"s\">\"feat: add cart (#7)\"<\/span><\/span>\n<span class=\"l\">git log --oneline --graph<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (git 2.43, bash 5.2, Ubuntu)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> bash setup.sh &gt; \/dev\/null &amp;&amp; cd shop\n<span class=\"gp\">$<\/span> git merge --squash feature\/cart\nAutomatic merge went well; stopped before committing as requested\nSquash commit -- not updating HEAD\n<span class=\"gp\">$<\/span> git commit -qm \"feat: add cart (#7)\"\n<span class=\"gp\">$<\/span> git log --oneline --graph\n* c5f9188 feat: add cart (#7)\n* 194d1e1 docs: add license\n* b5f8f6b chore: initial commit<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 5, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<div class=\"dd-vsc-pane p3\">\n<div class=\"dd-vsc-crumbs\">merge-lab \u203a rebase.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># Option 3: rebase, then fast-forward (straight line, no merge commit)<\/span><\/span>\n<span class=\"l\">bash setup.sh &gt; \/dev\/null &amp;&amp; <span class=\"nb\">cd<\/span> shop<\/span>\n<span class=\"l\">git switch -q feature\/cart<\/span>\n<span class=\"l\">git rebase main<\/span>\n<span class=\"l\">git switch -q main<\/span>\n<span class=\"l\">git merge --ff-only feature\/cart<\/span>\n<span class=\"l\">git log --oneline --graph<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (git 2.43, bash 5.2, Ubuntu)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> bash setup.sh &gt; \/dev\/null &amp;&amp; cd shop\n<span class=\"gp\">$<\/span> git switch -q feature\/cart\n<span class=\"gp\">$<\/span> git rebase main\nRebasing (1\/2)\nRebasing (2\/2)\nSuccessfully rebased and updated refs\/heads\/feature\/cart.\n<span class=\"gp\">$<\/span> git switch -q main\n<span class=\"gp\">$<\/span> git merge --ff-only feature\/cart\nUpdating 194d1e1..3db006f\nFast-forward\n cart.js | 2 ++\n 1 file changed, 2 insertions(+)\n create mode 100644 cart.js\n<span class=\"gp\">$<\/span> git log --oneline --graph\n* 3db006f fix: typo in cart\n* 1b33fe9 feat: add cart\n* 194d1e1 docs: add license\n* b5f8f6b chore: initial commit<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 7, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Click each tab, then \u25b6 Run, and compare the <code>git log --graph<\/code> at the end of each output.<\/p>\n<div class=\"dd-tablewrap\"><table>\n<tr><th>Method<\/th><th>History on main<\/th><th>Pros<\/th><th>Cons<\/th><\/tr>\n<tr><td><strong>Merge commit<\/strong><\/td><td>All branch commits + a merge commit<\/td><td>Full, honest history; easy to see which commits came from which PR<\/td><td>Noisy log full of &#8220;fix typo&#8221; commits and merge bubbles<\/td><\/tr>\n<tr><td><strong>Squash<\/strong><\/td><td>One commit per PR<\/td><td>Clean, readable log; one commit to revert if the PR breaks something<\/td><td>Loses the small commits inside the PR<\/td><\/tr>\n<tr><td><strong>Rebase<\/strong><\/td><td>Branch commits replayed on top, straight line<\/td><td>Linear history without merge commits<\/td><td>Rewrites commit IDs; never rebase a branch other people are working on<\/td><\/tr>\n<\/table><\/div>\n<div class=\"dd-tip\"><strong>Our recommendation:<\/strong> for most teams, <strong>squash and merge<\/strong> is the best default. One PR = one commit on <code>main<\/code> = one clear message. It fits trunk-based development perfectly. You can turn off the other buttons in your repository settings so everyone uses the same method.<\/div>\n\n<h2 id=\"conflicts\">Fixing merge conflicts step by step<\/h2>\n<p>A <strong>merge conflict<\/strong> happens when two branches change the <em>same lines<\/em> of the same file and Git cannot know which version is right. It is not an error \u2013 Git is simply asking a human to decide. In this example, <code>main<\/code> changed the port to 5000 while the feature branch changed it to 8080:<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: conflict.sh\">\n<input type=\"radio\" name=\"v6\" id=\"v6f1\" class=\"f1\" checked>\n<input type=\"checkbox\" id=\"v6r1\" class=\"r1\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">conflict.sh \u2014 conflict-lab \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v6f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>conflict.sh<\/label>\n<label for=\"v6r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">conflict-lab \u203a conflict.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># Two branches change the SAME line of app.env<\/span><\/span>\n<span class=\"l\">git init -q -b main shop-api &amp;&amp; <span class=\"nb\">cd<\/span> shop-api<\/span>\n<span class=\"l\">git config user.name <span class=\"s\">\"Asha\"<\/span> &amp;&amp; git config user.email <span class=\"s\">\"asha@example.com\"<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">printf<\/span> <span class=\"s\">'PORT=3000\\nLOG_LEVEL=info\\n'<\/span> &gt; app.env<\/span>\n<span class=\"l\">git add . &amp;&amp; git commit -qm <span class=\"s\">\"chore: add config\"<\/span><\/span>\n<span class=\"l\">git switch -qc feature\/port<\/span>\n<span class=\"l\">sed -i <span class=\"s\">\"s\/3000\/8080\/\"<\/span> app.env &amp;&amp; git commit -qam <span class=\"s\">\"feat: use port 8080\"<\/span><\/span>\n<span class=\"l\">git switch -q main<\/span>\n<span class=\"l\">sed -i <span class=\"s\">\"s\/3000\/5000\/\"<\/span> app.env &amp;&amp; git commit -qam <span class=\"s\">\"fix: use port 5000\"<\/span><\/span>\n<span class=\"l\">git merge feature\/port<\/span>\n<span class=\"l\">git status --short<\/span>\n<span class=\"l\">cat app.env<\/span>\n<span class=\"l\"><span class=\"c\"># Fix it: keep the line you want, delete the markers, then commit<\/span><\/span>\n<span class=\"l\"><span class=\"nb\">printf<\/span> <span class=\"s\">'PORT=8080\\nLOG_LEVEL=info\\n'<\/span> &gt; app.env<\/span>\n<span class=\"l\">git add app.env<\/span>\n<span class=\"l\">git commit -qm <span class=\"s\">\"Merge feature\/port: use port 8080\"<\/span><\/span>\n<span class=\"l\">git log --oneline --graph<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (git 2.43, bash 5.2, Ubuntu)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> git init -q -b main shop-api &amp;&amp; cd shop-api\n<span class=\"gp\">$<\/span> git config user.name \"Asha\" &amp;&amp; git config user.email \"asha@example.com\"\n<span class=\"gp\">$<\/span> printf 'PORT=3000\\nLOG_LEVEL=info\\n' &gt; app.env\n<span class=\"gp\">$<\/span> git add . &amp;&amp; git commit -qm \"chore: add config\"\n<span class=\"gp\">$<\/span> git switch -qc feature\/port\n<span class=\"gp\">$<\/span> sed -i \"s\/3000\/8080\/\" app.env &amp;&amp; git commit -qam \"feat: use port 8080\"\n<span class=\"gp\">$<\/span> git switch -q main\n<span class=\"gp\">$<\/span> sed -i \"s\/3000\/5000\/\" app.env &amp;&amp; git commit -qam \"fix: use port 5000\"\n<span class=\"gp\">$<\/span> git merge feature\/port\nAuto-merging app.env\nCONFLICT (content): Merge conflict in app.env\nAutomatic merge failed; fix conflicts and then commit the result.\n<span class=\"gp\">$<\/span> git status --short\nUU app.env\n<span class=\"gp\">$<\/span> cat app.env\n&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD\nPORT=5000\n=======\nPORT=8080\n&gt;&gt;&gt;&gt;&gt;&gt;&gt; feature\/port\nLOG_LEVEL=info\n<span class=\"gp\">$<\/span> printf 'PORT=8080\\nLOG_LEVEL=info\\n' &gt; app.env\n<span class=\"gp\">$<\/span> git add app.env\n<span class=\"gp\">$<\/span> git commit -qm \"Merge feature\/port: use port 8080\"\n<span class=\"gp\">$<\/span> git log --oneline --graph\n*   0a76a86 Merge feature\/port: use port 8080\n|\\  \n| * e285f39 feat: use port 8080\n* | 5d52af9 fix: use port 5000\n|\/  \n* 80d6556 chore: add config<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 17, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Click \u25b6 Run and look at the <code>cat app.env<\/code> output: that is what a merge conflict looks like inside a file.<\/p>\n<p>Git marks the conflict inside the file:<\/p>\n<ul>\n<li><code>&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD<\/code> to <code>=======<\/code> is the version on your current branch (<code>main<\/code>).<\/li>\n<li><code>=======<\/code> to <code>&gt;&gt;&gt;&gt;&gt;&gt;&gt; feature\/port<\/code> is the incoming version.<\/li>\n<\/ul>\n<p>To resolve it: edit the file so it contains the final correct content, <strong>delete all three marker lines<\/strong>, then <code>git add<\/code> the file and <code>git commit<\/code>. VS Code shows &#8220;Accept Current \/ Accept Incoming \/ Accept Both&#8221; buttons above each conflict, which makes this easy. If you get lost, <code>git merge --abort<\/code> takes you back to before the merge.<\/p>\n<div class=\"dd-note\"><strong>The best fix is prevention:<\/strong> small branches that live for hours rarely conflict. Before opening a PR, run <code>git switch main &amp;&amp; git pull --rebase<\/code>, then update your branch with <code>git rebase main<\/code> (or <code>git merge main<\/code>) so you fix conflicts on your branch, not on <code>main<\/code>.<\/div>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"protect-main\">Protecting the main branch: branch rules, CI checks and CODEOWNERS<\/h2>\n<p>A workflow only works if it is enforced. On GitHub you do this with <strong>rulesets<\/strong> (or the older &#8220;branch protection rules&#8221;): <em>Settings \u2192 Rules \u2192 Rulesets \u2192 New branch ruleset<\/em>, target the default branch, and turn on:<\/p>\n<ul>\n<li><strong>Require a pull request before merging<\/strong>, with at least 1 approval, and &#8220;dismiss stale approvals when new commits are pushed&#8221;.<\/li>\n<li><strong>Require status checks to pass<\/strong> \u2013 pick your CI job (here: <code>test<\/code>) so a red pipeline blocks the merge button.<\/li>\n<li><strong>Block force pushes<\/strong> and <strong>restrict deletions<\/strong> so nobody can rewrite or delete <code>main<\/code>.<\/li>\n<li>Optional: <strong>require linear history<\/strong> (only squash or rebase merges) and <strong>require review from Code Owners<\/strong>.<\/li>\n<\/ul>\n<p>The <a href=\"https:\/\/docs.github.com\/en\/repositories\/configuring-branches-and-merges-in-your-repository\/managing-rulesets\/about-rulesets\" rel=\"noopener\" target=\"_blank\">GitHub rulesets documentation<\/a> lists every option. GitLab has the same idea under <em>Protected branches<\/em> and <em>Merge request approvals<\/em>. Three small files in your repo complete the setup:<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: ci.yml, CODEOWNERS, pull_request_template.md\">\n<input type=\"radio\" name=\"v7\" id=\"v7f1\" class=\"f1\" checked>\n<input type=\"radio\" name=\"v7\" id=\"v7f2\" class=\"f2\">\n<input type=\"radio\" name=\"v7\" id=\"v7f3\" class=\"f3\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">ci.yml \u2014 shop-api \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v7f1\" class=\"t1\"><b class=\"ic yml\">YML<\/b>ci.yml<\/label>\n<label for=\"v7f2\" class=\"t2\"><b class=\"ic txt\">TXT<\/b>CODEOWNERS<\/label>\n<label for=\"v7f3\" class=\"t3\"><b class=\"ic md\">MD<\/b>pull_request_template.md<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">shop-api \u203a .github \u203a workflows \u203a ci.yml<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># .github\/workflows\/ci.yml \u2013 runs on every pull request to main<\/span><\/span>\n<span class=\"l\"><span class=\"kc\">name<\/span>: CI<\/span>\n<span class=\"l\"><span class=\"kc\">on<\/span>:<\/span>\n<span class=\"l\">  <span class=\"kc\">pull_request<\/span>:<\/span>\n<span class=\"l\">    <span class=\"kc\">branches<\/span>: [<span class=\"n\">main<\/span>]<\/span>\n<span class=\"l\">  <span class=\"kc\">push<\/span>:<\/span>\n<span class=\"l\">    <span class=\"kc\">branches<\/span>: [<span class=\"n\">main<\/span>]<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"kc\">jobs<\/span>:<\/span>\n<span class=\"l\">  <span class=\"kc\">test<\/span>:<\/span>\n<span class=\"l\">    <span class=\"kc\">runs-on<\/span>: ubuntu-latest<\/span>\n<span class=\"l\">    <span class=\"kc\">steps<\/span>:<\/span>\n<span class=\"l\">      - <span class=\"kc\">uses<\/span>: actions\/checkout@v6<\/span>\n<span class=\"l\">      - <span class=\"kc\">uses<\/span>: actions\/setup-node@v6<\/span>\n<span class=\"l\">        <span class=\"kc\">with<\/span>:<\/span>\n<span class=\"l\">          <span class=\"kc\">node-version<\/span>: 24<\/span>\n<span class=\"l\">          <span class=\"kc\">cache<\/span>: npm<\/span>\n<span class=\"l\">      - <span class=\"kc\">run<\/span>: npm ci<\/span>\n<span class=\"l\">      - <span class=\"kc\">run<\/span>: npm run lint<\/span>\n<span class=\"l\">      - <span class=\"kc\">run<\/span>: npm test<\/span><\/code><\/pre>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 20, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>YAML<\/span><\/div>\n<\/div>\n<div class=\"dd-vsc-pane p2\">\n<div class=\"dd-vsc-crumbs\">shop-api \u203a .github \u203a CODEOWNERS<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># .github\/CODEOWNERS \u2013 who must review which files<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># The last matching line wins.<\/span><\/span>\n<span class=\"l\">*            <span class=\"s\">@your-org\/backend-team<\/span><\/span>\n<span class=\"l\">\/.github\/    <span class=\"s\">@your-org\/devops-team<\/span><\/span>\n<span class=\"l\">\/infra\/      <span class=\"s\">@your-org\/devops-team<\/span><\/span>\n<span class=\"l\">*.md         <span class=\"s\">@your-org\/docs-team<\/span><\/span><\/code><\/pre>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 6, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Plain Text<\/span><\/div>\n<\/div>\n<div class=\"dd-vsc-pane p3\">\n<div class=\"dd-vsc-crumbs\">shop-api \u203a .github \u203a pull_request_template.md<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"nf\">## What does this PR change?<\/span><\/span>\n<span class=\"l\">&lt;!-- One or two sentences. Link the issue: Closes #123 --&gt;<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"nf\">## How was it tested?<\/span><\/span>\n<span class=\"l\"><span class=\"k\">- [ ]<\/span> Unit tests pass locally<\/span>\n<span class=\"l\"><span class=\"k\">- [ ]<\/span> Tried the change manually<\/span>\n<span class=\"l\"><\/span>\n<span class=\"l\"><span class=\"nf\">## Checklist<\/span><\/span>\n<span class=\"l\"><span class=\"k\">- [ ]<\/span> Small and focused (ideally under 400 changed lines)<\/span>\n<span class=\"l\"><span class=\"k\">- [ ]<\/span> No secrets, keys or .env files committed<\/span>\n<span class=\"l\"><span class=\"k\">- [ ]<\/span> Docs or README updated if needed<\/span><\/code><\/pre>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 11, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Markdown<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Three files that live in the <code>.github<\/code> folder of your repo. Click the tabs to switch between them.<\/p>\n<ul>\n<li><strong><code>ci.yml<\/code><\/strong> runs lint and tests on every PR to <code>main<\/code>. Its job name (<code>test<\/code>) is what you select as a required status check.<\/li>\n<li><strong><code>CODEOWNERS<\/code><\/strong> automatically requests a review from the right team. Here, any change to the pipeline files in <code>.github\/<\/code> needs the DevOps team&#8217;s approval. Replace the <code>@your-org\/\u2026<\/code> names with your real GitHub teams.<\/li>\n<li><strong><code>pull_request_template.md<\/code><\/strong> pre-fills every new PR description with the same short checklist.<\/li>\n<\/ul>\n<div class=\"dd-tip\"><strong>Busy repo?<\/strong> When many PRs merge every hour, turn on GitHub&#8217;s <strong>merge queue<\/strong>. It tests each PR together with the PRs ahead of it, so two PRs that pass alone can&#8217;t break <code>main<\/code> together.<\/div>\n\n<h2 id=\"commits\">Good commit messages with Conventional Commits<\/h2>\n<p>Messages like &#8220;updated stuff&#8221; or &#8220;fix&#8221; tell your teammates nothing. <a href=\"https:\/\/www.conventionalcommits.org\/\" rel=\"noopener\" target=\"_blank\">Conventional Commits<\/a> is a simple, popular format: <code>&lt;type&gt;(optional scope): &lt;short summary&gt;<\/code>.<\/p>\n<div class=\"dd-tablewrap\"><table>\n<tr><th>Type<\/th><th>Use it for<\/th><th>Example<\/th><\/tr>\n<tr><td><code>feat<\/code><\/td><td>A new feature for users<\/td><td><code>feat(cart): add discount codes<\/code><\/td><\/tr>\n<tr><td><code>fix<\/code><\/td><td>A bug fix<\/td><td><code>fix(login): handle expired tokens<\/code><\/td><\/tr>\n<tr><td><code>docs<\/code><\/td><td>Documentation only<\/td><td><code>docs: add setup steps to README<\/code><\/td><\/tr>\n<tr><td><code>test<\/code><\/td><td>Adding or fixing tests<\/td><td><code>test(cart): cover empty cart<\/code><\/td><\/tr>\n<tr><td><code>ci<\/code> \/ <code>build<\/code><\/td><td>Pipeline or build changes<\/td><td><code>ci: cache npm in GitHub Actions<\/code><\/td><\/tr>\n<tr><td><code>refactor<\/code> \/ <code>chore<\/code><\/td><td>Code clean-up, upkeep<\/td><td><code>chore: update dependencies<\/code><\/td><\/tr>\n<\/table><\/div>\n<p>A <code>!<\/code> after the type (<code>feat!:<\/code>) marks a breaking change. In Part 9 we will use these messages to bump version numbers and write release notes automatically. You can enforce the format with a small <strong>Git hook<\/strong> \u2013 a script Git runs before it saves a commit:<\/p>\n<div class=\"dd-vsc\" role=\"group\" aria-label=\"Code editor: try-hook.sh, commit-msg\">\n<input type=\"radio\" name=\"v8\" id=\"v8f1\" class=\"f1\" checked>\n<input type=\"radio\" name=\"v8\" id=\"v8f2\" class=\"f2\">\n<input type=\"checkbox\" id=\"v8r1\" class=\"r1\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">try-hook.sh \u2014 hooks-demo \u2014 Visual Studio Code<\/span><\/div>\n<div class=\"dd-vsc-tabs\">\n<label for=\"v8f1\" class=\"t1\"><b class=\"ic sh\">$_<\/b>try-hook.sh<\/label>\n<label for=\"v8f2\" class=\"t2\"><b class=\"ic sh\">$_<\/b>commit-msg<\/label>\n<label for=\"v8r1\" class=\"run run1\" title=\"Show the output in the terminal\">\u25b6 Run<\/label>\n<\/div>\n<div class=\"dd-vsc-pane p1\">\n<div class=\"dd-vsc-crumbs\">hooks-demo \u203a try-hook.sh<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\">git init -q -b main demo &amp;&amp; <span class=\"nb\">cd<\/span> demo<\/span>\n<span class=\"l\">git config user.name <span class=\"s\">\"Asha\"<\/span> &amp;&amp; git config user.email <span class=\"s\">\"asha@example.com\"<\/span><\/span>\n<span class=\"l\">cp ..\/commit-msg .git\/hooks\/commit-msg &amp;&amp; chmod +x .git\/hooks\/commit-msg<\/span>\n<span class=\"l\"><span class=\"nb\">echo<\/span> <span class=\"s\">\"10% off\"<\/span> &gt; discount.txt &amp;&amp; git add discount.txt<\/span>\n<span class=\"l\">git commit -m <span class=\"s\">\"updated stuff\"<\/span><\/span>\n<span class=\"l\">git commit -m <span class=\"s\">\"feat(cart): add discount codes\"<\/span><\/span>\n<span class=\"l\">git log --oneline<\/span><\/code><\/pre>\n<div class=\"dd-vsc-term\"><div class=\"dd-term-head\"><span>TERMINAL<\/span><span>OUTPUT<\/span><span>PROBLEMS<\/span><span class=\"dd-term-note\">Real output (git 2.43, bash 5.2, Ubuntu)<\/span><\/div><pre><code><span class=\"gp\">$<\/span> git init -q -b main demo &amp;&amp; cd demo\n<span class=\"gp\">$<\/span> git config user.name \"Asha\" &amp;&amp; git config user.email \"asha@example.com\"\n<span class=\"gp\">$<\/span> cp ..\/commit-msg .git\/hooks\/commit-msg &amp;&amp; chmod +x .git\/hooks\/commit-msg\n<span class=\"gp\">$<\/span> echo \"10% off\" &gt; discount.txt &amp;&amp; git add discount.txt\n<span class=\"gp\">$<\/span> git commit -m \"updated stuff\"\n\u2716 Bad commit message: \"updated stuff\"\n  Use: &lt;type&gt;(scope): &lt;summary&gt;  e.g. feat(cart): add discount codes\n<span class=\"gp\">$<\/span> git commit -m \"feat(cart): add discount codes\"\n[main (root-commit) 4e7ef42] feat(cart): add discount codes\n 1 file changed, 1 insertion(+)\n create mode 100644 discount.txt\n<span class=\"gp\">$<\/span> git log --oneline\n4e7ef42 feat(cart): add discount codes<\/code><\/pre><\/div>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 7, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<div class=\"dd-vsc-pane p2\">\n<div class=\"dd-vsc-crumbs\">hooks-demo \u203a commit-msg<\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\">#!\/usr\/bin\/env bash<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># .git\/hooks\/commit-msg \u2013 rejects messages that don't follow<\/span><\/span>\n<span class=\"l\"><span class=\"c\"># Conventional Commits: &lt;type&gt;(optional scope): &lt;summary&gt;<\/span><\/span>\n<span class=\"l\"><span class=\"n\">msg<\/span>=<span class=\"k\">$(<\/span>head -n1 <span class=\"s\">\"<\/span><span class=\"n\">$1<\/span><span class=\"s\">\"<\/span><span class=\"k\">)<\/span><\/span>\n<span class=\"l\"><span class=\"n\">types<\/span>=<span class=\"s\">\"feat|fix|docs|style|refactor|test|chore|ci|build|perf\"<\/span><\/span>\n<span class=\"l\"><span class=\"n\">pattern<\/span>=<span class=\"s\">\"^(<\/span><span class=\"n\">$types<\/span><span class=\"s\">)(\\([a-z0-9-]+\\))?!?: .{1,72}<\/span>$<span class=\"s\">\"<\/span><\/span>\n<span class=\"l\"><span class=\"k\">if<\/span> [[ ! <span class=\"s\">\"<\/span><span class=\"n\">$msg<\/span><span class=\"s\">\"<\/span> =~ <span class=\"n\">$pattern<\/span> ]]; <span class=\"k\">then<\/span><\/span>\n<span class=\"l\">  <span class=\"nb\">echo<\/span> <span class=\"s\">\"\u2716 Bad commit message: \\\"<\/span><span class=\"n\">$msg<\/span><span class=\"s\">\\\"\"<\/span><\/span>\n<span class=\"l\">  <span class=\"nb\">echo<\/span> <span class=\"s\">\"  Use: &lt;type&gt;(scope): &lt;summary&gt;  e.g. feat(cart): add discount codes\"<\/span><\/span>\n<span class=\"l\">  <span class=\"nb\">exit<\/span> <span class=\"m\">1<\/span><\/span>\n<span class=\"l\"><span class=\"k\">fi<\/span><\/span><\/code><\/pre>\n<div class=\"dd-vsc-status\"><span>\u2387 main<\/span><span>Ln 11, Col 1<\/span><span class=\"sp\"><\/span><span>Spaces: 2<\/span><span>UTF-8<\/span><span>LF<\/span><span>Shell Script<\/span><\/div>\n<\/div>\n<\/div>\n<p class=\"dd-vsc-hint\">Open the <code>commit-msg<\/code> tab to read the hook, then go back to <code>try-hook.sh<\/code> and click \u25b6 Run: the first commit is rejected, the second one passes.<\/p>\n<p>A local hook only protects your own machine. Teams usually add the same check in CI too (for example with <code>commitlint<\/code>), or they check only the PR title when they squash-merge, because that title becomes the commit message on <code>main<\/code>.<\/p>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"errors\">Common Git errors and how to fix them<\/h2>\n<div class=\"dd-tablewrap\"><table>\n<tr><th>Error or problem<\/th><th>Why it happens<\/th><th>Fix<\/th><\/tr>\n<tr><td><code>! [rejected] main -&gt; main (fetch first)<\/code><\/td><td>The remote has commits you don&#8217;t have yet<\/td><td><code>git pull --rebase<\/code>, then push again<\/td><\/tr>\n<tr><td><code>protected branch hook declined<\/code> \/ &#8220;Changes must be made through a pull request&#8221;<\/td><td>You tried to push straight to a protected <code>main<\/code><\/td><td>Create a branch, push it and open a PR<\/td><\/tr>\n<tr><td><code>CONFLICT (content): Merge conflict in \u2026<\/code><\/td><td>Two branches changed the same lines<\/td><td>Edit the file, remove the markers, <code>git add<\/code>, <code>git commit<\/code> (or <code>git merge --abort<\/code>)<\/td><\/tr>\n<tr><td>&#8220;Required status check is expected&#8221; and the PR can&#8217;t merge<\/td><td>The check name in the rule doesn&#8217;t match a job that runs on PRs<\/td><td>Make sure the workflow has <code>on: pull_request<\/code> and select the exact job name<\/td><\/tr>\n<tr><td><code>fatal: Not possible to fast-forward, aborting.<\/code><\/td><td><code>--ff-only<\/code> was used but the branches have split<\/td><td>Rebase your branch on <code>main<\/code> first, or use a normal merge<\/td><\/tr>\n<tr><td>Rejected push after a rebase (<code>non-fast-forward<\/code>)<\/td><td>Rebase rewrote your branch&#8217;s commits<\/td><td><code>git push --force-with-lease<\/code> \u2013 only on your own branch<\/td><\/tr>\n<tr><td>Committed a secret or <code>.env<\/code> file<\/td><td>Missing <code>.gitignore<\/code> entry<\/td><td>Rotate the secret immediately, remove the file, add it to <code>.gitignore<\/code><\/td><\/tr>\n<\/table><\/div>\n<p>And here are the &#8220;undo&#8221; commands you will reach for most often:<\/p>\n<div class=\"dd-vsc dd-vsc-termonly\" role=\"group\" aria-label=\"Terminal: fixing common Git mistakes\">\n<div class=\"dd-vsc-bar\"><span class=\"dd-dots\"><i><\/i><i><\/i><i><\/i><\/span><span class=\"dd-vsc-title\">Terminal \u2014 fixing common Git mistakes<\/span><\/div>\n<pre class=\"dd-vsc-code\"><code><span class=\"l\"><span class=\"c\"># Change the message of your last commit (only if not pushed yet)<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git commit --amend -m \"fix(cart): round prices correctly\"<\/span>\n<span class=\"l\"><span class=\"c\"># Throw away local changes in one file<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git restore cart.js<\/span>\n<span class=\"l\"><span class=\"c\"># Unstage a file you added by mistake (keeps the changes)<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git restore --staged .env<\/span>\n<span class=\"l\"><span class=\"c\"># Undo the last commit but keep the changes in your files<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git reset --soft HEAD~1<\/span>\n<span class=\"l\"><span class=\"c\"># Undo a commit that is already on main (makes a new \"undo\" commit)<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git revert a1b2c3d<\/span>\n<span class=\"l\"><span class=\"c\"># Park unfinished work, switch branch, bring it back later<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git stash<\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git stash pop<\/span>\n<span class=\"l\"><span class=\"c\"># After rebasing YOUR OWN branch, update the remote safely<\/span><\/span>\n<span class=\"l\"><span class=\"gp\">$<\/span> git push --force-with-lease<\/span><\/code><\/pre>\n<\/div>\n<div class=\"dd-warn\"><strong>Never rewrite shared history:<\/strong> <code>git reset<\/code>, <code>--amend<\/code>, rebase and force-push are fine on your own unmerged branch. On <code>main<\/code> or any branch others use, undo with <code>git revert<\/code> instead \u2013 it adds a new commit and keeps everyone&#8217;s history in sync.<\/div>\n\n<h2 id=\"best-practices\">Git best practices for teams<\/h2>\n<ul>\n<li><strong>Protect <code>main<\/code><\/strong>: PR required, CI must pass, at least one approval, no force pushes.<\/li>\n<li><strong>Keep branches short-lived<\/strong> \u2013 merge within a day or two, ideally within hours.<\/li>\n<li><strong>Keep PRs small<\/strong> and focused on one change; split refactors from features.<\/li>\n<li><strong>Pick one merge method<\/strong> (squash is a great default) and disable the others.<\/li>\n<li><strong>Pull often<\/strong> with <code>git pull --rebase<\/code> to stay close to <code>main<\/code>.<\/li>\n<li><strong>Use feature flags<\/strong> to merge unfinished work safely instead of keeping long branches.<\/li>\n<li><strong>Write meaningful commit messages<\/strong> with Conventional Commits.<\/li>\n<li><strong>Delete merged branches<\/strong> (GitHub can do it automatically) so the branch list stays clean.<\/li>\n<li><strong>Never commit secrets<\/strong>; keep a good <code>.gitignore<\/code> from day one.<\/li>\n<\/ul>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"practice\">Practice task<\/h2>\n<div class=\"dd-note\">\n<p><strong>Set up a team-ready repository<\/strong> on your own GitHub account:<\/p>\n<ol>\n<li>Create a new repo with a small Node.js project (the one from <a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/06\/github-actions-ci-cd-nodejs\/\">our GitHub Actions guide<\/a> is perfect) and add the <code>ci.yml<\/code>, <code>CODEOWNERS<\/code> (use your own username) and <code>pull_request_template.md<\/code> files from this post.<\/li>\n<li>Add a ruleset for <code>main<\/code>: require a pull request and the <code>test<\/code> status check, block force pushes.<\/li>\n<li>Try <code>git push origin main<\/code> directly and read the error you get.<\/li>\n<li>Create <code>feature\/greeting<\/code>, add a feature behind a flag like <code>FLAG_GREETING<\/code>, open a PR and squash-merge it once CI is green.<\/li>\n<li>Create a merge conflict on purpose (two branches changing the same line) and resolve it.<\/li>\n<li><em>Bonus:<\/em> install the <code>commit-msg<\/code> hook and try a bad and a good commit message.<\/li>\n<\/ol>\n<\/div>\n<p>New to pipelines? The <a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/06\/github-actions-ci-cd-nodejs\/\">GitHub Actions CI\/CD for Node.js guide<\/a> walks through a complete workflow, and the free <a href=\"https:\/\/git-scm.com\/book\/en\/v2\/Git-Branching-Branches-in-a-Nutshell\" rel=\"noopener\" target=\"_blank\">Pro Git book<\/a> explains branching in depth.<\/p>\n\n<h2 id=\"faq\">Frequently asked questions<\/h2>\n<details><summary>What is the best Git branching strategy for CI\/CD?<\/summary><p>For most teams: GitHub Flow with small pull requests, moving towards trunk-based development as your tests improve. Both keep a single <code>main<\/code> branch that is always deployable, which is exactly what continuous delivery needs. Git Flow is better only for software released as numbered versions.<\/p><\/details>\n<details><summary>Is trunk-based development the same as committing directly to main?<\/summary><p>Not quite. Very small or very experienced teams sometimes do push straight to <code>main<\/code>, but most trunk-based teams still use tiny branches and quick pull requests so CI and a reviewer check every change. The key point is that branches live for hours, not days.<\/p><\/details>\n<details><summary>Should I use merge, squash or rebase?<\/summary><p>Squash and merge is the simplest default: one PR becomes one clean commit on <code>main<\/code>. Use merge commits if you want to keep every small commit, and rebase if you want a straight history without squashing. Whatever you choose, use the same method across the team.<\/p><\/details>\n<details><summary>What is the difference between git merge and git rebase?<\/summary><p><code>git merge<\/code> joins two branches with a new merge commit and keeps the original commits as they are. <code>git rebase<\/code> replays your commits on top of another branch, creating new commits with new IDs and a straight-line history. Rebase only branches that nobody else is using.<\/p><\/details>\n<details><summary>How big should a pull request be?<\/summary><p>As small as possible while still making sense on its own. Many teams aim for under 200\u2013400 changed lines. Small PRs get reviewed faster, have fewer bugs and rarely conflict.<\/p><\/details>\n<details><summary>What is the difference between a pull request and a merge request?<\/summary><p>Nothing important \u2013 it is the same idea with a different name. GitHub and Bitbucket say &#8220;pull request&#8221;, GitLab says &#8220;merge request&#8221;.<\/p><\/details>\n\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>You now know how a team uses Git in a CI\/CD world: short feature branches, pull requests checked by CI and a reviewer, a single merge method, a protected <code>main<\/code> branch and clear Conventional Commit messages. You also saw why <strong>trunk-based development<\/strong> \u2013 tiny branches merged every day, with feature flags for unfinished work \u2013 is the branching strategy that fits continuous delivery best. These habits make every later part of this series easier, because every pipeline we build will start from a clean Git event.<\/p>\n\n<div class=\"dd-next\"><strong>Next part \u2013 DevOps CI\/CD Part 4: Automated Testing for Pipelines: Unit, Integration and End-to-End.<\/strong> A protected <code>main<\/code> is only as safe as the tests that guard it. We will learn the testing pyramid and add unit, integration and end-to-end tests that run on every pull request.<\/div>\n\n<div class=\"dd-cta\">\n<h3>New posts every day on DevDojo<\/h3>\n<p>Follow the full <strong>DevOps CI\/CD series<\/strong> \u2013 30 parts from &#8220;what is DevOps&#8221; to a complete pipeline from commit to production. Bookmark <a href=\"https:\/\/devdojo.co.in\/\">devdojo.co.in<\/a> and tell us in the comments which branching strategy your team uses.<\/p>\n<\/div>\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Learn Git for teams: feature branches, pull requests, merge vs squash vs rebase, merge conflicts and trunk-based development for safe, fast CI\/CD.<\/p>\n","protected":false},"author":1,"featured_media":290,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[57],"tags":[153,61,151,155,152,121,154],"class_list":["post-291","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-branching-strategy","tag-devops","tag-git","tag-github","tag-pull-requests","tag-series-devops-ci-cd","tag-trunk-based-development"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.4 - aioseo.com -->\n\t<meta name=\"description\" content=\"Learn Git for teams: feature branches, pull requests, merge vs squash vs rebase, merge conflicts and trunk-based development for safe, fast CI\/CD.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Lokendra\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.4\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"DevDojo - Empowering Developers\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"DevOps CI\/CD Part 3: Git for Teams and Trunk-Based Development\" \/>\n\t\t<meta property=\"og:description\" content=\"Learn Git for teams: feature branches, pull requests, merge vs squash vs rebase, merge conflicts and trunk-based development for safe, fast CI\/CD.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-11-04T14:55:23+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-11-04T14:55:23+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"DevOps CI\/CD Part 3: Git for Teams and Trunk-Based Development\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Learn Git for teams: feature branches, pull requests, merge vs squash vs rebase, merge conflicts and trunk-based development for safe, fast CI\/CD.\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/#blogposting\",\"name\":\"Git for Teams: PRs and Trunk-Based Development\",\"headline\":\"DevOps CI\\\/CD Part 3: Git for Teams: Branches, Pull Requests and Trunk-Based Development\",\"author\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/devops-series-part-3-git-trunk-based-development.jpg\",\"width\":1200,\"height\":630,\"caption\":\"DevOps CI\\\/CD Part 3 banner: Git for Teams \\u2013 branches, pull requests and trunk-based development, with a Git graph of feature branches merging into main\"},\"datePublished\":\"2026-11-04T14:55:23+00:00\",\"dateModified\":\"2026-11-04T14:55:23+00:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/#webpage\"},\"articleSection\":\"DevOps, Branching Strategy, DevOps, Git, GitHub, Pull Requests, Series: DevOps CI\\\/CD, Trunk-Based Development\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/devdojo.co.in\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/devops\\\/#listItem\",\"name\":\"DevOps\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/devops\\\/#listItem\",\"position\":2,\"name\":\"DevOps\",\"item\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/devops\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/#listItem\",\"name\":\"DevOps CI\\\/CD Part 3: Git for Teams: Branches, Pull Requests and Trunk-Based Development\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/#listItem\",\"position\":3,\"name\":\"DevOps CI\\\/CD Part 3: Git for Teams: Branches, Pull Requests and Trunk-Based Development\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/devops\\\/#listItem\",\"name\":\"DevOps\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\",\"name\":\"DevDojo\",\"description\":\"Empowering Developers\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/\",\"name\":\"Lokendra\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/99f102f0cc9080237ce54b91304fd1b3b6fa29925cbaeba93cd796872e58b45c?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Lokendra\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/#webpage\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/\",\"name\":\"Git for Teams: PRs and Trunk-Based Development\",\"description\":\"Learn Git for teams: feature branches, pull requests, merge vs squash vs rebase, merge conflicts and trunk-based development for safe, fast CI\\\/CD.\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/devops-series-part-3-git-trunk-based-development.jpg\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/#mainImage\",\"width\":1200,\"height\":630,\"caption\":\"DevOps CI\\\/CD Part 3 banner: Git for Teams \\u2013 branches, pull requests and trunk-based development, with a Git graph of feature branches merging into main\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/11\\\/04\\\/devops-series-part-3-git-trunk-based-development\\\/#mainImage\"},\"datePublished\":\"2026-11-04T14:55:23+00:00\",\"dateModified\":\"2026-11-04T14:55:23+00:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#website\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/\",\"name\":\"DevDojo\",\"description\":\"Empowering Developers\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"Git for Teams: PRs and Trunk-Based Development","description":"Learn Git for teams: feature branches, pull requests, merge vs squash vs rebase, merge conflicts and trunk-based development for safe, fast CI\/CD.","canonical_url":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/#blogposting","name":"Git for Teams: PRs and Trunk-Based Development","headline":"DevOps CI\/CD Part 3: Git for Teams: Branches, Pull Requests and Trunk-Based Development","author":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"publisher":{"@id":"https:\/\/devdojo.co.in\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/devdojo.co.in\/wp-content\/uploads\/2026\/11\/devops-series-part-3-git-trunk-based-development.jpg","width":1200,"height":630,"caption":"DevOps CI\/CD Part 3 banner: Git for Teams \u2013 branches, pull requests and trunk-based development, with a Git graph of feature branches merging into main"},"datePublished":"2026-11-04T14:55:23+00:00","dateModified":"2026-11-04T14:55:23+00:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/#webpage"},"isPartOf":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/#webpage"},"articleSection":"DevOps, Branching Strategy, DevOps, Git, GitHub, Pull Requests, Series: DevOps CI\/CD, Trunk-Based Development"},{"@type":"BreadcrumbList","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/#listItem","position":1,"name":"Home","item":"https:\/\/devdojo.co.in\/","nextItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/devops\/#listItem","name":"DevOps"}},{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/devops\/#listItem","position":2,"name":"DevOps","item":"https:\/\/devdojo.co.in\/index.php\/category\/devops\/","nextItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/#listItem","name":"DevOps CI\/CD Part 3: Git for Teams: Branches, Pull Requests and Trunk-Based Development"},"previousItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/#listItem","position":3,"name":"DevOps CI\/CD Part 3: Git for Teams: Branches, Pull Requests and Trunk-Based Development","previousItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/devops\/#listItem","name":"DevOps"}}]},{"@type":"Organization","@id":"https:\/\/devdojo.co.in\/#organization","name":"DevDojo","description":"Empowering Developers","url":"https:\/\/devdojo.co.in\/"},{"@type":"Person","@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author","url":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/","name":"Lokendra","image":{"@type":"ImageObject","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/99f102f0cc9080237ce54b91304fd1b3b6fa29925cbaeba93cd796872e58b45c?s=96&d=mm&r=g","width":96,"height":96,"caption":"Lokendra"}},{"@type":"WebPage","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/#webpage","url":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/","name":"Git for Teams: PRs and Trunk-Based Development","description":"Learn Git for teams: feature branches, pull requests, merge vs squash vs rebase, merge conflicts and trunk-based development for safe, fast CI\/CD.","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/devdojo.co.in\/#website"},"breadcrumb":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/#breadcrumblist"},"author":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"creator":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"image":{"@type":"ImageObject","url":"https:\/\/devdojo.co.in\/wp-content\/uploads\/2026\/11\/devops-series-part-3-git-trunk-based-development.jpg","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/#mainImage","width":1200,"height":630,"caption":"DevOps CI\/CD Part 3 banner: Git for Teams \u2013 branches, pull requests and trunk-based development, with a Git graph of feature branches merging into main"},"primaryImageOfPage":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/#mainImage"},"datePublished":"2026-11-04T14:55:23+00:00","dateModified":"2026-11-04T14:55:23+00:00"},{"@type":"WebSite","@id":"https:\/\/devdojo.co.in\/#website","url":"https:\/\/devdojo.co.in\/","name":"DevDojo","description":"Empowering Developers","inLanguage":"en-US","publisher":{"@id":"https:\/\/devdojo.co.in\/#organization"}}]},"og:locale":"en_US","og:site_name":"DevDojo - Empowering Developers","og:type":"article","og:title":"DevOps CI\/CD Part 3: Git for Teams and Trunk-Based Development","og:description":"Learn Git for teams: feature branches, pull requests, merge vs squash vs rebase, merge conflicts and trunk-based development for safe, fast CI\/CD.","og:url":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/","article:published_time":"2026-11-04T14:55:23+00:00","article:modified_time":"2026-11-04T14:55:23+00:00","twitter:card":"summary_large_image","twitter:title":"DevOps CI\/CD Part 3: Git for Teams and Trunk-Based Development","twitter:description":"Learn Git for teams: feature branches, pull requests, merge vs squash vs rebase, merge conflicts and trunk-based development for safe, fast CI\/CD."},"aioseo_meta_data":{"post_id":"291","title":"Git for Teams: PRs and Trunk-Based Development","description":"Learn Git for teams: feature branches, pull requests, merge vs squash vs rebase, merge conflicts and trunk-based development for safe, fast CI\/CD.","keywords":null,"keyphrases":{"focus":{"keyphrase":"trunk-based development","score":71},"additional":[]},"primary_term":null,"canonical_url":null,"og_title":"DevOps CI\/CD Part 3: Git for Teams and Trunk-Based Development","og_description":"Learn Git for teams: feature branches, pull requests, merge vs squash vs rebase, merge conflicts and trunk-based development for safe, fast CI\/CD.","og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":"","og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"BlogPosting","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":"-1","robots_max_videopreview":"-1","robots_max_imagepreview":"large","priority":0,"frequency":"default","local_seo":null,"breadcrumb_settings":null,"limit_modified_date":false,"ai":{"faqs":[],"keyPoints":[],"schemas":[],"titles":[],"descriptions":[],"socialPosts":{"email":{"subject":"","preview":"","content":""},"linkedin":[],"twitter":[],"facebook":[],"instagram":[]}},"created":"2026-11-04 14:47:46","updated":"2026-11-04 16:32:52","focus_keyword":"trunk-based development","additional_keywords":null,"truseo_locale":null,"seo_analyzer_scan_date":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/devdojo.co.in\/\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/devdojo.co.in\/index.php\/category\/devops\/\" title=\"DevOps\">DevOps<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tDevOps CI\/CD Part 3: Git for Teams: Branches, Pull Requests and Trunk-Based Development\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/devdojo.co.in\/"},{"label":"DevOps","link":"https:\/\/devdojo.co.in\/index.php\/category\/devops\/"},{"label":"DevOps CI\/CD Part 3: Git for Teams: Branches, Pull Requests and Trunk-Based Development","link":"https:\/\/devdojo.co.in\/index.php\/2026\/11\/04\/devops-series-part-3-git-trunk-based-development\/"}],"_links":{"self":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/291","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/comments?post=291"}],"version-history":[{"count":1,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/291\/revisions"}],"predecessor-version":[{"id":292,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/291\/revisions\/292"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/media\/290"}],"wp:attachment":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/media?parent=291"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/categories?post=291"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/tags?post=291"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}