ریبیس تعاملی: کامیتهایتان را قبل از اشتراکگذاری مرتب کنید
با git rebase -i یک ردِ شلوغ از کامیتهای نیمهکاره را به تاریخچهای تمیز و قابل بازبینی تبدیل کنید. کامیتها را ادغام، بازنویسی، جابهجا، تقسیم و حذف کنید و بگذارید کامیتهای fixup مرتبسازی را برایتان انجام دهند.
در این صفحه
کارِ واقعی شلوغ است. یک فرمِ نیمهکاره را کامیت میکنید، بعد «WIP»، بعد «fix typo»، بعد یک print برای دیباگ که یادتان رفته پاکش کنید. تا وقتی مشغول کارید اشکالی ندارد، اما این تاریخچهٔ خوبی برای تحویل به بازبین نیست، و برای پیدا کردنش شش ماه بعد در git log یا git blame از آن هم بدتر است.
ریبیس تعاملی (interactive rebase) به شما اجازه میدهد برنچ خودتان را قبل از اشتراکگذاری بازنویسی کنید: کامیتها را با هم ادغام کنید، پیامشان را از نو بنویسید، ترتیبشان را عوض کنید، آنها را تقسیم یا حذف کنید. وقتی با برنچها راحت شدید، این یکی از مفیدترین مهارتهایی است که در گیت میتوانید یاد بگیرید. اگر هنوز راحت نیستید، اول روش کاری عملی با برنچهای گیت برای مبتدیها را بخوانید.
برنچِ شلوغ#
این یک برنچ فیچر با پنج کامیت روی main است:
$ git log --oneline main..
746aec7 debug print
451c410 fix typo
63d1135 Add password validation
0ce6ff7 WIP
4430f61 Add login formmain.. یعنی «کامیتهایی از این برنچ که در main نیستند». چیزی که میخواهیم به اشتراک بگذاریم فقط دو کامیت است: Add login form (بههمراه WIP و رفع غلط تایپی که هر دو روی همان فرم بودند) و Add password validation، بدون print دیباگ.
شروع ریبیس تعاملی#
git rebase -i mainگیت ویرایشگرتان را با یک فهرست کار (todo list) باز میکند؛ برای هر کامیت یک خط:
pick 4430f61 Add login form
pick 0ce6ff7 WIP
pick 63d1135 Add password validation
pick 451c410 fix typo
pick 746aec7 debug print
# Rebase 1c893f9..746aec7 onto 1c893f9 (5 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, ...
# d, drop <commit> = remove commit
# ...فهرست کار نسبت به git log برعکس است
git log جدیدترین کامیت را اول نشان میدهد. فهرست کار قدیمیترین را اول نشان میدهد، چون گیت کامیتها را به همین ترتیب دوباره اعمال میکند. آن را مثل یک اسکریپت از بالا به پایین بخوانید.
گیت از main شروع میکند و خطها را یکییکی اجرا میکند. کلمهٔ اول هر خط را عوض کنید تا بلایی که سر آن کامیت میآید عوض شود:
| دستور | چه کاری میکند |
|---|---|
pick |
کامیت را همانطور که هست نگه میدارد |
reword |
تغییرات را نگه میدارد، اما میایستد تا پیام را ویرایش کنید |
edit |
کامیت را اعمال میکند و بعد مکث میکند تا آن را تغییر دهید (amend، تقسیم و…) |
squash |
در کامیتِ بالایی ادغام میشود و هر دو پیام را با هم ترکیب میکند |
fixup |
در کامیتِ بالایی ادغام میشود و پیامِ این کامیت را دور میریزد |
drop |
کامیت را حذف میکند (پاک کردنِ خط هم همین کار را میکند) |
exec |
در این نقطه یک دستور شل اجرا میکند و اگر شکست بخورد میایستد |
ادغام، جابهجایی و حذف#
فهرست کار را اینطور ویرایش کنید، بعد ذخیره کنید و ویرایشگر را ببندید:
pick 4430f61 Add login form
fixup 0ce6ff7 WIP
fixup 451c410 fix typo
pick 63d1135 Add password validation
drop 746aec7 debug printسه چیز را عوض کردیم: دو کامیتِ کوچک به fixup Add login form تبدیل شدند، fix typo را بالا بردیم تا درست زیرِ کامیتی قرار بگیرد که اصلاحش میکند، و print دیباگ حذف شد.
$ git rebase -i main
Successfully rebased and updated refs/heads/login-form.
$ git log --oneline main..
486c6b2 Add password validation
24c6dfe Add login formدقت کنید که هشها عوض شدند، حتی برای Add password validation که به محتوایش دست نزدیم. ریبیس هرگز کامیتها را ویرایش نمیکند: کامیتهای جدیدی با همان تغییرات میسازد. این نکته جلوتر، موقع push، مهم میشود.
squash یا fixup؟
وقتی پیامِ کامیت دوم چیزی دارد که ارزش نگهداشتن دارد از squash استفاده کنید: گیت ویرایشگر را با هر دو پیام باز میکند تا آنها را با هم ترکیب کنید. برای کامیتهای «اوه، یادم رفت» که پیامشان را به هر حال پاک میکردید، fixup را به کار ببرید.
کامیتهای fixup: در حین کار مرتب کنید#
جابهجا کردنِ دستیِ خطها خستهکننده میشود. اگر از قبل میدانید یک تغییر مال کدام کامیت است، موقع کامیت کردن به گیت بگویید:
# Spotted a bug in "Add password validation"? Fix it, then:
git commit -a --fixup=486c6b2گیت کامیتی به نام fixup! Add password validation میسازد. بعداً --autosquash همهٔ این کامیتها را زیرِ کامیتِ هدفشان میبرد و برایتان fixup علامت میزند:
$ git rebase -i --autosquash mainpick 24c6dfe Add login form
pick 486c6b2 Add password validation
fixup 4eb2adb fixup! Add password validationفقط ذخیره کنید و ببندید. برای اینکه این رفتار پیشفرض شود، git config --global rebase.autoSquash true را اجرا کنید.
تقسیم یک کامیت با edit#
گاهی یک کامیت دو کارِ بیربط انجام میدهد. آن را edit علامت بزنید تا گیت درست بعد از اعمالش مکث کند:
edit 9a1c2d3 Add login form and fix footer links
pick 3f7e8b1 Add password validationgit reset HEAD^ # undo the commit, keep its changes as unstaged edits
git add login.html
git commit -m "Add login form"
git add footer.html
git commit -m "Fix footer links"
git rebase --continue # carry on with the rest of the todo listوقتی هر دو تغییر در یک فایل هستند، git add -p اینجا به کار میآید: دربارهٔ هر تکه از تغییرات جداگانه از شما میپرسد.
وقتی تداخل پیش میآید#
جابهجا یا حذف کردن کامیتها ممکن است تداخل (conflict) ایجاد کند، دقیقاً مثل merge. گیت میایستد و میگوید کدام فایلها تداخل دارند. سه راه دارید:
# fix the files, then mark them as resolved and continue
git add login.html
git rebase --continue
git rebase --skip # drop the commit that conflicts and move on
git rebase --abort # give up: put the branch back exactly as it was--abort تور ایمنی شماست. تا وقتی ریبیس تمام نشده، هیچ چیزی واقعاً تغییر نکرده است.
تستها را روی تکتکِ کامیتها اجرا کنید
git rebase -i main --exec "npm test" (یا -x) بعد از هر کامیت یک خط exec اضافه میکند. اگر تستها هر جایی شکست بخورند، ریبیس روی همان کامیتی میایستد که آنها را خراب کرده. این یک بررسی نهاییِ عالی است که هر کامیت بهتنهایی کار میکند؛ همان چیزی که بعدها git bisect را مفید میکند.
ریبیس بدون جابهجا کردنِ برنچ#
git rebase -i main برنچ شما را روی آخرین نسخهٔ main هم میبرد. معمولاً همین را میخواهید، اما اگر main خیلی جلو رفته باشد، به تداخلهایی میخورید که هیچ ربطی به مرتبسازیِ شما ندارند. برای مرتب کردن کامیتها سرِ جایشان، با همان نقطهٔ شروع:
git rebase -i --keep-base mainنقطهٔ شروع را مستقیم هم میتوانید بدهید: git rebase -i HEAD~3 اجازه میدهد سه کامیت آخر را ویرایش کنید.
push کردنِ یک برنچِ بازنویسیشده#
چون کامیتها جدیدند، برنچی که قبل از ریبیس push کرده بودید دیگر با ریموت جور نیست و یک git push معمولی رد میشود. از این استفاده کنید:
git push --force-with-leaseبرخلاف --force، اگر از آخرین fetch شما کس دیگری روی برنچِ ریموت push کرده باشد، --force-with-lease از بازنویسی آن خودداری میکند؛ پس نمیتوانید تصادفاً کارِ همتیمیتان را پاک کنید.
قانون طلایی ریبیس
فقط کامیتهایی را بازنویسی کنید که هیچکس روی آنها کار نساخته است. مرتب کردنِ برنچ فیچرِ خودتان قبل از pull request (یا در حین آن) مشکلی ندارد. ریبیس کردنِ main، یا برنچی که همتیمیهایتان pull کردهاند، برایشان نسخههایی از کامیتهای قدیمی و یک merge دردناک باقی میگذارد.
برگرداندن یک ریبیس#
ریبیس را تمام کردهاید و پشیمانید؟ گیت جای قبلیِ برنچ شما را در ORIG_HEAD نگه میدارد:
git reset --hard ORIG_HEADاین فقط درست بعد از ریبیس کار میکند، چون دستورهای دیگر هم ORIG_HEAD را بهروز میکنند. برای عقبتر، reflog هر جایی را که برنچتان بوده به خاطر دارد (git reflog). گیت واقعاً چطور کار میکند توضیح میدهد چرا کامیتهای قدیمی هنوز آنجا هستند.
جمعبندی#
git rebase -i mainیک فهرست کار از کامیتهای برنچتان باز میکند، با قدیمیترین در ابتدا.pickرا بهreword،edit،squash،fixupیاdropتغییر دهید و برای عوض کردن ترتیب کامیتها، خطها را جابهجا کنید.git commit --fixup=<commit>بههمراه--autosquashمرتبسازی را تقریباً خودکار میکند.--abortیک ریبیسِ در جریان را متوقف میکند؛ORIG_HEADو reflog یک ریبیسِ تمامشده را برمیگردانند.- ریبیس کامیتهای جدید میسازد، پس با
--force-with-leasepush کنید، و هرگز کامیتهایی را که دیگران روی آنها کار ساختهاند ریبیس نکنید.