এই পর্বে যা শিখবেন
- “কী ফেরাতে চান, কোথায় আছে” — এই দুই প্রশ্নে সঠিক undo command বেছে নেওয়া
restore,restore --staged,commit --amendআরreset-এর তিন mode তিনটি এলাকায় ঠিক কী বদলায়- কেন শেয়ার করা branch-এ
revertনিরাপদ আরreset+ force push বিপজ্জনক git cleanদিয়ে untracked ফাইল নিরাপদে সরানো আর reflog দিয়ে হারানো commit উদ্ধার- AI যখন
reset --hardবাpush --forceপরামর্শ দেয়, তখন কীভাবে যাচাই করবেন
১ · কী ফেরাতে চান, কোথায় আছে — undo-এর মানচিত্র
পরীক্ষার খাতার কথা ভাবুন। খসড়া পাতায় ভুল লিখলে কেটে দিলেই হয়। ফেয়ার কপিতে ভুল হলে কাটাকুটি চোখে পড়ে, তাই সাবধানে শোধরাতে হয়। আর খাতা জমা দিয়ে ফেললে আপনি আর সেটি টেনে আনতে পারেন না — বড়জোর একটি আলাদা সংশোধনী পাতা জুড়তে পারেন। Git-এ ভুল শোধরানোও ঠিক এমন: ভুলটি কোন জায়গায় আছে, তার ওপর নির্ভর করে কোন পদ্ধতি নিরাপদ।
পর্ব ৮-এ আপনি Git-এর তিনটি এলাকা দেখেছেন। এখানে শুধু মনে করিয়ে দিই। Working tree হলো আপনার ফোল্ডারের ফাইল, যেখানে আপনি লেখেন। Index (staging area) হলো পরের commit-এর খসড়া, যা git add দিয়ে ভরে। আর repository-তে থাকে সব commit; বর্তমান branch যে commit-এর দিকে নির্দেশ করে, সেটিই HEAD. Undo করার প্রতিটি command আসলে এই তিন জায়গার একটি বা একাধিক জায়গাকে অন্য একটি জায়গার মতো করে দেয়।
| ভুলটি কোথায় | উদাহরণ | Command | ঝুঁকি |
|---|---|---|---|
| শুধু working tree-তে | ফাইল এলোমেলো করে ফেলেছেন, add করেননি | git restore <file> | ⚠ পরিবর্তন চিরতরে যায় |
| Index-এ (stage করা) | ভুল ফাইল git add করেছেন | git restore --staged <file> | নিরাপদ — কাজ থাকে |
| নতুন untracked ফাইল | debug.log, tmp/ | git clean -n, তারপর -f | ⚠ ফাইল মুছে যায় |
| শেষ commit-এ, push হয়নি | বার্তায় বানান ভুল, একটি ফাইল বাদ | git commit --amend | নতুন commit ID |
| পুরোনো commit-এ, push হয়নি | শেষ তিনটি commit বাতিল করবেন | git reset (--soft/--mixed/--hard) | --hard হলে ⚠ |
| Push হয়ে যাওয়া commit | main-এ একটি bug ঢুকেছে | git revert <commit> | নিরাপদ — history অটুট |
| ভুল command-এ commit “হারিয়েছেন” | ভুল reset --hard | git reflog → git reset --hard HEAD@{n} | শুধু commit করা কাজ ফেরে |
এই সারণিটিই নিচের flowchart-এ সিদ্ধান্তের ধাপ হিসেবে আঁকা। ওপর থেকে প্রশ্নগুলোর উত্তর দিতে দিতে নামুন; প্রথম “হ্যাঁ” যেখানে, সেখানেই আপনার command.
কোন undo command — সিদ্ধান্তের ধাপ
Choosing the right undo command: where is the change, and has it been shared?
Git-এ “undo” একটি বোতাম নয়, একটি প্রশ্ন: কোন এলাকাকে, কোন উৎস থেকে, আবার লিখবেন? restore ফাইলকে index বা কোনো commit থেকে আবার লেখে। reset branch-এর pointer সরায়, আর mode অনুযায়ী index ও working tree-কে সেই commit-এর মতো করে। revert কিছুই মোছে না — উল্টো পরিবর্তনের একটি নতুন commit বানায়।
যে repository দিয়ে পুরো পর্বের উদাহরণ
এই পর্বের প্রতিটি “উদাহরণ” আউটপুট এই যন্ত্রে Git ২.৫৩ দিয়ে একটি আলাদা scratch ফোল্ডারে চালিয়ে হুবহু কপি করা। প্রজেক্টটি একটি কাল্পনিক অনলাইন বাজার অ্যাপের ছোট অংশ। লেখক, ইমেল আর সময় নির্দিষ্ট রাখা হয়েছে, তাই ফাইলের লেখা হুবহু এক হলে আপনার যন্ত্রেও একই commit ID আসবে:
$ git -c init.defaultBranch=main init -q bazar && cd bazar
$ git config user.name "Rahim"; git config user.email "rahim@example.com"
$ git config core.autocrlf false
$ export GIT_AUTHOR_DATE="2026-01-15T10:00:00+06:00" GIT_COMMITTER_DATE="2026-01-15T10:00:00+06:00"
$ printf '# Bazar app\n' > README.md
$ git add README.md; git commit -q -m "Add README"
$ export GIT_AUTHOR_DATE="2026-01-15T10:01:00+06:00" GIT_COMMITTER_DATE="2026-01-15T10:01:00+06:00"
$ printf 'function total(items) {\n return items.reduce((s, it) => s + it.price * it.qty, 0);\n}\nmodule.exports = { total };\n' > cart.js
$ printf '# Bazar app\n- cart total\n' > README.md
$ git add cart.js README.md; git commit -q -m "Add cart total"
$ export GIT_AUTHOR_DATE="2026-01-15T10:02:00+06:00" GIT_COMMITTER_DATE="2026-01-15T10:02:00+06:00"
$ printf 'function discount(sum) {\n return sum >= 1000 ? sum * 0.9 : sum;\n}\nmodule.exports = { discount };\n' > discount.js
$ git add discount.js; git commit -q -m "Add discount rule"
$ git log --oneline
f469880 Add discount rule
f8f0806 Add cart total
df9c5fe Add README২ · Working tree আর index ফেরানো — git restore
ধরুন আপনি cart.js-এ কিছু পরীক্ষা করতে গিয়ে ফাইলটি এলোমেলো করে ফেলেছেন, আর বুঝছেন এই পরিবর্তন রাখার মতো নয়। git restore ফাইলটিকে index-এ যা আছে, সেই অবস্থায় ফিরিয়ে আনে। Index-এ সাধারণত শেষ commit-এর লেখাই থাকে, যদি না আপনি নতুন কিছু add করে থাকেন।
$ git status --short
M cart.js
$ git restore cart.js
$ git status
On branch main
nothing to commit, working tree cleanএই command কোনো প্রশ্ন করে না, কোনো নিশ্চিতকরণও চায় না। git restore <file> চালানোর মুহূর্তে ফাইলের যে পরিবর্তন কখনো add বা commit হয়নি, তা Git-এর কোথাও ছিল না — তাই reflog বা অন্য কোনো উপায়ে ফেরে না। সন্দেহ থাকলে আগে git diff cart.js দেখুন (পর্ব ৯), অথবা পরিবর্তনটি একটি আলাদা ফাইলে কপি করে রাখুন। পরিবর্তন সাময়িকভাবে সরিয়ে রাখার আরেক উপায় git stash, যা পর্ব ৫৬-এ দেখবেন।
ভুল ফাইল stage করেছেন — restore --staged
এবার উল্টো ঘটনা। আপনি একটি debug লাইন যোগ করে ভুলে git add করে ফেলেছেন। Commit করার আগে এটিকে index থেকে নামাতে চান, কিন্তু ফাইলের লেখা হারাতে চান না:
$ git add cart.js
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: cart.js
$ git restore --staged cart.js
$ git status --short
M cart.jsখেয়াল করুন, git status নিজেই পরামর্শ দিচ্ছে কোন command দিয়ে unstage করবেন। আর শেষ লাইনে M-এর আগে একটি ফাঁকা ঘর: বাঁয়ের অক্ষর index-এর অবস্থা, ডানের অক্ষর working tree-র অবস্থা। পরিবর্তন এখন শুধু working tree-তে আছে, index আবার HEAD-এর মতো। --staged index-কে HEAD থেকে আবার লেখে, working tree ছোঁয় না — তাই এটি নিরাপদ।
পুরোনো কোনো commit থেকে একটি ফাইল আনা — --source
কখনো কখনো একটি ফাইলের দুই commit আগের রূপ দরকার হয়, কিন্তু পুরো project পেছনে নিতে চান না। --source=<commit> বলে দেয় কোন commit থেকে লেখা আনতে হবে। ডিফল্টভাবে এটি শুধু working tree-তে বসে; index আর branch যেমন ছিল তেমনই থাকে:
$ git restore --source=HEAD~2 README.md
$ cat README.md
# Bazar app
$ git status --short
M README.md
$ git restore README.md
$ cat README.md
# Bazar app
- cart totalHEAD~2 মানে HEAD থেকে দুই ধাপ পেছনের commit, এখানে df9c5fe. আনা লেখা পছন্দ হলে add করে commit করবেন; না হলে শেষের মতো আবার git restore দিয়ে বাতিল। দুই জায়গাতেই একসাথে বসাতে চাইলে git restore --source=HEAD~2 --staged --worktree README.md.
পুরোনো command — checkout -- আর reset HEAD
git restore আর git switch এসেছে Git ২.২৩-এ, ২০১৯ সালে। তার আগে একই কাজ হতো git checkout আর git reset দিয়ে। পুরোনো বই, Stack Overflow-এর উত্তর আর অনেক AI এখনো এগুলোই দেখায়। এগুলো আজও কাজ করে:
$ git add cart.js
$ git reset HEAD cart.js
Unstaged changes after reset:
M cart.js
$ git checkout -- cart.js
$ git status --short| কাজ | Git ২.২৩ থেকে | পুরোনো রূপ |
|---|---|---|
| Working tree-র পরিবর্তন বাতিল | git restore f | git checkout -- f |
| Unstage | git restore --staged f | git reset HEAD f |
| পুরোনো commit থেকে ফাইল | git restore --source=X f | git checkout X -- f (index-ও বদলায়) |
| Branch বদলানো | git switch b | git checkout b |
কেন ভাঙা হলো? পুরোনো git checkout একই নামে দুটি একদম ভিন্ন কাজ করত — branch বদলানো (নিরাপদ) আর ফাইলের পরিবর্তন মুছে দেওয়া (বিপজ্জনক)। একটি অক্ষর এদিক-ওদিক হলেই একটির বদলে অন্যটি চলত। Branch বদলানো আর switch নিয়ে বিস্তারিত পর্ব ১২-এ।
৩ · শেষ commit ঠিক করা — git commit --amend
Commit করে ফেলার পরেই চোখে পড়ল দুটি ভুল: বার্তায় “delivery”-র বানান ভুল, আর নতুন fee.json ফাইলটি add করতে ভুলে গেছেন। git commit --amend শেষ commit-টিকে বাতিল করে তার জায়গায় একটি নতুন commit বানায় — আগের commit-এর বিষয়বস্তু, তার সাথে index-এ এখন যা আছে, আর নতুন বার্তা মিলিয়ে।
$ export GIT_AUTHOR_DATE="2026-01-15T10:03:00+06:00" GIT_COMMITTER_DATE="2026-01-15T10:03:00+06:00"
$ printf 'const { discount } = require("./discount");\nconst FEE = require("./fee.json").fee;\nfunction total(items) {\n const sum = items.reduce((s, it) => s + it.price * it.qty, 0);\n return discount(sum) + FEE;\n}\nmodule.exports = { total };\n' > cart.js
$ printf '{ "fee": 60 }\n' > fee.json
$ git add cart.js
$ git commit -m "Add delivry fee"
[main 97611f9] Add delivry fee
1 file changed, 4 insertions(+), 1 deletion(-)
$ git status --short
?? fee.json
$ export GIT_AUTHOR_DATE="2026-01-15T10:04:00+06:00" GIT_COMMITTER_DATE="2026-01-15T10:04:00+06:00"
$ git add fee.json
$ git commit --amend -m "Add delivery fee"
[main d09aad4] Add delivery fee
Date: Thu Jan 15 10:03:00 2026 +0600
2 files changed, 5 insertions(+), 1 deletion(-)
create mode 100644 fee.json
$ git log --oneline
d09aad4 Add delivery fee
f469880 Add discount rule
f8f0806 Add cart total
df9c5fe Add READMEতিনটি জিনিস খেয়াল করুন। এক, commit ID বদলে গেছে: 97611f9 থেকে d09aad4. দুই, git log-এ 97611f9 আর নেই, যেন কখনো ছিলই না। তিন, Date: লাইনে ১০টা ৩ মিনিট — amend ডিফল্টভাবে লেখকের (author) মূল সময় রেখে দেয়, শুধু committer-এর সময় নতুন হয়। বার্তা না বদলে শুধু ফাইল যোগ করতে চাইলে git commit --amend --no-edit.
ID কেন বদলায়? একটি commit-এর ID তার পুরো বিষয়বস্তুর hash: snapshot, parent commit, লেখক, সময় আর বার্তা। এর যেকোনো একটি বদলালে hash বদলায় (ভেতরের খুঁটিনাটি পর্ব ৫২-এ)। তাই Git-এ কোনো commit “সম্পাদনা” করা যায় না — শুধু নতুন একটি বানিয়ে branch-কে সেদিকে সরানো যায়। নিচের ডায়াগ্রামে ধাপে ধাপে দেখুন:
| main | |
| Index-এ fee.json | |
| 97611f9 |
- ভুল commit.
97611f9-এ বার্তার বানান ভুল, আরfee.jsonতখনো untracked (??). main এই commit-এর দিকে। git add fee.json. শুধু index বদলাল; commit আর main আগের মতোই। Amend-এর জন্য index-কে আগে প্রস্তুত করতে হয়।git commit --amend. Git একটি নতুন commitd09aad4বানাল, যার parent সেই একইf469880— অর্থাৎ পুরোনোটির “পরে” নয়, “বদলে”। main সরে গেল নতুনটিতে।97611f9মোছেনি; শুধু এখন কোনো branch তার দিকে নির্দেশ করে না, তাইgit log-এ দেখা যায় না। Reflog-এ সে এখনো আছে।- যদি আগেই push হয়ে থাকত? সহকর্মী
97611f9pull করে তার ওপর কাজ শুরু করে থাকতে পারেন। আপনার main এখন অন্য একটি commit-এ, তাই সাধারণ push প্রত্যাখ্যাত হবে, আর জোর করে push করলে দুই history আলাদা হয়ে যাবে। সেজন্য নিয়ম: push-করা commit amend নয়; বার্তার ভুল সহ্য করুন, বা একটি নতুন commit দিন।
৪ · git reset — branch-এর pointer সরানো, তিনটি mode
git reset <commit> সবচেয়ে শক্তিশালী আর সবচেয়ে ভুল বোঝা undo command. এর মূল কাজ একটাই: বর্তমান branch-কে অন্য একটি commit-এর দিকে সরানো। তারপর mode ঠিক করে আরও কতদূর যাবে:
--soft
শুধু branch সরে। Index আর working tree আগের মতো থাকে — বাতিল commit-গুলোর সব পরিবর্তন “staged” অবস্থায় হাজির। কয়েকটি commit মিলিয়ে একটি বানাতে কাজে লাগে।
branch--mixed (ডিফল্ট)
Branch সরে, আর index সেই commit-এর মতো হয়। Working tree ছোঁয় না — পরিবর্তনগুলো ফাইলে থাকে, কিন্তু unstaged. Mode না লিখলে এটিই চলে।
branch + index--hard
Branch, index আর working tree — তিনটিই সেই commit-এর মতো। Tracked ফাইলের সব অসংরক্ষিত পরিবর্তন মুছে যায়। Untracked ফাইল ছোঁয় না।
⚠ branch + index + working treeনিচের ডায়াগ্রামে একই শুরুর অবস্থা থেকে তিনটি mode পাশাপাশি চলছে। শুরুতে main আছে C3-এ (যেখানে cart.js-এর রূপ v3); আপনি ফাইলটি বদলে v4 বানিয়ে stage করেছেন, তারপর আবার বদলে v5 করেছেন। Command: git reset --<mode> HEAD~2.
| --soft | |
| --mixed | |
| --hard |
- শুরু — তিন mode-এর জন্য একই অবস্থা। main
C3-এ, index-এ v4 (stage করা), working tree-তে v5.git statusএখন “staged” আর “unstaged” দুই রকম পরিবর্তনই দেখাবে। - প্রথম কাজ, তিন mode-এই: branch সরানো। main
C3থেকেC1-এ গেল।--softএখানেই থামে: index-এ v4 আর ফাইলে v5 আগের মতো। HEAD এখন v1, তাই v1 থেকে v4 পর্যন্ত সব পরিবর্তন staged দেখায়। - দ্বিতীয় কাজ,
--mixedআর--hard: index মেলানো। Index-কেC1-এর মতো (v1) করা হলো।--mixedএখানে থামে। ফাইলে v5 আছে, কিন্তু কিছুই staged নয়। লক্ষ করুন: আপনার stage করা v4 এখন index-এও নেই, ফাইলেও নেই। - তৃতীয় কাজ, শুধু
--hard: working tree মেলানো। ফাইলটিও v1 হয়ে গেল। v5 কখনো commit বা add হয়নি, তাই সেটি এখন আর কোথাও নেই। এজন্যই--hardচালানোর আগেgit statusদেখা অভ্যাস করুন। C3কোথায় গেল? কোথাও যায়নি। তিন mode-এর কোনোটিই commit মোছে না; শুধু main আরC3-এর দিকে নির্দেশ করে না। Git সঙ্গে সঙ্গেORIG_HEAD-এ পুরোনো অবস্থান লিখে রাখে, আর reflog-এও লেখা থাকে। তাইgit reset --hard ORIG_HEADদিলে main আবারC3-এ ফেরে — কিন্তু v4 আর v5 ফেরে না।
এবার আসল Git-এ d09aad4 থেকে এক commit পেছনে, প্রতিবার status দেখে। প্রতিটি পরীক্ষার পর git reset --hard ORIG_HEAD দিয়ে আবার d09aad4-এ ফিরেছি। ORIG_HEAD হলো reset, merge বা rebase-এর আগে HEAD যেখানে ছিল, সেই জায়গার একটি বিশেষ নাম।
$ git reset --soft HEAD~1
$ git log --oneline -1
f469880 Add discount rule
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: cart.js
new file: fee.json
$ git reset --hard ORIG_HEAD
HEAD is now at d09aad4 Add delivery feeCommit বাতিল, কিন্তু তার দুটি পরিবর্তন staged হয়ে অপেক্ষা করছে। এখন git commit দিলে একটি নতুন commit হবে — আবার লেখা বার্তাসহ।
$ git reset HEAD~1
Unstaged changes after reset:
M cart.js
$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: cart.js
Untracked files:
(use "git add <file>..." to include in what will be committed)
fee.json
no changes added to commit (use "git add" and/or "git commit -a")
$ git reset --hard ORIG_HEAD
HEAD is now at d09aad4 Add delivery feefee.json এবার “untracked” — কারণ index-কে f469880-এর মতো করা হয়েছে, যেখানে এই ফাইল ছিলই না। ফাইলটি কিন্তু ফোল্ডারে আছে। কোন পরিবর্তন কোন commit-এ যাবে, নতুন করে ভাগ করতে চাইলে এই mode.
$ git reset --hard HEAD~1
HEAD is now at f469880 Add discount rule
$ git status
On branch main
nothing to commit, working tree clean
$ git log --oneline
f469880 Add discount rule
f8f0806 Add cart total
df9c5fe Add README
$ ls
README.md
cart.js
discount.jsসব পরিষ্কার — আর ঠিক এটাই বিপদ। fee.json ফোল্ডার থেকেও উধাও, কারণ সেটি d09aad4-এ tracked ছিল। d09aad4 এখন log-এ নেই। অংশ ৭-এ এই অবস্থা থেকেই তাকে ফিরিয়ে আনব।
দুই রূপের git reset: commit দিলে (git reset HEAD~1) branch সরে। ফাইলের নাম দিলে (git reset cart.js বা git reset HEAD cart.js) branch সরে না — শুধু সেই ফাইলের index-এর লেখা HEAD থেকে আবার বসে, অর্থাৎ unstage. এই দুই রূপ গুলিয়ে ফেলাই নতুনদের সবচেয়ে বড় বিভ্রান্তি; restore --staged আসার একটি কারণও এটি। আরও দুটি কম-প্রচলিত mode আছে — --keep আর --merge — যেগুলো অসংরক্ষিত পরিবর্তন মুছে ফেলার বদলে থেমে যায়; বিস্তারিত git help reset-এ।
তিন mode কোন এলাকা ছোঁয়, তা একটি ছোট মডেলে চালিয়ে দেখুন। প্রতিটি command একই শুরুর অবস্থার একটি নতুন কপিতে চলে, যাতে পাশাপাশি তুলনা করা যায়। শেষ দুই লাইন restore-এর দুই রূপ:
// Three areas + a branch pointer. One file, cart.js; "v1".."v5" are its versions.
const commits = { C1: { parent: null, cart: "v1" }, C2: { parent: "C1", cart: "v2" },
C3: { parent: "C2", cart: "v3" } };
function start() { // main at C3, v4 staged (index), then edited again to v5 (working tree)
return { main: "C3", index: "v4", work: "v5" };
}
function back(id, n) { while (n-- > 0) id = commits[id].parent; return id; }
const ops = {
"git reset --soft HEAD~2": s => { s.main = back(s.main, 2); }, // branch only
"git reset HEAD~2 (--mixed)": s => { s.main = back(s.main, 2); // branch + index
s.index = commits[s.main].cart; },
"git reset --hard HEAD~2": s => { s.main = back(s.main, 2); // all three
s.index = s.work = commits[s.main].cart; },
"git restore --staged cart.js": s => { s.index = commits[s.main].cart; }, // index <- HEAD
"git restore cart.js": s => { s.work = s.index; }, // work <- index
};
function row(label, s) {
const head = commits[s.main].cart;
const staged = s.index !== head ? "yes" : "no", unstaged = s.work !== s.index ? "yes" : "no";
console.log(label.padEnd(31) + s.main.padEnd(6) + head.padEnd(6) + s.index.padEnd(7) +
s.work.padEnd(6) + staged.padEnd(8) + unstaged);
}
console.log("command (each on a fresh copy)".padEnd(31) + "main HEAD index work staged unstaged");
row("(start)", start());
for (const [cmd, f] of Object.entries(ops)) { const s = start(); f(s); row(cmd, s); }
// Commits are never deleted by reset: C3 still exists, only "main" stopped pointing at it.
console.log("C3 still in object store:", "C3" in commits);আউটপুট:
command (each on a fresh copy) main HEAD index work staged unstaged
(start) C3 v3 v4 v5 yes yes
git reset --soft HEAD~2 C1 v1 v4 v5 yes yes
git reset HEAD~2 (--mixed) C1 v1 v1 v5 no yes
git reset --hard HEAD~2 C1 v1 v1 v1 no no
git restore --staged cart.js C3 v3 v3 v5 no yes
git restore cart.js C3 v3 v4 v4 yes no
C3 still in object store: true
“staged” মানে index আর HEAD আলাদা; “unstaged” মানে working tree আর index আলাদা — ঠিক যা git status দেখায়। --soft: শুধু main C1-এ গেল, index v4 আর ফাইল v5 অক্ষত, দুই রকম পরিবর্তনই আছে। --mixed: index v1 হলো, তাই staged “no”, কিন্তু ফাইলে v5 থাকায় unstaged “yes”. --hard: তিন ঘরেই v1, কোনো পরিবর্তন নেই — v4 আর v5 হারিয়েছে। restore --staged: main নড়েনি (C3), index HEAD-এর মতো v3 হলো। restore: ফাইল index-এর মতো v4 হলো, v5 মুছে গেল, আর v4 staged থেকে গেল। শেষ লাইন মনে করিয়ে দেয়: reset commit মোছে না।
৫ · git revert — শেয়ার করা history-তে নিরাপদ undo
ধরুন f469880 Add discount rule commit-টি ইতিমধ্যে দলের main-এ push হয়ে গেছে, আর এখন দেখা গেল ছাড়ের নিয়মটি ভুল। দলের অন্তত দুজন সেটি pull করেছেন। এখানে reset ভুল পথ, কারণ সেটি history-র একটি অংশ মুছে দিতে চায় — অথচ সেই অংশ অন্যদের clone-এ আছে। git revert <commit> উল্টো পথে হাঁটে: ভুল commit-এর ঠিক উল্টো পরিবর্তন দিয়ে একটি নতুন commit বানায়। ব্যাংকের হিসাবের খাতায় ভুল এন্ট্রি কেটে ফেলা হয় না; তার নিচে একটি উল্টো এন্ট্রি লেখা হয় — revert ঠিক সেই হিসাবরক্ষকের নিয়ম।
$ export GIT_AUTHOR_DATE="2026-01-15T10:05:00+06:00" GIT_COMMITTER_DATE="2026-01-15T10:05:00+06:00"
$ git revert --no-edit HEAD~1
[main 5fcff4a] Revert "Add discount rule"
Date: Thu Jan 15 10:05:00 2026 +0600
1 file changed, 4 deletions(-)
delete mode 100644 discount.js
$ git log --oneline
5fcff4a Revert "Add discount rule"
d09aad4 Add delivery fee
f469880 Add discount rule
f8f0806 Add cart total
df9c5fe Add README
$ git show --stat --format="%h %s%n%n%b" HEAD
5fcff4a Revert "Add discount rule"
This reverts commit f469880657c8f5c5040e8ba79bec5a9bd6d72a98.
discount.js | 4 ----
1 file changed, 4 deletions(-)History-তে কিছুই মোছেনি: f469880 যেমন ছিল, তেমনই আছে, আর ওপরে নতুন 5fcff4a. বার্তায় Git নিজেই লিখে দিয়েছে কোন commit উল্টানো হলো — পরে কেউ খুঁজলে পুরো গল্প পাবেন। --no-edit না দিলে Git বার্তা লেখার জন্য editor খোলে; ভালো চর্চা হলো সেখানে কেন উল্টানো হলো তা এক লাইনে লেখা।
খেয়াল করুন, আমরা HEAD~1 উল্টিয়েছি, অর্থাৎ শেষটির আগের commit. Revert যেকোনো পুরোনো commit-এ চলে, যদি পরের commit-গুলো একই লাইন না ছুঁয়ে থাকে। ছুঁয়ে থাকলে conflict হতে পারে, যা merge-এর conflict-এর মতো করেই মেটাতে হয় (পর্ব ১৩)। কয়েকটি commit একসাথে উল্টাতে git revert --no-commit A..B, তারপর একটি commit.
Merge commit উল্টানো — -m লাগে
একটি merge commit-এর দুটি parent থাকে। “উল্টো পরিবর্তন” মানে কোন parent-এর তুলনায়? Git নিজে ঠিক করে না, আপনাকে বলতে হয়। একটি আলাদা ছোট repository-তে:
$ git log --oneline --graph
* 075c171 Merge branch 'feature'
|\
| * e82c547 Add feature
* | 0f0d1e0 Main work
|/
* e3e3874 Base
$ git revert HEAD
error: commit 075c1716991ddc948cbac2499e86b0d425698972 is a merge but no -m option was given.
fatal: revert failed
$ git revert -m 1 --no-edit HEAD
[main 897a62d] Revert "Merge branch 'feature'"
Date: Thu Jan 15 10:14:00 2026 +0600
1 file changed, 1 deletion(-)
delete mode 100644 f.txt-m 1 মানে প্রথম parent (merge-এর সময় যে branch-এ ছিলেন, এখানে main) হলো মূল ধারা; feature থেকে যা এসেছিল, শুধু সেটুকু উল্টানো হবে। একটি সূক্ষ্ম ফাঁদ: এরপর একই feature branch আবার merge করলে Git মনে করে ওই commit-গুলো আগেই মেলানো হয়েছে, তাই পরিবর্তনগুলো আর ফেরে না। তখন revert-টিকেই revert করতে হয়। Git-এর নিজস্ব documentation-এ এ নিয়ে একটি পুরো লেখা আছে (“How to revert a faulty merge”); merge-এর ভেতরের যুক্তি পর্ব ৫৪-এ।
Reset + force push বনাম revert — দুই clone-এ কী ঘটে
এবার মডেলটি দুই মানুষের। Origin-এ A-B-C, আর C-তে একটি bug. করিম C pull করে তার ওপর D (search ফিচার) বানিয়েছেন, এখনো push করেননি। রহিম দুইভাবে bug সরাতে পারেন। প্রথম পথে তিনি reset --hard B করে একটি নতুন fix E বানিয়ে force push করেন। দ্বিতীয় পথে তিনি git revert C দিয়ে R বানিয়ে সাধারণ push করেন। তারপর করিম pull করেন (merge):
// Shared history: A-B-C is on origin; C added a bug. Karim already has C and adds D.
const C = {}; // id -> { parents, files: Set }
function commit(id, parents, files) { C[id] = { parents, files: new Set(files) }; return id; }
function ancestors(id, seen = new Set()) {
if (!seen.has(id)) { seen.add(id); for (const p of C[id].parents) ancestors(p, seen); } return seen;
}
function mergeBase(a, b) { // nearest common ancestor (enough for this small graph)
const A = ancestors(a); let best = null;
for (const x of ancestors(b)) if (A.has(x) && (!best || ancestors(x).has(best))) best = x;
return best;
}
function merge(id, ours, theirs) { // three-way merge, file by file
const base = C[mergeBase(ours, theirs)].files, o = C[ours].files, t = C[theirs].files;
const all = new Set([...base, ...o, ...t]), out = [];
for (const f of all) { const keep = o.has(f) === base.has(f) ? t.has(f) : o.has(f); if (keep) out.push(f); }
return commit(id, [ours, theirs], out.sort());
}
const log = (name, id) => console.log(name.padEnd(9), ("{" + [...ancestors(id)].sort().join(",") + "}").padEnd(16),
"files:", [...C[id].files].join(", "));
commit("A", [], ["cart"]); commit("B", ["A"], ["cart", "fee"]); commit("C", ["B"], ["cart", "fee", "BUG"]);
commit("D", ["C"], ["cart", "fee", "BUG", "search"]); // Karim's local work on top of C
console.log("--- way 1: Rahim runs reset --hard B, adds fix E, push --force");
commit("E", ["B"], ["cart", "fee", "coupon"]); // origin/main is now E
console.log("Karim ahead/behind:", [...ancestors("D")].filter(x => !ancestors("E").has(x)).length,
"/", [...ancestors("E")].filter(x => !ancestors("D").has(x)).length);
log("pull->M1", merge("M1", "D", "E"));
console.log("--- way 2: Rahim runs git revert C (new commit R), normal push");
commit("R", ["C"], ["cart", "fee"]); // origin/main is now R
console.log("Karim ahead/behind:", [...ancestors("D")].filter(x => !ancestors("R").has(x)).length,
"/", [...ancestors("R")].filter(x => !ancestors("D").has(x)).length);
log("pull->M2", merge("M2", "D", "R"));আউটপুট:
--- way 1: Rahim runs reset --hard B, adds fix E, push --force
Karim ahead/behind: 2 / 1
pull->M1 {A,B,C,D,E,M1} files: BUG, cart, coupon, fee, search
--- way 2: Rahim runs git revert C (new commit R), normal push
Karim ahead/behind: 1 / 1
pull->M2 {A,B,C,D,M2,R} files: cart, fee, search
প্রথম পথ: force push-এর পর origin-এ C নেই, কিন্তু করিমের কাছে আছে। তাঁর branch ২টি commit এগিয়ে (C, D), ১টি পিছিয়ে (E). Pull-এর merge-এ base হলো B; করিমের দিকে BUG “নতুন যোগ” দেখায়, তাই merge সেটি রেখে দেয়। ফল: files-এ আবার BUG, আর history-তে C ফিরে এসেছে। পরের push-এ bug আবার origin-এ। দ্বিতীয় পথ: R-এর parent C, তাই করিম শুধু ১ এগিয়ে, ১ পিছিয়ে। Merge-এর base C; R BUG সরিয়েছে আর করিম সেটি ছোঁননি, তাই ফলাফলে BUG নেই, করিমের search আছে। History-তে C আর R দুটিই — সৎ ইতিহাস। (মডেলটি ফাইলের নাম দিয়ে merge করে; আসল Git লাইন ধরে মেলায়, পর্ব ১৩।)
“Public history নতুন করে লিখবেন না” — এই নিয়মটি Git-এর শুরুর দিন থেকেই আলোচিত। Linus Torvalds Linux kernel-এর mailing list-এ বহুবার লিখেছেন যে অন্যরা যে history-র ওপর কাজ করছেন, তা rebase বা reset করে বদলানো উচিত নয়; নিজের এখনো-প্রকাশ-না-করা কাজ যত খুশি গুছিয়ে নিন। একারণেই বহু দলের hosting platform-এ main-এর মতো branch-এ force push বন্ধ করে রাখা হয় (“protected branch”, platform-ভেদে নাম আলাদা)। Branching strategy-তে এ নিয়ম কীভাবে বসে, তা পর্ব ১৭-এ।
৬ · git clean — untracked ফাইল পরিষ্কার
restore আর reset শুধু tracked ফাইল নিয়ে কাজ করে। Build-এর পর জমে থাকা debug.log বা tmp/ ফোল্ডারের মতো যেসব ফাইল Git কখনো track করেনি (untracked), সেগুলো তারা ছোঁয় না। এগুলো সরাতে git clean. নিয়ম একটাই: আগে -n (dry run), তারপর -f.
$ git status --short
?? debug.log
?? tmp/
$ git clean -n
Would remove debug.log
$ git clean -nd
Would remove debug.log
Would remove tmp/
$ git clean -fd
Removing debug.log
Removing tmp/
$ git status --short| Flag | অর্থ |
|---|---|
-n | শুধু দেখায় কী মুছবে, কিছুই মোছে না — সবসময় আগে এটি |
-f | সত্যিই মোছা। ডিফল্ট সেটিং (clean.requireForce) এটি ছাড়া clean চলতে দেয় না |
-d | Untracked ফোল্ডারও ধরে; না দিলে শুধু ফাইল (ওপরে -n-এ tmp/ আসেনি) |
-x | .gitignore-এ লেখা ফাইলও মোছে — যেমন node_modules/, .env. খুব সাবধানে |
-i | প্রতিটি ফাইল ধরে ধরে জিজ্ঞেস করে |
Untracked ফাইল Git-এর কাছে কখনো ছিল না, তাই git clean-এ মোছা ফাইল reflog বা অন্য কোনো Git command দিয়ে ফেরে না — সাধারণত recycle bin-এও যায় না। -x বিশেষ বিপজ্জনক: .env-এর মতো ignore করা ফাইলে প্রায়ই এমন গোপন তথ্য থাকে যা repository-তে কখনো রাখা হয়নি। কোন ফাইল ignore হবে, তা পর্ব ১০-এ দেখেছেন।
৭ · Reflog — Git-এর নিরাপত্তা জাল
অংশ ৪-এর শেষে আমরা git reset --hard HEAD~1 দিয়ে d09aad4-কে log থেকে “হারিয়ে” ফেলেছিলাম। এখন কি তাকে ফেরানো যায়? যায়। Git প্রতিবার HEAD বা কোনো branch সরালে একটি local খাতায় লিখে রাখে: আগে কোথায় ছিল, এখন কোথায়, কোন command-এর কারণে। এই খাতার নাম reflog. মুদি দোকানের হিসাবের খাতার মতো — দিনের প্রতিটি লেনদেন ক্রমানুসারে লেখা থাকে, ভুল লেনদেনও।
$ git reflog
f469880 HEAD@{0}: reset: moving to HEAD~1
d09aad4 HEAD@{1}: reset: moving to ORIG_HEAD
f469880 HEAD@{2}: reset: moving to HEAD~1
d09aad4 HEAD@{3}: reset: moving to ORIG_HEAD
f469880 HEAD@{4}: reset: moving to HEAD~1
d09aad4 HEAD@{5}: commit (amend): Add delivery fee
97611f9 HEAD@{6}: commit: Add delivry fee
f469880 HEAD@{7}: commit: Add discount rule
f8f0806 HEAD@{8}: commit: Add cart total
df9c5fe HEAD@{9}: commit (initial): Add README
$ git branch rescue d09aad4
$ git log --oneline -1 rescue
d09aad4 Add delivery fee
$ git reset --hard HEAD@{1}
HEAD is now at d09aad4 Add delivery fee
$ git log --oneline
d09aad4 Add delivery fee
f469880 Add discount rule
f8f0806 Add cart total
df9c5fe Add README
$ git branch -d rescue
Deleted branch rescue (was d09aad4).ওপর থেকে নিচে নতুন থেকে পুরোনো। HEAD@{0} এখনকার অবস্থান, HEAD@{1} তার ঠিক আগে HEAD যেখানে ছিল, এভাবে। অংশ ৪-এর soft আর mixed পরীক্ষার পর ORIG_HEAD দিয়ে ফেরার চলাচলও লেখা আছে — reflog কিছুই বাদ দেয় না। নিচে 97611f9-ও আছে, amend-এর আগের সেই ভুল-বানানের commit. উদ্ধারের দুটি পথ দেখানো হয়েছে: git branch rescue d09aad4 একটি নতুন branch বসিয়ে commit-টিকে নিরাপদ করে, কিছুই না বদলে; তারপর git reset --hard HEAD@{1} main-কে সেখানে ফেরায়। Working tree-তে অসংরক্ষিত কাজ থাকলে --hard-এর বদলে আগে branch বসানোই নিরাপদ।
| main | |
| git log-এ d09aad4 | |
| fee.json ফোল্ডারে | |
| HEAD@{1} | |
| rescue branch |
- সব ঠিক আছে। main
d09aad4-এ,fee.jsonফোল্ডারে। - ভুল:
git reset --hard HEAD~1. mainf469880-এ নামল,fee.jsonফোল্ডার থেকে উধাও,git log-এd09aad4নেই। আতঙ্কের মুহূর্ত — কিন্তু commit-টি object store-এ এখনো আছে। git reflog. খাতার দ্বিতীয় লাইনেd09aad4 HEAD@{1}: “এক চলাচল আগে HEAD এখানে ছিল”। বার্তা দেখে নিশ্চিত হোন এটিই সেই commit. আর কোনো command চালানোর আগে hash-টি লিখে রাখুন, কারণ পরের প্রতিটি চলাচলেHEAD@{1}-এর অর্থ বদলায়।git branch rescue d09aad4. আগে একটি branch বসিয়ে commit-টিকে “reachable” করলেন। এখন garbage collection-ও তাকে ছোঁবে না, আর আপনার working tree অক্ষত।git reset --hard HEAD@{1}. main ফিরলd09aad4-এ,fee.jsonফোল্ডারে ফিরল। Reflog-এ নতুন একটি লাইন যোগ হলো, তাই এখনHEAD@{1}মানেf469880. কাজ শেষেrescueমুছে দিতে পারেন।
নিচের মডেলে একটি “ব্যস্ত, একটু অসাবধান দিন” তৈরি হয় seed-দেওয়া এলোমেলো সংখ্যা দিয়ে (PRNG mulberry32, তাই প্রতিবার একই ফল): প্রতি ধাপে হয় নতুন commit, নয়তো একটি reset --hard HEAD~1. তারপর প্রোগ্রাম খোঁজে কোন commit reflog-এ আছে কিন্তু main থেকে আর পৌঁছানো যায় না, আর সেগুলো ফেরায়। Hash এখানে FNV-1a, Git-এর SHA-1-এর বদলে একটি ছোট হিসাব:
// Reflog: every move of HEAD is written down, so a "lost" commit can be found again.
function mulberry32(a) { return () => { a = (a + 0x6D2B79F5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; }
function fnv1a(s) { let h = 0x811c9dc5; for (const ch of s) { h ^= ch.charCodeAt(0); h = Math.imul(h, 0x01000193); }
return (h >>> 0).toString(16).padStart(8, "0").slice(0, 7); } // stands in for Git's SHA-1
const parentOf = {}, reflog = []; // reflog[0] is the newest entry, like HEAD@{0}
let head = null, n = 0;
function move(to, why) { head = to; reflog.unshift({ id: to, why }); }
function commit(msg) { const id = fnv1a(msg + (head || "") + n++); parentOf[id] = head; move(id, "commit: " + msg); }
const rnd = mulberry32(15);
commit("Add README");
for (let i = 1; i <= 7; i++) { // a busy, slightly careless day
if (rnd() < 0.6 || !parentOf[head]) commit("work " + i);
else move(parentOf[head], "reset: moving to HEAD~1"); // like git reset --hard HEAD~1
}
reflog.forEach((e, i) => console.log(`${e.id} HEAD@{${i}}: ${e.why}`));
const reachable = new Set(); for (let c = head; c; c = parentOf[c]) reachable.add(c);
const lost = [...new Set(reflog.map(e => e.id))].filter(id => !reachable.has(id));
console.log("on main now :", [...reachable].reverse().join(" <- "));
console.log("only in reflog (lost):", lost.join(", ") || "none");
const tip = lost.find(id => !lost.some(x => parentOf[x] === id)); // lost commit with no lost child
const pick = reflog.findIndex(e => e.id === tip);
move(tip, `reset: moving to HEAD@{${pick}}`); // git reset --hard HEAD@{n}
const back = []; for (let c = head; c; c = parentOf[c]) back.push(c);
console.log(`git reset --hard HEAD@{${pick}} -> HEAD = ${head}`);
console.log("still lost:", lost.filter(id => !back.includes(id)).join(", ") || "none");আউটপুট:
345a3eb HEAD@{0}: reset: moving to HEAD~1
f0bbec3 HEAD@{1}: reset: moving to HEAD~1
68f8092 HEAD@{2}: commit: work 5
f0bbec3 HEAD@{3}: commit: work 4
345a3eb HEAD@{4}: commit: work 3
4286e30 HEAD@{5}: commit: work 2
04f1121 HEAD@{6}: commit: work 1
69c1b2e HEAD@{7}: commit: Add README
on main now : 69c1b2e <- 04f1121 <- 4286e30 <- 345a3eb
only in reflog (lost): f0bbec3, 68f8092
git reset --hard HEAD@{2} -> HEAD = 68f8092
still lost: none
দিনের শেষে পরপর দুটি reset main-কে 345a3eb-এ নামিয়েছে; f0bbec3 (work 4) আর 68f8092 (work 5) এখন শুধু reflog-এ। দুটি হারানো commit-এর মধ্যে কোনটিতে ফিরবেন? যেটির কোনো হারানো সন্তান নেই — 68f8092, কারণ তার parent f0bbec3. সেখানে ফিরলে দুটিই আবার main থেকে পৌঁছানো যায়, তাই “still lost: none”. লক্ষ করুন, উদ্ধারের সংখ্যা HEAD@{2}, HEAD@{1} নয় — reflog পড়ে বুঝে নম্বর বাছতে হয়, অনুমানে নয়। seed বদলে চালিয়ে দেখুন অন্য দিনে কী হয়।
যা ফেরানো যায় না
Reflog শুধু commit-এর হিসাব রাখে। নিচের জিনিসগুলো সে বাঁচাতে পারে না:
- যে পরিবর্তন কখনো
addবাcommitহয়নি, আরgit restore,git checkout -- fবাreset --hard-এ মুছে গেছে। (আপনার editor-এর নিজস্ব “local history” থাকলে সেখানে ভাগ্য পরীক্ষা করতে পারেন — সেটি Git নয়।) git clean-এ মোছা untracked ফাইল।- অন্য যন্ত্রের reflog: reflog local, push বা clone হয় না। Server-এ কেউ force push করলে আপনার যন্ত্রের reflog শুধু আপনার নিজের চলাচল জানে।
- অনেক পুরোনো entry: ডিফল্টভাবে reflog-এর entry ৯০ দিন, আর কোনো branch থেকে পৌঁছানো যায় না এমন commit-এর entry ৩০ দিন পর garbage collection-এ মুছতে পারে (
gc.reflogExpire,gc.reflogExpireUnreachable).
একটি মাঝামাঝি ঘটনা: যা add করেছিলেন কিন্তু commit করেননি, তার লেখা একটি blob হিসেবে object store-এ কিছুদিন থেকে যায়, git fsck --lost-found দিয়ে খোঁজা যায়। Ref, reflog আর garbage collection-এর পুরো গল্প পর্ব ৫৩-এ।
৮ · শেয়ার করা branch-এর সোনালি নিয়ম
পুরো পর্বকে একটি সারণিতে আনা যায়: কোন command কোন এলাকা বদলায়, আর কোনটি history নতুন করে লেখে।
| Command | Branch | Index | Working tree | History বদলায়? |
|---|---|---|---|---|
restore f | — | — | index থেকে ⚠ | না |
restore --staged f | — | HEAD থেকে | — | না |
commit --amend | নতুন commit-এ | — | — | হ্যাঁ (শেষ commit) |
reset --soft X | X-এ | — | — | হ্যাঁ |
reset X (mixed) | X-এ | X থেকে | — | হ্যাঁ |
reset --hard X | X-এ | X থেকে | X থেকে ⚠ | হ্যাঁ |
revert X | নতুন commit যোগ | নতুন commit-এর মতো | নতুন commit-এর মতো | না — শুধু যোগ করে |
clean -f | — | — | untracked মোছে ⚠ | না |
যা অন্যদের কাছে গেছে, তাতে শুধু নতুন commit যোগ করুন।
- Push-করা commit-এ
--amend,resetবা rebase নয়। উল্টাতে হলেgit revert. - Shared branch-এ force push নয়। নিজের feature branch-এ একান্ত দরকারে
git push --force-with-lease— এটি শেষ fetch-এর পর অন্য কেউ push করে থাকলে থেমে যায়, সাধারণ--forceথামে না (remote-এর বিস্তারিত পর্ব ১৪-এ)। - বিপজ্জনক command-এর আগে
git statusআরgit stashবা একটি backup branch.git branch backup-before-resetলিখতে দুই সেকেন্ড লাগে। git clean-এ সবসময় আগে-n.- কিছু হারালে থামুন। আর command না চালিয়ে
git reflogপড়ুন, hash লিখে রাখুন, branch বসান। - Secret ভুলে push হয়ে গেলে revert যথেষ্ট নয়। সেটি history-তে থেকে যায়; আগে secret বাতিল করে নতুন বানান, তারপর history পরিষ্কারের কথা ভাবুন (পর্ব ৫৭)।
পুরোনো local commit গুছিয়ে নেওয়ার আরও সূক্ষ্ম হাতিয়ার আছে — কয়েকটি commit মেলানো, ক্রম বদলানো, মাঝের একটির বার্তা বদলানো। সেটি interactive rebase, পর্ব ১৫-এ। আর push হওয়ার আগে কাজ গুছিয়ে pull request খোলার ধারা পর্ব ১৬-এ।
আরও গভীরে: reset-এর আসল সংজ্ঞা Git-এর ভেতরের তিনটি “tree”-র ভাষায় — HEAD যে commit-এর tree দেখায়, index-এর flat তালিকা আর working directory. Scott Chacon ও Ben Straub-এর বিনামূল্যের বই Pro Git (২য় সংস্করণ, ২০১৪)-এর “Reset Demystified” অধ্যায়টি ঠিক এই তিন tree দিয়ে reset ব্যাখ্যা করে। Reflog আসলে .git/logs/ ফোল্ডারের সাধারণ text ফাইল; একটি commit “হারায়” তখনই, যখন কোনো ref বা reflog entry তার দিকে নির্দেশ করে না আর git gc তাকে prune করে।
পরীক্ষা ও ইন্টারভিউতে:
git resetআরgit revert-এর পার্থক্য? — reset branch-কে পেছনে সরিয়ে history বদলায়; revert একটি নতুন উল্টো commit যোগ করে, history অটুট রাখে।--soft,--mixed,--hard? — যথাক্রমে শুধু branch; branch ও index; branch, index ও working tree.- ভুল
reset --hard-এর পর commit ফেরাবেন কীভাবে? —git reflog-এ hash খুঁজেgit branch rescue <hash>বাgit reset --hard HEAD@{n}. - Unstage করবেন কীভাবে? —
git restore --staged f(পুরোনো রূপgit reset HEAD f). - Amend-এর পর commit ID কেন বদলায়? — ID বিষয়বস্তু, parent, সময় আর বার্তার hash; amend একটি নতুন commit বানায়।
৯ · AI-এর যুগে — undo-র পরামর্শ যাচাই
“Git-এ গোলমাল করে ফেলেছি, কীভাবে ঠিক করব?” — এই প্রশ্নে AI প্রায়ই সবচেয়ে ছোট উত্তরটি দেয়: git reset --hard origin/main বা git push --force. দুটিই কাজ করে, আর দুটিই বিনা সতর্কতায় কাজ মুছে দিতে পারে। AI আপনার working tree দেখতে পায় না, জানে না কোন branch শেয়ার করা, কে কী pull করেছেন। Undo-র ক্ষেত্রে AI-এর সাধারণ ভুলগুলো:
| AI যা বলতে পারে | সমস্যা | যা করবেন |
|---|---|---|
git reset --hard, কোনো সতর্কতা ছাড়া | অসংরক্ষিত কাজ চিরতরে যায় | আগে git status, দরকারে git stash বা backup branch |
Shared main-এ git push --force | সহকর্মীদের history ভাঙে, bug ফিরে আসে | git revert; নিজের branch-এ হলে --force-with-lease |
না থাকা flag, যেমন git revert --hard | Command ব্যর্থ, বা বিভ্রান্তি | git help revert বা অফিশিয়াল documentation দেখুন |
শুধু পুরোনো checkout -- | Branch আর ফাইলের কাজ গুলিয়ে যায় | restore/switch (Git ২.২৩+) |
| “reflog-এ সব আছে” | Add-না-করা কাজ reflog-এ থাকে না | বিপজ্জনক command-এর আগে commit বা stash |
git clean -fdx এক লাইনে | .env-সহ ignore করা ফাইল মোছে | আগে git clean -nd, -x শুধু জেনে-বুঝে |
AI-কে প্রশ্ন করার সময় অবস্থা পুরোটা দিন, আর বলুন প্রতিটি command কোন এলাকা বদলাবে তা ব্যাখ্যা করতে। Command নিজের আসল repository-তে চালানোর আগে একটি throwaway clone-এ (git clone bazar /tmp/bazar-test) চালিয়ে দেখুন:
AI-এর দেওয়া undo command চালানোর আগে — চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Git ২.২৩-এ কেন git checkout-কে ভেঙে switch আর restore বানানো হলো? পুরোনো command তো কাজ করছিল।
কাজ করছিল ঠিকই, কিন্তু একটি command-এর ঘাড়ে অনেকগুলো ভিন্ন কাজ ছিল। git checkout main branch বদলায়। git checkout -b new নতুন branch বানিয়ে সেখানে যায়। git checkout a1b2c3d একটি commit-এ “detached HEAD” অবস্থায় যায়। আর git checkout -- cart.js ফাইলের অসংরক্ষিত পরিবর্তন কোনো প্রশ্ন ছাড়াই মুছে দেয়। প্রথম তিনটি সাধারণত নিরাপদ — Git অসংরক্ষিত কাজ ঝুঁকিতে পড়লে থেমে যায়। চতুর্থটি ধ্বংসাত্মক। অথচ সবগুলোর নাম এক।
সমস্যাটা বাস্তব ছিল: যদি cart.js নামে একটি branch-ও থাকে? git checkout cart.js তখন branch বদলাবে, না ফাইল মুছবে? Git একটি নিয়ম মেনে সিদ্ধান্ত নেয়, আর -- চিহ্ন দিয়ে অস্পষ্টতা দূর করতে হয় — কিন্তু নতুনদের কাছে এটা ধাঁধা। বহু বছর ধরে Git-এর ব্যবহারযোগ্যতা নিয়ে সমালোচনার বড় অংশ ছিল এই command ঘিরে।
২০১৯ সালের আগস্টে প্রকাশিত Git ২.২৩-এ দুটি নতুন command আসে: git switch শুধু branch-এর কাজ করে, git restore শুধু ফাইলের। Git mailing list-এর প্রকাশ্য আলোচনা অনুযায়ী মূল কাজটি করেছিলেন দীর্ঘদিনের contributor Nguyễn Thái Ngọc Duy. শুরুতে এগুলোকে documentation-এ “experimental” বলা হয়েছিল, যাতে প্রয়োজনে আচরণ বদলানো যায়। পুরোনো checkout সরানো হয়নি — লাখো script আর অভ্যাস ভেঙে যেত বলে। Git-এর নীতি সাধারণত এমনই: পুরোনো আচরণ রেখে নতুন, পরিষ্কার পথ যোগ করা।
এখানে software engineering-এর একটি বড় শিক্ষা আছে। একটি interface-এর নকশা শুধু “কাজ করে কি না” দিয়ে বিচার হয় না; ব্যবহারকারী কত সহজে ভুল করেন, সেটিও মাপকাঠি। নিরাপদ আর ধ্বংসাত্মক কাজকে আলাদা নাম দেওয়া, ধ্বংসাত্মক কাজকে লম্বা বা স্পষ্ট flag-এর পেছনে রাখা — ভালো API নকশার সাধারণ নীতি (পর্ব ৩১-এ interface আর contract).
মূল উপলব্ধি: এক নামে দুটি ভিন্ন ঝুঁকির কাজ রাখলে ভুল হবেই। নতুন command এসেছে মানুষের ভুল কমাতে, ক্ষমতা বাড়াতে নয় — তাই নতুন রূপটিই অভ্যাস করুন, আর পুরোনোটি পড়ে চিনতে শিখুন।
প্র ০২History নতুন করে লেখা কি সবসময় খারাপ? তাহলে reset, amend আর rebase আছেই বা কেন?
না, খারাপ নয় — প্রশ্নটা কার history. নিজের যন্ত্রে, এখনো কাউকে না দেখানো commit-গুলো আপনার খসড়া খাতা। সেখানে “WIP”, “fix typo”, “আবার চেষ্টা” ধরনের দশটি commit গুছিয়ে তিনটি পরিষ্কার, যুক্তিসঙ্গত commit বানানো ভালো চর্চা। Reviewer সহজে বোঝেন, পরে git bisect দিয়ে bug খুঁজতে সুবিধা হয় (পর্ব ৫৫), আর history একটি পরিষ্কার গল্প বলে। এই কাজের জন্যই amend, reset --soft আর interactive rebase.
সমস্যা শুরু হয় যখন commit অন্যদের কাছে চলে যায়। Git-এ commit ID তার parent-এর ID-র ওপর নির্ভর করে, তাই একটি commit বদলালে তার পরের সব commit-এর ID বদলায়। আপনার সহকর্মীর কাছে পুরোনো ID-গুলো আছে, তার ওপর তিনি নতুন কাজ করেছেন। আপনি force push করলে তাঁর পরের pull-এ Git দুটি আলাদা history দেখে মেলাতে চায় — কখনো conflict, কখনো মুছে ফেলা commit-এর পুনরাগমন, যেমন ওপরের মডেলে BUG ফিরে এসেছিল। আরও খারাপ: কেউ যদি নিজের কাজ push করার আগে force push হওয়া branch-এর ওপর reset --hard origin/main করেন, তাঁর commit সেখান থেকে হারাতে পারে।
Linux kernel-এর মতো বড় project-এ এই নিয়ম লিখিত সংস্কৃতি: maintainer-রা প্রকাশিত tree rebase করেন না, আর Linus Torvalds বহুবার mailing list-এ এ নিয়ে স্পষ্ট মত দিয়েছেন। অন্যদিকে অনেক দল pull request merge করার আগে branch “squash” বা rebase করে — কারণ সেই branch তখনো শুধু একজনের কাজ। অর্থাৎ নিয়মটি একই, প্রয়োগ পরিস্থিতিভেদে।
Platform-গুলো এই নিয়ম প্রযুক্তি দিয়ে চাপিয়ে দিতে পারে: main-এ force push বন্ধ, merge শুধু pull request দিয়ে, review বাধ্যতামূলক। আর ব্যক্তিগত স্তরে --force-with-lease একটি নিরাপত্তা বেল্ট: “শেষবার আমি যা দেখেছিলাম, server-এ যদি এখনো তাই থাকে, তবেই চাপিয়ে দেওয়া হবে”।
মূল উপলব্ধি: নিজের খসড়া গুছিয়ে নিন, অন্যের হাতে যাওয়া ইতিহাসে শুধু যোগ করুন। “History বদলানো যাবে কি না” প্রযুক্তির প্রশ্ন নয়, দলের সাথে চুক্তির প্রশ্ন।
প্র ০৩“Git-এ commit করা কাজ প্রায় কখনো হারায় না” — কথাটা কতটা সত্যি, আর কোথায় এর সীমা?
কথাটা মূলত সত্যি, আর এর কারণ Git-এর নকশায়। Git-এর object store-এ একবার লেখা commit, tree বা blob কখনো “সম্পাদিত” হয় না; নতুন কিছু হলে নতুন object লেখা হয়। Branch, tag, HEAD — এগুলো শুধু নির্দেশক (ref), যারা কোনো commit-এর ID ধরে রাখে। reset, amend, rebase কোনো object মোছে না; শুধু নির্দেশক সরায়। আর প্রতিটি সরানো reflog-এ লেখা হয়।
তাহলে object কখন সত্যিই মোছে? git gc (garbage collection) যখন চলে — হাতে চালালে, বা অনেক command-এর পর Git নিজেই মাঝেমধ্যে চালায় — তখন সে দেখে কোন object কোনো ref বা reflog entry থেকে পৌঁছানো যায় না। এরকম object-ও একটি সময়সীমা পার হলে তবেই মোছে (gc.pruneExpire, ডিফল্ট প্রায় দুই সপ্তাহ)। তার ওপর reflog entry নিজেই ডিফল্টভাবে ৯০ দিন, আর unreachable commit-এর entry ৩০ দিন থাকে। অর্থাৎ ভুল reset --hard-এর পর সাধারণত কয়েক সপ্তাহ সময় পান।
সীমাগুলো কোথায়? এক, reflog local — অন্য যন্ত্রে আপনার চলাচলের কোনো খাতা নেই, আর server-এ সাধারণত reflog বন্ধ বা সীমিত থাকে (platform-ভেদে আলাদা)। নতুন clone-এ reflog খালি শুরু হয়। দুই, যে কাজ commit হয়নি, তা object store-এ ঢোকেইনি — Git-এর গ্যারান্টি সেখানে খাটে না। তিন, কেউ জেনে-বুঝে git reflog expire --expire=now --all আর git gc --prune=now চালালে সব নিরাপত্তা জাল একসাথে সরে যায়; secret মুছে ফেলার সময় ঠিক এটাই উদ্দেশ্য থাকে (পর্ব ৫৭)।
এই নকশার একটি সুন্দর দিক: undo নিরাপদ হওয়ার কারণ “পরিবর্তনযোগ্য নয় এমন data + সরানো যায় এমন নির্দেশক”। Database-এর append-only log, event sourcing বা হিসাবের খাতার double-entry — অনেক নির্ভরযোগ্য ব্যবস্থা একই কৌশলে দাঁড়িয়ে (System Design কোর্সের event sourcing দেখুন)।
মূল উপলব্ধি: Git-এর নিরাপত্তা জাল শুধু commit-এর জন্য, আর সেটিও সময়সীমিত ও local. তাই ঝুঁকির কাজের আগে ছোট একটি commit বা stash — সবচেয়ে সস্তা বীমা।
প্র ০৪Git-এ একটি সাধারণ “undo” বোতাম নেই কেন? অন্য version control system কীভাবে করে?
Word processor-এ Ctrl+Z শেষ কাজটি উল্টায়, কারণ সেখানে একটিই অবস্থা — খোলা নথি। Git-এ অবস্থা অন্তত চার জায়গায়: working tree, index, local branch আর remote. “শেষ কাজ উল্টানো” বললে প্রশ্ন ওঠে: শেষ কাজ কোনটি — শেষ add, শেষ commit, না শেষ push? আর উল্টানো মানে কি মুছে ফেলা, না উল্টো একটি commit যোগ? ভিন্ন উত্তরের ঝুঁকি ভিন্ন। তাই Git আলাদা command দেয়, প্রতিটির স্পষ্ট অর্থসহ।
অন্য ব্যবস্থাগুলোও এই দ্বিধায় পড়েছে। Subversion-এর মতো centralized ব্যবস্থায় (পর্ব ৭) commit সরাসরি server-এ যায়, তাই সেখানে commit “মোছা” সাধারণ কাজ নয়; একটি পুরোনো পরিবর্তন উল্টাতে উল্টো merge করে নতুন commit দেওয়া হয় — Git-এর revert-এর মতো। Mercurial-এ hg backout প্রায় revert-এর কাজ করে; তার পুরোনো hg rollback শেষ transaction উল্টাত, কিন্তু ঝুঁকির কারণে পরে নিরুৎসাহিত করা হয় — যতদূর জানা যায়, Mercurial-এর documentation-এ এটি deprecated বলা আছে।
Git-এর জগতে কিছু তৃতীয় পক্ষের টুল সত্যিকারের “undo” দেওয়ার চেষ্টা করেছে — reflog আর নিজস্ব হিসাব পড়ে আগের অবস্থায় ফেরা। কিছু GUI client-এও undo বোতাম আছে। কিন্তু এগুলো working tree-র অসংরক্ষিত কাজ ফেরাতে পারে না, কারণ সেই তথ্য কোথাও লেখা থাকে না।
এখানে নকশার একটি গভীর trade-off আছে। একটি জাদুর বোতাম শেখা সহজ, কিন্তু কী ঘটছে লুকিয়ে রাখে; দলের shared history-তে ভুল জাদু বড় ক্ষতি করতে পারে। আলাদা আলাদা স্পষ্ট command শেখা কঠিন, কিন্তু প্রতিটির পরিণাম আগে থেকে বলা যায়। পেশাদার টুলের নকশায় এই পূর্বানুমেয়তা প্রায়ই বেশি মূল্যবান।
মূল উপলব্ধি: “Undo” তখনই সহজ, যখন অবস্থা এক জায়গায়। Git-এ অবস্থা কয়েক স্তরে, তাই undo-ও কয়েক রকম — আর সঠিকটি বাছার চাবি হলো জানা যে ভুলটি কোন স্তরে।
প্র ০৫একটি API key ভুলে commit হয়ে push হয়ে গেছে। git revert দিলেই কি সমস্যা মিটবে?
না — আর এটাই revert-এর সীমা বোঝার সবচেয়ে ভালো উদাহরণ। Revert একটি নতুন commit যোগ করে, যেখানে key-টি ফাইল থেকে সরানো। কিন্তু পুরোনো commit history-তে অক্ষত থাকে; যে কেউ git log -p বা git show দিয়ে key-টি পড়তে পারেন। Repository public হলে ধরে নিতে হবে key-টি ইতিমধ্যে কেউ দেখে ফেলেছে — বিভিন্ন নিরাপত্তা গবেষণা ও platform-এর প্রকাশিত অভিজ্ঞতায় দেখা গেছে যে public repository-তে ফাঁস হওয়া key স্বয়ংক্রিয় টুল খুব দ্রুত খুঁজে নিতে পারে।
তাই সঠিক ক্রম হলো:
- প্রথমে key বাতিল (revoke/rotate). যে সেবার key, সেখানে গিয়ে পুরোনোটি বন্ধ করে নতুন একটি বানান। এটিই একমাত্র ধাপ যা সত্যিকারের ক্ষতি থামায়; history পরিষ্কার করার আগেই করুন।
- লগ দেখা: সেবার access log-এ সন্দেহজনক ব্যবহার হয়েছে কি না দেখুন।
- ভবিষ্যৎ ঠেকানো: key environment variable বা secret manager-এ রাখুন, ফাইলটি
.gitignore-এ যোগ করুন (পর্ব ১০), আর commit-এর আগে secret খোঁজার টুল বা hook চালান। - History পরিষ্কার (যদি দরকার হয়):
git filter-repo-এর মতো টুল দিয়ে পুরো history থেকে ফাইলটি সরানো যায়, কিন্তু এটি সব commit ID বদলায়, সব সহকর্মীকে নতুন করে clone করতে হয়, আর hosting platform-এর cache বা fork-এ কপি থেকে যেতে পারে। পদ্ধতিটি পর্ব ৫৭-এ।
এখানে undo-র দুই ধারণা মুখোমুখি। Revert “কার্যকারিতা” উল্টায় — পরের build-এ key আর থাকে না। কিন্তু নিরাপত্তার প্রশ্ন “কে জানে” — আর তথ্য একবার বেরিয়ে গেলে কোনো Git command সেটি ফেরাতে পারে না।
মূল উপলব্ধি: কোড উল্টানো যায়, প্রকাশিত তথ্য ফেরানো যায় না। Secret ফাঁস হলে Git-এর আগে secret নিজেই বদলান; history পরিষ্কার দ্বিতীয় ধাপ, প্রথম নয়।
অনুশীলন
আউটপুট অনুমান, পরিস্থিতি বিচার, command-এর ক্রম আর নিয়মের সমালোচনা — প্রতিটির পূর্ণ উত্তর দেওয়া আছে। নিজে চালিয়ে দেখতে চাইলে একটি scratch ফোল্ডার ব্যবহার করুন।
-
তিন এলাকার অবস্থা অনুমান করুন: একটি repository-তে commit
C1→C2→C3; প্রতিটিতেcart.js-এর রূপ যথাক্রমে v1, v2, v3. mainC3-এ। আপনি ফাইলটি বদলে v4 করেgit addদিলেন, তারপর আবার বদলে v5 করলেন (add করেননি)। এবার পরপর তিনটি command: (ক)git reset --soft HEAD~1(খ)git restore --staged cart.js(গ)git reset --hard. প্রতিটির পর main, index আর working tree-তে কী থাকবে? শেষে v3, v4 আর v5-এর কোনগুলো ফেরানো সম্ভব, আর কীভাবে?(ক) main =
C2, index = v4, working tree = v5. Soft শুধু branch সরায়।git status: staged পরিবর্তন (v2 → v4) আর unstaged পরিবর্তন (v4 → v5).(খ) main =
C2, index = v2 (এখনকার HEAD থেকে), working tree = v5. শুধু unstaged পরিবর্তন (v2 → v5).(গ) commit না দিলে
git reset --hardমানেgit reset --hard HEAD: main =C2, index = v2, working tree = v2. সব পরিষ্কার।কী ফেরে: v3 ফেরে — সেটি
C3-এ commit করা, reflog-এC3আছে:git reset --hard HEAD@{1}-এর মতো কিছু (নম্বর reflog দেখে), বাgit branch rescue <C3-এর hash>. v4 সম্ভবত ফেরে, তবে কষ্টে — add করার সময় তার blob object store-এ লেখা হয়েছিল, কোনো commit নেই, তাই reflog-এ নেই;git fsck --lost-foundদিয়ে dangling blob খুঁজে পড়া যায়, যতদিন gc মুছে না দেয় (পর্ব ৫৩)। v5 ফেরে না — কখনো add বা commit হয়নি, Git সেটি দেখেইনি। এই অনুশীলনের ধাপ (ক) আর (খ) অংশ ৪-এর JavaScript মডেলে একটু বদলে মিলিয়ে নিতে পারেন। -
কোন command, আর কেন: প্রতিটি পরিস্থিতিতে সবচেয়ে উপযুক্ত command আর এক লাইনে কারণ লিখুন। (ক)
git add .দিয়ে ভুলেsecrets.txt-ও stage করেছেন; commit করেননি। (খ) এইমাত্র commit করেছেন, push করেননি; বার্তায় issue নম্বর লিখতে ভুলেছেন। (গ) শেষ চারটি commit local, push হয়নি; সেগুলো মিলিয়ে একটি commit বানাতে চান। (ঘ) দুই দিন আগে main-এ push হওয়া একটি commit production-এ bug ঢুকিয়েছে; তার পরে আরও ছয়টি commit এসেছে। (ঙ) একটি পরীক্ষামূলক কাজে শুধুconfig.jsএলোমেলো হয়েছে, অন্য ফাইলের কাজ রাখতে চান।(ক)
git restore --staged secrets.txt— শুধু index থেকে নামে, ফাইল থাকে। তারপর ফাইলটি.gitignore-এ যোগ করুন, যাতে আবার না আসে।(খ)
git commit --amend(editor-এ বার্তা ঠিক করুন, বা-m) — শেষ commit, push হয়নি, তাই history বদলানো নিরাপদ।(গ)
git reset --soft HEAD~4, তারপরgit commit— চারটির সব পরিবর্তন staged থাকে, একটি commit-এ ঢোকে। (একই কাজ interactive rebase-এর squash দিয়েও হয়, পর্ব ১৫।)(ঘ)
git revert <সেই commit>— শেয়ার করা history; পরের ছয়টি commit আর অন্যদের clone অক্ষত থাকে। Conflict হলে মিটিয়ে commit করুন, আর বার্তায় কারণ লিখুন।(ঙ)
git restore config.js— শুধু সেই ফাইল index-এর অবস্থায় ফেরে, বাকি ফাইল অক্ষত।reset --hardএখানে ভুল, কারণ সেটি সব ফাইলের কাজ মুছত। চালানোর আগেgit diff config.jsদেখে নিন যে সত্যিই কিছু রাখার মতো নেই। -
উদ্ধারের command-ক্রম: একজন সহকর্মী ক্লান্ত হয়ে feature branch-এ
git reset --hard HEAD~3দিয়ে ফেলেছেন। তিনটি commit-ই local ছিল, push হয়নি। Working tree-তে তখন কোনো অসংরক্ষিত কাজ ছিল না। তাঁকে ধাপে ধাপে কী কী command দিতে বলবেন, আর প্রতিটির পর কী দেখে নিশ্চিত হবেন? কোন ভুল এড়াতে বলবেন?- থামা। আর কোনো commit, reset বা checkout নয় — প্রতিটি নতুন চলাচল reflog-এর নম্বর বদলায়।
git reflog -5— প্রথম লাইন হবেreset: moving to HEAD~3, তার নিচের লাইনে (HEAD@{1}) reset-এর আগের commit. বার্তা দেখে নিশ্চিত হোন এটি তিন commit-এর শেষটি। Hash লিখে রাখুন।git branch rescue <hash>— commit-টিকে একটি নামে বাঁধা; এখন আর কিছু হারানোর ভয় নেই।git log --oneline -4 rescue— তিনটি হারানো commit আর তার নিচে এখনকার HEAD দেখা যাবে। মিলে গেলে ঠিক জায়গা।git reset --hard rescue— branch আবার আগের জায়গায়। (Working tree পরিষ্কার ছিল বলেই--hardনিরাপদ।) চাইলেORIG_HEAD-ও কাজ করত, কিন্তু reflog দেখে hash দিয়ে কাজ করা বেশি নিশ্চিত।git log --oneline -4আরgit statusদিয়ে শেষ যাচাই, তারপরgit branch -d rescue.
যে ভুল এড়াবেন: অনুমানে
HEAD@{3}লেখা (“তিনটি commit গেছে তাই ৩”) — reflog-এর নম্বর চলাচল গোনে, commit নয়; এখানে ঠিক সংখ্যা ১। আর কোনো অবস্থাতেই এই সময়েgit gc --prune=nowনয়। -
দলের নিয়মের সমালোচনা: একটি কাল্পনিক বিশ্ববিদ্যালয়ের ফলাফল পোর্টালের দল নিচের নিয়মগুলো লিখেছে। প্রতিটির সমস্যা কী, আর কীভাবে লিখলে ভালো হতো? (১) “main-এ ভুল commit গেলে যিনি দিয়েছেন তিনি
reset --hardকরেpush --forceদেবেন, যাতে history পরিষ্কার থাকে।” (২) “Commit-এর বার্তা ভুল হলে push-এর পরেও amend করা যাবে।” (৩) “প্রতি শুক্রবারgit clean -fdxচালিয়ে সবাই ফোল্ডার পরিষ্কার রাখবেন।” (৪) “Reflog আছে, তাই backup branch লাগবে না।”(১) Shared history নতুন করে লেখা — যাঁরা ইতিমধ্যে pull করেছেন, তাঁদের clone থেকে ভুল commit আবার ফিরতে পারে, বা তাঁদের কাজ হারাতে পারে। ভালো রূপ: “main-এ ভুল commit গেলে
git revertদিয়ে একটি নতুন commit, বার্তায় কারণসহ। main-এ force push platform-এ বন্ধ থাকবে।”(২) Amend নতুন commit ID বানায় — push-এর পর করলে আবার force push লাগে, একই সমস্যা। ভালো রূপ: “Push-এর আগে amend চলবে; push-এর পর বার্তার ছোট ভুল রেখে দেওয়া হবে, বা দরকারে একটি নতুন commit.”
(৩)
-xignore করা ফাইলও মোছে —.env, local config, ডাউনলোড করা ডেটা; সেগুলো কোথাও ফেরে না। আর-nছাড়া চালানো অন্ধ মোছা। ভালো রূপ: “দরকার হলেgit clean -ndদেখে তারপরgit clean -fd;-xশুধু build ফোল্ডারের জন্য, জেনে-বুঝে।”(৪) Reflog শুধু commit বাঁচায়, শুধু local, আর সময়সীমিত; অসংরক্ষিত কাজ বা অন্য যন্ত্রের অবস্থা জানে না। ভালো রূপ: “ঝুঁকির কাজের (reset, rebase, history পরিষ্কার) আগে
git status, আর একটি backup branch বা stash.”সাধারণ শিক্ষা: ভালো দলীয় নিয়ম “কী করবেন” বলে, কারণ লেখে, আর platform-এর সেটিং দিয়ে যতটা সম্ভব স্বয়ংক্রিয়ভাবে চাপিয়ে দেয় — মানুষের মনে রাখার ওপর ছেড়ে দেয় না।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমভুল git reset --hard-এর পর একটি local commit উদ্ধারের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- নতুন কোনো command না চালিয়ে
git reflogপড়া - বার্তা দেখে হারানো commit-এর hash চিহ্নিত করা
git branch rescue <hash>দিয়ে commit-টিকে নামে বাঁধাgit log --oneline rescueদিয়ে যাচাইgit reset --hard rescueদিয়ে branch ফেরানো
একটি branch-এ শেষ commit C3, তার parent C2. Working tree পরিষ্কার। git reset --soft HEAD~1 চালানোর পর কী হবে?
- C3 মুছে যাবে আর ফাইল C2-এর মতো হয়ে যাবে
- Branch C2-তে যাবে, C3-এর পরিবর্তনগুলো staged অবস্থায় থাকবে
- Branch C2-তে যাবে, পরিবর্তনগুলো unstaged থাকবে
- একটি নতুন উল্টো commit তৈরি হবে
একটি commit দলের shared main-এ push হয়ে গেছে, আর কয়েকজন সহকর্মী তা pull করেছেন। এখন দেখা গেল commit-টিতে bug. সবচেয়ে নিরাপদ পথ কোনটি?
git reset --hard HEAD~1তারপরgit push --forcegit commit --amendদিয়ে commit ঠিক করে pushgit revert <commit>দিয়ে নতুন commit, তারপর সাধারণ pushgit clean -fd
cart.js-এ কিছু পরিবর্তন করেছেন, কখনো git add বা commit করেননি। এরপর git restore cart.js চালালেন। সেই পরিবর্তন কি ফেরানো যাবে?
- না — Git সেটি কখনো সংরক্ষণ করেনি, তাই কোনো Git command দিয়ে ফেরে না
- হ্যাঁ —
git reflogদিয়ে - হ্যাঁ —
git reset --hard ORIG_HEADদিয়ে - হ্যাঁ —
git revert HEADদিয়ে
git revert HEAD চালালে Git বলল: error: commit 075c171… is a merge but no -m option was given. এর অর্থ আর সমাধান কী?
- Merge commit কখনো revert করা যায় না, reset ছাড়া উপায় নেই
--no-editযোগ করলেই চলবে- Conflict হয়েছে, ফাইল মিটিয়ে
git revert --continue - এটি merge commit, তাই মূল ধারার parent বলে দিতে হবে — যেমন
git revert -m 1 HEAD
শেষ commit 97611f9 (push হয়নি)-এ একটি ফাইল যোগ করে git commit --amend চালালেন, নতুন ID এলো d09aad4. 97611f9-এর কী হলো?
- একই commit-টিই সম্পাদিত হয়ে নতুন নাম পেয়েছে
- এটি object store-এ থেকে গেছে, কোনো branch তার দিকে নির্দেশ করে না, reflog-এ পাওয়া যায়
d09aad4-এর parent হয়ে history-তে আছে- সঙ্গে সঙ্গে স্থায়ীভাবে মুছে গেছে
Working tree-তে untracked debug.log ফাইল আর untracked tmp/ ফোল্ডার আছে। কোন command চালালে কিছু না মুছে দেখা যাবে যে দুটিই মোছা হবে?
git clean -ngit clean -fdgit clean -ndgit reset --hard
নিচের কোন বক্তব্যটি reflog সম্পর্কে সঠিক?
- এটি local — push বা clone হয় না, আর entry একটি সময়সীমার পর মুছে যেতে পারে
- এটি working tree-র প্রতিটি অসংরক্ষিত পরিবর্তনও লিখে রাখে
- এটি server-এ রাখা থাকে, তাই সহকর্মীর হারানো commit-ও আপনার reflog-এ পাওয়া যায়
- HEAD@{3} সবসময় মানে “তিন commit আগে”
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- আগে প্রশ্ন: ভুলটি কোথায় (working tree, index, শেষ commit, পুরোনো commit) আর অন্যরা কি পেয়েছেন.
git restore fফাইল index থেকে ফেরায় — অসংরক্ষিত কাজ চিরতরে যায়;--stagedশুধু unstage করে।reset: soft = branch, mixed = branch + index (ডিফল্ট), hard = তিনটিই; কোনোটিই commit মোছে না।- Push-করা commit-এ
revert, amend বা reset নয়; merge উল্টাতে-m 1. - হারালে
git reflog→ hash →git branch rescue; কিন্তু add-না-করা কাজ আরclean-এ মোছা ফাইল ফেরে না।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১২ · Branch — pointer, HEAD, switch আর detached HEADপরবর্তী পর্বreset যে pointer সরায়, সেই branch আর HEAD-এর পুরো ছবি।
- পর্ব ০৮ · প্রথম Git repository — init, add, commit আর তিনটি এলাকাআগে পড়ুনWorking tree, index আর repository — এই পর্বের সব command-এর ভিত্তি।
- সফটওয়্যার ইঞ্জিনিয়ারিং ও Git · কোর্স হোম৭০টি পর্বপুরো কোর্সের রোডম্যাপ; reflog-এর গভীরে পর্ব ৫৩, interactive rebase পর্ব ১৫।
- System Design · Deployment আর releaseপাশের ট্র্যাকProduction-এ ভুল release ফেরানো — rollback, roll-forward আর feature flag; revert-এর বড় ভাই।
- Operating Systems · Directory, link আর mountingপাশের ট্র্যাকফাইল মুছলে OS-এ কী ঘটে — কেন
git clean-এ মোছা ফাইল সাধারণত ফেরে না।