-
1. شروع به کار
- 1.1 دربارهٔ کنترل نسخه
- 1.2 تاریخچهٔ کوتاهی از گیت
- 1.3 گیت چیست؟
- 1.4 خط فرمان
- 1.5 نصب گیت
- 1.6 اولین راهاندازی گیت
- 1.7 کمک گرفتن
- 1.8 خلاصه
-
2. مقدمات گیت
- 2.1 دستیابی به یک مخزن گیت
- 2.2 ثبت تغییرات در مخزن
- 2.3 دیدن تاریخچهٔ کامیتها
- 2.4 بازگردانی کارها
- 2.5 کار با ریموتها
- 2.6 برچسبگذاری
- 2.7 نامهای مستعار در گیت
- 2.8 خلاصه
-
3. شاخهسازی در گیت
- 3.1 شاخهها در یک کلمه
- 3.2 شاخهسازی و ادغام مقدماتی
- 3.3 مدیریت شاخه
- 3.4 روند کاری شاخهسازی
- 3.5 شاخههای ریموت
- 3.6 ریبیسکردن
- 3.7 خلاصه
-
4. گیت روی سرور
- 4.1 پروتکلها
- 4.2 راهاندازی گیت در سرور
- 4.3 ساختن کلید عمومی SSH
- 4.4 نصب و راهاندازی سرور
- 4.5 دیمن گیت
- 4.6 HTTP هوشمند
- 4.7 گیتوب
- 4.8 گیتلب
- 4.9 گزینههای شخصی ثالث میزبانی شده
- 4.10 خلاصه
-
5. گیت توزیعشده
- 5.1 روندهای کاری توزیعشده
- 5.2 مشارکت در یک پروژه
- 5.3 نگهداری یک پروژه
- 5.4 خلاصه
-
6. GitHub
-
7. Git Tools
- 7.1 Revision Selection
- 7.2 Interactive Staging
- 7.3 Stashing and Cleaning
- 7.4 Signing Your Work
- 7.5 Searching
- 7.6 Rewriting History
- 7.7 Reset Demystified
- 7.8 Advanced Merging
- 7.9 Rerere
- 7.10 Debugging with Git
- 7.11 Submodules
- 7.12 Bundling
- 7.13 Replace
- 7.14 Credential Storage
- 7.15 Summary
-
8. Customizing Git
- 8.1 Git Configuration
- 8.2 Git Attributes
- 8.3 Git Hooks
- 8.4 An Example Git-Enforced Policy
- 8.5 Summary
-
9. Git and Other Systems
- 9.1 Git as a Client
- 9.2 Migrating to Git
- 9.3 Summary
-
10. Git Internals
- 10.1 Plumbing and Porcelain
- 10.2 Git Objects
- 10.3 Git References
- 10.4 Packfiles
- 10.5 The Refspec
- 10.6 Transfer Protocols
- 10.7 Maintenance and Data Recovery
- 10.8 Environment Variables
- 10.9 Summary
-
A1. پیوست A: Git in Other Environments
- A1.1 Graphical Interfaces
- A1.2 Git in Visual Studio
- A1.3 Git in Visual Studio Code
- A1.4 Git in Eclipse
- A1.5 Git in IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.6 Git in Sublime Text
- A1.7 Git in Bash
- A1.8 Git in Zsh
- A1.9 Git in PowerShell
- A1.10 Summary
-
A2. پیوست B: Embedding Git in your Applications
- A2.1 Command-line Git
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. پیوست C: Git Commands
- A3.1 Setup and Config
- A3.2 Getting and Creating Projects
- A3.3 Basic Snapshotting
- A3.4 Branching and Merging
- A3.5 Sharing and Updating Projects
- A3.6 Inspection and Comparison
- A3.7 Debugging
- A3.8 Patching
- A3.9 Email
- A3.10 External Systems
- A3.11 Administration
- A3.12 Plumbing Commands
3.6 شاخهسازی در گیت - ریبیسکردن
ریبیسکردن
در گیت دو راه اصلی برای تعبیه تغییرات از برنچی به برنچ دیگر وجود دارد: merge
و rebase
.
در این بخش خواهید آموخت که ریبیس (Rebase) چیست، چکار میکند، چرا ابزار شگفتانگیزی است و در چه شرایطی نمیخواهید از آن استفاده کنید.
ریبیس مقدماتی
اگر به یک مثال قدیمیتر از ادغام مقدماتی بازگردیم، میبینید که کار شما و کامیتهای که کردهاید دوشاخه شده است.
همانطور که قبلاً بررسی کردیم، آسانترین راه برای یکپارچهسازی برنچها دستور merge
است.
این دستور یک ادغامسازی سه طرفه بین آخرین اسنپشاتهای دو برنچ (C3
و C4
) و آخرین والد مشترک آنها (C2
) انجام میدهد، یک اسنپشات جدید میسازد (و آنرا کامیت میکند).
هرچند راه دیگری نیز وجود دارد: شما میتوانید پچ تغییراتی که در C4
معرفی شدهاند را گرفته و آنرا به بالای C3
اعمال کنید.
در گیت این عمل ریبیسکردن نامیده میشود.
با دستور rebase
شما میتوانید تمام تغییراتی که روی یک برنچ کامیت شدهاند را بگیرید و آنها را به برنچ دیگر بازاعمال کنید.
برای این مثال شما برنچ experiment
را چکاوت میکنید و بعد آنرا به master
ریبیس میکنید، که به این شکل است:
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
این عملیات با رفتن به والد مشترک دو برنچ (آنکه رویش قرار دارید و آنکه رویش ریبیس میکنید)، گرفتن دیف معرفی شده در هر کامیت برنچی که روی آن هستید، ذخیره آن دیفها روی فایلهای موقت، بازنشانی برنچ فعلی به کامیت برنچی که روی آن ریبیس میکنید و در نهایت اعمال به ترتیب تغییرات کار میکند.
C4
به روی C3
در این نقطه میتوانید به برنچ master
بازگردید و یک مرج fast-forward کنید.
$ git checkout master
$ git merge experiment
master
حال اسنپشاتی که C4'
به آن اشاره میکند دقیقاً به مانند همانی است که که سابقاً توسط C5
در مثال مرج به آن اشاره میشد.
تفاوتی در محصول نهایی این یکپارچهسازی وجود ندارد اما ریبیسکردن تاریخچهای تمیزتر را خلق میکند.
اگر لاگ یک برنچ ریبیسشده را مطالعه کنید، به نظر یک تاریخچهٔ خطی است: طوری نمایش داده میشود که انگار همه چیز در طی یک فرآیند سری اتفاق افتاده حتی اگر در حقیقت به طور موازی انجام شده است.
توجه داشته باشید که اسنپشاتی که توسط کامیت نهایی تولیدی به آن اشاره میشود، چه آخرین کامیت ریبیسشده باشد، چه آخرین کامیت پس از مرج باشید، همان اسنپشات است — فقط تاریخچه تغییر کرده است. ریبیسکردن تغییرات را به ترتیبی که معرفی شدهاند از خطی که به خط دیگر باز اعمال میکند، درحالی که مرج نقاط نهایی را میگیرد و آنها را ادغام میکند.
ریبیسهای جذابتر
شما همچنین میتوانید تغییرات را روی چیزی به غیر از برنچ هدف اعمال کنید.
به طور مثال یک تاریخچه مانند تاریخچهای با یک برنچ موضوعی که از برنچ موضوعی دیگر شروع میشود را در نظر بگیرید.
یک برنچ موضوعی (server
) ساختید تا چند مورد سمت سرور به پروژهٔ خود اضافه کنید و کامیتی گرفتید.
سپس یک برنچ ساختید (client
) تا تغییرات سمت کلاینت را اعمال کنید و چند کامیت هم آنجا گرفتید.
در نهایت به برنچ سرور بازگشتید و چند کامیت دیگر گرفتهاید.
فرض کنید تصمیم دارید که برای یک انتشار، تغییرات سمت کلاینت را در خط اصلی پروژه ادغام کنید ولی میخواهید که تغییرات سمت سرور تا زمان تست شدن باقی بمانند.
شما میتوانید با استفاده از آپشن --onto
از دستور git rebase
تغییراتی از client
را که در server
نیستند (C8
و C9
) را گرفته و آنها را روی برنچ master
اعمال کنید:
$ git rebase --onto master server client
این دستور خیلی ساده میگوید که «برنچ client
را بگیر، از زمانی که از server
جدا شده تغییرات آن را بررسی کن و آنها را دوباره طوری به برنچ client
اعمال کن که انگار برنچ مستقیماً
از پایهٔ master
شروع شده بوده.»
کمی پیچیده است اما نتایجش جالب هستند.
حال میتوانید برنچ master
خود را fast-forward کنید (به فست-فوروارد کردن برنچ master
برای اضافه کردن تغییرات برنچ کلاینت نگاهی بیاندازید):
$ git checkout master
$ git merge client
master
برای اضافه کردن تغییرات برنچ کلاینتبیایید فرض کنیم که تصمیم گرفتهاید تا برنچ سرور خود را نیز پول کنید.
با اجرای git rebase <basebranch> <topicbranch>
میتوانید برنچ server
را روی master
ریبیس کنید بدون آنکه مجبور باشید ابتدا آنرا چکاوت کنید — این دستور برای شما
برنچ موضوعی را چکاوت میکند (در این مثال server
) و تغییرات آنرا روی برنچ پایه (بیس) (master
) بازاعمال میکند.
$ git rebase master server
همانطور که در ریبیس کردن برنچ سرورتان روی برنچ master
نمایش داده شد، این دستور کارهای برنچ server
را روی کارهای master
شما سوار میکند.
master
پس از این میتوانید برنچ پایه (master
) را fast-forward کنید:
$ git checkout master
$ git merge server
شما میتوانید برنچهای client
و server
را حذف کنید چراکه تمام کارها تعبیه شدهاند و شما دیگر به آنها احتیاجی ندارید، در نتیجه تمام تاریخچه شما مشابه تاریخچهٔ کامیت نهایی میشود:
$ git branch -d client
$ git branch -d server
خطرات ریبیسکردن
ولی، ریبیس هم مشکلات خودش را دارد، مشکلاتی که در یک خط خلاصه میتوان از آنها اجتناب کرد:
کامیتهایی که خارج از مخزن شما هستند و یا کار دیگران به آن بستگی دارد را ریبیس نکنید.
اگر این راهنما را به گوش بسپارید، با مشکلی مواجه نخواهید شد. اگر نسپارید، ملت از شما متنفر خواهند شد و شما توسط دوستان و خانوادهٔ خود ترد خواهید شد.
وقتی چیزی را ریبیس میکنید، در حال پشت سر گذاشتن کامیتهای حاضر و ساختن کامیتهای مشابه، اما متفاوت، با آنها هستید.
اگر جایی این کامیتها را پوش کنید و دیگران آنها را پول کرده و روی آنها کار کنند و سپس آن کامیتها را با git rebase
بازنویسی و دوباره پوش کنید، همکاران شما مجبور
خواهند بود تا دوباره کار خود را باز-ادغام کنند و هنگامی که بخواهید کار آنها را به کار خودتان پول کنید همهچیز خراب خواهد شد.
بیایید نگاهی به مثالی از اینکه چگونه ریبیس کردن کاری که به صورت عمومی انتشار دادهاید میتواند مشکلساز باشد بیاندازیم. فرض کنید که از یک سرور مرکزی کلون کردهاید و سپس کمی کار روی آن انجام دادهاید. تاریخچهٔ کامیت شما شبیه زیر است:
حال شخص دیگری نیز کمی کار محتوی یک مرج انجام میدهد و آنرا به سرور مرکزی پوش میکند. شما آنرا فچ میکنید و برنچ ریموت جدید را در کار خود مرج میکنید که باعث میشود تاریخچهٔ شما به شکل زیر شود:
پس از این، شخصی که کار مرجشده را پوش کرده تصمیم میگیرد که باز گردد و بجای آن کار خود را ریبیس کند؛ او یک git push --force
میکند تا تاریخچهٔ سرور را بازنویسی کند.
سپس شما آن سرور را فچ میکنید و کامیتهای جدید را دریافت میکنید:
حالا هر دوی شما گیر افتادهاید.
اگر شما git pull
کنید، یک مرج کامیت خواهید ساخت که شامل هر دو خط از تاریخچه خواهد بود و مخزن شما به شکل زیر در خواهد آمد:
اگر در همین حین که تاریخچهٔ شما به این شکل است، یک git log
اجرا کنید خواهید دید که دو کامیت وجود دارد که دقیقاً یک نویسنده، تاریخ و پیغام دارند که میتواند بسیار گیجکننده باشد.
بعلاوه اگر این تاریخچه را به سرور پوش کنید، شما تمام آن کامیتهای ریبیس شده را دوباره به سرور مرکزی معرفی میکنید که به مقدار گیجکنندگی مسئله بیش از پیش میافزاید.
خیلی راحت میتوان فرض را بر این گذاشت که توسعهدهندگان دیگر نمیخواهند که C4
و C6
در تاریخچه باشند و همین باعث بوده که در وهله اول ریبیس انجام شود.
وقتی ریبیس میکنید ریبیس کنید
اگر شما خودتان را در چنین شرایطی میبینید، گیت کمی راه حل جادویی پیش پای شما گذاشته است که ممکن است به شما کمک کنند. اگر شخصی در تیمتان فورس پوشی کرده که پایهٔ کار شما را بازنویسی کرده است، چالش شما تشخیص این خواهد بود که چه کاری مال شماست و چه چیزی را آنها بازنویسی کردهاند.
در اینجا معلوم میشود که علاوه بر چکسام SHA-1 کامیت، گیت همچنین چکسامی از تغییراتی که کامیت به تاریخچه افزوده محاسبه میکند. این چکسام را «شناسهٔ پچ» (Patch-ID) میخوانیم.
اگر کاری که بازنویسی شده را پول کردهاید و آنرا به روی کامیتهای جدید همکارتان ریبیس کنید، اکثر مواقع گیت متوجه میشود که چه کاری منحصر به شماست و آنها را دوباره به بالای برنچ جدید اعمال میکند.
به طور مثال، در شرایط قبلی، اگر هنگامی که در شخصی کامیتهای ریبیسشده را پوش میکند، کامیتهایی که شما کار خود را روی آنها بنا گذاشتهاید را پشت سر میگذارد هستید به جای مرجکردن، git rebase teamone/master
را اجرا کنید، گیت:
-
خواهد فهمید که چه کاری مختص به برنچ ماست (
C2
،C3
،C4
،C6
،C7
) -
خواهد فهمید کدام کامیتها مرج نشدهاند (
C2
،C3
،C4
) -
خواهد فهمید کدام کامیتها در برنچ هدف بازنویسی نشدهاند (فقط
C2
وC3
، از آنجایی کهC4
همان پچC4'
است) -
آن کامیتها را به روی
teamone/master
اعمال خواهد کرد
بنابراین بجای برخوردن با شما همان کارها را دوباره مرج میکنید و یک مرج کامیت جدید میسازید با نتیجهای بیشتر شبیه به روی یک کار ریبیس و فورس پوش شده ریبیس میکنید مواجه خواهیم شد.
این فقط در شرایطی کار خواهد کرد که C4
و C4'
که همکار شما ساخته تقریباً پچهای یکسانی هستند.
در غیر این صورت ریبیس به شما نخواهد گفت که تغییرات کپی هستند و یک پچ شبه-C4 (که احتمالاً اعمالش شکست خواهد خورد چراکه تغییرات قبلاً یک جایی پیادهسازی شدهاند) میسازد.
شما همچنین میتوانید با اجرای git pull --rebase
به جای یک git pull
معمولی این را سادهتر کنید.
یا میتوانستید با یک git fetch
که پس از آن git rebase teamone/master
(با توجه به مثال) اجرا شده به طور دستی آنرا انجام دهید.
اگر از git pull
استفاده میکنید و میخواهید --rebase
پیشفرض باشد، میتوانید مقدار کانفیگ pull.rebase
را با چیزی مشابه git config --global pull.rebase true
تنظیم کنید.
اگر شما فقط کامیتهایی که هرگز کامپیوتر شما را ترک نکردهاند ریبیس میکنید به مشکلی بر نخواهید خورد. اگر کامیتهایی را ریبیس میکنید که پوش شدهاند اما هیچ شخص دیگری روی آنها کاری را شروع نکرده است باز هم به مشکلی بر نخواهید خورد. اگر کامیتهایی را ریبیس میکنید که به طور عمومی پوش کردهاید و ممکن است اشخاصی روی آنها کار کرده باشند، ممکن است در دردسر طاقتفرسایی افتاده باشید و توسط هم تیمیهایتان ترد شوید.
اگر شما یا همکارتان به این نتیجه رسید که انجام چنین کاری ضروری است، از اینکه همه git pull --rebase
را اجرا میکنند اطمینان حاصل کنید تا در آینده درد کمتری نسیبتان شود.
ریبیس در مقابل ادغام
حال که ریبیس و مرج را در عمل دیدید، ممکن است به این فکر کنید که کدام بهتر است. پیش از اینکه به آن پاسخ دهیم بیایید کمی به عقب بازگردیم و به مبحث اینکه تاریخچه چه معنایی دارد بپردازیم.
از دیدگاهی تاریخچهٔ کامیتهای مخزن شما ثبت وقایع اتفاق افتاده است. سندی تاریخی و باارزش است که نباید با آن خیلی بازی کرد. از این زاویه دید تغییر دادن تاریخچهٔ کامیتها تقریباً تعریف را نقض میکند؛ شما در حال دروغ گفتن دربارهٔ اتفاقاتی هستید که واقعاً افتادهاند. پس اگر دستهای از مرج کامیتهای شلوغ داشته باشیم چه کنیم؟ اتفاقی است که افتاده و مخزن باید آنرا برای آیندگان نگه دارد.
در نقطه مقابل دیدگاهی است که میگوید تاریخچهٔ کامیتها داستان چگونگی ساخت پروژهٔ شما است.
پیش نمیآید که اولین پیشنویس یک کتاب و راهنمایی درباره اینکه «چرا نرمافزارتان مستحق ویرایشهای محتاطانه است» منتشر کنید.
افرادی که این دیدگاه را دارند از ابزارهایی مانند rebase
و filter-branch
استفاده میکنند تا داستان را به نحوی بسرایند که برای خوانندگان احتمالی پخته باشد.
حال برای جواب دادن به اینکه مرج بهتر است یا ریبیس: خوشبختانه ملاحظه میکنید که جواب دادن خیلی ساده نیست. گیت ابزار قدرتمندی است، به شما اجازهٔ انجام خیلی کارها را به خصوص روی تاریخچهتان میدهد، اما هر تیم و هر پروژه متفاوت است. حال که میدانید چگونه اینها کار میکنند، به شما بستگی دارد که تصمیم بگیرید که کدام برای شرایط بخصوص شما بهترین است.
در کل بهترین حالت ممکن این است که تغییرات محلی را که اعمال کردهاید اما منتشر نکردهاید ریبیس کنید تا پیش از پوش تاریخچهٔ شما تمیز باشد، اما هرگز هیچ چیزی را که جایی پوش کردهاید ریبیس نکنید.