অধ্যায় 1 · পেশাদারের মতো Git ও GitHub
অ্যাডভান্সড Git: stash, rebase, cherry-pick, reflog ও ট্যাগ
- পৃষ্ঠা 1 / 22
- 23 মিনিট পড়া
বিগিনার টিউটোরিয়ালে আপনি প্রতিদিনের চক্রটা শিখেছেন: ফাইল বদলানো, add, commit, ব্রাঞ্চ, মার্জ, পুশ। কাজ এলোমেলো না হওয়া পর্যন্ত এটুকুই যথেষ্ট। কিন্তু ধরুন, একটা এক্সপেরিমেন্টের মাঝপথে হঠাৎ জরুরি ফিক্স চলে এলো। অথবা আপনার ব্রাঞ্চে "wip" নামের নয়টা কমিট জমে গেছে, আর কাল একজন সহকর্মী সেটা রিভিউ করবেন। অথবা git reset --hard চালানোর পর শেষ দুই ঘণ্টার কাজ উধাও। অথবা একটা মডেল প্রোডাকশনে চলে গেছে, কিন্তু কোন কমিট থেকে সেটা এসেছে কেউ বলতে পারছে না।
এই পাতা এমন মুহূর্তগুলোর যন্ত্রপাতির বাক্স: stash, ইন্টারেক্টিভ rebase, cherry-pick, reflog, ট্যাগ আর নিরাপদে হিস্ট্রি বদলানো। AI প্রজেক্টে হিস্ট্রি আসলে আপনার এক্সপেরিমেন্ট লগও: কোন প্রম্পট, কোন চাংক সাইজ, কোন এমবেডিং মডেলে কী ফল এসেছিল। পরিষ্কার আর বিশ্বাসযোগ্য হিস্ট্রি রাখার পরিশ্রম সার্থক। এখানের প্রতিটা কমান্ড সত্যিই চালানো হয়েছে, তাই টার্মিনালে (Windows-এ Git Bash বা WSL) সাথে সাথে চালিয়ে দেখুন।
যা শিখবেন
git stashদিয়ে (push, list, apply, pop, drop) অসমাপ্ত কাজ সরিয়ে রাখা আর ফিরিয়ে আনা।- ইন্টারেক্টিভ
rebaseদিয়ে ব্রাঞ্চ পরিষ্কার করা: কমিটের ক্রম বদলানো, squash/fixup করা, মেসেজ বদলানো (reword), আর এডিটর ছাড়াই সেটা চালানো। cherry-pickদিয়ে এক ব্রাঞ্চ থেকে আরেক ব্রাঞ্চে একটা-দুটো কমিট কপি করা।reflogদিয়ে "হারিয়ে যাওয়া" কমিট আর মুছে ফেলা ব্রাঞ্চ উদ্ধার করা।- অ্যানোটেটেড ট্যাগ দিয়ে রিলিজ চিহ্নিত করা, আর
--force-with-leaseদিয়ে পুশ করা হিস্ট্রি নিরাপদে বদলানো। - একটা সহজ নিয়মে ঠিক করা, মার্জ নাকি রিবেস।
অনুশীলনের জন্য ছোট একটা রিপোজিটরি
কিছু হিস্ট্রিসহ একটা রিপোজিটরি দরকার। এটা একটা RAG (retrieval-augmented generation) সার্ভিসের ছোট রিট্রিভাল প্রজেক্ট। এখনো কোনো রিমোট লাগছে না।
mkdir -p ~/projects/rag-notes
cd ~/projects/rag-notes
git init -q
cat > retriever.py <<'EOF'
def top_k(scores, k=3):
return sorted(scores, reverse=True)[:k]
EOF
echo "# rag-notes" > README.md
echo "embedding_model = embed-small" > config.txt
git add .
git commit -q -m "Add retriever, README and config"
echo "chunk_size = 500" >> config.txt
git commit -q -am "Set chunk size"
git log --oneline0d752d3 Set chunk size
1ab9c13 Add retriever, README and configআপনার হ্যাশগুলো এখানে ছাপা হ্যাশ থেকে আলাদা হবে, কারণ হ্যাশ নির্ভর করে লেখক, সময় আর প্যারেন্ট কমিটের ওপর। সবসময় নিজের হ্যাশ ব্যবহার করুন।
git stash: কমিট না করে কাজ সরিয়ে রাখা
আপনি এমবেডিং মডেল বদলাচ্ছেন আর একটা রিরেঙ্কার যোগ করছেন, এমন সময় কেউ README-তে এক লাইনের ফিক্স চাইলেন। আপনার পরিবর্তনগুলো কমিট করার মতো তৈরি নয়, আবার ফিক্সের মধ্যেও সেগুলো চান না। git stash পরিবর্তিত সব ট্র্যাক করা ফাইল সরিয়ে রাখে, আর আপনাকে দেয় একটা পরিষ্কার ওয়ার্কিং ট্রি।
sed -i 's/embed-small/embed-large/' config.txt
printf 'def rerank(hits):\n return hits\n' >> retriever.py
echo "scratch notes" > notes.tmp
git status --short
git stash push -m "try large embeddings"
git status --short
git stash list M config.txt
M retriever.py
?? notes.tmp
Saved working directory and index state On main: try large embeddings
?? notes.tmp
stash@{0}: On main: try large embeddingsদুটো জিনিস লক্ষ করুন। স্ট্যাশে আপনার দেওয়া মেসেজটা আছে, আর notes.tmp স্ট্যাশ হয়নি: ডিফল্টভাবে শুধু ট্র্যাক করা ফাইল সরে যায় (আনট্র্যাকড ফাইলও নিতে চাইলে -u, ইগনোর করা ফাইলও নিতে চাইলে -a)। এবার জরুরি ফিক্সটা করুন, তারপর কাজ ফিরিয়ে আনুন:
echo "Run: python retriever.py" >> README.md
git commit -q -am "Document how to run"
git stash apply -q
git stash list
git stash show
git status --shortstash@{0}: On main: try large embeddings
config.txt | 2 +-
retriever.py | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
M config.txt
M retriever.py
?? notes.tmpapply পরিবর্তনগুলো ফিরিয়ে আনে কিন্তু স্ট্যাশটা রেখে দেয়। একই পরিবর্তন দুটো ব্রাঞ্চে লাগাতে চাইলে এটা কাজে আসে। git stash pop মানে apply, তারপর drop। প্রয়োগ হয়ে গেছে, এখন স্ট্যাশটা মুছে ফেলুন:
git stash drop -q
git stash list
git status --short M config.txt
M retriever.py
?? notes.tmpখালি stash list দেখাচ্ছে কিছুই সরিয়ে রাখা নেই। এবার একটা আনট্র্যাকড ফাইলসহ একই কাজ, আর pop। প্রথম status কিছুই ছাপে না (ট্রি পরিষ্কার, এমনকি notes.tmpও সরে গেছে); দ্বিতীয়টায় সবকিছু ফিরে এসেছে:
git stash push -u -q -m "embedding experiment with notes"
git status --short
git stash pop -q
git status --short M config.txt
M retriever.py
?? notes.tmp| কমান্ড | কী করে |
|---|---|
git stash push -m "text" | লেবেলসহ ট্র্যাক করা পরিবর্তন স্ট্যাশ করে (লেবেল ছাড়া শুধু git stashও চলে) |
git stash push -u | আনট্র্যাকড ফাইলসহ; -a দিলে ইগনোর করা ফাইলসহ |
git stash push -- path | শুধু দেওয়া ফাইলগুলো স্ট্যাশ করে |
git stash list | সব স্ট্যাশ দেখায়, নতুনটা আগে, stash@{0} নামে |
git stash show -p stash@{1} | একটা স্ট্যাশের ভেতরটা ডিফ আকারে দেখায় |
git stash apply [stash@{n}] | আবার প্রয়োগ করে, স্ট্যাশ রেখে দেয় |
git stash pop | সবচেয়ে নতুন স্ট্যাশ প্রয়োগ করে মুছে দেয় (কনফ্লিক্ট হলে স্ট্যাশ থেকে যায়) |
git stash drop / git stash clear | একটা স্ট্যাশ / সব স্ট্যাশ মুছে ফেলে |
git stash branch new-name | স্ট্যাশ করার সময়কার কমিট থেকে নতুন ব্রাঞ্চ বানিয়ে সেখানে প্রয়োগ করে (pop-এ কনফ্লিক্ট হলে কাজে লাগে) |
স্ট্যাশ ঘণ্টাখানেকের জন্য নিরাপদ জায়গা, সপ্তাহের জন্য নয়। স্ট্যাশ শুধু আপনার লোকাল রিপোজিটরিতে থাকে, কখনো পুশ হয় না, আর ভুলে যাওয়া সহজ। কাজটা গুরুত্বপূর্ণ হলে একটা ব্রাঞ্চে কমিট করে রাখুন (
git switch -c wip/embeddings); পরে সেই কমিট বদলে নেওয়া যায়।
এক্সপেরিমেন্টটা নিয়ে আমরা এখন সেটাই করব: নিজের ব্রাঞ্চে কমিট করব, পরের অংশে এটা লাগবে।
git switch -q -c feature/reranker
git add retriever.py config.txt
git commit -q -m "Add reranker stub"
git switch -q main
git branch feature/reranker
* mainইন্টারেক্টিভ রিবেস: অন্যরা দেখার আগে ব্রাঞ্চ গুছিয়ে নেওয়া
কাজের সময় কমিটগুলো ছোট আর সৎ হয়: "wip", "fix typo", "try again"। রিভিউয়ার আর ভবিষ্যতের আপনি চান একটা গল্প: প্রতিটা আইডিয়ার জন্য একটা কমিট। git rebase -i (ইন্টারেক্টিভ রিবেস) ব্রাঞ্চের সেই গল্প এডিট করতে দেয়: কমিটের ক্রম বদলানো, জোড়া দেওয়া, মেসেজ বদলানো। এটা কমিটগুলোকে একটা বেসের ওপর নতুন করে চালিয়ে নতুন কমিট বানায়, তাই হ্যাশ বদলে যায়। এ কারণেই যে কমিটের ওপর আর কেউ কাজ করেনি, শুধু সেগুলোই বদলাবেন।
রিরেঙ্কার ব্রাঞ্চে কিছু এলোমেলো কমিট যোগ করা যাক:
git switch -q feature/reranker
echo "TOP_N = 5" > rerank_config.py
git add rerank_config.py && git commit -q -m "wip"
printf 'def test_rerank():\n assert rerank([1, 2]) == [1, 2]\n' > test_rerank.py
git add test_rerank.py && git commit -q -m "Add rerank tests"
sed -i 's/rerank(hits)/rerank(hits, top_n=5)/' retriever.py
git commit -q -am "fix typo in reranker"
echo "TOP_N = 8" > rerank_config.py
git commit -q -am "wip again"
git log --oneline main..HEAD80ba815 wip again
f79fb55 fix typo in reranker
5c58e67 Add rerank tests
c81ef69 wip
f39e1d4 Add reranker stubপাঁচটা কমিট, অথচ গল্পটা আসলে "রিরেঙ্কার যোগ" আর "তার টেস্ট যোগ"। git rebase -i main চালালে Git আপনার এডিটরে একটা todo তালিকা খোলে: প্রতি কমিটের জন্য একটা লাইন, সবচেয়ে পুরনোটা আগে (git log-এর উল্টো), প্রতিটা লাইন একটা কমান্ড দিয়ে শুরু। Git যে লাইনগুলো বানায় সেগুলো নিজেই ছাপিয়ে দেখতে পারেন:
git log --reverse --format='pick %h %s' main..HEADpick f39e1d4 Add reranker stub
pick c81ef69 wip
pick 5c58e67 Add rerank tests
pick f79fb55 fix typo in reranker
pick 80ba815 wip againআসল ফাইলে কমান্ডগুলো বোঝানো কিছু কমেন্ট লাইনও থাকে। আপনি তালিকা এডিট করে সেভ করে বন্ধ করেন, আর Git উপর থেকে নিচে সেটা পালন করে। রোজকার দরকারি কমান্ডগুলো:
| কমান্ড | সংক্ষেপে | ফল |
|---|---|---|
pick | p | কমিট যেমন আছে তেমনই রাখে |
reword | r | পরিবর্তন রাখে, থেমে মেসেজ এডিট করতে দেয় |
squash | s | ওপরের কমিটের সাথে জুড়ে দেয়; এডিটরে দুটো মেসেজ একসাথে দেখায় |
fixup | f | ওপরের কমিটের সাথে জুড়ে দেয় আর এই কমিটের মেসেজ ফেলে দেয় |
edit | e | এই কমিটের পর থামে, যাতে কমিটটা বদলানো বা ভাগ করা যায় |
drop | d | কমিটটা মুছে দেয় (লাইনটা মুছলেও একই ফল) |
ক্রম বদলানো মানে লাইন সরানো। জোড়া দেওয়া মানে, যে কমিট ওপরেরটার সাথে মিশে যাবে তার লাইনে pick বদলে squash বা fixup লেখা। আসল কাজে আপনি এটা এডিটরে হাতে করবেন। এখানে প্রতিটা ধাপ যাতে হুবহু পুনরাবৃত্তি করা যায়, সেজন্য আমরা GIT_SEQUENCE_EDITOR-এর মাধ্যমে এডিটরের জায়গায় এক লাইনের একটা sed স্ক্রিপ্ট বসিয়েছি (GIT_SEQUENCE_EDITOR হলো todo তালিকার এডিটর; GIT_EDITOR হলো কমিট মেসেজের এডিটর)। macOS-এ sed -i-এর পরে একটা ফাঁকা সাফিক্স দিতে হয় (sed -i ''); Windows-এর Git Bash-এ GNU sed আছে, তাই যেমন লেখা তেমনই চলে। রিবেসের প্রগ্রেস লাইন লুকাতেই শুধু -q দিয়েছি।
ধাপ ১: ক্রম বদলানো
"fix typo" কমিটটা প্রথম রিরেঙ্কার কমিটের পাশে থাকার কথা, কিন্তু মাঝখানে একটা "wip" কমিট আর টেস্টগুলো। todo তালিকায় পাঁচটা লাইন, আর আমরা চাই ক্রম ১, ৪, ২, ৩, ৫: ২ আর ৩ নম্বর লাইন ধরে রেখে (hold) ৪ নম্বরের পরে বসাব।
GIT_SEQUENCE_EDITOR="sed -i -e '2,3{H;d}' -e '4G'" git rebase -q -i main
git log --oneline main..HEAD467cfc4 wip again
4d4839f Add rerank tests
928ba4d wip
ae6ccbd fix typo in reranker
f39e1d4 Add reranker stubধাপ ২: নয়েজ জুড়ে দেওয়া
এখন "fix typo" প্রথম কমিটের ঠিক পরেই। দুটো "wip" কমিট আর টাইপো-ফিক্সকে fixup করুন, যাতে সেগুলো ওপরের কমিটে মিশে যায়:
GIT_SEQUENCE_EDITOR="sed -i -e '/ wip/s/^pick/fixup/' -e '/ fix typo/s/^pick/fixup/'" git rebase -q -i main
git log --oneline main..HEAD67891bd Add rerank tests
5f06968 Add reranker stubধাপ ৩: মেসেজ বদলানো (reword)
প্রথম কমিটে এখন স্টাব, কনফিগ আর টাইপো-ফিক্স সব আছে, তাই পুরনো মেসেজটা ছোট হয়ে গেছে। reword দিয়ে সেটা বদলান। Git মেসেজ এডিটর খোলে; এখানে দ্বিতীয় একটা sed মেসেজটা বদলে দেয়:
GIT_SEQUENCE_EDITOR="sed -i '1s/^pick/reword/'" GIT_EDITOR="sed -i '1s/.*/Add reranker with config and top-n parameter/'" git rebase -q -i main
git log --oneline main..HEAD[detached HEAD a787fdf] Add reranker with config and top-n parameter
Date: Thu Jan 15 10:00:00 2026 +0600
3 files changed, 4 insertions(+), 1 deletion(-)
create mode 100644 rerank_config.py
f1a9944 Add rerank tests
a787fdf Add reranker with config and top-n parameterপাঁচটার বদলে দুটো পরিষ্কার কমিট। রিবেসের মাঝপথে আটকে গেলে (রিপ্লে করার সময় কনফ্লিক্ট) তিনটা বেরোনোর পথ আছে: ফাইল ঠিক করে git add দিয়ে git rebase --continue; কমিটটা বাদ দিতে git rebase --skip; অথবা git rebase --abort দিয়ে শুরুর অবস্থায় ফিরে যাওয়া। নিরাপত্তার জাল সবসময় একই: reflog-এর মেয়াদ শেষ না হওয়া পর্যন্ত কিছুই হারায় না, একটু পরেই দেখবেন।
রিবেস দিয়ে ব্রাঞ্চ আপডেটও করা যায়:
git rebase mainআপনার কমিটগুলোmain-এর নতুন মাথার ওপর আবার বসায়, ফলে হিস্ট্রি সোজা একটা রেখায় থাকে।git commit --fixup <hash>আরgit rebase -i --autosquash mainএকসাথে "fix typo" ধরনের কাজ স্বয়ংক্রিয় করে: fixup লাইনটা Git নিজেই সঠিক জায়গায় বসায়।
গুছিয়ে নেওয়া ব্রাঞ্চটা main-এ মার্জ করুন। Git চাইলে শুধু main-কে সামনে সরিয়ে দিতে পারত (fast-forward), কিন্তু --no-ff একটা মার্জ কমিট বানাতে বাধ্য করে, যা ফিচারটা কোথায় শুরু আর শেষ হলো তা মনে রাখে; GitHub-এর সবুজ "Merge" বাটনও এটাই করে।
git switch -q main
git merge -q --no-ff --no-edit feature/reranker
git log --oneline --graph* d7477e6 Merge branch 'feature/reranker'
|\
| * f1a9944 Add rerank tests
| * a787fdf Add reranker with config and top-n parameter
|/
* f9d7ed2 Document how to run
* 0d752d3 Set chunk size
* 1ab9c13 Add retriever, README and configcherry-pick: পুরো ব্রাঞ্চ নয়, একটা কমিট নেওয়া
কখনো অন্য ব্রাঞ্চের শুধু একটা কমিট দরকার হয়: একটা এক্সপেরিমেন্টের ভেতরে লুকানো বাগ-ফিক্স, কিংবা রিলিজ ব্রাঞ্চে বসাতে হবে এমন হটফিক্স। git cherry-pick একটা কমিটের পরিবর্তন কপি করে বর্তমান ব্রাঞ্চে নতুন কমিট (নতুন হ্যাশ) বানায়। এখানে একটা এক্সপেরিমেন্ট ব্রাঞ্চে BM25 বেসলাইন (কীওয়ার্ড সার্চের একটা পরিচিত স্কোরার) আর চাংক সাইজের পরিবর্তন আছে; আমরা চাই শুধু চাংক সাইজ।
git switch -q -c experiment
printf 'def bm25(query, docs):\n return docs\n' > bm25.py
git add bm25.py && git commit -q -m "Add BM25 baseline"
sed -i 's/chunk_size = 500/chunk_size = 300/' config.txt
git commit -q -am "Tune chunk size to 300"
git switch -q main
git cherry-pick -x experiment
git log --oneline -3
git show -s --format=%B HEAD[main a4e95a1] Tune chunk size to 300
Date: Thu Jan 15 10:00:00 2026 +0600
1 file changed, 1 insertion(+), 1 deletion(-)
a4e95a1 Tune chunk size to 300
d7477e6 Merge branch 'feature/reranker'
f9d7ed2 Document how to run
Tune chunk size to 300
(cherry picked from commit 54f24a418301449f518e2d1548e4d7b752961d9d)লক্ষ করুন -x যে (cherry picked from commit ...) লাইনটা যোগ করেছে: এতে কমিটটা কোথা থেকে এসেছে তা লেখা থাকে, যা পুরনো রিলিজ ব্রাঞ্চে ব্যাকপোর্ট করার সময় কাজে আসে। সাধারণ cherry-pick এমন কোনো চিহ্ন রাখে না, তাই উৎস কমিট পাবলিক হলে -x ব্যবহার করুন।
| রূপ | ব্যবহার |
|---|---|
git cherry-pick A B C | একাধিক কমিট, দেওয়া ক্রমে |
git cherry-pick A^..B | A থেকে B পর্যন্ত পুরো পরিসর, দুই প্রান্তসহ |
git cherry-pick -n A | পরিবর্তন প্রয়োগ করে কিন্তু কমিট করে না (কয়েকটা একসাথে জোড়া দিতে) |
git cherry-pick --continue / --abort | কনফ্লিক্টের পর: শেষ করা, অথবা পুরো চেষ্টাটা বাতিল করা |
git cherry-pick -m 1 <merge-commit> | মার্জ কমিটে -m দিয়ে বলতে হয় কোন প্যারেন্টটা মূল লাইন |
cherry-pick একটা পরিবর্তনকে নকল করে: একই পরিবর্তন তখন দুটো কমিট হিসেবে থাকে। পরে ব্রাঞ্চ দুটো মার্জ করলে Git সাধারণত সামলে নেয়, তবু এটাকে অভ্যাস বানাবেন না। পুরো ব্রাঞ্চ দরকার হলে মার্জ করুন।
reflog: হারিয়ে যাওয়া কমিটের আনডু বাটন
Git একটা ব্যক্তিগত ডায়েরি রাখে, যার নাম reflog: গত কয়েক সপ্তাহে এই রিপোজিটরিতে HEAD আর প্রতিটা ব্রাঞ্চ কোথায় কোথায় ছিল তার তালিকা। কোনো ব্রাঞ্চে আর নেই এমন কমিট সাথে সাথে মুছে যায় না; reflog তার হ্যাশ জানে। তাই Git ভাঙা কঠিন। চলুন ভেঙে দেখি:
git reset --hard HEAD~2
git log --oneline
git reflog -6HEAD is now at f9d7ed2 Document how to run
f9d7ed2 Document how to run
0d752d3 Set chunk size
1ab9c13 Add retriever, README and config
f9d7ed2 HEAD@{0}: reset: moving to HEAD~2
a4e95a1 HEAD@{1}: cherry-pick: Tune chunk size to 300
d7477e6 HEAD@{2}: checkout: moving from experiment to main
54f24a4 HEAD@{3}: commit: Tune chunk size to 300
a851e07 HEAD@{4}: commit: Add BM25 baseline
d7477e6 HEAD@{5}: checkout: moving from main to experimentreset চেরি-পিক করা কমিট আর রিরেঙ্কারের মার্জ দুটোই ছুড়ে ফেলেছে। লগ দেখে মন খারাপ হয়, কিন্তু reflog-এ HEAD@{1} আছে, মানে এক ধাপ আগে আমরা যেখানে ছিলাম। প্রতিটা লাইন একটা সরণ: HEAD@{n} মানে "n ধাপ আগে HEAD যেখানে ছিল"। ফিরে যান:
git reset --hard 'HEAD@{1}'
git log --oneline -4HEAD is now at a4e95a1 Tune chunk size to 300
a4e95a1 Tune chunk size to 300
d7477e6 Merge branch 'feature/reranker'
f9d7ed2 Document how to run
f1a9944 Add rerank testsসব ফিরে এসেছে। একই কৌশলে মুছে ফেলা ব্রাঞ্চও বাঁচানো যায়, যা সবচেয়ে সাধারণ আতঙ্ক:
git switch -q -c scratch-eval
echo "eval_set = qa_v1.jsonl" > eval.cfg
git add eval.cfg && git commit -q -m "Add evaluation set config"
git switch -q main
git branch -D scratch-eval
git reflog -3
git branch scratch-eval 'HEAD@{1}'
git log --oneline -1 scratch-evalDeleted branch scratch-eval (was d92bd3b).
a4e95a1 HEAD@{0}: checkout: moving from scratch-eval to main
d92bd3b HEAD@{1}: commit: Add evaluation set config
a4e95a1 HEAD@{2}: checkout: moving from main to scratch-eval
d92bd3b Add evaluation set configreflog-এর যেকোনো হ্যাশ সরাসরিও ব্যবহার করা যায়: git branch rescue 1a2b3c4 বা git cherry-pick 1a2b3c4। সীমাগুলো জানা জরুরি:
- reflog লোকাল। নতুন ক্লোনে আপনার ভুলের কোনো ইতিহাস থাকে না, আর এটা পুশও হয় না।
- এন্ট্রির মেয়াদ আছে: কোনো ব্রাঞ্চ থেকে পৌঁছানো যায় এমন কমিটের জন্য ৯০ দিন, পৌঁছানো যায় না এমনগুলোর জন্য ৩০ দিন (
gc.reflogExpire,gc.reflogExpireUnreachable)। এরপরgit gcসেগুলো মুছে দিতে পারে। - শুধু কমিট করা (আর স্ট্যাশ করা) কাজ উদ্ধার করা যায়। যে ফাইল কখনো
addবা কমিট করেননি, তারপরgit restoreবাreset --hardদিয়ে ওভাররাইট হয়েছে, সেটা চলে গেছে। তাড়াতাড়ি কমিট করুন, পরে বদলে নিলেও। - মুছে ফেলা স্ট্যাশ, বা reflog এন্ট্রি নেই এমন অনাথ অবজেক্টের জন্য
git fsck --lost-foundঝুলে থাকা কমিটগুলো তালিকাভুক্ত করে।
ট্যাগ ও রিলিজ
ব্রাঞ্চের নাম প্রতিটা কমিটের সাথে সরে যায়। ট্যাগ এমন একটা নাম যা একটা কমিটে চিরকালের জন্য বসে থাকে: "এটা ভার্সন ০.২.০" বা "সোমবার এটাই ডিপ্লয় হয়েছিল" বলার সঠিক উপায়। Git-এ ট্যাগ দুই রকম:
git tag v0.1.0 main~3
git tag -a v0.2.0 -m "Release 0.2.0: reranker and chunk size 300"
git tag
git cat-file -t v0.1.0
git cat-file -t v0.2.0
git tag -nv0.1.0
v0.2.0
commit
tag
v0.1.0 Set chunk size
v0.2.0 Release 0.2.0: reranker and chunk size 300লাইটওয়েট ট্যাগ শুধু একটা কমিটের দিকে নির্দেশ করা নাম (ধরন commit)। অ্যানোটেটেড ট্যাগ (-a) একটা আসল Git অবজেক্ট (ধরন tag), যাতে ট্যাগকারী, তারিখ আর মেসেজ থাকে এবং সেটা সাইন করা যায়। রিলিজের জন্য অ্যানোটেটেড ট্যাগ ব্যবহার করুন:
git show v0.2.0 -s
git describe
git describe --tags HEAD~1tag v0.2.0
Tagger: Nadia Rahman <nadia@example.com>
Date: Thu Jan 15 10:00:00 2026 +0600
Release 0.2.0: reranker and chunk size 300
commit a4e95a1eb6123fb38885eec926847de7b16c82db
Author: Nadia Rahman <nadia@example.com>
Date: Thu Jan 15 10:00:00 2026 +0600
Tune chunk size to 300
(cherry picked from commit 54f24a418301449f518e2d1548e4d7b752961d9d)
v0.2.0
v0.1.0-4-gd7477e6git describe একটা কমিটকে মানুষের পড়ার মতো ভার্সনে বদলে দেয়: কাছের ট্যাগ, তার পর কয়টা কমিট হয়েছে, আর ছোট হ্যাশ (ট্যাগের সাথে হুবহু মিললে শুধু ট্যাগের নাম আসে)। এটা আপনার বিল্ডে আর /health এন্ডপয়েন্টে রাখলে যেকোনো বাগ রিপোর্ট ঠিক একটা কমিট পর্যন্ত খুঁজে নেওয়া যায়। --tags ছাড়া এটা শুধু অ্যানোটেটেড ট্যাগ দেখে।
| লাইটওয়েট | অ্যানোটেটেড | |
|---|---|---|
| তৈরি | git tag v1 | git tag -a v1 -m "message" |
| কী রাখে | একটা কমিটের নাম | ট্যাগকারী, তারিখ, মেসেজ (চাইলে সিগনেচার) |
| কিসের জন্য | ব্যক্তিগত বুকমার্ক | রিলিজ, ডিপ্লয়মেন্ট, মডেল ভার্সন |
git push --follow-tags পাঠায় কি না | না | হ্যাঁ |
সাধারণ git push ট্যাগ পুশ করে না। একটা ট্যাগ পাঠাতে git push origin v0.2.0, সব ট্যাগ পাঠাতে git push --tags, আর পুশ হওয়া কমিটগুলোর দিকে নির্দেশ করা অ্যানোটেটেড ট্যাগ শুধু পাঠাতে git push --follow-tags। ভুল ট্যাগ লোকালে মুছতে git tag -d v0.2.0, রিমোটে মুছতে git push origin --delete v0.2.0; কিন্তু অন্যরা যে ট্যাগ নামিয়ে ফেলেছে তাকে স্থায়ী ধরে নেওয়াই ভালো। ট্যাগ সরানোর বদলে ঠিক করা নতুন ভার্সন রিলিজ করুন।
GitHub-এ রিলিজ হলো একটা ট্যাগের সাথে শিরোনাম, নোট আর ইচ্ছে হলে ডাউনলোডযোগ্য ফাইল। ওয়েব পেজের Releases থেকে Draft a new release দিয়ে, অথবা GitHub CLI দিয়ে এটা বানানো যায় (এই টিউটোরিয়ালে চালানো হয়নি, কারণ আসল GitHub অ্যাকাউন্ট লাগে):
gh release create v0.2.0 --title "0.2.0" --generate-notesভার্সন চলে সেমান্টিক ভার্সনিং MAJOR.MINOR.PATCH মেনে: ভাঙনধরানো পরিবর্তনে (যেমন API থেকে একটা ফিল্ড সরানো) মেজর বাড়ান, সামঞ্জস্যপূর্ণ নতুন ফিচারে (নতুন এন্ডপয়েন্ট) মাইনর, বাগ-ফিক্সে প্যাচ। মেশিন লার্নিং প্রজেক্টে রিলিজে কী আছে তাও ট্যাগ করা কাজের: কোডের ট্যাগের পাশে model-2026-01, আর DVC ব্যবহার করলে ডেটার ভার্সন (দুই পাতা পরে)।
নিরাপদে হিস্ট্রি বদলানো: force-with-lease
ওপরের সবকিছু শুধু লোকাল হিস্ট্রি বদলেছে। একটা কমিট পুশ হয়ে গেলে সেটা বদলানো মানে রিমোট ব্রাঞ্চটাকে প্রতিস্থাপন করা, আর সাধারণ পুশ তা প্রত্যাখ্যান করে। Git-এর চোখে GitHub শুধু আরেকটা রিপোজিটরি যেখানে আপনি পুশ করেন; তাই আমরা সার্ভার হিসেবে একটা লোকাল বেয়ার রিপোজিটরি ব্যবহার করছি (বেয়ার রিপোজিটরিতে ওয়ার্কিং ফাইল থাকে না, শুধু হিস্ট্রি, GitHub-এর রিপোজিটরিও ঠিক তাই)।
git init -q --bare ~/remotes/rag-notes.git
git remote add origin ~/remotes/rag-notes.git
git push -q -u origin main --tags
git switch -q -c feature/hybrid-search
echo "alpha = 0.5" > hybrid.cfg
git add hybrid.cfg && git commit -q -m "Add hybrid search weights"
echo "alpha = 0.7" > hybrid.cfg
git commit -q -am "wip"
git push -q -u origin feature/hybrid-search
git status -sb## feature/hybrid-search...origin/feature/hybrid-search
?? notes.tmpব্রাঞ্চটা এখন শেয়ার করা (Nadia পুল রিকোয়েস্টের জন্য পুশ করেছে)। রিভিউয়াররা "wip" কমিট পছন্দ করেন না, তাই সেটা জুড়ে দিন, তারপর সাধারণ পুশ চেষ্টা করুন:
GIT_SEQUENCE_EDITOR="sed -i '2s/^pick/fixup/'" git rebase -q -i main
git log --oneline main..HEAD
git push origin feature/hybrid-search 2>&1 | grep -v '^hint'e89c33e Add hybrid search weights
To /home/nadia/remotes/rag-notes.git
! [rejected] feature/hybrid-search -> feature/hybrid-search (non-fast-forward)
error: failed to push some refs to '/home/nadia/remotes/rag-notes.git'Git প্রত্যাখ্যান করেছে: রিমোট ব্রাঞ্চে দুটো কমিট, আর আপনার ব্রাঞ্চে আলাদা একটা, তাই এটা fast-forward নয়। প্রথমেই মাথায় আসা সমাধান git push --force রিমোট ব্রাঞ্চে যা-ই থাকুক তা ওভাররাইট করে। এর মধ্যে কোনো সহকর্মী পুশ করে থাকলে আপনি না জেনেই তাঁর কাজ মুছে দেবেন। --force-with-lease এর নিরাপদ জ্ঞাতি: রিমোট ব্রাঞ্চ এখনো আপনার শেষ ফেচে যেখানে দেখেছিলেন ঠিক সেখানে থাকলেই শুধু জোর খাটায়। দেখা যাক, সেটা কীভাবে সহকর্মীকে বাঁচায়। Sam (নিজের ক্লোনসহ একজন টিমমেট) একই ব্রাঞ্চে একটা রিভিউ-ফিক্স পুশ করছেন:
git clone -q ~/remotes/rag-notes.git ~/projects/rag-notes-sam
cd ~/projects/rag-notes-sam
git switch -q feature/hybrid-search
echo "# alpha is tuned on qa_v1" >> hybrid.cfg
git commit -q -am "Document alpha"
git push -q origin feature/hybrid-search
cd ~/projects/rag-notes
git push --force-with-lease origin feature/hybrid-search 2>&1To /home/nadia/remotes/rag-notes.git
! [rejected] feature/hybrid-search -> feature/hybrid-search (stale info)
error: failed to push some refs to '/home/nadia/remotes/rag-notes.git'stale info বলে প্রত্যাখ্যাত: রিমোট সম্পর্কে Nadia-র ধারণা পুরনো ছিল, তাই lease আটকে দিয়েছে। ফেচ করুন, কী আছে দেখুন, Sam-এর কমিটটা তুলে আনুন, তারপর আবার lease দিয়ে পুশ করুন:
git fetch -q
git log --oneline origin/feature/hybrid-search
git cherry-pick origin/feature/hybrid-search
git push --force-with-lease origin feature/hybrid-search 2>&10b07427 Document alpha
f065b39 wip
3155e98 Add hybrid search weights
a4e95a1 Tune chunk size to 300
d7477e6 Merge branch 'feature/reranker'
f9d7ed2 Document how to run
f1a9944 Add rerank tests
0d752d3 Set chunk size
a787fdf Add reranker with config and top-n parameter
1ab9c13 Add retriever, README and config
[feature/hybrid-search c8cee9d] Document alpha
Date: Thu Jan 15 10:00:00 2026 +0600
1 file changed, 1 insertion(+)
To /home/nadia/remotes/rag-notes.git
+ 0b07427...c8cee9d feature/hybrid-search -> feature/hybrid-search (forced update)পুশ সফল হয়েছে, আর Sam-এর কমিট টিকে গেছে। সাধারণ --force প্রথমবারেই সফল হতো এবং কমিটটা মুছে দিত। হিস্ট্রি বদলানোর হাতে-কলমে নিয়ম:
- রিভিউয়ের আগে বা সময়ে নিজের ব্রাঞ্চে নির্দ্বিধায় বদলান।
mainবা অন্যরা যে ব্রাঞ্চের ওপর কাজ করছে তা কখনো বদলাবেন না; GitHub-এmainসুরক্ষিত রাখুন, যাতে ফোর্স-পুশ প্রত্যাখ্যাত হয় (পরের পাতা)। --force-with-leaseব্যবহার করুন, কখনো খালি--forceনয়। নতুন Git-এ এর পাশে--force-if-includesও আছে, রিমোটের কমিটগুলো লোকালে মেলানো না হলে যা আটকে দেয়।- শেয়ার করা ব্রাঞ্চ বদলানোর আগে টিমকে জানান। তারা নিজেদের কপি ঠিক করবে
git fetch, তারপরgit reset --hard origin/<branch>দিয়ে (লোকাল কমিট সরিয়ে রাখার পর)। - ফাঁস হওয়া সিক্রেট লুকাতে হিস্ট্রি বদলাবেন না। পুরনো কমিট ক্লোন, ফর্ক আর ক্যাশে থেকে যায়। সিক্রেট রোটেট করুন; হিস্ট্রি ঠিকভাবে পরিষ্কারের নিয়ম পরের একটা পাতায় আছে।
মার্জ নাকি রিবেস?
দুটোই দুই হিস্ট্রি লাইনের কাজ এক করে। পার্থক্য ফলাফলের আকারে, কনটেন্টে নয়।
merge: rebase:
A---B---C main A---B---C main
\ \ \
D---E---M feature D'--E' feature
(history keeps both lines, (history is one straight line;
plus a merge commit M) D and E are new commits D' and E')| পরিস্থিতি | কী ব্যবহার করবেন | কেন |
|---|---|---|
ফিচার ব্রাঞ্চে main-এর নতুন কাজ আনা | ব্রাঞ্চ শুধু আপনার হলে রিবেস (git rebase main); নইলে main-কে ব্রাঞ্চে মার্জ | সোজা হিস্ট্রি, বাড়তি মার্জ কমিট নেই; শেয়ার করা ব্রাঞ্চ রিবেস করলে সহকর্মীদের কাজ ভাঙে |
শেষ হওয়া ফিচার main-এ আনা | মার্জ (বা GitHub-এর "squash and merge" বাটন) | মার্জ কমিট মনে রাখে কখন কী যুক্ত হলো |
| ব্রাঞ্চে এলোমেলো "wip" কমিট | ইন্টারেক্টিভ রিবেস, তারপর মার্জ | প্রতি আইডিয়ায় একটা কমিট |
| কমিট পুশ হয়ে গেছে আর অন্যরা ব্যবহার করছে | মার্জ, কখনো রিবেস নয় | রিবেস তাদের কাছে থাকা কমিটগুলোই বদলে দেয় |
| সহকর্মীদের কাজ লোকাল ব্রাঞ্চে আনা | git pull --rebase আপনার লোকাল কমিট ওপরে রাখে; সাধারণ git pull মার্জ বানায় | অকারণ মার্জ কমিট কম হয় |
| ইতিমধ্যে পাবলিক হয়ে যাওয়া পরিবর্তন ফেরানো | git revert | নতুন একটা কমিট যোগ করে; কিছু বদলায় না |
সোনালি নিয়ম: আপনার রিপোজিটরির বাইরে থাকা কমিট কখনো রিবেস করবেন না, যদি না যাদের কাছে সেগুলো আছে তারা সবাই জানে। বেশিরভাগ টিম একটা রীতি ঠিক করে (যেমন প্রতিটা পুল রিকোয়েস্টে "squash and merge") আর সেটাই মেনে চলে।
বাস্তব কাজে ব্যবহার
- এক্সপেরিমেন্ট: নতুন চাংকিং কৌশল একটা ব্রাঞ্চে চেষ্টা করুন, শুধু ইভ্যালুয়েশন স্ক্রিপ্টের ফিক্সটা cherry-pick করে
main-এ আনুন, আর প্রোডাকশনে প্যাচ দরকার হলে অর্ধেক হওয়া আইডিয়া স্ট্যাশ করে রাখুন। - রিভিউ: পুল রিকোয়েস্ট খোলার আগে নয়েজ জুড়ে দিন, যাতে রিভিউয়ার একই ফাইলের নয়টা অবস্থা না পড়ে "Add reranker" আর "Add reranker tests" পড়েন।
- রিলিজ: প্রতি ডিপ্লয়মেন্টে একটা অ্যানোটেটেড ট্যাগ, আর অ্যাপের ভেতরে
git describe, এক সেকেন্ডে বলে দেয় "কোন কোড চলছে?"। মডেল রিলিজও ট্যাগ করুন। - দুর্যোগ উদ্ধার: কমিট করা থাকলে reflog "দুই দিনের কাজ মুছে ফেলেছি" সমস্যাটাকে দুই মিনিটের কাজে বদলে দেয়।
সাধারণ ভুল
- ভুলে যাওয়া যে
stashআনট্র্যাকড ফাইল বাদ দেয়। নতুন ফাইলগুলো পড়ে থাকে, আর পরে ভাবেন সেগুলোও স্ট্যাশ হয়েছিল।git stash push -uব্যবহার করুন, আর স্ট্যাশের পরgit statusদেখে নিন। - অন্যরা ব্যবহার করছে এমন ব্রাঞ্চ রিবেস করা। সহকর্মীরা ডুপ্লিকেট কমিট আর বিভ্রান্তিকর কনফ্লিক্ট পান। শুধু নিজের অপ্রকাশিত (বা শুধু রিভিউয়ের জন্য পুশ করা) ব্রাঞ্চ রিবেস করুন।
git push --forceব্যবহার। টিমমেট যা পুশ করেছে তা নিঃশব্দে মুছে ফেলে।--force-with-leaseব্যবহার করুন, আরmain-এ কখনো ফোর্স-পুশ করবেন না।- খারাপ reset বা rebase-এর পর আতঙ্কিত হওয়া। তাড়াহুড়ো করে আরও কমান্ড চালাবেন না:
git reflogপড়ুন, ভুলের ঠিক আগের লাইনটা খুঁজুন, আরgit reset --hardদিয়ে সেখানে ফিরুন। রিবেস এখনো চলতে থাকলেgit rebase --abort। - লাইটওয়েট ট্যাগ দিয়ে রিলিজের আশা করা। সাধারণ ট্যাগে মেসেজ থাকে না, আর
--follow-tagsসেগুলো উপেক্ষা করে। রিলিজ বানানgit tag -aদিয়ে।
নিজে চেষ্টা করুন
- সহজ:
~/projects/ex-stashনামে একটা নতুন রিপোজিটরিতেprompt.txtনামে একটা কমিট করা ফাইল রাখুন। ফাইলটা বদলান আরdraft.txtনামে একটা নতুন আনট্র্যাকড ফাইল বানান। দুটোকেই "prompt draft" মেসেজসহ স্ট্যাশ করুন, ট্রি পরিষ্কার কি না দেখুন, তারপর ফিরিয়ে আনুন। - সহজ: সেই রিপোজিটরিতে মেসেজসহ
v1.0.0নামে একটা অ্যানোটেটেড ট্যাগ বানান আর তার অবজেক্টের ধরন ও মেসেজ ছাপান। - মাঝারি: সেই রিপোজিটরিতে একটা কমিটসহ
ideaব্রাঞ্চ বানান,main-এ ফিরে-Dদিয়েideaমুছুন, তারপর reflog দিয়ে ব্রাঞ্চটা ফিরিয়ে আনুন। - মাঝারি:
tidyব্রাঞ্চে "step 1", "oops", "step 2 final" নামে তিনটা কমিট করুন, তারপর (এডিটর ছাড়া) ইন্টারেক্টিভ রিবেস দিয়ে এমন করুন যাতে দুটো কমিট থাকে: "oops" মিশে যাওয়া "step 1", আর "step 2 final"। - কঠিন:
~/remotes/ex.gitনামে একটা বেয়ার রিমোট বানান,tidyপুশ করুন, তারপর এর দুটো কমিট "Add steps" মেসেজে একটা কমিটে জুড়ুন (squashআর স্ক্রিপ্ট করা মেসেজ এডিটর ব্যবহার করে) এবং--force-with-leaseদিয়ে বদলটা প্রকাশ করুন। দেখান রিমোট ব্রাঞ্চে এখন একটাই কমিট।
উত্তর
১ ও ২।
mkdir -p ~/projects/ex-stash && cd ~/projects/ex-stash
git init -q
echo "Answer briefly." > prompt.txt
git add prompt.txt && git commit -q -m "Add prompt"
echo "Answer briefly, in Bangla." > prompt.txt
echo "ideas" > draft.txt
git stash push -u -q -m "prompt draft"
git status --short
git stash list
git stash pop -q
git status --short
git tag -a v1.0.0 -m "First prompt release"
git cat-file -t v1.0.0
git tag -nstash@{0}: On main: prompt draft
M prompt.txt
?? draft.txt
tag
v1.0.0 First prompt release৩। reflog-এর "moving from idea to main" লাইনের আগের এন্ট্রি, অর্থাৎ HEAD@{1}, হলো idea-র ডগা:
git switch -q -c idea
echo "tone: friendly" > style.txt
git add style.txt && git commit -q -m "Add style"
git switch -q main
git branch -D idea
git branch idea 'HEAD@{1}'
git log --oneline -1 ideaDeleted branch idea (was 7fabfaa).
7fabfaa Add style৪। রিবেসের বেস main, আর তিনটা কমিট তার ওপরে। todo তালিকার ২ নম্বর লাইন "oops":
git switch -q -c tidy
echo 1 > steps.txt && git add steps.txt && git commit -q -m "step 1"
echo 2 > steps.txt && git commit -q -am "oops"
echo 3 > steps.txt && git commit -q -am "step 2 final"
GIT_SEQUENCE_EDITOR="sed -i '2s/^pick/fixup/'" git rebase -q -i main
git log --oneline main..HEAD0610e6d step 2 final
4d868aa step 1৫। আগে পুশ, তারপর হিস্ট্রি বদল, তারপর lease দিয়ে পুশ। squash-এর মেসেজ এডিটরের জায়গায় একটা sed, যা প্রথম লাইন বসায় আর বাকিটা মুছে দেয়:
git init -q --bare ~/remotes/ex.git
git remote add origin ~/remotes/ex.git
git push -q -u origin tidy
GIT_SEQUENCE_EDITOR="sed -i '2s/^pick/squash/'" GIT_EDITOR="sed -i -e '1s/.*/Add steps/' -e '2,\$d'" git rebase -q -i main
git push -q --force-with-lease origin tidy
git log --oneline main..origin/tidy
git --git-dir ~/remotes/ex.git log --oneline tidy[detached HEAD 6601923] Add steps
Date: Thu Jan 15 10:00:00 2026 +0600
2 files changed, 2 insertions(+), 1 deletion(-)
create mode 100644 steps.txt
6601923 Add steps
6601923 Add steps
ba5217b Add promptসারসংক্ষেপ
git stash push -u -mঅসমাপ্ত কাজ সরিয়ে রাখে;popফিরিয়ে এনে মুছে দেয়,applyফিরিয়ে এনে স্ট্যাশ রেখে দেয়। স্ট্যাশ লোকাল ও সাময়িক।git rebase -i <base>ব্রাঞ্চ নতুন করে লেখে: লাইন সরিয়ে ক্রম বদল, জোড়া দিতেfixup/squash, নাম বদলাতেreword; যেকোনো সময়--abort। স্ক্রিপ্ট করতেGIT_SEQUENCE_EDITOR।git cherry-pick -xএকটা কমিট বর্তমান ব্রাঞ্চে কপি করে।git reflogHEAD কোথায় ছিল তা রাখে, আর খারাপ reset বা মুছে ফেলা ব্রাঞ্চের পর কমিট উদ্ধার করে, মোটামুটি ৩০ থেকে ৯০ দিন, তবে শুধু কমিট করা কাজের জন্য।- অ্যানোটেটেড ট্যাগ (
git tag -a) রিলিজ চিহ্নিত করে;git describeকাছের ট্যাগ দিয়ে যেকোনো কমিটের নাম দেয়; ট্যাগ আলাদা করে পুশ করতে হয়। - শুধু নিজের ব্রাঞ্চের হিস্ট্রি বদলান,
--force-with-leaseদিয়ে প্রকাশ করুন, আর শেয়ার করা হিস্ট্রিতে মার্জ বেছে নিন।
এরপর: GitHub-এ টিম হিসেবে কাজ আপনার নিজের রিপোজিটরি থেকে শেয়ার করা রিপোজিটরিতে যায়: ফর্ক, পুল রিকোয়েস্ট, রিভিউ, ব্রাঞ্চ প্রোটেকশন আর রিলিজ।