یکی از قدرتمندترین ابزارهایی که یک توسعه دهنده می تواند در جعبه ابزار خود داشته باشد این است git rebase. با این حال، به دلیل پیچیده بودن و سوء تفاهم بدنام است.

حقیقت این است، اگر متوجه شوید که آن چیست در حقیقت میکند، git rebase ابزاری بسیار زیبا و ساده برای دستیابی به موارد مختلف در Git است.

در پست های قبلی متوجه شدید که تفاوت های Git چیست، ادغام چیست و چگونه Git تداخل های ادغام را حل می کند. در این پست متوجه خواهید شد که Git rebase چیست، چرا با ادغام متفاوت است و چگونه با اطمینان دوباره بیایید.

نکاتی قبل از شروع

  1. من همچنین یک ویدیو ایجاد کردم که محتوای این پست را پوشش می دهد. اگر می خواهید در کنار خواندن تماشا کنید، می توانید آن را در اینجا بیابید.
  2. اگر می خواهید با مخزنی که من استفاده کردم بازی کنید و دستورات را برای خودتان امتحان کنید، می توانید مخزن را از اینجا دریافت کنید.
  3. من دارم روی کتابی درباره Git کار می کنم! آیا علاقه مند به خواندن نسخه های اولیه و ارائه بازخورد هستید؟ برای من یک ایمیل بفرستید: gitting.things@gmail.com

باشه، آماده ای؟

خلاصه کوتاه – Git Merge چیست؟ 🤔

در زیر کاپوت، git rebase و git merge چیزهای بسیار بسیار متفاوتی هستند پس چرا مردم همیشه آنها را با هم مقایسه می کنند؟

دلیل استفاده از آنهاست. هنگام کار با گیت معمولا در شاخه های مختلف کار می کنیم و تغییراتی را در آن شاخه ها معرفی می کنیم.

در یک آموزش قبلی، مثالی زدم که در آن جان و پل (از گروه بیتلز) آهنگ جدیدی را با هم می نویسند. آنها از main شاخه، و سپس هر کدام از هم جدا شدند، اشعار را اصلاح کردند و تغییرات خود را انجام دادند.

سپس، این دو می خواستند تغییرات خود را ادغام کنند، چیزی که هنگام کار با Git اغلب اتفاق می افتد.

تصویر-197
تاریخ متفاوت – paul_branch و john_branch از main (منبع: مختصر)

دو راه اصلی برای ادغام تغییرات معرفی شده در شاخه های مختلف در گیت یا به عبارت دیگر commit ها و commit history های مختلف وجود دارد. اینها ادغام و تغییر پایه هستند.

در آموزش قبلی با هم آشنا شدیم git merge به خوبی. دیدیم که هنگام انجام یک ادغام، a ایجاد می کنیم ادغام commit – که محتویات این commit ترکیبی از دو شاخه است و همچنین دارای دو والد است که در هر شاخه یکی می باشد.

بنابراین، بگویید که در شعبه هستید john_branch (با فرض تاریخچه نشان داده شده در نقاشی بالا)، و شما اجرا می کنید git merge paul_branch. شما به این حالت خواهید رسید – کجا john_branch، یک تعهد جدید با دو والدین وجود دارد. اولین مورد، commit on خواهد بود john_branch شعبه کجا HEAD قبل از انجام ادغام به آن اشاره می کرد، در این مورد – “Commit 6”. دوم تعهدی است که توسط آن اشاره شده است paul_branch، “تعهد 9”.

تصویر-196
نتیجه دویدن git merge paul_branch: یک ادغام جدید با دو والدین (منبع: مختصر)

دوباره به نمودار تاریخچه نگاه کنید: شما یک را ایجاد کردید واگرا شد تاریخ. در واقع می توانید ببینید که کجا شاخه شد و دوباره کجا ادغام شد.

بنابراین هنگام استفاده git merge، تاریخ را بازنویسی نمی کنید – بلکه یک commit به تاریخچه موجود اضافه می کنید. و به طور خاص، تعهدی که تاریخچه ای واگرا ایجاد می کند.

چطور است git rebase متفاوت از git merge? 🤔

هنگام استفاده از git rebase، اتفاق متفاوتی می افتد. 🥁

بیایید با تصویر بزرگ شروع کنیم: اگر در حال حاضر هستید paul_branch، و استفاده کنید git rebase john_branch، گیت به جد مشترک شاخه جان و شاخه پل می رود. سپس وصله‌های معرفی‌شده در commit‌های شاخه Paul را می‌گیرد و آن تغییرات را در شاخه John اعمال می‌کند.

بنابراین در اینجا، شما استفاده کنید rebase برای برداشتن تغییراتی که در یک شاخه – شاخه پل – انجام شده و آنها را در شاخه دیگری پخش کنید john_branch.

تصویر-198
نتیجه دویدن git rebase john_branch: متعهد می شود paul_branch در بالای آن “بازپخش” شدند john_branch (منبع: مختصر)

صبر کن یعنی چی؟ 🤔

اکنون این را ذره ذره بررسی می کنیم تا مطمئن شویم که به طور کامل متوجه می شوید که در زیر کاپوت چه اتفاقی می افتد.

cherry-pick به عنوان پایه ای برای Rebase

این مفید است که rebase را به عنوان عملکردی در نظر بگیرید git cherry-pick – یک فرمان یک commit می گیرد، آن را محاسبه می کند پچ این commit با محاسبه تفاوت بین commit والد و خود commit را معرفی می کند و سپس cherry-pick این تفاوت را “بازپخش” می کند.

بیایید این کار را به صورت دستی انجام دهیم.

اگر به تفاوت معرفی شده توسط «Commit 5» با اجرا نگاه کنیم git diff main <SHA_OF_COMMIT_5>:

تصویر-199
در حال دویدن git diff برای مشاهده پچ معرفی شده توسط “Commit 5” (منبع: مختصر)

(اگر می خواهید با مخزن مورد استفاده من بازی کنید و دستورات را برای خود امتحان کنید، می توانید مخزن را از اینجا دریافت کنید).

می بینید که در این کامیت، جان شروع به کار روی آهنگی به نام “Lucy in the Sky with Diamonds” کرد:

تصویر-200
خروجی از git diff – پچ معرفی شده توسط “Commit 5” (منبع: مختصر)

به عنوان یادآوری، می توانید از دستور نیز استفاده کنید git show برای دریافت همان خروجی:

git show <SHA_OF_COMMIT_5>

حالا اگر شما cherry-pick در این commit، این تغییر را به طور خاص در شاخه فعال معرفی خواهید کرد. تغییر به main اولین:

git checkout main (یا git switch main)

و فقط برای روشن شدن یک شاخه دیگر ایجاد کنید:

git checkout -b my_branch (یا git switch -c my_branch)

تصویر-201
پدید آوردن my_branch که از main (منبع: مختصر)

و cherry-pick این تعهد:

git cherry-pick <SHA_OF_COMMIT_5>
تصویر-202
استفاده کردن cherry-pick برای اعمال تغییرات معرفی شده در “Commit 5” بر روی main (منبع: مختصر)

ورود به سیستم (خروجی از git lol):

تصویر-205
خروجی از git lol (منبع: مختصر)

(git lol نام مستعاری است که من به Git اضافه کردم تا تاریخچه را به صورت گرافیکی به وضوح ببینم. میتوانید اینجا پیدایش کنید).

انگار تو هستی کپی پیست شد “تعهد 5”. به یاد داشته باشید که حتی اگر پیام commit یکسانی داشته باشد، و تغییرات یکسانی را معرفی می کند، و حتی به همان شی درختی به عنوان “Commit 5” اصلی در این مورد اشاره می کند – همچنان یک شی commit متفاوت است، همانطور که با یک ایجاد شده است. مهر زمانی متفاوت

نگاهی به تغییرات، استفاده از git show HEAD:

تصویر-204
خروجی از git show HEAD (منبع: مختصر)

آنها همان “Commit 5” هستند.

و البته، اگر به فایل نگاه کنید (مثلاً با استفاده از nano lucy_in_the_sky_with_diamonds.md)، در همان حالتی خواهد بود که پس از “Commit 5” اصلی بوده است.

سرد! 😎

خوب، اکنون می توانید شاخه جدید را حذف کنید تا هر بار در تاریخچه شما ظاهر نشود:

git checkout main
git branch -D my_branch

فراتر cherry-pick – نحوه استفاده git rebase

می توانید نگاه کنید git rebase به عنوان راهی برای انجام چندگانه cherry-pickیکی پس از دیگری – یعنی “بازپخش” چندین commit. این تنها کاری نیست که می توانید با آن انجام دهید rebase، اما نقطه شروع خوبی برای توضیح ما است.

زمان بازی است git rebase! 👏🏻👏🏻

قبلا ادغام شدی paul_branch به john_branch. چه اتفاقی می افتد اگر شما مجدداً پایه گذاری شده است paul_branch در بالای john_branch? شما یک تاریخ بسیار متفاوت دریافت خواهید کرد.

در اصل، به نظر می رسد که ما تغییرات ارائه شده در commit ها را در نظر گرفته ایم paul_branch، و آنها را دوباره پخش کرد john_branch. نتیجه یک خواهد بود خطی تاریخ.

برای درک فرآیند، نمای سطح بالا را ارائه می‌دهم و سپس در هر مرحله عمیق‌تر فرو می‌روم. فرآیند تغییر پایه یک شاخه بر روی شاخه دیگر به شرح زیر است:

  1. جد مشترک را پیدا کنید.
  2. تعهداتی را که قرار است «بازپخش» شوند، شناسایی کنید.
  3. برای هر تعهد X، محاسبه کنید diff(parent(X), X)، و آن را به عنوان یک ذخیره کنید patch(X).
  4. حرکت HEAD به پایگاه جدید
  5. پچ های تولید شده را به ترتیب روی شاخه هدف اعمال کنید. هر بار یک شی commit جدید با حالت جدید ایجاد کنید.

فرآیند ایجاد commit های جدید با همان تغییرات موجود نیز نامیده می شود “بازپخش” آن commit ها، اصطلاحی که قبلاً استفاده کرده ایم.

وقت آن است که با Rebase دست به کار شوید🙌🏻

از شعبه پل شروع کنید:

git checkout paul_branch

این تاریخ است:

تصویر-206
قبل از اجرا تاریخچه را متعهد کنید git rebase (منبع: مختصر)

و حالا به قسمت هیجان انگیزش می رسیم:

git rebase john_branch

و تاریخ را مشاهده کنید:

تصویر-207
تاریخچه پس از rebasing (منبع: مختصر)

( gg نام مستعار یک ابزار خارجی است که در ویدیو معرفی کردم).

بنابراین در حالی که با git merge شما به تاریخ اضافه کردید، با git rebase شما بازنویسی تاریخ. شما ایجاد می کنید جدید ارتکاب اشیاء علاوه بر این، نتیجه یک نمودار تاریخچه خطی است – نه یک نمودار واگرا.

تصویر-209
تاریخچه پس از rebasing (منبع: مختصر)

در اصل، ما commit هایی را که در آن بودند «کپی» کردیم paul_branch و بعد از “Commit 4” معرفی شد و آنها را در بالای آن “چسباند”. john_branch.

این فرمان “rebase” نامیده می شود، زیرا commit پایه شاخه ای را که از آن اجرا می شود تغییر می دهد. یعنی در مورد شما قبل از اجرا git rebase، پایه از paul_branch “تعهد 4” بود – زیرا این شعبه “متولد” (از main). با rebaseشما از گیت خواستید که پایگاه دیگری به آن بدهد – یعنی طوری وانمود کنید که گویی از «Commit 6» متولد شده است.

برای انجام این کار، Git آنچه قبلاً “Commit 7” بود را انتخاب کرد و تغییرات ایجاد شده در این commit را روی “Commit 6” “بازپخش” کرد و سپس یک شی commit جدید ایجاد کرد. این شی از سه جنبه با “Commit 7” اصلی متفاوت است:

  1. مهر زمانی متفاوتی دارد.
  2. این یک تعهد والدین متفاوت دارد – “Commit 6” به جای “Commit 4”.
  3. شی درختی که به آن اشاره می کند متفاوت است – زیرا تغییرات در درختی که توسط “Commit 6” به آن اشاره شده است، و نه درختی که توسط “Commit 4” به آن اشاره شده است، ارائه شده است.
پیشنهاد می‌کنیم بخوانید:  راهنمای Vue PropsVue یک چارچوب جاوا اسکریپت است که به توسعه دهندگان این امکان را می دهد تا اجزایی ایجاد کنند که برای تقسیم رابط کاربری به قطعات کوچکتر به جای ساختن کل UI در یک فایل استفاده می شود. هنگام استفاده از کامپوننت ها، ممکن است بخواهیم داده ها را از مؤلفه والد به مؤلفه فرزند منتقل کنیم...

به آخرین commit در اینجا، “Commit 9” توجه کنید. عکس فوری که نشان می دهد (یعنی درختی که به آن اشاره می کند) دقیقاً همان درختی است که با ادغام دو شاخه به دست می آورید. وضعیت فایل ها در مخزن Git شما خواهد بود همان مثل اینکه استفاده کردی git merge. این فقط تاریخ است که متفاوت است، و البته موارد commit.

اکنون می توانید به سادگی استفاده کنید:

git checkout main
git merge paul_branch

هوم …. اگر این آخرین دستور را اجرا می کردید چه اتفاقی می افتاد؟ 🤔 پس از بررسی مجدد، تاریخچه ارتکاب را در نظر بگیرید main:

تصویر-210
تاریخچه پس از rebasing و بررسی main (منبع: مختصر)

ادغام به چه معناست main و paul_branch?

در واقع، Git می تواند به سادگی یک ادغام سریع به جلو انجام دهد، زیرا تاریخچه کاملاً خطی است (اگر به یادآوری در مورد ادغام سریع به جلو نیاز دارید، این پست را بررسی کنید). در نتیجه، main و paul_branch حالا به همان commit اشاره کنید:

تصویر-211
نتیجه یک ادغام سریع به جلو (منبع: مختصر)

Rebasing پیشرفته در Git💪🏻

اکنون که اصول rebase را درک کردید، زمان آن فرا رسیده است که موارد پیشرفته تری را در نظر بگیرید، جایی که سوئیچ ها و آرگومان های اضافی برای rebase دستور به کار خواهد آمد.

در مثال قبل وقتی فقط گفتید rebase (بدون سوئیچ های اضافی)، Git تمام commit ها را از جد مشترک تا نوک شاخه فعلی دوباره پخش کرد.

اما rebase یک قدرت فوق العاده است، این یک فرمان قادر مطلق است که می تواند … خوب، تاریخ را بازنویسی کند. و اگر می‌خواهید تاریخچه را برای خود تغییر دهید، می‌تواند مفید باشد.

آخرین ادغام را با ایجاد خنثی کنید main دوباره به “Commit 4” اشاره کنید:

git reset -–hard <ORIGINAL_COMMIT 4>
تصویر-238
“واگرد” آخرین عملیات ادغام (منبع: مختصر)

و با استفاده از:

git checkout paul_branch
git reset -–hard <ORIGINAL_COMMIT 9>
تصویر-239
“لغو” عملیات rebase (منبع: مختصر)

توجه داشته باشید که دقیقاً به همان سابقه قبلی رسیده اید:

تصویر-240
تجسم تاریخچه پس از “لغو” عملیات rebase (منبع: مختصر)

باز هم، برای روشن شدن، “Commit 9” فقط زمانی که از جریان فعلی قابل دسترسی نباشد ناپدید نمی شود. HEAD. بلکه هنوز در پایگاه داده شی ذخیره می شود. و همانطور که شما استفاده کردید git reset اکنون برای تغییر HEAD برای اشاره به این commit، می توانید آن را بازیابی کنید، و همچنین والد آن نیز از آنجایی که آنها در پایگاه داده ذخیره می شوند، متعهد می شوند. خیلی باحاله، نه؟ 😎

خوب، به سرعت تغییراتی را که پل معرفی کرد مشاهده کنید:

git show HEAD
تصویر-241
git show HEAD پچ معرفی شده توسط “Commit 9” را نشان می دهد (منبع: مختصر)

در نمودار commit به عقب ادامه دهید:

git show HEAD~
تصویر-242
git show HEAD~ (مثل git show HEAD~1) پچ معرفی شده توسط “Commit 8” را نشان می دهد (منبع: مختصر)

و یک ارتکاب بیشتر:

git show HEAD~2
تصویر-243
git show HEAD~2 پچ معرفی شده توسط “Commit 7” را نشان می دهد (منبع: مختصر)

بنابراین، این تغییرات خوب هستند، اما شاید پل این نوع تاریخ را نمی خواهد. بلکه می خواهد طوری به نظر برسد که تغییرات «Commit 7» و «Commit 8» را به صورت تک کامیت معرفی کرده است.

برای آن، می توانید از یک استفاده کنید در ارتباط بودن تغییر پایه برای انجام این کار، ما را اضافه می کنیم -i (یا --interactive) به rebase دستور:

git rebase -i <SHA_OF_COMMIT_4>

یا، از آن زمان main به “Commit 4” اشاره می کند، ما به سادگی می توانیم اجرا کنیم:

git rebase -i main

با اجرای این دستور، به Git می‌گویید که از یک پایه جدید، “Commit 4” استفاده کند. بنابراین شما از Git می‌خواهید که به تمام commit‌هایی که پس از «Commit 4» معرفی شده‌اند و از زمان کنونی قابل دسترسی هستند بازگردد. HEADو آن تعهدات را دوباره پخش کنید.

برای هر commit که دوباره پخش می شود، Git از ما می پرسد که می خواهیم با آن چه کار کنیم:

تصویر-250
git rebase -i main از شما می خواهد انتخاب کنید که با هر commit چه کاری انجام دهید (منبع: مختصر)

در این زمینه مفید است که یک commit را به عنوان یک پچ در نظر بگیریم. یعنی «Commit 7» مانند «وصله‌ای که «Commit 7» در بالای والد خود معرفی کرد».

یکی از گزینه ها استفاده است pick. این رفتار پیش‌فرض است که به Git می‌گوید تغییرات ایجاد شده در این commit را دوباره پخش کند. در این مورد، اگر آن را همانطور که هست رها کنید – و pick همه commit ها – شما تاریخچه یکسانی را دریافت خواهید کرد و Git حتی اشیاء commit جدیدی ایجاد نمی کند.

گزینه دیگر این است squash. آ له شده commit محتویات آن در محتویات commit قبل از آن “تا” می شود. بنابراین در مورد ما، پل مایل است “تعهد 8” را به “تعهد 7” تبدیل کند:

تصویر-251
له کردن “Commit 8” به “Commit 7” (منبع: مختصر)

همانطور که می بینید، git rebase -i گزینه های اضافی را ارائه می دهد، اما ما در این پست به همه آنها نمی پردازیم. اگر اجازه دهید rebase اجرا شود، از شما خواسته می شود که یک پیام commit را برای commit تازه ایجاد شده انتخاب کنید (یعنی پیامی که تغییرات “Commit 7” و “Commit 8” را ایجاد کرده است):

تصویر-252
ارائه پیام commit: Commits 7+8 (منبع: مختصر)

و به تاریخچه نگاه کنید:

تصویر-253
تاریخچه پس از rebase تعاملی (منبع: مختصر)

دقیقا همانطور که ما می خواستیم! داریم paul_branch “Commit 9” (البته، این یک شی متفاوت از “Commit 9” اصلی است). این به “Commits 7+8” اشاره می کند، که یک commit واحد است که تغییرات “Commit 7” و “Commit 8” اصلی را معرفی می کند. والد این commit “Commit 4” است، که در آن main اشاره می کند به شما دارید john_branch.

تصویر-254
تاریخچه پس از تغییر مجدد تعاملی – تجسم شده (منبع: مختصر)

اوه وای، این باحال نیست؟ 😎

git rebase به شما کنترل نامحدودی بر شکل هر شاخه می دهد. می‌توانید از آن برای ترتیب دادن مجدد تعهدات یا حذف تغییرات نادرست یا اصلاح یک تغییر در نگاه به گذشته استفاده کنید. از طرف دیگر، می توانید پایه شاخه خود را به commit دیگری منتقل کنید، هر commitی که می خواهید.

نحوه استفاده از --onto سوئیچ از git rebase

بیایید یک مثال دیگر را در نظر بگیریم. به main از نو:

git checkout main

و نشانگرها را حذف کنید paul_branch و john_branch بنابراین دیگر آنها را در نمودار commit نمی بینید:

git branch -D paul_branch
git branch -D john_branch

و حالا منشعب از main به شعبه جدید:

git checkout -b new_branch
تصویر-255
پدید آوردن new_branch که از main (منبع: مختصر)
تصویر-256
یک تاریخ پاک با new_branch که از main (منبع: مختصر)

اکنون، چند تغییر را در اینجا اضافه کنید و آنها را انجام دهید:

nano code.py
تصویر-257
اضافه کردن تابع new_branch به code.py (منبع: مختصر)
git add code.py
git commit -m "Commit 10"

برگشتن به main:

git checkout main

و یک تغییر دیگر معرفی کنید:

تصویر-258
یک docstring در ابتدای فایل اضافه شد (منبع: مختصر)

زمان برای صحنه سازی و انجام این تغییرات:

git add code.py
git commit -m "Commit 11"

و یک تغییر دیگر:

تصویر-259
اضافه @Author to the docstring (منبع: مختصر)

این تغییر را نیز انجام دهید:

git add code.py
git commit -m "Commit 12"

اوه صبر کنید، اکنون متوجه شدم که می خواستم تغییراتی را که در “Commit 11” به عنوان بخشی از new_branch. اوه چه کاری می توانی انجام بدهی؟ 🤔

تاریخ را در نظر بگیرید:

تصویر-260
تاریخچه پس از معرفی “Commit 12” (منبع: مختصر)

چیزی که من می‌خواهم این است که به جای اینکه “Commit 10” فقط روی آن باشد main شاخه، من می خواهم آن را در هر دو باشد main شعبه و همچنین new_branch. از نظر بصری، می‌خواهم آن را در نمودار به پایین منتقل کنم:

تصویر-261
از نظر بصری، از شما می‌خواهم که “Commit 10” را “فشار دهید” (منبع: مختصر)

میتونی ببینی کجا دارم میرم؟ 😇

خوب، همانطور که فهمیدیم، rebase اساساً به ما اجازه می دهد بازپخش تغییرات وارد شده در new_branch، مواردی که در “ارتکاب 10” معرفی شده اند، به گونه ای که گویی در ابتدا به جای “ارتکاب 4” بر روی “ارتباط 11” انجام شده اند.

برای این کار می توانید از آرگومان های دیگر استفاده کنید git rebase. به Git می‌گویید که می‌خواهید تمام تاریخچه‌ای را که بین جد مشترک معرفی شده است، بگیرید main و new_branch، که “Commit 4” است و پایگاه جدید آن تاریخ “Commit 11” است. برای انجام این کار از:

git rebase -–onto <SHA_OF_COMMIT_11> main new_branch
تصویر-262
تاریخچه قبل و بعد از rebase، “Commit 10” “هل” شده است (منبع: مختصر)

و به تاریخ زیبای ما نگاه کنید! 😍

تصویر-263
تاریخچه قبل و بعد از rebase، “Commit 10” “هل” شده است (منبع: مختصر)

بیایید یک مورد دیگر را در نظر بگیریم.

بگو من در یک شعبه شروع به کار کردم و به اشتباه از آنجا شروع به کار کردم feature_branch_1، به جای از main.

بنابراین برای تقلید از این، ایجاد کنید feature_branch_1:

git checkout main
git checkout -b feature_branch_1

و پاک کن new_branch بنابراین دیگر آن را در نمودار نمی بینید:

git branch -D new_branch

یک فایل پایتون ساده به نام ایجاد کنید 1.py:

تصویر-264
یک فایل جدید، 1.py، با print('Hello world!') (منبع: مختصر)

این فایل را مرحله بندی و commit کنید:

git add 1.py
git commit -m  "Commit 13"

اکنون منشعب شده (به اشتباه) از feature_branch_1:

git checkout -b feature_branch_2

و یک فایل دیگر ایجاد کنید 2.py:

تصویر-265
پدید آوردن 2.py (منبع: مختصر)

این فایل را نیز استیج و commit کنید:

git add 2.py
git commit -m  "Commit 14"

و چند کد دیگر را معرفی کنید 2.py:

تصویر-266
در حال اصلاح 2.py (منبع: مختصر)

این تغییرات را نیز صحنه سازی و انجام دهید:

git add 2.py
git commit -m  "Commit 15"

تا اینجا شما باید این سابقه را داشته باشید:

تصویر-267
تاریخچه پس از معرفی “Commit 15” (منبع: مختصر)

برگشتن به feature_branch_1 و ویرایش کنید 1.py:

git checkout feature_branch_1
تصویر-268
در حال اصلاح 1.py (منبع: مختصر)

اکنون مرحله و متعهد شوید:

git add 1.py
git commit -m  "Commit 16"

تاریخچه شما باید به این صورت باشد:

تصویر-270
تاریخچه پس از معرفی “Commit 16” (منبع: مختصر)

بگو حالا فهمیدی اشتباه کردی تو واقعا می خواستی feature_branch_2 به دنیا آمدن از main شاخه، به جای از feature_branch_1.

چگونه می توانید به آن دست پیدا کنید؟ 🤔

با توجه به نمودار تاریخچه و آنچه در مورد آن آموخته اید، سعی کنید در مورد آن فکر کنید --onto پرچم برای rebase فرمان

خوب، شما می خواهید “جایگزین” والد اولین commit خود شوید feature_branch_2، که “متعهد 14” است، در بالای آن قرار گیرد main شاخه، در این مورد، “متعهد 12″، به جای آغاز feature_branch_1، در این مورد، “متعهد 13”. بنابراین دوباره، شما یک را ایجاد خواهید کرد پایگاه جدید، این بار برای اولین کامیت در feature_branch_2.

تصویر-271
شما می خواهید در اطراف “Commit 14” و “Commit 15” حرکت کنید (منبع: مختصر)

چگونه می خواهید انجام دهید؟

ابتدا به feature_branch_2:

git checkout feature_branch_2

و اکنون می توانید استفاده کنید:

git rebase -–onto main <SHA_OF_COMMIT_13>

در نتیجه شما دارید feature_branch_2 بر اساس main به جای feature_branch_1:

تصویر-272
تاریخچه ارتکاب پس از انجام rebase (منبع: مختصر)

نحو از دستور زیر است:

git rebase --onto <new_parent> <old_parent>

چگونه بر روی یک شاخه واحد مجدداً پایه گذاری کنیم

همچنین می توانید استفاده کنید git rebase در حالی که به تاریخچه یک شاخه نگاه می کنیم.

پیشنهاد می‌کنیم بخوانید:  Undo Git Add – نحوه حذف فایل های اضافه شده در Git

بیایید ببینیم می توانید در اینجا به من کمک کنید.

بگو من از آن کار کردم feature_branch_2، و به طور خاص فایل را ویرایش کرد code.py. من با تغییر دادن همه رشته‌ها به جای نقل قول‌های تکی، با دو کوتیوم پیچیده شروع کردم:

تصویر-273
در حال تغییر ' به " که در code.py (منبع: مختصر)

سپس به صحنه بردم و متعهد شدم:

git add code.py
git commit -m "Commit 17"

سپس تصمیم گرفتم یک تابع جدید در ابتدای فایل اضافه کنم:

تصویر-274
اضافه کردن تابع another_feature (منبع: مختصر)

دوباره صحنه سازی کردم و متعهد شدم:

git add code.py
git commit -m "Commit 18"

و اکنون متوجه شدم که در واقع فراموش کرده ام که نقل قول های تکی را به نقل قول های دوتایی تغییر دهم __main__ (همانطور که ممکن است متوجه شده باشید)، بنابراین من این کار را نیز انجام دادم:

تصویر-275
در حال تغییر '__main__' به "__main__" (منبع: مختصر)

البته من این تغییر را صحنه سازی و انجام دادم:

git add code.py
git commit -m "Commit 19"

حال، تاریخچه را در نظر بگیرید:

تصویر-276
تاریخچه ارتکاب پس از معرفی “Commit 19” (منبع: مختصر)

واقعاً خوب نیست، اینطور است؟ منظورم این است که من دو commit دارم که به یکدیگر مرتبط هستند، “Commit 17” و “Commit 19” (چرخش 'به "s)، اما آنها توسط “Commit 18” نامرتبط تقسیم می شوند (جایی که من یک تابع جدید اضافه کردم). چه می توانیم بکنیم؟ 🤔 میشه کمکم کنید؟

به طور مستقیم، من می خواهم تاریخچه را در اینجا ویرایش کنم:

تصویر-277
اینها commit هایی هستند که می خواهم ویرایش کنم (منبع: مختصر)

بنابراین، چه کاری انجام می دهید؟

حق با شماست! 👏🏻

من می توانم تاریخچه را از “Commit 17” به “Commit 19” در بالای “Commit 15” تغییر دهم. برای انجام آن:

git rebase --interactive --onto <SHA_OF_COMMIT_15> <SHA_OF_COMMIT_15>

توجه داشته باشید که من “Commit 15” را به عنوان ابتدای محدوده commit ها، به استثنای این commit، مشخص کردم. و نیازی به ذکر صریح نداشتم HEAD به عنوان آخرین پارامتر

تصویر-279
استفاده کردن rebase --onto در یک شاخه واحد (منبع: مختصر)

پس از پیروی از توصیه شما و اجرای آن rebase دستور (با تشکر! 😇) صفحه زیر را دریافت می کنم:

تصویر-280
rebase تعاملی (منبع: مختصر)

پس من چه کار کنم؟ من می خواهم “Commit 19” را بگذارم قبل از “Commit 18″، پس درست بعد از “Commit 17” می آید. می توانم جلوتر بروم و آنها را با هم له کنم، مانند این:

تصویر-281
Rebase تعاملی – تغییر ترتیب commit و squashing (منبع: مختصر)

اکنون هنگامی که از من برای یک پیام commit خواسته می شود، می توانم پیام “Commit 17+19” را ارائه کنم:

تصویر-282
ارائه پیام commit (منبع: مختصر)

و اکنون، تاریخ زیبای ما را ببینید:

تصویر-283
تاریخچه حاصل (منبع: مختصر)

بازم ممنون 🙌🏻

موارد استفاده Rebase بیشتر + تمرین بیشتر

در حال حاضر امیدوارم با نحو rebase احساس راحتی کنید. بهترین راه برای درک واقعی آن این است که موارد مختلف را در نظر بگیرید و نحوه حل آنها را خودتان بیابید.

با توجه به موارد استفاده آتی، اکیداً پیشنهاد می کنم پس از معرفی هر مورد استفاده، مطالعه را متوقف کنید و سپس سعی کنید آن را خودتان حل کنید.

نحوه حذف تعهدات

بگویید این سابقه را در مخزن دیگری دارید:

تصویر-284
تاریخچه ارتکاب دیگری (منبع: مختصر)

قبل از بازی کردن با آن، یک برچسب را در “Commit F” ذخیره کنید تا بتوانید بعداً به آن بازگردید:

git tag original_commit_f

اکنون، شما در واقع نمی خواهید تغییرات در “Commit C” و “Commit D” گنجانده شود. می توانید مانند قبل از یک rebase تعاملی استفاده کنید و تغییرات آنها را حذف کنید. یا، می تواند دوباره استفاده کند git rebase -–onto. چگونه استفاده می کنید --onto برای “حذف” این دو commit؟

شما می توانید rebase کنید HEAD در بالای “Commit B”، جایی که والد قدیمی در واقع “Commit D” بود و اکنون باید “Commit B” باشد. تاریخ را دوباره در نظر بگیرید:

تصویر-284
تاریخ دوباره (منبع: مختصر)

Rebasing به طوری که “Commit B” پایه “Commit E” باشد، به معنای “حرکت” هر دو “Commit E” و “Commit F” و دادن دیگری به آنها است. پایه – «تعهد ب». آیا می توانید خودتان دستور را ارائه دهید؟

git rebase --onto <SHA_OF_COMMIT_B> <SHA_OF_COMMIT_D> HEAD

توجه داشته باشید که استفاده از نحو بالا حرکت نمی کند main برای اشاره به commit جدید، بنابراین نتیجه یک “جدایی” است HEAD. اگر استفاده می کنید gg یا ابزار دیگری که تاریخچه قابل دسترسی از شاخه ها را نشان می دهد ممکن است شما را گیج کند:

تصویر-285
Rebasing با --onto منجر به جدا شدن می شود HEAD (منبع: مختصر)

اما اگر به سادگی استفاده کنید git log (یا نام مستعار من git lol، تاریخچه مورد نظر را خواهید دید:

تصویر-286
تاریخچه حاصل (منبع: مختصر)

من در مورد شما نمی دانم، اما این نوع چیزها من را واقعا خوشحال می کند. 😊😇

به هر حال، شما می توانید حذف کنید HEAD از دستور قبلی زیرا این مقدار پیش فرض برای پارامتر سوم است. بنابراین فقط با استفاده از:

git rebase --onto <SHA_OF_COMMIT_B> <SHA_OF_COMMIT_D>

همین تاثیر را خواهد داشت آخرین پارامتر در واقع به Git می گوید که پایان دنباله فعلی commits برای rebase کجاست. بنابراین نحو از git rebase --onto با سه آرگومان است:

git rebase --onto <new_parent> <old_parent> <until>

نحوه جابجایی commit ها در شاخه ها

بنابراین بیایید بگوییم که به همان تاریخ قبلی می رسیم:

git checkout original_commit_f

و اکنون فقط “Commit E” را می خواهم که در یک شاخه مبتنی بر “Commit B” باشد. یعنی من می خواهم یک شعبه جدید داشته باشم که از «Commit B» منشعب می شود و فقط «Commit E» دارد.

تصویر-287
تاریخچه فعلی، با توجه به “Commit E” (منبع: مختصر)

بنابراین، این به چه معناست از نظر rebase؟ تصویر بالا را در نظر بگیرید. چه commit (یا commit هایی) را باید تغییر دهم و کدام commit پایه جدید خواهد بود؟

میدونم اینجا میتونم روی تو حساب کنم😉

آنچه من می خواهم این است که “Commit E” و فقط این commit را بگیرم و پایه آن را به “Commit B” تغییر دهم. به عبارت دیگر، به بازپخش تغییرات ارائه شده در “Commit E” بر روی “Commit B”.

آیا می توانید آن منطق را در نحو اعمال کنید git rebase?

اینجاست (این بار دارم می نویسم <COMMIT_B> بجای <SHA_OF_COMMIT_B>برای اختصار):

git rebase –-onto <COMMIT_B> <COMMIT_D> <COMMIT_E>

اکنون تاریخ به این شکل است:

تصویر-288
تاریخچه پس از rebase (منبع: مختصر)

عالی!

نکته ای در مورد تعارضات

توجه داشته باشید که هنگام انجام یک rebase، ممکن است مانند هنگام ادغام با تداخل مواجه شوید. ممکن است تداخل داشته باشید زیرا هنگام تغییر پایه، سعی می‌کنید وصله‌ها را روی پایه دیگری اعمال کنید، شاید در جایی که وصله‌ها اعمال نمی‌شوند.

به عنوان مثال، مخزن قبلی را دوباره در نظر بگیرید، و به طور خاص، تغییر معرفی شده در “Commit 12” را که توسط main:

git show main
تصویر-289
پچ معرفی شده در “Commit 12” (منبع: مختصر)

من قبلاً به فرمت آن پرداختم git diff به تفصیل در پست قبلی، اما به عنوان یک یادآوری سریع، این commit به Git دستور می دهد تا یک خط بعد از دو خط زمینه اضافه کند:

```
This is a sample file

و قبل از این سه خط زمینه:

```
def new_feature():
  print('new feature')

بگویید که می‌خواهید «Commit 12» را به یک commit دیگر تغییر دهید. اگر به دلایلی، این خطوط زمینه مانند وصله مربوط به commit که در حال تغییر آن هستید وجود نداشته باشد. به سوی، در این صورت درگیری خواهید داشت. برای کسب اطلاعات بیشتر در مورد تضادها و نحوه حل آنها، به این راهنما مراجعه کنید.

بزرگنمایی برای تصویر بزرگ

تصویر-290
مقایسه rebase و ادغام (منبع: مختصر)

در ابتدای این راهنما، من با ذکر شباهت بین این دو شروع کردم git merge و git rebase: هر دو برای ادغام تغییرات ارائه شده در تاریخچه های مختلف استفاده می شوند.

اما، همانطور که اکنون می دانید، آنها در نحوه عملکرد بسیار متفاوت هستند. در حالی که ادغام منجر به تاریخچه واگرا می شود، تغییر پایه منجر به یک تاریخچه خطی می شود. تعارض در هر دو مورد ممکن است. و یک ستون دیگر در جدول بالا توضیح داده شده است که نیاز به توجه دقیق دارد.

اکنون که می دانید “Git rebase” چیست و چگونه از rebase تعاملی یا استفاده کنید rebase --ontoهمانطور که امیدوارم موافق باشید git rebase یک ابزار فوق العاده قدرتمند است با این حال، در مقایسه با ادغام، یک اشکال بزرگ دارد.

Git rebase تاریخچه را تغییر می دهد.

این به این معنی است که شما باید نه rebase commit هایی که خارج از کپی محلی شما از مخزن وجود دارد و ممکن است افراد دیگر تعهدات خود را بر اساس آن بنا کرده باشند.

به عبارت دیگر، اگر تنها commit های مورد نظر آنهایی هستند که به صورت محلی ایجاد کرده اید – ادامه دهید، از rebase استفاده کنید، وحشی شوید.

اما اگر commit ها تحت فشار قرار گرفته باشند، این می تواند به یک مشکل بزرگ منجر شود – زیرا ممکن است شخص دیگری به این commit ها اعتماد کند، که بعداً آنها را بازنویسی می کنید، و سپس شما و آنها نسخه های متفاوتی از مخزن خواهید داشت.

این بر خلاف است merge که همانطور که دیدیم تاریخ را تغییر نمی دهد.

به عنوان مثال، آخرین موردی را در نظر بگیرید که در آن این تاریخچه را مجدداً تغییر دادیم و به نتیجه رسیدیم:

تصویر-288
تاریخچه پس از rebase (منبع: مختصر)

حال، فرض کنید که من قبلاً این شاخه را به ریموت فشار داده ام. و بعد از اینکه شاخه را هل دادم، توسعه دهنده دیگری آن را کشید و از “Commit C” منشعب شد. توسعه‌دهنده دیگر نمی‌دانست که در همین حین، من به صورت محلی شعبه‌ام را تغییر می‌دادم و بعداً دوباره آن را فشار می‌دادم.

این منجر به یک ناسازگاری می شود: توسعه دهنده دیگر از یک commit کار می کند که دیگر در نسخه مخزن من موجود نیست.

من در این راهنما در مورد اینکه دقیقاً چه چیزی باعث این می شود توضیح نمی دهم، زیرا پیام اصلی من این است که قطعاً باید از چنین مواردی اجتناب کنید. اگر به آنچه واقعاً اتفاق می‌افتد علاقه دارید، پیوندی به یک منبع مفید در زیر می‌گذارم. در حال حاضر، اجازه دهید آنچه را که پوشش داده ایم، خلاصه کنیم.

خلاصه

در این آموزش با این موضوع آشنا شدید git rebase، ابزاری فوق العاده قدرتمند برای بازنویسی تاریخ در Git. شما چند مورد استفاده را در نظر گرفتید که در آن git rebase می تواند مفید باشد، و نحوه استفاده از آن با یک، دو یا سه پارامتر، با و بدون آن --onto تعویض.

امیدوارم تونسته باشم شما رو متقاعد کنم git rebase قدرتمند است – اما این که پس از دریافت اصل مطلب بسیار ساده است. این ابزاری برای “کپی پیست” commit ها (یا دقیق تر، وصله ها) است. و این ابزار مفیدی است که باید در زیر کمربند خود داشته باشید.

مراجع اضافی

  • لیست پخش Git Internals YouTube — توسط Brief (کانال YouTube من).
  • پست قبلی Omer در مورد داخلی Git.
  • آموزش Omer درباره Git UNDO – بازنویسی تاریخچه با Git.
  • Git docs در rebasing
  • انشعاب و قدرت rebase
  • Rebasing تعاملی
  • Git rebase –onto

درباره نویسنده

Omer Rosenbaum مدیر ارشد فناوری Swimm است. او نویسنده کانال کوتاه یوتیوب است. او همچنین کارشناس آموزش سایبری و بنیانگذار آکادمی امنیتی چک پوینت است. او نویسنده شبکه های کامپیوتری (به زبان عبری) است. می توانید او را در آن پیدا کنید توییتر.