-
1. شروع به کار (getting started)
-
2. مقدمات گیت (git basics chapter)
- 2.1 گرفتن یک مخزن گیت (Getting a Git Repository)
- 2.2 ثبت تغییرات در مخزن (Recording Changes to the Repository)
- 2.3 مشاهده تاریخچه کامیتها (Viewing the Commit History)
- 2.4 بازگرداندن تغییرات (Undoing Things)
- 2.5 کار کردن با ریموت ها (Working with Remotes)
- 2.6 تگ کردن (Tagging)
- 2.7 نام مستعار گیت (Git Aliases)
- 2.8 خلاصه (summary)
-
3. انشعابگیری در گیت (Git Branching)
-
4. گیت روی سرور (Git on the server)
- 4.1 پروتکلها (The Protocols)
- 4.2 راهاندازی گیت روی یک سرور (Getting Git on a Server)
- 4.3 ایجاد کلید عمومی SSH شما (Generating Your SSH Public Key)
- 4.4 نصب و راهاندازی سرور (Setting up server)
- 4.5 سرویسدهنده گیت (Git Daemon)
- 4.6 HTTP هوشمند (Smart HTTP)
- 4.7 گیتوب (GitWeb)
- 4.8 گیتلب (GitLab)
- 4.9 گزینههای میزبانی شخص ثالث (Third Party Hosted Options)
- 4.10 خلاصه (Summary)
-
5. گیت توزیعشده (Distributed git)
-
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. سفارشیسازی Git (Customizing Git)
-
9. گیت و سیستمهای دیگر (Git and Other Systems)
-
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 در ویژوال استودیو (Git in Visual Studio)
- A1.3 Git در Visual Studio Code (Git in Visual Studio Code)
- A1.4 Git در IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine (Git in IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine)
- A1.5 Git در Sublime Text (Git in Sublime Text)
- A1.6 گیت در بش (Git in Bash)
- A1.7 Git در Zsh (Git in Zsh)
- A1.8 Git در PowerShell (Git in PowerShell)
- A1.9 خلاصه (Summary)
-
A2. پیوست B: گنجاندن گیت در برنامههای شما (Embedding Git in your Applications)
-
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)
7.8 ابزارهای گیت (Git Tools) - ادغام پیشرفته (Advanced Merging)
ادغام پیشرفته (Advanced Merging)
ادغام در گیت معمولاً نسبتاً آسان است. از آنجا که گیت ادغام شاخهای دیگر را چندین بار آسان میکند، این بدان معناست که میتوانید یک شاخه بسیار طولانیمدت داشته باشید و در عین حال آن را بهروز نگه دارید، بهگونهای که اغلب تعارضات کوچک را حل کنید، به جای اینکه در پایان با یک تعارض عظیم و ناگهانی مواجه شوید.
با این حال، گاهی اوقات تعارضات پیچیدهای پیش میآید. برخلاف برخی سیستمهای کنترل نسخه دیگر، گیت سعی نمیکند بسیار هوشمندانه تعارضهای ادغام را به صورت خودکار حل کند. فلسفه گیت این است که در تعیین اینکه آیا راهحل ادغام بدون ابهام است هوشمندانه عمل کند، اما اگر تعارضی وجود داشته باشد، تلاش نمیکند به طور خودکار و هوشمندانه آن را حل کند. بنابراین، اگر برای ادغام دو شاخهای که سریع از هم جدا شدهاند زمان زیادی صبر کنید، ممکن است با مشکلاتی مواجه شوید.
در این بخش، به بررسی برخی از این مشکلات و ابزارهایی که گیت برای مدیریت این موقعیتهای پیچیده در اختیار شما میگذارد، میپردازیم. همچنین انواع مختلف و غیرمعمول ادغامهایی که میتوانید انجام دهید را بررسی میکنیم و نحوه بازگرداندن ادغامهایی که انجام دادهاید را نیز مرور خواهیم کرد.
تعارضهای ادغام (Merge Conflicts)
در حالی که برخی اصول اولیه حل تعارضهای ادغام را در مرج کانفیلیکت پایه (Basic Merge Conflicts) پوشش دادیم، برای تعارضهای پیچیدهتر، گیت چند ابزار فراهم میکند تا به شما کمک کند بفهمید چه اتفاقی میافتد و چگونه بهتر با تعارض برخورد کنید.
اول از همه، اگر ممکن است، قبل از انجام ادغامی که ممکن است تعارض داشته باشد، مطمئن شوید که شاخه کاری شما پاک (clean) است. اگر روی کاری در حال پیشرفت هستید، آن را یا به یک شاخه موقت کامیت کنید یا از طریق stash ذخیره کنید. این کار به شما امکان میدهد هر کاری که در این مرحله انجام میدهید را به راحتی برگردانید. اگر در شاخه کاری خود تغییرات ذخیرهنشده دارید و ادغام را امتحان میکنید، برخی از این نکات میتواند به حفظ آن تغییرات کمک کند.
بیایید با یک مثال بسیار ساده پیش برویم.
یک فایل روبی بسیار ساده داریم که hello world
را چاپ میکند.
#! /usr/bin/env ruby
def hello
puts 'hello world'
end
hello()
در مخزن خود، یک شاخه جدید به نام whitespace
ایجاد میکنیم و تمام انتهای خطوط یونیکس را به انتهای خطوط DOS تغییر میدهیم، به عبارتی هر خط فایل را فقط از نظر فضای سفید تغییر میدهیم.
سپس خط "hello world" را به "hello mundo" تغییر میدهیم.
$ git checkout -b whitespace
Switched to a new branch 'whitespace'
$ unix2dos hello.rb
unix2dos: converting file hello.rb to DOS format ...
$ git commit -am 'Convert hello.rb to DOS'
[whitespace 3270f76] Convert hello.rb to DOS
1 file changed, 7 insertions(+), 7 deletions(-)
$ vim hello.rb
$ git diff -b
diff --git a/hello.rb b/hello.rb
index ac51efd..e85207e 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,7 @@
#! /usr/bin/env ruby
def hello
- puts 'hello world'
+ puts 'hello mundo'^M
end
hello()
$ git commit -am 'Use Spanish instead of English'
[whitespace 6d338d2] Use Spanish instead of English
1 file changed, 1 insertion(+), 1 deletion(-)
حالا برمیگردیم به شاخه master
و مقداری مستندات برای تابع اضافه میکنیم.
$ git checkout master
Switched to branch 'master'
$ vim hello.rb
$ git diff
diff --git a/hello.rb b/hello.rb
index ac51efd..36c06c8 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,5 +1,6 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
puts 'hello world'
end
$ git commit -am 'Add comment documenting the function'
[master bec6336] Add comment documenting the function
1 file changed, 1 insertion(+)
اکنون سعی میکنیم شاخه whitespace
را ادغام کنیم و به دلیل تغییرات فضای سفید با تعارض مواجه میشویم.
$ git merge whitespace
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Automatic merge failed; fix conflicts and then commit the result.
لغو ادغام (Aborting a Merge)
حالا چند گزینه داریم.
اول، بیایید بررسی کنیم چگونه از این وضعیت خارج شویم.
اگر انتظار تعارض نداشتید و نمیخواهید فعلاً با آن درگیر شوید، میتوانید به سادگی با دستور git merge --abort
از ادغام خارج شوید.
$ git status -sb
## master
UU hello.rb
$ git merge --abort
$ git status -sb
## master
گزینه git merge --abort
تلاش میکند تا به حالتی که قبل از اجرای عملیات ادغام (merge) داشتید برگردد. تنها زمانی ممکن است این کار را بهطور کامل نتواند انجام دهد که هنگام اجرای ادغام، تغییراتی بدون ذخیرهسازی (unstashed) یا بدون کامیت در دایرکتوری کاری خود داشته باشید؛ در غیر این صورت، این گزینه بهخوبی کار خواهد کرد.
اگر به هر دلیلی بخواهید از اول شروع کنید، میتوانید دستور git reset --hard HEAD
را اجرا کنید تا مخزن شما به آخرین حالت کامیت شده بازگردد. توجه داشته باشید که هر تغییر ذخیرهنشدهای از دست خواهد رفت؛ پس مطمئن شوید که هیچ کدام از تغییرات خود را نمیخواهید حفظ کنید.
نادیده گرفتن فاصلهها (Ignoring Whitespace)
در این مورد خاص، تداخلها به دلیل فاصلهها هستند. این موضوع را میدانیم چون مسئله ساده است، اما در موارد واقعی هم وقتی به تداخل نگاه میکنید، بهراحتی قابل تشخیص است چون هر خط در یک طرف حذف شده و دوباره در طرف دیگر اضافه شده است. بهطور پیشفرض، گیت تمام این خطوط را بهعنوان تغییر یافته میبیند و نمیتواند فایلها را ادغام کند.
با این حال، استراتژی پیشفرض ادغام میتواند آرگومانهایی دریافت کند که برخی از آنها مربوط به نادیده گرفتن صحیح فاصلهها هستند. اگر در یک ادغام با مشکلات زیادی درباره فاصله مواجه شدید، میتوانید بهسادگی ادغام را لغو کنید و دوباره اجرا کنید، اینبار با گزینههای -Xignore-all-space
یا -Xignore-space-change
. گزینه اول هنگام مقایسه خطوط، فاصلهها را کاملاً نادیده میگیرد و گزینه دوم دنبالهای از یک یا چند کاراکتر فاصله را معادل در نظر میگیرد.
$ git merge -Xignore-space-change whitespace
Auto-merging hello.rb
Merge made by the 'recursive' strategy.
hello.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
از آنجا که در این مورد تغییرات واقعی فایلها با هم تداخل نداشتند، وقتی فاصلهها را نادیده میگیریم، همه چیز بهخوبی ادغام میشود.
این قابلیت بسیار مفید است اگر در تیم شما کسی باشد که گاهی همه چیز را از فضاها به تبها یا برعکس تغییر فرمت میدهد.
ادغام مجدد دستی فایلها (Manual File Re-merging)
اگرچه گیت پیشپردازش فاصلهها را بهخوبی مدیریت میکند، انواع دیگری از تغییرات وجود دارند که شاید گیت نتواند بهطور خودکار آنها را مدیریت کند، اما میتوان آنها را با اسکریپت اصلاح کرد. بهعنوان مثال، فرض کنیم گیت نتواند تغییر فاصلهها را مدیریت کند و مجبور باشیم این کار را دستی انجام دهیم.
کاری که باید انجام دهیم این است که فایل مورد نظر برای ادغام را قبل از تلاش برای ادغام واقعی، با برنامهای مانند dos2unix
پردازش کنیم. چگونه این کار را انجام دهیم؟
ابتدا وارد حالت تداخل ادغام میشویم. سپس باید نسخههای فایل خودمان، نسخه طرف مقابل (از شاخهای که میخواهیم ادغام کنیم) و نسخه مشترک (جایی که هر دو شاخه منشعب شدهاند) را تهیه کنیم. بعد باید طرف خودمان یا طرف مقابل را اصلاح کنیم و دوباره ادغام را فقط برای این فایل امتحان کنیم.
تهیه سه نسخه فایل کار چندان دشواری نیست. گیت همه این نسخهها را در ایندکس تحت عنوان "مرحلهها" (stages) نگه میدارد که هر کدام شمارهای دارند. مرحله ۱ نسخه اجدادی مشترک است، مرحله ۲ نسخه شما و مرحله ۳ نسخهای است که از MERGE_HEAD
میآید، یعنی نسخهای که میخواهید ادغام کنید ("طرف مقابل").
میتوانید هر یک از این نسخههای فایل درگیر در تداخل را با دستور git show
و یک نحو خاص استخراج کنید.
$ git show :1:hello.rb > hello.common.rb
$ git show :2:hello.rb > hello.ours.rb
$ git show :3:hello.rb > hello.theirs.rb
اگر بخواهید کمی حرفهایتر عمل کنید، میتوانید با دستور کمکی ls-files -u
شناسه SHA-1 بلاکهای گیت برای هر یک از این فایلها را به دست آورید.
$ git ls-files -u
100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1 hello.rb
100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2 hello.rb
100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3 hello.rb
:1:hello.rb
فقط یک کوتاهنویسی برای جستجوی آن شناسه SHA-1 بلاک است.
حالا که محتوای هر سه مرحله را در دایرکتوری کاری خود داریم، میتوانیم نسخه طرف مقابل را بهصورت دستی برای رفع مشکل فاصله اصلاح کنیم و سپس با دستور کمتر شناخته شده git merge-file
فایل را مجدداً ادغام کنیم.
$ dos2unix hello.theirs.rb
dos2unix: converting file hello.theirs.rb to Unix format ...
$ git merge-file -p \
hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb
$ git diff -b
diff --cc hello.rb
index 36c06c8,e85207e..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,8 -1,7 +1,8 @@@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
در این مرحله فایل بهخوبی ادغام شده است. در واقع، این روش بهتر از گزینه ignore-space-change
عمل میکند چون ابتدا مشکل فاصلهها را رفع میکند و بعد ادغام میکند، نه اینکه فقط آنها را نادیده بگیرد. در ادغام با گزینه ignore-space-change
، در واقع چند خط دارای انتهای خط DOS باقی میماند که باعث مخلوط شدن فرمتها میشود.
اگر بخواهید قبل از نهایی کردن این کامیت، ایدهای از تغییرات واقعی بین یک طرف و طرف دیگر داشته باشید، میتوانید از git diff
استفاده کنید تا تغییرات موجود در دایرکتوری کاری که میخواهید بهعنوان نتیجه ادغام کامیت کنید را با هر یک از این مرحلهها مقایسه کنید. بیایید همه آنها را مرور کنیم.
برای مقایسه نتیجه خود با آنچه قبل از ادغام در شاخهتان داشتید، یعنی دیدن تغییراتی که ادغام وارد کرده است، میتوانید دستور git diff --ours
را اجرا کنید:
$ git diff --ours
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index 36c06c8..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -2,7 +2,7 @@
# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
اینجا بهراحتی میبینیم که در شاخه خودمان چه اتفاقی افتاده، یعنی دقیقاً چه چیزی را با این ادغام به فایل اضافه میکنیم، که تغییر یک خط است.
اگر بخواهید ببینید نتیجه ادغام چقدر با نسخه طرف مقابل تفاوت دارد، میتوانید دستور git diff --theirs
را اجرا کنید. در این مثال و مثال بعدی، باید گزینه -b
را اضافه کنید تا فاصلهها حذف شوند، چون مقایسه را با آنچه در گیت است انجام میدهیم، نه فایل پاکسازیشده hello.theirs.rb
خودمان.
$ git diff --theirs -b
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index e85207e..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,5 +1,6 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
puts 'hello mundo'
end
در نهایت، میتوانید ببینید که فایل از هر دو طرف چگونه تغییر کرده است با دستور git diff --base
.
$ git diff --base -b
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index ac51efd..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,8 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
در این مرحله میتوانیم از دستور git clean
استفاده کنیم تا فایلهای اضافیای که برای انجام ادغام دستی ایجاد کردهایم ولی دیگر نیازی به آنها نداریم را پاک کنیم.
$ git clean -f
Removing hello.common.rb
Removing hello.ours.rb
Removing hello.theirs.rb
Checking Out Conflicts (بررسی تعارضها)
شاید به دلیلی از نتیجه حل تعارض راضی نیستیم، یا شاید ویرایش دستی یک یا هر دو طرف هنوز خوب جواب نداده و به زمینهی بیشتری نیاز داریم.
بیایید مثال را کمی تغییر دهیم. در این مثال، دو شاخه بلندمدت داریم که هر کدام چند کامیت در خود دارند ولی هنگام ادغام، یک تعارض محتوایی واقعی ایجاد میکنند.
$ git log --graph --oneline --decorate --all
* f1270f7 (HEAD, master) Update README
* 9af9d3b Create README
* 694971d Update phrase to 'hola world'
| * e3eb223 (mundo) Add more tests
| * 7cff591 Create initial testing script
| * c3ffff1 Change text to 'hello mundo'
|/
* b7dcc89 Initial hello world code
اکنون سه کامیت منحصر به فرد داریم که فقط روی شاخه master
هستند و سه کامیت دیگر که روی شاخه mundo
قرار دارند.
اگر بخواهیم شاخه mundo
را ادغام کنیم، با تعارض مواجه میشویم.
$ git merge mundo
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Automatic merge failed; fix conflicts and then commit the result.
میخواهیم ببینیم تعارض ادغام چیست. اگر فایل را باز کنیم، چیزی شبیه این میبینیم:
#! /usr/bin/env ruby
def hello
<<<<<<< HEAD
puts 'hola world'
=======
puts 'hello mundo'
>>>>>>> mundo
end
hello()
هر دو طرف ادغام محتوایی به این فایل اضافه کردهاند، اما برخی از کامیتها فایل را در یک نقطه مشابه تغییر دادهاند که باعث این تعارض شده است.
بیایید چند ابزار که اکنون در اختیار دارید را بررسی کنیم تا بفهمیم این تعارض چگونه به وجود آمده است. شاید واضح نباشد که دقیقاً چگونه باید این تعارض را رفع کنید. به زمینهی بیشتری نیاز دارید.
یکی از ابزارهای مفید، دستور git checkout
با گزینه --conflict
است.
این دستور فایل را دوباره چکاوت میکند و نشانگرهای تعارض ادغام را جایگزین میکند.
این میتواند زمانی مفید باشد که بخواهید نشانگرها را ریست کرده و دوباره سعی کنید آنها را حل کنید.
میتوانید به --conflict
مقدار diff3
یا merge
(که پیشفرض است) بدهید.
اگر diff3
را بدهید، گیت از نسخهای کمی متفاوت از نشانگرهای تعارض استفاده میکند که نه تنها نسخههای “ours” و “theirs” بلکه نسخه “base” را هم به صورت درونخطی نشان میدهد تا زمینه بیشتری به شما بدهد.
$ git checkout --conflict=diff3 hello.rb
وقتی این دستور را اجرا کنیم، فایل به این شکل خواهد بود:
#! /usr/bin/env ruby
def hello
<<<<<<< ours
puts 'hola world'
||||||| base
puts 'hello world'
=======
puts 'hello mundo'
>>>>>>> theirs
end
hello()
اگر این فرمت را دوست دارید، میتوانید آن را به عنوان پیشفرض برای تعارضهای ادغام آینده با تنظیم گزینه merge.conflictstyle
روی diff3
قرار دهید.
$ git config --global merge.conflictstyle diff3
دستور git checkout
همچنین گزینههای --ours
و --theirs
را میپذیرد که راه بسیار سریعی برای انتخاب فقط یکی از دو طرف بدون انجام ادغام کامل است.
این برای تعارضهای فایلهای باینری که میتوانید به سادگی یکی از دو طرف را انتخاب کنید، یا زمانی که فقط میخواهید برخی فایلها را از شاخه دیگر ادغام کنید، بسیار مفید است — میتوانید ادغام را انجام دهید و سپس قبل از کامیت، فایلهای خاصی را از یکی از دو طرف چکاوت کنید.
گزارش ادغام (Merge Log)
ابزار مفید دیگری که هنگام حل تعارضهای ادغام کاربرد دارد، git log
است.
این میتواند به شما کمک کند تا بفهمید چه چیزی ممکن است به ایجاد تعارضها کمک کرده باشد.
مرور کمی از تاریخچه برای به یاد آوردن اینکه چرا دو خط توسعه به یک بخش مشابه از کد دست زدهاند، گاهی بسیار مفید است.
برای دریافت فهرست کامل تمام کامیتهای منحصر به فردی که در هر یک از شاخههای دخیل در این ادغام وجود دارند، میتوانیم از نحو «سه نقطه» که در سه نقطه (Triple Dot) یاد گرفتهایم استفاده کنیم.
$ git log --oneline --left-right HEAD...MERGE_HEAD
< f1270f7 Update README
< 9af9d3b Create README
< 694971d Update phrase to 'hola world'
> e3eb223 Add more tests
> 7cff591 Create initial testing script
> c3ffff1 Change text to 'hello mundo'
این فهرست خوبی از شش کامیت کلی درگیر است، همچنین مشخص میکند هر کامیت مربوط به کدام شاخه توسعه بوده است.
اما ما میتوانیم این را سادهتر کنیم تا زمینه بسیار مشخصتری به دست بیاوریم.
اگر گزینه --merge
را به git log
اضافه کنیم، فقط کامیتهایی را نشان میدهد که در هر دو طرف عملیات ادغام ویرایشی روی فایلی داشتهاند که در حال حاضر دچار تعارض است.
$ git log --oneline --left-right --merge
< 694971d Update phrase to 'hola world'
> c3ffff1 Change text to 'hello mundo'
اگر به جای آن، این فرمان را با گزینه -p
اجرا کنید، فقط تفاوتهای مربوط به فایلی که در نهایت دچار تعارض شده را خواهید دید.
این میتواند بسیار مفید باشد تا سریعاً زمینهای که به شما کمک میکند بفهمید چرا تعارض رخ داده و چگونه میتوان آن را با هوشمندی بیشتری حل کرد را به دست آورید.
قالب تفاوت ترکیبی (Combined Diff Format)
از آنجا که گیت هر نتیجه ادغامی که موفق باشد را مرحلهبندی میکند، وقتی در حالت ادغام دچار تعارض هستید و git diff
را اجرا میکنید، فقط مواردی را میبینید که هنوز دچار تعارض هستند.
این میتواند به شما کمک کند ببینید چه چیزهایی هنوز باید حل و فصل شود.
وقتی که بلافاصله پس از یک تعارض ادغام git diff
را اجرا میکنید، اطلاعاتی در قالب خروجی تفاوتی نسبتاً منحصر به فرد دریافت خواهید کرد.
$ git diff
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,11 @@@
#! /usr/bin/env ruby
def hello
++<<<<<<< HEAD
+ puts 'hola world'
++=======
+ puts 'hello mundo'
++>>>>>>> mundo
end
hello()
این قالب به نام "تفاوت ترکیبی" شناخته میشود و دو ستون داده کنار هر خط به شما نشان میدهد. ستون اول نشان میدهد که آیا آن خط بین شاخه “ours” و فایل در دایرکتوری کاری شما متفاوت است (حذف یا اضافه شده) و ستون دوم همین را بین شاخه “theirs” و کپی دایرکتوری کاری شما نشان میدهد.
مثلاً در این مثال میبینید خطوط <<<<<<<
و >>>>>>>
در کپی کاری وجود دارند اما در هیچکدام از دو طرف ادغام نیستند.
این منطقی است چون ابزار ادغام آنها را برای زمینه ما قرار داده، اما انتظار میرود که آنها را حذف کنیم.
اگر تعارض را حل کنیم و دوباره git diff
را اجرا کنیم، همان خروجی را میبینیم ولی کمی مفیدتر است.
$ vim hello.rb
$ git diff
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end
hello()
این به ما نشان میدهد که “hola world” در سمت ما بوده ولی در کپی کاری نیست، “hello mundo” در سمت آنها بوده ولی در کپی کاری نیست و در نهایت “hola mundo” در هیچکدام از دو طرف نبوده ولی اکنون در کپی کاری وجود دارد. این برای بازبینی قبل از ثبت نهایی حل تعارض مفید است.
شما همچنین میتوانید این خروجی را از git log
هر ادغام ببینید تا بفهمید بعد از ادغام چگونه مسئله حل شده است.
گیت این قالب را وقتی git show
را روی یک کامیت ادغام اجرا کنید یا اگر گزینه --cc
را به git log -p
اضافه کنید (که به طور پیشفرض فقط پچهای کامیتهای غیرادغام را نشان میدهد) نمایش میدهد.
$ git log --cc -p -1
commit 14f41939956d80b9e17bb8721354c33f8d5b5a79
Merge: f1270f7 e3eb223
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Sep 19 18:14:49 2014 +0200
Merge branch 'mundo'
Conflicts:
hello.rb
diff --cc hello.rb
index 0399cd5,59727f0..e1d0799
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end
hello()
بازگرداندن ادغامها (Undoing Merges)
حالا که میدانید چگونه یک کامیت ادغام ایجاد کنید، احتمالاً بعضی را اشتباهی خواهید ساخت. یکی از چیزهای عالی در کار با گیت این است که اشتباه کردن اشکالی ندارد، چون امکان اصلاح آن وجود دارد و در بسیاری موارد آسان است.
کامیتهای ادغام هم همینطور هستند.
فرض کنید روی یک شاخه موضوعی کار میکردید، اشتباهی آن را به master
ادغام کردید و حالا تاریخچه کامیت شما اینگونه شده است:

دو راه برای رفع این مشکل وجود دارد، بسته به اینکه نتیجه دلخواه شما چیست.
اصلاح اشارهگرها (Fix the references)
اگر کامیت ادغام ناخواسته فقط روی مخزن محلی شما وجود دارد، سادهترین و بهترین راه این است که شاخهها را جابجا کنید تا به جایگاه دلخواه برسند.
در بیشتر موارد، اگر بعد از ادغام اشتباهی git merge
، دستور git reset --hard HEAD~
را اجرا کنید، اشارهگر شاخهها را به این شکل تنظیم میکند:

git reset --hard HEAD~
ما پیشتر reset
را در بازنشانی به زبان ساده (Reset Demystified) پوشش دادهایم، بنابراین نباید دشوار باشد که بفهمید اینجا چه اتفاقی میافتد.
یک یادآوری سریع: reset --hard
معمولاً سه مرحله دارد:
-
حرکت دادن شاخهای که HEAD به آن اشاره میکند. در این مورد، میخواهیم
master
را به جایی که پیش از کامیت ادغام بود (C6
) برگردانیم. -
همسانسازی شاخص (index) با HEAD.
-
همسانسازی دایرکتوری کاری با شاخص.
نقطه ضعف این روش این است که تاریخچه را بازنویسی میکند، که در مخزن مشترک میتواند مشکلساز باشد.
برای اطلاعات بیشتر در این زمینه به خطرات بازپایهگذاری (The Perils of Rebasing) مراجعه کنید؛ خلاصه این است که اگر دیگران کامیتهایی که شما بازنویسی میکنید را دارند، بهتر است از reset
استفاده نکنید.
همچنین این روش زمانی کار نمیکند که کامیتهای دیگری بعد از ادغام ساخته شده باشند؛ جابجایی اشارهگرها باعث از دست رفتن آن تغییرات خواهد شد.
معکوس کردن کامیت (Reverse the commit)
اگر جابجا کردن اشارهگر شاخهها برای شما مناسب نیست، گیت این امکان را میدهد که کامیت جدیدی بسازید که تمام تغییرات یک کامیت موجود را برگرداند. گیت این عملیات را “revert” مینامد و در این موقعیت خاص، دستور به این شکل است:
$ git revert -m 1 HEAD
[master b1d8379] Revert "Merge branch 'topic'"
پرچم -m 1
مشخص میکند کدام والد “mainline” است و باید نگه داشته شود.
وقتی یک ادغام را روی HEAD
اجرا میکنید (git merge topic
)، کامیت جدید دو والد دارد: اولی HEAD
(C6
) است و دومی سر شاخهای که ادغام شده (C4
).
در اینجا میخواهیم همه تغییراتی که از ادغام والد شماره ۲ (C4
) آمده را برگردانیم و همه محتویات والد شماره ۱ (C6
) را نگه داریم.
تاریخچه با کامیت ریورت شده اینگونه خواهد بود:

git revert -m 1
کامیت جدید ^M
دقیقاً همان محتوای C6
را دارد، بنابراین از اینجا به بعد انگار ادغام هرگز انجام نشده، البته کامیتهای ادغام نشده هنوز در تاریخچه HEAD
باقی هستند.
اگر بخواهید دوباره شاخه topic
را به master
ادغام کنید، گیت گیج خواهد شد:
$ git merge topic
Already up-to-date.
هیچ چیزی در شاخهی topic
وجود ندارد که قبلاً از شاخهی master
قابل دسترسی نباشد.
بدتر از آن، اگر روی topic
کار اضافه کنید و دوباره ادغام (merge) کنید، گیت فقط تغییراتی را که بعد از ادغام معکوس شده ایجاد شدهاند وارد میکند:

بهترین راه برای حل این مشکل این است که ادغام اصلی را لغو معکوس نکنید، چون حالا میخواهید تغییراتی که قبلاً معکوس شدهاند را وارد کنید، سپس یک کامیت ادغام جدید ایجاد کنید:
$ git revert ^M
[master 09f0126] Revert "Revert "Merge branch 'topic'""
$ git merge topic

در این مثال، M
و ^M
همدیگر را خنثی میکنند.
^^M
عملاً تغییرات از C3
و C4
را ادغام میکند و C8
تغییرات از C7
را وارد میکند، بنابراین اکنون شاخهی topic
کاملاً ادغام شده است.
انواع دیگر ادغامها (Other Types of Merges)
تا اینجا ما ادغام معمول دو شاخه را پوشش دادیم که معمولاً با استراتژی ادغام به نام “recursive” انجام میشود. اما روشهای دیگری هم برای ادغام شاخهها وجود دارد. بیایید چند مورد از آنها را سریع بررسی کنیم.
اولویت ما یا آنها (Our or Theirs Preference)
اول از همه، یک قابلیت مفید دیگر در حالت معمول ادغام “recursive” وجود دارد.
ما قبلاً گزینههای ignore-all-space
و ignore-space-change
را که با -X
منتقل میشوند دیدیم، اما میتوانیم به گیت بگوییم که در صورت بروز تعارض، طرف یکی از شاخهها را ترجیح دهد.
به طور پیشفرض، وقتی گیت تعارضی بین دو شاخه در حال ادغام میبیند، نشانگرهای تعارض را در کد اضافه میکند و فایل را به حالت تعارضدار درمیآورد تا شما آن را حل کنید.
اگر ترجیح میدهید گیت بهجای اینکه شما دستی تعارض را حل کنید، یکی از طرفین را به طور کامل انتخاب کند و طرف دیگر را نادیده بگیرد، میتوانید به دستور merge
گزینههای -Xours
یا -Xtheirs
را بدهید.
اگر گیت این گزینهها را ببیند، نشانگر تعارض نمیگذارد. هر تفاوتی که قابل ادغام باشد را ادغام میکند. هر تفاوتی که تعارض داشته باشد، به سادگی طرف مشخص شده توسط شما را به طور کامل انتخاب میکند، حتی فایلهای باینری را.
اگر به مثال “hello world” که قبلاً داشتیم برگردیم، میبینیم ادغام شاخهی ما باعث تعارض میشود.
$ git merge mundo
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Resolved 'hello.rb' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
اما اگر با -Xours
یا -Xtheirs
اجرا شود، تعارضی ایجاد نمیشود.
$ git merge -Xours mundo
Auto-merging hello.rb
Merge made by the 'recursive' strategy.
hello.rb | 2 +-
test.sh | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
create mode 100644 test.sh
در این حالت، به جای اینکه نشانگرهای تعارض در فایل با “hello mundo” در یک طرف و “hola world” در طرف دیگر ظاهر شود، به سادگی “hola world” را انتخاب میکند. با این حال، تمام تغییرات غیر متعارض دیگر در آن شاخه به درستی ادغام میشوند.
این گزینه همچنین میتواند به دستور git merge-file
که قبلاً دیدیم داده شود، مثلاً با اجرای git merge-file --ours
برای ادغام فایلهای جداگانه.
اگر بخواهید کاری مشابه انجام دهید اما نخواهید گیت حتی تلاش کند تغییرات طرف دیگر را ادغام کند، گزینهی سختگیرانهتری به نام استراتژی ادغام “ours” وجود دارد. این متفاوت است از گزینهی “ours” در ادغام بازگشتی.
این عملاً یک ادغام جعلی انجام میدهد. یک کامیت ادغام جدید با هر دو شاخه به عنوان والدین ثبت میکند، اما حتی به شاخهای که میخواهید ادغام کنید نگاه نمیکند. در نتیجه ادغام، دقیقاً کد شاخهی فعلی شما ثبت میشود.
$ git merge -s ours mundo
Merge made by the 'ours' strategy.
$ git diff HEAD HEAD~
$
میبینید که هیچ تفاوتی بین شاخهای که روی آن بودیم و نتیجه ادغام وجود ندارد.
این اغلب مفید است تا گیت را فریب دهید که شاخهای قبلاً ادغام شده است وقتی بعداً میخواهید ادغام کنید.
برای مثال، فرض کنید از شاخهی release
یک شاخه گرفتهاید و روی آن کاری انجام دادهاید که میخواهید بعداً به شاخهی master
بازگردانید.
در همین حین، یک رفع اشکال روی master
نیاز به انتقال به شاخهی release
دارد.
میتوانید شاخهی رفع اشکال را به release
ادغام کنید و همچنین همان شاخه را با گزینهی merge -s ours
به master
ادغام کنید (حتی اگر رفع اشکال قبلاً در آن باشد) تا وقتی بعداً شاخهی release
را دوباره ادغام میکنید، تعارضی از رفع اشکال پیش نیاید.
ادغام Subtree (Subtree Merging)
ایدهی ادغام subtree این است که شما دو پروژه دارید، و یکی از این پروژهها به یک زیرشاخه (subdirectory) از پروژهی دیگر نگاشت میشود. وقتی شما یک ادغام subtree مشخص میکنید، Git اغلب به اندازه کافی هوشمند است که تشخیص دهد یکی زیرمجموعهی دیگری است و آنها را به درستی ادغام کند.
ما از طریق یک مثال جلو میرویم: اضافه کردن یک پروژهی جداگانه به یک پروژهی موجود و سپس ادغام کدهای پروژهی دوم در یک زیرشاخه از پروژهی اول.
ابتدا، ما برنامهی Rack را به پروژهمان اضافه میکنیم. پروژهی Rack را به عنوان یک remote reference به پروژهی خودمان اضافه میکنیم و سپس آن را در یک شاخهی مجزا checkout میکنیم:
$ git remote add rack_remote https://github.com/rack/rack
$ git fetch rack_remote --no-tags
warning: no common commits
remote: Counting objects: 3184, done.
remote: Compressing objects: 100% (1465/1465), done.
remote: Total 3184 (delta 1952), reused 2770 (delta 1675)
Receiving objects: 100% (3184/3184), 677.42 KiB | 4 KiB/s, done.
Resolving deltas: 100% (1952/1952), done.
From https://github.com/rack/rack
* [new branch] build -> rack_remote/build
* [new branch] master -> rack_remote/master
* [new branch] rack-0.4 -> rack_remote/rack-0.4
* [new branch] rack-0.9 -> rack_remote/rack-0.9
$ git checkout -b rack_branch rack_remote/master
Branch rack_branch set up to track remote branch refs/remotes/rack_remote/master.
Switched to a new branch "rack_branch"
حالا ما ریشهی پروژهی Rack را در شاخهی rack_branch
داریم و پروژهی خودمان را در شاخهی master
.
اگر یکی را checkout کنید و سپس دیگری را، میتوانید ببینید که ریشههای متفاوتی دارند:
$ ls
AUTHORS KNOWN-ISSUES Rakefile contrib lib
COPYING README bin example test
$ git checkout master
Switched to branch "master"
$ ls
README
این کمی مفهوم عجیبی است. همهی شاخههای موجود در مخزن شما لزوماً شاخههای یک پروژه نیستند. این کار رایج نیست، چون به ندرت کاربردی دارد، اما کاملاً ساده است که شاخههایی با تاریخچههای کاملاً متفاوت داشته باشید.
در این حالت، ما میخواهیم پروژهی Rack را به عنوان یک زیرشاخه به پروژهی master
بیاوریم.
ما میتوانیم این کار را در Git با دستور git read-tree
انجام دهیم.
شما در بخش (Git Internals) بیشتر دربارهی read-tree
و ابزارهای مرتبطش یاد خواهید گرفت، اما فعلاً بدانید که این دستور درخت ریشهی یک شاخه را به staging area و working directory فعلی شما میخواند.
ما به شاخهی master
برگشتیم، و شاخهی rack_branch
را به زیرشاخهی rack
در شاخهی master
پروژهی اصلی خودمان کشیدیم:
$ git read-tree --prefix=rack/ -u rack_branch
وقتی commit میکنیم، به نظر میرسد همهی فایلهای Rack زیر آن زیرشاخه قرار دارند — انگار آنها را از یک tarball کپی کردهایم. نکتهی جالب این است که ما میتوانیم تغییرات را نسبتاً راحت بین شاخهها ادغام کنیم. پس اگر پروژهی Rack بهروزرسانی شد، میتوانیم تغییرات upstream را با سوییچ کردن به آن شاخه و pull گرفتن دریافت کنیم:
$ git checkout rack_branch
$ git pull
سپس میتوانیم آن تغییرات را دوباره به شاخهی master
ادغام کنیم.
برای گرفتن تغییرات و آماده کردن پیام commit بهطور خودکار، از گزینهی --squash
همراه با استراتژی ادغام recursive و گزینهی -Xsubtree
استفاده کنید.
استراتژی recursive اینجا پیشفرض است، اما برای وضوح آن را ذکر میکنیم.
$ git checkout master
$ git merge --squash -s recursive -Xsubtree=rack rack_branch
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested
همهی تغییرات پروژهی Rack ادغام شدهاند و آمادهاند که به صورت محلی commit شوند.
شما همچنین میتوانید برعکس این کار را انجام دهید — تغییراتی را در زیرشاخهی rack
از شاخهی master
انجام دهید و سپس آنها را به شاخهی rack_branch
ادغام کنید تا برای نگهدارندگان ارسال یا به upstream push شوند.
این روش به ما یک workflow مشابه workflow زیرماژولها (submodules) میدهد، بدون اینکه از submodules استفاده کنیم (که در بخش سابماژول ها (Submodules) آن را پوشش خواهیم داد). ما میتوانیم شاخههایی با پروژههای مرتبط دیگر را در مخزن خود نگه داریم و هر از گاهی آنها را به پروژهی اصلی ادغام کنیم. این روش در بعضی جنبهها خوب است، مثلاً همهی کدها در یکجا commit میشوند. اما معایبی هم دارد، مثلاً کمی پیچیدهتر است و احتمال خطا در یکپارچهسازی مجدد تغییرات یا push تصادفی یک شاخه به یک مخزن غیرمرتبط بیشتر میشود.
یک نکتهی عجیب دیگر این است که برای گرفتن diff بین آنچه در زیرشاخهی rack
دارید و کدی که در شاخهی rack_branch
وجود دارد — برای اینکه ببینید آیا نیاز به ادغام دارند یا نه — نمیتوانید از دستور معمول diff
استفاده کنید.
در عوض باید git diff-tree
را با شاخهای که میخواهید با آن مقایسه کنید اجرا کنید:
$ git diff-tree -p rack_branch
یا، برای مقایسهی آنچه در زیرشاخهی rack
دارید با آنچه شاخهی master
روی سرور آخرین بار که fetch کردهاید بوده است، میتوانید اجرا کنید:
$ git diff-tree -p rack_remote/master