সরাসরি পর্বে যান
পর্ব ১৭ · ৭০-এর মধ্যে · মডিউল ৩

Branching strategy — Git Flow, GitHub Flow আর trunk-based development

Branching Strategies — Git Flow, GitHub Flow, GitLab Flow and Trunk-based Development

Taught in Bangla, this lesson explains why teams need a branching strategy and quantifies integration risk of long-lived branches with a runnable simulation. It covers Git Flow's permanent and temporary branches with a hands-on release and hotfix in plain Git, GitHub Flow, GitLab Flow and environment branches, trunk-based development with feature flags, release branches with cherry-pick, and choosing a strategy by product and team.

এই পর্বে যা শিখবেন

  • দলের কেন একটি সম্মত branching strategy লাগে — integration-এর ব্যথা, “merge hell” আর release-এর সমন্বয়
  • Git Flow-এর পাঁচ রকম branch আর তাদের সঠিক প্রবাহ — plain Git command দিয়ে, আসল আউটপুটসহ
  • GitHub Flow, GitLab Flow আর trunk-based development — নিয়ম, শর্ত আর সীমাবদ্ধতা
  • Release branch, cherry-pick, feature flag আর branch by abstraction — অসমাপ্ত কাজ নিরাপদে মূল branch-এ রাখা
  • Long-lived branch-এর ঝুঁকি হিসাব করা, আর product ও দল দেখে কৌশল বেছে নেওয়া ও যুক্তি দেওয়া

১ · দলের কেন একটি branching strategy লাগে

পর্ব ১২-এ (Branch — pointer, HEAD, switch আর detached HEAD) দেখেছেন, Git-এর কাছে একটি branch মাত্র একটি ছোট pointer — বানাতে খরচ নেই, মুছতেও না। পর্ব ১৩-এ (Merge আর conflict) দেখেছেন দুই branch কীভাবে merge হয়, আর কখন conflict হয়। প্রশ্ন হলো: পাঁচ, বিশ বা দুশো জন মানুষ যখন একই repository-তে কাজ করেন, তখন এই সস্তা branch-গুলো কে কখন খুলবেন, কতদিন রাখবেন, আর কোথায় মেলাবেন?

রেলের সময়সূচির কথা ভাবুন। লাইন আছে, ইঞ্জিন আছে, স্টেশন আছে — কিন্তু সময়সূচি না থাকলে প্রতিটি ট্রেন নিজের ইচ্ছেমতো ছাড়ত, আর জংশনে গিয়ে জট বাঁধত। সময়সূচি কোনো যন্ত্র নয়; এটি সবার মেনে চলা একটি চুক্তি — কোন ট্রেন কোন লাইনে, কখন, কোন জংশনে মিলবে। Git হলো রেললাইন আর ইঞ্জিন; branching strategy হলো সেই সময়সূচি।

চুক্তি না থাকলে দলগুলো সাধারণত তিন রকম ব্যথায় ভোগে:

Integration-এর ব্যথা

প্রত্যেকে নিজের branch-এ সপ্তাহের পর সপ্তাহ কাজ করেন। প্রত্যেকের কোড একা ঠিক চলে, কিন্তু একসাথে মেলাতে গেলেই conflict, ভাঙা build আর এমন bug, যা কারও একার নয়।

integration pain

Merge hell

Release-এর আগের সপ্তাহটা কাটে শুধু মেলাতে — শত শত conflict, কে কোন লাইন কেন বদলেছিলেন কেউ মনে করতে পারেন না। এই অবস্থাকে মানুষ ঠাট্টা করে integration hell বা merge hell বলেন।

merge hell

Release-এর সমন্বয়

“কোন কোডটা আসলে গ্রাহকের কাছে গেছে?” “জরুরি fix কোন branch-এ করব, আর সেটা পরের version-এ থাকবে তো?” লিখিত নিয়ম না থাকলে এসব প্রশ্নের উত্তর প্রতিবার আলাদা।

release coordination

একটি branching strategy অন্তত এই প্রশ্নগুলোর উত্তর লিখে রাখে:

  1. কোন branch-গুলো স্থায়ী (long-lived), আর কোনগুলো কাজ শেষে মুছে ফেলার জন্য (short-lived)?
  2. নতুন কাজের branch কোথা থেকে খোলা হবে, আর কোথায় merge হবে? কোন দিকে merge নিষেধ?
  3. একটি অস্থায়ী branch সর্বোচ্চ কতদিন বাঁচতে পারে?
  4. Release কোথা থেকে বের হয় — কোন branch, কোন tag? জরুরি fix কোথায় যায়, আর কীভাবে বাকি জায়গায় পৌঁছায়?
  5. মূল branch-এ ঢোকার শর্ত কী — review, সবুজ CI, কয়জনের অনুমোদন?

এই পর্বে চারটি বহুল আলোচিত উত্তর দেখব — Git Flow, GitHub Flow, GitLab Flow আর trunk-based development — তারপর শিখব নিজের দলের জন্য কীভাবে বাছবেন। মনে রাখবেন, এগুলো কোনো আন্তর্জাতিক মানদণ্ড নয়; প্রতিটি কোনো একজন লেখক বা প্রতিষ্ঠানের প্রস্তাব, আর বাস্তবে দলগুলো প্রায়ই মিশিয়ে ব্যবহার করে। নামও সবসময় এক অর্থে ব্যবহার হয় না — কেউ “GitHub Flow” বলতে যা বোঝান, আরেকজন তাকেই “feature branch workflow” বলেন।

Branching নিয়ে বিতর্ক Git-এর চেয়ে পুরোনো। ১৯৯০-এর দশকের বড় project-গুলোতে “integration phase” প্রায়ই সময়সূচি পিছিয়ে দিত; তার জবাবে Kent Beck-এর Extreme Programming দিনে কয়েকবার মেলানোর — continuous integration-এর — কথা বলে। আজকের বিতর্কও এই টানাপোড়েন: আলাদা রেখে নিয়ন্ত্রণ, না ঘন ঘন মিলিয়ে দ্রুত feedback.

২ · Long-lived branch আর integration-এর ঝুঁকি — হিসাব করে দেখি

“Branch বেশিদিন রাখলে ঝামেলা বাড়ে” — কথাটা অনেকে বলেন। কিন্তু কতটা বাড়ে? একটি সরল মডেল দিয়ে মেপে দেখা যাক। পর্ব ১৩-এর শেষে দেখেছিলেন, দুই branch যত বেশি লাইন বদলায়, conflict-এর সম্ভাবনা তত দ্রুত বাড়ে। এবার পুরো দলের দিক থেকে দেখি।

মডেল: ৬ জন developer, ৩০ কর্মদিবস, ২০০০ লাইনের একটি codebase. প্রত্যেকে দিনে ৫টি এলোমেলো লাইন বদলান। প্রত্যেকের branch বাঁচে T দিন, তারপর মূল branch-এ (trunk) merge হয় আর নতুন branch শুরু হয়। Merge-এর সময় এই branch যে লাইনগুলো বদলেছে, সেগুলোর কোনোটি যদি branch শুরুর পরে অন্য কেউ trunk-এ বদলে থাকেন, তবে সেই লাইন হাতে মেলাতে হবে। তিনটি T তুলনা করি — ১, ৩ আর ১০ দিন। Seed-যুক্ত PRNG (mulberry32) ব্যবহার করায় প্রতিবার একই ফল আসবে, আর তিনটি T-তেই হুবহু একই সম্পাদনাগুলো হবে — বদলাচ্ছে শুধু কতদিন পরপর মেলানো হয়।

// Integration risk: 6 developers, 30 working days, a 2000-line codebase.
// Each developer edits 5 random lines a day on a branch that lives T days,
// then merges. Lines this branch changed that others also changed on trunk
// since the branch started must be resolved by hand (a model, not real Git).
function mulberry32(a) { return () => { a = (a + 0x6D2B79F5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; }
const DEVS = 6, DAYS = 30, LINES = 2000, PER_DAY = 5;
function simulate(T) {
  const rnd = mulberry32(44);               // same seed: same edits for every T
  const trunk = [];                         // [{day, lines}] merged so far
  const open = Array.from({ length: DEVS }, () => ({ start: 0, lines: new Set() }));
  let merges = 0, conflicted = 0, toResolve = 0;
  for (let day = 0; day < DAYS; day++) {
    for (let d = 0; d < DEVS; d++)
      for (let k = 0; k < PER_DAY; k++) open[d].lines.add(Math.floor(rnd() * LINES));
    if ((day + 1) % T !== 0 && day !== DAYS - 1) continue;   // branches merge every T days
    for (let d = 0; d < DEVS; d++) {
      const b = open[d], others = new Set();
      for (const m of trunk) if (m.day >= b.start) for (const x of m.lines) others.add(x);
      const clash = [...b.lines].filter(x => others.has(x)).length;
      merges++; if (clash) conflicted++; toResolve += clash;
      trunk.push({ day, lines: b.lines });
      open[d] = { start: day + 1, lines: new Set() };
    }
  }
  return { merges, conflicted, toResolve };
}
console.log("branch life  merges  merges with conflict  lines to resolve  per merge");
for (const T of [1, 3, 10]) {
  const r = simulate(T);
  console.log(String(T + " day").padStart(11) + String(r.merges).padStart(8) +
    String(r.conflicted).padStart(22) + String(r.toResolve).padStart(18) +
    (r.toResolve / r.merges).toFixed(2).padStart(11));
}

আউটপুট:

branch life  merges  merges with conflict  lines to resolve  per merge
      1 day     180                     5                 5       0.03
      3 day      60                    15                16       0.27
     10 day      18                    14                53       2.94

মোট কাজ তিনবারই সমান (৬ × ৩০ × ৫ = ৯০০টি লাইন-সম্পাদনা)। তবুও হাতে মেলানোর লাইন ৫ থেকে বেড়ে ৫৩ — দশগুণের বেশি। ১ দিনের branch-এ ১৮০টি merge-এর মাত্র ৫টিতে সংঘর্ষ, প্রতিটিতে একটি লাইন। ১০ দিনের branch-এ ১৮টি merge-এর ১৪টিতেই সংঘর্ষ, গড়ে প্রায় ৩টি লাইন প্রতি merge-এ। শেষ কলামটি খেয়াল করুন: আয়ু ৩ গুণ হলে প্রতি merge-এর বোঝা প্রায় ৯ গুণ (০.০৩ → ০.২৭), ১০ গুণ হলে প্রায় ১০০ গুণ (০.০৩ → ২.৯৪)। অর্থাৎ প্রতি merge-এর বোঝা বাড়ে প্রায় T² হারে, আর মোট বোঝা প্রায় T হারে।

কেন T²? — একটি ছোট হিসাব

একটি branch T দিনে নিজে প্রায় 5T লাইন বদলায়। একই সময়ে বাকিরা trunk-এ গড়ে আরও কয়েকগুণ 5T লাইন ঢোকান। দুটি এলোমেলো set-এর প্রত্যাশিত মিল ≈ (প্রথম set-এর আকার × দ্বিতীয় set-এর আকার) ÷ মোট লাইন। দুটি আকারই T-এর সমানুপাতিক, তাই গুণফল T²-এর সমানুপাতিক। আবার মোট merge-এর সংখ্যা ৩০/T, তাই সব merge মিলিয়ে বোঝা ≈ (৩০/T) × T² ∝ T. শত-হাজারবার আলাদা seed দিয়ে চালালে গড় দাঁড়ায় প্রতি merge-এ প্রায় ০.০৩, ০.২৮ আর ২.৯৫ — উপরের ফলের খুব কাছে; seed ৪৪ সেজন্যই প্রতিনিধিত্বমূলক।

Branch-এর আয়ু ×২ → প্রতি merge-এ সংঘর্ষ প্রায় ×৪
ছোট ব্যবধানে বারবার মেলানো = ছোট ছোট, সহজ conflict

মডেলটি ইচ্ছে করে সরল। বাস্তবে বদল এলোমেলো নয় — কিছু “গরম” ফাইলে (route তালিকা, config, dependency-র তালিকা) ভিড় অনেক বেশি, তাই সংঘর্ষও বেশি। Git পাশাপাশি লাইনেও conflict দেখায়, যা এখানে গোনা হয়নি। আর সবচেয়ে বড় খরচ — semantic conflict, যেখানে লেখায় মিল নেই কিন্তু অর্থে বিরোধ — কোনো লাইন-গোনায় আসে না। তাই সংখ্যাগুলো সঠিক মাপ নয়, প্রবণতার ইঙ্গিত: দীর্ঘ branch-এ ঝুঁকি রৈখিকের চেয়ে দ্রুত বাড়ে।

তবে ইচ্ছাকৃত দীর্ঘজীবী branch — যেমন পুরোনো version-এর release branch, যেখানে শুধু বাছাই করা fix যায় — এই ঝুঁকির বাইরে। বিপদ হলো নতুন কাজের দীর্ঘজীবী branch.

৩ · Git Flow — দুটি স্থায়ী branch আর তিন রকম অস্থায়ী branch

২০১০ সালের জানুয়ারিতে ডাচ developer Vincent Driessen নিজের blog-এ “A successful Git branching model” নামে একটি লেখা প্রকাশ করেন। লেখাটি দ্রুত ছড়িয়ে পড়ে, আর মডেলটি পরিচিত হয় Git Flow নামে। এর মূল চিন্তা: release-এর জন্য তৈরি কোড আর চলমান উন্নয়নের কোড আলাদা লাইনে রাখা, আর প্রতিটি কাজের ধরনের জন্য আলাদা ধরনের branch.

Git Flow — পাঁচটি লেন

Long-lived main and develop; short-lived feature, release and hotfix branches (time flows left to right)

Git Flow-এর branch লেন main hotfix/* release/* develop feature/* tag v1.0.0tag v1.0.1 wallet-paymentorder-tracking version 1.0.0fix + 1.0.1
ছবি ১৭.১ — বড় বৃত্ত = --no-ff merge commit. Release আর hotfix দুই জায়গায় মেশে — main-এ আর develop-এ। অংশ ৪-এর আসল transcript ঠিক এই আকৃতির।
Branchআয়ুকোথা থেকে খোলেকোথায় মেশেকাজ
main (মূল লেখায় master)স্থায়ী——শুধু প্রকাশিত কোড; প্রতিটি merge একটি release, আর প্রতিটিতে একটি version tag
developস্থায়ীmain থেকে, একবার—পরের release-এর জন্য জমা হওয়া কাজ — integration branch
feature/*দিন থেকে সপ্তাহdevelopdevelop (--no-ff)একটি নতুন সুবিধা; main-এ সরাসরি কখনো নয়
release/*কয়েক দিনdevelopmain আর developRelease-এর প্রস্তুতি: version নম্বর, শেষ মুহূর্তের bug fix; নতুন feature নিষেধ
hotfix/*ঘণ্টা থেকে দিনmainmain আর developProduction-এর জরুরি সংশোধন, পরের নির্ধারিত release-এর অপেক্ষা না করে

কয়েকটি খুঁটিনাটি, যা প্রায়ই ভুল বলা হয়:

  • নামকরণ: মূল লেখায় নাম ছিল release-1.2, hotfix-1.2.1 ধাঁচের; feature/, release/, hotfix/ উপসর্গ জনপ্রিয় করে পরের git-flow extension.
  • সবসময় --no-ff: মূল লেখা feature merge-এ --no-ff ব্যবহারের পরামর্শ দেয়, যাতে history-তে feature-এর সীমানা থাকে আর পুরো feature একটি merge commit revert করে ফেরানো যায় (পর্ব ১৩-এর অংশ ৩)।
  • Back-merge: Release বা hotfix শুধু main-এ মেলালে কাজ অর্ধেক। ঠিক করা bug আর নতুন version নম্বর develop-এ না ফিরলে পরের release-এ bug আবার ফিরে আসে। একটি ব্যতিক্রম: hotfix-এর সময় কোনো release branch চালু থাকলে, মূল লেখা অনুযায়ী hotfix develop-এর বদলে সেই release branch-এ মেলানো হয় — release শেষে সেটি এমনিতেই develop-এ পৌঁছাবে।
  • git flow command মূল Git-এর অংশ নয়। এটি আলাদা করে ইনস্টল করা একটি extension; কয়েকটি ভিন্ন সংস্করণ আছে আর তাদের option আলাদা। পরের অংশে আমরা শুধু plain Git দিয়ে পুরো প্রবাহ চালাব — তাতে ভেতরে কী হচ্ছে, স্পষ্ট দেখা যাবে।

৪ · Git Flow হাতে-কলমে — plain Git-এ একটি release আর একটি hotfix

এই পর্বের সব Git আউটপুট আসল — একটি খালি পরীক্ষার folder-এ Git ২.৫৩ দিয়ে চালানো। লেখকের নাম “Rahim”, email rahim@example.com, আর প্রতিটি commit-এর সময় GIT_AUTHOR_DATE ও GIT_COMMITTER_DATE দিয়ে ২০২৬-০১-১৫ সকাল ১০:০১ (+06:00) থেকে প্রতিটি commit ও tag-এ এক মিনিট করে বাড়ানো, তাই একই ফাইল আর একই ধাপ মানলে আপনিও একই hash পাবেন। গল্পটি একটি কাল্পনিক অনলাইন বাজার অ্যাপের: শুরুতে main-এ একটি commit 2d3260e (“Initial commit”), যেখানে VERSION ফাইলে লেখা 0.1.0. প্রথমে feature আর release:

$ git switch -c develop
Switched to a new branch 'develop'
$ git switch -c feature/wallet-payment
Switched to a new branch 'feature/wallet-payment'
# (create payment.conf; git add payment.conf)
$ git commit -m "Add mobile wallet payment"
[feature/wallet-payment c8f1ddc] Add mobile wallet payment
 1 file changed, 1 insertion(+)
 create mode 100644 payment.conf
$ git switch develop
Switched to branch 'develop'
$ git merge --no-ff feature/wallet-payment --no-edit
Merge made by the 'ort' strategy.
 payment.conf | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 payment.conf
$ git branch -d feature/wallet-payment
Deleted branch feature/wallet-payment (was c8f1ddc).
$ git switch -c release/1.0 develop
Switched to a new branch 'release/1.0'
# (VERSION: 0.1.0 -> 1.0.0; git add VERSION)
$ git commit -m "Bump version to 1.0.0"
[release/1.0 7656b66] Bump version to 1.0.0
 1 file changed, 1 insertion(+), 1 deletion(-)
# meanwhile, another feature lands on develop: 41b9e33 "Add order tracking",
# merged with --no-ff as 3ed99bd (same commands as above, output omitted) …
$ git switch main
Switched to branch 'main'
$ git merge --no-ff release/1.0 --no-edit
Merge made by the 'ort' strategy.
 VERSION      | 2 +-
 payment.conf | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 payment.conf
$ git tag -a v1.0.0 -m "Release 1.0.0"
$ git switch develop
Switched to branch 'develop'
$ git merge --no-ff release/1.0 --no-edit
Merge made by the 'ort' strategy.
 VERSION | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git branch -d release/1.0
Deleted branch release/1.0 (was 7656b66).

Feature branch খোলা হলো develop থেকে, ফিরলও develop-এ — main ছুঁলই না। Release branch কাটার মুহূর্তে পরের release-এর “বিষয়বস্তু” জমে গেল: release/1.0-এ শুধু wallet payment আছে। এর পরে develop-এ order tracking ঢুকলেও সেটি ১.০-তে যাবে না — এটাই Git Flow-এর release-স্থিতির (stabilisation) মূল সুবিধা। main-এ merge-এর diff-এ VERSION আর payment.conf দুটিই আছে, কারণ main-এর দৃষ্টিতে দুটিই নতুন। কিন্তু develop-এ back-merge-এ শুধু VERSION — wallet payment সেখানে আগে থেকেই ছিল। git tag -a কোনো আউটপুট দেয় না; এটি একটি annotated tag, যাতে লেখক, সময় আর বার্তা থাকে (tag আর release-এর বিস্তারিত পর্ব ১৮ — ভালো commit, tag আর release). Merge-এর বার্তা Git নিজে বানিয়েছে (--no-edit); পরের log-এ দেখবেন develop-এ merge হলে বার্তার শেষে “into develop” যোগ হয়, main-এ হয় না।

এবার production-এ একটি ভুল ধরা পড়ল — README-তে ডেলিভারি ফি লেখা নেই, গ্রাহকেরা বিভ্রান্ত। পরের release পর্যন্ত অপেক্ষা করা যাবে না, আর develop-এ ইতিমধ্যে অপ্রকাশিত order tracking আছে, তাই সেখান থেকেও release করা যাবে না। এটাই hotfix branch-এর কাজ:

$ git switch -c hotfix/1.0.1 main
Switched to a new branch 'hotfix/1.0.1'
# (add the delivery fee line to README.md; git add)
$ git commit -m "Fix delivery fee shown on README"
[hotfix/1.0.1 f465007] Fix delivery fee shown on README
 1 file changed, 1 insertion(+)
# (VERSION: 1.0.0 -> 1.0.1; git add)
$ git commit -m "Bump version to 1.0.1"
[hotfix/1.0.1 d0aed8e] Bump version to 1.0.1
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git switch main
Switched to branch 'main'
$ git merge --no-ff hotfix/1.0.1 --no-edit
Merge made by the 'ort' strategy.
 README.md | 1 +
 VERSION   | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)
$ git tag -a v1.0.1 -m "Release 1.0.1"
$ git switch develop
Switched to branch 'develop'
$ git merge --no-ff hotfix/1.0.1 --no-edit
Merge made by the 'ort' strategy.
 README.md | 1 +
 VERSION   | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)
$ git branch -d hotfix/1.0.1
Deleted branch hotfix/1.0.1 (was d0aed8e).
$ git log --graph --oneline --all --decorate
*   a704cd1 (HEAD -> develop) Merge branch 'hotfix/1.0.1' into develop
|\
* \   b266439 Merge branch 'release/1.0' into develop
|\ \
* \ \   3ed99bd Merge branch 'feature/order-tracking' into develop
|\ \ \
| * | | 41b9e33 Add order tracking
|/ / /
| | | *   f94e273 (tag: v1.0.1, main) Merge branch 'hotfix/1.0.1'
| | | |\
| | | |/
| | |/|
| | * | d0aed8e Bump version to 1.0.1
| | * | f465007 Fix delivery fee shown on README
| | |/
| | *   34ce3fb (tag: v1.0.0) Merge branch 'release/1.0'
| | |\
| | |/
| |/|
| * | 7656b66 Bump version to 1.0.0
|/ /
* |   001c6ac Merge branch 'feature/wallet-payment' into develop
|\ \
| |/
|/|
| * c8f1ddc Add mobile wallet payment
|/
* 2d3260e Initial commit
$ git log --oneline --first-parent main
f94e273 Merge branch 'hotfix/1.0.1'
34ce3fb Merge branch 'release/1.0'
2d3260e Initial commit

--decorate branch আর tag-এর নাম বন্ধনীতে দেখায় (terminal-এ সাধারণত এমনিতেই দেখায়; output pipe বা ফাইলে গেলে দেখায় না)। Graph-টি জটিল, কিন্তু ছবি ১৭.১-এর সাথে মিলিয়ে পড়ুন: f94e273-এর দুই parent 34ce3fb (আগের main) আর d0aed8e (hotfix); a704cd1-এরও দ্বিতীয় parent d0aed8e — একই hotfix দুই জায়গায় গেছে। রেখার এই জট Git Flow-এর পরিচিত সমালোচনা: চারটি অস্থায়ী branch (দুটি feature, একটি release, একটি hotfix)-এর জন্য ছয়টি merge commit. কিন্তু শেষ command-টি দেখুন — main-এর first-parent history মাত্র তিন লাইন: প্রতিটি লাইন একটি প্রকাশিত version. “গ্রাহকের কাছে কী কী গেছে” প্রশ্নের এত পরিষ্কার উত্তরই Git Flow-এর সবচেয়ে বড় পাওনা।

প্রতিটি ধাপের পরে branch, tag আর VERSION (আসল hash)
main
develop
feature/*
release/1.0
hotfix/1.0.1
tag
VERSION
command
  1. দুটি স্থায়ী লাইন। main থেকে একবারই develop খোলা হলো। এখন দুটিই 2d3260e-এ। এরপর থেকে দৈনন্দিন কাজ হবে develop-কে ঘিরে; main-এ আসবে শুধু release আর hotfix.
  2. Feature branch. develop থেকে feature/wallet-payment, তাতে একটি commit c8f1ddc. develop আর main নড়েনি।
  3. Feature → develop. --no-ff merge-এ তৈরি হলো 001c6ac, যার দুই parent 2d3260e আর c8f1ddc. Feature branch মুছে ফেলা হলো — commit-গুলো develop-এর history-তে রয়ে গেল।
  4. Release branch কাটা। develop থেকে release/1.0; তাতে শুধু version নম্বর বদল (7656b66). একই সময়ে develop-এ order tracking এসে মিশল (3ed99bd) — কিন্তু সেটি ১.০-তে যাবে না, কারণ release branch আগেই কাটা হয়ে গেছে।
  5. Release → main, tag. main-এ --no-ff merge (34ce3fb) আর তার ওপর annotated tag v1.0.0. এই commit-টিই গ্রাহকের কাছে যায়। খেয়াল করুন, develop-এ VERSION এখনো ০.১.০ — back-merge বাকি।
  6. Back-merge. release/1.0 এবার develop-এ মেশে (b266439) — version নম্বর আর release branch-এ করা যেকোনো fix develop-এ পৌঁছায়। তারপর release branch মুছে ফেলা হয়; tag রয়ে যায়।
  7. Hotfix branch. Production-এ ভুল! develop-এ অপ্রকাশিত কাজ আছে, তাই hotfix খোলা হয় main থেকে — অর্থাৎ ঠিক যে কোড গ্রাহকের কাছে আছে, তার ওপর। দুটি commit: fix (f465007) আর version ১.০.১ (d0aed8e).
  8. Hotfix দুই দিকে। main-এ merge (f94e273) আর tag v1.0.1; তারপর develop-এ merge (a704cd1), যাতে পরের release-এ ভুলটি আবার না ফেরে। এখন আবার শুধু দুটি স্থায়ী branch বাকি।

একই প্রবাহ একটি ছোট মডেলেও চালিয়ে দেখা যায়। নিচের প্রোগ্রামে commit-এর DAG একটি সাধারণ তালিকা: প্রতিটি commit তার parent-দের মনে রাখে, আর কোন লেনে (branch-এর ধরনে) তৈরি হয়েছিল সেটিও। একটি Git Flow command-এর ক্রম চালিয়ে প্রোগ্রামটি লেন-ভিত্তিক graph ছাপে — o সাধারণ commit, M merge commit:

// A tiny commit-DAG model that runs a Git Flow command script and prints lanes.
const LANES = ["main", "hotfix", "release", "develop", "feature"];
const commits = [], refs = {}, tags = {}; let head = "main", n = 0;
const lane = b => LANES.find(l => b === l || b.startsWith(l + "/"));
function commit(msg, parents) {
  const id = "c" + (++n);
  commits.push({ id, msg, parents, lane: lane(head) });
  refs[head] = id;
}
const git = {
  commit: msg => commit(msg, refs[head] ? [refs[head]] : []),
  branch: (b, from = head) => { refs[b] = refs[from]; head = b; },   // switch -c b from
  switch: b => { head = b; },
  merge: b => commit("Merge " + b + " into " + head, [refs[head], refs[b]]),  // --no-ff
  tag: t => { tags[refs[head]] = t; },
  delete: b => { delete refs[b]; }
};
function show(title) {
  console.log("== " + title + "  (" + LANES.map(l => l.slice(0, 4)).join(" ") + ")");
  for (const c of commits) {
    const cells = LANES.map(l => (l === c.lane ? (c.parents.length > 1 ? "M" : "o") : ".").padEnd(5));
    console.log(c.id.padEnd(4) + cells.join("") + c.msg + (tags[c.id] ? "  [tag " + tags[c.id] + "]" : ""));
  }
}
git.commit("Initial commit"); git.branch("develop");
git.branch("feature/wallet"); git.commit("Add wallet payment");
git.switch("develop"); git.merge("feature/wallet"); git.delete("feature/wallet");
git.branch("release/1.0"); git.commit("Bump version to 1.0.0");
git.switch("main"); git.merge("release/1.0"); git.tag("v1.0.0");
git.switch("develop"); git.merge("release/1.0"); git.delete("release/1.0");
git.branch("hotfix/1.0.1", "main"); git.commit("Fix delivery fee, bump to 1.0.1");
git.switch("main"); git.merge("hotfix/1.0.1"); git.tag("v1.0.1");
git.switch("develop"); git.merge("hotfix/1.0.1"); git.delete("hotfix/1.0.1");
show("after feature, release, tag, hotfix, back-merge");
console.log("branches left:", Object.keys(refs).join(", "), "| parents of c9:", commits[8].parents.join(" + "));

আউটপুট:

== after feature, release, tag, hotfix, back-merge  (main hotf rele deve feat)
c1  o    .    .    .    .    Initial commit
c2  .    .    .    .    o    Add wallet payment
c3  .    .    .    M    .    Merge feature/wallet into develop
c4  .    .    o    .    .    Bump version to 1.0.0
c5  M    .    .    .    .    Merge release/1.0 into main  [tag v1.0.0]
c6  .    .    .    M    .    Merge release/1.0 into develop
c7  .    o    .    .    .    Fix delivery fee, bump to 1.0.1
c8  M    .    .    .    .    Merge hotfix/1.0.1 into main  [tag v1.0.1]
c9  .    .    .    M    .    Merge hotfix/1.0.1 into develop
branches left: main, develop | parents of c9: c6 + c7

নয়টি commit-এর পাঁচটিই merge commit (M) — একটি release আর একটি hotfix-এর জন্য। main-এর লেনে শুধু তিনটি চিহ্ন: শুরু আর দুটি tag-ওয়ালা release, ঠিক আসল --first-parent-এর মতো। c9-এর parent c6 + c7: hotfix-এর commit develop-এ পৌঁছেছে। শেষ লাইন দেখায় কাজ শেষে কেবল দুটি স্থায়ী branch থাকে। মডেলটি সরল — এখানে branch খোলার সময় working tree বা conflict নেই — কিন্তু history-র আকৃতি হুবহু Git Flow-এর। Hotfix-এর back-merge বাদ দিলে develop-এ ১.০.১-এর fix পৌঁছাত না — Git Flow-এর সবচেয়ে চেনা ভুল, যা অনুশীলন ৪-এ আবার দেখবেন।

Git Flow কোথায় মানায় — আর লেখক নিজে কী বলেছেন

Git Flow-এর কাঠামো ভালো কাজ করে যখন software নির্দিষ্ট version নম্বর নিয়ে, নির্দিষ্ট সময়ে প্রকাশ হয়, আর একই সময়ে একাধিক version চালু থাকে: দোকানে বিক্রি হওয়া desktop software, একটি library যার ব্যবহারকারীরা নিজেরাই ঠিক করেন কবে upgrade করবেন, যন্ত্রের firmware, বা গ্রাহকের নিজের server-এ বসানো product. সেখানে “release-এর জন্য স্থির কোড” আলাদা রাখার দাম আছে।

অন্যদিকে দিনে কয়েকবার deploy হওয়া web app-এ develop আর main-এর ফারাক প্রায়ই অর্থহীন বোঝা হয়ে দাঁড়ায়। মজার ব্যাপার হলো, এই কথা লেখক নিজেই বলেছেন। ২০২০ সালের মার্চে Driessen মূল লেখার ওপরে একটি “note of reflection” যোগ করেন। আমাদের জানা মতে তার সারকথা: এক দশকে software-এর ধরন বদলেছে; continuously delivered web app-এর দলের জন্য তিনি GitHub Flow-এর মতো সরলতর workflow বেছে নেওয়ার পরামর্শ দেন, আর স্পষ্টভাবে version-যুক্ত, একাধিক version চালু থাকা software-এর জন্য Git Flow এখনো উপযুক্ত হতে পারে। শব্দ ধরে উদ্ধৃত করার আগে মূল লেখাটি নিজে পড়ে নিন — কিন্তু শিক্ষাটি পরিষ্কার: একটি কৌশল তার প্রেক্ষাপটের সাথে বাঁধা।

৫ · GitHub Flow — main সবসময় deploy-যোগ্য

২০১১ সালে GitHub-এর সহ-প্রতিষ্ঠাতা Scott Chacon নিজের blog-এ লেখেন, GitHub নিজে Git Flow ব্যবহার করে না, কারণ তাঁরা দিনে বহুবার deploy করেন, আর সেখানে Git Flow অপ্রয়োজনীয় জটিল। তিনি যে সরল প্রবাহ বর্ণনা করেন, সেটি GitHub Flow নামে পরিচিত। আমাদের জানা মতে মূল লেখার নিয়মগুলো মোটামুটি এমন:

  1. main-এ (তখনকার নাম master) যা আছে, তা সবসময় deploy করা যায়।
  2. নতুন কাজের জন্য main থেকে বর্ণনামূলক নামের একটি branch খুলুন।
  3. নিয়মিত commit করুন, আর একই নামের branch-এ server-এ push করুন (push আর remote — পর্ব ১৪, Remote repository).
  4. মতামত দরকার হলে বা কাজ শেষ হলে একটি pull request খুলুন।
  5. কেউ review করে অনুমোদন দিলে main-এ merge করুন।
  6. main-এ merge হওয়ার পর সাথে সাথে deploy করুন।

প্রথম নিয়মটিই সবচেয়ে কঠিন: “সবসময় deploy-যোগ্য” মানে প্রতিটি merge-এর আগে test, CI আর review-কে বিশ্বাস করার মতো শক্ত হতে হবে।

  1. Branchfix/cart-totalmain থেকে
  2. Commit + pushgit push -uছোট ছোট ধাপে
  3. Pull requestPR #42আলোচনার জায়গা
  4. Review + CIchecks ✓অনুমোদন
  5. Mergemainbranch মুছুন
  6. Deployproductionসাথে সাথে
অবস্থা
main
branch
branch-এর বয়স
  1. Branch খোলা। git switch -c fix/cart-total main. নাম পড়েই যেন কাজ বোঝা যায় (নামের রীতি পর্ব ১২-এর অংশ ৮-এ)। Git Flow-এর মতো আলাদা develop নেই — শুরু সবসময় main থেকে, যা সবসময় production-এর কোডের সাথে মেলে।
  2. Commit আর push. ছোট ছোট commit, আর প্রথমবার git push -u origin fix/cart-total. Server-এ branch থাকায় দলের অন্যরা দেখতে পান কাজ কোথায়, আর CI প্রতিটি push-এ test চালাতে পারে।
  3. Pull request. কাজ শেষ হলে — বা মাঝপথে মতামত চাইলে — PR খোলা হয়। PR শুধু merge-এর বোতাম নয়; এটি আলোচনা, review আর CI ফলের এক জায়গা (বিস্তারিত পর্ব ১৬ — Pull request আর fork).
  4. Review আর CI. একজন সহকর্মী কোড দেখে একটি পরিবর্তন চাইলেন; আরেকটি commit push হলো, CI আবার সবুজ। অনেক দলে main সুরক্ষিত (protected) থাকে: অনুমোদন আর সবুজ check ছাড়া merge-এর বোতামই কাজ করে না।
  5. Merge আর branch মোছা। PR merge হলো — merge commit, squash বা rebase, দলের নিয়ম অনুযায়ী (পর্ব ১৫ — Rebase). Branch মুছে ফেলা হয়। পুরো যাত্রা এক দিনের মতো — branch ছোট রাখা GitHub Flow-এর অলিখিত শর্ত।
  6. Deploy. main সাথে সাথে production-এ যায়, সাধারণত স্বয়ংক্রিয় pipeline দিয়ে। কিছু দল merge-এর আগে branch থেকেই production-এ deploy করে যাচাই করে, তারপর merge করে — এটি GitHub Flow-এর একটি প্রচলিত রূপভেদ, platform আর দলভেদে আলাদা।

GitHub Flow কোথায় মানায়: একটিমাত্র চালু version (যেমন একটি web app বা SaaS), ঘন ঘন deploy, আর ভালো স্বয়ংক্রিয় test. কোথায় কষ্ট: একই সময়ে ১.x আর ২.x দুটোকেই fix দিতে হলে (এখানে এর কোনো নিয়ম নেই); app store-এর মতো বাইরের অনুমোদনের কারণে “merge করলাম, কিন্তু deploy হতে এক সপ্তাহ” হলে; আর test দুর্বল হলে, কারণ তখন “main সবসময় deploy-যোগ্য” কেবল একটি আশা। আরেকটি সূক্ষ্ম বিষয়: GitHub Flow branch-এর আয়ু বেঁধে দেয় না। দুই সপ্তাহের PR-ও GitHub Flow — আর তখন অংশ ২-এর সব ঝুঁকি ফিরে আসে।

৬ · GitLab Flow আর environment branch-এর বিতর্ক

GitHub Flow-এর ফাঁকগুলো পূরণ করতে ২০১৪ সালের দিকে GitLab একটি মাঝামাঝি প্রস্তাব দেয়, যা GitLab Flow নামে পরিচিত। ভিত্তি GitHub Flow-এর মতোই — feature branch, merge request (GitLab-এ pull request-এর নাম), main — কিন্তু সাথে দুই রকম বাড়তি branch, পরিস্থিতি অনুযায়ী। (GitLab-এর পরের documentation-এ বর্ণনা কয়েকবার বদলেছে; নিচে মূল ধারণাটি দেওয়া হলো।)

Environment branch

যখন merge করলেই deploy করা যায় না — যেমন নির্দিষ্ট সময়ে deploy-এর সুযোগ, বা app store-এর অনুমোদন। তখন main → pre-production → production নামের branch; কোড সবসময় “নিচের দিকে” merge হয়, আর প্রতিটি branch বলে দেয় সেই environment-এ কোন কোড আছে।

downstream merge

Release branch

যখন বাইরের ব্যবহারকারীদের জন্য একাধিক version চালু — যেমন 2-3-stable, 2-4-stable. নীতি “upstream first”: bug fix আগে main-এ, তারপর cherry-pick করে release branch-এ।

stable branches

Release branch-এর অংশটি অংশ ৮-এ trunk-based development-এর সাথে বিস্তারিত দেখব — দুই কৌশলে নিয়মটি প্রায় একই। এখানে বিতর্কিত অংশটি নিয়ে কথা বলি: environment branch.

Environment branch — কেন অনেকে একে anti-pattern বলেন

অনেক দলে একটি “সহজ” ব্যবস্থা দেখা যায়: dev, staging, production তিনটি branch; প্রতিটি branch-এ push হলে সেই environment-এ নতুন build হয়ে deploy হয়। Staging-এ পরীক্ষা শেষ হলে staging-কে production-এ merge করা হয়। শুনতে সহজ, কিন্তু Continuous Delivery-র সমর্থকেরা — বিশেষ করে Jez Humble ও David Farley-র Continuous Delivery (২০১০) বইয়ের ধারায় — কয়েকটি সমস্যার কথা বলেন:

  • প্রতি environment-এ নতুন build: Staging-এ যে binary test হলো, production-এ সেটি যায় না; যায় আরেকটি build. Dependency-র version বা build-এর পরিবেশ একটু বদলালেই “staging-এ চলেছিল” আর মূল্যহীন। পছন্দের নিয়ম হলো build একবার, একই artifact ধাপে ধাপে এগিয়ে নেওয়া (System Design কোর্সের Deployment আর release পর্বের অংশ ১)।
  • Branch-গুলো আলাদা হয়ে যায় (drift): কেউ জরুরি অবস্থায় সরাসরি production-এ fix করলেন, dev-এ ফেরত দিতে ভুলে গেলেন। কয়েক মাস পরে তিন branch-এর কোড তিন রকম, আর merge মানেই conflict.
  • Cherry-pick-এর জঙ্গল: “এই তিনটি feature production-এ যাবে, ওই দুটি নয়” — তখন merge-এর বদলে বাছাই করা cherry-pick, আর history-র প্রতি আস্থা শেষ।

তাহলে GitLab Flow কেন এটি প্রস্তাব করে? তাদের সংস্করণে নিয়ম কঠোর: merge সবসময় এক দিকে (main থেকে নিচে), environment branch-এ সরাসরি কাজ নিষেধ, আর branch-গুলো আসলে “কোন environment-এ কোন commit আছে” তার রেকর্ড। কিছু ক্ষেত্রে এটি সত্যিই কাজের — যেমন একটি static website বা infrastructure-এর config, যেখানে branch-এর অবস্থাই deploy-এর ঘোষণা (একে অনেক দল GitOps-এর অংশ হিসেবে দেখে)। সারকথা: environment branch তখনই বিপজ্জনক, যখন সেটি একমুখী নয়, আর প্রতিটি branch থেকে আলাদা build হয়।

বেশিরভাগ আধুনিক pipeline-এ একই কাজ branch ছাড়াই হয়: main-এর প্রতিটি commit একবার build হয়, তারপর সেই artifact-কে pipeline ধাপে ধাপে dev → staging → production-এ নিয়ে যায়, মাঝে অনুমোদন বা স্বয়ংক্রিয় পরীক্ষাসহ। “কোন environment-এ কোন version” তখন pipeline-এর রেকর্ড বা একটি tag বলে দেয়, branch নয়। এর নকশা পর্ব ৫৯-এ (Continuous delivery আর DevOps).

৭ · Trunk-based development — একটি লাইন, ছোট ধাপ, feature flag

Trunk-based development (সংক্ষেপে TBD) অংশ ২-এর হিসাবকে তার যৌক্তিক শেষ পর্যন্ত নিয়ে যায়: branch-এর আয়ু যদি ঝুঁকির মূল কারণ হয়, তাহলে আয়ুই কমিয়ে দিন। “Trunk” হলো মূল branch-এর পুরোনো নাম (Subversion-এর যুগ থেকে) — আজ সাধারণত main. ধারণাটি নতুন নয়; continuous integration-এর মূল কথাই এটি। আজকের পরিচিত বর্ণনার একটি বড় উৎস trunkbaseddevelopment.com নামের সাইট, যা Paul Hammant-সহ কয়েকজন লেখক রক্ষণাবেক্ষণ করেন। সেখানের মূল নিয়মগুলো সংক্ষেপে (শব্দ ধরে নয়):

  • সবাই একটি branch-এ মেলান। ছোট দলে সরাসরি trunk-এ commit; বড় দলে খুব short-lived feature branch — সাধারণত এক-দুই দিনের বেশি নয় — আর pull request-এ review.
  • Trunk কখনো ভাঙা থাকে না। প্রতিটি commit-এর আগে আর পরে build ও test; ভাঙলে প্রথম কাজ সেটি ঠিক করা বা ফেরত নেওয়া।
  • অসমাপ্ত কাজ লুকানো থাকে feature flag দিয়ে, বা বড় পরিবর্তন হয় branch by abstraction দিয়ে — কোডে, branch-এ নয়।
  • Release হয় trunk থেকে, অথবা দরকার হলে trunk থেকে কাটা একটি release branch থেকে; সেখানে fix আসে trunk থেকে cherry-pick করে (অংশ ৮)।

Trunk-based development — একটি প্রধান লেন

Tiny commits on main; a release branch cut from trunk receives a cherry-picked fix

Trunk-based development-এর লেন main (trunk) release/1.4 9d5de7a44ba59a4c283f9 654735c7b6618bপরের… aea1563 ছোট branch (ঘণ্টা) flag: off tag v1.4.0 tag v1.4.1 cherry-pick -x
ছবি ১৭.২ — Git Flow-এর পাঁচ লেনের বদলে একটি প্রধান লেন। Release branch-এ নতুন কাজ ঢোকে না, শুধু trunk থেকে বাছাই করা fix; এটি কখনো trunk-এ merge হয় না। Hash-গুলো অংশ ৮-এর আসল transcript থেকে।

Feature flag — অসমাপ্ত কাজ trunk-এ, কিন্তু ব্যবহারকারীর চোখের আড়ালে

প্রশ্ন জাগে: নতুন checkout পাতা বানাতে যদি দুই সপ্তাহ লাগে, আর branch এক দিনের বেশি রাখা নিষেধ হয়, তাহলে অর্ধেক বানানো পাতা কি production-এ চলে যাবে? যাবে — কিন্তু বন্ধ অবস্থায়। কোড deploy হওয়া আর ব্যবহারকারীর কাছে সুবিধাটি release হওয়া দুটি আলাদা ঘটনা; মাঝখানে থাকে একটি সুইচ:

if (flags.isOn("new_checkout", user)) {
  return newCheckout(cart);      // unfinished work, hidden by default
}
return checkout(cart);           // what every user sees today

বিয়েবাড়ির প্যান্ডেলের কথা ভাবুন। আলোকসজ্জার কারিগরেরা কয়েকদিন ধরে একটু একটু করে বাতি লাগান — দিনের বেলায়ও কাজ চলে, অতিথিরা আসা-যাওয়া করেন। বাতিগুলো লাগানো থাকে, কিন্তু মেইন সুইচ বন্ধ। বিয়ের সন্ধ্যায় সুইচ চাপলেই সব জ্বলে ওঠে, আর কোনো লাইনে সমস্যা হলে শুধু সেই লাইনের সুইচ বন্ধ করা যায় — পুরো প্যান্ডেল খুলে ফেলতে হয় না। Feature flag ঠিক এই মেইন সুইচ।

নিচের stepper-এ trunk-based দলের একটি দিন দেখুন। প্রথম তিনটি commit-এর বার্তা অংশ ৮-এর আসল transcript-এর সাথে মেলে।

নতুন checkout-এর অবস্থা
সময়
trunk-এ নতুন commit
branch-এর আয়ু
production-এ deploy
new_checkout flag
কারা নতুন checkout দেখেন
  1. দিনের শুরু। git pull --ff-only দিয়ে নিজের main হালনাগাদ। নতুন checkout এখনো শুধু একটি পরিকল্পনা; flag off, ক্রেতারা পুরোনো checkout দেখেন।
  2. প্রথম ছোট ধাপ, সরাসরি trunk-এ। newCheckout()-এর একটি খালি কাঠামো — কেউ ডাকে না, flag বন্ধ। Test সবুজ, তাই commit trunk-এ গেল আর production-এ deploy-ও হলো। ব্যবহারকারীর কোনো পরিবর্তন নেই — অথচ কোড এখন সবার সামনে, বাকিদের কাজের সাথে প্রতিদিন মিলছে।
  3. ৪০ মিনিটের branch. Cart-এর যোগফলের অংশটি একজন সহকর্মীর review চায়, তাই রুমা একটি ছোট branch খুলে PR দিলেন; ৪০ মিনিটে অনুমোদন, fast-forward merge, branch মুছে ফেলা। Deploy হলো — flag এখনো বন্ধ।
  4. Staging-এ flag চালু। শুধু staging environment-এ flag on — tester-রা নতুন checkout দেখছেন, production-এর ক্রেতারা নন। Tester-রা ধরলেন: VAT-এর লাইন নেই। একটি branch মেলাতে হয়নি, কোনো “integration-এর দিন” নেই — শুধু একটি সুইচ।
  5. Fix, তারপর ধীরে ধীরে চালু। VAT যোগের commit trunk-এ, deploy. এবার production-এ flag চালু শুধু ৫% ক্রেতার জন্য; সমস্যা হলে এক ক্লিকে বন্ধ — নতুন deploy লাগে না। শতাংশ ধরে ধীরে চালুর কৌশল (canary, progressive rollout) System Design কোর্সের Deployment আর release পর্বে।
  6. Flag মুছে ফেলা। সবাই নতুন checkout পাওয়ার পর flag আর পুরোনো checkout() দুটিই কোড থেকে মুছে ফেলা হলো। এই শেষ ধাপটি বাদ দিলে flag জমতে জমতে technical debt হয়ে দাঁড়ায় — আর ভুল flag-এ পুরোনো, ভুলে যাওয়া কোড আবার জেগে উঠতে পারে (ভাবনার প্রশ্ন ৩)।

Branch by abstraction — বড় পরিবর্তন, branch ছাড়াই

Flag ছোট সুবিধার জন্য ভালো, কিন্তু ধরুন পুরো payment library বদলাতে হবে — কয়েকশো জায়গায় ব্যবহার। দুই মাসের branch না খুলে branch by abstraction (নামটি Paul Hammant-এর দেওয়া বলে পরিচিত; Martin Fowler-ও এ নিয়ে লিখেছেন) কয়েকটি ছোট, প্রতিটি নিজে নিরাপদ ধাপে কাজটি করে:

  1. পুরোনো library-র সামনে একটি abstraction (interface) বসান, যার ভেতরে আপাতত পুরোনো implementation.
  2. ধীরে ধীরে সব ব্যবহারকারী কোডকে সরাসরি library-র বদলে abstraction ডাকতে বদলান — প্রতিটি ধাপ আলাদা commit, trunk-এ।
  3. একই abstraction-এর পেছনে নতুন implementation লিখুন, ধাপে ধাপে, trunk-এ — flag দিয়ে বন্ধ রেখে।
  4. Flag দিয়ে নতুন implementation-এ সুইচ করুন, প্রথমে অল্প ব্যবহারকারীর জন্য।
  5. সব ঠিক থাকলে পুরোনো implementation মুছে দিন; চাইলে abstraction-ও।

প্রতিটি ধাপের পর software চলে, test সবুজ থাকে, আর বাকি দল প্রতিদিনের কাজ চালিয়ে যায়। Interface আর dependency inversion-এর ধারণা পর্ব ২৮ (SOLID ২) আর ৩১-এ (Interface আর contract) গভীরভাবে আসবে।

৮ · Release branch আর cherry-pick — trunk থেকে release

সব দল প্রতিটি commit সাথে সাথে release করতে পারে না। Mobile app store-এ যায়, গ্রাহককে আগে জানাতে হয়, বা পুরোনো version-কে কিছুদিন fix দিতে হয়। Trunk-based দল তখন trunk থেকে একটি release branch কাটে — কিন্তু Git Flow-এর সাথে দুটি বড় পার্থক্য: (১) release branch-এ সরাসরি কাজ হয় না, fix আগে trunk-এ যায়, তারপর cherry-pick করে release branch-এ আসে; (২) release branch কখনো trunk-এ ফেরত merge হয় না। পুরো ঘটনাটি plain Git-এ (তারিখ এবার ২০২৬-০১-২০, বাকি নিয়ম আগের মতো; শুরুতে একটি “Initial commit” 9d5de7a):

# (add newCheckout() skeleton, still unused; git add)
$ git commit -m "Add newCheckout skeleton behind flag"
[main 44ba59a] Add newCheckout skeleton behind flag
 1 file changed, 1 insertion(+)
$ git switch -c ruma/cart-total
Switched to a new branch 'ruma/cart-total'
# (fill in the cart total; git add)
$ git commit -m "Compute cart total in newCheckout"
[ruma/cart-total 4c283f9] Compute cart total in newCheckout
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git switch main
Switched to branch 'main'
$ git merge --ff-only ruma/cart-total
Updating 44ba59a..4c283f9
Fast-forward
 checkout.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git branch -d ruma/cart-total
Deleted branch ruma/cart-total (was 4c283f9).
$ git switch -c release/1.4
Switched to a new branch 'release/1.4'
$ git tag -a v1.4.0 -m "Release 1.4.0"
$ git switch main
Switched to branch 'main'
# (add the VAT line; git add)
$ git commit -m "Add VAT line to newCheckout"
[main 654735c] Add VAT line to newCheckout
 1 file changed, 1 insertion(+), 1 deletion(-)
# (create limits.conf with max_items = 50; git add)
$ git commit -m "fix: limit cart to 50 items"
[main 7b6618b] fix: limit cart to 50 items
 1 file changed, 1 insertion(+)
 create mode 100644 limits.conf
$ git switch release/1.4
Switched to branch 'release/1.4'
$ git cherry-pick -x 7b6618b
[release/1.4 aea1563] fix: limit cart to 50 items
 Date: Tue Jan 20 10:06:00 2026 +0600
 1 file changed, 1 insertion(+)
 create mode 100644 limits.conf
$ git tag -a v1.4.1 -m "Release 1.4.1"
$ git log --graph --oneline --all --decorate
* aea1563 (HEAD -> release/1.4, tag: v1.4.1) fix: limit cart to 50 items
| * 7b6618b (main) fix: limit cart to 50 items
| * 654735c Add VAT line to newCheckout
|/
* 4c283f9 (tag: v1.4.0) Compute cart total in newCheckout
* 44ba59a Add newCheckout skeleton behind flag
* 9d5de7a Initial commit
$ git log -1 --format=%B release/1.4
fix: limit cart to 50 items

(cherry picked from commit 7b6618b5b66fce31811818022b1bdb61b0fe0324)

ছোট branch: ruma/cart-total-এর জীবন একটি commit; --ff-only merge-এ কোনো merge commit হয়নি, তাই history এক সরলরেখা। Release কাটা: release/1.4 তৈরি হলো 4c283f9-এ, আর সেখানেই v1.4.0. এরপর trunk এগিয়ে গেল — VAT-এর অসমাপ্ত কাজ (654735c) ১.৪-এ যাবে না। Fix: cart-এর সীমার bug ঠিক হলো trunk-এ (7b6618b), তারপর git cherry-pick -x শুধু সেই commit-এর পরিবর্তনটুকু release branch-এ নতুন একটি commit হিসেবে বসাল — aea1563. Hash আলাদা, কারণ parent আলাদা (4c283f9), committer-এর সময় আলাদা, আর বার্তায় -x-এর লাইন যোগ হয়েছে; Date: লাইনটি দেখায় author-এর মূল সময় রক্ষা পেয়েছে। -x বার্তার শেষে (cherry picked from commit …) লাইন যোগ করেছে — পরে যে কেউ বুঝবেন এই fix কোথা থেকে এসেছে। Graph-এ release branch একটি ছোট পার্শ্বরেখা, trunk-এ কোনো merge commit নেই। Cherry-pick-এর খুঁটিনাটি — conflict, --continue, একাধিক commit — পর্ব ৫৬-এ (Git-এর শক্তিশালী হাতিয়ার)।

উল্টো দিক কেন বিপজ্জনক: fix যদি শুধু release branch-এ করা হয় আর trunk-এ ফেরত দিতে কেউ ভুলে যান, পরের release (১.৫) trunk থেকে কাটা হবে — আর পুরোনো bug ফিরে আসবে। “আগে trunk, তারপর cherry-pick” (GitLab Flow-এর ভাষায় upstream first) এই ভুলকে কাঠামোগতভাবে অসম্ভব করে। জরুরি অবস্থায় trunk-এ fix অসম্ভব হলে (যেমন trunk-এ কোডটি আর নেই), release branch-এ fix করে তখনই trunk-এর জন্য আলাদা একটি কাজ খুলে রাখুন।

গবেষণা কী বলে — DORA আর Accelerate

Branching নিয়ে বিতর্ক প্রায়ই মতামতের লড়াই। কিছু তথ্য এসেছে DORA (DevOps Research and Assessment) দলের বার্ষিক জরিপ থেকে, যার ফলাফল Nicole Forsgren, Jez Humble ও Gene Kim-এর বই Accelerate (২০১৮)-এ সংকলিত। আমাদের জানা মতে বইটি জানায়: যেসব দল trunk-based development মানে — একই সময়ে তিনটির কম সক্রিয় branch, branch এক দিনের কম সময়ে trunk-এ মেশে, আর “code freeze” বা integration-এর আলাদা পর্যায় নেই — তারা software delivery-র মাপকাঠিতে (কত ঘন ঘন deploy, পরিবর্তন production-এ পৌঁছাতে কত সময়, কত পরিবর্তন সমস্যা ঘটায়, সমস্যা থেকে ফিরতে কত সময়) গড়ে ভালো করে। এই DORA metric-গুলোর হিসাব পর্ব ৫৯-এ।

তবে এটি জরিপভিত্তিক সম্পর্ক (correlation), প্রতিটি দলের জন্য নিশ্চিত কারণ নয়: একই দলগুলোর প্রায়ই ভালো test, CI আর দ্রুত review-ও আছে, আর জরিপের দলগুলো মূলত service বা web software বানায়। কীভাবে পড়বেন — ভাবনার প্রশ্ন ৫-এ।

৯ · কৌশল বেছে নেওয়া — product, দল, release-এর গতি আর নিয়ন্ত্রণ

চারটি কৌশল পাশাপাশি রাখলে দেখা যায়, এরা আসলে একটি মাত্রার ওপর বসানো: বাঁয়ে “আলাদা রাখুন, নির্দিষ্ট দিনে মেলান” (Git Flow), ডানে “সবসময় মেলান, সুইচ দিয়ে লুকান” (TBD). GitHub Flow আর GitLab Flow মাঝখানে।

বিষয়Git FlowGitHub FlowGitLab FlowTrunk-based
স্থায়ী branchmain, developmainmain + environment বা release branchmain (+ দরকারে release branch)
কাজের branch-এর আয়ুদিন থেকে সপ্তাহবাঁধা নেই, সাধারণত ছোটবাঁধা নেই, সাধারণত ছোটঘণ্টা থেকে ১–২ দিন, বা branch নেই
অসমাপ্ত কাজ কোথায়feature branch-এfeature branch-এfeature branch-এtrunk-এ, flag-এর আড়ালে
একাধিক version-এ fixhotfix ও release branchনিয়ম নেইrelease branch, upstream firstrelease branch, cherry-pick
প্রধান শর্তশৃঙ্খলা (back-merge ভোলা যাবে না)ভালো test আর CIএকমুখী mergeখুব ভালো test, CI, flag, দ্রুত review
মানানসইversion-যুক্ত, নির্ধারিত release-এর productএকটি চালু version-এর web appবাইরের অনুমোদনে আটকানো deploy, একাধিক versionঘন ঘন deploy, যেকোনো আকারের দল

বাছাইয়ের সময় চারটি প্রশ্ন সবচেয়ে বেশি কাজে লাগে:

  • Product-এর ধরন আর version: একই সময়ে কয়টি version-কে fix দিতে হয়? একটি হলে release branch-এর জটিলতা লাগে না।
  • Release-এর গতি (cadence): দিনে কয়েকবার, সপ্তাহে একবার, না তিন মাসে একবার? আর deploy কি আপনার হাতে, না app store বা গ্রাহকের হাতে?
  • দলের আকার আর পরিপক্বতা: কতজন, কতটা স্বয়ংক্রিয় test, review কত দ্রুত হয়? দুর্বল test নিয়ে TBD মানে প্রতিদিন ভাঙা trunk.
  • নিয়ন্ত্রণ আর audit: ব্যাংকিং, স্বাস্থ্য বা সরকারি কাজে প্রায়ই প্রমাণ রাখতে হয় — কোন পরিবর্তন কে অনুমোদন দিলেন, কোন version কবে production-এ গেল। এর উত্তর branch-এর সংখ্যায় নয়; সুরক্ষিত branch, বাধ্যতামূলক review, tag আর pipeline-এর রেকর্ডে। Trunk-based দলও এগুলো পূরণ করতে পারে।

এই প্রশ্নগুলোকে নিয়মে লিখে ফেললে একটি ছোট “পরামর্শদাতা” বানানো যায়। নিচের নিয়মগুলো কোনো মানদণ্ড নয় — আলোচনার জন্য লেখা, যাতে প্রতিটি নিয়ম নিয়ে আপনি তর্ক করতে পারেন। চারটি কাল্পনিক দলের জন্য চালিয়ে দেখুন:

// A rule-based branching-strategy recommender. The rules are a teaching aid,
// written out so you can argue with them: each rule adds or removes points.
const RULES = [
  ["Git Flow",    "2+ versions supported at once", p => p.versions >= 2, +3],
  ["Git Flow",    "scheduled releases (< 1 per week)", p => p.deploysPerWeek < 1, +2],
  ["Git Flow",    "deploys 5+ times a week", p => p.deploysPerWeek >= 5, -3],
  ["GitHub Flow", "deploys at least weekly", p => p.deploysPerWeek >= 1, +3],
  ["GitHub Flow", "only one live version", p => p.versions === 1, +2],
  ["GitHub Flow", "2+ versions supported at once", p => p.versions >= 2, -3],
  ["GitHub Flow", "weak test automation", p => p.tests === "low", -1],
  ["GitLab Flow", "release gated by an outside step (store, client)", p => p.gated, +3],
  ["GitLab Flow", "2+ versions supported at once", p => p.versions >= 2, +1],
  ["GitLab Flow", "regulated: audit of what reached production", p => p.regulated, +1],
  ["Trunk-based", "strong automated tests and CI", p => p.tests === "high", +3],
  ["Trunk-based", "deploys 5+ times a week", p => p.deploysPerWeek >= 5, +2],
  ["Trunk-based", "weak test automation", p => p.tests === "low", -4],
  ["Trunk-based", "large team (30+), merge pain grows", p => p.team >= 30, +1],
];
const profiles = [
  { name: "web shop",      deploysPerWeek: 15,  versions: 1, team: 12, tests: "high",   gated: false, regulated: false },
  { name: "desktop app",   deploysPerWeek: 0.1, versions: 3, team: 8,  tests: "medium", gated: false, regulated: false },
  { name: "banking app",   deploysPerWeek: 0.5, versions: 2, team: 35, tests: "medium", gated: true,  regulated: true },
  { name: "2-person team", deploysPerWeek: 3,   versions: 1, team: 2,  tests: "low",    gated: false, regulated: false },
];
for (const p of profiles) {
  const score = { "Git Flow": 0, "GitHub Flow": 0, "GitLab Flow": 0, "Trunk-based": 0 };
  for (const [s, , test, pts] of RULES) if (test(p)) score[s] += pts;
  const ranked = Object.entries(score).sort((a, b) => b[1] - a[1]);
  const close = ranked[0][1] - ranked[1][1] <= 1 ? "  (close: discuss!)" : "";
  console.log(p.name.padEnd(14) + ranked.map(([s, v]) => s + " " + v).join(" | ") + close);
}
const why = RULES.filter(r => r[2](profiles[2])).map(r => r[0] + ": " + r[1] + " (" + (r[3] > 0 ? "+" : "") + r[3] + ")");
console.log("why, for banking app ->\n  " + why.join("\n  "));

আউটপুট:

web shop      GitHub Flow 5 | Trunk-based 5 | GitLab Flow 0 | Git Flow -3  (close: discuss!)
desktop app   Git Flow 5 | GitLab Flow 1 | Trunk-based 0 | GitHub Flow -3
banking app   Git Flow 5 | GitLab Flow 5 | Trunk-based 1 | GitHub Flow -3  (close: discuss!)
2-person team GitHub Flow 4 | Git Flow 0 | GitLab Flow 0 | Trunk-based -4
why, for banking app ->
  Git Flow: 2+ versions supported at once (+3)
  Git Flow: scheduled releases (< 1 per week) (+2)
  GitHub Flow: 2+ versions supported at once (-3)
  GitLab Flow: release gated by an outside step (store, client) (+3)
  GitLab Flow: 2+ versions supported at once (+1)
  GitLab Flow: regulated: audit of what reached production (+1)
  Trunk-based: large team (30+), merge pain grows (+1)

Desktop app (তিনটি চালু version, বছরে কয়েকটি release) স্পষ্টভাবে Git Flow. দুই জনের দল-এ test দুর্বল, তাই TBD-র −৪ — GitHub Flow জেতে; দুর্বল test নিয়ে প্রতিদিন trunk-এ মেলানো ঝুঁকিপূর্ণ। দুটি টাই সবচেয়ে শিক্ষণীয়। Web shop-এ GitHub Flow আর TBD সমান — স্বাভাবিক, কারণ খুব ছোট PR-সহ GitHub Flow আর short-lived branch-সহ TBD বাস্তবে প্রায় একই জিনিস; পার্থক্য branch-এর আয়ুর লিখিত সীমায় আর flag-এর ব্যবহারে। Banking app-এ Git Flow আর GitLab Flow সমান, আর “why” তালিকা দেখায় কোন নিয়ম কত পয়েন্ট দিল। খেয়াল করুন একটি দুর্বলতা: trunk থেকে কাটা release branch-ও একাধিক version সামলাতে পারে (অংশ ৮), কিন্তু নিয়মে TBD-কে তার জন্য কোনো পয়েন্ট দেওয়া হয়নি। এমন একটি নিয়ম যোগ করলে ফল কীভাবে বদলায়? — এটাই আসল অনুশীলন: পরামর্শদাতার উত্তর নয়, নিয়মগুলো নিয়ে দলের আলোচনা।

Branch-এর নাম আর সুরক্ষা — নিয়মকে কাগজ থেকে ব্যবস্থায়

নামের Git-নিয়ম আর প্রচলিত উপসর্গ (feature/, fix/, hotfix/, release/) পর্ব ১২-এর অংশ ৮-এ দেখেছেন। কৌশলের দিক থেকে কয়েকটি বাড়তি রীতি:

  • নাম কৌশলের সাথে মিলিয়ে: Git Flow-এ উপসর্গই বলে দেয় branch কোথা থেকে খোলা আর কোথায় মিশবে; release branch-এ version নম্বর (release/1.4) রাখলে tag-এর সাথে মেলানো সহজ। TBD-তে branch এত ছোট যে নাম প্রায়ই ticket নম্বর বা লেখকের নামসহ (ruma/cart-total).
  • সুরক্ষিত branch (branch protection): hosting platform-গুলোতে স্থায়ী branch-এ নিয়ম বসানো যায় — সরাসরি push নিষেধ, অন্তত একজনের অনুমোদন, সবুজ CI check বাধ্যতামূলক, force push আর মুছে ফেলা নিষেধ, কখনো কখনো linear history বাধ্যতামূলক। নাম আর বিস্তারিত platform-ভেদে আলাদা, আর লেখার সময়ে কিছু সুবিধা শুধু নির্দিষ্ট plan-এ পাওয়া যায়।
  • Merge queue: ব্যস্ত trunk-এ দুটি PR আলাদাভাবে সবুজ, কিন্তু একসাথে ভাঙে (semantic conflict). Merge queue প্রতিটি PR-কে সর্বশেষ trunk-এর সাথে মেলানো অবস্থায় test করে, তবেই ঢোকায়। TBD-র বড় দলে এটি প্রায় অপরিহার্য।

Monorepo হলে: অনেকগুলো project একই repository-তে থাকলে branching-এর প্রশ্ন আরও তীক্ষ্ণ হয় — দশটি দলের দশটি দীর্ঘজীবী branch মানে দশগুণ integration-এর ঝুঁকি, তাই বড় monorepo-গুলো সাধারণত trunk-based পথে যায়, আর প্রতিটি PR-এ শুধু প্রভাবিত অংশের test চালায়। Google-এর কর্মীরা ২০১৬ সালে একটি প্রবন্ধে (Potvin ও Levenberg, Communications of the ACM) নিজেদের বিশাল monorepo-তে trunk-based কাজের বর্ণনা দিয়েছেন — তবে তাঁদের version control Git নয়, নিজস্ব ব্যবস্থা। Monorepo, submodule আর subtree পর্ব ১৯-এ (বড় repository).

আরও গভীরে: মূল উৎসগুলো নিজে পড়ার মতো: Vincent Driessen-এর “A successful Git branching model” (২০১০; ২০২০-এর মন্তব্যসহ), Scott Chacon-এর “GitHub Flow” (২০১১), trunkbaseddevelopment.com, আর Martin Fowler-এর দীর্ঘ প্রবন্ধ “Patterns for Managing Source Code Branches” (২০২০), যেখানে mainline, release branch, environment branch, integration-এর ঘনত্ব — সবকিছু “pattern” হিসেবে তুলনা করা হয়েছে। Scott Chacon ও Ben Straub-এর বই Pro Git-এর “Branching Workflows” অধ্যায় বিনামূল্যে পড়া যায়। গবেষণার জন্য Accelerate (২০১৮) আর পরের বছরগুলোর DORA প্রতিবেদন।

পরীক্ষা ও ইন্টারভিউতে:

  • Git Flow-এর পাঁচ রকম branch কী কী? — স্থায়ী main আর develop; অস্থায়ী feature/* (develop থেকে, develop-এ), release/* (develop থেকে, main আর develop-এ), hotfix/* (main থেকে, main আর develop-এ)।
  • Hotfix কেন develop থেকে নয়, main থেকে খোলা হয়? — develop-এ অপ্রকাশিত কাজ আছে; fix বসাতে হবে ঠিক সেই কোডে, যা production-এ চলছে।
  • Trunk-based development-এ অসমাপ্ত feature কীভাবে production-এ নিরাপদ থাকে? — feature flag (বা branch by abstraction) দিয়ে বন্ধ রেখে; deploy আর release আলাদা।
  • Release branch-এ bug fix কোথায় আগে করবেন? — trunk/main-এ, তারপর git cherry-pick -x দিয়ে release branch-এ, যাতে পরের release-এ bug না ফেরে।
  • দীর্ঘজীবী feature branch কেন ঝুঁকিপূর্ণ? — সংঘর্ষ বাড়ে আয়ুর বর্গের হারে, feedback দেরিতে আসে, আর semantic conflict ধরা পড়ে release-এর ঠিক আগে।

১০ · AI-এর যুগে — branching-এর পরামর্শে AI-কে যাচাই

“আমাদের দলের জন্য একটি Git workflow বানিয়ে দিন” — AI কয়েক সেকেন্ডে সুন্দর ছক, command আর CI ফাইল দিয়ে দেয়। কিন্তু এই বিষয়ে তার কিছু চেনা ভুল আছে:

AI-এর ভুলকীভাবে ধরবেন
প্রেক্ষাপট না জেনেই সবার জন্য Git Flow (বা সবার জন্য TBD) — কারণ ইন্টারনেটে সেটিই বেশি লেখাআগে নিজের চারটি উত্তর দিন: কয়টি version, deploy কত ঘন ঘন, test কতটা, audit লাগে কি না; AI-কে সেগুলো দিয়ে যুক্তি চান
git flow command-কে মূল Git-এর অংশ ধরে নেওয়া, বা অস্তিত্বহীন option বানানোgit help আর extension-এর নিজের documentation; প্রথমে plain Git-এ কী ঘটে বুঝুন
Release বা hotfix-এর back-merge বাদ দেওয়া; release branch-কে trunk-এ merge করতে বলাপ্রতিটি fix শেষে কোন কোন branch-এ থাকবে, কাগজে আঁকুন; git branch --contains <hash> দিয়ে যাচাই
Release branch “ঠিক করতে” git reset --hard বা git push --force — কোনো সতর্কবার্তা ছাড়াসুরক্ষিত branch-এ force push বন্ধ রাখুন; ধ্বংসাত্মক command আগে একটি throwaway clone-এ চালান
-x ছাড়া cherry-pick, বা ভুল দিকে (release থেকে trunk) cherry-pickনিয়ম লিখে রাখুন: আগে trunk, তারপর cherry-pick -x; বার্তায় উৎসের লাইন আছে কি না দেখুন
Feature flag বসায়, কিন্তু মোছার পরিকল্পনা দেয় নাপ্রতিটি flag-এর মালিক আর মেয়াদ; flag-এর তালিকা নিয়মিত পরিষ্কার
DORA বা “৮০% দল TBD ব্যবহার করে” জাতীয় বানানো পরিসংখ্যান, বা ভুল সাল ও লেখকমূল উৎস (Accelerate, DORA প্রতিবেদন, মূল blog post) নিজে খুলে মেলান
Prompt-এ পুরো CI config বা repository-র URL — সাথে token বা গোপন মানAI-কে দেওয়ার আগে গোপন মান মুছুন (পর্ব ৫৭)

নিচের prompt-এ AI-কে আগে প্রশ্ন করতে, তারপর বিকল্পগুলোর তুলনা করতে বলা হয়েছে — যাতে আপনি যুক্তি যাচাই করতে পারেন, শুধু একটি “উত্তর” না পান।

আমাদের দলের জন্য একটি Git branching strategy বাছাইয়ে সাহায্য চাই। তথ্য: - product: [web app / mobile app / desktop software / library] - একসাথে কয়টি version-কে fix দিতে হয়: [সংখ্যা] - deploy বা release কত ঘন ঘন, আর কে নিয়ন্ত্রণ করে (আমরা / app store / গ্রাহক): [...] - দলের আকার, test automation আর CI কতটা ভালো: [...] - audit বা নিয়ন্ত্রক শর্ত: [...] ১. কোনো তথ্য অস্পষ্ট বা কম থাকলে আগে প্রশ্ন করো। ২. Git Flow, GitHub Flow, GitLab Flow আর trunk-based development — প্রতিটির পক্ষে ও বিপক্ষে আমাদের তথ্য ধরে যুক্তি দাও, শেষে একটি সুপারিশ আর তার শর্ত। ৩. একটি release আর একটি জরুরি fix-এর পুরো প্রবাহ শুধু plain git command দিয়ে দেখাও; প্রতিটি command কোন branch বা ref বদলায় তা লেখো। কোনো reset --hard বা force push দেবে না। ৪. কোনো গবেষণা বা পরিসংখ্যান উল্লেখ করলে উৎসের নাম ও সাল দাও; নিশ্চিত না হলে স্পষ্ট বলো।

AI-এর দেওয়া workflow হাতে পেলে — চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১Git Flow-এর লেখক নিজেই দশ বছর পরে সতর্ক করলেন। একটি জনপ্রিয় মডেল কেন এভাবে “পুরোনো” হয়ে যায়?

২০১০ সালে Driessen যখন লেখাটি প্রকাশ করেন, তখন অনেক software-এর জীবন ছিল এমন: কয়েক মাস ধরে পরের version বানানো, তারপর একটি নির্দিষ্ট দিনে release, তারপর সেই version-এর জন্য কিছুদিন patch. CD-ROM বা download-এর মাধ্যমে বিতরণ, গ্রাহক নিজের সময়ে upgrade করেন। এমন জগতে “release-এর জন্য স্থির করা কোড” আর “পরের version-এর চলমান কাজ” আলাদা রাখা খুব যৌক্তিক — Git Flow-এর release/* আর develop ঠিক এই সমস্যার সমাধান। Git তখন নতুন, আর অনেক দলের কাছে Git Flow-ই ছিল প্রথম স্পষ্ট, ছবি-সহ নিয়মের বই। সহজে বোঝা যায় এমন একটি diagram একটি ধারণাকে কতটা ছড়াতে পারে, Git Flow তার উদাহরণ।

পরের দশকে web আর cloud-এর software অনেক বেশি “service” হয়ে গেল: একটিমাত্র চালু version, দিনে বহুবার deploy, ব্যবহারকারী upgrade-এর কথা জানেনই না। সেখানে develop থেকে main-এ আনুষ্ঠানিক release একটি অপ্রয়োজনীয় ধাপ, আর release branch মানে সপ্তাহখানেকের দেরি। ঠিক এজন্যই ২০১১-তে Scott Chacon GitHub Flow-এর কথা লেখেন, আর ২০২০-তে Driessen নিজেই মূল লেখার ওপর মন্তব্য যোগ করেন যে continuously delivered software-এর জন্য সরলতর workflow ভালো হতে পারে, কিন্তু version-যুক্ত software-এর জন্য তাঁর মডেল এখনো মানানসই হতে পারে।

শিক্ষাটি software engineering-এর একটি পুরোনো সত্য মনে করিয়ে দেয়: পদ্ধতি একটি নির্দিষ্ট প্রেক্ষাপটের সমস্যার উত্তর। পর্ব ৪-এ (SDLC আর process model) দেখেছেন, waterfall-কেও অনেক সময় তার প্রেক্ষাপট থেকে বিচ্ছিন্ন করে “খারাপ” বলা হয়। কিন্তু সমস্যা মডেলে নয়, মডেলকে ভুল জায়গায় বসানোয়। Git Flow আজও একটি library বা firmware-এর দলের জন্য চমৎকার হতে পারে, আবার একটি দৈনিক deploy হওয়া web app-এর দলে শুধু অভ্যাসের বশে টিকে থাকা ভার।

মূল উপলব্ধি: কোনো কৌশল গ্রহণের আগে জিজ্ঞেস করুন — এটি কোন সমস্যার সমাধানে বানানো হয়েছিল, আর সেই সমস্যা আমাদের আছে কি না। প্রেক্ষাপট বদলালে কৌশল বদলানো ব্যর্থতা নয়, প্রকৌশল।

প্র ০২“শেষে একবারে মেলাব” কেন “প্রতিদিন একটু একটু মেলাব”-এর চেয়ে খারাপ — শুধু conflict-এর সংখ্যা, না আরও কিছু?

অংশ ২-এর সিমুলেশন একটি দিক দেখিয়েছে: মোট কাজ সমান হলেও branch-এর আয়ু ১০ গুণ হলে হাতে মেলানোর লাইন দশগুণের বেশি, আর প্রতি merge-এ প্রায় শতগুণ। কিন্তু সংখ্যার চেয়েও বড় তিনটি খরচ আছে, যা মডেলে আসেনি।

প্রথম — স্মৃতির খরচ। আজকের লেখা conflict আজ মেলানো সহজ; দুই সপ্তাহ আগের লেখা কোড মেলাতে গেলে মনে করতে হয় “কেন এটা বদলেছিলাম”, আর অন্যজনের উদ্দেশ্য জানতে তাঁকে খুঁজতে হয়। দ্বিতীয় — feedback-এর দেরি। দুই সপ্তাহ পরে জানা গেল আপনার নকশা অন্য দলের নকশার সাথে খাপ খায় না — তখন দুই সপ্তাহের কাজ নতুন করে করতে হয়। প্রতিদিন মেলালে একই অমিল প্রথম দিনেই ধরা পড়ত, যখন বদলানো সস্তা। পর্ব ১-এ দেখা “পরিবর্তনের খরচ সময়ের সাথে বাড়ে” নীতি এখানেও খাটে। তৃতীয় — semantic conflict. দুটি বড় branch-এর মধ্যে একটি function-এর নাম বদল আর সেই function-এর নতুন ব্যবহার — লেখায় কোনো সংঘর্ষ নেই, কিন্তু প্রোগ্রাম ভাঙে (পর্ব ১৩-এর অংশ ৮)। বড় merge-এ এমন ভাঙন খুঁজে পাওয়া খড়ের গাদায় সুঁই খোঁজা।

Martin Fowler তাঁর continuous integration-এর লেখায় একটি জনপ্রিয় কথা বলেন, যার সারমর্ম: যে কাজ কষ্টদায়ক, সেটা বেশি বেশি করুন — ঘন ঘন করলে প্রতিবারের কষ্ট এত ছোট হয় যে সহজ হয়ে যায়। ঈদের আগে বাসের টিকিটের ভিড়ের সাথে তুলনা করা যায়: সবাই শেষ দিনে কাউন্টারে গেলে বিশৃঙ্খলা, অথচ একই সংখ্যক মানুষ সারা মাসে ছড়িয়ে গেলে লাইনই হয় না।

তবে শর্ত একটাই: প্রতিটি merge-এর আগে স্বয়ংক্রিয় test আর build — নইলে ঘন ঘন মেলানো মানে ঘন ঘন অজানা ভাঙন।

মূল উপলব্ধি: দেরিতে মেলানোর আসল দাম conflict-এর সংখ্যা নয় — দেরিতে পাওয়া feedback আর অদৃশ্য অর্থগত বিরোধ। ছোট batch আর ভালো test একসাথে এই দাম কমায়।

প্র ০৩Feature flag এত শক্তিশালী হলে বিপদ কোথায়? Knight Capital-এর ২০১২ সালের ঘটনা কী শেখায়?

Feature flag deploy আর release আলাদা করে — অসমাপ্ত কাজ trunk-এ রাখা যায়, সমস্যা হলে এক ক্লিকে বন্ধ করা যায়, অল্প ব্যবহারকারীকে আগে দেখানো যায়। কিন্তু প্রতিটি flag কোডে একটি নতুন শাখা যোগ করে: দুটি flag মানে চারটি সম্ভাব্য অবস্থা, দশটি মানে এক হাজারের বেশি। সবগুলো test করা অসম্ভব। আর যে flag-এর কাজ শেষ, সেটি মুছে না ফেললে তার পেছনের পুরোনো কোড “মৃত” হয়েও কোডে বেঁচে থাকে — dead code.

সবচেয়ে উদ্ধৃত উদাহরণ মার্কিন শেয়ার-লেনদেনের প্রতিষ্ঠান Knight Capital. মার্কিন SEC-এর ২০১৩ সালের আদেশে বর্ণিত ঘটনা অনুযায়ী, ১ আগস্ট ২০১২ তারিখে তারা আটটি server-এ নতুন লেনদেনের কোড বসায়, কিন্তু একটি server-এ নতুন কোড পৌঁছায়নি। নতুন কোড একটি পুরোনো flag-কে নতুন অর্থে ব্যবহার করেছিল; আর সেই আটকে পড়া server-এ ওই flag বহু বছর আগে বন্ধ করা একটি পুরোনো ফাংশন (“Power Peg”) আবার চালু করে দেয়। প্রায় ৪৫ মিনিটে কয়েক মিলিয়ন অনাকাঙ্ক্ষিত order যায়, আর প্রতিষ্ঠানটির ক্ষতি হয় প্রায় ৪৬০ মিলিয়ন ডলার। এখানে কয়েকটি ব্যর্থতা একসাথে — হাতে করা deployment, যাচাইয়ের অভাব, পুরোনো কোড না মোছা, আর একটি flag-এর অর্থ বদলানো।

শিক্ষাগুলো সরাসরি: (১) প্রতিটি flag-এর একজন মালিক আর একটি মেয়াদ থাকুক; release flag কাজ শেষে কোড থেকে মুছে ফেলুন (অংশ ৭-এর stepper-এর শেষ ধাপ)। (২) একটি flag-এর নাম বা অর্থ কখনো নতুন কাজে পুনর্ব্যবহার করবেন না। (৩) দীর্ঘমেয়াদি “operational” flag (যেমন জরুরি অবস্থায় কোনো সুবিধা বন্ধের kill switch) আর স্বল্পমেয়াদি release flag আলাদা করে চিনুন। (৪) Deploy স্বয়ংক্রিয় আর যাচাইযোগ্য রাখুন — সব server-এ একই artifact. এ বিষয়ে System Design কোর্সের Deployment আর release পর্বে আরও আছে।

মূল উপলব্ধি: Feature flag branch-এর জটিলতাকে কোডের ভেতরে সরিয়ে আনে। সেই জটিলতাও ঋণ — নিয়মিত পরিশোধ (মুছে ফেলা) না করলে সুদে-আসলে ফেরত আসে।

প্র ০৪নিয়ন্ত্রিত শিল্পে (ব্যাংক, স্বাস্থ্য) কি trunk-based development চলে, নাকি সেখানে Git Flow-ই নিরাপদ?

একটি প্রচলিত ধারণা হলো: যেখানে audit, অনুমোদন আর পরিবর্তন-নিয়ন্ত্রণ কঠোর, সেখানে বেশি branch মানেই বেশি নিয়ন্ত্রণ। কিন্তু নিয়ন্ত্রক সাধারণত branch-এর আকৃতি জানতে চান না; জানতে চান কয়েকটি প্রশ্নের প্রমাণ — কোন পরিবর্তন কে করলেন, কে review ও অনুমোদন দিলেন, কোন test চলেছিল, কোন version কবে production-এ গেল, আর সমস্যা হলে কীভাবে ফেরানো হলো। এর প্রতিটির উত্তর branch-এর সংখ্যা ছাড়াই দেওয়া যায়।

Trunk-based দলের ক্ষেত্রে: সুরক্ষিত main-এ সরাসরি push নিষেধ, প্রতিটি পরিবর্তন একটি PR-এ অন্তত একজনের অনুমোদনসহ — এতে “চার চোখের নিয়ম” (একজন লেখেন, অন্যজন অনুমোদন দেন) পূরণ হয়। PR-এর সাথে ticket নম্বর থাকলে requirement থেকে কোড পর্যন্ত traceability (পর্ব ২৪-এ আসবে)। প্রতিটি deploy pipeline-এ একটি tag আর রেকর্ড রাখে। আর feature flag-এর প্রতিটি বদলও লগ হতে পারে, যাতে “কখন কার জন্য সুবিধাটি চালু হলো” বলা যায়।

উল্টো দিকে, Git Flow-এর release branch কিছু নিয়ন্ত্রিত পরিবেশে সত্যিই কাজে লাগে — যেমন যখন প্রতিটি release-এর আগে বাইরের একটি সংস্থার যাচাই বা সনদ লাগে, যাতে সপ্তাহ বা মাস যায়, আর সেই সময়ে কোড স্থির রাখতে হয়। তখন trunk থেকে release branch কাটা (TBD-র ধারা) আর develop থেকে release branch কাটা (Git Flow) — দুটিই একই প্রয়োজন মেটায়; পার্থক্য হলো দৈনন্দিন কাজ কোথায় মেলে।

মূল উপলব্ধি: নিয়ন্ত্রণ আসে প্রমাণযোগ্য প্রক্রিয়া থেকে — সুরক্ষিত branch, review, tag, pipeline-এর রেকর্ড — branch-এর সংখ্যা থেকে নয়। কৌশল বাছুন release-এর বাস্তব শর্ত দেখে।

প্র ০৫DORA বলছে trunk-based দলগুলো ভালো করে। তাহলে কি কাল থেকেই সব branch নিষেধ করে দেবেন? গবেষণা পড়ার সঠিক উপায় কী?

Accelerate (২০১৮) বইটি কয়েক বছরের State of DevOps জরিপের ওপর দাঁড়ানো — হাজারো উত্তরদাতা, তাঁদের দলের অভ্যাস আর delivery-র ফল নিয়ে প্রশ্ন। লেখকেরা পরিসংখ্যানের পদ্ধতি ব্যবহার করে দেখান কোন অভ্যাস ভালো ফলের সাথে জোরালোভাবে জড়িত, আর trunk-based development (অল্প সক্রিয় branch, এক দিনের কম আয়ু, code freeze নেই) সেই তালিকায় আছে। এটি গুরুত্বপূর্ণ তথ্য — মতামতের লড়াইয়ে এটি প্রমাণের একটি স্তর যোগ করে।

কিন্তু সঠিকভাবে পড়তে কয়েকটি প্রশ্ন জিজ্ঞেস করুন। এক — কারণ না সম্পর্ক? হয়তো যেসব দল আগে থেকেই দক্ষ, ভালো test ও CI-সহ, তারাই TBD বেছে নিতে পারে; অর্থাৎ দক্ষতা TBD-র কারণ, TBD দক্ষতার নয়। লেখকেরা এই প্রশ্ন নিয়ে আলোচনা করেছেন, কিন্তু জরিপ দিয়ে কার্যকারণ পুরোপুরি প্রমাণ কঠিন। দুই — কারা উত্তর দিয়েছেন? স্বেচ্ছায় DevOps জরিপে অংশ নেওয়া মানুষ সব দলের প্রতিনিধি না-ও হতে পারেন। তিন — প্রসঙ্গ কী? মাপকাঠিগুলো (deploy-এর ঘনত্ব, lead time) ঘন ঘন deploy-এর software-এর জন্য স্বাভাবিক; বছরে দুবার release হওয়া চিকিৎসা-যন্ত্রের firmware-এর দলকে একই মাপে মাপা ন্যায্য নয়।

ব্যবহারিক পথ: সব branch নিষেধ না করে আগে প্রস্তুতি — স্বয়ংক্রিয় test-এর ভিত্তি, দ্রুত CI (পর্ব ৫৮), দ্রুত review-এর অভ্যাস, feature flag-এর একটি সহজ ব্যবস্থা। তারপর ধাপে ধাপে branch-এর আয়ুর লক্ষ্য কমানো — দুই সপ্তাহ থেকে তিন দিন, তারপর এক দিন — আর প্রতিটি ধাপে নিজের মাপকাঠি দেখা: conflict-এ কত সময় যায়, merge থেকে deploy কত সময়, কত পরিবর্তনে সমস্যা। পর্ব ৬-এ (Kanban আর lean) দেখা “ধারাবাহিক উন্নতি” এখানেও পথ।

মূল উপলব্ধি: গবেষণা দিক দেখায়, গন্তব্যে পৌঁছানোর রাস্তা নয়। সম্পর্ক আর কারণ আলাদা করুন, নিজের প্রসঙ্গ মেলান, আর পরিবর্তনের ফল নিজের দলেই মাপুন।

অনুশীলন

প্রথমে নিজে খাতায় সমাধান করুন, তারপর উত্তর মেলান।

  1. Integration-এর ঝুঁকি হিসাব করুন: দুজন developer একই দিনে ১০০০ লাইনের একটি codebase থেকে branch খোলেন। প্রত্যেকে প্রতিদিন ১০টি নতুন, আলাদা, এলোমেলো লাইন বদলান (একজনের নিজের লাইন কখনো দুবার নয়)। দুজনেই T দিন পরে মেলান — প্রথমজন বিনা বাধায়, দ্বিতীয়জনকে মিল থাকা লাইনগুলো হাতে মেলাতে হয়। তারপর দুজনেই নতুন branch খোলেন। T = ১, ৩ আর ১০ দিনের জন্য বের করুন: (ক) প্রতি merge-এ প্রত্যাশিত মিল থাকা লাইন, (খ) অন্তত একটি conflict-এর সম্ভাবনা, (গ) ৩০ দিনে মোট প্রত্যাশিত হাতে মেলানো লাইন।

    T দিনে প্রত্যেকে k = 10T-টি আলাদা লাইন বদলান। (ক) দ্বিতীয়জনের প্রতিটি লাইন প্রথমজনের set-এ থাকার সম্ভাবনা k/1000, তাই প্রত্যাশিত মিল = k × k/1000 = (10T)²/1000. T = ১: ১০²/১০০০ = ০.১; T = ৩: ৩০²/১০০০ = ০.৯; T = ১০: ১০০²/১০০০ = ১০ লাইন।

    (খ) কোনো মিল না থাকার সম্ভাবনা = 1000−k লাইন থেকে k-টি বাছার উপায় ÷ 1000 থেকে k-টি বাছার উপায় = C(1000−k, k)/C(1000, k) ≈ (1 − k/1000)k. T = ১: 1 − C(990,10)/C(1000,10) ≈ ০.০৯৬ (আসন্ন সূত্রে 1 − 0.99¹⁰ ≈ ০.০৯৬)। T = ৩: 1 − C(970,30)/C(1000,30) ≈ ০.৬০. T = ১০: C(900,100)/C(1000,100) ≈ ১.৫ × ১০⁻⁵, তাই সম্ভাবনা প্রায় ১ (০.৯৯৯৯৮)। অর্থাৎ ১০ দিনের branch-এ conflict প্রায় নিশ্চিত।

    (গ) ৩০ দিনে merge-এর জোড়া ৩০/T বার। মোট = (৩০/T) × (10T)²/1000 = 3T. T = ১: ৩ লাইন (৩০ বারে ছড়ানো); T = ৩: ৯; T = ১০: ৩০ লাইন, মাত্র ৩ বারে — প্রতিবার গড়ে ১০টি। মোট কাজ (৬০০ লাইন-সম্পাদনা) তিন ক্ষেত্রেই সমান। প্রতি merge-এর বোঝা T²-এর সাথে, মোট বোঝা T-এর সাথে বাড়ে — অংশ ২-এর সিমুলেশনের সাথে একই প্রবণতা।

  2. কৌশল বাছুন ও যুক্তি দিন: একটি বিশ্ববিদ্যালয়ের ফলাফল পোর্টাল (কাল্পনিক)। Web অংশে একটিই চালু version, সপ্তাহে ২–৩ বার deploy; সাথে একটি mobile app, যা app store-এর review পেরিয়ে মাসে একবার release হয়, আর পুরোনো app version-ও কয়েক মাস চালু থাকে। দলে ৫ জন, test automation দুর্বল থেকে মাঝারি। ফল প্রকাশের দিনগুলোতে কোনো ঝুঁকি নেওয়া যাবে না। কোন branching strategy প্রস্তাব করবেন, আর কেন? কোন শর্ত পূরণ হলে পরে বদলাবেন?

    একটি যুক্তিসঙ্গত উত্তর (একমাত্র নয়):

    • Web অংশে GitHub Flow, ছোট branch-এর লিখিত সীমা সহ (যেমন সর্বোচ্চ ২ দিন)। একটিই version, সপ্তাহে কয়েকবার deploy — Git Flow-এর develop আর release branch এখানে শুধু বাড়তি ধাপ। main সুরক্ষিত: অনুমোদন আর সবুজ CI ছাড়া merge নয়।
    • এখনই পুরো TBD নয়, কারণ test দুর্বল; প্রতিদিন trunk-এ মেলানো মানে ভাঙা main-এর ঝুঁকি। আগে result গণনার মূল অংশে স্বয়ংক্রিয় test.
    • Mobile app-এর জন্য release branch, trunk থেকে কাটা: app store-এ জমা দেওয়ার সময় release/2.3, tag v2.3.0; fix আগে main-এ, তারপর cherry-pick -x (GitLab Flow-এর release branch বা TBD-র release branch — একই নিয়ম)। পুরোনো app version চালু থাকায় এটি দরকার।
    • ফল প্রকাশের দিন: branch নয়, নিয়ম — সেই দিনগুলোর আগে deploy-বিরতি, আর release tag থেকে নিশ্চিত rollback-এর পরিকল্পনা। ঝুঁকিপূর্ণ নতুন সুবিধা flag দিয়ে বন্ধ।
    • কখন বদলাবেন: মূল অংশে test coverage আর CI নির্ভরযোগ্য হলে, আর PR-গুলো স্বাভাবিকভাবে এক দিনের মধ্যে merge হতে থাকলে trunk-based-এর দিকে — branch-এর আয়ুর সীমা এক দিন, অসমাপ্ত কাজ flag-এ।

    মূল্যায়নের মাপকাঠি: উত্তরে version-সংখ্যা, deploy-এর নিয়ন্ত্রণ (app store), test-এর অবস্থা আর ঝুঁকির দিন — চারটিই বিবেচিত হয়েছে কি না।

  3. Command-এর ক্রম লিখুন: একটি Git Flow দলে release/2.0 branch এখনো চালু (release-এর প্রস্তুতি চলছে)। এমন সময় production-এ (v1.4.0) জরুরি bug. Driessen-এর মূল নিয়ম মেনে plain git দিয়ে hotfix-এর পুরো ক্রম লিখুন — branch খোলা থেকে মুছে ফেলা পর্যন্ত। Fix কোন কোন branch-এ পৌঁছাবে, আর কীভাবে?
    git switch -c hotfix/1.4.1 main
    # (fix the bug; bump VERSION to 1.4.1; commit)
    git switch main
    git merge --no-ff hotfix/1.4.1
    git tag -a v1.4.1 -m "Release 1.4.1"
    git switch release/2.0               # a release branch is open:
    git merge --no-ff hotfix/1.4.1       # merge here instead of develop
    git branch -d hotfix/1.4.1

    Hotfix খোলা হয় main থেকে, কারণ সেটিই production-এর কোড। main-এ merge আর tag v1.4.1. মূল লেখার নিয়মে, release branch চালু থাকলে hotfix develop-এর বদলে সেই release branch-এ মেশে — নইলে ২.০ release-এ fix বাদ পড়ত। পরে release/2.0 শেষ হয়ে যখন develop-এ back-merge হবে, fix তখন develop-এ পৌঁছাবে। (Fix খুব জরুরি আর develop-এ সাথে সাথে দরকার হলে দল develop-এও merge করতে পারে।) Release branch-এ merge-এর সময় VERSION ফাইলে conflict হতে পারে (১.৪.১ বনাম ২.০.০) — তখন ২.০.০ রাখুন।

  4. আউটপুট অনুমান করুন: একটি নতুন repository-তে (main-এ একটি “Initial commit”) পরপর চালানো হলো: git switch -c develop; commit “Add search”; git switch -c release/2.0; commit “Bump version to 2.0.0”; git switch main; git merge --no-ff release/2.0 --no-edit; git tag -a v2.0.0 -m "Release 2.0.0"; git switch develop; commit “Add filters”. এবার (ক) git log --graph --oneline --all --decorate-এ কতটি commit থাকবে, কোনটি merge commit, আর tag কোথায়? (খ) git branch --merged main কোন branch-গুলো দেখাবে? (গ) Git Flow অনুযায়ী কোন ধাপটি বাদ পড়েছে, আর তার ফল কী?

    আসল আউটপুট (Git ২.৫৩; তারিখ ২০২৬-০১-২৫, লেখক Rahim):

    $ git log --graph --oneline --all --decorate
    * 81e0b3f (HEAD -> develop) Add filters
    | *   250955e (tag: v2.0.0, main) Merge branch 'release/2.0'
    | |\
    | | * 6ef8238 (release/2.0) Bump version to 2.0.0
    | |/
    |/|
    * | d3939d7 Add search
    |/
    * 8ae0f20 Initial commit
    $ git branch --merged main
      main
      release/2.0

    (ক) পাঁচটি commit; একমাত্র merge commit 250955e (parent 8ae0f20 আর 6ef8238), tag v2.0.0 সেখানেই, main-ও। develop আছে 81e0b3f-এ, যার parent d3939d7 — release-এর commit তার history-তে নেই। (খ) main আর release/2.0; develop নয়, কারণ “Add filters” এখনো main-এ যায়নি। (গ) বাদ পড়েছে release/2.0-এর back-merge (git switch develop; git merge --no-ff release/2.0). ফল: develop-এ VERSION এখনো পুরোনো, আর release branch-এ কোনো bug fix হয়ে থাকলে সেটিও develop-এ নেই — পরের release-এ সেই bug ফিরে আসবে। আর release branch মুছে ফেলা হয়নি, তাই সেটি এখনো ঝুলে আছে।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমGit Flow-এ একটি release-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. develop থেকে release/1.2 branch খোলা
  2. Release branch-এ version নম্বর বদল আর শুধু bug fix
  3. Release branch-কে main-এ --no-ff merge
  4. main-এর নতুন merge commit-এ tag v1.2.0
  5. Release branch-কে develop-এ ফেরত merge (back-merge)
  6. Release branch মুছে ফেলা

Git Flow অনুযায়ী production-এর একটি জরুরি bug-এর জন্য hotfix branch কোথা থেকে খোলা হয়, আর কোথায় merge হয় (কোনো release branch চালু নেই)?

  • develop থেকে খুলে শুধু main-এ
  • main থেকে খুলে main আর develop দুটিতেই
  • main থেকে খুলে শুধু main-এ
  • একটি feature branch থেকে খুলে develop-এ

GitHub Flow-এর সবচেয়ে মৌলিক নিয়ম কোনটি?

  • সব কাজ develop-এ জমা করে মাসে একবার release
  • প্রতিটি environment-এর জন্য আলাদা স্থায়ী branch
  • main সবসময় deploy-যোগ্য; branch → pull request → review → merge → deploy
  • সবাই review ছাড়া সরাসরি main-এ push করবেন

একটি trunk-based দলে নতুন checkout পাতা বানাতে দুই সপ্তাহ লাগবে, কিন্তু নিয়ম হলো branch এক দিনের বেশি রাখা যাবে না। অসমাপ্ত পাতাটি production-এর ব্যবহারকারীদের কাছ থেকে লুকানোর স্বাভাবিক উপায় কী?

  • Feature flag দিয়ে বন্ধ রেখে ছোট ছোট ধাপে trunk-এ মেলানো
  • দুই সপ্তাহের একটি feature branch, শেষে একবার merge
  • দুই সপ্তাহের জন্য trunk-এ code freeze
  • কাজ শেষ না হওয়া পর্যন্ত কোনো deploy না করা

Trunk থেকে কাটা release/1.4-এ একটি bug পাওয়া গেল, যা trunk-এও আছে। Upstream-first নীতিতে সঠিক পথ কোনটি?

  • শুধু release/1.4-এ fix করা
  • release/1.4-এ fix করে পুরো branch trunk-এ merge
  • Trunk-কে release branch-এর সাথে মিলিয়ে git push --force
  • আগে trunk-এ fix, তারপর git cherry-pick -x দিয়ে release/1.4-এ

একটি সরল মডেলে দুই branch প্রতিদিন সমান সংখ্যক এলোমেলো লাইন বদলায়। Branch-এর আয়ু ৩ দিন থেকে বাড়িয়ে ৬ দিন করলে প্রতি merge-এ প্রত্যাশিত সংঘর্ষের লাইন মোটামুটি কত গুণ হবে?

  • ২ গুণ
  • ৪ গুণ
  • ৮ গুণ
  • অপরিবর্তিত

Accelerate (২০১৮) বইয়ের trunk-based development নিয়ে ফলাফলটি সবচেয়ে সঠিকভাবে কোনটি?

  • প্রমাণিত যে TBD চালু করলেই যেকোনো দলের deploy দ্বিগুণ হবে
  • Git Flow ব্যবহার আন্তর্জাতিক মানদণ্ডে নিষিদ্ধ
  • অল্প সক্রিয় branch, এক দিনের কম আয়ু আর code freeze না থাকা ভালো delivery-র সাথে জড়িত — জরিপভিত্তিক সম্পর্ক
  • দীর্ঘজীবী feature branch ভালো delivery-র সাথে জড়িত

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Branching strategy দলের লিখিত চুক্তি — কোন branch কতদিন, কোথা থেকে, কোথায় মিশবে, release কোথা থেকে। Branch-এর আয়ু বাড়লে merge-এ সংঘর্ষ বাড়ে প্রায় আয়ুর বর্গের হারে।
  2. Git Flow: স্থায়ী main আর develop; feature/* develop ↔ develop, release/* develop → main + develop, hotfix/* main → main + develop; সবখানে --no-ff, release-এ tag. Version-যুক্ত, একাধিক চালু version-এর software-এ মানানসই।
  3. GitHub Flow: main সবসময় deploy-যোগ্য; branch → PR → review → merge → deploy. GitLab Flow যোগ করে environment বা release branch, merge একমুখী, fix upstream first.
  4. Trunk-based development: ১–২ দিনের branch বা সরাসরি trunk; অসমাপ্ত কাজ feature flag বা branch by abstraction-এর আড়ালে; release branch trunk থেকে কাটা, fix আগে trunk-এ তারপর cherry-pick -x, release branch কখনো trunk-এ merge নয়।
  5. বাছাই করুন প্রসঙ্গ দেখে: কয়টি version, deploy-এর গতি ও নিয়ন্ত্রণ, test-এর অবস্থা, audit. নিয়ম কার্যকর করুন সুরক্ষিত branch, review, CI আর merge queue দিয়ে; flag-এর মেয়াদ রাখুন; গবেষণাকে সম্পর্ক হিসেবে পড়ুন।

পরের পর্বে: আজ বারবার tag বসিয়েছেন — v1.0.0, v1.0.1, v1.4.1. কিন্তু version নম্বর কে, কীভাবে ঠিক করে? পর্ব ১৮-এ ভালো commit বার্তা, Conventional Commits, SemVer, annotated tag আর changelog — যাতে প্রতিটি release-এর গল্প history থেকেই পড়া যায়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান