ادغام در گیت معمولاً نسبتاً آسان است. از آنجا که گیت ادغام شاخهای دیگر را چندین بار آسان میکند، این بدان معناست که میتوانید یک شاخه بسیار طولانیمدت داشته باشید و در عین حال آن را بهروز نگه دارید، بهگونهای که اغلب تعارضات کوچک را حل کنید، به جای اینکه در پایان با یک تعارض عظیم و ناگهانی مواجه شوید.
با این حال، گاهی اوقات تعارضات پیچیدهای پیش میآید. برخلاف برخی سیستمهای کنترل نسخه دیگر، گیت سعی نمیکند بسیار هوشمندانه تعارضهای ادغام را به صورت خودکار حل کند. فلسفه گیت این است که در تعیین اینکه آیا راهحل ادغام بدون ابهام است هوشمندانه عمل کند، اما اگر تعارضی وجود داشته باشد، تلاش نمیکند به طور خودکار و هوشمندانه آن را حل کند. بنابراین، اگر برای ادغام دو شاخهای که سریع از هم جدا شدهاند زمان زیادی صبر کنید، ممکن است با مشکلاتی مواجه شوید.
در این بخش، به بررسی برخی از این مشکلات و ابزارهایی که گیت برای مدیریت این موقعیتهای پیچیده در اختیار شما میگذارد، میپردازیم. همچنین انواع مختلف و غیرمعمول ادغامهایی که میتوانید انجام دهید را بررسی میکنیم و نحوه بازگرداندن ادغامهایی که انجام دادهاید را نیز مرور خواهیم کرد.
در حالی که برخی اصول اولیه حل تعارضهای ادغام را در ch03-git-branching.asc پوشش دادیم، برای تعارضهای پیچیدهتر، گیت چند ابزار فراهم میکند تا به شما کمک کند بفهمید چه اتفاقی میافتد و چگونه بهتر با تعارض برخورد کنید.
اول از همه، اگر ممکن است، قبل از انجام ادغامی که ممکن است تعارض داشته باشد، مطمئن شوید که شاخه کاری شما پاک (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.حالا چند گزینه داریم.
اول، بیایید بررسی کنیم چگونه از این وضعیت خارج شویم.
اگر انتظار تعارض نداشتید و نمیخواهید فعلاً با آن درگیر شوید، میتوانید به سادگی با دستور 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 را اجرا کنید تا مخزن شما به آخرین حالت کامیت شده بازگردد. توجه داشته باشید که هر تغییر ذخیرهنشدهای از دست خواهد رفت؛ پس مطمئن شوید که هیچ کدام از تغییرات خود را نمیخواهید حفظ کنید.
در این مورد خاص، تداخلها به دلیل فاصلهها هستند. این موضوع را میدانیم چون مسئله ساده است، اما در موارد واقعی هم وقتی به تداخل نگاه میکنید، بهراحتی قابل تشخیص است چون هر خط در یک طرف حذف شده و دوباره در طرف دیگر اضافه شده است. بهطور پیشفرض، گیت تمام این خطوط را بهعنوان تغییر یافته میبیند و نمیتواند فایلها را ادغام کند.
با این حال، استراتژی پیشفرض ادغام میتواند آرگومانهایی دریافت کند که برخی از آنها مربوط به نادیده گرفتن صحیح فاصلهها هستند. اگر در یک ادغام با مشکلات زیادی درباره فاصله مواجه شدید، میتوانید بهسادگی ادغام را لغو کنید و دوباره اجرا کنید، اینبار با گزینههای -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(-)از آنجا که در این مورد تغییرات واقعی فایلها با هم تداخل نداشتند، وقتی فاصلهها را نادیده میگیریم، همه چیز بهخوبی ادغام میشود.
این قابلیت بسیار مفید است اگر در تیم شما کسی باشد که گاهی همه چیز را از فضاها به تبها یا برعکس تغییر فرمت میدهد.
اگرچه گیت پیشپردازش فاصلهها را بهخوبی مدیریت میکند، انواع دیگری از تغییرات وجود دارند که شاید گیت نتواند بهطور خودکار آنها را مدیریت کند، اما میتوان آنها را با اسکریپت اصلاح کرد. بهعنوان مثال، فرض کنیم گیت نتواند تغییر فاصلهها را مدیریت کند و مجبور باشیم این کار را دستی انجام دهیم.
کاری که باید انجام دهیم این است که فایل مورد نظر برای ادغام را قبل از تلاش برای ادغام واقعی، با برنامهای مانند 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شاید به دلیلی از نتیجه حل تعارض راضی نیستیم، یا شاید ویرایش دستی یک یا هر دو طرف هنوز خوب جواب نداده و به زمینهی بیشتری نیاز داریم.
بیایید مثال را کمی تغییر دهیم. در این مثال، دو شاخه بلندمدت داریم که هر کدام چند کامیت در خود دارند ولی هنگام ادغام، یک تعارض محتوایی واقعی ایجاد میکنند.
$ 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 را میپذیرد که راه بسیار سریعی برای انتخاب فقط یکی از دو طرف بدون انجام ادغام کامل است.
این برای تعارضهای فایلهای باینری که میتوانید به سادگی یکی از دو طرف را انتخاب کنید، یا زمانی که فقط میخواهید برخی فایلها را از شاخه دیگر ادغام کنید، بسیار مفید است — میتوانید ادغام را انجام دهید و سپس قبل از کامیت، فایلهای خاصی را از یکی از دو طرف چکاوت کنید.
ابزار مفید دیگری که هنگام حل تعارضهای ادغام کاربرد دارد، git log است.
این میتواند به شما کمک کند تا بفهمید چه چیزی ممکن است به ایجاد تعارضها کمک کرده باشد.
مرور کمی از تاریخچه برای به یاد آوردن اینکه چرا دو خط توسعه به یک بخش مشابه از کد دست زدهاند، گاهی بسیار مفید است.
برای دریافت فهرست کامل تمام کامیتهای منحصر به فردی که در هر یک از شاخههای دخیل در این ادغام وجود دارند، میتوانیم از نحو «سه نقطه» که در ch07-git-tools.asc یاد گرفتهایم استفاده کنیم.
$ 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 اجرا کنید، فقط تفاوتهای مربوط به فایلی که در نهایت دچار تعارض شده را خواهید دید.
این میتواند بسیار مفید باشد تا سریعاً زمینهای که به شما کمک میکند بفهمید چرا تعارض رخ داده و چگونه میتوان آن را با هوشمندی بیشتری حل کرد را به دست آورید.
از آنجا که گیت هر نتیجه ادغامی که موفق باشد را مرحلهبندی میکند، وقتی در حالت ادغام دچار تعارض هستید و 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()حالا که میدانید چگونه یک کامیت ادغام ایجاد کنید، احتمالاً بعضی را اشتباهی خواهید ساخت. یکی از چیزهای عالی در کار با گیت این است که اشتباه کردن اشکالی ندارد، چون امکان اصلاح آن وجود دارد و در بسیاری موارد آسان است.
کامیتهای ادغام هم همینطور هستند.
فرض کنید روی یک شاخه موضوعی کار میکردید، اشتباهی آن را به master ادغام کردید و حالا تاریخچه کامیت شما اینگونه شده است:
دو راه برای رفع این مشکل وجود دارد، بسته به اینکه نتیجه دلخواه شما چیست.
اگر کامیت ادغام ناخواسته فقط روی مخزن محلی شما وجود دارد، سادهترین و بهترین راه این است که شاخهها را جابجا کنید تا به جایگاه دلخواه برسند.
در بیشتر موارد، اگر بعد از ادغام اشتباهی git merge، دستور git reset --hard HEAD~ را اجرا کنید، اشارهگر شاخهها را به این شکل تنظیم میکند:
ما پیشتر reset را در ch07-git-tools.asc پوشش دادهایم، بنابراین نباید دشوار باشد که بفهمید اینجا چه اتفاقی میافتد.
یک یادآوری سریع: reset --hard معمولاً سه مرحله دارد:
-
حرکت دادن شاخهای که HEAD به آن اشاره میکند. در این مورد، میخواهیم
masterرا به جایی که پیش از کامیت ادغام بود (C6) برگردانیم. -
همسانسازی شاخص (index) با HEAD.
-
همسانسازی دایرکتوری کاری با شاخص.
نقطه ضعف این روش این است که تاریخچه را بازنویسی میکند، که در مخزن مشترک میتواند مشکلساز باشد.
برای اطلاعات بیشتر در این زمینه به ch03-git-branching.asc مراجعه کنید؛ خلاصه این است که اگر دیگران کامیتهایی که شما بازنویسی میکنید را دارند، بهتر است از reset استفاده نکنید.
همچنین این روش زمانی کار نمیکند که کامیتهای دیگری بعد از ادغام ساخته شده باشند؛ جابجایی اشارهگرها باعث از دست رفتن آن تغییرات خواهد شد.
اگر جابجا کردن اشارهگر شاخهها برای شما مناسب نیست، گیت این امکان را میدهد که کامیت جدیدی بسازید که تمام تغییرات یک کامیت موجود را برگرداند. گیت این عملیات را “revert” مینامد و در این موقعیت خاص، دستور به این شکل است:
$ git revert -m 1 HEAD
[master b1d8379] Revert "Merge branch 'topic'"پرچم -m 1 مشخص میکند کدام والد “mainline” است و باید نگه داشته شود.
وقتی یک ادغام را روی HEAD اجرا میکنید (git merge topic)، کامیت جدید دو والد دارد: اولی HEAD (C6) است و دومی سر شاخهای که ادغام شده (C4).
در اینجا میخواهیم همه تغییراتی که از ادغام والد شماره ۲ (C4) آمده را برگردانیم و همه محتویات والد شماره ۱ (C6) را نگه داریم.
تاریخچه با کامیت ریورت شده اینگونه خواهد بود:
کامیت جدید ^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 کاملاً ادغام شده است.
تا اینجا ما ادغام معمول دو شاخه را پوشش دادیم که معمولاً با استراتژی ادغام به نام “recursive” انجام میشود. اما روشهای دیگری هم برای ادغام شاخهها وجود دارد. بیایید چند مورد از آنها را سریع بررسی کنیم.
اول از همه، یک قابلیت مفید دیگر در حالت معمول ادغام “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 را دوباره ادغام میکنید، تعارضی از رفع اشکال پیش نیاید.




