git rebase: কীভাবে কাজ করে, ইন্টার্যাকটিভ মোড ও কমিট squash
git rebase আপনার ব্রাঞ্চের কমিটগুলো নতুন ভিত্তিতে সরায়: Git সাময়িকভাবে কমিটগুলো সরিয়ে রাখে, ব্রাঞ্চকে নতুন শেষ কমিটে নেয় এবং সেগুলো নতুন কমিট হিসেবে আবার প্রয়োগ করে। ফলে merge কমিট ছাড়া সরল ইতিহাস মেলে, আর git rebase -i দিয়ে কমিট squash, reword বা drop করা যায়; শুধু যেসব কমিট এখনো অন্যদের কাছে প্রকাশিত নয়, সেগুলো পুনর্লিখতে হয়।
git rebase কী করে
git rebase আপনার ব্রাঞ্চকে ইতিহাসের অন্য একটি বিন্দুতে "স্থানান্তর" করে। ধরুন, এক সপ্তাহ আগে main থেকে feature আলাদা করেছেন, এবং তারপর main এগিয়ে গেছে। rebase-এর পর আপনার ব্রাঞ্চ এমনভাবে দেখাবে, যেন আপনি এইমাত্র সর্বশেষ main কমিট থেকে কাজ শুরু করেছেন।
git switch feature
git fetch origin
git rebase origin/main
ভেতরে তিনটি ধাপ ঘটে। Git ব্রাঞ্চ দুটির সাধারণ পূর্বপুরুষ খোঁজে, তারপর আপনার কমিটগুলোর পরিবর্তন একটি একটি করে সরিয়ে রাখে, ব্রাঞ্চকে origin/main-এ নেয়, এবং সেই পরিবর্তনগুলো আবার প্রয়োগ করে। ফল: একই বিষয়বস্তু ও বার্তার নতুন কমিট, কিন্তু ভিন্ন হ্যাশসহ।
আগে: A---B---C---F---G main
\
D---E feature
পরে: A---B---C---F---G main
\
D'---E' feature
D' ও E'-এর চিহ্নটি সাজসজ্জা নয়। এগুলো আলাদা অবজেক্ট, আলাদা হ্যাশ, ভিন্ন প্যারেন্ট। এখান থেকেই rebase-এর মূল নিয়মটি আসে, যা নিচে আছে।
সোনার নিয়ম: প্রকাশিত কমিট পুনর্লিখবেন না
rebase ইতিহাস লেখে। যতক্ষণ কমিট শুধু আপনার কাছে, ততক্ষণ নিরাপদ। কিন্তু সহকর্মীরা ব্রাঞ্চটি নামিয়ে ফেললে rebase-এর পর তাঁদের কাছে পুরনো D ও E থাকবে, আপনার কাছে নতুন D' ও E'। পরের pull-এ ইতিহাস দ্বিগুণ হবে, এবং কনফ্লিক্ট আবার মেটাতে হবে।
ব্যবহারিক নিয়ম: rebase শুধু নিজের লোকাল ব্রাঞ্চ ও ব্যক্তিগত feature-ব্রাঞ্চে করুন, যেখানে একজনই কাজ করেন। শেয়ার্ড ব্রাঞ্চ — main, develop, রিলিজ ব্রাঞ্চ — কখনো rebase করা হয় না।
rebase-এর পর ব্রাঞ্চ পাঠানো
ব্রাঞ্চ আগেই সার্ভারে থাকলে সাধারণ git push প্রত্যাখ্যাত হবে: স্থানীয় ইতিহাস আর রিমোটের ধারাবাহিকতা নয়। প্রয়োজন নিরাপদ force push:
git push --force-with-lease
--force-এর বিপরীতে --force-with-lease ওভাররাইট করতে অস্বীকৃতি জানাবে, যদি সার্ভারে এমন কমিট এসে থাকে যা আপনি দেখেননি। ফলে ভুল করে অন্যের কাজ মুছবেন না।
rebase-এ কনফ্লিক্ট
কমিটগুলো একটি একটি করে প্রয়োগ হয়, তাই কনফ্লিক্ট যেকোনো কমিটে আসতে পারে, এমনকি একের পর এক কয়েকবার। Git থেমে যায় এবং জানায় কোন কমিট প্রয়োগ করা যায়নি।
$ git rebase origin/main
Auto-merging src/api.js
CONFLICT (content): Merge conflict in src/api.js
error: could not apply 4e1f9a2... অনুরোধের ক্যাশ যোগ করা
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
করণীয়:
- কনফ্লিক্টের ফাইল খুলুন, সঠিক অংশ বেছে নিন এবং
<<<<<<<,=======,>>>>>>>মার্কার মুছুন। - ফাইলটিকে সমাধান হিসেবে চিহ্নিত করুন:
git add src/api.js। এখানে কমিট করতে হয় না। - এগিয়ে যান:
git rebase --continue। Git পরের কমিট প্রয়োগ করবে।
প্রক্রিয়া নিয়ন্ত্রণের অন্য কমান্ডগুলো:
| কমান্ড | কী করে |
|---|---|
git rebase --continue | কনফ্লিক্ট মেটানোর পর এগিয়ে যাওয়া |
git rebase --skip | বর্তমান কমিট পুরোটা বাদ (এর পরিবর্তন হারাবে) |
git rebase --abort | rebase বাতিল করে ব্রাঞ্চকে শুরুর অবস্থায় ফেরানো |
"আমাদের" ও "তাদের" দিক নিয়ে বিভ্রান্তি সাধারণ। rebase-এর সময় HEAD হলো ভিত্তি, যার ওপর আপনি স্থানান্তর করছেন, আর "আসছে" পরিবর্তন আপনার নিজের কমিট। তাই rebase-এ git checkout --ours নেয় main-এর সংস্করণ, আর --theirs নেয় আপনার সংস্করণ। merge-এ সবকিছু উল্টো।
বারবার আসা কনফ্লিক্ট: rerere
দীর্ঘ ব্রাঞ্চ বারবার rebase করলে একই কনফ্লিক্ট বারবার মেটাতে হয়। git config --global rerere.enabled true দিলে Git মনে রাখে আপনি কীভাবে মিটিয়েছিলেন, এবং পরের বার নিজেই প্রয়োগ করে।
ইন্টার্যাকটিভ rebase: প্রকাশের আগে ইতিহাস গোছানো
rebase-এর সবচেয়ে মূল্যবান অংশ ইন্টার্যাকটিভ মোড। এতে রিভিউয়ের আগে নিজের কমিট গুছানো যায়: "fix", "fix typo", "আরও ঠিক" একটি অর্থবহ কমিটে জোড়া দেওয়া, বার্তা লেখা, ক্রম বদলানো, বা অপ্রয়োজনীয় কিছু বাদ দেওয়া।
git rebase -i HEAD~4 # শেষ ৪টি কমিট
git rebase -i origin/main # ব্রাঞ্চের সেইসব কমিট, যা main-এ নেই
Git একটি এডিটর খোলে, যেখানে কমিটের তালিকা থাকে পুরনো থেকে নতুনের দিকে (git log-এর উল্টো):
pick 1a2b3c4 লগইন ফর্ম যোগ করা
pick 5d6e7f8 fix
pick 9a0b1c2 ইমেইল যাচাই
pick 3d4e5f6 যাচাইয়ের বানান ভুল
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash" but keep only the previous commit's log message
# d, drop = remove commit
লাইনের শুরুর শব্দটি বদলান, সেভ করে এডিটর বন্ধ করুন, এবং Git পরিকল্পনাটি ওপর থেকে নিচে চালাবে।
| কমান্ড | প্রভাব |
|---|---|
pick | কমিট যেমন আছে তেমন রাখা |
reword | পরিবর্তন রেখে শুধু বার্তা বদলানো |
edit | কমিটে থেমে বিষয়বস্তু বদলানো (git commit --amend) বা কয়েকটিতে ভাঙা |
squash | আগের কমিটে মিশিয়ে দেওয়া, বার্তা জুড়ে সম্পাদনা করা |
fixup | আগের কমিটে মেশানো, এই কমিটের বার্তা বাদ |
drop | কমিট পরিবর্তনসহ মুছে ফেলা |
লাইনের ক্রম বদলালে কমিটের ক্রমও বদলায়, এবং লাইন মুছলে তা drop-এর সমান।
Git squash: কয়েকটি কমিট একটিতে
Git-এ আলাদা git squash কমান্ড নেই। কমিট squash করার তিনটি উপায় আছে, তার মধ্যে ইন্টার্যাকটিভ rebase সবচেয়ে নমনীয়।
উপায় ১: ইন্টার্যাকটিভ rebase
উপরের উদাহরণে চারটি কমিট দুটি পরিষ্কার কমিটে পরিণত করা যাক:
pick 1a2b3c4 লগইন ফর্ম যোগ করা
fixup 5d6e7f8 fix
pick 9a0b1c2 ইমেইল যাচাই
fixup 3d4e5f6 যাচাইয়ের বানান ভুল
প্রতিটি দলের প্রথম কমিট pick থাকতে হবে: squash ও fixup কমিটটিকে আগের কমিটে মেশায়, আর সবচেয়ে প্রথমটির আগে আর কিছু নেই।
উপায় ২: fixup কমিট ও autosquash
কোন কমিট সংশোধন করছেন তা আগে থেকে জানা থাকলে, সংশোধনটিকে আগে থেকেই চিহ্নিত করে বানান:
git commit --fixup=1a2b3c4
git rebase -i --autosquash origin/main
Git ঠিক জায়গায় fixup কমিটটি বসিয়ে fixup চিহ্ন দেয়, শুধু পরিকল্পনা সেভ করা বাকি থাকে। এই আচরণ ডিফল্ট করতে git config --global rebase.autoSquash true।
উপায় ৩: পুরো ব্রাঞ্চ একটি কমিটে
git reset --soft $(git merge-base HEAD origin/main)
git commit -m "ইমেইল যাচাইসহ ইমেইল দিয়ে লগইন"
reset --soft ব্রাঞ্চের পয়েন্টার শাখা-বিন্দুতে ফিরিয়ে নেয়, কিন্তু সব পরিবর্তন ইনডেক্সে রাখে; তারপর একটি কমিট করলেই হয়। আরেকটি উপায়: মূল ব্রাঞ্চে মেশানোর সময় git merge --squash, অথবা pull request-এর "Squash and merge" বোতাম।
rebase-এর কয়েকটি বিশেষ রূপ
git pull --rebase
সাধারণ pull সার্ভারের ব্রাঞ্চ লোকাল ব্রাঞ্চে মেশায়, এবং সহকর্মীর সঙ্গে সমান্তরালে কমিট হলে একটি merge কমিট বানায়। git pull --rebase তার বদলে আপনার লোকাল কমিটগুলো সার্ভারের নতুন কমিটের ওপর সাজায়। এটিকে ডিফল্ট করতে: git config --global pull.rebase true। এটাই rebase-এর নিরাপদ এলাকা: আপনার লোকাল কমিট কেউ এখনো দেখেনি।
--onto: ব্রাঞ্চের একটি অংশ সরানো
আপনি feature-b-কে feature-a থেকে আলাদা করেছেন, এখন চান feature-b সরাসরি main থেকে আসুক, feature-a-এর কমিট ছাড়া:
git rebase --onto main feature-a feature-b
পড়ুন এভাবে: feature-b-এর সেই কমিটগুলো নাও, যেগুলো feature-a-এ নেই, এবং সেগুলো main-এ বসাও।
--exec: প্রতিটি কমিট যাচাই
git rebase -i --exec "npm test" origin/main
প্রতিটি কমিট প্রয়োগের পর টেস্ট চলবে; ব্যর্থ হলে rebase সমস্যাযুক্ত কমিটে থামবে। যখন প্রতিটি কমিট আলাদাভাবে বিল্ড হওয়া দরকার, তখন এটি কাজের।
--update-refs: নির্ভরশীল ব্রাঞ্চের স্তূপ
আপনি যদি একে অপরের ওপর নির্ভরশীল ব্রাঞ্চের শৃঙ্খল রাখেন, তাহলে --update-refs (Git 2.38+) উপরের ব্রাঞ্চ rebase করার সময় মাঝের ব্রাঞ্চগুলোও সরায়।
rebase বাতিল করা
প্রক্রিয়া চলাকালীন: git rebase --abort। rebase শেষ হয়ে ফল পছন্দ না হলে পুরনো কমিটগুলো মুছে যায়নি: সেগুলো রেফারেন্সের লগে থাকে।
$ git reflog
e5f6a7b HEAD@{0}: rebase (finish): returning to refs/heads/feature
e5f6a7b HEAD@{1}: rebase (pick): ইমেইল যাচাই
...
c3d4e5f HEAD@{5}: commit: যাচাইয়ের বানান ভুল
$ git reset --hard HEAD@{5}
git reflog-এ rebase শুরুর আগের এন্ট্রি খুঁজে ব্রাঞ্চকে সেখানে রিসেট করুন। rebase-এর ঠিক পরেই একটি ছোট পথও আছে: git reset --hard ORIG_HEAD। Reflog লোকালি থাকে এবং সাধারণত প্রায় ৯০ দিন তথ্য রাখে, তাই অসফল rebase-এ আতঙ্কিত হওয়ার কারণ নেই।
সাধারণ ভুল
- শেয়ার্ড ব্রাঞ্চ rebase করে force push। সহকর্মীদের ইতিহাস ছড়িয়ে পড়ল। সমাধান: যার কাছে ব্রাঞ্চটি সঠিক, তার reflog থেকে ফেরানো এবং সার্ভারে প্রটেক্টেড ব্রাঞ্চে force push নিষেধ করা।
--forceবদলে--force-with-leaseনা দেওয়া এবং সহকর্মীর পাঠানো কমিট মুছে ফেলা।- পরিকল্পনার প্রথম লাইনে squash। Git জানাবে "cannot 'squash' without a previous commit"।
- অপরিষ্কার ওয়ার্কিং ফোল্ডারে rebase শুরু। Git চালু হতে দেবে না। কমিট করুন,
git stash-এ রাখুন, অথবাgit rebase --autostashব্যবহার করুন। - কনফ্লিক্ট মিটিয়ে
git commitদেওয়াgit rebase --continue-এর বদলে। এতে একটি বাড়তি কমিট হয়;--continue-এর পর তা পুনরায় ইন্টার্যাকটিভ rebase-এ সরানো যায়।
সারসংক্ষেপ
git rebase আপনার কমিটগুলো নতুন ভিত্তিতে সাজিয়ে আবার লেখে, তাই ইতিহাস সরল ও পরিষ্কার হয়। ইন্টার্যাকটিভ মোডে খসড়া কমিট কাজের ইতিহাসে পরিণত হয়: squash ও fixup জোড়া দেয়, reword বার্তা ঠিক করে, drop অপ্রয়োজনীয় কিছু মোছে। মূল নিয়ম একটাই: শুধু যা এখনো অন্যদের কাছে প্রকাশিত নয়, তা rebase করুন, এবং নিজের রিমোট ব্রাঞ্চ পাঠাতে --force-with-lease ব্যবহার করুন। rebase ও ইতিহাস পরিষ্কারের কাজ Git কোর্সে বাস্তব উদাহরণে শেখানো হয়।
সাধারণ প্রশ্ন
git rebase কি কোডের বিষয়বস্তু বদলায়?
কনফ্লিক্ট না থাকলে চূড়ান্ত ফাইলের বিষয়বস্তু merge-এর মতোই হয়, তবে কমিটগুলো নতুন হ্যাশ ও নতুন প্যারেন্টসহ লেখা হয়। কনফ্লিক্টে ভিন্ন সমাধান বাছলে মাঝের কমিটগুলো মূলের থেকে আলাদা হবে।
প্রথম কমিটে কীভাবে rebase করব?
git rebase -i --root ব্যবহার করুন: পরিকল্পনায় সব কমিট আসবে, মূলটিও। এটি বিরল দরকারে লাগে, যেমন সবচেয়ে প্রথম কমিটের বার্তা বদলাতে।
rebase কি লেখক ও তারিখ রাখে?
লেখক ও লেখার তারিখ থাকে, কিন্তু কমিটার ও কমিটের তারিখ বদলে যায়। --committer-date-is-author-date ফ্ল্যাগ কমিটের তারিখকে লেখকের তারিখের সমান রাখে।
merge কমিটও কি rebase করা যায়?
ডিফল্টে rebase ইতিহাস সরল করে এবং merge কমিট বাদ দেয়। merge-এর কাঠামো রাখতে ব্যবহার করুন git rebase --rebase-merges।
rebase এখন চলছে কি না কীভাবে বুঝব?
git status-এ দেখাবে interactive rebase in progress বা You are currently rebasing, এবং কোন কমান্ড এখন উপলব্ধ তাও বলে দেবে। অনেক শেল ও IDE-ও প্রম্পটে এটি দেখায়।
“শূন্য থেকে আত্মবিশ্বাসী ডেভেলপার পর্যন্ত গিট” কোর্সে বিষয়টি অনুশীলনসহ বিস্তারিত শেখানো হয়েছে।
কোর্সে যান