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

Git ইন্টারভিউ — ধারণা, পরিস্থিতিভিত্তিক সমস্যা আর command-এর অনুশীলন

Git Interview Preparation — Concept Questions, Scenario Problems Solved in Real Repositories, Command Drills and Dangerous Commands

Taught in Bangla, this page practises Git interviews: a five-step answering frame with commit graphs, 25 concept questions at beginner, intermediate and experienced levels, 15 scenario problems such as a commit on the wrong branch, work lost to reset --hard or a colleague's force push, each reproduced in a real repository, command drills, and a list of dangerous commands with safe alternatives.

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

  • Git-এর প্রশ্নে অবস্থা জিজ্ঞেস করে, commit graph এঁকে, জোরে ভেবে আর ঝুঁকি বলে গোছানো উত্তর দেওয়ার পাঁচ ধাপের কাঠামো
  • শুরু, মাঝারি আর অভিজ্ঞ স্তরের ২৫টি ধারণা এমনভাবে বলা, যাতে ফলো-আপ প্রশ্নেও আটকে না যান
  • নিজের repository-র ভুল (ভুল branch, হারানো commit, অগোছালো commit) আর দলের ভুল (push-এর পরে undo, force push, পুরোনো pull request, ফাঁস হওয়া secret) ধাপে ধাপে সারানো
  • প্রচলিত কাজের ঠিক command মুখস্থ নয়, যুক্তি দিয়ে বের করা — range, ahead/behind, reset-এর তিন রূপ, lease
  • কোন command ফেরত আনা যায় না তা চেনা, আর প্রতিটির নিরাপদ বিকল্প

১ · Git-এর প্রশ্ন কীভাবে সামলাবেন — জোরে ভাবা, graph আঁকা আর রিভিশনের মানচিত্র

Developer, software engineer বা DevOps পদের ইন্টারভিউতে Git নিয়ে প্রশ্ন প্রায়ই আসে, আর তা সাধারণত তিন রকম। এক, ধারণার প্রশ্ন: “merge আর rebase-এর পার্থক্য কী?” দুই, পরিস্থিতির প্রশ্ন: “আপনি ভুল করে main-এ দুটি commit করে ফেলেছেন, এখনো push করেননি — কী করবেন?” তিন, command-এর প্রশ্ন: “feature-এ আছে কিন্তু main-এ নেই এমন commit-গুলো দেখার command লিখুন।” প্রশ্নের ধরন আর গভীরতা প্রতিষ্ঠান ও পদভেদে অনেক আলাদা; এই পাতার কোনো প্রশ্ন কোনো নির্দিষ্ট প্রতিষ্ঠান বা পরীক্ষা থেকে নেওয়া নয় — এগুলো এ ধরনের আলোচনায় যা প্রায়ই আসে, তার অনুশীলন।

রেলস্টেশনের স্টেশন মাস্টারের কথা ভাবুন। কেউ জিজ্ঞেস করলেন, “ভুল লাইনে দুটি বগি জুড়ে গেছে, ট্রেন এখনো ছাড়েনি — কী করবেন?” ভালো স্টেশন মাস্টার আগে জিজ্ঞেস করেন, “ট্রেন কি সত্যিই ছাড়েনি? অন্য কেউ কি ওই বগিতে মাল তুলেছেন?” তারপর বোর্ডে লাইনগুলো এঁকে দেখান কোন বগি কোথায় যাবে, আর কোন ধাপে কোনো বগি হারানোর ঝুঁকি নেই। Git-এর প্রশ্নও ঠিক এমন: commit হলো বগি, branch হলো লাইনের নামফলক, আর “push হয়েছে কি না” হলো “ট্রেন ছেড়েছে কি না”।

পাঁচ ধাপের কাঠামো

  1. অবস্থা জিজ্ঞেস করুন: commit কি push হয়েছে? Branch-টি কি অন্যরা ব্যবহার করেন? Working tree-তে কি commit না করা বদল আছে? এই তিন উত্তরেই ঠিক হয় ইতিহাস বদলানো চলবে কি না।
  2. Commit graph আঁকুন: বৃত্ত = commit, তীর = parent-এর দিকে, লেবেল = branch আর HEAD. বেশিরভাগ Git প্রশ্ন আসলে “কোন লেবেল কোথায় সরবে” প্রশ্ন।
  3. আগে শুধু দেখার command: git status, git log --oneline --graph --decorate --all, git reflog. ইন্টারভিউয়ারকে বলুন, “আগে নিশ্চিত হব, তারপর বদলাব।”
  4. সবচেয়ে কম ধ্বংসাত্মক সমাধান, ঝুঁকিসহ: push হয়ে থাকলে নতুন commit (revert), না হলে ইতিহাস গোছানো (reset, rebase -i); প্রতিটি ধাপ কী বদলায় আর ফেরত আনা যায় কি না, এক বাক্যে।
  5. যাচাই আর প্রতিরোধ: শেষে আবার log বা test দিয়ে প্রমাণ, তারপর “ভবিষ্যতে যাতে না হয়” — protected branch, hook, CI, দলের নিয়ম।
জোরে ভাবা — কয়েকটি কাজের বাক্য
  • “আমি ধরে নিচ্ছি এটি এখনো push হয়নি — ঠিক আছে? Push হয়ে থাকলে উত্তর বদলাবে।”
  • “একটু এঁকে নিই: main এখানে, feature এখান থেকে আলাদা হয়েছে, merge base এই commit.”
  • “এই ধাপে working tree বদলাবে না, শুধু branch-এর pointer সরবে।”
  • “এখানে ঝুঁকি আছে, তাই আগে একটি backup branch রাখছি — পরে মুছে দেব।”

এই অভ্যাসকে বলে think-aloud বা জোরে ভাবা। ইন্টারভিউয়ার তাতে আপনার যুক্তি অনুসরণ করতে পারেন, আর ভুল হলেও মাঝপথে ধরিয়ে দিতে পারেন। নিচের টেবিলে দেখুন কোন অংশে কোন পর্ব লাগবে; আটকে গেলে আগে সেই পর্বটি আবার পড়ে আসুন — পর্বগুলোই সত্যের উৎস।

অংশবিষয়রিভিশন
২Commit, তিন এলাকা, branch, HEAD, merge, reset-revert, fetch-pull, SemVer৭, ৮, ৯, ১১, ১২, ১৩, ১৪, ১৮
৩Rebase, force push, reflog, stash, cherry-pick, bisect, worktree, hook, branching strategy১৫, ১৬, ১৭, ৫৩, ৫৫, ৫৬
৪Object model, SHA-1/SHA-256, packfile, rerere, signed commit, monorepo, secret১৯, ৫২, ৫৪, ৫৭
৫–৬১৫টি পরিস্থিতি১১–১৫, ৫৩–৫৭
৭–৮Command drill আর বিপজ্জনক command১০, ১১, ১৪, ৫৮

Transcript-গুলো কীভাবে তৈরি: প্রতিটি পরিস্থিতি Git ২.৫৩ (Windows)-এ আলাদা scratch repository-তে সত্যিই চালানো। লেখক Rahim (rahim@example.com), সহকর্মী Karim — দুজনই কাল্পনিক; তারিখ স্থির (১৫ জানুয়ারি ২০২৬, সকাল ১০টা থেকে প্রতি commit-এ এক মিনিট), core.autocrlf=false, কোনো global config নেই। তাই একই ধাপ একই সময় দিয়ে চালালে আপনি একই hash পাবেন। “Remote” হলো একটি local bare repository (../s10srv.git-ধাঁচের path). কিছু commit-এর আউটপুটে Date: লাইন আসে, কারণ লেখকের সময় আমরা হাতে স্থির করে দিয়েছি; সাধারণ ব্যবহারে সেটি সাধারণত দেখা যায় না। git filter-repo Git-এর সাথে আসে না, আলাদা install করতে হয় — এখানে ২.৪৭ সংস্করণ। # … দিয়ে শুরু লাইন ব্যাখ্যা, command নয়।

২ · শুরুর স্তর — commit, তিন এলাকা, branch, HEAD আর undo-এর ধারণা

রিভিশন: পর্ব ৮ · তিনটি এলাকা, পর্ব ১১ · restore, reset, revert, পর্ব ১২ · branch আর HEAD, পর্ব ১৩ · merge, পর্ব ১৪ · remote, পর্ব ১৮ · commit আর release

এই স্তরের প্রশ্নে ইন্টারভিউয়ার দেখেন আপনি Git-কে “জাদুর বাক্স” হিসেবে নয়, একটি সরল মডেল হিসেবে বোঝেন কি না: snapshot, pointer আর তিনটি এলাকা। উত্তর ছোট রাখুন, কিন্তু প্রতিটিতে একটি ছোট উদাহরণ দিন।

প্র ০১একটি commit আসলে কী? “পরিবর্তনের তালিকা” বলা কি ঠিক? (সহজ)

উত্তর: Commit হলো পুরো project-এর একটি snapshot — সেই মুহূর্তে সব file কেমন ছিল — সাথে metadata: লেখক, committer, সময়, বার্তা আর parent commit-এর ID (প্রথম commit-এ কোনো parent নেই, merge commit-এ দুই বা বেশি). যুক্তির দিক থেকে এটি diff নয়; git show যে diff দেখায়, Git তা parent-এর snapshot-এর সাথে তুলনা করে তখনই বানায়। Commit-এর ID তার সব বিষয়বস্তুর hash, তাই কোনো কিছু বদলালে ID-ও বদলায় — commit কখনো “সম্পাদনা” হয় না, নতুন commit তৈরি হয়।

ফলো-আপ: (ক) “প্রতিটি snapshot কি পুরো file কপি করে?” — না; একই বিষয়বস্তুর file একবারই সংরক্ষিত হয় (অংশ ৪-এর প্র ১৯), আর packfile delta দিয়ে জায়গা বাঁচায়। (খ) “ভালো commit কেমন?” — একটি যুক্তিসংগত বদল, test পাস, বার্তায় “কেন” (পর্ব ১৮)।

প্র ০২Git-এর “তিনটি এলাকা” কী কী? git add আর git commit কোন এলাকা থেকে কোথায় নেয়? (সহজ)

উত্তর: Working tree (আপনার folder-এর আসল file), index বা staging area (পরের commit-এর খসড়া) আর repository (.git-এর ভেতরে commit-এর ইতিহাস)। git add working tree থেকে index-এ তোলে; git commit index-এর অবস্থাকে নতুন commit বানায় আর branch-কে সেখানে সরায়। পরীক্ষার খাতার খসড়া আর ফেয়ার কপির মতো: খসড়ায় যত খুশি কাটাকুটি, ফেয়ার কপিতে শুধু যা বেছে তুলেছেন।

git status --short-এর দুই কলাম এই এলাকাগুলোরই তুলনা: প্রথম কলাম index বনাম HEAD, দ্বিতীয়টি working tree বনাম index. M মানে staged, M মানে শুধু folder-এ, MM মানে দুটোই।

ফলো-আপ: “Index কেন দরকার, সরাসরি commit কেন নয়?” — একই file-এর কিছু অংশ বেছে commit (git add -p), merge conflict-এর সময় একাধিক রূপ রাখা, আর ছোট, যুক্তিসংগত commit বানানো। Staged আর unstaged diff আলাদা দেখতে git diff --staged আর git diff.

প্র ০৩“Git-এ branch খুব সস্তা” — কেন? Branch আসলে কী? (সহজ)

উত্তর: Branch হলো একটি commit-এর দিকে নির্দেশ করা নাম — একটি ছোট file (.git/refs/heads/main), যাতে শুধু ৪০ অক্ষরের একটি commit ID লেখা। নতুন branch মানে নতুন file, কোনো কোড কপি হয় না; তাই মুহূর্তে তৈরি হয়। নতুন commit করলে বর্তমান branch-এর pointer নিজে থেকে নতুন commit-এ সরে যায়। পুরোনো centralized system-এ (যেমন Subversion) branch মানে server-এ folder-এর একটি “সস্তা কপি”; বানানো দ্রুত হলেও প্রতিটি কাজে server লাগত, আর merge-এর হিসাব রাখা কঠিন ছিল — Git-এ সবই local আর হালকা (পর্ব ৭, পর্ব ১২)।

ফলো-আপ: (ক) “Branch মুছলে commit মুছে যায়?” — না, শুধু নামটি যায়; অন্য কোনো branch থেকে পৌঁছানো না গেলে commit কিছুদিন reflog-এ থাকে, পরে garbage collection মুছতে পারে। (খ) “Tag আর branch-এর পার্থক্য?” — tag একটি নির্দিষ্ট commit-এ স্থির থাকে, নতুন commit-এ সরে না।

প্র ০৪HEAD কী? “Detached HEAD” অবস্থা কখন হয়, আর সেটি কি বিপজ্জনক? (সহজ)

উত্তর: HEAD মানে “আপনি এখন কোথায়”। সাধারণত HEAD একটি branch-এর নাম ধরে রাখে (.git/HEAD-এ লেখা ref: refs/heads/main), আর সেই branch একটি commit ধরে রাখে। কোনো tag, পুরোনো commit বা remote branch সরাসরি checkout করলে (git switch --detach v1.0) HEAD সরাসরি একটি commit-এ বসে — এটাই detached HEAD.

দেখা বা পরীক্ষা করার জন্য এটি একেবারে নিরাপদ। ঝুঁকি একটাই: এই অবস্থায় নতুন commit করে অন্য branch-এ চলে গেলে সেই commit-গুলোর কোনো নাম থাকে না — Git সতর্ক করে, আর reflog থেকে উদ্ধার করা যায় (পরিস্থিতি ৬)। সমাধান: কাজ রাখার ইচ্ছা হলে আগেই git switch -c <নাম>.

ফলো-আপ: “HEAD~2 আর HEAD^2-এর পার্থক্য?” — ~2 মানে প্রথম parent ধরে দুই ধাপ পেছনে; ^2 মানে merge commit-এর দ্বিতীয় parent.

প্র ০৫Fast-forward merge আর three-way merge-এর পার্থক্য কী? কখন কোনটি হয়? (সহজ)

উত্তর: বর্তমান branch যদি অন্য branch-এর সরাসরি পূর্বপুরুষ হয় — অর্থাৎ বর্তমান branch আলাদা হওয়ার পরে নিজে কোনো commit পায়নি — তাহলে Git শুধু pointer-টি সামনে সরিয়ে দেয়; নতুন commit হয় না। এটাই fast-forward. দুই দিকেই নতুন commit থাকলে Git তিনটি snapshot তুলনা করে — দুই branch-এর মাথা আর তাদের merge base (সবচেয়ে কাছের সাধারণ পূর্বপুরুষ) — আর দুই parent-ওয়ালা একটি merge commit বানায়। একই লাইন দুই দিকে আলাদাভাবে বদলালে conflict.

ফলো-আপ: (ক) “--no-ff কেন?” — fast-forward সম্ভব হলেও merge commit রেখে দেখানো যে এই commit-গুলো একটি feature হিসেবে এসেছিল। (খ) “--ff-only?” — fast-forward না হলে থেমে যায়, অজান্তে merge commit হয় না। অংশ ৩-এর প্রথম simulator-এ (প্র ১০-এর নিচে) নিজে অনুমান করে যাচাই করুন।

প্র ০৬git reset-এর তিন রূপ (--soft, --mixed, --hard) আর git revert-এর পার্থক্য কী? কোনটি কখন? (মাঝারি)

উত্তর: Reset বর্তমান branch-কে অন্য commit-এ সরায় — ইতিহাস বদলায়। --soft শুধু branch সরায়; index আর working tree আগের মতো, তাই বাদ পড়া commit-গুলোর বদল staged অবস্থায় থাকে। --mixed (ডিফল্ট) branch আর index দুটোই সরায়; বদলগুলো working tree-তে unstaged থাকে। --hard তিনটিকেই সরায় — commit না করা বদল চিরতরে যায়। Revert ইতিহাস বদলায় না; একটি নতুন commit বানায়, যা পুরোনো commit-এর উল্টো বদল আনে।

নিয়ম: push হয়নি এমন নিজের commit গোছাতে reset; push হয়ে গেলে, বিশেষত shared branch-এ, revert (পরিস্থিতি ৮)।

ফলো-আপ: (ক) “reset --hard-এ হারানো commit ফেরে?” — হ্যাঁ, reflog থেকে (পরিস্থিতি ২); কিন্তু কখনো commit বা add না করা লেখা ফেরে না। (খ) “--keep?” — reset --keep --hard-এর মতো সরায়, কিন্তু local বদল হারানোর ঝুঁকি থাকলে থেমে যায় (অংশ ৮)। (গ) “Merge commit revert?” — git revert -m 1 <merge>; -m ছাড়া Git ২.৫৩ বলে “is a merge but no -m option was given”.

Reset-এর তিন রূপ নিচের ছোট model-এ চালিয়ে দেখুন। শুরুতে HEAD হলো C2 (এতে f-এর মান v2 আর নতুন file g), আর f-কে working tree-তে v3 করা হয়েছে, stage করা হয়নি। প্রশ্ন: git reset --soft/--mixed/--hard C1-এর পরে git status --short কী দেখাবে? guess-এ একটি প্রচলিত ভুল অনুমান বসানো আছে — নিজেরটি লিখে Run চাপুন।

// Three areas as maps file -> content. C1 has f=v1; C2 (HEAD) has f=v2 and adds g.
const C1 = { f: "v1" }, C2 = { f: "v2", g: "g1" };
const start = () => ({ head: C2, index: { ...C2 }, wt: { f: "v3", g: "g1" } });  // f edited, not staged
function reset(s, mode, target) {
  s.head = target;                                        // --soft: only HEAD moves
  if (mode !== "soft") s.index = { ...target };           // --mixed: index too
  if (mode === "hard") s.wt = { ...target };              // --hard: working tree too
  return s;
}
function status(s) {                                      // like "git status --short"
  const files = [...new Set([...Object.keys(s.head), ...Object.keys(s.index), ...Object.keys(s.wt)])].sort();
  return files.map(f => {
    if (!(f in s.index) && !(f in s.head)) return "?? " + f;
    const x = !(f in s.head) ? "A" : !(f in s.index) ? "D" : s.head[f] !== s.index[f] ? "M" : " ";
    const y = !(f in s.index) ? " " : !(f in s.wt) ? "D" : s.index[f] !== s.wt[f] ? "M" : " ";
    return x + y + " " + f;
  }).filter(l => l.slice(0, 2) !== "  ").join(" | ") || "(clean)";
}
// Your prediction for "git reset --<mode> C1" - edit, then Run
const guess = { soft: "M  f | A  g", mixed: " M f | ?? g", hard: "(clean)" };
console.log("before".padEnd(6), status(start()));
for (const mode of ["soft", "mixed", "hard"]) {
  const real = status(reset(start(), mode, C1));
  console.log(mode.padEnd(6), real.padEnd(14), real === guess[mode] ? "OK" : "CHECK (you said: " + guess[mode] + ")");
}

আউটপুট:

before  M f
soft   MM f | A  g    CHECK (you said: M  f | A  g)
mixed   M f | ?? g    OK
hard   (clean)        OK

--soft-এ ভুলটি সবচেয়ে প্রচলিত: অনেকে ভাবেন f শুধু staged (M ) হবে। কিন্তু working tree-তে v3-ও আছে, তাই দুই কলামেই M — MM: index-এ C2-এর v2 (HEAD C1-এর v1 থেকে আলাদা), folder-এ v3 (index-এর v2 থেকে আলাদা)। g C1-এ নেই কিন্তু index-এ আছে, তাই A. --mixed-এ index C1-এর মতো, তাই g হয় untracked (??). --hard-এ সব C1 — v3-এর লেখা আর g দুটোই মুছে গেছে। Model-টি আসল Git ২.৫৩-এর সাথে মিলিয়ে দেখা হয়েছে; একই repository-তে তিনবার চালিয়ে git status --short হুবহু MM f.txt + A g.txt, তারপর M f.txt + ?? g.txt, আর শেষে ফাঁকা।

প্র ০৭git fetch আর git pull-এর পার্থক্য কী? Pull কখন “থেমে” যায়? (সহজ)

উত্তর: Fetch remote থেকে নতুন commit নামায় আর শুধু remote-tracking branch (origin/main) আপডেট করে — আপনার branch বা file ছোঁয় না, তাই সবসময় নিরাপদ। Pull = fetch + আপনার branch-এ সেটি মেলানো (merge, বা config থাকলে rebase). দুই দিকে আলাদা commit থাকলে (diverged) আর কোনো পছন্দ ঠিক করা না থাকলে নতুন Git সরাসরি থেমে যায়:

$ git pull
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.

অর্থাৎ Git চায় আপনি নিজে ঠিক করুন: merge, rebase, না শুধু fast-forward. অনেক দল pull.ff only রাখে, যাতে অজান্তে merge commit না হয়।

ফলো-আপ: “নিরাপদ অভ্যাস?” — আগে git fetch, তারপর git log --oneline HEAD..origin/main দেখে সিদ্ধান্ত।

প্র ০৮origin/main আর main কি একই? git status-এর “ahead 2, behind 1” কীভাবে হিসাব হয়? (সহজ)

উত্তর: না। main আপনার local branch; origin/main হলো “শেষবার fetch বা push-এর সময় server-এর main কোথায় ছিল” তার স্থানীয় স্মৃতি — আপনি সরাসরি এতে commit করেন না। এটি নিজে থেকে আপডেট হয় না; fetch না করলে পুরোনো খবর দেখায়। Ahead/behind হলো দুটি range-এর মাপ: ahead = origin/main..main (আপনার কাছে আছে, server-এ নেই), behind = main..origin/main. একসাথে: git rev-list --left-right --count origin/main...main.

ফলো-আপ: “Upstream কী?” — local branch যে remote branch-কে অনুসরণ করে (git push -u বা git branch -u দিয়ে ঠিক হয়); @{u} লিখে তাকে বোঝানো যায়।

Range আর ahead/behind-এর হিসাব নিচের DAG-এ নিজে করে দেখুন। ইতিহাস: A → B → C; C থেকে main-এ D, আর feature-এ E → F; তারপর main-এ G = D আর F-এর merge; feature-এ আরও H (F-এর পরে)। অর্থাৎ main = G, feature = H.

// Commit DAG: each commit lists its parents (G is a merge of D and F).
const parents = { A: [], B: ["A"], C: ["B"], D: ["C"], E: ["C"], F: ["E"], G: ["D", "F"], H: ["F"] };
const tips = { main: "G", feature: "H" }, order = Object.keys(parents);   // oldest first
function reach(x, seen = new Set()) {            // x and all its ancestors
  if (!seen.has(x)) { seen.add(x); parents[x].forEach(p => reach(p, seen)); }
  return seen;
}
const R = (r) => reach(tips[r] || r);   // branch name or commit
const range = (a, b) => order.filter(c => R(b).has(c) && !R(a).has(c));   // a..b
function mergeBase(a, b) {                       // best common ancestors
  const common = order.filter(c => R(a).has(c) && R(b).has(c));
  return common.filter(c => !common.some(d => d !== c && reach(d).has(c)));
}
// Your prediction (a common first guess is filled in) - edit, then Run
const guess = {
  "main..feature": ["E", "F", "H"],
  "feature..main": ["D", "G"],
  "merge-base": ["C"],
  "left/right main...feature": [2, 3],
};
const real = {
  "main..feature": range("main", "feature"),
  "feature..main": range("feature", "main"),
  "merge-base": mergeBase("main", "feature"),
  "left/right main...feature": [range("feature", "main").length, range("main", "feature").length],
};
for (const k in real) {
  const ok = JSON.stringify(guess[k]) === JSON.stringify(real[k]);
  console.log((ok ? "OK    " : "CHECK ") + k.padEnd(27) + JSON.stringify(real[k]));
}

আউটপুট:

CHECK main..feature              ["H"]
OK    feature..main              ["D","G"]
CHECK merge-base                 ["F"]
CHECK left/right main...feature  [2,1]

প্রচলিত ভুল অনুমান ছিল “feature-এর নিজের commit E, F, H”. কিন্তু G merge commit হওয়ায় E আর F এখন main থেকেও পৌঁছানো যায়, তাই main..feature-এ শুধু H. একই কারণে merge base আর C নয়, F — দুই branch-এর সবচেয়ে কাছের সাধারণ পূর্বপুরুষ। ফলে git rev-list --left-right --count main...feature দেখায় 2 1 (বাঁয়ে শুধু main-এর D, G; ডানে শুধু feature-এর H). ঠিক এই DAG Git ২.৫৩-এ বানিয়ে মিলিয়ে দেখা হয়েছে: git log main..feature দেয় H, feature..main দেয় D, G, আর git merge-base --all দেয় F.

প্র ০৯Conventional Commits আর SemVer কী, আর দুটি কীভাবে যুক্ত? (সহজ)

উত্তর: Conventional Commits ১.০.০ হলো commit বার্তার একটি প্রচলিত ধাঁচ: type(scope): বর্ণনা, যেমন feat(cart): add coupon, fix(pay): read key from environment; ভাঙনধরা বদলে feat!: বা footer-এ BREAKING CHANGE:. SemVer ২.০.০-এ version MAJOR.MINOR.PATCH: ভাঙনধরা API বদলে MAJOR, পেছনের দিকে সামঞ্জস্যপূর্ণ নতুন সুবিধায় MINOR, শুধু bug fix-এ PATCH. সংযোগ: tool বার্তা পড়ে পরের version আর changelog স্বয়ংক্রিয়ভাবে বানাতে পারে — শেষ release থেকে শুধু fix থাকলে PATCH, অন্তত একটি feat থাকলে MINOR, ভাঙন থাকলে MAJOR (পর্ব ১৮)।

ফলো-আপ: (ক) “1.4.0 থেকে শুধু একটি fix গেলে?” — 1.4.1 (পরিস্থিতি ১৫-এ ঠিক তাই)। (খ) “0.x?” — SemVer-এ শুরুর উন্নয়ন পর্যায়, যেকোনো সময় যেকোনো কিছু বদলাতে পারে। (গ) “সব project কি SemVer মানে?” — না; অনেকে calendar versioning (যেমন 2026.01) ব্যবহার করে — দুই ধারার উদ্দেশ্য আলাদা।

৩ · মাঝারি স্তর — rebase, force push, reflog, stash, cherry-pick, bisect আর দলের নিয়ম

রিভিশন: পর্ব ১৫ · rebase, পর্ব ১৬ · pull request, পর্ব ১৭ · branching strategy, পর্ব ৫৩ · reflog, পর্ব ৫৫ · bisect, পর্ব ৫৬ · stash, cherry-pick, worktree, hook

এই স্তরে প্রশ্নগুলো প্রায়ই “কখন করবেন, কখন করবেন না” ধাঁচের। শুধু command জানা যথেষ্ট নয়; অন্য মানুষের কাজের ওপর তার প্রভাব বলতে হয়।

প্র ১০Merge আর rebase-এর পার্থক্য কী? কখন rebase করবেন না? (মাঝারি)

উত্তর: দুটিই দুই branch-এর কাজ মেলায়, কিন্তু ইতিহাস আলাদাভাবে রাখে। Merge দুই ইতিহাস যেমন আছে রেখে একটি merge commit যোগ করে — কী আসলে ঘটেছিল, তার সৎ ছবি। Rebase আপনার branch-এর commit-গুলো (upstream..HEAD) একে একে নতুন ভিত্তির ওপর আবার চালায় (replay); বদল একই, কিন্তু parent আলাদা বলে প্রতিটি commit-এর ID নতুন — ফলে ইতিহাস এক সরলরেখা।

সোনালি নিয়ম (golden rule): যে commit অন্যরা ইতিমধ্যে নিয়েছেন বা যার ওপর কাজ শুরু করেছেন, তা rebase করবেন না — তাঁদের কাছে পুরোনো ID থেকে যায়, আর পরের merge-এ একই বদল দুবার আসে। নিজের, এখনো একা ব্যবহার করা feature branch-এ rebase নিরাপদ; push করা থাকলে তারপর --force-with-lease লাগে (পরিস্থিতি ১৩)।

ফলো-আপ: (ক) “Rebase-এর conflict-এ ‘ours’ কে?” — উল্টো! Rebase-এর সময় HEAD হলো নতুন ভিত্তি (যেমন main), আর replay হওয়া commit-টি “theirs” (পরিস্থিতি ৩-এর marker দেখুন)। (খ) “Squash merge?” — platform-এর বিকল্প, পুরো branch একটি commit হয়ে main-এ যায়; ইতিহাস পরিষ্কার, কিন্তু branch-এর ভেতরের commit হারায়।

নিচের ইতিহাসে A → B → C → D একটি লাইন, আর B থেকে আলাদা হয়েছে E → F. প্রতিটি ক্ষেত্রে আপনার অনুমান (merge হলে fast-forward / three-way / already up to date; rebase হলে কোন commit replay হবে) cases-এর শেষ ঘরে লিখুন। কয়েকটিতে ইচ্ছা করে ভুল অনুমান বসানো আছে।

// What does "git merge X" or "git rebase X" do on the current branch?
const parents = { A: [], B: ["A"], C: ["B"], D: ["C"], E: ["B"], F: ["E"] };
const order = Object.keys(parents);
function reach(x, s = new Set()) {
  if (!s.has(x)) { s.add(x); parents[x].forEach(p => reach(p, s)); }
  return s;
}
function merge(cur, other) {
  if (reach(cur).has(other)) return "already up to date";
  if (reach(other).has(cur)) return "fast-forward to " + other;
  return "three-way merge, new commit with parents " + cur + "," + other;
}
function rebase(cur, onto) {               // replay onto..cur, oldest first
  const todo = order.filter(c => reach(cur).has(c) && !reach(onto).has(c));
  if (!todo.length) return "nothing to replay";
  return "replay " + todo.join(",") + " as " + todo.map(c => c + "'").join(",") + " on " + onto;
}
// Each case: [command, current tip, other tip, your prediction]
const cases = [
  ["merge", "C", "D", "three-way merge, new commit with parents C,D"],
  ["merge", "D", "F", "fast-forward to F"],
  ["merge", "D", "B", "already up to date"],
  ["rebase", "F", "D", "replay E,F as E',F' on D"],
  ["rebase", "D", "F", "replay D as D' on F"],
];
for (const [cmd, cur, other, guess] of cases) {
  const real = cmd === "merge" ? merge(cur, other) : rebase(cur, other);
  console.log((real === guess ? "OK    " : "CHECK ") + `HEAD=${cur}, git ${cmd} ${other}: ${real}`);
}

আউটপুট:

CHECK HEAD=C, git merge D: fast-forward to D
CHECK HEAD=D, git merge F: three-way merge, new commit with parents D,F
OK    HEAD=D, git merge B: already up to date
OK    HEAD=F, git rebase D: replay E,F as E',F' on D
CHECK HEAD=D, git rebase F: replay C,D as C',D' on F

প্রথম সারি: C নিজেই D-এর পূর্বপুরুষ, তাই C-তে দাঁড়িয়ে git merge D মানে শুধু pointer সামনে সরানো — fast-forward, কোনো নতুন commit নয়। দ্বিতীয়: D আর F দুজনেরই নিজের commit আছে (C, D বনাম E, F), merge base B — three-way merge. চতুর্থ আর পঞ্চম সারি দেখায় rebase-এর দিক কতটা গুরুত্বপূর্ণ: F-কে D-এর ওপর আনলে E, F নতুন হয়; D-কে F-এর ওপর আনলে C, D নতুন হয়। Git ২.৫৩-এ একই DAG-এ মিলিয়ে দেখা: প্রথমটিতে “Fast-forward”, দ্বিতীয়টিতে “Merge made by the 'ort' strategy.”, তৃতীয়টিতে “Already up to date.”, আর rebase দুটির পরে git log D..HEAD দেয় F, E এবং git log F..HEAD দেয় D, C.

প্র ১১git push --force, --force-with-lease আর --force-if-includes-এর পার্থক্য কী? (মাঝারি)

উত্তর: সাধারণ push শুধু fast-forward গ্রহণ করে — server-এর branch আপনার নতুন commit-এর পূর্বপুরুষ হতে হবে। --force এই শর্ত বাদ দেয়: server-এ যা-ই থাকুক, মুছে আপনার ইতিহাস বসায়। --force-with-lease একটি শর্ত যোগ করে: “server-এর branch এখনো সেখানেই থাকলে তবেই, যেখানে আমার origin/main বলছে”। মাঝে কেউ push করে থাকলে প্রত্যাখ্যান — “stale info”. পরিস্থিতি ১২-এর Karim-এর আসল আউটপুট:

$ git push --force-with-lease origin main
To ../s10srv.git
 ! [rejected]        main -> main (stale info)
error: failed to push some refs to '../s10srv.git'

ফাঁক: Karim যদি আগে git fetch করতেন (কিন্তু Rahim-এর commit দেখে মেলাতেন না), তাহলে তাঁর origin/main আপডেট হয়ে যেত, আর lease পাস করত — Rahim-এর কাজ মুছত। --force-if-includes (Git ২.৩০ থেকে) এটাও ধরে: remote-tracking ref-এর নতুন অবস্থা আপনার local branch-এর ইতিহাসে আছে কি না দেখে। একই অবস্থায় Git ২.৫৩:

$ git push --force-with-lease --force-if-includes origin main
To ../j4srv.git
 ! [rejected]        main -> main (remote ref updated since checkout)
error: failed to push some refs to '../j4srv.git'
hint: Updates were rejected because the tip of the remote-tracking branch has
hint: been updated since the last checkout. If you want to integrate the
hint: remote changes, use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

ফলো-আপ: “তাহলে শুধু flag-ই যথেষ্ট?” — না; আসল সুরক্ষা server-এ: main-এ force push নিষেধ (protected branch, platform-ভেদে নাম আলাদা)।

Server কোন push গ্রহণ করবে, তার ছোট model. Rahim server-এ R push করেছেন; Karim-এর clone পুরোনো (তাঁর origin/main এখনো A), আর তিনি A-এর ওপর K বানিয়েছেন। প্রতিটি অবস্থায় “accepted” না “rejected” — আপনার অনুমান লিখুন।

// Will this push be accepted? A tiny model of the server's checks.
// server: where the server's branch points; tracking: your origin/main;
// local: your main; ancestors: what each commit contains (itself + history).
const ancestors = { A: ["A"], R: ["A", "R"], K: ["A", "K"], K2: ["A", "R", "K2"] };
const contains = (x, y) => ancestors[x].includes(y);       // is y in x's history?
function push(mode, { server, tracking, local }) {
  if (contains(local, server)) return "accepted (fast-forward)";
  if (mode === "plain") return "rejected (non-fast-forward / fetch first)";
  if (mode === "force") return "accepted (forced) - server lost " + server;
  if (tracking !== server) return "rejected (stale info)";  // --force-with-lease
  return "accepted (forced, lease held) - server lost " + server;
}
// Karim's clone is stale: his origin/main is still A, but Rahim pushed R.
const stale = { server: "R", tracking: "A", local: "K" };
// After "git fetch", origin/main = R, but Karim never looked at R.
const fetched = { server: "R", tracking: "R", local: "K" };
// After "git fetch" + "git rebase origin/main": local K2 builds on R.
const rebased = { server: "R", tracking: "R", local: "K2" };
const cases = [   // [mode, state, your prediction: "accepted" or "rejected"]
  ["plain", stale, "rejected"], ["force", stale, "accepted"],
  ["lease", stale, "accepted"], ["lease", fetched, "rejected"],
  ["plain", rebased, "accepted"],
];
for (const [mode, st, guess] of cases) {
  const real = push(mode, st);
  const name = st === stale ? "stale" : st === fetched ? "fetched" : "rebased";
  console.log((real.startsWith(guess) ? "OK    " : "CHECK ") + (mode + " / " + name).padEnd(17) + real);
}

আউটপুট:

OK    plain / stale    rejected (non-fast-forward / fetch first)
OK    force / stale    accepted (forced) - server lost R
CHECK lease / stale    rejected (stale info)
CHECK lease / fetched  accepted (forced, lease held) - server lost R
OK    plain / rebased  accepted (fast-forward)

তৃতীয় সারি সবচেয়ে শিক্ষণীয়: stale clone থেকে --force-with-lease প্রত্যাখ্যাত, কারণ Karim-এর ধারণা (A) আর server-এর বাস্তব (R) মেলে না — ওপরের আসল আউটপুটের “stale info” ঠিক এটাই। চতুর্থ সারি ফাঁকটি দেখায়: শুধু fetch করলেই lease “ধরা” হয়ে যায়, আর R মুছে যায়; model-এ --force-if-includes নেই, সেটাই এর সীমা — আসল Git-এ সেই flag এখানে “remote ref updated since checkout” বলে থামত। শেষ সারি সঠিক পথ: fetch, তারপর R-এর ওপর rebase করে K2, তখন সাধারণ push-ই fast-forward.

প্র ১২Reflog কী, আর git log-এর থেকে আলাদা কেন? Reflog কতদিন থাকে? (মাঝারি)

উত্তর: git log commit-এর parent ধরে ইতিহাস দেখায় — “এই commit কোথা থেকে এল”। Reflog প্রতিটি ref-এর (HEAD, প্রতিটি branch) নিজের খাতা — “এই নামটি কবে কোন commit-এ ছিল”: commit, checkout, reset, rebase, merge সব লেখা থাকে। তাই reset বা rebase-এ যে commit log থেকে “হারিয়ে” যায়, সেটি reflog-এ HEAD@{1}-এর মতো নামে খুঁজে পাওয়া যায় (পরিস্থিতি ২)।

ডিফল্টে reflog entry ৯০ দিন রাখা হয়, আর যে entry-র commit কোনো ref থেকে পৌঁছানো যায় না তার জন্য ৩০ দিন (gc.reflogExpire, gc.reflogExpireUnreachable); তারপর garbage collection object মুছতে পারে (পর্ব ৫৩)।

ফলো-আপ: (ক) “Reflog কি push হয়?” — না, সম্পূর্ণ local; নতুন clone-এ থাকে না। তাই সহকর্মীর হারানো কাজ তাঁর নিজের reflog-এ খুঁজতে হয়। (খ) “Reflog-ও না থাকলে?” — git fsck --unreachable বা --lost-found (পরিস্থিতি ৭)।

প্র ১৩Stash কী, apply আর pop-এর পার্থক্য কী, আর stash কখন ভুল হাতিয়ার? (মাঝারি)

উত্তর: Stash অর্ধেক কাজ (working tree আর index-এর বদল) তুলে রেখে folder পরিষ্কার করে — branch বদলে জরুরি কাজ সেরে ফিরে আসার জন্য। এটি একটি stack: নতুনটি stash@{0}. ভেতরে প্রতিটি entry সাধারণ commit (working tree-র W, index-এর I, আর -u দিলে untracked-এর U), যা refs/stash-এ ঝোলে। apply বদল বসায় কিন্তু entry রাখে; pop বসায় আর সফল হলে মোছে — conflict হলে মোছে না। ডিফল্টে untracked file নেয় না, তাই git stash push -u -m "…".

কখন ভুল: কয়েক দিনের কাজ রাখতে — stash push হয় না, নম্বর বদলায়, নাম থাকে না। তখন WIP branch-এ commit করে push, পরে গুছিয়ে নিন; আর একসাথে দুটি কাজ চললে worktree (প্র ১৬)।

ফলো-আপ: “ভুল করে drop করলে?” — পরিস্থিতি ৭।

প্র ১৪Cherry-pick কী করে? কখন উপযোগী, আর কখন এড়াবেন? (মাঝারি)

উত্তর: একটি commit তার parent-এর তুলনায় যা বদলেছিল, সেই বদল বর্তমান branch-এর মাথায় নতুন commit হিসেবে বসায়। বার্তা আর লেখক একই থাকে, কিন্তু parent আলাদা, তাই ID আলাদা। সবচেয়ে প্রচলিত ব্যবহার backport: main-এর একটি fix পুরোনো release branch-এ নেওয়া, -x দিয়ে উৎস লিখে (পরিস্থিতি ১৫)।

এড়াবেন: পুরো branch-এর কাজ আনতে অনেকগুলো cherry-pick — merge-ই ঠিক পথ; একই বদল দুই ID-তে থাকলে পরে merge-এ বিভ্রান্তি আর conflict. আর কোনো fix যদি আগের কোনো refactor-এর ওপর নির্ভর করে, শুধু fix-টি pick করলে ভুল আচরণ হতে পারে — release branch-এ test চালাতেই হবে।

ফলো-আপ: (ক) “git cherry-pick A..B-তে A আসে?” — না, A বাদ; A-সহ চাইলে A^..B (Git ২.৫৩-এ যাচাই করা)। (খ) “Merge commit pick?” — -m 1 দিয়ে কোন parent-এর সাপেক্ষে diff, তা বলতে হয়।

প্র ১৫git bisect কীভাবে কাজ করে? ১০০০ commit-এর মধ্যে bug আনা commit খুঁজতে মোটামুটি কতবার test লাগবে? (মাঝারি)

উত্তর: Bisect হলো ইতিহাসের ওপর binary search. আপনি একটি “bad” (bug আছে) আর একটি “good” (bug নেই) commit বলেন; Git মাঝখানের একটি commit checkout করে, আপনি test করে good বা bad বলেন, আর প্রতি ধাপে সন্দেহের পরিসর মোটামুটি অর্ধেক হয়। N টি সন্দেহজনক commit-এ প্রায় ⌈log₂ N⌉ ধাপ — ১০০০-এ প্রায় ১০টি (২¹⁰ = ১০২৪)। Merge-ভরা ইতিহাসে সংখ্যা একটু বেশি হতে পারে।

git bisect run <script> পুরো কাজটি স্বয়ংক্রিয় করে: script-এর exit code ০ মানে good, ১২৫ মানে “এটি পরীক্ষা করা যায় না, বাদ দিন”, ১ থেকে ১২৭-এর বাকিগুলো bad (পরিস্থিতি ১০)।

ফলো-আপ: (ক) “Test flaky হলে?” — bisect ভুল commit দেখাবে; আগে test নির্ভরযোগ্য করুন বা কয়েকবার চালিয়ে সিদ্ধান্ত। (খ) “কোন লাইন কে বদলেছেন?” — git blame -L; “কোন commit কোনো লেখা যোগ বা মুছেছে?” — git log -S.

প্র ১৬git worktree কী সমস্যার সমাধান করে? Stash বা দ্বিতীয় clone-এর সাথে তুলনা করুন। (মাঝারি)

উত্তর: এক repository, একাধিক working folder: git worktree add -b hotfix/vat ../hotfix main একটি নতুন folder খোলে নতুন branch-এ, কিন্তু object আর ref ভাগ করে (Git ২.৫, ২০১৫ থেকে)। মাঝপথের feature না ছুঁয়ে hotfix বা review করা যায়। Git ২.৫৩-এ আসল আউটপুট:

$ git worktree add -b hotfix/vat ../hotfix main
Preparing worktree (new branch 'hotfix/vat')
HEAD is now at 9082124 Merge branch 'feature'

তুলনা: stash হালকা কিন্তু এক সময়ে একটিই কাজ খোলা আর ফেরাতে conflict-এর ঝুঁকি; দ্বিতীয় clone পুরো আলাদা, কিন্তু ইতিহাস দুবার নামে আর commit আনতে push-fetch লাগে; worktree মাঝামাঝি — সেকেন্ডে তৈরি, এক ইতিহাস।

ফলো-আপ: “একই branch দুই worktree-তে?” — ডিফল্টে Git দেয় না (বলে 'main' is already used by worktree); একটি branch একসাথে একটি worktree-তেই checkout থাকে। কাজ শেষে git worktree remove ../hotfix.

প্র ১৭Git hook কী? Client-side hook দিয়ে কি দলের নিয়ম নিশ্চিত করা যায়? (মাঝারি)

উত্তর: Hook হলো Git-এর নির্দিষ্ট মুহূর্তে চলা script: client-side-এ pre-commit (commit-এর আগে, যেমন format বা secret scan), commit-msg (বার্তা যাচাই), pre-push; server-side-এ pre-receive, update, post-receive. Exit code শূন্য না হলে কাজ থামে (post-* hook ছাড়া)।

Client-side hook নিশ্চয়তা দেয় না: .git/hooks clone-এ আসে না (নিরাপত্তার কারণে — নইলে clone করামাত্র অচেনা কোড চলত), প্রত্যেককে core.hooksPath বা একটি framework দিয়ে চালু করতে হয়, আর --no-verify এগুলো এড়ায়। তাই একই নিয়ম আবার server-side hook বা protected branch-এর বাধ্যতামূলক CI-তে (পর্ব ৫৮)। Local hook দ্রুত feedback, CI নিশ্চয়তা।

ফলো-আপ: “pre-commit hook ধীর হলে?” — মানুষ --no-verify-এ অভ্যস্ত হন; local অংশ কয়েক সেকেন্ডে রাখুন, ভারী test CI-তে।

প্র ১৮Git Flow, GitHub Flow আর trunk-based development — কোন দলের জন্য কোনটি? (মাঝারি)

উত্তর: Git Flow (Vincent Driessen-এর ২০১০ সালের লেখা থেকে জনপ্রিয়): দীর্ঘস্থায়ী main আর develop, সাথে feature, release আর hotfix branch. একসাথে কয়েকটি version রক্ষণাবেক্ষণ করতে হয় এমন software-এ (যেমন install করা app, library) কাজে লাগে, কিন্তু merge বেশি আর feedback ধীর। GitHub Flow: একটি main, ছোট feature branch, pull request, review আর merge — প্রায়ই deploy হওয়া web app-এর জন্য সরল। Trunk-based development: সবাই খুব ছোট বদল ঘন ঘন (দিনে অন্তত একবার) main-এ মেলান, অসম্পূর্ণ কাজ feature flag-এর পেছনে; ভালো CI আর test ছাড়া চলে না।

Forsgren, Humble আর Kim-এর Accelerate (২০১৮) বইয়ের গবেষণায় trunk-based ধাঁচের অনুশীলনের সাথে ভালো delivery performance-এর সম্পর্ক পাওয়া গেছে বলে জানানো হয় — তবে সম্পর্ক মানেই কারণ নয় (পর্ব ১৭, পর্ব ৫৯)।

ফলো-আপ: “দীর্ঘস্থায়ী feature branch-এর সমস্যা?” — integration দেরিতে, বড় conflict, review কঠিন; উপায় ছোট pull request আর branch by abstraction.

৪ · অভিজ্ঞ স্তর — object model, hash, packfile, rerere, signing, monorepo আর secret

রিভিশন: পর্ব ১৯ · monorepo আর submodule, পর্ব ৫২ · Git-এর ভেতরে, পর্ব ৫৪ · merge-এর ভেতরে, পর্ব ৫৭ · নিরাপত্তা

অভিজ্ঞ স্তরের প্রশ্নে ইন্টারভিউয়ার দেখতে চান আপনি command-এর নিচের ব্যবস্থা বোঝেন কি না, আর বড় দল বা বড় repository-র সমস্যায় ভারসাম্য টানতে পারেন কি না। “নির্ভর করে” বললে সাথে বলুন কীসের ওপর।

প্র ১৯Git-এর object model ব্যাখ্যা করুন — blob, tree, commit. File-এর নাম কোথায় থাকে? (কঠিন)

উত্তর: Git একটি content-addressed key-value store: প্রতিটি object-এর নাম তার বিষয়বস্তুর hash (type size\0content-এর SHA-1). Blob-এ শুধু file-এর বিষয়বস্তু — নাম নেই। Tree-তে একটি folder-এর তালিকা: mode, type, ID আর নাম। Commit-এ একটি root tree, parent-গুলো, লেখক, committer আর বার্তা। তাই file-এর নাম tree-তে থাকে; rename মানে শুধু tree বদল, blob একই। একটি ছোট repository-তে Git ২.৫৩:

$ git cat-file -p HEAD
tree 1f01aec9f1ec93ab2f1615d5df75bd003e587cdf
author Rahim <rahim@example.com> 1768449660 +0600
committer Rahim <rahim@example.com> 1768449660 +0600

feat: cart
$ git cat-file -p HEAD^{tree}
100644 blob 2ed6330246dfcb27bdee07aec6d59673c9c39bae	cart.js
040000 tree 90ac27eb6de108975ed6fc01587a572fb9fbf49a	docs
$ git cat-file -p HEAD:docs
100644 blob 2ed6330246dfcb27bdee07aec6d59673c9c39bae	copy.js
$ printf "function total() {}\n" | git hash-object --stdin
2ed6330246dfcb27bdee07aec6d59673c9c39bae
$ git count-objects -v | head -2
count: 4
size: 0

cart.js আর docs/copy.js-এর বিষয়বস্তু একই, তাই দুই tree-তেই একই blob 2ed6330 — একবারই সংরক্ষিত, মোট object ৪টি (commit, দুটি tree, একটি blob). hash-object দেখায়, Git-এ না রেখেও বিষয়বস্তু থেকে ID হিসাব করা যায়।

ফলো-আপ: (ক) “Commit-এর তারিখ বদলালে ID?” — বদলায়, কারণ তারিখ commit object-এর অংশ; এজন্যই এই পাতায় তারিখ স্থির রাখা হয়েছে। (খ) “Annotated tag?” — চতুর্থ object type, নিজের বার্তা ও লেখকসহ।

প্র ২০Git কেন SHA-1 ব্যবহার করে, SHA-1 ভাঙার খবরের পরে কী বদলেছে, আর SHA-256-এর অবস্থা কী? (কঠিন)

উত্তর: Git-এ (২০০৫) hash-এর মূল কাজ ছিল অখণ্ডতা: প্রতিটি commit তার tree আর parent-এর ID ধরে রাখে, তাই ইতিহাসের যেকোনো byte বদলালে পরের সব ID বদলে যায় — একটি Merkle-ধাঁচের শৃঙ্খল। ২০১৭ সালে Google আর CWI Amsterdam-এর গবেষকেরা প্রথম ব্যবহারিক SHA-1 collision (“SHAttered”) প্রকাশ করেন। Git-এর সাড়া: Git ২.১৩ (২০১৭) থেকে ডিফল্টে “hardened SHA-1” (SHA-1DC), যা এ ধরনের collision আক্রমণের চিহ্ন hash করার সময়ই ধরে; আর দীর্ঘমেয়াদে SHA-256 object format — Git ২.২৯ (২০২০) থেকে git init --object-format=sha256, শুরুতে পরীক্ষামূলক।

সতর্কতা: SHA-1 আর SHA-256 repository-র মধ্যে আদান-প্রদান এখনো সীমিত, আর hosting platform ও tool-এর সমর্থন অসমান (লেখার সময়ে); ভবিষ্যৎ বড় version-এ SHA-256-কে ডিফল্ট করার পরিকল্পনার কথা Git-এর নিজের নথিতে আছে — release note দেখে নিন (পর্ব ৫২, ৫৭)।

ফলো-আপ: “Hash collision হলে signed commit-এর কী?” — signature commit object-এর ওপর, যা hash দিয়ে tree আর parent-কে বাঁধে; তাই hash দুর্বল হলে signature-এর নিশ্চয়তাও দুর্বল হয়।

প্র ২১প্রতিটি commit পুরো snapshot হলে repository এত ছোট থাকে কীভাবে? Packfile ব্যাখ্যা করুন। (কঠিন)

উত্তর: তিন স্তরে বাঁচানো হয়। এক, deduplication: অপরিবর্তিত file-এর blob আর অপরিবর্তিত folder-এর tree নতুন commit-এ আবার ব্যবহৃত হয় (প্র ১৯)। দুই, প্রতিটি “loose” object zlib দিয়ে সংকুচিত। তিন, git gc (আর push/fetch) object-গুলোকে packfile-এ ভরে, যেখানে অনেক object অন্য একটি মিলের object-এর সাপেক্ষে delta হিসেবে থাকে — কোনটির সাপেক্ষে, তা Git type, নাম আর আকার দেখে heuristic-এ বাছে, অবশ্যই “আগের version” নয়। .idx file দ্রুত খোঁজার সূচি। যুক্তিতে snapshot, ডিস্কে delta.

দেখতে: git count-objects -vH (পরিস্থিতি ১১-এ size-pack 3.00 MiB থেকে 1.97 KiB), git verify-pack -v. Random binary file (zip, ভিডিও) প্রায় সংকুচিত হয় না, delta-ও কাজে আসে না — তাই সেগুলোর জন্য Git LFS.

ফলো-আপ: “বড় repository clone দ্রুত করতে?” — shallow clone (--depth 1), partial clone (--filter=blob:none), আর sparse checkout.

প্র ২২rerere কী, কখন কাজে লাগে, আর ঝুঁকি কী? (কঠিন)

উত্তর: “Reuse recorded resolution”. git config rerere.enabled true চালু থাকলে Git প্রতিটি conflict-এর আকৃতি (দুই দিকের লেখা) আর আপনার সমাধান .git/rr-cache-এ লিখে রাখে; পরে হুবহু একই conflict আবার এলে সমাধানটি নিজেই বসায়। কাজে লাগে যখন একই conflict বারবার আসে: দীর্ঘস্থায়ী branch বারবার rebase, একটি merge বাতিল করে আবার চেষ্টা, বা integration branch-এ নিয়মিত test merge.

ঝুঁকি: একবার ভুল সমাধান লিখে রাখলে সেটিও বারবার বসবে, নিঃশব্দে। তাই rerere বসানোর পরেও file দেখে নিজে git add করুন (ডিফল্টে Git আপনার জন্য stage করে না), test চালান, আর ভুল হলে git rerere forget <path> (পর্ব ৫৪)।

ফলো-আপ: “Conflict কম করতে আর কী?” — merge.conflictStyle diff3 বা zdiff3 (base-ও দেখায়), ছোট ও ঘন ঘন merge, আর formatter-এর ঐকমত্য।

প্র ২৩Commit-এর লেখকের নাম কি বিশ্বাসযোগ্য? Signed commit কী প্রমাণ করে, আর কী করে না? (কঠিন)

উত্তর: না — user.name আর user.email যে-কেউ যা খুশি লিখতে পারেন; এই পাতার Rahim আর Karim-ও শুধু config-এর দুটি লাইন। Signed commit-এ commit object-এর ওপর একটি cryptographic স্বাক্ষর যুক্ত হয় — GPG, SSH key (Git ২.৩৪, ২০২১ থেকে, gpg.format ssh) বা X.509. git commit -S বা commit.gpgsign true; যাচাই git log --show-signature, git verify-commit; SSH-এর জন্য gpg.ssh.allowedSignersFile. Platform-গুলো যাচাই হলে “Verified”-ধাঁচের চিহ্ন দেখায় (platform-ভেদে আলাদা)।

প্রমাণ করে: এই key-এর মালিক এই নির্দিষ্ট commit (tree, parent, বার্তা) সই করেছেন, পরে কিছু বদলায়নি। করে না: কোডটি নিরাপদ, review হয়েছে, বা key চুরি হয়নি। Rebase বা amend করলে নতুন commit — আবার সই লাগে।

ফলো-আপ: “Tag সই?” — git tag -s; release-এর জন্য বেশি গুরুত্বপূর্ণ, কারণ মানুষ tag দেখে download করেন।

প্র ২৪Submodule, subtree আর monorepo — কোনটি কখন? Submodule-এর প্রচলিত ফাঁদ কী? (কঠিন)

উত্তর: Submodule মূল repository-তে অন্য একটি repository-র একটি নির্দিষ্ট commit-এর রেফারেন্স রাখে (tree-তে mode 160000-এর একটি “gitlink”, সাথে .gitmodules-এ URL). সুবিধা: আলাদা ইতিহাস, আলাদা অধিকার, নির্দিষ্ট version-এ বাঁধা। ফাঁদ: clone-এ --recurse-submodules ভুলে যাওয়া (খালি folder), submodule-এর ভেতরে detached HEAD-এ কাজ করে হারানো, ভেতরে commit করে বাইরের pointer আপডেট না করা, আর CI-তে আলাদা credential. Subtree অন্য project-এর file (চাইলে ইতিহাসসহ) একটি sub-folder-এ সরাসরি কপি করে — ব্যবহারকারীর জন্য সহজ, ফেরত পাঠানো কঠিন।

Monorepo-তে অনেক project এক repository-তে: এক commit-এ কয়েকটি project জুড়ে বদল (atomic change), এক CI, সহজ refactor; কিন্তু আকার বাড়লে tooling লাগে — sparse checkout, partial clone, বদলানো অংশ বুঝে build ও test, আর CODEOWNERS-এর মতো মালিকানা নিয়ম। সিদ্ধান্তের মূল প্রশ্ন: project-গুলো কি একসাথে বদলায় আর release হয়? (পর্ব ১৯)

ফলো-আপ: “বড় binary?” — Git LFS: repository-তে ছোট pointer file, আসল file আলাদা server-এ।

প্র ২৫একটি API key ভুল করে push হয়ে গেছে। প্রথম পাঁচ মিনিটে কী করবেন, আর কোন ক্রমে? (কঠিন)

উত্তর: ধরে নিন key ফাঁস হয়ে গেছে — কেউ clone করে থাকতে পারেন, স্বয়ংক্রিয় bot public repository স্ক্যান করে। তাই ক্রম: (১) Revoke — যে service-এর key, সেখানে key বাতিল; (২) Rotate — নতুন key বানিয়ে secret manager বা environment variable-এ, কোডে নয়; (৩) ব্যবহারের log দেখে অপব্যবহার হয়েছে কি না যাচাই; (৪) তারপর ইতিহাস পরিষ্কার — নতুন clone-এ git filter-repo (বা BFG), force push, দলকে নতুন করে clone করতে বলা; (৫) প্রতিরোধ — .gitignore-এ .env, commit-এর আগে secret scanner, platform-এর push protection, CI-তে আবার scan.

কেন আগে বাতিল: ইতিহাস পরিষ্কার করতে সময় লাগে, আর সব কপি মোছা যায় না — অন্যদের clone, fork, platform-এর cache বা pull request-এর ref. বাতিল করা key history-তে থাকলেও মূল্যহীন (পরিস্থিতি ৯, পর্ব ৫৭)।

ফলো-আপ: “শুধু একটি নতুন commit-এ key মুছে দিলে?” — key পুরোনো commit-এ থেকেই যায়; git log -S দিয়ে যে-কেউ খুঁজে পান।

৫ · পরিস্থিতিভিত্তিক সমস্যা (১) — নিজের repository-তে ভুল, push-এর আগে

রিভিশন: পর্ব ১১ · ভুল শোধরানো, পর্ব ১২ · branch, পর্ব ১৫ · interactive rebase, পর্ব ৫৩ · উদ্ধার, পর্ব ৫৬ · stash

এই সাতটি পরিস্থিতিতে কাজ এখনো শুধু আপনার কম্পিউটারে, তাই ইতিহাস বদলানো নিরাপদ — শুধু নিজের কাজ না হারালেই হলো। প্রতিটি উত্তরে চারটি অংশ: ভাবনা (graph-এ কী ঘটেছে), সমাধান (আসল transcript), কেন কাজ করে, আর ফলো-আপ। প্রতিটিতে আগে খাতায় graph এঁকে নিজের উত্তর লিখুন।

প্র ২৬পরিস্থিতি ১ · আপনি ভুল করে main-এ দুটি commit করে ফেলেছেন, যা feature/coupon branch-এ যাওয়ার কথা ছিল। এখনো push করেননি। কীভাবে সরাবেন? (সহজ)

ভাবনা: commit দুটি ঠিকই আছে, শুধু ভুল নামফলকের নিচে। সমাধান মানে লেবেল সরানো: যেখানে commit-গুলো আছে সেখানে নতুন branch-এর নাম বসানো, আর main-কে origin/main-এ ফিরিয়ে নেওয়া। কোনো commit কপি বা নতুন করে তৈরি করতে হয় না।

$ git status -sb
## main...origin/main [ahead 2]
$ git log --oneline --graph --decorate --all
* 3094561 (HEAD -> main) feat(coupon): apply 10% off
* d44ecb6 feat(coupon): add coupon constant
* 8599541 (origin/main) feat: cart total
$ git branch feature/coupon
$ git reset --keep origin/main
$ git switch feature/coupon
Switched to branch 'feature/coupon'
$ git log --oneline --graph --decorate --all
* 3094561 (HEAD -> feature/coupon) feat(coupon): apply 10% off
* d44ecb6 feat(coupon): add coupon constant
* 8599541 (origin/main, main) feat: cart total
$ git status -sb
## feature/coupon

কেন কাজ করে: git branch feature/coupon বর্তমান commit 3094561-এ নতুন নাম বসায় — এখন দুই commit দুই branch থেকেই পৌঁছানো যায়, তাই পরের ধাপে কিছুই হারায় না। git reset --keep origin/main main-কে 8599541-এ ফেরায়; --hard-এর বদলে --keep নিয়েছি, কারণ commit না করা কোনো বদল থাকলে এটি মুছে না দিয়ে থেমে যায়। শেষ log-এ commit-গুলোর ID একই — শুধু লেবেল বদলেছে (নিচের ছবি)।

ফলো-আপ: (ক) “feature/coupon আগে থেকেই থাকলে, আর তাতে অন্য commit থাকলে?” — সেটিতে switch করে git cherry-pick d44ecb6^..3094561, তারপর main-এ ফিরে git reset --keep origin/main. (খ) “Push হয়ে গিয়ে থাকলে?” — shared main-এ reset নয়; branch থেকে কাজ চালিয়ে যান আর main-এ git revert (পরিস্থিতি ৮), বা দলের সাথে কথা বলে সিদ্ধান্ত।

ভুল branch-এ commit — আগে আর পরে

The commits never move; only the labels main and feature/coupon do

ভুল branch থেকে commit সরানো: commit একই, লেবেল সরল আগে 8599541d44ecb63094561 origin/main main, HEAD coupon-এর দুই commit — ভুল জায়গায় পরে · branch → reset --keep → switch 8599541d44ecb63094561 main, origin/main feature/coupon, HEAD
ছবি ৬৯.১ — তীর child থেকে parent-এর দিকে। তিনটি commit-এর ID আগে-পরে হুবহু এক; বদলেছে শুধু কোন নাম কোন commit-কে দেখায়।
কাল্পনিক বাজার অ্যাপ · main-এ ভুল করে coupon-এর দুই commit
Command
main
feature/coupon
HEAD
Coupon-এর দুই commit পৌঁছানো যায়
  1. সমস্যা। ## main...origin/main [ahead 2] — main server-এর চেয়ে দুই commit এগিয়ে, আর সেই দুটি coupon-এর কাজ।
  2. আগে নিরাপত্তার জাল। নতুন নাম feature/coupon বর্তমান commit-এ বসল। Commit এখন দুই নাম থেকেই পৌঁছানো যায় — পরের ধাপে কিছু হারানোর ভয় নেই।
  3. main ফেরত। --keep main-কে 8599541-এ সরাল; working tree-তে commit না করা বদল থাকলে এটি থেমে যেত। Coupon-এর commit এখন শুধু নতুন branch-এ।
  4. কাজ চালিয়ে যাওয়া। HEAD এখন feature/coupon-এ; status-এ আর “ahead” নেই, কারণ এই branch-এর এখনো কোনো upstream নেই।
প্র ২৭পরিস্থিতি ২ · feature/search-এ ভুল করে git reset --hard HEAD~3 চালিয়ে তিনটি commit “হারিয়ে” ফেলেছেন। ফেরত আনুন। (মাঝারি)

ভাবনা: reset commit মোছে না, শুধু branch-এর pointer সরায়। হারানো commit-গুলো object store-এ আছে, আর HEAD-এর reflog-এ লেখা আছে reset-এর ঠিক আগে HEAD কোথায় ছিল।

$ git log --oneline
9fc0cfd test(search): add trim test
d0c4649 feat(search): trim the query
6bbbcfb feat(search): add search stub
25ae777 feat: cart skeleton
$ git reset --hard HEAD~3
HEAD is now at 25ae777 feat: cart skeleton
$ git log --oneline
25ae777 feat: cart skeleton
$ git reflog -5
25ae777 HEAD@{0}: reset: moving to HEAD~3
9fc0cfd HEAD@{1}: commit: test(search): add trim test
d0c4649 HEAD@{2}: commit: feat(search): trim the query
6bbbcfb HEAD@{3}: commit: feat(search): add search stub
25ae777 HEAD@{4}: checkout: moving from main to feature/search
$ git branch rescue/search HEAD@{1}
$ git log --oneline rescue/search
9fc0cfd test(search): add trim test
d0c4649 feat(search): trim the query
6bbbcfb feat(search): add search stub
25ae777 feat: cart skeleton
$ git reset --hard rescue/search
HEAD is now at 9fc0cfd test(search): add trim test
$ git branch -d rescue/search
Deleted branch rescue/search (was 9fc0cfd).
$ git log --oneline
9fc0cfd test(search): add trim test
d0c4649 feat(search): trim the query
6bbbcfb feat(search): add search stub
25ae777 feat: cart skeleton
$ git reflog -3
9fc0cfd HEAD@{0}: reset: moving to rescue/search
25ae777 HEAD@{1}: reset: moving to HEAD~3
9fc0cfd HEAD@{2}: commit: test(search): add trim test

কেন কাজ করে: reflog-এর সবচেয়ে নতুন entry HEAD@{0} হলো reset নিজে; তার ঠিক আগের HEAD@{1} = 9fc0cfd, শেষ commit. সরাসরি reset না করে আগে একটি rescue/search branch বানালাম, যাতে যাচাইয়ের সময়ও commit-গুলো একটি নামে বাঁধা থাকে; তারপর branch-টিকে সেখানে ফিরিয়ে rescue মুছে দিলাম (-d রাজি হলো, কারণ সবকিছু এখন merged). শেষ reflog দেখায় উদ্ধারটিও একটি নতুন entry — reflog কখনো পেছনে লেখে না, শুধু যোগ করে।

ফলো-আপ: (ক) “Reset-এর আগে commit না করা বদল ছিল?” — সেটি ফেরে না, কারণ কখনো object হয়নি; শুধু git add করা থাকলে git fsck --lost-found-এ blob হিসেবে পাওয়া যেতে পারে। (খ) “ORIG_HEAD?” — reset নিজেই ORIG_HEAD লিখে রাখে, তাই ঠিক পরের মুহূর্তে git reset --hard ORIG_HEAD-ও কাজ করত।

প্র ২৮পরিস্থিতি ৩ · feature/badge-কে main-এর ওপর rebase করতে গিয়ে conflict. সমাধান করে rebase শেষ করুন। (মাঝারি)

ভাবনা: main-এ label()-এর লাইনে দুই দশমিক যোগ হয়েছে, আর feature-এর প্রথম commit একই লাইনে “(VAT incl.)” যোগ করেছে। Rebase commit-গুলো একে একে চালায়, তাই conflict হবে প্রথম commit-এ, আর থামবে মাঝপথে।

$ git log --oneline --graph --decorate --all
* ee44e89 (main) fix(cart): show 2 decimals
| * 51ef1c3 (HEAD -> feature/badge) feat(cart): add badge style
| * 3fc5c8d feat(cart): mention VAT in label
|/
* 304691b feat: cart label
$ git rebase main
Auto-merging cart.js
CONFLICT (content): Merge conflict in cart.js
error: could not apply 3fc5c8d... feat(cart): mention VAT in label
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Could not apply 3fc5c8d... # feat(cart): mention VAT in label
$ git status --short
UU cart.js
$ cat cart.js
const VAT = 0.15;
function label(t) {
<<<<<<< HEAD
  return "Total: " + t.toFixed(2);
=======
  return "Total: " + t + " (VAT incl.)";
>>>>>>> 3fc5c8d (feat(cart): mention VAT in label)
}
$ # keep both changes, remove the markers
$ git add cart.js
$ git rebase --continue
[detached HEAD c8b9567] feat(cart): mention VAT in label
 1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/feature/badge.
$ git log --oneline --graph --decorate --all
* c2edcb2 (HEAD -> feature/badge) feat(cart): add badge style
* c8b9567 feat(cart): mention VAT in label
* ee44e89 (main) fix(cart): show 2 decimals
* 304691b feat: cart label

কেন কাজ করে: marker-এর লেবেল লক্ষ করুন — HEAD-এর দিকে main-এর লেখা (toFixed(2)), আর নিচে 3fc5c8d, অর্থাৎ replay হওয়া আপনার commit. Rebase-এ “ours” মানে নতুন ভিত্তি, merge-এর ঠিক উল্টো। দুটি বদলই রেখে marker মুছে git add, তারপর git rebase --continue (editor খোলে commit বার্তা নিয়ে; এখানে না বদলে সংরক্ষণ করা হয়েছে)। দ্বিতীয় commit conflict ছাড়াই বসেছে। শেষে সরলরেখা, আর দুটি commit-ই নতুন ID পেয়েছে (c8b9567, c2edcb2). (চলার সময় Git একই লাইনে Rebasing (1/2)-ধাঁচের অগ্রগতি দেখায় আর পরে মুছে দেয়; এখানে চূড়ান্ত পর্দা।)

ফলো-আপ: (ক) “মাঝপথে সব বাতিল?” — git rebase --abort, branch আগের অবস্থায়। (খ) “এই commit-টি আর দরকার নেই?” — git rebase --skip. (গ) “একই conflict বারবার?” — rerere (প্র ২২)।

প্র ২৯পরিস্থিতি ৪ · শেষ commit “wip: total and docs”-এ দুটি আলাদা কাজ মিশে গেছে — code আর documentation. এটিকে দুটি commit-এ ভাগ করুন। (মাঝারি)

ভাবনা: commit-টি ভেঙে বদলগুলো আবার working tree-তে ফিরিয়ে আনা, তারপর অংশ অংশ করে stage আর commit. শেষ commit হলে --mixed reset-ই যথেষ্ট।

$ git show --stat --oneline HEAD
cb28318 wip: total and docs
 README.md | 1 +
 cart.js   | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)
$ git reset HEAD~1
Unstaged changes after reset:
M	README.md
M	cart.js
$ git status --short
 M README.md
 M cart.js
$ git add cart.js
$ git commit -m "feat(cart): sum item prices"
[main 5d7fe9d] feat(cart): sum item prices
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git add README.md
$ git commit -m "docs: how to run"
[main 88c45a3] docs: how to run
 1 file changed, 1 insertion(+)
$ git log --oneline
88c45a3 docs: how to run
5d7fe9d feat(cart): sum item prices
9e5888e feat: cart skeleton

কেন কাজ করে: git reset HEAD~1 (ডিফল্ট --mixed) commit-টি বাদ দেয় কিন্তু বদল folder-এ রাখে — Git নিজেই “Unstaged changes after reset” তালিকা দেখায়। তারপর file ধরে আলাদা commit. একই file-এর ভেতরে দুই কাজ মিশে থাকলে git add -p দিয়ে hunk বেছে নিন।

ফলো-আপ: “Commit-টি শেষেরটি না হয়ে তিন commit আগের হলে?” — git rebase -i HEAD~3, সেই commit-এর লাইনে pick-এর বদলে edit; rebase সেখানে থামলে ঠিক ওপরের মতো git reset HEAD~1, ভাগ করে commit, তারপর git rebase --continue.

প্র ৩০পরিস্থিতি ৫ · Pull request খোলার আগে শেষ তিনটি commit (“feat(cart): multiply by qty”, “fix typo”, “default qty to 1”) একটিতে মেলাতে চান, প্রথমটির বার্তা রেখে। দুটি পথ দেখান। (মাঝারি)

ভাবনা: তিনটির মোট বদল হবে একটি commit-এর বদল; ভিত্তি 88c45a3 একই থাকবে। পথ ১: interactive rebase-এ ২য় আর ৩য়টিকে fixup (বার্তা ফেলে মেলানো; squash হলে বার্তাও মেলে)। পথ ২: reset --soft করে একবারে commit.

$ git log --oneline
8954a6d default qty to 1
02f0755 fix typo
ce6963c feat(cart): multiply by qty
88c45a3 docs: how to run
5d7fe9d feat(cart): sum item prices
9e5888e feat: cart skeleton
$ git rebase -i HEAD~3
Successfully rebased and updated refs/heads/main.

Editor-এ খোলা todo আর বদলানোর পরের রূপ (মন্তব্যের লাইন বাদ দিয়ে):

pick ce6963c # feat(cart): multiply by qty
pick 02f0755 # fix typo
pick 8954a6d # default qty to 1
# ---- বদলে করা হলো ----
pick ce6963c # feat(cart): multiply by qty
fixup 02f0755 # fix typo
fixup 8954a6d # default qty to 1
$ git log --oneline
48ce2b8 feat(cart): multiply by qty
88c45a3 docs: how to run
5d7fe9d feat(cart): sum item prices
9e5888e feat: cart skeleton
$ git show --stat --oneline HEAD
48ce2b8 feat(cart): multiply by qty
 cart.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git diff --quiet ORIG_HEAD HEAD && echo "same content"
same content
$ git switch -q -c try/soft ORIG_HEAD
$ git reset --soft HEAD~3
$ git status --short
M  cart.js
$ git commit -q -m "feat(cart): multiply by qty"
$ git log --oneline -2
dd88c74 feat(cart): multiply by qty
88c45a3 docs: how to run

কেন কাজ করে: rebase প্রথম commit চালিয়ে তার ওপর বাকি দুটির বদল একই commit-এ যোগ করেছে; ORIG_HEAD (rebase-এর আগের মাথা 8954a6d) আর নতুন 48ce2b8-এর file হুবহু এক — “same content”. পথ ২-এ reset --soft HEAD~3 তিনটি বদলকেই staged রেখে branch পেছায় (M cart.js), আর একটি commit-ই যথেষ্ট। দুই পথের ফল একই বিষয়বস্তু, কিন্তু ID আলাদা (48ce2b8 বনাম dd88c74): fixup প্রথম commit-এর লেখকের সময় রাখে, নতুন commit নতুন সময় নেয়।

ফলো-আপ: “আগে থেকেই পরিকল্পনা করে?” — git commit --fixup=<hash>, পরে git rebase -i --autosquash main; todo নিজেই সাজানো থাকে।

main · ভিত্তি 88c45a3, ওপরে ছোট ছোট তিনটি commit
Command
Todo
main-এর ইতিহাস
ORIG_HEAD
  1. আগে। একটি আসল কাজ আর দুটি ছোট সংশোধন — reviewer-এর জন্য অপ্রয়োজনীয় শব্দ।
  2. Todo খোলে। HEAD~3 মানে ভিত্তি 88c45a3; তার পরের তিনটি commit পুরোনো থেকে নতুন ক্রমে। Editor বন্ধ না করা পর্যন্ত ইতিহাস একটুও বদলায় না।
  3. Fixup বসানো। fixup মানে “আগের commit-এর সাথে মেলানো, নিজের বার্তা বাদ”; প্রথম লাইন pick থাকতেই হবে।
  4. ফল। একটি commit 48ce2b8, বার্তা প্রথমটির। পুরোনো মাথা 8954a6d ORIG_HEAD আর reflog-এ আছে — মন বদলালে git reset --hard ORIG_HEAD.
প্র ৩১পরিস্থিতি ৬ · একটি পুরোনো tag v1.0 checkout করে পরীক্ষা চালাতে গিয়ে দুটি commit করে ফেলেছেন, তারপর git switch main. Commit দুটি কোথায়, আর কীভাবে রাখবেন? (সহজ)

ভাবনা: tag checkout মানে detached HEAD; সেখানে commit হয়, কিন্তু কোনো branch নামের নিচে নয়। Switch করলে HEAD সরে যায়, আর commit দুটি নামহীন হয়ে পড়ে — তবে মুছে যায় না।

$ git switch --detach v1.0
HEAD is now at 724e420 feat: cart
$ git commit -q -am "try: old behaviour"
$ git commit -q -am "try: second idea"
$ git status | head -1
HEAD detached from v1.0
$ git switch main
Warning: you are leaving 2 commits behind, not connected to
any of your branches:

  8a665bf try: second idea
  6103f73 try: old behaviour

If you want to keep them by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> 8a665bf

Switched to branch 'main'
$ git reflog -2
90061cc HEAD@{0}: checkout: moving from 8a665bf650f6816b78f2bfe57da2b14e45933737 to main
8a665bf HEAD@{1}: commit: try: second idea
$ git branch try/old-behaviour 8a665bf
$ git log --oneline --graph --decorate --all
* 8a665bf (try/old-behaviour) try: second idea
* 6103f73 try: old behaviour
| * 90061cc (HEAD -> main) fix: total returns 0
|/
* 724e420 (tag: v1.0) feat: cart

কেন কাজ করে: Git নিজেই সতর্ক করেছে (“you are leaving 2 commits behind”) আর ঠিক কোন command চালাতে হবে তাও বলেছে — git branch <new-branch-name> 8a665bf. বার্তাটি চলে গেলেও reflog-এ HEAD@{1} একই commit দেখায়। নাম বসানোর পরে graph-এ দেখা যায় পরীক্ষার branch v1.0 থেকে আলাদা হয়েছে।

ফলো-আপ: “ভবিষ্যতে?” — পুরোনো version-এ কাজ করার পরিকল্পনা থাকলে শুরুতেই git switch -c try/old v1.0. আর git status-এর প্রথম লাইন “HEAD detached from v1.0” দেখলেই সতর্ক হোন।

প্র ৩২পরিস্থিতি ৭ · অর্ধেক কাজের একটি stash ভুল করে git stash drop করেছেন, আর terminal-ও বন্ধ করে ফেলেছেন। কাজটি ফেরত আনুন। (কঠিন)

ভাবনা: stash entry একটি commit; drop শুধু refs/stash-এর reflog থেকে তাকে সরায়। Commit object এখনো আছে, কিন্তু কোনো ref বা reflog থেকে পৌঁছানো যায় না — অর্থাৎ unreachable. git fsck এমন object খুঁজে দেয়।

$ git stash push -m "discount: half-done badge"
Saved working directory and index state On main: discount: half-done badge
$ git stash list
stash@{0}: On main: discount: half-done badge
$ git stash drop
Dropped refs/stash@{0} (97c075e9d39347366116c1afffc8fd8efa9042a5)
$ git stash list
$ # terminal closed; the hash above is gone from view
$ git fsck --no-reflogs --unreachable | grep commit
unreachable commit 0d4ae7023f9e2e1a6e2b38ed59c8a5c624c66f8f
unreachable commit 97c075e9d39347366116c1afffc8fd8efa9042a5
$ git log --oneline --no-walk 0d4ae70 97c075e
0d4ae70 index on main: 724e420 feat: cart
97c075e On main: discount: half-done badge
$ git stash apply -q 97c075e
$ git status --short
 M cart.js

কেন কাজ করে: drop-এর বার্তায় বন্ধনীর hash-টিই stash commit (97c075e) — দেখা থাকলে সরাসরি git stash apply 97c075e. Hash হারালে git fsck --no-reflogs --unreachable দুটি commit দেখায়: stash-এর W আর তার index-অংশ I. বার্তা দেখে চেনা যায় — “index on main: …” হলো I, “On main: discount: half-done badge” হলো W. W-কে apply করলেই কাজ ফেরত।

ফলো-আপ: (ক) “কতদিন পর্যন্ত?” — garbage collection unreachable object-কে ডিফল্টে দুই সপ্তাহের বেশি পুরোনো হলে মুছতে পারে (gc.pruneExpire), তাই দেরি না করাই ভালো। (খ) “অনেক unreachable commit হলে?” — git log --oneline --no-walk $(git fsck --no-reflogs --unreachable | awk '/commit/ {print $3}') দিয়ে সব একসাথে দেখুন, আর “WIP on” / “On <branch>:” বার্তা খুঁজুন।

৬ · পরিস্থিতিভিত্তিক সমস্যা (২) — push-এর পরে, আর দলের সাথে

রিভিশন: পর্ব ১৪ · remote আর force push, পর্ব ১৬ · pull request, পর্ব ৫৫ · bisect, পর্ব ৫৬ · cherry-pick, পর্ব ৫৭ · secret আর history পরিষ্কার

এখানে কাজ অন্যদের হাতেও পৌঁছেছে। তাই প্রথম প্রশ্ন সবসময়: “ইতিহাস বদলালে কার কার কপি পুরোনো হয়ে যাবে?” যেখানে উত্তর “অনেকের”, সেখানে নতুন commit দিয়ে সারানো ভালো; যেখানে ইতিহাস বদলাতেই হবে (secret, বড় file), সেখানে দলকে আগে জানানো।

প্র ৩৩পরিস্থিতি ৮ · “free shipping for all” commit-টি main-এ push হয়ে গেছে, তার পরে আরও একটি commit-ও। দোকানের ক্ষতি হচ্ছে — নিরাপদে বাতিল করুন। (সহজ)

ভাবনা: push হওয়া shared branch-এ reset করলে সবার ইতিহাস ভেঙে যায়। তাই উল্টো বদলের একটি নতুন commit — revert. পরের commit (README) অক্ষত থাকবে।

$ git log --oneline --decorate
d51da66 (HEAD -> main, origin/main) docs: add README
058b956 feat: free shipping for all
b1f5392 feat: add config
$ git revert --no-edit HEAD~1
[main fd4d001] Revert "feat: free shipping for all"
 Date: Thu Jan 15 10:04:00 2026 +0600
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --decorate
fd4d001 (HEAD -> main) Revert "feat: free shipping for all"
d51da66 (origin/main) docs: add README
058b956 feat: free shipping for all
b1f5392 feat: add config
$ git show --stat --format="%h %s%n%n%b" HEAD
fd4d001 Revert "feat: free shipping for all"

This reverts commit 058b956b4cf277c8005cf5947ed51ed4da29ecfe.


 config.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ cat config.js
const VAT = 0.15;
const SHIPPING = 60;
$ git push origin main
To ../s02srv.git
   d51da66..fd4d001  main -> main

কেন কাজ করে: git revert HEAD~1 শুধু 058b956-এর বদল উল্টায় — SHIPPING আবার 60 — আর বার্তায় লেখে কোন commit উল্টানো হলো। ইতিহাস শুধু সামনে বেড়েছে, তাই push সাধারণ fast-forward (d51da66..fd4d001); কারও কপি ভাঙেনি।

ফলো-আপ: (ক) “পরে feature-টি আবার চালু করতে?” — revert commit-টিকেই revert করুন (git revert fd4d001); পুরোনো branch আবার merge করলে Git ভাবে ওই বদল আগেই এসেছে। (খ) “Merge commit বাতিল?” — git revert -m 1 <merge>. (গ) “একসাথে কয়েকটি?” — git revert --no-commit A^..B, তারপর একটি commit.

প্র ৩৪পরিস্থিতি ৯ · Payment sandbox-এর একটি key config.js-এ commit হয়ে push হয়ে গেছে; পরের commit-এ সেটি environment variable-এ সরানো হয়েছে। কী করবেন? (কঠিন)

ভাবনা: পরের commit key মুছলেও আগের commit-এ সেটি আছে। তাই প্রথমে key বাতিল আর নতুন key (Git-এর বাইরের কাজ, এখানে দেখানো নেই), তারপর কোন commit-এ আছে খোঁজা, তারপর ইতিহাস পরিষ্কার। (এই key কাল্পনিক, FAKE_ দিয়ে শুরু।)

$ git log --oneline
5cca787 fix(pay): read key from environment
c667265 feat(pay): connect payment sandbox
e0ea449 feat: add config
$ git log --oneline -S FAKE_sk_test --all
5cca787 fix(pay): read key from environment
c667265 feat(pay): connect payment sandbox
$ git grep -n FAKE_sk_test $(git rev-list --all)
c66726554c9c9d02eb4a3273850971efe2f68943:config.js:2:const PAY_KEY = "FAKE_sk_test_9fQ2xLm4Rt7Vb1Nz";

git log -S দেখায় কোন commit লেখাটি যোগ বা মুছেছে (c667265 যোগ, 5cca787 মুছেছে), আর git grep … $(git rev-list --all) দেখায় কোন commit-এর snapshot-এ এখনো আছে। এবার একটি নতুন clone-এ, একটি replacement file দিয়ে (regex:FAKE_sk_test_[A-Za-z0-9]+==>REDACTED):

$ git clone -q s07srv.git s07clean
$ git filter-repo --replace-text ../replacements.txt
NOTICE: Removing 'origin' remote; see 'Why is my origin removed?'
        in the manual if you want to push back there.
        (was ../s07srv.git)
Parsed 3 commits
HEAD is now at 58cd9c7 fix(pay): read key from environment

New history written in 0.16 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
Completely finished after 0.32 seconds.
$ git log --oneline
58cd9c7 fix(pay): read key from environment
53b0e30 feat(pay): connect payment sandbox
e0ea449 feat: add config
$ git log --oneline -S FAKE_sk_test --all
$ git show HEAD~1:config.js
const VAT = 0.15;
const PAY_KEY = "REDACTED";
$ git remote -v
$ git remote add origin ../s07srv.git
$ git push --force --all origin
To ../s07srv.git
 + 5cca787...58cd9c7 main -> main (forced update)
$ git push --force --tags origin
Everything up-to-date

কেন কাজ করে: filter-repo প্রতিটি commit-এর blob-এ লেখাটি বদলেছে; যে commit বদলেছে তার আর তার পরের সবার ID নতুন (c667265 → 53b0e30), কিন্তু প্রথম commit e0ea449-এ key ছিল না, তাই সেটি একই। ভুল জায়গায় push ঠেকাতে filter-repo নিজেই origin সরায়; আমরা ইচ্ছা করে আবার যোগ করে --force push করেছি। (অগ্রগতির লাইনগুলো একসাথে এক লাইনে ছাপা হয়, আর সময় প্রতিবার আলাদা।)

ফলো-আপ: “এতেই কি শেষ?” — না। সবাইকে নতুন করে clone করতে বলুন (পুরোনো clone থেকে push হলে key ফিরে আসবে); platform-এ fork, pull request-এর ref বা cache-এ কপি থাকতে পারে — platform-এর নিয়ম দেখে তাদের সাহায্য চাইতে হতে পারে। এজন্যই বাতিল প্রথম কাজ।

প্র ৩৫পরিস্থিতি ১০ · v1.0-এ ১০০ টাকার দাম VAT-সহ ১১৫ দেখাত, এখন ১১৬ দেখায়। মাঝে আটটি commit. কোনটি bug এনেছে, স্বয়ংক্রিয়ভাবে খুঁজুন। (মাঝারি)

ভাবনা: good = v1.0, bad = HEAD, আর একটি ছোট script যা ১১৫ পেলে exit 0, না পেলে শূন্য ছাড়া কিছু দেয় — তাহলে git bisect run নিজেই binary search চালাবে।

$ git log --oneline
a4b9c61 docs: note 7
ea4bdd0 docs: note 6
70e078e docs: note 5
096faf0 docs: note 4
41d99a1 refactor(price): round total up
70f3c70 docs: note 3
578c54e docs: note 2
815d0cc docs: note 1
7b57e89 feat: price with VAT
$ cat test.sh
r=$(node -e "console.log(require(\"./price.js\")(100))")
[ "$r" = "115" ]
$ sh test.sh; echo "exit=$?"
exit=1
$ git bisect start HEAD v1.0
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[41d99a17a460f0aeb79b00e1e023a16950f76627] refactor(price): round total up
$ git bisect run sh test.sh
running 'sh' 'test.sh'
Bisecting: 1 revision left to test after this (roughly 1 step)
[578c54e00235f7635796682de9f5d4f89a7d8e10] docs: note 2
running 'sh' 'test.sh'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[70f3c70ef42f9c5b353dd364d3eed3747817a297] docs: note 3
running 'sh' 'test.sh'
41d99a17a460f0aeb79b00e1e023a16950f76627 is the first bad commit
commit 41d99a17a460f0aeb79b00e1e023a16950f76627
Author: Rahim <rahim@example.com>
Date:   Thu Jan 15 10:05:00 2026 +0600

    refactor(price): round total up

 price.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
bisect found first bad commit
$ git bisect log | grep -v "^#"
git bisect start 'HEAD' 'v1.0'
git bisect bad 41d99a17a460f0aeb79b00e1e023a16950f76627
git bisect good 578c54e00235f7635796682de9f5d4f89a7d8e10
git bisect good 70f3c70ef42f9c5b353dd364d3eed3747817a297
$ git bisect reset
Previous HEAD position was 70f3c70 docs: note 3
Switched to branch 'main'
$ git show --format=%h 41d99a1 -- price.js | tail -3
-function withVat(p) { return p + p * 15 / 100; }
+function withVat(p) { return Math.ceil(p + p * 15 / 100 + 0.01); }
 module.exports = withVat;

কেন কাজ করে: সন্দেহের তালিকায় আটটি commit (v1.0-এর পরের সব)। Git প্রথমে মাঝখানের 41d99a1 পরীক্ষা করেছে — bad; তারপর 578c54e — good; তারপর 70f3c70 — good; তিনটি পরীক্ষাতেই উত্তর, ⌈log₂ ৮⌉ = ৩-এর সাথে মেলে। ধরা পড়া commit-টির diff-এ দেখা যায় Math.ceil আর + 0.01 যোগ হয়েছে — ১১৫.০১-কে ১১৬ বানাচ্ছে। শেষে git bisect reset আপনাকে আগের branch-এ ফেরায়।

ফলো-আপ: (ক) “এই test-টি পরে কী হবে?” — regression test হিসেবে test suite-এ রাখুন, যাতে bug ফিরে না আসে। (খ) “কিছু commit build-ই হয় না?” — script সেখানে exit 125 দিলে Git সেটি বাদ দেয়। (গ) “Bug যোগ না হয়ে ‘ঠিক হয়েছে’ এমন commit খুঁজতে?” — git bisect start --term-old=broken --term-new=fixed.

প্র ৩৬পরিস্থিতি ১১ · কেউ ভুল করে ৩ MiB-এর build/app.zip commit করে push করেছিলেন। পরের commit-এ মুছলেও repository ভারী থেকে গেছে। ইতিহাস থেকে file-টি সরান। (কঠিন)

ভাবনা: পরের commit-এ মুছলে শুধু সর্বশেষ snapshot থেকে যায়, পুরোনো commit তো file-টি ধরে রাখে — তাই clone-এর আকার কমে না। দরকার প্রতিটি commit থেকে path-টি বাদ দিয়ে ইতিহাস নতুন করে লেখা, তারপর দলের সাথে সমন্বয় করে force push.

$ git clone -q --no-local s09srv.git s09clean
$ git log --oneline --stat
fdc5210 feat: cart v2
 cart.js | 1 +
 1 file changed, 1 insertion(+)
94803e5 build: add release zip
 build/app.zip | Bin 0 -> 3145728 bytes
 cart.js       |   1 +
 2 files changed, 1 insertion(+)
724e420 feat: cart
 cart.js | 1 +
 1 file changed, 1 insertion(+)
$ git count-objects -vH | grep size-pack
size-pack: 3.00 MiB
$ git filter-repo --invert-paths --path build/app.zip
NOTICE: Removing 'origin' remote; see 'Why is my origin removed?'
        in the manual if you want to push back there.
        (was ../s09srv.git)
Parsed 3 commits
HEAD is now at 43a23ed feat: cart v2

New history written in 0.17 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
Completely finished after 0.32 seconds.
$ git log --oneline --stat
43a23ed feat: cart v2
 cart.js | 1 +
 1 file changed, 1 insertion(+)
939fe6c build: add release zip
 cart.js | 1 +
 1 file changed, 1 insertion(+)
724e420 feat: cart
 cart.js | 1 +
 1 file changed, 1 insertion(+)
$ git count-objects -vH | grep size-pack
size-pack: 1.97 KiB
$ echo "build/" > .gitignore
$ git add .gitignore
$ git commit -q -m "chore: ignore build output"
$ git remote add origin ../s09srv.git
$ git push --force origin main
To ../s09srv.git
 + fdc5210...1c08a31 main -> main (forced update)

কেন কাজ করে: --invert-paths --path build/app.zip মানে “এই path ছাড়া বাকি সব রাখা হোক”। যে commit-এ zip ছিল (94803e5) সেটি আর তার পরের সব নতুন ID পেয়েছে; প্রথম 724e420 অপরিবর্তিত। Pack-এর আকার 3.00 MiB থেকে 1.97 KiB. Clone-টি --no-local দিয়ে নেওয়া হয়েছে, যাতে object pack হিসেবে আসে আর মাপ তুলনা করা যায়; filter-repo নিজেও একটি “fresh clone” চায়, যাতে ভুল repository-তে চলে না যায়। শেষে .gitignore-এ build/, যাতে আবার না ঘটে।

ফলো-আপ: (ক) “Server-এর জায়গা কি সাথে সাথে কমবে?” — server-এর নিজের garbage collection-এর ওপর নির্ভর করে; platform-ভেদে আলাদা। (খ) “পুরোনো পথ git filter-branch?” — এখনো Git-এর সাথে আসে, কিন্তু Git নিজেই চালানোর সময় সতর্ক করে আর filter-repo সুপারিশ করে (অংশ ৮)। (গ) “বড় file যদি সত্যিই দরকার?” — Git LFS.

প্র ৩৭পরিস্থিতি ১২ · Rahim main-এ tax() যোগের commit push করেছিলেন। পুরোনো clone থেকে Karim git push --force দিয়ে নিজের README commit বসিয়েছেন — server থেকে Rahim-এর commit উধাও। দুজনের দিক থেকেই কী ঘটল, আর কীভাবে সারাবেন? (কঠিন)

ভাবনা: server-এ commit হারালেও Rahim-এর local main-এ তা আছে। Rahim fetch করলে দেখবেন server-এর ইতিহাস “forced update” হয়েছে; তাঁর কাজ Karim-এর নতুন commit-এর ওপর আবার বসিয়ে সাধারণ push করলেই হলো। প্রথমে Karim-এর দিক:

$ git push origin main
To ../s10srv.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to '../s10srv.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
$ git push --force-with-lease origin main
To ../s10srv.git
 ! [rejected]        main -> main (stale info)
error: failed to push some refs to '../s10srv.git'
$ git push --force origin main
To ../s10srv.git
 + dcba3db...d467cc1 main -> main (forced update)

সাধারণ push “fetch first” বলে থেমেছে; --force-with-lease থেমেছে “stale info” বলে — এটিই সুরক্ষা কাজ করার মুহূর্ত। Karim তবু --force দিলেন। এবার Rahim:

$ git fetch origin
From ../s10srv
 + dcba3db...d467cc1 main       -> origin/main  (forced update)
$ git status -sb
## main...origin/main [ahead 1, behind 1]
$ git log --oneline --graph --decorate --all
* d467cc1 (origin/main, origin/HEAD) docs: README
| * dcba3db (HEAD -> main) feat(cart): add tax()
|/
* 724e420 feat: cart
$ git reflog -3 origin/main
d467cc1 refs/remotes/origin/main@{0}: fetch origin: forced-update
dcba3db refs/remotes/origin/main@{1}: update by push
724e420 refs/remotes/origin/main@{2}: update by push
$ git rebase origin/main
Successfully rebased and updated refs/heads/main.
$ git log --oneline --graph --decorate --all
* 7af6ce9 (HEAD -> main) feat(cart): add tax()
* d467cc1 (origin/main, origin/HEAD) docs: README
* 724e420 feat: cart
$ git push origin main
To ../s10srv.git
   d467cc1..7af6ce9  main -> main

কেন কাজ করে: fetch-এর + … (forced update) আর status-এর “ahead 1, behind 1” বলে দেয় ইতিহাস লেখা-বদল হয়েছে। origin/main-এর নিজের reflog দেখায় আগে সেটি dcba3db-এ ছিল — Rahim-এর local main না থাকলে এটিই উদ্ধারের সূত্র হতো (git branch rescue origin/main@{1})। git rebase origin/main Rahim-এর একমাত্র নিজের commit-টিকে Karim-এর README-র ওপর চালাল (7af6ce9), তারপর সাধারণ push — কোনো force লাগেনি, Karim-এর কাজও মোছেনি।

ফলো-আপ: “ভবিষ্যতে ঠেকাবেন কীভাবে?” — main-এ force push নিষেধ (protected branch); ব্যক্তিগত branch-এ শুধু --force-with-lease (দরকারে --force-if-includes); আর blameless আলোচনা — কেন Karim ভেবেছিলেন force দরকার।

ভিত্তি 724e420 · Rahim-এর tax() = dcba3db · Karim-এর README = d467cc1
ঘটনা
Server-এর main
Rahim-এর main
Rahim-এর origin/main
git status
  1. স্বাভাবিক push. Server আর Rahim-এর তিনটি নামই dcba3db-এ।
  2. Force push. Karim-এর README server-এ বসল, আর dcba3db server-এর ইতিহাস থেকে বাদ। Rahim-এর কম্পিউটারে কিছুই বদলায়নি — fetch ছাড়া কোনো খবর আসে না।
  3. Fetch-এ ধরা পড়া। origin/main জোর করে d467cc1-এ সরল; Rahim-এর main এখন এক দিকে এগিয়ে (tax), এক দিকে পিছিয়ে (README).
  4. Rebase. Rahim-এর নিজের commit README-র ওপর নতুন ID নিয়ে বসল; এখন শুধু “ahead 1”, কোনো “behind” নেই।
  5. সাধারণ push. Fast-forward, কারণ নতুন commit server-এর মাথার ওপরেই। দুজনের কাজই রইল।
প্র ৩৮পরিস্থিতি ১৩ · আপনার pull request-এর branch feature/search খোলার পরে main-এ দুটি নতুন commit merge হয়েছে; platform বলছে branch পুরোনো। কীভাবে আপডেট করবেন? (মাঝারি)

ভাবনা: দুটি বৈধ পথ। দলের নিয়ম সরল ইতিহাস চাইলে: fetch, origin/main-এর ওপর rebase, তারপর --force-with-lease (branch-টি শুধু আপনার)। ইতিহাস না বদলাতে চাইলে: git merge origin/main আর সাধারণ push — একটি merge commit বাড়ে। এখানে প্রথম পথ:

$ git fetch -q origin
$ git rev-list --left-right --count origin/main...feature/search
2	2
$ git log --oneline --graph --decorate --all
* cf3a1cb (origin/main, origin/HEAD) fix(cart): total returns 0
* a287782 docs: README
| * 8ede6fa (HEAD -> feature/search, origin/feature/search) test(search): trim test
| * a06f4b9 feat(search): add search
|/
* 724e420 (main) feat: cart
$ git rebase origin/main
Successfully rebased and updated refs/heads/feature/search.
$ git rev-list --left-right --count origin/main...feature/search
0	2
$ git log --oneline --graph --decorate --all
* b287559 (HEAD -> feature/search) test(search): trim test
* ad09c8a feat(search): add search
* cf3a1cb (origin/main, origin/HEAD) fix(cart): total returns 0
* a287782 docs: README
| * 8ede6fa (origin/feature/search) test(search): trim test
| * a06f4b9 feat(search): add search
|/
* 724e420 (main) feat: cart
$ git push origin feature/search
To ../s11srv.git
 ! [rejected]        feature/search -> feature/search (non-fast-forward)
error: failed to push some refs to '../s11srv.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
$ git push --force-with-lease origin feature/search
To ../s11srv.git
 + 8ede6fa...b287559 feature/search -> feature/search (forced update)

কেন কাজ করে: rebase-এর আগে 2 2 — main-এ আপনার কাছে না থাকা দুটি, আপনার দুটি main-এ নেই; পরে 0 2 — শুধু আপনার দুটি, main-এর সবকিছু ভিত্তিতে। Rebase-এর পরে commit-গুলোর ID নতুন (a06f4b9 → ad09c8a), আর server-এ পুরোনো দুটি আছে, তাই সাধারণ push “non-fast-forward” বলে থেমেছে। Lease-সহ force push নিরাপদে সেগুলো বদলেছে — মাঝে অন্য কেউ এই branch-এ push করলে থামত।

ফলো-আপ: “Reviewer যদি আগের commit-এ মন্তব্য করে থাকেন?” — rebase-এর পরে কিছু platform মন্তব্যকে “পুরোনো” দেখায়; বড় review চলাকালীন অনেক দল merge পছন্দ করে, বা শেষে একবারই rebase. git range-diff দিয়ে আগের আর নতুন commit-সারির পার্থক্যও দেখানো যায়।

পুরোনো pull request branch-কে main-এর ওপর আনা

Before: two commits on each side. After rebase: a straight line; the old pair stays on the server until the lease push

Stale pull request branch rebase-এর আগে ও পরে আগে · 2 behind, 2 ahead 724e420a287782cf3a1cba06f4b98ede6fa feature/search origin/main পরে · git rebase origin/main 724e420a287782cf3a1cbad09c8ab287559 a06f4b98ede6fa origin/feature/search (পুরোনো) origin/main feature/search
ছবি ৬৯.২ — তীর child থেকে parent-এর দিকে। Rebase-এর পরে git rev-list --left-right --count origin/main...feature/search = 0 আর 2. ড্যাশ দেওয়া পুরোনো দুটি commit server-এ থাকে যতক্ষণ না --force-with-lease push সেগুলো বদলায়।
প্র ৩৯পরিস্থিতি ১৪ · main merge করতে গিয়ে package.json আর package-lock.json দুটিতেই conflict — আপনার branch একটি library যোগ করেছে, main আরেকটি। Lockfile হাতে মেলাবেন? (মাঝারি)

ভাবনা: package.json মানুষের লেখা — হাতে মেলানো যায়: দুটি dependency-ই রাখুন। Lockfile tool-এর তৈরি, হাজার লাইনের হতে পারে, আর ভেতরের hash ও version একে অপরের সাথে বাঁধা — হাতে মেলানো ঝুঁকিপূর্ণ। নিয়ম: এক দিকের lockfile নিন, তারপর tool দিয়ে আবার তৈরি করুন। (এখানে library দুটি ছোট local folder, যাতে network ছাড়াই npm চালানো যায়।)

$ git switch -q feature/money
$ git merge main
Auto-merging package-lock.json
CONFLICT (content): Merge conflict in package-lock.json
Auto-merging package.json
CONFLICT (content): Merge conflict in package.json
Automatic merge failed; fix conflicts and then commit the result.
$ git status --short
UU package-lock.json
UU package.json
$ git diff --name-only --diff-filter=U
package-lock.json
package.json
$ sed -n "1,12p" package.json
{
  "name": "bazar",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
<<<<<<< HEAD
    "money": "file:../s13libs/money"
=======
    "dates": "file:../s13libs/dates"
>>>>>>> main
  }
}
$ # package.json by hand: keep both dependencies
$ git checkout --theirs package-lock.json
Updated 1 path from the index
$ npm install --package-lock-only

up to date in 219ms
$ grep -c "node_modules/" package-lock.json
2
$ git add package.json package-lock.json
$ git commit --no-edit
[feature/money 2abf6ee] Merge branch 'main' into feature/money
$ git log --oneline --graph
*   2abf6ee Merge branch 'main' into feature/money
|\
| * 2a00fdb feat: add dates lib
* | 1cba94a feat: add money lib
|/
* 5ad2a6e chore: npm init

কেন কাজ করে: merge-এর সময় --ours = আপনার বর্তমান branch, --theirs = যে branch মেলাচ্ছেন (এখানে main) — rebase-এর সময় ঠিক উল্টো। main-এর lockfile নিয়ে npm install --package-lock-only চালালে npm হাতে মেলানো package.json দেখে আপনার library-টিও যোগ করে; lockfile-এ এখন দুটি package (node_modules/-এর দুটি entry). তারপর দুটি file add করে merge commit. (npm-এর সময়ের সংখ্যা প্রতিবার আলাদা।)

ফলো-আপ: (ক) “Lockfile-এ conflict কম করতে?” — dependency-র বদল ছোট আর আলাদা pull request-এ, আর main থেকে ঘন ঘন আপডেট। (খ) “CI-তে কী যাচাই?” — npm ci, যা lockfile আর package.json না মিললে ব্যর্থ হয়। অন্য ecosystem-এর lockfile-এও (যেমন Python, Rust) নীতি একই, command আলাদা।

প্র ৪০পরিস্থিতি ১৫ · main-এ VAT দুই দশমিকে গোল করার fix এসেছে; main-এ coupon-এর নতুন feature-ও আছে, যা release-এ যাবে না। Fix-টি release/1.4-এ নিয়ে v1.4.1 বের করুন। (মাঝারি)

ভাবনা: পুরো main merge করলে coupon-ও চলে আসবে। দরকার শুধু একটি commit-এর বদল — cherry-pick, -x দিয়ে উৎস লিখে; তারপর SemVer অনুযায়ী patch version.

$ git log --oneline --decorate --all
32ef04c (HEAD -> main) fix(cart): round VAT to 2 decimals
c1bd507 feat(cart): add coupon flag
28dd5c3 (tag: v1.4.0, release/1.4) feat: first release of the cart
$ git switch release/1.4
Switched to branch 'release/1.4'
$ git cherry-pick -x 32ef04c
[release/1.4 8be521e] fix(cart): round VAT to 2 decimals
 Date: Thu Jan 15 10:03:00 2026 +0600
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --decorate --all --graph
* 8be521e (HEAD -> release/1.4) fix(cart): round VAT to 2 decimals
| * 32ef04c (main) fix(cart): round VAT to 2 decimals
| * c1bd507 feat(cart): add coupon flag
|/
* 28dd5c3 (tag: v1.4.0) feat: first release of the cart
$ git log -1 --format=%B
fix(cart): round VAT to 2 decimals

(cherry picked from commit 32ef04c14a5c155b1a523ea058af9a78a2d5be8c)

$ ls
cart.js
$ git cherry -v main release/1.4
- 8be521e71461bf4a4ae2a00d12d35c747f8efd43 fix(cart): round VAT to 2 decimals
$ git tag -a v1.4.1 -m "v1.4.1"
$ git describe
v1.4.1

কেন কাজ করে: নতুন commit 8be521e একই বদল আর বার্তা বহন করে, কিন্তু parent v1.4.0, তাই ID আলাদা; বার্তার শেষে “(cherry picked from commit 32ef04c…)”. ls-এ coupon.js নেই — feature আসেনি। git cherry -v main release/1.4-এর - চিহ্ন বলে এই বদলের সমতুল্য commit main-এ আগেই আছে (patch-id মিলিয়ে)। শুধু fix, তাই 1.4.0 → 1.4.1.

ফলো-আপ: (ক) “Fix আগে release-এ, পরে main-এ?” — উল্টো ক্রম ঝুঁকিপূর্ণ: পরের release-এ bug ফিরে আসে; “upstream first”. (খ) “Pick-এ conflict?” — আমাদের প্রথম চেষ্টায় সত্যিই হয়েছিল, যখন coupon-এর লাইন cart.js-এর ঠিক পরের লাইনে ছিল — পাশাপাশি লাইনের বদলও conflict ঘটায়; সমাধান, git add, git cherry-pick --continue. (গ) Release branch-এ CI চালানো বাধ্যতামূলক।

৭ · Command drill — “command-টি লিখুন”

রিভিশন: পর্ব ৯ · log আর diff, পর্ব ১১ · restore, reset, revert, পর্ব ১৪ · push আর fetch, পর্ব ১৫ · rebase --onto, পর্ব ৫৬ · stash, cherry-pick, worktree

প্রতিটি প্রশ্নে আগে খাতায় command-টি নিজে লিখুন, তারপর বিকল্প দেখুন। ভুল বিকল্পগুলোও বাস্তব command — সেগুলো ভুল কাজ করে, আর ব্যাখ্যায় সেটিই বলা আছে। সব সঠিক উত্তর Git ২.৫৩-এ scratch repository-তে চালিয়ে যাচাই করা।

D১ (সহজ) · app.js ভুল করে stage করেছেন, কিন্তু বদলটি পরের commit-এ চান — folder-এ বদল থাকুক, শুধু index থেকে নামুক। কোন command?

  • git reset --hard app.js
  • git restore --staged app.js
  • git restore app.js
  • git rm app.js

D২ (সহজ) · feature branch-এ আছে কিন্তু main-এ নেই — শুধু এমন commit-গুলোর তালিকা দেখতে?

  • git log feature..main
  • git diff main feature
  • git log main...feature
  • git log --oneline main..feature

D৩ (সহজ) · শেষ commit-এ একটি file দিতে ভুলে গেছেন; এখন সেটি stage করেছেন। Commit এখনো push হয়নি। বার্তা না বদলে file-টি সেই commit-এই যোগ করতে?

  • git commit --amend --no-edit
  • git commit --fixup HEAD
  • git revert HEAD
  • git reset --soft HEAD

D৪ (সহজ) · নতুন branch feature/login তৈরি করে এক command-এ সেখানে যেতে (Git ২.২৩ বা পরের)?

  • git branch feature/login
  • git switch feature/login
  • git switch -c feature/login
  • git checkout -- feature/login

D৫ (সহজ) · a1b2c3d commit-টি shared main-এ push হয়ে গেছে, আর অন্যরা তার পরে কাজ নিয়েছেন। এর বদল নিরাপদে বাতিল করতে?

  • git reset --hard a1b2c3d~1 তারপর git push --force
  • git commit --amend
  • git revert a1b2c3d
  • git rebase -i-এ লাইনটি drop করে push

D৬ (সহজ) · নতুন local branch feature/login প্রথমবার server-এ পাঠাতে, আর পরে শুধু git push/git pull লিখলেই যেন চলে?

  • git push -u origin feature/login
  • git push origin
  • git fetch -u
  • git branch -u origin/feature/login

D৭ (মাঝারি) · নিজের pull request-এর branch origin/main-এর ওপর rebase করেছেন; branch-টি আগে push করা ছিল। এখন push করার সবচেয়ে নিরাপদ command?

  • git push --force
  • git push
  • git pull তারপর git push
  • git push --force-with-lease

D৮ (সহজ) · Commit করার আগে দেখতে চান ঠিক কী commit হবে — অর্থাৎ index-এ তোলা বদলের diff. কোন command?

  • git diff
  • git diff --staged
  • git show
  • git log -p -1

D৯ (সহজ) · Untracked file মোছার আগে দেখতে চান কোনগুলো মুছবে — কিছু না মুছে। কোন command?

  • git clean -n
  • git clean -f
  • git reset --hard
  • git rm -r --cached .

D১০ (মাঝারি) · অর্ধেক কাজে একটি নতুন, এখনো add না করা file আছে। node_modules/ ignored. নতুন file-সহ কাজটি বার্তা দিয়ে তুলে রাখতে, কিন্তু ignored file বাদ দিয়ে?

  • git stash
  • git stash push -m "search: half-done"
  • git stash push -u -m "search: half-done"
  • git stash push -a -m "search: half-done"

D১১ (মাঝারি) · পুরো ইতিহাসে, সব branch-এ, কোন কোন commit FAKE_sk_test লেখাটি যোগ বা মুছেছে — খুঁজতে?

  • git grep FAKE_sk_test
  • git log -S FAKE_sk_test --oneline --all
  • git blame config.js
  • git log --grep=FAKE_sk_test

D১২ (মাঝারি) · বর্তমান branch origin/main-এর চেয়ে কয়টি commit এগিয়ে আর কয়টি পিছিয়ে — দুটি সংখ্যা এক command-এ?

  • git log origin/main
  • git diff --stat origin/main
  • git rev-list --count origin/main
  • git rev-list --left-right --count origin/main...HEAD

D১৩ (মাঝারি) · main-এর fix commit 32ef04c release/1.4-এ আনতে চান, main-এর অন্য কিছু নয়, আর নতুন commit-এর বার্তায় উৎস লেখা থাকুক। Release branch-এ দাঁড়িয়ে?

  • git cherry-pick -n 32ef04c
  • git merge 32ef04c
  • git cherry-pick -e 32ef04c
  • git cherry-pick -x 32ef04c

D১৪ (মাঝারি) · একটি সরলরেখার ইতিহাসে পুরোনো থেকে নতুন … → C → D → E → F. অন্য branch-এ শুধু D আর E — দুটিই — আনতে?

  • git cherry-pick D^..E
  • git cherry-pick D..E
  • git cherry-pick D..F
  • git cherry-pick D...E

D১৫ (কঠিন) · topic branch ভুল করে feature থেকে খোলা হয়েছিল, তাই এতে feature-এর একটি commit-ও আছে। শুধু topic-এর নিজের commit-গুলো main-এর ওপর বসাতে?

  • git rebase main
  • git merge main
  • git rebase --onto main feature topic
  • git cherry-pick main..topic

D১৬ (সহজ) · git merge main-এ conflict হয়েছে, আর আপনি এখন merge-টাই বাতিল করে আগের অবস্থায় ফিরতে চান?

  • git reset --soft
  • git merge --abort
  • git merge --continue
  • git checkout -- .

D১৭ (সহজ) · সব tracked file-এ যে বদল এখনো stage করেননি, সেগুলো ফেলে দিয়ে শেষ stage-করা অবস্থায় ফিরতে (নতুন command-এ)?

  • git restore .
  • git restore --staged .
  • git clean -fd
  • git stash drop

D১৮ (সহজ) · সহকর্মী server থেকে old-feature branch মুছে দিয়েছেন, কিন্তু আপনার git branch -r-এ এখনো origin/old-feature দেখাচ্ছে। পরিষ্কার করতে?

  • git branch -d old-feature
  • git push origin --delete old-feature
  • git remote remove origin
  • git fetch --prune

D১৯ (মাঝারি) · বর্তমান HEAD-এ bug আছে, v1.0-এ ছিল না। Bisect শুরু করতে এক command-এ?

  • git bisect start v1.0 HEAD
  • git bisect start HEAD v1.0
  • git bisect good HEAD
  • git bisect run v1.0

D২০ (মাঝারি) · মাঝপথের feature না ছুঁয়ে, একই repository-র ইতিহাস ভাগ করে, আলাদা folder ../hotfix-এ main থেকে নতুন branch hotfix/vat-এ কাজ শুরু করতে?

  • git stash তারপর git switch main
  • git clone . ../hotfix
  • git worktree add -b hotfix/vat ../hotfix main
  • git switch -c hotfix/vat main

৮ · বিপজ্জনক command-এর তালিকা আর নিরাপদ বিকল্প

রিভিশন: পর্ব ১০ · .gitignore, পর্ব ১১ · ভুল শোধরানো, পর্ব ১৪ · force push, পর্ব ৫৩ · কী ফেরানো যায়, পর্ব ৫৭ · history পরিষ্কার

ইন্টারভিউতে প্রায়ই জিজ্ঞেস করা হয়, “কোন Git command চালাতে আপনি ভয় পান?” ভালো উত্তর ভয় নয়, শ্রেণিবিভাগ: কোন command commit না করা কাজ মোছে (reflog-ও বাঁচাতে পারে না), কোনটি অন্যদের ইতিহাস বদলায়, আর কোনটি শুধু local ইতিহাস বদলায় (reflog বাঁচায়)। প্রথম দুই দলের আগে থামুন।

Commandকী হারাতে পারেফেরত আনা যায়?নিরাপদ বিকল্প বা আগের ধাপ
git reset --hardCommit না করা সব বদল (staged আর unstaged); branch-এর পরের commitCommit হলে reflog-এ হ্যাঁ; commit বা add না করা লেখা নাআগে git status, git stash push -u; শুধু branch সরাতে git reset --keep; শুধু file ফেরাতে git restore <file>
git push --forceServer-এ অন্যদের push করা commitকারও local কপি বা reflog থাকলে (পরিস্থিতি ১২)--force-with-lease (+ --force-if-includes), শুধু নিজের branch-এ; main-এ protected branch
git clean -fdxসব untracked আর ignored file — .env, local config, build cacheনা — Git এগুলো কখনো জানতই নাআগে git clean -n বা -ndx, বা git clean -i; -x কেন লাগবে নিজেকে জিজ্ঞেস করুন
git checkout -- . / git restore .সব tracked file-এর unstaged বদলনা (add করা থাকলে fsck --lost-found-এ blob হতে পারে)আগে git diff; সন্দেহ হলে git stash push — পরে drop করা সহজ
git filter-branchপুরো ইতিহাস ভুলভাবে লেখা হতে পারে; সব ID বদলায়Local-এ refs/original আর reflog থেকে, push-এর আগেgit filter-repo একটি নতুন clone-এ; দলের সাথে সমন্বয় করে force push
git branch -D, git stash drop/clearMerge না হওয়া branch, stash entryকিছুদিন — reflog বা fsck (পরিস্থিতি ৭)-d (merge না হলে থামে), git stash list আর show -p দেখে তারপর
Shared branch-এ rebase / commit --amendঅন্যদের কপির সাথে ইতিহাসের মিলহ্যাঁ, কিন্তু সবার জন্য ঝামেলাPush হওয়া shared branch-এ revert; rebase শুধু নিজের branch-এ

কয়েকটি আসল আচরণ দেখে নিন। প্রথমে একটি repository-তে app.js-এ বদল, একটি untracked notes.txt, আর ignored .env ও node_modules/:

$ git status --short
 M app.js
?? notes.txt
$ git clean -n
Would remove notes.txt
$ git clean -ndx
Would remove .env
Would remove node_modules/
Would remove notes.txt
$ git checkout -- .
$ git status --short
?? notes.txt

clean -n শুধু notes.txt দেখায়; -x যোগ করতেই .env আর node_modules/-ও তালিকায় — -fdx চালালে এগুলো চিরতরে যেত। আর git checkout -- . কোনো প্রশ্ন না করে app.js-এর বদল মুছে দিয়েছে; status-এ শুধু untracked file রইল। এই বদল কখনো commit বা add হয়নি, তাই reflog-এ এর কোনো চিহ্ন নেই।

reset --keep আর reset --hard-এর পার্থক্য: শেষ commit-এ c.txt যোগ হয়েছিল, আর এখন তাতে commit না করা বদল আছে।

$ git reset --keep HEAD~1
error: Entry 'c.txt' not uptodate. Cannot merge.
fatal: Could not reset index file to revision 'HEAD~1'.
$ git log --oneline -1
0afbff7 add c
$ git reset --hard HEAD~1
HEAD is now at baefc23 add b
$ git status --short; ls
a.txt
b.txt

--keep বুঝেছে যে branch সরালে c.txt-এর local বদল হারাবে, তাই কিছু না বদলে থেমেছে (branch এখনো 0afbff7-এ)। --hard কোনো প্রশ্ন করেনি: branch সরেছে আর c.txt তার বদলসহ folder থেকে উধাও। ইন্টারভিউতে এই পার্থক্য বলতে পারলে বোঝা যায় আপনি “কোন command কী ঝুঁকি নেয়” ভাবেন।

আর filter-branch চালালে Git ২.৫৩ নিজেই সতর্ক করে (তারপর কয়েক সেকেন্ড অপেক্ষা করে কাজ শুরু করে):

$ git filter-branch --index-filter "git rm -q --cached --ignore-unmatch notes.txt" HEAD
WARNING: git-filter-branch has a glut of gotchas generating mangled history
	 rewrites.  Hit Ctrl-C before proceeding to abort, then use an
	 alternative filtering tool such as 'git filter-repo'
	 (https://github.com/newren/git-filter-repo/) instead.  See the
	 filter-branch manual page for more details; to squelch this warning,
	 set FILTER_BRANCH_SQUELCH_WARNING=1.
Proceeding with filter-branch...

Rewrite 8669b071abf272577acd51d4dd0e41d0f4263015 (1/1) (0 seconds passed, remaining 0 predicted)
WARNING: Ref 'refs/heads/main' is unchanged

নিয়মটি মনে রাখুন: অচেনা বা ধ্বংসাত্মক command আগে একটি throwaway clone-এ। git clone --no-local repo /tmp/try-ধাঁচের একটি কপিতে চালিয়ে ফল দেখুন, তারপর আসলটিতে। আর বড় কোনো কাজের আগে এক সেকেন্ডের নিরাপত্তা জাল: git branch backup/before-rebase.

৯ · AI-এর যুগে — Git প্রস্তুতি আর Git-এর কাজে AI-কে যাচাই করা

AI tool Git-এর প্রশ্নে খুব আত্মবিশ্বাসী উত্তর দেয়, আর বেশিরভাগ সময় ঠিকও বলে। কিন্তু Git-এ একটি ভুল command সেকেন্ডে কয়েক দিনের কাজ মুছতে পারে, তাই “বেশিরভাগ সময়” যথেষ্ট নয়। ইন্টারভিউর প্রস্তুতিতে AI-কে ইন্টারভিউয়ারের ভূমিকায় বসান, কিন্তু প্রতিটি command নিজে চালিয়ে যাচাই করুন।

AI প্রায়ই যা করেকীভাবে ধরবেন
“git reset --hard আর git push --force দিন” — push হয়েছে কি না, shared কি না, জিজ্ঞেস না করেইপাঁচ ধাপের কাঠামোর প্রথম ধাপ নিজে করুন; push হওয়া shared branch-এ revert
অস্তিত্বহীন flag বানানো (git stash pop --keep, git rebase --undo)git help <command> বা official documentation; নিজের git --version
Merge আর rebase-এ ours/theirs গুলিয়ে ফেলাScratch repository-তে conflict বানিয়ে marker-এর লেবেল দেখুন (পরিস্থিতি ৩)
Secret ফাঁসে শুধু “history থেকে মুছুন”, key বাতিলের কথা নেইক্রম: revoke → rotate → clean → force push → জানানো
পুরোনো Git-এর উত্তর (checkout সব কাজে) বা খুব নতুন flag পুরোনো Git-এRelease note মিলিয়ে নিন; দলের সবার Git version এক নাও হতে পারে
Prompt-এ আপনার আসল .env, token বা private remote URL চেয়ে নেওয়াকখনো দেবেন না; কাল্পনিক মান আর example.com ব্যবহার করুন

নিচের prompt-এ AI-কে ইন্টারভিউয়ার বানানো হয়েছে, যে আপনার উত্তর যাচাই করবে আর ফলো-আপ প্রশ্ন করবে — উত্তর বলে দেবে না।

তুমি একজন অভিজ্ঞ software engineer, আমার Git ইন্টারভিউ অনুশীলন নিচ্ছ (Git 2.53). নিয়ম: ১. আমাকে একটি পরিস্থিতিভিত্তিক Git সমস্যা দাও (যেমন ভুল branch-এ commit, push হওয়া commit বাতিল, force push-এ হারানো কাজ)। শুরুতে বলো commit push হয়েছে কি না আর branch shared কি না — না বললে আমি জিজ্ঞেস করব। ২. আমি commit graph এঁকে (লেখায়) আর command-সহ উত্তর দেব। তুমি উত্তর বলে দেবে না; শুধু বলবে কোন ধাপে working tree, index, ref বা server-এর কী বদলাবে আর কোথায় কাজ হারানোর ঝুঁকি আছে। ৩. প্রতিটি উত্তরের পরে একটি ফলো-আপ প্রশ্ন করো, যেটি অবস্থা একটু বদলায় (যেমন "এবার ধরো এটা push হয়ে গেছে")। ৪. কোনো flag বা আচরণ নিয়ে নিশ্চিত না হলে "নিশ্চিত নই, git help দেখে নাও" বলবে — বানিয়ে লিখবে না। ৫. শেষে আমার উত্তরের তিনটি দুর্বলতা বলো।

AI-এর পরামর্শ হাতে পেলে — চেকলিস্ট

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

পুরো পাতার ধারণা মিশিয়ে ছয়টি প্রশ্ন আর একটি ক্রম-সাজানোর ধাঁধা। ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি API key push হয়ে গেছে। কাজগুলো ঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Service-এ গিয়ে key বাতিল (revoke)
  2. নতুন key বানিয়ে secret manager বা environment variable-এ রাখা (rotate)
  3. নতুন clone-এ git filter-repo দিয়ে ইতিহাস থেকে key সরানো
  4. সমন্বয় করে force push, আর দলকে নতুন করে clone করতে বলা
  5. Commit-এর আগে secret scanner আর platform-এর push protection চালু করা

(মাঝারি) feature-কে main-এর ওপর rebase করার সময় conflict হলো। File-এ <<<<<<< HEAD আর =======-এর মাঝের অংশে কার লেখা?

  • নতুন ভিত্তির (main-এর দিকের) লেখা
  • যে commit-টি এখন replay হচ্ছে, সেই আপনার commit-এর লেখা
  • দুই branch-এর merge base-এর লেখা
  • Server-এর origin/feature-এর লেখা

(মাঝারি) Server-এর main = R (সহকর্মীর নতুন commit). আপনার clone fetch করেনি, তাই আপনার origin/main = A, আর আপনি A-এর ওপর K বানিয়েছেন। git push --force-with-lease origin main চালালে?

  • Push সফল, R মুছে K বসে
  • প্রত্যাখ্যাত — “stale info”
  • Git নিজে R আর K merge করে push করে
  • Push-এর আগে Git স্বয়ংক্রিয়ভাবে fetch করে নেয়

(সহজ) এইমাত্র “feat: add search” commit করেছেন (push হয়নি), এখন বুঝলেন বার্তা আর একটি file দুটোই বদলাতে হবে। git reset --soft HEAD~1-এর পরে অবস্থা কী?

  • Commit আর তার সব বদল মুছে গেছে
  • Commit নেই, বদল working tree-তে unstaged
  • Commit নেই, তার বদল staged অবস্থায় index-এ আছে
  • একটি উল্টো বদলের নতুন commit তৈরি হয়েছে

(মাঝারি) নিচের কোনটি reflog বা git fsck দিয়েও ফেরত আনা যায় না?

  • ভুল git reset --hard HEAD~3-এ হারানো তিনটি commit
  • git branch -D দিয়ে মোছা branch-এর commit
  • git stash drop করা stash
  • কখনো add না করা বদল, যা git checkout -- . মুছে দিয়েছে

(সহজ) শেষ release v2.3.4. তারপর Conventional Commits অনুযায়ী commit: একটি feat(cart): … আর দুটি fix: …; কোনো ! বা BREAKING CHANGE নেই। SemVer-এ পরের version?

  • v2.4.0
  • v2.3.5
  • v3.0.0
  • v2.4.6

(কঠিন) একটি commit-এ cart.js আর docs/copy.js দুটি file-এর বিষয়বস্তু হুবহু এক (function total() {})। এই commit-এর snapshot-এ blob কয়টি?

  • দুটি — প্রতিটি file-এর জন্য একটি
  • একটি — দুই tree-এর entry একই blob-কে দেখায়
  • শূন্য — বিষয়বস্তু tree-এর ভেতরে থাকে
  • তিনটি — দুটি file আর docs folder-এর জন্য

শব্দকোষ

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

মনে রাখুন

  1. আগে অবস্থা, তারপর command: push হয়েছে? Branch shared? Commit না করা বদল আছে? তারপর graph এঁকে, শুধু দেখার command দিয়ে নিশ্চিত হয়ে, ঝুঁকিসহ সবচেয়ে কম ধ্বংসাত্মক সমাধান।
  2. Git-এর মডেল ছোট: commit = snapshot + parent, branch = নামফলক, HEAD = আপনি কোথায়; reset নামফলক সরায় (soft/mixed/hard কতদূর), revert নতুন commit যোগ করে।
  3. প্রায় সবকিছু ফেরে: reset, rebase, মোছা branch, drop করা stash — reflog আর fsck; ফেরে না শুধু কখনো add না করা লেখা আর clean-এ মোছা untracked file.
  4. অন্যদের ইতিহাস পবিত্র: shared branch-এ revert, নিজের branch-এ rebase + --force-with-lease; main-এ force push নিষেধ server-এর নিয়মে।
  5. Secret ফাঁসে বাতিল আগে: revoke → rotate → filter-repo → force push → জানানো → প্রতিরোধ; আর ধ্বংসাত্মক যেকোনো command আগে throwaway clone-এ।

পরের পর্বে: Git-এর বাইরের ইন্টারভিউ — SOLID আর design pattern-এর প্রশ্ন, testing আর code review-এর পরিস্থিতি, আর “একটি কঠিন সহকর্মীর সাথে কাজের অভিজ্ঞতা বলুন”-ধাঁচের আচরণগত প্রশ্নের গোছানো উত্তর — পর্ব ৭০-এ, কোর্সের শেষ পর্ব।

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

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