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

Pull request আর fork — দলের কাজের ধারা, review আর merge-এর নিয়ম

Pull Requests and Forks — Team Workflow, Code Review and Merge Rules

Taught in Bangla, this lesson explains that a pull request is a platform feature, not a Git command, and shows Git's own format-patch, am and request-pull path. It compares shared-repository and fork workflows, keeping a fork in sync with upstream, structuring a good PR, reviews, status checks and branch protection, handling review comments with fixup and autosquash, and the merge commit, squash and rebase merge methods.

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

  • Pull request বা merge request আসলে কী, আর Git নিজে কীভাবে একই কাজ করে (patch email, request-pull)
  • Fork workflow শুরু থেকে শেষ: fork, clone, upstream যোগ, branch, push, PR, আর fork হালনাগাদ রাখা
  • একটি ভালো PR-এর গঠন, review-এর অবস্থা, CI check, branch protection আর CODEOWNERS
  • Merge commit, squash আর rebase-merge — প্রতিটি history-তে কী করে, আসল Git সমতুল্য দিয়ে
  • Review-এর মন্তব্য সামলানো, পুরোনো PR হালনাগাদ করা, আর দলের ভেতরে ভদ্র ও কাজের যোগাযোগ

১ · Pull request কী — Git-এর command নয়, platform-এর সুবিধা

পর্ব ১৩-এ (Merge আর conflict) আপনি নিজের কম্পিউটারে git merge চালিয়েছিলেন — সিদ্ধান্তও আপনার, কাজও আপনার। কিন্তু দলে কাজ করলে প্রশ্ন বদলে যায়: আমার branch মূল কোডে ঢুকবে কি না, সেই সিদ্ধান্ত কে নেবে? কে আগে পড়ে দেখবে? Test চলেছে কি না কে নিশ্চিত করবে? এই প্রশ্নগুলোর উত্তর দিতেই তৈরি হয়েছে pull request — সংক্ষেপে PR.

পরিবারের পুরোনো রান্নার খাতার কথা ভাবুন। খাতাটি থাকে নানির কাছে। আপনি বিরিয়ানির রেসিপিতে একটু বদল আনতে চান — এলাচ কম, আলু বেশি। আপনি সরাসরি খাতায় কেটে লেখেন না। একটি আলাদা কাগজে বদলটা লেখেন, পাশে কারণ লেখেন (“গত ঈদে সবাই বলেছে এলাচ বেশি হয়েছিল”), রান্না করে খাইয়েও দেখান। নানি পড়েন, প্রশ্ন করেন, হয়তো বলেন “আলু ঠিক আছে, এলাচ আধা চামচ হলেই চলবে”। আপনি কাগজ ঠিক করেন। শেষে নানি নিজেই খাতায় তুলে নেন। ওই আলাদা কাগজটিই pull request; নানি হলেন maintainer; আর রান্না করে খাওয়ানো হলো test.

খেয়াল করুন, “pull request” নামটি অনুরোধকারীর দিক থেকে: “আমার branch থেকে আমার কাজ আপনি pull করে নিন।” GitLab একই জিনিসকে বলে merge request (MR) — নামটি গ্রহণকারীর দিক থেকে: “এটি merge করুন।” Bitbucket আর GitHub বলে pull request. নাম আলাদা, মূল ধারণা একই। এই পর্বে দুটির জন্যই “PR” লিখব।

মূল ধারণা

Git-এর ভেতরে “pull request” নামে কোনো object, ref বা command নেই (কিছু platform server-এ refs/pull/7/head ধরনের ref নিজে বানায়, তবে সেটি platform-এর রীতি)। একটি PR আসলে platform-এর database-এ রাখা একটি রেকর্ড: কোন branch (source) কোন branch-এ (target) যাবে, শিরোনাম আর বিবরণ, মন্তব্য, review-এর সিদ্ধান্ত, CI-এর ফল, আর একটি merge বোতাম। Branch আর commit-গুলো Git-এর; আলোচনা আর অনুমতির নিয়ম platform-এর। তাই repository অন্য platform-এ সরালে commit সব যায়, কিন্তু PR-এর আলোচনা সাধারণত আলাদা করে export না করলে যায় না।

এক নজরে একটি PR-এর ভেতরে যা থাকে:

Git থেকে

Source branch (যেমন feature/coupon), target branch (সাধারণত main), তাদের merge base থেকে source-এর commit-গুলো আর মোট diff.

commit, branch, diff

মানুষের কাছ থেকে

শিরোনাম, বিবরণ, লাইনে লাইনে মন্তব্য, review-এর সিদ্ধান্ত (comment, approve, request changes).

code review

যন্ত্রের কাছ থেকে

CI-এর build আর test-এর ফল (status check), linter, নিরাপত্তা scan — সবুজ টিক বা লাল ক্রস।

continuous integration

নিয়ম থেকে

কে merge করতে পারবেন, কয়টি approval লাগবে, কোন check সবুজ হতেই হবে, কোন merge পদ্ধতি চলবে।

branch protection

Git তৈরি হয়েছিল ২০০৫ সালে Linux kernel-এর জন্য (পর্ব ৭ — Version control কী)। সেখানে কাজের ধারা শুরু থেকেই email-ভিত্তিক: অবদানকারী patch পাঠান mailing list-এ, আর subsystem maintainer-রা নিজেদের tree থেকে Linus Torvalds-কে email-এ “pull request” পাঠান — Git-এর git request-pull command সেই email-এর লেখা বানিয়ে দেয়। ২০০৮ সালে চালু হওয়া GitHub এই ধারণাটিকে ওয়েব পাতায় তুলে আনে: fork বোতাম, branch-এর তুলনা আর আলোচনার পাতা। “Pull request” নামটি Git-এর ওই পুরোনো প্রথা থেকেই এসেছে বলে ধরা হয়। পরে GitLab, Bitbucket ও অন্যরা একই ধরনের সুবিধা দেয়।

২ · Git-এর নিজস্ব পথ — format-patch, am আর request-pull

Platform ছাড়াও Git দিয়ে অন্যের কাছে কাজ পাঠানো যায় — এবং পৃথিবীর সবচেয়ে বড় open-source project-গুলোর কয়েকটি আজও তাই করে। এই পদ্ধতি বুঝলে PR-এর ভেতরের ধারণাটিও পরিষ্কার হয়: commit-কে এমনভাবে পাঠানো, যাতে অন্যজন পড়ে, মন্তব্য করে, তারপর নিজের history-তে বসাতে পারেন।

এই পর্বের পুরো গল্পটি একটি কাল্পনিক অনলাইন বাজার অ্যাপ-এর ছোট project bazar-cart নিয়ে। Maintainer রহিম (Rahim); বাইরের একজন অবদানকারী নাদিয়া (Nadia) checkout-এ coupon code-এর সুবিধা যোগ করতে চান। সব আউটপুট এই কম্পিউটারে Git ২.৫৩ দিয়ে আসল scratch repository-তে চালিয়ে নেওয়া; “upstream” আর “fork” দুটোই স্থানীয় bare repository, আর তারিখ স্থির রাখা হয়েছে, তাই hash-গুলো আপনিও একই পাবেন।

Patch তৈরি — git format-patch

নাদিয়ার feature/coupon branch-এ main-এর পরে দুটি commit আছে। git format-patch প্রতিটি commit-কে একটি email-আকারের ফাইলে লেখে:

$ git format-patch -o ../outgoing main
../outgoing/0001-Add-coupon-code-field.patch
../outgoing/0002-Limit-coupon-code-to-12-characters.patch
$ cat ../outgoing/0002-*.patch
From a36d5d25df1f1371ee60a208af54817e55ce312d Mon Sep 17 00:00:00 2001
From: Nadia <nadia@example.com>
Date: Thu, 15 Jan 2026 10:03:00 +0600
Subject: [PATCH 2/2] Limit coupon code to 12 characters

---
 cart.conf | 1 +
 1 file changed, 1 insertion(+)

diff --git a/cart.conf b/cart.conf
index 711a8db..cbe1a3f 100644
--- a/cart.conf
+++ b/cart.conf
@@ -3,3 +3,4 @@ max_items = 30
 delivery_fee = 60
 tax = 0
 coupon = enabled
+coupon_max_length = 12
--
2.53.0.windows.2

main-এ নেই এমন প্রতিটি commit একটি করে ফাইল হলো, নম্বর দিয়ে সাজানো। ফাইলটি আসলে একটি email: From: লাইনে আসল লেখক, Date: লাইনে commit-এর author সময়, Subject:-এ [PATCH 2/2] আর commit বার্তা। ----এর নিচে diffstat আর পরিচিত unified diff. প্রথম লাইনের Mon Sep 17 00:00:00 2001 কোনো আসল তারিখ নয় — এটি mbox ফাইল-ফরম্যাটের জন্য Git-এর বসানো একটি স্থির “চিহ্ন” তারিখ। শেষ লাইনে Git-এর version (Windows-এর Git-এ 2.53.0.windows.2; Linux-এ শুধু 2.53.0 দেখাবে)। এই ফাইল git send-email দিয়ে mailing list-এ পাঠানো হয়; reviewer-রা reply-তে diff-এর লাইনের নিচেই মন্তব্য লেখেন।

Patch বসানো — git am

রহিম email থেকে ফাইলগুলো সংরক্ষণ করে নিজের repository-তে git am (apply mailbox) চালালেন। এর মধ্যে রহিমের main-এ README-এর একটি বানান-সংশোধন যোগ হয়েছে:

$ git am ../outgoing/0001-Add-coupon-code-field.patch ../outgoing/0002-Limit-coupon-code-to-12-characters.patch
Applying: Add coupon code field
Applying: Limit coupon code to 12 characters
$ git log --format='%h %s  (author %an, committer %cn)' -3
6e4ce92 Limit coupon code to 12 characters  (author Nadia, committer Rahim)
6a4da1e Add coupon code field  (author Nadia, committer Rahim)
8fab4eb Fix typo in README  (author Rahim, committer Rahim)

দুটি patch পরপর বসল, প্রতিটি থেকে একটি নতুন commit. Author নাদিয়াই রইলেন (patch-এর From: আর Date: থেকে), কিন্তু committer হলেন রহিম — যিনি commit-টি নিজের history-তে বসালেন। নাদিয়ার কাছে commit দুটির hash ছিল e993824 আর a36d5d2; এখানে 6a4da1e আর 6e4ce92. কারণ parent আলাদা (8fab4eb), committer আলাদা, সময়ও আলাদা — আর commit-এর hash এই সবকিছু থেকেই হিসাব হয় (পর্ব ৮)। ফাইলের বিষয়বস্তু অবশ্য হুবহু এক। Patch না মিললে git am --3way merge base খুঁজে three-way merge চেষ্টা করে; থেমে গেলে git am --continue, --skip বা --abort.

Pull-এর অনুরোধ লেখা — git request-pull

অনেক commit হলে patch না পাঠিয়ে বলা সহজ: “আমার repository-র অমুক branch থেকে pull করে নিন।” git request-pull সেই অনুরোধের লেখা তৈরি করে — কোথা থেকে শুরু, কোথায় পাওয়া যাবে, কোন commit পর্যন্ত, আর সারসংক্ষেপ:

$ git request-pull main ../fork.git feature/coupon
The following changes since commit c5c366f1016194ea36864dea49dd4b62e0456014:

  Raise max items to 30 (2026-01-15 10:04:00 +0600)

are available in the Git repository at:

  ../fork.git feature/coupon

for you to fetch changes up to a36d5d25df1f1371ee60a208af54817e55ce312d:

  Limit coupon code to 12 characters (2026-01-15 10:07:00 +0600)

----------------------------------------------------------------
Nadia (2):
      Add coupon code field
      Limit coupon code to 12 characters

 cart.conf | 2 ++
 1 file changed, 2 insertions(+)

এটি কিছু পাঠায় না — শুধু একটি লেখা ছাপে, যা email-এ পেস্ট করা হয়। এতে আছে base commit (c5c366f, যার পরের কাজ), repository-র ঠিকানা আর branch (এখানে স্থানীয় পথ ../fork.git; বাস্তবে একটি public URL), শেষ commit (a36d5d2), লেখক-অনুযায়ী commit-এর তালিকা (shortlog) আর diffstat. Command-টি চালানোর আগে Git যাচাই করে যে ওই ঠিকানায় সত্যিই ওই branch-এ ওই commit আছে — নইলে সতর্ক করে। একটি web PR-এর পাতায় ঠিক এই তথ্যগুলোই থাকে, শুধু সুন্দর করে সাজানো।

Linux kernel-এর অবদান-নির্দেশিকা (kernel-এর Documentation/process/submitting-patches.rst) অনুযায়ী patch যায় mailing list-এ, প্রতিটি commit-এ Signed-off-by: নামের একটি trailer লাইন থাকে (git commit -s) — যা Developer Certificate of Origin-এ লেখকের সম্মতি বোঝায়। Maintainer-দের pull request তৈরির নিয়মও kernel-এর documentation-এ লেখা আছে। এই ধারা ধীর মনে হতে পারে, কিন্তু এতে কোনো একটি কোম্পানির platform-এর ওপর নির্ভরতা নেই — email যে কেউ পড়তে ও সংরক্ষণ করতে পারেন।

৩ · দুই ধরনের কাজের ধারা — shared repository বনাম fork

Platform-এ PR খোলার দুটি প্রচলিত ধারা আছে। পার্থক্যটা হলো: আপনার branch কোথায় থাকে, আর মূল repository-তে push করার অনুমতি আপনার আছে কি না।

বিষয়Shared repositoryFork workflow
কোথায় ব্যবহার হয়একটি কোম্পানি বা দলের ভেতরের projectOpen-source project, বাইরের অবদানকারী
Branch কোথায়মূল repository-তেই (origin/feature/x)আপনার নিজের কপিতে — fork
Push-এর অনুমতিদলের সবার আছে (protected branch বাদে)মূলটিতে নেই; শুধু নিজের fork-এ
Remote কয়টিসাধারণত একটি: originদুটি: origin = fork, upstream = মূল
হালনাগাদ থাকাgit fetch origingit fetch upstream, তারপর fork-এও push
ঝুঁকিভুল করে অন্যের branch-এ pushFork পুরোনো হয়ে পড়া; বাইরের কোডে CI চালানোর নিরাপত্তা

Fork হলো platform-এর server-এ মূল repository-র একটি পূর্ণ কপি, যার মালিক আপনি। Git-এর দৃষ্টিতে এটি শুধু আরেকটি remote repository; “fork” শব্দটির বিশেষ অর্থ শুধু platform-এর কাছে — সে মনে রাখে কোন repository কোনটির কপি, তাই fork থেকে মূলটিতে PR খোলা যায়। মূল repository-কে প্রচলিত ভাষায় বলে upstream. নিচের ছবিতে পুরো ধারাটি দেখুন:

Fork workflow — তিনটি repository, দুটি remote

Upstream, fork, local clone and the pull request

Fork workflow: upstream, fork, local clone upstream মূল repository example/bazar-cart fork (origin) server-এ আপনার কপি nadia/bazar-cart local clone আপনার কম্পিউটার remote: origin, upstream ③ branch + commit ① fork ⑤ PR ② clone ④ push ⑥ fetch upstream
ছবি ১৬.১ — আপনি upstream-এ সরাসরি push করেন না। Push যায় নিজের fork-এ (④), আর PR (⑤) হলো fork-এর branch থেকে upstream-এর main-এ মেলানোর অনুরোধ। Upstream-এর নতুন কাজ আনতে local থেকে fetch (⑥)।

এবার আসল command-গুলো। Platform-এ “Fork” বোতাম চাপলে server যা করে, স্থানীয় ডেমোতে তার কাছাকাছি সমতুল্য হলো একটি --bare clone. তারপর নাদিয়া নিজের fork clone করেন আর মূল repository-কে upstream নামে যোগ করেন:

$ git clone --bare upstream.git fork.git
Cloning into bare repository 'fork.git'...
done.
$ git clone fork.git bazar-cart
Cloning into 'bazar-cart'...
done.
$ git remote set-url origin ../fork.git
$ git remote add upstream ../upstream.git
$ git remote -v
origin	../fork.git (fetch)
origin	../fork.git (push)
upstream	../upstream.git (fetch)
upstream	../upstream.git (push)
$ git switch -c feature/coupon
Switched to a new branch 'feature/coupon'
$ git commit -m "Add coupon code field"
[feature/coupon 39c5008] Add coupon code field
 1 file changed, 1 insertion(+)
$ git commit -m "Limit coupon code to 12 characters"
[feature/coupon 3762e90] Limit coupon code to 12 characters
 1 file changed, 1 insertion(+)
$ git push -u origin feature/coupon
To ../fork.git
 * [new branch]      feature/coupon -> feature/coupon
branch 'feature/coupon' set up to track 'origin/feature/coupon'.

Clone করলে Git নিজে থেকেই উৎসটিকে origin নাম দেয়। স্থানীয় clone-এ Git পূর্ণ পথ মনে রাখে, তাই আউটপুট ছোট রাখতে set-url দিয়ে আপেক্ষিক পথ বসানো হয়েছে — বাস্তবে এখানে থাকত https://github.com/nadia/bazar-cart.git-এর মতো ঠিকানা। git remote -v দেখাচ্ছে দুটি remote, প্রতিটির fetch আর push ঠিকানা। দুটি commit (প্রতিটির আগে git add cart.conf চালানো হয়েছে, এখানে দেখানো হয়নি) তারপর git push -u origin feature/coupon: branch গেল fork-এ, upstream-এ নয়, আর -u স্থানীয় branch-টিকে origin/feature/coupon-এর সাথে জুড়ে দিল (upstream branch; পর্ব ১৪ — Remote repository). সাবধান: “upstream” শব্দটির এখানে দুটি অর্থ — remote-এর নাম upstream (মূল repository) আর কোনো branch-এর “upstream branch” (যার সাথে সে জোড়া)। দুটি আলাদা জিনিস।

এবার পুরো ধারাটি ধাপে ধাপে দেখুন। প্রতিটি ধাপে তিন জায়গার branch কোন commit-এ আছে, তা টেবিলে বদলাবে (hash-গুলো ওপরের আর পরের আউটপুট থেকে নেওয়া)।

  1. Forkserver-এ কপি①
  2. Clone+ upstream②
  3. Branchcommit③
  4. Pushorigin④
  5. PR#7⑤
  6. Syncfetch upstream⑥
কোন branch কোথায়
upstream main
fork main
fork feature/coupon
local main
local feature/coupon
PR #7
  1. Fork. নাদিয়া platform-এ মূল repository example/bazar-cart-এর পাতায় “Fork” চাপলেন। Server-এ তাঁর নামে একটি পূর্ণ কপি তৈরি হলো। দুটিরই main একই commit 714ea35-এ।
  2. Clone আর upstream যোগ। নিজের fork clone করলেন (নাম origin), তারপর git remote add upstream … দিয়ে মূলটিকে যোগ করলেন। Local main = 714ea35.
  3. Branch আর commit. main-এ নয়, আলাদা feature/coupon branch-এ দুটি commit: 39c5008 আর 3762e90. শুধু local-এ আছে, আর কেউ এখনো দেখেননি।
  4. Push নিজের fork-এ। git push -u origin feature/coupon. এখন fork-এ branch আছে 3762e90-এ। Upstream এখনো কিছুই জানে না।
  5. PR খোলা। Platform-এ “Compare & pull request”: source = nadia:feature/coupon, target = example:main. PR #7 তৈরি হলো, দেখাচ্ছে ২টি commit আর তাদের diff.
  6. Upstream এগিয়ে গেল, sync. রহিম upstream-এ c5c366f যোগ করলেন। নাদিয়া git fetch upstream, main-কে fast-forward করে fork-এ push, তারপর feature branch rebase করে --force-with-lease push (অংশ ৪)। Branch-এর tip নতুন hash 7e2341e; PR নিজে থেকেই নতুন commit দেখায়।
  7. Review মেটানো। রহিম নাম বদলাতে বললেন; নাদিয়া fixup commit বানিয়ে autosquash করলেন (অংশ ৭)। Branch এখন a36d5d2-এ। এর মধ্যে upstream main-এ আরেকটি commit 8fab4eb এসেছে; fork-এর main আবার একটু পিছিয়ে — PR-এর জন্য তাতে সমস্যা নেই।
  8. Merge আর পরিষ্কার। রহিম “Squash and merge” বেছে নিলেন: upstream main-এ একটি নতুন commit 04dd1b1 (অংশ ৮)। নাদিয়া branch মুছে দিলেন, আর git fetch upstream + fast-forward + push দিয়ে local আর fork-এর main-ও 04dd1b1-এ আনলেন। পরের কাজ আবার main থেকে নতুন branch-এ।

৪ · Fork হালনাগাদ রাখা — upstream থেকে fetch, তারপর merge বা rebase

Fork তৈরির মুহূর্ত থেকেই সেটি পুরোনো হতে শুরু করে। Upstream-এ প্রতিদিন অন্যদের PR merge হয়, কিন্তু আপনার fork নিজে থেকে কিছু জানে না। আর সবচেয়ে বিভ্রান্তিকর ব্যাপার: আপনার local main “upstream branch” হিসেবে চেনে origin/main-কে, অর্থাৎ fork-কে, মূল repository-কে নয়। নিচে দেখুন — রহিম upstream-এ c5c366f যোগ করার পরে নাদিয়ার repository কী বলছে:

$ git fetch upstream
From ../upstream
 * [new branch]      main       -> upstream/main
$ git switch main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
$ git status -sb
## main...origin/main
$ git rev-list --left-right --count main...upstream/main
0	1
$ git merge --ff-only upstream/main
Updating 714ea35..c5c366f
Fast-forward
 cart.conf | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push origin main
To ../fork.git
   714ea35..c5c366f  main -> main

Fetch upstream-এর main-কে স্থানীয় remote-tracking branch upstream/main হিসেবে আনল। তবুও git switch main বলছে Your branch is up to date with 'origin/main' — কথাটি সত্য, কিন্তু শুধু fork-এর তুলনায়! আসল অবস্থা দেখায় git rev-list --left-right --count main...upstream/main: বাঁয়ের সংখ্যা = main-এ আছে কিন্তু upstream/main-এ নেই এমন commit (০), ডানেরটি = উল্টোটা (১)। অর্থাৎ ১টি commit পিছিয়ে। --ff-only merge শুধু fast-forward করতে রাজি — local main-এ ভুল করে নিজের কোনো commit থাকলে থেমে যাবে, তাই এটিই নিরাপদ অভ্যাস। শেষে git push origin main fork-এর main-কেও হালনাগাদ করল (714ea35..c5c366f). কিছু platform-এ fork-এর পাতায় “Sync fork” ধরনের বোতামও থাকে, যা server-এই একই কাজ করে (নাম আর সুবিধা platform-ভেদে আলাদা)।

নিচের মডেলে একই ঘটনা ছোট আকারে, প্রতিটি ধাপে দুই দিকের তুলনাসহ। এখানে commit-গুলোর নাম অক্ষর (A, B, C…), আর তিনটি repository শুধু “branch → tip” map:

// Fork sync model: three repositories, each a map of branch -> tip; parents shared.
const parentOf = { A: null, B: "A" };                      // commit -> its parent
const upstream = { main: "B" }, fork = { main: "B" }, local = { main: "B" };
const remoteRefs = {};                                     // local copies: origin/..., upstream/...
function commit(repo, branch, name) { parentOf[name] = repo[branch]; repo[branch] = name; }
function ancestry(c) { const s = new Set(); for (; c; c = parentOf[c]) s.add(c); return s; }
function aheadBehind(x, y) {                               // like git rev-list --left-right --count x...y
  const hx = ancestry(x), hy = ancestry(y);
  return [...hx].filter(c => !hy.has(c)).length + " ahead, " + [...hy].filter(c => !hx.has(c)).length + " behind";
}
function gitFetch(name, repo) { for (const b in repo) remoteRefs[name + "/" + b] = repo[b]; }
function report(step) {
  const up = remoteRefs["upstream/main"];
  console.log(step.padEnd(32) + "| main vs origin/main: " + aheadBehind(local.main, remoteRefs["origin/main"]) +
    " | vs upstream/main: " + (up ? aheadBehind(local.main, up) : "unknown"));
}
gitFetch("origin", fork); report("1 clone fork, add upstream");
local.feature = local.main; commit(local, "feature", "F1"); commit(local, "feature", "F2");
fork.feature = local.feature; report("2 commit F1,F2; push to fork");
commit(upstream, "main", "C"); commit(upstream, "main", "D");  // maintainers merge other PRs
report("3 upstream gets C,D (no fetch)");
gitFetch("upstream", upstream); report("4 git fetch upstream");
console.log("   feature vs upstream/main:", aheadBehind(local.feature, remoteRefs["upstream/main"]));
local.main = remoteRefs["upstream/main"]; report("5 merge --ff-only upstream/main");
fork.main = local.main; gitFetch("origin", fork); console.log("   fork main now", fork.main, "(pushed)");
// rebase feature: replay F1,F2 on top of the new main, getting new names F1', F2'
let base = local.main; const old = local.feature;
for (const c of ["F1", "F2"]) { parentOf[c + "'"] = base; base = c + "'"; }
local.feature = base;
console.log("6 rebase feature onto main".padEnd(32) + "| feature vs upstream/main:", aheadBehind(local.feature, remoteRefs["upstream/main"]));
console.log("   old tip", old, "-> new tip", local.feature, "; fork still has", fork.feature, "-> needs push --force-with-lease");

আউটপুট:

1 clone fork, add upstream      | main vs origin/main: 0 ahead, 0 behind | vs upstream/main: unknown
2 commit F1,F2; push to fork    | main vs origin/main: 0 ahead, 0 behind | vs upstream/main: unknown
3 upstream gets C,D (no fetch)  | main vs origin/main: 0 ahead, 0 behind | vs upstream/main: unknown
4 git fetch upstream            | main vs origin/main: 0 ahead, 0 behind | vs upstream/main: 0 ahead, 2 behind
   feature vs upstream/main: 2 ahead, 2 behind
5 merge --ff-only upstream/main | main vs origin/main: 2 ahead, 0 behind | vs upstream/main: 0 ahead, 0 behind
   fork main now D (pushed)
6 rebase feature onto main      | feature vs upstream/main: 2 ahead, 0 behind
   old tip F2 -> new tip F2' ; fork still has F2 -> needs push --force-with-lease

ধাপ ৩: upstream-এ C আর D এসেছে, কিন্তু local repository-র কাছে খবর নেই — Git network-এ নিজে থেকে কিছু দেখে না; তাই unknown. ধাপ ৪: fetch-এর পরে জানা গেল main ২টি পিছিয়ে, অথচ origin/main-এর তুলনায় সব “ঠিক আছে” — ঠিক আসল Git-এর মতো। Feature branch-টি 2 ahead, 2 behind: তার নিজের F1, F2 আছে, upstream-এর C, D নেই। ধাপ ৫: fast-forward-এর পরে local main upstream-এর সমান, কিন্তু এবার fork-এর চেয়ে ২টি এগিয়ে — তাই push লাগে। ধাপ ৬: rebase F1, F2-কে D-এর ওপর নতুন করে বসায়, নতুন নাম F1′, F2′ (আসল Git-এ নতুন hash). এখন feature শুধু এগিয়ে, পিছিয়ে নেই। কিন্তু fork-এ এখনো পুরোনো F2 — নতুন history পুরোনোটির বংশধর নয়, তাই সাধারণ push প্রত্যাখ্যাত হবে।

পুরোনো PR হালনাগাদ — merge না rebase?

PR অনেক দিন খোলা থাকলে (একে বলে stale PR) target branch অনেক এগিয়ে যায়। তখন দুটি সমস্যা: conflict-এর সম্ভাবনা বাড়ে, আর CI যে কোড পরীক্ষা করছে তা আজকের main-এর সাথে মেলানো অবস্থা নয়। দুটি উপায়:

  • Upstream-এর main নিজের branch-এ merge: git merge upstream/main. History বদলায় না, force push লাগে না; কিন্তু PR-এ একটি “Merge branch 'main' into …” commit জমে। কিছু platform-এর “Update branch” বোতাম ঠিক এটাই করে (কোথাও rebase-এর বিকল্পও থাকে)।
  • নতুন main-এর ওপর rebase: git rebase upstream/main, তারপর git push --force-with-lease. History সোজা থাকে, কিন্তু commit-গুলোর hash বদলায়, আর reviewer-দের আগের মন্তব্য কোন লাইনে ছিল তা কখনো কখনো “outdated” দেখায়।

আমাদের গল্পে নাদিয়া দ্বিতীয় পথ নিলেন:

$ git switch feature/coupon
Switched to branch 'feature/coupon'
Your branch is up to date with 'origin/feature/coupon'.
$ git rebase main
Successfully rebased and updated refs/heads/feature/coupon.
$ git push --force-with-lease
To ../fork.git
 + 3762e90...7e2341e feature/coupon -> feature/coupon (forced update)
$ git log --oneline --graph --all
* 7e2341e Limit coupon code to 12 characters
* e993824 Add coupon code field
* c5c366f Raise max items to 30
* 714ea35 Add README
* decee1a Add cart config

Rebase দুটি commit-কে c5c366f-এর ওপর নতুন করে বসাল: 39c5008 → e993824, 3762e90 → 7e2341e (rebase চলার সময় terminal-এ Rebasing (1/2) ধরনের অগ্রগতি এক লাইনে মুছে মুছে দেখায়; শেষে শুধু “Successfully rebased” থাকে)। Push-এর লাইনে + চিহ্ন আর (forced update) মানে remote-এর branch-কে এমন commit-এ সরানো হলো, যা আগেরটির বংশধর নয়। --force-with-lease শুধু তখনই এটা করে, যখন remote-এর branch এখনো আপনার শেষ দেখা জায়গায় (3762e90) আছে; মাঝে অন্য কেউ ওই branch-এ push করলে প্রত্যাখ্যান করে — সাধারণ force push-এর মতো চুপচাপ মুছে দেয় না। নিয়মটি মনে রাখুন: force-with-lease-ও শুধু নিজের PR branch-এ, কখনো main বা অন্যের branch-এ নয়। Rebase-এর পুরো ব্যাখ্যা পর্ব ১৫-এ (Rebase). git log --graph --all একটি সরলরেখা দেখাচ্ছে, কারণ এখন সব branch একই পথের ওপর।

৫ · ভালো pull request-এর গঠন — ছোট পরিধি, শিরোনাম, বিবরণ আর linked issue

Reviewer আপনার মাথার ভেতরে ঢুকতে পারেন না। তিনি জানেন না কেন এই বদল, কী কী ভেবে দেখেছেন, কীভাবে পরীক্ষা করেছেন। PR-এর লেখাই তাঁর একমাত্র সূত্র। একটি ভালো PR-এর পাঁচটি অংশ:

  1. ছোট পরিধি — এক PR, এক কাজ। Coupon-এর সুবিধা আর পুরো project-এর formatting একই PR-এ মেশালে reviewer আসল বদলটি খুঁজেই পাবেন না। Formatting বা নাম-বদলের মতো যান্ত্রিক কাজ আলাদা PR-এ দিন। বড় feature হলে কয়েকটি ধাপে ভাগ করুন — প্রতিটি নিজে চলার মতো আর test-সহ; অসম্পূর্ণ অংশ লুকিয়ে রাখতে feature flag (পর্ব ১৭ — Branching strategy).
  2. শিরোনাম। এক লাইনে কী বদলাল, আদেশসূচক ক্রিয়া দিয়ে: Add coupon code to checkout — “fix”, “update”, “changes” নয়। অনেক দল squash merge-এর সময় শিরোনামকেই commit বার্তা বানায়, তাই এটি commit message-এর নিয়ম মেনে লেখা ভালো; Conventional Commits-এর feat:/fix: ধাঁচ পর্ব ১৮-এ (ভালো commit, tag আর release).
  3. বিবরণ — কী, কেন, কীভাবে পরীক্ষা। নিচের ধরনের একটি PR template অনেক দল repository-তে রেখে দেয়, যাতে প্রতিটি নতুন PR-এ ঘরগুলো আগে থেকেই বসানো থাকে।
  4. Linked issue. কোন issue বা কাজের অনুরোধ থেকে এই PR, তার নম্বর।
  5. প্রস্তুতির অবস্থা। কাজ শেষ না হলে draft PR.
## কী বদলেছে
Checkout-এ coupon code লেখার ঘর যোগ হয়েছে; code-এর দৈর্ঘ্য সর্বোচ্চ 12 অক্ষর।

## কেন
Closes #42 — ঈদের অফারে দোকানগুলো coupon দিতে চায়।

## কীভাবে পরীক্ষা করা হয়েছে
- নতুন unit test: coupon_test.js (৬টি case, 0 থেকে 13 অক্ষর)
- হাতে: 12 অক্ষরের code গৃহীত, 13 অক্ষরের code প্রত্যাখ্যাত

## Screenshot
(checkout পাতা — আগে আর পরে)

## Reviewer-এর জন্য
cart.conf-এ নতুন key: coupon, coupon_max_length. পুরোনো কোনো key বদলায়নি।

Linked issue আর closing keyword: বিবরণে Closes #42, Fixes #42 বা Resolves #42 লিখলে অনেক platform PR merge হওয়ার সাথে সাথে issue #42 নিজে থেকে বন্ধ করে দেয়। তবে খুঁটিনাটি platform-ভেদে আলাদা: কোন শব্দগুলো গণ্য হয় (GitHub-এর তালিকায় close, fix, resolve-এর বিভিন্ন রূপ; GitLab-এর default তালিকা একটু বড় আর project-এ বদলানো যায়), সাধারণত শুধু default branch-এ merge হলেই কাজ করে, আর Bitbucket-এর মতো platform-এ issue প্রায়ই অন্য tool-এ (যেমন Jira) থাকে। লেখার সময়ের অবস্থা আপনার platform-এর documentation-এ মিলিয়ে নিন। শুধু উল্লেখ করতে চাইলে, বন্ধ নয় — Related to #42 বা শুধু #42.

Draft PR: কাজ অর্ধেক হলেও PR খুলে রাখা যায় “draft” হিসেবে — CI চলে, সহকর্মীরা আগেভাগে দিক নিয়ে মত দিতে পারেন, কিন্তু merge বোতাম বন্ধ আর reviewer-দের আনুষ্ঠানিক অনুরোধ যায় না। GitHub-এ “Draft pull request”, GitLab-এ “Draft” চিহ্ন (শিরোনামের শুরুতে Draft:); অন্য platform-এ সুবিধাটি ভিন্ন নামে বা না-ও থাকতে পারে। প্রস্তুত হলে “Ready for review”.

ছোট PR কেন দ্রুত এগোয় — গবেষণা আর একটি মডেল

অভিজ্ঞ দলগুলো প্রায় সবাই বলে “PR ছোট রাখুন”। এর পক্ষে কিছু প্রকাশিত প্রমাণ আছে, তবে সাবধানে পড়তে হয়। SmartBear-এর Jason Cohen-এর বই Best Kept Secrets of Peer Code Review (২০০৬)-এ Cisco-র একটি বড় code review সমীক্ষার কথা আছে, যার সুপারিশ ছিল একবারে কয়েকশো লাইনের বেশি review না করা — বেশি হলে ত্রুটি খুঁজে পাওয়ার হার কমে যায় বলে জানানো হয়েছে। এটি একটি কোম্পানির প্রকাশিত, একটি পরিবেশের ফল; সংখ্যাগুলো সব দলে খাটবে, এমন নয়। Google-এর প্রকাশ্য engineering practices নির্দেশিকাতেও (“Small CLs” অংশ) ছোট পরিবর্তনের পক্ষে যুক্তি দেওয়া আছে: দ্রুত review, বেশি মনোযোগী review, কম bug, সহজে revert. কোনো সর্বজনীন “আদর্শ লাইন-সংখ্যা” নেই।

কারণটির একটি অংশ queue-র গণিত, যা পর্ব ৬-এ (Kanban আর lean) Little’s law আর WIP-এ দেখেছেন। নিচের মডেলে একই পরিমাণ কাজ (৪০০ কর্মঘণ্টায় ঘণ্টায় ১০০ লাইন) বিভিন্ন আকারের PR-এ ভাগ করা হয়েছে। অনুমানগুলো কোডের শুরুতে লেখা — এগুলো মডেল, মাপা তথ্য নয়:

// PR review queue: same total work (100 changed lines per working hour, 400 hours),
// cut into PRs of different sizes. ASSUMPTIONS (a model, not measured data):
//  A1 one reviewer, first come first served; a review reads 200 lines per hour
//  A2 every 50 lines carry a 10% chance of a finding -> P(approve) = 0.9^(L/50)
//  A3 after "request changes" the author needs 0.5 h + L/100 h, then the PR re-enters
//     the queue; a re-review reads the whole PR again at half the time
function mulberry32(a) { return () => { a |= 0; 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; }; }
function simulate(L, seed) {
  const rnd = mulberry32(seed), n = Math.round(400 * 100 / L), gap = L / 100;
  let t = 0; const prs = [];
  for (let i = 0; i < n; i++) { t += -Math.log(1 - rnd()) * gap; prs.push({ open: t, ready: t, rounds: 0, wait: 0 }); }
  let free = 0, busy = 0, pending = prs.slice();
  while (pending.length) {
    pending.sort((a, b) => a.ready - b.ready);
    const p = pending.shift(), start = Math.max(free, p.ready);
    p.wait += start - p.ready; p.rounds++;
    const work = (L / 200) * (p.rounds === 1 ? 1 : 0.5); busy += work; free = start + work;
    if (rnd() < Math.pow(0.9, L / 50)) p.merged = free;
    else { p.ready = free + 0.5 + L / 100; pending.push(p); }
  }
  const avg = f => prs.reduce((s, p) => s + f(p), 0) / n;
  const sorted = prs.map(p => p.merged - p.open).sort((a, b) => a - b);
  return { n, rounds: avg(p => p.rounds), wait: avg(p => p.wait), med: sorted[Math.floor(n / 2)],
           p90: sorted[Math.floor(n * 0.9)], load: busy / 400 };
}
console.log("lines/PR  PRs  rounds  reviewer-load  queue-wait(h)  open->merged median / p90 (h)");
for (const L of [50, 100, 200, 400, 800]) {
  const r = simulate(L, 2026);
  console.log(String(L).padStart(8), String(r.n).padStart(4), r.rounds.toFixed(2).padStart(7), (Math.round(r.load * 100) + "%").padStart(14),
    r.wait.toFixed(2).padStart(14), (r.med.toFixed(1) + " / " + r.p90.toFixed(1)).padStart(20));
}

আউটপুট (seed ২০২৬, তাই প্রতিবার একই):

lines/PR  PRs  rounds  reviewer-load  queue-wait(h)  open->merged median / p90 (h)
      50  800    1.11            53%           0.18            0.3 / 1.4
     100  400    1.25            56%           0.53            0.9 / 3.1
     200  200    1.50            62%           1.32            1.9 / 9.5
     400  100    2.12            78%           3.63           8.2 / 27.5
     800   50    4.74           144%         101.23        131.5 / 300.1

তিনটি প্রভাব একসাথে কাজ করছে। (১) Round বাড়ে: অনুমান A2 অনুযায়ী approve-এর সম্ভাবনা q = 0.9L/50; L = ১০০ হলে q = ০.৮১, L = ৪০০ হলে q ≈ ০.৪৩. Round-সংখ্যা geometric, গড় 1/q — অর্থাৎ প্রায় ১.২৩ আর ২.৩২; simulation দেখাচ্ছে ১.২৫ আর ২.১২ (সসীম নমুনার কারণে একটু আলাদা)। (২) Reviewer-এর চাপ বাড়ে: প্রথম review-এ মোট চাপ সবসময় ৫০% (ঘণ্টায় ১০০ লাইন আসে, reviewer পড়েন ২০০), কিন্তু প্রতিটি বাড়তি round আরও চাপ যোগ করে। ৮০০ লাইনে চাপ ১৪৪% — ১০০%-এর বেশি মানে queue কখনো খালি হয় না, অপেক্ষা ক্রমাগত বাড়ে। (৩) প্রতিটি অপেক্ষা লম্বা হয়: বড় কাজ queue-তে সামনে থাকলে পেছনের সবাই বেশি অপেক্ষা করে। ফল: ১০০ লাইনের PR-এর median প্রায় ০.৯ ঘণ্টা, ৪০০ লাইনের ৮.২ ঘণ্টা — আকার ৪ গুণ, সময় প্রায় ৯ গুণ। মডেলের সীমা: প্রতি-PR-এর স্থির খরচ (CI চলা, context বদল) এখানে নেই, যা খুব ছোট PR-কে ব্যয়বহুল করে; আর A2-র ১০% সংখ্যাটি বানানো। A2 বদলে দেখুন (যেমন 0.95) — প্রবণতা টিকে থাকে, মাত্রা বদলায়।

৬ · Review, status check আর branch protection

Review-এর তিন রকম সিদ্ধান্ত

বেশিরভাগ platform-এ reviewer লাইনে লাইনে মন্তব্য লিখে শেষে একটি সামগ্রিক সিদ্ধান্ত জানান। GitHub-এর তিনটি নাম সবচেয়ে পরিচিত:

💬 Comment

মত বা প্রশ্ন, কিন্তু কোনো সিদ্ধান্ত নয়। Merge আটকায় না, approval-ও গণ্য হয় না।

✅ Approve

“আমার দিক থেকে merge করা যায়।” বাধ্যতামূলক review-এর নিয়মে এটিই গোনা হয়।

🛑 Request changes

“এগুলো ঠিক না হলে merge নয়।” নিয়ম চালু থাকলে reviewer আবার দেখে approve না করা পর্যন্ত merge আটকে থাকে।

GitLab-এ মূলত “Approve” আর মন্তব্যের thread (নিয়ম চালু থাকলে প্রতিটি thread “resolve” করতে হয়); Bitbucket-এ approve আর changes চাওয়ার ব্যবস্থা — নাম আর আচরণ platform ও সংস্করণ-ভেদে আলাদা। কী দেখবেন আর কীভাবে মন্তব্য লিখবেন, তার পূর্ণ আলোচনা পর্ব ৪৪-এ (Code review).

Status check — যন্ত্রের review

PR খুললে বা নতুন commit push করলে platform CI চালায়: build, unit test, linter, নিরাপত্তা scan. প্রতিটির ফল PR-এর পাতায় একটি status check হয়ে দেখায় — সবুজ টিক, লাল ক্রস বা হলুদ “চলছে”। একটি গুরুত্বপূর্ণ সূক্ষ্মতা: CI সাধারণত পরীক্ষা করে PR-এর branch আর target-এর merge করা অবস্থা (অনেক platform একটি অস্থায়ী merge commit বানিয়ে তাতে চালায়) — কিন্তু target এগিয়ে গেলে পুরোনো সবুজ ফল আর আজকের অবস্থা বোঝায় না। এজন্য কোনো কোনো দল “merge-এর আগে branch হালনাগাদ থাকতে হবে” নিয়ম রাখে, বা merge queue ব্যবহার করে। Pipeline কীভাবে বানাবেন, তা পর্ব ৫৮-এ (Continuous integration).

Branch protection, বাধ্যতামূলক review আর CODEOWNERS

main-কে সুরক্ষিত রাখতে platform-এ branch protection নিয়ম বসানো যায় (GitHub-এ এখন “rulesets”-ও আছে, GitLab-এ “protected branches”, Bitbucket-এ “branch restrictions” বা “branch permissions” — লেখার সময়ের নাম, সংস্করণভেদে আলাদা)। সাধারণ নিয়মগুলো:

  • সরাসরি push নিষেধ — সব বদল PR দিয়ে
  • অন্তত ১ বা ২টি approval বাধ্যতামূলক; নতুন commit এলে পুরোনো approval বাতিল (ঐচ্ছিক)
  • নির্দিষ্ট status check সবুজ হতেই হবে
  • সব মন্তব্যের thread মীমাংসা হতে হবে
  • Force push আর branch মোছা নিষেধ; কখনো signed commit বাধ্যতামূলক (পর্ব ৫৭)
  • কোন merge পদ্ধতি চলবে (শুধু squash, বা শুধু linear history)

কোন ফাইল কে review করবেন, তা ঠিক করে CODEOWNERS ফাইল — GitHub আর GitLab দুটিতেই আছে (Bitbucket-এ সমতুল্য সুবিধা লেখার সময়ে ভিন্নভাবে)। প্রতিটি লাইনে একটি path-pattern আর মালিক; GitHub-এ ফাইলের শেষের দিকের মিলে-যাওয়া লাইনটি প্রাধান্য পায়:

# Default owners for everything in the repository
*                 @example/core-team

# Payment code needs the payments team
/src/payment/     @example/payments

# Database migrations need a database reviewer
*.sql             @example/db-reviewers

এখন কেউ src/payment/-এর কোনো ফাইল বদলালে platform নিজে থেকেই payments দলকে reviewer হিসেবে যোগ করে, আর “code owner-এর review বাধ্যতামূলক” নিয়ম চালু থাকলে তাঁদের approval ছাড়া merge হবে না। বড় monorepo-তে এটি বিশেষ কাজের (পর্ব ১৯ — বড় repository).

Fork থেকে আসা PR-এ CI চালানো নিরাপত্তার প্রশ্ন। অচেনা কেউ PR-এ এমন কোড বা build script দিতে পারেন, যা CI-এর ভেতরে আপনার গোপন token পড়ে বাইরে পাঠায়। তাই platform-গুলো সাধারণত fork-এর PR-এর CI-তে secret দেয় না, আর প্রথমবারের অবদানকারীর CI চালাতে maintainer-এর অনুমতি চায়। এই নিরাপত্তা-ব্যবস্থা পাশ কাটানোর সেটিং চালু করার আগে documentation ভালো করে পড়ুন; বিস্তারিত পর্ব ৫৭ আর ৫৮-এ।

এবার একটি PR-এর পুরো জীবন ধাপে ধাপে দেখুন — ধরুন main-এ নিয়ম: ১টি approval, CI সবুজ, আর শুধু squash merge.

  1. Draftকাজ চলছে
  2. Readyreview চাই
  3. Changesrequested
  4. ApprovedCI সবুজ
  5. Mergedsquash
PR #7-এর পাতা
অবস্থা
branch-এ commit
CI check
review
merge বোতাম
  1. Draft PR. নাদিয়া কাজ শেষ হওয়ার আগেই draft খুললেন। CI চলল আর সবুজ হলো, কিন্তু merge বোতাম বন্ধ — draft অবস্থায় merge হয় না। দলের কেউ চাইলে আগেভাগে দিক নিয়ে মত দিতে পারেন।
  2. Ready for review. এর মধ্যে upstream এগিয়ে যাওয়ায় branch rebase করেছেন (অংশ ৪), তাই commit দুটির hash এখন e993824 আর 7e2341e. বিবরণ লিখে “Ready for review” চাপলেন। বদলানো ফাইল সবার জন্য, তাই CODEOWNERS-এর * লাইন অনুযায়ী core-team নিজে থেকেই reviewer. নিয়মে ১টি approval লাগে, আছে ০টি — বোতাম বন্ধ।
  3. Request changes. রহিম coupon_len লাইনে মন্তব্য করলেন: “নামটি coupon_max_length করলে পরিষ্কার হয়” — সিদ্ধান্ত request changes. এখন merge পুরোপুরি আটকে।
  4. Fixup commit push. নাদিয়া নাম বদলে git commit --fixup দিয়ে একটি বাড়তি commit 101e41a বানিয়ে push করলেন (অংশ ৭)। CI আবার চলল। Request changes এখনো বহাল — reviewer নিজে আবার না দেখা পর্যন্ত সাধারণত তা সরে না।
  5. Autosquash আর lease. Merge-এর আগে history পরিষ্কার করতে নাদিয়া autosquash করলেন: fixup মিশে গেল, branch-এ আবার ২টি commit (a36d5d2 নতুন hash). --force-with-lease push; CI আবার সবুজ। রহিমকে মন্তব্যে জানালেন কী বদলেছে।
  6. Approve. রহিম শুধু শেষ পরিবর্তনটুকু দেখে (অনেক platform “শেষ review-এর পরের পরিবর্তন” আলাদা করে দেখায়) approve করলেন। ১/১ approval, CI সবুজ — বোতাম খুলে গেল।
  7. Merged. রহিম “Squash and merge” চাপলেন। main-এ একটি নতুন commit; বিবরণের Closes #42-এর কারণে issue #42 বন্ধ; source branch মুছে ফেলা হলো। PR-এর পাতা আর আলোচনা platform-এ থেকে যায় — ভবিষ্যতে “কেন এই বদল” খুঁজতে কাজে লাগে।

৭ · Review-এর মন্তব্য সামলানো — নতুন commit, fixup আর autosquash

Reviewer পরিবর্তন চাইলে দুটি প্রচলিত পথ:

  • সাধারণ নতুন commit (Rename coupon_len) push করা। সহজ, force push লাগে না, আর reviewer ঠিক কী বদলাল আলাদা করে দেখতে পান। দল squash merge করলে শেষে সব এক commit হবে, তাই মাঝের commit-গুলো নিয়ে চিন্তা নেই।
  • Fixup commit বানিয়ে পরে autosquash. দল যদি merge commit বা rebase-merge ব্যবহার করে (অর্থাৎ PR-এর প্রতিটি commit main-এ যাবে), তাহলে “typo ঠিক”, “review মেটানো” ধরনের commit history নোংরা করে। Fixup ঠিক যে commit-কে সংশোধন করছে, তার সাথে মিশে যায়।
$ git commit --fixup=7e2341e
[feature/coupon 101e41a] fixup! Limit coupon code to 12 characters
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline main..
101e41a fixup! Limit coupon code to 12 characters
7e2341e Limit coupon code to 12 characters
e993824 Add coupon code field
$ git rebase -i --autosquash main
Successfully rebased and updated refs/heads/feature/coupon.
$ git log --oneline main..
a36d5d2 Limit coupon code to 12 characters
e993824 Add coupon code field
$ git push --force-with-lease
To ../fork.git
 + 7e2341e...a36d5d2 feature/coupon -> feature/coupon (forced update)

--fixup=7e2341e একটি সাধারণ commit বানায়, যার বার্তা fixup! + লক্ষ্য commit-এর বার্তা। এটি push করলে reviewer সংশোধনটুকু আলাদা দেখতে পান। পরে git rebase -i --autosquash main: interactive rebase-এর তালিকায় Git নিজেই fixup-টিকে লক্ষ্যের ঠিক নিচে বসিয়ে fixup চিহ্ন দেয়; editor খুললে তালিকাটি শুধু সংরক্ষণ করে বন্ধ করলেই হয় (আমাদের জানা মতে Git ২.৪৪ থেকে --autosquash non-interactive rebase-এও কাজ করে; rebase.autoSquash config দিয়ে সবসময় চালু রাখা যায়)। ফল: ২টি commit, শেষটির বিষয়বস্তুতে সংশোধন মিশে নতুন hash a36d5d2. খেয়াল করুন e993824 বদলায়নি — rebase দেখল তার parent আগের মতোই, তাই নতুন করে বসানোর দরকার পড়েনি। আর author-এর তারিখ (10:03) আগের মতোই থাকে; শুধু committer-এর সময় নতুন — request-pull-এর আউটপুটে যে 10:07 দেখেছিলেন, সেটি committer-এর সময়।

কিছু দল review চলার সময় history-বদল (rebase, autosquash) নিষেধ করে: “review শেষ না হওয়া পর্যন্ত শুধু নতুন commit; merge-এর ঠিক আগে একবার পরিষ্কার।” কারণ force push-এর পরে reviewer-কে বুঝতে হয় কী বদলাল (কিছু platform “compare” লিংক দেয়, সবাই দেয় না)। আপনার দলের নিয়ম কী, তা PR template বা CONTRIBUTING ফাইলে লিখে রাখা ভালো।

৮ · Merge-এর তিন পথ — merge commit, squash আর rebase

PR approve হলো। এবার merge বোতামের পাশের ছোট তীর চাপলে (GitHub-এ) repository-র সেটিংয়ে চালু থাকা বিকল্পগুলো দেখা যায় — সব চালু থাকলে তিনটি: Create a merge commit, Squash and merge আর Rebase and merge. GitLab-এ project-এর সেটিংয়ে merge পদ্ধতি বাছাই করা হয় (merge commit, semi-linear history-সহ merge commit, fast-forward) আর squash আলাদা একটি বিকল্প; Bitbucket-এও merge commit, squash আর fast-forward ধরনের কৌশল আছে — নাম আর ঠিক আচরণ platform ও সংস্করণ-ভেদে আলাদা, লেখার সময়ের documentation দেখে নিন। তিনটির পেছনের Git-কাজ কিন্তু একই, আর আপনি নিজের কম্পিউটারেই তা চালিয়ে দেখতে পারেন।

রহিম নাদিয়ার fork-কে remote হিসেবে যোগ করে branch-টি আনলেন। Merge-এর ঠিক আগে অবস্থা:

$ git remote add nadia ../fork.git
$ git fetch nadia
From ../fork
 * [new branch]      feature/coupon -> nadia/feature/coupon
 * [new branch]      main           -> nadia/main
$ git log --oneline --graph main nadia/feature/coupon
* 8fab4eb Fix typo in README
| * a36d5d2 Limit coupon code to 12 characters
| * e993824 Add coupon code field
|/
* c5c366f Raise max items to 30
* 714ea35 Add README
* decee1a Add cart config

Merge base c5c366f; main-এ একটি নতুন commit (8fab4eb), PR-এ দুটি। এই একই অবস্থার তিনটি হুবহু কপিতে তিনটি পদ্ধতি চালানো হলো।

১. Merge commit = git merge --no-ff

$ git merge --no-ff nadia/feature/coupon -m 'Merge pull request #7 from nadia/feature/coupon'
Merge made by the 'ort' strategy.
 cart.conf | 2 ++
 1 file changed, 2 insertions(+)
$ git log --oneline --graph
*   b96aa2e Merge pull request #7 from nadia/feature/coupon
|\
| * a36d5d2 Limit coupon code to 12 characters
| * e993824 Add coupon code field
* | 8fab4eb Fix typo in README
|/
* c5c366f Raise max items to 30
* 714ea35 Add README
* decee1a Add cart config

PR-এর দুটি commit হুবহু একই hash নিয়ে (e993824, a36d5d2) main-এর history-তে ঢুকল, সাথে একটি merge commit b96aa2e, যার প্রথম parent 8fab4eb আর দ্বিতীয় a36d5d2. Platform fast-forward সম্ভব হলেও সাধারণত merge commit বানায়, অর্থাৎ --no-ff-এর মতো আচরণ করে; বার্তায় PR-এর নম্বর থাকে। সুবিধা: সম্পূর্ণ, অপরিবর্তিত history; git log --first-parent main দেখায় শুধু “কোন PR কখন ঢুকল” (পর্ব ১৩)। পুরো PR ফেরত নিতে একটি git revert -m 1 b96aa2e যথেষ্ট (পর্ব ১১)। অসুবিধা: PR-এর প্রতিটি অগোছালো commit-ও চিরকাল থেকে যায়।

২. Squash and merge = git merge --squash + git commit

$ git merge --squash nadia/feature/coupon
Automatic merge went well; stopped before committing as requested
Squash commit -- not updating HEAD
$ git status -s
M  cart.conf
$ git commit --author='Nadia <nadia@example.com>' -m 'Add coupon code support (#7)'
[main 04dd1b1] Add coupon code support (#7)
 Author: Nadia <nadia@example.com>
 1 file changed, 2 insertions(+)
$ git log --oneline --graph
* 04dd1b1 Add coupon code support (#7)
* 8fab4eb Fix typo in README
* c5c366f Raise max items to 30
* 714ea35 Add README
* decee1a Add cart config
$ git log -1 --format='%h author %an, committer %cn'
04dd1b1 author Nadia, committer Rahim

--squash merge-এর ফলাফল working tree আর index-এ বসায়, কিন্তু commit করে না, আর HEAD-ও সরায় না (Squash commit -- not updating HEAD). git status -s-এর M (প্রথম ঘরে M) মানে বদলটি staged. তারপর একটি সাধারণ commit — একটিমাত্র parent (8fab4eb), আর PR-এর দুটি commit main-এর history-তে একেবারেই নেই। এখানে --author দিয়ে নাদিয়াকে লেখক রাখা হয়েছে; platform-এর squash সাধারণত PR-এর লেখককেই author করে, আর একাধিক লেখক থাকলে Co-authored-by: trailer যোগ করতে পারে (platform-ভেদে আলাদা)। -m না দিলে Git editor-এ সব commit-এর বার্তা জুড়ে একটি খসড়া দেয়। সুবিধা: main-এর প্রতিটি commit = একটি পূর্ণ PR, পরিষ্কার সরলরেখা। অসুবিধা: PR-এর ভেতরের ধাপগুলো (কোন commit কোন সিদ্ধান্ত) main-এ হারায়, বড় PR-এ bisect কম সূক্ষ্ম হয় (পর্ব ৫৫), আর squash-এর পরে পুরোনো branch-টি Git-এর চোখে “merged” নয় — তার ওপর আবার কাজ করলে একই বদল দুবার আসার মতো conflict হতে পারে।

৩. Rebase and merge = git rebase + fast-forward

$ git switch -c pr-7 nadia/feature/coupon
Switched to a new branch 'pr-7'
branch 'pr-7' set up to track 'nadia/feature/coupon'.
$ git rebase main
Successfully rebased and updated refs/heads/pr-7.
$ git switch main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
$ git merge --ff-only pr-7
Updating 8fab4eb..aced851
Fast-forward
 cart.conf | 2 ++
 1 file changed, 2 insertions(+)
$ git log --oneline --graph
* aced851 Limit coupon code to 12 characters
* 2ba98d0 Add coupon code field
* 8fab4eb Fix typo in README
* c5c366f Raise max items to 30
* 714ea35 Add README
* decee1a Add cart config

PR-এর দুটি commit 8fab4eb-এর ওপর নতুন করে বসল — বার্তা আর author একই, কিন্তু hash নতুন: e993824 → 2ba98d0, a36d5d2 → aced851. তারপর main শুধু fast-forward হলো; কোনো merge commit নেই। (Terminal-এ Your branch is up to date with 'origin/main' লাইনটি রহিমের clone-এর origin, অর্থাৎ upstream-এর সাথে তুলনা — push-এর আগের অবস্থা।) সুবিধা: সরলরেখা, আর PR-এর প্রতিটি অর্থবহ commit আলাদা থাকে। অসুবিধা: hash বদলায় বলে নাদিয়ার কাছে থাকা commit আর main-এর commit আলাদা; signed commit-এর স্বাক্ষর নতুন commit-এ থাকে না; আর প্রতিটি মধ্যবর্তী commit-কে নিজে build ও test-যোগ্য হতে হয়, নইলে bisect ভুল পথে যায়। GitHub-এর documentation অনুযায়ী তাদের “Rebase and merge” fast-forward সম্ভব হলেও commit নতুন করে লেখে (committer তথ্য বদলায়) — লেখার সময়ের আচরণ যাচাই করে নিন।

তিনটি কপির শেষ অবস্থায় git rev-parse --short HEAD^{tree} চালালে প্রতিটিতে একই উত্তর আসে: b0db900. অর্থাৎ ফাইলের শেষ snapshot তিনটিতেই হুবহু এক (patch-এর পথে git am দিয়েও একই tree পাওয়া যায়) — পার্থক্য শুধু history-র আকারে। নিচের stepper-এ তিনটি পাশাপাশি দেখুন:

BTP1P2 MS P1′P2′ c5c366f8fab4eb e993824a36d5d2 b96aa2e04dd1b1 2ba98d0aced851 PR branch main main main আগে: base c5c366f, দুই দিকে নতুন কাজ merge commit: P1, P2 অক্ষত + M (২ parent) squash: একটি নতুন commit S (১ parent) rebase: P1′, P2′ নতুন hash, সরলরেখা
main-এর অবস্থা
main-এর tip
c5c366f-এর পরে main-এ commit
PR-এর আসল hash main-এ?
tree (snapshot)
  1. Merge-এর আগে। নীল সারি main: B = c5c366f, তারপর রহিমের T = 8fab4eb. সবুজ সারি PR branch: P1 = e993824, P2 = a36d5d2, শুরু B থেকে। তীর parent-এর দিকে। দুই পথ আলাদা, তাই সরাসরি fast-forward সম্ভব নয়।
  2. Merge commit. নতুন M = b96aa2e, প্রথম parent T, দ্বিতীয় P2. P1 আর P2 নিজেদের hash নিয়েই main-এর অংশ। main-এ B-এর পরে এখন ৪টি commit.
  3. Squash. নতুন S = 04dd1b1, একমাত্র parent T; P1 আর P2-এর সব বদল S-এর ভেতরে। ফিকে বৃত্ত দুটি এখনো fork-এর branch-এ আছে, কিন্তু main থেকে আর পৌঁছানো যায় না। B-এর পরে ২টি commit.
  4. Rebase-merge. P1 আর P2 T-এর ওপর নতুন করে বসল: P1′ = 2ba98d0, P2′ = aced851; তারপর main fast-forward. B-এর পরে ৩টি commit, সরলরেখা। চারটি ক্ষেত্রেই (আগেরটি বাদে) tree b0db900 — ফাইল এক, history ভিন্ন।

নিচের প্রোগ্রামে একই তিনটি পদ্ধতি একটি খেলনা commit-মডেলে। এখানে commit-এর id একটি ছোট hash ফাংশন (FNV-1a, Git-এর SHA-1-এর বদলে শুধু শেখানোর জন্য) দিয়ে parent, বার্তা আর পরিবর্তন থেকে হিসাব হয় — তাই আসল Git-এর মতোই parent বদলালে id বদলায়:

// Three platform merge methods on the same PR. A commit = {id, msg, parents, change}.
// ids come from FNV-1a 32-bit (a stand-in for Git's SHA-1), so a commit with a new
// parent gets a new id - exactly why rebased commits change their hashes.
function fnv(s) { let h = 0x811c9dc5; for (const c of s) h = Math.imul(h ^ c.charCodeAt(0), 0x01000193) >>> 0; return h.toString(16).padStart(8, "0").slice(0, 7); }
function mk(msg, parents, change) { const id = fnv(parents.join() + "|" + msg + "|" + change); return { id, msg, parents, change }; }
const A = mk("Raise max items to 30", [], "max_items");
const T = mk("Fix typo in README", [A.id], "readme");          // main moved on after the PR opened
const P1 = mk("Add coupon code field", [A.id], "coupon");
const P2 = mk("Limit coupon code to 12 characters", [P1.id], "coupon_len");
const pr = [P1, P2];
function show(name, commits) {                                  // newest first, like git log --oneline
  console.log("== " + name);
  for (const c of [...commits].reverse())
    console.log("  " + c.id + " " + c.msg + (c.parents.length > 1 ? "   <- 2 parents" : ""));
}
// 1) Merge commit (git merge --no-ff): PR commits kept as they are + one merge commit.
const M = mk("Merge pull request #7", [T.id, P2.id], "merge");
show("merge commit", [A, P1, P2, T, M]);
// 2) Squash and merge (git merge --squash + git commit): one new commit, all changes in it.
const S = mk("Add coupon code support (#7)", [T.id], pr.map(c => c.change).join("+"));
show("squash and merge", [A, T, S]);
// 3) Rebase and merge (git rebase + fast-forward): each PR commit replayed on top of T.
let base = T.id; const replayed = [];
for (const c of pr) { const n = mk(c.msg, [base], c.change); replayed.push(n); base = n.id; }
show("rebase and merge", [A, T, ...replayed]);
console.log("old PR ids:", pr.map(c => c.id).join(" "), "| after rebase:", replayed.map(c => c.id).join(" "));
const changes = l => l.map(c => c.change).filter(x => x !== "merge").join("+").split("+").sort().join(",");
console.log("same final changes?", changes([A, P1, P2, T]) === changes([A, T, S]) && changes([A, T, S]) === changes([A, T, ...replayed]));

আউটপুট:

== merge commit
  cb7a495 Merge pull request #7   <- 2 parents
  397fb8b Fix typo in README
  66688c3 Limit coupon code to 12 characters
  d1dcdb3 Add coupon code field
  1327aff Raise max items to 30
== squash and merge
  3d22fd6 Add coupon code support (#7)
  397fb8b Fix typo in README
  1327aff Raise max items to 30
== rebase and merge
  30f1e5f Limit coupon code to 12 characters
  6cdb3fa Add coupon code field
  397fb8b Fix typo in README
  1327aff Raise max items to 30
old PR ids: d1dcdb3 66688c3 | after rebase: 6cdb3fa 30f1e5f
same final changes? true

Id-গুলো খেলনা hash, তাই আসল Git-এর hash-এর সাথে মিলবে না — মিলবে আচরণ। Merge commit: PR-এর id দুটি (d1dcdb3, 66688c3) অপরিবর্তিত, ওপরে দুই-parent-ওয়ালা একটি commit (তালিকাটি সরল ক্রমে, git log --graph-এর মতো সাজানো নয়)। Squash: তিনটি commit মাত্র; PR-এর কোনো id নেই। Rebase: একই বার্তা, কিন্তু id নতুন — কারণ প্রথমটির parent এখন 397fb8b, আর দ্বিতীয়টির parent নতুন প্রথমটি; একটি বদল শিকলের মতো পরের সবগুলো বদলায়। শেষ লাইন নিশ্চিত করে তিন পথেই মোট পরিবর্তন একই।

বিষয়Merge commitSquashRebase-merge
Git সমতুল্যmerge --no-ffmerge --squash + commitrebase + merge --ff-only
main-এ নতুন commitPR-এর সব + ১টি merge১টিPR-এর সব (নতুন hash)
History-র আকারশাখা-প্রশাখাসরলরেখাসরলরেখা
পুরো PR revertrevert -m 1 <merge>একটি commit revertকয়েকটি commit revert
কখন মানানসইPR-এর commit-গুলো যত্নে সাজানো, history-র সম্পূর্ণতা জরুরিPR-এর ভেতরের commit অগোছালো; “এক PR = এক commit” চাইপ্রতিটি commit পরিষ্কার ও test-যোগ্য, আর merge commit চান না

৯ · শিষ্টাচার আর যোগাযোগ — দলে PR-এর ভাষা

PR একটি প্রযুক্তিগত জিনিস, কিন্তু এর বেশিরভাগ সমস্যা মানুষের। দলীয় ক্রিকেট ম্যাচের কথা ভাবুন: ফিল্ডার ক্যাচ ফেললে অধিনায়ক যদি সবার সামনে চেঁচান, পরের বলে সে আরও ভয়ে থাকবে। আর অধিনায়ক যদি কিছুই না বলেন, একই ভুল বারবার হবে। ভালো অধিনায়ক নির্দিষ্ট করে, শান্তভাবে, ম্যাচের স্বার্থে বলেন। Review-ও তাই।

PR-এর লেখকের জন্য

  • নিজের PR আগে নিজে review করুন — diff-এর প্রতিটি লাইন পড়ুন। Debug-এর console.log, মন্তব্য করে রাখা কোড, অপ্রাসঙ্গিক ফাইল প্রায়ই এখানেই ধরা পড়ে।
  • Reviewer-এর প্রতিটি মন্তব্যের উত্তর দিন: “ঠিক করেছি (commit 101e41a)”, বা কেন একমত নন তার কারণ। চুপচাপ thread “resolve” করে দেওয়া এড়িয়ে চলুন।
  • মন্তব্যকে ব্যক্তিগত আক্রমণ ভাববেন না — review হয় কোডের, মানুষের নয়।
  • অনেক দিন উত্তর না এলে ভদ্রভাবে মনে করিয়ে দিন; দলের চ্যানেলে একটি লিংক আর এক লাইনের সারাংশ যথেষ্ট।

Reviewer-এর জন্য

  • প্রশ্নের আকারে লিখুন, আদেশে নয়: “এখানে null এলে কী হবে?” — “এটা ভুল” নয়।
  • কোনটি বাধ্যতামূলক আর কোনটি রুচির ব্যাপার, আলাদা করে বলুন। অনেক দল nit: (ছোট, ঐচ্ছিক), question:, suggestion: ধরনের উপসর্গ ব্যবহার করে — “Conventional Comments” নামে এর একটি প্রকাশিত প্রস্তাবও আছে। এ ধরনের ঐচ্ছিক মন্তব্যকে বলে nitpick.
  • দ্রুত সাড়া দিন। সম্পূর্ণ review এখনই সম্ভব না হলে অন্তত জানান কখন দেখবেন। অংশ ৫-এর মডেল দেখিয়েছে, অপেক্ষাই PR-এর সবচেয়ে বড় খরচ।
  • ভালো কাজের প্রশংসাও লিখুন — বিশেষ করে নতুন সহকর্মীর প্রথম PR-এ।

বাংলাভাষী দলে কয়েকটি বাস্তব কথা

আমাদের অনেক দলে কোডের কথা হয় ইংরেজি আর বাংলা মিশিয়ে, কখনো Banglish-এ। কোনোটিই ভুল নয়; দরকার হলো দলের সবাই যেন বুঝতে পারেন। কয়েকটি অভ্যাস কাজে দেয়:

  • PR-এর শিরোনাম, commit বার্তা আর কোডের মন্তব্য একটি ভাষায় (সাধারণত ইংরেজি), কারণ ভবিষ্যতে দেশের বাইরের কেউ বা search tool এগুলো পড়বে। আলোচনার thread-এ যে ভাষায় সবাই স্বচ্ছন্দ, সেটি চলতে পারে।
  • বয়স বা পদের সংকোচ: “ভাইয়া/আপু”-র কোডে প্রশ্ন তুলতে অনেক নতুন সদস্য দ্বিধা করেন। দলের নেতারা নিজে থেকে বলে দিলে ভালো: “আমার PR-এও প্রশ্ন করবেন, সেটাই কাজ।” Review-কে সম্মানের পরীক্ষা নয়, শেখার জায়গা হিসেবে দেখা জরুরি।
  • লেখায় সুর বোঝা কঠিন। সামনাসামনি যে কথা হাসিমুখে বলতেন, লেখায় তা কড়া শোনাতে পারে। জটিল বা আবেগপ্রবণ আলোচনা দীর্ঘ হলে ছোট একটি call করে নিন, তারপর সিদ্ধান্তটি PR-এ লিখে রাখুন — যাতে পরে যে কেউ জানতে পারেন।
  • সময় আর ছুটি: ঈদ বা পূজার আগে বড় PR না খোলাই ভালো; reviewer না থাকলে PR পুরোনো হয়ে conflict জমে।

আরও গভীরে: Code review নিয়ে সবচেয়ে উদ্ধৃত গবেষণাগুলোর একটি Alberto Bacchelli আর Christian Bird-এর “Expectations, Outcomes, and Challenges of Modern Code Review” (ICSE ২০১৩), যা Microsoft-এর ডেভেলপারদের ওপর করা; তাঁদের পর্যবেক্ষণ ছিল, মানুষ মূলত ত্রুটি খুঁজতে review করেন বলে মনে করেন, কিন্তু বাস্তবে review-এর বড় লাভ জ্ঞান ভাগাভাগি, দলের সচেতনতা আর বিকল্প সমাধান খোঁজা। Git-এর দিক থেকে git help format-patch, git help am, git help request-pull আর Linux kernel-এর Documentation/process/ ফোল্ডার email-ভিত্তিক ধারার নির্ভরযোগ্য উৎস; platform-এর merge পদ্ধতির ঠিক আচরণ জানতে সেই platform-এর সর্বশেষ documentation পড়ুন।

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

  • Pull request কি Git-এর feature? — না; hosting platform-এর সুবিধা। Git-এর নিজের পথ format-patch/send-email/am আর request-pull.
  • Fork আর clone-এর পার্থক্য? — Fork হলো platform-এর server-এ আপনার মালিকানার কপি; clone হলো যেকোনো repository-র স্থানীয় কপি। Fork workflow-এ fork-কে clone করেন।
  • Fork-কে upstream-এর সাথে হালনাগাদ রাখবেন কীভাবে? — git fetch upstream, git switch main, git merge --ff-only upstream/main, git push origin main; feature branch দরকারে rebase করে --force-with-lease.
  • Squash merge আর rebase merge-এর পার্থক্য? — Squash সব commit মিলিয়ে একটি নতুন commit বানায়; rebase merge প্রতিটি commit আলাদা রেখে নতুন base-এর ওপর বসায় (নতুন hash). দুটিতেই merge commit নেই।
  • --force আর --force-with-lease-এর পার্থক্য? — দ্বিতীয়টি remote-এর branch আপনার শেষ দেখা অবস্থায় না থাকলে push প্রত্যাখ্যান করে, তাই অন্যের কাজ নীরবে মোছে না।

১০ · AI-এর যুগে — PR-এর কাজে AI-কে যাচাই

AI এখন PR-এর বিবরণ লিখে দেয়, diff পড়ে review মন্তব্য দেয়, এমনকি conflict মেটানোর command সাজেস্ট করে। কাজের সহায়ক, কিন্তু এই বিষয়ে এর কিছু চেনা ভুল আছে:

AI-এর ভুলকীভাবে ধরবেন
“Fork-এর branch পুরোনো? git push --force দিন” — lease ছাড়া, এমনকি main-এশুধু নিজের PR branch-এ, শুধু --force-with-lease; আগে git fetch, তারপর git log ..origin/branch দেখে নিন remote-এর কোন commit মুছে যাবে
git reset --hard upstream/main — local-এর অ-push করা commit নীরবে হারায়আগে git status আর git log upstream/main..main; নিরাপদ বিকল্প merge --ff-only
বানানো command বা flag (যেমন অস্তিত্বহীন git pull-request) বা platform-এর পুরোনো নামgit help -a, অফিসিয়াল documentation; platform-এর CLI (যেমন GitHub-এর gh) Git-এর অংশ নয়
PR-এর বিবরণে “সব test pass করেছে” — অথচ test চালানোই হয়নিCI-এর ফল আর নিজের চালানো test-এর আউটপুট মিলিয়ে নিন; যা করেননি তা লিখবেন না
Diff-এর সারাংশে এমন পরিবর্তনের কথা, যা diff-এ নেই (বা উল্টো)বিবরণের প্রতিটি দাবি diff-এর সাথে মিলিয়ে পড়ুন
AI reviewer শুধু style নিয়ে অসংখ্য মন্তব্য, আসল যুক্তির ভুল বাদমানুষের review বাদ দেবেন না; linter-কে style-এর কাজ দিন (পর্ব ৪৪)
Closing keyword বা merge পদ্ধতি নিয়ে আত্মবিশ্বাসী কিন্তু ভুল দাবিআপনার platform-এর বর্তমান documentation
পুরো diff প্রম্পটে পেস্ট — সাথে API key বা গ্রাহকের তথ্যAI-কে দেওয়ার আগে গোপন মান মুছে দিন; secret ফাঁস হলে কী করবেন, পর্ব ৫৭

নিচের prompt-এ AI-কে PR-এর বিবরণের খসড়া লিখতে বলা হয়েছে, কিন্তু শুধু দেওয়া তথ্য থেকে — আর যা জানে না, সেখানে ফাঁকা রাখতে বলা হয়েছে:

আমি একটি pull request খুলছি। নিচে git diff --stat, commit-এর তালিকা আর আমার নিজের লেখা নোট দিলাম (গোপন মান মুছে দিয়েছি): [git log --oneline main.. এর আউটপুট] [git diff --stat main... এর আউটপুট] [আমার নোট: কেন এই বদল, কোন issue, কী test চালিয়েছি] ১. "কী বদলেছে / কেন / কীভাবে পরীক্ষা করা হয়েছে / reviewer-এর জন্য নোট" — এই চার ভাগে একটি বিবরণ লেখো। ২. শুধু আমার দেওয়া তথ্য ব্যবহার করবে। আমি যে test-এর কথা বলিনি, তা "চালানো হয়েছে" লিখবে না — সেখানে [যাচাই বাকি] লিখবে। ৩. এই PR কি একটির বেশি কাজ মেশাচ্ছে? মেশালে কীভাবে ভাগ করা যায়, প্রস্তাব দাও। ৪. কোনো git command সাজেস্ট করলে তার প্রতিটি কী বদলায় (local branch, remote, history) লেখো; force push লাগলে কারণসহ শুধু --force-with-lease.

AI-এর সাহায্যে PR — চেকলিস্ট

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

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

প্র ০১Git-এর জন্মস্থান Linux kernel আজও web-এর pull request নয়, email-এ patch দিয়ে চলে। কেন? এর লাভ-ক্ষতি কী?

Linux kernel-এ প্রতি release-এ হাজারো মানুষের কাজ ঢোকে, আর তার কাঠামো স্তরভিত্তিক: অবদানকারী patch পাঠান সংশ্লিষ্ট subsystem-এর mailing list-এ; subsystem maintainer সেগুলো নিজের tree-তে নেন; তারপর merge window-তে Linus Torvalds-কে git request-pull-এ তৈরি email পাঠান। এই ধারা Git-এর আগে থেকেই চলছিল, আর Git তৈরিই হয়েছিল একে দ্রুত করতে।

লাভগুলো: email কোনো একটি কোম্পানির সম্পত্তি নয় — platform বন্ধ হলে বা নিয়ম বদলালে আলোচনা হারায় না; সব আলোচনা public archive-এ থাকে (যেমন lore.kernel.org). যে কেউ যে কোনো email client বা নিজের tool দিয়ে কাজ করতে পারেন। Patch পড়ে reply-তে লাইনের নিচে মন্তব্য করা খুব দ্রুত, আর বড় সিরিজকে [PATCH v3 2/7] ধরনে সংস্করণ ধরে আলোচনা করা যায়।

ক্ষতিগুলো: নতুনদের জন্য শুরু করা কঠিন — email client-এর formatting patch ভেঙে দিতে পারে, mailing list-এর আদবকায়দা শিখতে হয়। CI-এর ফল, কে approve করলেন, কোন মন্তব্য মীমাংসিত — এসব এক জায়গায় দেখা যায় না; এজন্য kernel সম্প্রদায় নিজেরাই b4-এর মতো tool আর স্বয়ংক্রিয় test bot বানিয়েছে।

Linus ২০১২ সালে GitHub-এ একটি pull request-এর নিচে প্রকাশ্য মন্তব্যে জানিয়েছিলেন, তিনি GitHub-এর pull request নেন না — তাঁর অভিযোগ ছিল তখনকার web interface-এ তৈরি merge বার্তা আর তথ্যের মান নিয়ে। পরে platform অনেক বদলেছে, কিন্তু মূল প্রশ্নটি থেকে গেছে: কাজের ধারার তথ্য কোথায় থাকবে, আর তার মালিক কে?

মূল উপলব্ধি: PR একটি ধারণা — “কাজ পাঠানো, পড়া, আলোচনা, তারপর মেলানো।” Web পাতা তার একটি রূপ, email আরেকটি। দলের আকার, বিশ্বাসের কাঠামো আর tool-এর ওপর নির্ভরতার ঝুঁকি দেখে রূপটি বাছতে হয়।

প্র ০২“আমাদের দলে শুধু squash merge চলবে” — এই নীতির পক্ষে-বিপক্ষে যুক্তি কী? কোন অবস্থায় এটি ভুল সিদ্ধান্ত?

পক্ষে: বেশিরভাগ PR-এর ভেতরের commit অগোছালো — “wip”, “fix test”, “review মেটানো”। Squash এগুলো লুকিয়ে main-এ “এক PR = এক commit” রাখে। ফলে git log পড়া সহজ, প্রতিটি commit-এ PR-এর নম্বর থাকে, পুরো feature ফেরত নিতে একটি git revert যথেষ্ট, আর লেখকদের history পরিষ্কার রাখার প্রশিক্ষণ লাগে না। Changelog বানানোর tool-ও (পর্ব ১৮) এক commit থেকে এক লাইন নিতে পারে।

বিপক্ষে: যত্ন নিয়ে সাজানো commit-এর তথ্য হারায়। ধরুন একটি PR-এ তিনটি ধাপ: “নতুন interface যোগ”, “পুরোনো code নতুন interface-এ সরানো”, “পুরোনো code মোছা”। আলাদা থাকলে git bisect (পর্ব ৫৫) ঠিক কোন ধাপে bug এল বলে দিতে পারত; squash-এর পরে বলবে শুধু “এই ৮০০ লাইনের কোথাও”। আর squash-এর পরে মূল branch-টি Git-এর হিসাবে merged নয়; তার ওপর ভিত্তি করে কেউ পরের কাজ শুরু করলে (stacked PR) একই বদল দুবার আসার মতো conflict হয়।

কখন ভুল: বড়, দীর্ঘ PR যেখানে প্রতিটি commit অর্থবহ; বাইরের অবদানকারীর signed commit বা Signed-off-by যেখানে আইনি কারণে অক্ষত রাখা দরকার; আর যেখানে PR-গুলো অন্য PR-এর ওপর দাঁড়িয়ে আছে। সমাধান হিসেবে অনেক দল দুটি নিয়ম মেলায়: সাধারণত squash, কিন্তু maintainer চাইলে merge commit; অথবা লেখককে PR-এর commit autosquash দিয়ে সাজাতে বলা, তারপর rebase-merge.

মূল উপলব্ধি: Merge পদ্ধতি হলো কোন তথ্য ভবিষ্যতের জন্য রাখবেন তার সিদ্ধান্ত। Squash সরলতা কেনে সূক্ষ্মতা দিয়ে; merge commit সম্পূর্ণতা কেনে ভিড় দিয়ে; rebase-merge সরলরেখা কেনে hash বদল আর প্রতিটি commit পরিষ্কার রাখার দায় দিয়ে।

প্র ০৩“PR ছোট রাখুন” — কিন্তু একটি বড় feature কীভাবে ছোট ছোট PR-এ ভাঙবেন, যখন প্রতিটি অংশ একা কোনো কাজেই লাগে না?

এটিই বাস্তবের আসল কঠিন প্রশ্ন। ধরুন একটি কাল্পনিক বিশ্ববিদ্যালয়ের ফলাফল পোর্টালে “GPA-র বদলে CGPA দেখানো” — database, হিসাবের কোড, API আর পাতা, চার জায়গায় বদল। এক PR-এ দিলে ২০০০ লাইন। ভাঙার কয়েকটি পরীক্ষিত কৌশল:

  • স্তর ধরে, কিন্তু প্রতিটি চলার মতো: প্রথম PR-এ শুধু নতুন column আর migration (পুরোনো কোড অপরিবর্তিত); দ্বিতীয়টিতে হিসাবের function আর তার test (এখনো কেউ ডাকে না); তৃতীয়টিতে API; চতুর্থটিতে পাতা। প্রতিটি merge হলেও ব্যবহারকারী কিছু টের পান না।
  • Feature flag: নতুন পথ কোডে ঢুকছে, কিন্তু flag বন্ধ। Trunk-based development (পর্ব ১৭) ঠিক এভাবে কাজ করে।
  • আগে যান্ত্রিক কাজ: নাম বদল, ফাইল সরানো, formatting আলাদা PR-এ — reviewer দ্রুত পার করতে পারেন, আর আসল যুক্তির PR ছোট থাকে।
  • Stacked PR: দ্বিতীয় PR প্রথমটির branch-এর ওপর দাঁড়িয়ে; প্রথমটি merge হলে দ্বিতীয়টি rebase. কাজের, তবে squash merge-এর সাথে ঝামেলা হয় (প্র ০২)।

কেন এত পরিশ্রম? অংশ ৫-এর মডেলে দেখেছেন, বড় কাজ queue-তে শুধু নিজে নয়, পেছনের সবাইকে আটকায় — Kanban-এ (পর্ব ৬) ছোট batch-এর যুক্তিও এটাই। প্রকাশিত নির্দেশিকাগুলোর (যেমন Google-এর “Small CLs”) যুক্তি একই দিকে: ছোট PR দ্রুত, মনোযোগ দিয়ে review হয়, সহজে revert হয়। তবে কোনো জাদু-সংখ্যা নেই; খুব ছোট PR-এর নিজস্ব খরচ আছে (CI-এর সময়, context বদল, প্রসঙ্গ হারানো)। নিয়মটি হলো: প্রতিটি PR একটি সম্পূর্ণ, যাচাইযোগ্য চিন্তা।

মূল উপলব্ধি: PR ভাঙা একটি design দক্ষতা — কোন ক্রমে বদল আনলে প্রতিটি ধাপে সিস্টেম চলমান থাকে। যিনি এটি পারেন, তিনি deployment-এর ঝুঁকিও কমাতে জানেন।

প্র ০৪Open-source project অচেনা মানুষের fork থেকে PR নেয়। এতে কী কী নিরাপত্তা-ঝুঁকি আছে, আর platform ও maintainer কীভাবে তা সামলান?

Fork workflow-এর শক্তি — যে কেউ অবদান রাখতে পারেন — একই সাথে তার ঝুঁকি। তিনটি প্রধান জায়গা:

  • CI-তে অচেনা কোড চলা: PR খুললে CI build script আর test চালায় — অর্থাৎ PR-এর লেখকের কোড আপনার CI machine-এ। তাতে deploy token বা cloud-এর চাবি থাকলে সেগুলো চুরি হতে পারে। তাই platform-গুলো সাধারণত fork-এর PR-এ secret দেয় না, প্রথমবারের অবদানকারীর CI চালাতে অনুমতি চায়। কিছু বিশেষ trigger (যেগুলো target repository-র অধিকার নিয়ে চলে) ভুলভাবে ব্যবহার করে অতীতে অনেক project ঝুঁকিতে পড়েছে বলে নিরাপত্তা গবেষকেরা জানিয়েছেন; এসব trigger-এ PR-এর কোড checkout করা বিপজ্জনক।
  • ক্ষতিকর বদল লুকানো: বড় PR-এর ভেতরে একটি নিরীহ-দেখতে লাইন, বা নির্ভরতার version বদল। ২০২৪-এর xz utils ঘটনায় একজন বহু বছর ধরে আস্থা অর্জন করে maintainer-এর ভূমিকায় এসে release-এর ভেতরে backdoor ঢুকিয়েছিলেন বলে প্রকাশিত বিশ্লেষণগুলো জানায় — সমস্যাটি শুধু PR-এর নয়, বিশ্বাসের কাঠামোর।
  • পরিচয়: commit-এর author নাম যে কেউ যা খুশি লিখতে পারেন; signed commit আর platform-এর যাচাই সাহায্য করে (পর্ব ৫৭)।

প্রতিরক্ষা: ছোট PR (যাতে পড়া সম্ভব), CODEOWNERS আর বাধ্যতামূলক review, নির্ভরতা-বদলের জন্য আলাদা সতর্কতা, CI-তে least privilege — যে কাজে যতটুকু অধিকার লাগে ততটুকুই — আর release-এর প্রক্রিয়া একজনের হাতে না রাখা।

মূল উপলব্ধি: একটি PR merge করা মানে কোডের সাথে লেখকের ওপর আস্থাও merge করা। প্রক্রিয়া এমন হওয়া উচিত, যাতে একজনের ভুল বা অসৎ উদ্দেশ্য একা পুরো ব্যবস্থাকে বিপদে ফেলতে না পারে।

প্র ০৫Code review কি মূলত bug ধরার জন্য? না হলে বাধ্যতামূলক review-এর আসল মূল্য কোথায়, আর এর খরচ কী?

সহজাত উত্তর “bug ধরা”, কিন্তু গবেষণা ছবিটা জটিল করে। Bacchelli আর Bird (ICSE ২০১৩) Microsoft-এর ডেভেলপারদের সাক্ষাৎকার আর review-এর মন্তব্য বিশ্লেষণ করে দেখান: ডেভেলপাররা প্রধান উদ্দেশ্য বলেন ত্রুটি খোঁজা, কিন্তু মন্তব্যের তুলনামূলক কম অংশ আসলে গুরুতর ত্রুটি নিয়ে; বড় অংশ কোড বোঝা, উন্নতির পরামর্শ আর জ্ঞান ভাগাভাগি। অর্থাৎ review-এর বড় মূল্য হলো:

  • জ্ঞান ছড়ানো: অন্তত দুজন মানুষ প্রতিটি বদল জানেন। একজন ছুটিতে গেলে বা চাকরি বদলালে project থমকে যায় না (“bus factor” বাড়ে)।
  • একরূপতা: নাম, গঠন আর নকশার সিদ্ধান্ত দলের নিয়মের সাথে মেলে।
  • শেখা: নতুনরা অভিজ্ঞদের মন্তব্য থেকে, আর অভিজ্ঞরা নতুনদের প্রশ্ন থেকে।
  • লেখকের সতর্কতা: কেউ পড়বেন জানলে মানুষ নিজেই বেশি যত্ন নেন।

খরচ: অপেক্ষা। প্রতিটি PR reviewer-এর জন্য বসে থাকে, আর অংশ ৫-এর মডেল দেখিয়েছে queue-তে এই অপেক্ষা কত দ্রুত বাড়ে। আরেকটি খরচ “rubber stamp” — সময়ের চাপে কোড না পড়েই “LGTM” (looks good to me) লিখে approve, যা নিরাপত্তার ভুল ধারণা দেয়। Pair programming-এর মতো বিকল্পে review কাজের সময়েই হয়ে যায়।

যন্ত্রের যা পারার কথা — formatting, সাধারণ ভুল, test — তা CI আর linter-কে দিন (পর্ব ৪৪, ৫৮), যাতে মানুষের মনোযোগ যায় নকশা, যুক্তি আর ঝুঁকিতে।

মূল উপলব্ধি: Review একটি মানুষের প্রক্রিয়া, যার লক্ষ্য দলের যৌথ বোঝাপড়া। Bug ধরা তার একটি ফল, একমাত্র নয় — আর অপেক্ষার খরচ কমাতে না পারলে লাভের চেয়ে ক্ষতি বেশি হতে পারে।

অনুশীলন

Git-এর কাজগুলো একটি throwaway scratch ফোল্ডারে চালিয়ে দেখুন — আসল project-এ নয়।

  1. Command-এর ক্রম: আপনার fork-এর main upstream-এর চেয়ে ৩টি commit পিছিয়ে, আর আপনার খোলা PR-এর branch fix/receipt পুরোনো main-এর ওপর দাঁড়িয়ে। Remote দুটি: origin (fork) আর upstream. দলের নিয়ম rebase করে হালনাগাদ রাখা। কোন command, কোন ক্রমে চালাবেন? প্রতিটির পরে কী যাচাই করবেন?
    git fetch upstream
    git switch main
    git rev-list --left-right --count main...upstream/main   # expect: 0  3
    git merge --ff-only upstream/main
    git push origin main
    git switch fix/receipt
    git rebase main                     # resolve conflicts if any, then: git rebase --continue
    git log --oneline main..            # only your PR commits, new hashes
    git push --force-with-lease

    rev-list-এর ফল 0 3 হলে local main-এ নিজের কোনো commit নেই, তাই --ff-only নিরাপদ; ভুল করে কিছু থাকলে এটি থামবে, তখন সেই commit আলাদা branch-এ সরিয়ে নিন। Rebase-এর পরে git log --oneline main.. দেখাবে শুধু আপনার commit, নতুন hash-এ; test চালিয়ে নিন। শেষে --force-with-lease — সাধারণ push প্রত্যাখ্যাত হবে, কারণ নতুন history পুরোনোটির বংশধর নয়। দলের নিয়ম merge হলে rebase-এর বদলে git merge main আর সাধারণ git push.

  2. আউটপুট অনুমান করুন: Merge-এর আগে git log --oneline --graph main pr দেখাচ্ছে:
    * 8fab4eb Fix typo in README
    | * a36d5d2 Limit coupon code to 12 characters
    | * e993824 Add coupon code field
    |/
    * c5c366f Raise max items to 30
    (ক) main-এ git merge --squash pr তারপর git commit -m "Add coupon code support (#7)" দিলে git log --oneline --graph কেমন দেখাবে? (খ) “rebase and merge” করলে main-এ c5c366f-এর পরে কয়টি commit থাকবে, আর a36d5d2 hash-টি কি main-এ দেখা যাবে?

    (ক) একটি সরলরেখা, শুধু একটি নতুন commit — আসল Git-এ চালালে:

    * 04dd1b1 Add coupon code support (#7)
    * 8fab4eb Fix typo in README
    * c5c366f Raise max items to 30

    (hash 04dd1b1 এসেছে লেখক, সময় আর বার্তা স্থির রেখে; আপনার নিজের চালানোয় এগুলো আলাদা হলে hash-ও আলাদা।) কোনো |\ শাখা নেই, কারণ squash commit-এর parent শুধু একটি। (খ) ৩টি: 8fab4eb আর PR-এর দুটি commit নতুন hash-এ (আমাদের চালানোয় 2ba98d0, aced851). a36d5d2 দেখা যাবে না — rebase-এর পরে parent বদলেছে, তাই একই বার্তার commit-টি নতুন hash পেয়েছে। a36d5d2 দেখা যেত শুধু merge commit পদ্ধতিতে।

  3. সমালোচনা ও নতুন করে লেখা: একটি PR-এর শিরোনাম fix; বিবরণ ফাঁকা; ৪১টি ফাইলে ২৩০০ লাইনের বদল — তার মধ্যে পুরো project-এর formatting, checkout-এ coupon-এর সুবিধা আর একটি নির্ভরতার version বদল; বিবরণের শেষে লেখা “Closes 42”; CI লাল, কিন্তু লেখক লিখেছেন “locally works”. কী কী সমস্যা দেখছেন? একটি ভালো বিকল্প প্রস্তাব করুন।
    • শিরোনাম: “fix” কিছুই বলে না — কী ঠিক হলো? যেমন Add coupon code to checkout.
    • পরিধি: তিনটি আলাদা কাজ এক PR-এ। ভাগ করুন: (১) formatting — শুধু যান্ত্রিক, আলাদা PR, দ্রুত পার; (২) নির্ভরতার version বদল — আলাদা, কারণ এর ঝুঁকি (breaking change, নিরাপত্তা) আলাদাভাবে দেখতে হয়; (৩) coupon-এর আসল কাজ — এখন হয়তো ২০০ লাইন, যা মনোযোগ দিয়ে পড়া যায়।
    • বিবরণ: কী, কেন, কীভাবে পরীক্ষা — অংশ ৫-এর template অনুযায়ী।
    • Linked issue: “Closes 42”-এ # নেই, তাই অনেক platform এটিকে issue-র উল্লেখ হিসেবে চিনবে না; লিখুন Closes #42.
    • CI লাল: “locally works” কোনো প্রমাণ নয় — CI-এর পরিবেশে কেন ভাঙছে তা খুঁজে ঠিক না করা পর্যন্ত review চাওয়াই উচিত নয়; দরকারে draft রাখুন।

    ভালো বিকল্প: তিনটি PR. Coupon-এর PR-এর শিরোনাম Add coupon code to checkout, বিবরণে “কী: checkout-এ coupon ঘর, সর্বোচ্চ ১২ অক্ষর · কেন: Closes #42 · কীভাবে পরীক্ষা: নতুন ৬টি unit test, হাতে ১২ আর ১৩ অক্ষর · Reviewer-এর জন্য: formatting আলাদা PR #51-এ”। CI সবুজ হলে তবেই “Ready for review”.

  4. হিসাব (মডেল থেকে): অংশ ৫-এর review-queue মডেলে অনুমান A2: approve-এর সম্ভাবনা q = 0.9L/50, আর round-সংখ্যা geometric (গড় 1/q). প্রথম review-এ reviewer-এর চাপ ৫০%, আর প্রতিটি বাড়তি round প্রথমটির অর্ধেক সময় নেয়। L = ৪০০ লাইনের জন্য (ক) গড় round, (খ) reviewer-এর মোট চাপ হিসাব করুন। (গ) কোন L-এর ওপরে চাপ ১০০% ছাড়ায়?

    (ক) L/50 = ৮, তাই q = 0.98 ≈ ০.৪৩০৫; গড় round = 1/q ≈ ২.৩২.

    (খ) বাড়তি round গড়ে ২.৩২ − ১ = ১.৩২টি, প্রতিটির চাপ প্রথমটির অর্ধেক (০.৫ × ৫০% = ২৫%). মোট চাপ = ৫০% + ১.৩২ × ২৫% ≈ ৮৩%. Simulation দেখিয়েছিল ৭৮% — ১০০টি PR-এর ছোট নমুনায় round একটু কম পড়েছে (২.১২)।

    (গ) চাপ = ৫০% + ২৫% × (1/q − ১) > ১০০% হলে 1/q > ৩, অর্থাৎ q < ১/৩. 0.9L/50 < ১/৩ ⇒ L/50 > ln 3 / ln(1/0.9) ≈ ১.০৯৯ / ০.১০৫৪ ≈ ১০.৪৩ ⇒ L > প্রায় ৫২১ লাইন. এর ওপরে এই মডেলে queue অসীমভাবে বাড়ে — যেমন ৮০০ লাইনে simulation-এর ১৪৪%. মনে রাখুন, সংখ্যাগুলো বানানো অনুমানের ফল; শিক্ষাটি প্রবণতায়: আকারের সাথে round আর চাপ দ্রুত বাড়ে।

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

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

ক্রমFork workflow-এ একটি অবদানের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🍴 Platform-এ মূল repository fork করা
  2. 💻 নিজের fork clone করে upstream remote যোগ করা
  3. ✍️ নতুন branch খুলে commit করা
  4. ⬆️ Branch-টি নিজের fork-এ (origin) push করা
  5. 📨 Fork-এর branch থেকে upstream-এর main-এ PR খোলা
  6. ✅ Review আর CI পার হয়ে maintainer-এর merge

Pull request (বা GitLab-এর merge request) সম্পর্কে কোন কথাটি সঠিক?

  • এটি Git-এর একটি object, যা .git ফোল্ডারে থাকে
  • এটি hosting platform-এর সুবিধা; branch আর commit Git-এর, আলোচনা ও review platform-এর
  • PR খোলার সাথে সাথে branch-টি main-এ merge হয়ে যায়
  • শুধু fork থেকেই PR খোলা যায়

Fork workflow-এ git fetch upstream-এর পরেও git status বলছে Your branch is up to date with 'origin/main', অথচ upstream-এ ২টি নতুন commit আছে। কেন?

  • Fetch কাজ করেনি
  • Git কখনো অন্য repository-র সাথে তুলনা করতে পারে না
  • Local main-এর tracking branch হলো origin/main (fork), তাই status শুধু fork-এর সাথে তুলনা করে
  • Fork নিজে থেকেই upstream-এর সাথে sync হয়ে গেছে

Platform-এর “Squash and merge”-এর সবচেয়ে কাছের Git সমতুল্য কোনটি?

  • git merge --squash feature, তারপর git commit
  • git merge --no-ff feature
  • git rebase main feature, তারপর main-এ git merge --ff-only feature
  • git cherry-pick দিয়ে প্রতিটি commit কপি করা

একটি PR-এর branch-এ দুটি commit P1, P2; main-এ base-এর পরে একটি নতুন commit T. কোন merge পদ্ধতিতে P1 আর P2-এর আসল hash main-এর history-তে অপরিবর্তিত থাকে?

  • Squash and merge
  • Rebase and merge
  • শুধু fast-forward
  • Merge commit (git merge --no-ff)

একটি repository-র CODEOWNERS ফাইলে আছে /src/payment/ @example/payments, আর branch protection-এ “code owner-এর review বাধ্যতামূলক” চালু। কেউ src/payment/fee.js বদলানো PR খুললে কী হবে?

  • PR খোলাই যাবে না
  • CI সবুজ হলেই merge করা যাবে
  • payments দল নিজে থেকেই reviewer হবে, আর তাঁদের approval ছাড়া merge হবে না
  • payments দলের সদস্য ছাড়া কেউ ফাইলটি পড়তেই পারবেন না

একজন অবদানকারী git format-patch দিয়ে patch পাঠালেন, maintainer git am দিয়ে বসালেন। নতুন commit-গুলোর author আর committer কে?

  • Author অবদানকারী, committer maintainer
  • দুজনই maintainer
  • দুজনই অবদানকারী
  • Author ফাঁকা থাকে, কারণ email থেকে নাম আসে না

শব্দকোষ

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

মনে রাখুন

  1. PR/MR platform-এর সুবিধা, Git-এর নয়: branch + আলোচনা + review + check + merge বোতাম। Git-এর নিজের পথ format-patch → am আর request-pull.
  2. Fork workflow: fork → clone → remote add upstream → branch → push origin → PR. হালনাগাদ: fetch upstream → merge --ff-only upstream/main → push origin main.
  3. ভালো PR: এক কাজ, ছোট, স্পষ্ট শিরোনাম, কী-কেন-কীভাবে পরীক্ষা, Closes #42 (platform-ভেদে), দরকারে draft.
  4. তিন merge পদ্ধতি: merge commit = --no-ff (hash অক্ষত), squash = --squash + commit (এক commit), rebase-merge = rebase + ff (নতুন hash). শেষ snapshot একই।
  5. নিয়ম আর আচরণ: branch protection, approval, CODEOWNERS, CI; মন্তব্য মেটাতে নতুন commit বা fixup + autosquash; force শুধু নিজের branch-এ, শুধু --force-with-lease; ভদ্র, নির্দিষ্ট, দ্রুত review.

পরের পর্বে: PR হলো একটি কাজের একক। কিন্তু পুরো দল কোন branch থেকে কাজ শুরু করবে, release কোথা থেকে যাবে, hotfix কীভাবে ঢুকবে? পর্ব ১৭-এ Git Flow, GitHub Flow আর trunk-based development — আর কেন অনেক দল লম্বা branch ছেড়ে ছোট, ঘন ঘন merge-এর দিকে গেছে।

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

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