Git
Chapters ▾ 2nd Edition

7.8 Git Alətləri - İnkişaf etmiş Birləşmə

İnkişaf etmiş Birləşmə

Git-də birləşdirmə əsasən olduqca asandır. Git başqa bir branch-ı dəfələrlə birləşdirməyi asanlaşdırdığından, uzunmüddətli bir branch-a sahib ola biləcəyinizi ifadə edir, ancaq irəlilədiyiniz müddətdə onu saxlaya bilərsiniz, yəni, çox böyük bir konfliktə təəccüblənməkdənsə kiçik münaqişələri tez-tez həll etməklə, seriyanında sonunda böyük problemlərdən yan keçə bilərsiniz.

Ancaq bəzən çətin konfliktlər əmələ gəlir. Bəzi digər versiyalara nəzarət sistemlərindən fərqli olaraq, Git münaqişələrin həlli məsələsində həddən artıq ağıllı olmağa çalışmır. Git-in fəlsəfəsi birləşmə həllinin nə vaxt olacağını müəyyənləşdirməkdə ağıllı olmaqdır, ancaq konflikt varsa, avtomatik olaraq həll etmək üçün ağıllı olmağa çalışmır. Buna görə də, tez ayrılan iki branch-ı birləşdirmək üçün çox gözləsəniz, bəzi problemlərlə qarşılaşa bilərsiniz.

Bu hissədə bəzi problemlərin nədən ibarət olacağını və Git-in bu daha çətin vəziyyətləri həll etmək üçün sizə hansı vasitələri verdiyini nəzərdən keçirəcəyik. Ayrıca edə biləcəyiniz fərqli, qeyri-standart birləşmələrin bəzilərini də əhatə edəcəyik, həm də etdiyiniz birləşmələri necə geri çəkə biləcəyimizə baxacağıq.

Konfliktləri Birləşdirmə

Daha mürəkkəb konfliktlər üçün Əsas Birləşmə Konfiliktləri-də birləşmə konfliktlərinin həlli ilə bağlı bəzi əsasları izah etsək də, Git nəyin baş verdiyini və münaqişəni daha yaxşı necə həll edəcəyinizi anlamağa kömək edəcək bir neçə vasitə təqdim edir.

Əvvəlcə, əgər mümkündürsə, konfliktlər ola biləcək birləşməni etməzdən əvvəl işçi qovluğunun təmiz olduğundan əmin olun. İşiniz davam edirsə, ya müvəqqəti bir branch-a verin və ya zibilə atın. Bu, burada etdiyiniz hər şeyi geri qaytara biləcəyiniz üçün edir. Birləşdirməyə çalışdığınız zaman iş qovluğunda qeyd olunmamış dəyişikliklər varsa, bu məsləhətlərdən bəziləri bu işi qorumağa kömək edə bilər.

Çox sadə bir misal verək. Üzərində Hello world yazdıran super sadə bir Ruby faylımız var.

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

hello()

Qovluğumuzda whitespace adlı yeni bir branch yaradırıq və bütün Unix sətir sonlarını DOS xətt sonlarına dəyişdirərək sənədin hər sətirini ancaq whitespace ilə dəyişdiririk. Sonra “hello world” yazısını “hello mundo” olaraq dəyişdiririk.

$ 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(-)

İndi yenidən master branch-a qayıdırıq və funksiya üçün bəzi sənədlər əlavə edirik.

$ 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(+)

İndi isə whitespace branch-da birləşdirmə etməyə çalışırıq və whitespace dəyişikliklərinə görə konfliktlər görəcəyik.

$ git merge whitespace
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Automatic merge failed; fix conflicts and then commit the result.

Birləşməni Ləğv etmək

İndi bizim bir neçə seçimimiz var. Əvvəlcə bu vəziyyətdən necə çıxacağımızı izah edək. Əgər siz konfliktlərin olacağını gözləmirdinizsə və vəziyyətlə hələ çox işləmək istəmirsinizsə, sadəcə git merge --abort ilə birləşməni ləğv edə bilərsiniz.

$ git status -sb
## master
UU hello.rb

$ git merge --abort

$ git status -sb
## master

git merge --abort seçimi birləşmədən əvvəlki vəziyyətə qayıtmağa çalışır. Onun mükəmməl bir şəkildə işləyə bilməyəcəyi yeganə hallar, işlədiyiniz qovluqda açılmamış, buraxılmamış dəyişikliklərin olması ola bilər, əks halda yaxşı işləməlidir.

Hər hansı bir səbəbdən yenidən başlamaq istəsəniz, təkrar git reset --hard HEAD işlədə bilərsiniz və bu zaman depolarınız son vəziyyətinə qaytarılacaq. Yadda saxlayın ki, hər hansı bir iş itirilə bilər, ona görə də dəyişikliklərinizdən heç birini istəmədiyinizdən əmin olun.

Whitespace-ə Məhəl Qoymamaq

Bu konkret vəziyyətdə, konfliktlər whitespace ilə əlaqədardır. Bunu bilirik, çünki məsələ sadədir, ancaq real vəziyyətlərdə konfliktə baxanda izah etmək çox asandır ki, hər xətt bir tərəfdən çıxarılır və digər tərəfdən yenidən əlavə olunur. Default olaraq, Git bu sətirlərin hamısının dəyişdirildiyini görür, buna görə də faylları birləşdirə bilmir.

Default birləşdirmə strategiyası arqumentlər götürə bilər və onlardan bir neçəsi whitespace dəyişikliklərinə məhəl qoymur. Birləşmədə çox sayda whitespace probleminizin olduğunu görsəniz, bu dəfə -Xignore-all-space və ya -Xignore-space-change ilə yenidən ləğv edib təkrar edə bilərsiniz. Birinci seçim xətləri müqayisə edərkən whitespace-i tamamilə gözdən keçirir, ikincisi bir və ya daha çox whitespace simvollarının ardıcıllığını ekvivalent olaraq qəbul edir.

$ 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(-)

Bu vəziyyətdə, faktiki fayl dəyişiklikləri bir-birinə zidd olmadığına görə, whitespace dəyişikliklərini görməməzlikdən gəldikdə, hər şey yaxşı birləşir. Komandanızda bəzən boşluqlardan nişanlara reformat etməyi sevən və ya əksinə edən biri varsa, bu sizin üçün bir xilaskardır.

Manual Faylı Yenidən Birləşdirmə

Git whitespace-i əvvəlcədən emal etməyi bacarsa da, dəyişikliklərin digər növləri vardır ki, onlarda Git avtomatik idarə edə bilmir, lakin dəyişdirilə bilən düzəlişlərdir. Misal olaraq, Git’in whitespace dəyişikliyini həll edə bilmədiyini iddia edək və bunu manual şəkildə edək.

Həqiqətən etməli olduğumuz şey, faktiki faylı birləşdirməyə cəhd etmədən əvvəl dos2unix proqramı ilə birləşdirməyə çalışdığımız faylı işə salmaqdır. Bəs bunu necə edə bilərik?

Əvvəlcə birləşmə konflikti vəziyyətinə giririk. Sonra, mənim versiyamın nüsxələrini, onların versiyasını (birləşdirdiyimiz branch-dan) və ümumi versiyadan (hər iki tərəfin branch-dan çıxdığı yerdən) surətlərini almaq istəyirik. Sonra onların tərəfini və ya öz tərəfimizi düzəltmək istəyirik və yenidən bu tək fayl üçün yenidən birləşdirməyə çalışırıq.

Üç fayl versiyasını əldə etmək əslində olduqca asandır. Git, bu versiyaların hamısını əlaqəli nömrələri olan “stages” indeksində saxlayır. Stage 1 ortaq kökdür, stage 2 sizin versiyanızdır və stage 3 MERGE_HEAD-dan, birləşdiyiniz versiyadır (“theirs”).

Konfliktli faylın bu versiyalarının hər birinin bir nüsxəsini git show əmri və xüsusi bir sintaksis ilə çıxara bilərsiniz.

$ git show :1:hello.rb > hello.common.rb
$ git show :2:hello.rb > hello.ours.rb
$ git show :3:hello.rb > hello.theirs.rb

Bir az daha sərt keçid əldə etmək istəyirsinizsə, bu faylların hər biri üçün Git bloklarının əsl SHA-1-lərini əldə etmək üçün ls-files -u Plumbing əmrindən də istifadə edə bilərsiniz.

$ git ls-files -u
100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1	hello.rb
100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2	hello.rb
100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3	hello.rb

:1:hello.rb SHA-1 çubuğunu axtarmaq üçün sadəcə bir stenddir.

İndi işlədiyimiz qovluqda hər üç mərhələnin məzmunu olduğundan, whitespace məsələsini həll etmək üçün onları manual şəkildə düzəldə bilərik və yalnız bunu az tanınan git merge-file əmri ilə faylı yenidən birləşdirə bilərik.

$ 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()

Bu anda faylı qəşəng bir şəkildə birləşdirdik. Əslində, bu, ignore-space-change seçimindən daha yaxşı işləyir, çünki bu, sadəcə boş yerə dəyişiklik etmədən yerinə boşluq dəyişikliklərini düzəldir. ignore-space-change birləşməsində, işləri həqiqətən qarışıq hala gətirərək DOS xətti ucları ilə bir neçə xətt əldə edirik.

Əslində bir tərəf və ya digəri arasında dəyişdirilənin nə olduğu ilə əlaqədar bu tapşırığı bitirmədən əvvəl bir fikir əldə etmək istəyirsinizsə, git diff-dan bu mərhələlərdən hər hansı birində birləşdirmə nəticəsində iş qovluğunuzda nə əmri verdiyinizi müqayisə etməsini istəyə bilərsiniz. Hamısını izah edək.

Nəticəni birləşdirmədən əvvəl branch-nızda nə əldə etdiyinizi görmək, başqa sözlə birləşmənin nəyə tətbiq olunduğunu görmək üçün git diff --ours proqramını işlədə bilərsiniz.

$ 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()

Beləliklə, burada asanlıqla görə bilərik ki, branch-mızda baş verənlər, əslində bu birləşmə ilə bu faylı tanıdığımız vahid xətti dəyişdirir.

Birləşmənin nəticəsinin onların tərəfindəki vəziyyətdən necə fərqləndiyini görmək istəyiriksə, git diff --theirs işlədə bilərik. Bu və aşağıdakı misalda biz təmizlənmiş hello.theirs.rb faylı ilə deyil, Git-də olanlarla müqayisə etdiyimiz üçün whitespace-dən çıxarmaq üçün -b istifadə etməliyik.

$ 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

Sonda siz faylın git diff --base ilə hər iki tərəfdən necə dəyişdiyini görə bilərsiniz.

$ 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()

Bu zaman git clean əmrini manual şəkildə birləşdirmək üçün yaratdığımız, lakin artıq lazım olmayan əlavə sənədləri silmək üçün istifadə edə bilərik.

$ git clean -f
Removing hello.common.rb
Removing hello.ours.rb
Removing hello.theirs.rb

Konfliktləri Yoxlamaq

Ola bilsin ki, indi hansısa səbəbdən həll ilə razı deyilik və ya bir və ya hər iki tərəfdən manual şəkildə düzəltmək yenə də yaxşı işləmədi və bizim daha çox kontekstə ehtiyacımız var.

Gəlin nümunəni bir az dəyişək. Bu nümunə üçün, hər ikisində bir neçə əmr yerinə yetirən, lakin birləşdikdə qanuni məzmun konflikti yaradan iki daha uzun branch var.

$ 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

İndi yalnız üç master branch-da və üç mundo branch-da yaşayan unikal əmrlər var. mundo branch-ı birləşdirməyə çalışarkən konflikt yaranır.

$ git merge mundo
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Automatic merge failed; fix conflicts and then commit the result.

Biz birləşdirmə konfliktinin nə olduğunu görmək istəyirik. Bu zaman faylı açsaq, bu kimi bir şey görərik:

#! /usr/bin/env ruby

def hello
<<<<<<< HEAD
  puts 'hola world'
=======
  puts 'hello mundo'
>>>>>>> mundo
end

hello()

Birləşmənin hər iki tərəfi bu fayla məzmun əlavə etdi, lakin bəziləri bu konfliktə səbəb olan yeri dəyişdirdi.

Bu konfliktin necə yarandığını müəyyənləşdirmək üçün indi əlinizdə olan bir neçə vasitəni araşdıraq. Bəlkə də bu konflikti dəqiq necə həll edə biləcəyiniz tam bəlli deyil. Daha çox kontekstə ehtiyacınız var.

Digər bir faydalı vasitə --conflict seçimi ilə git checkout-dur. Bu, faylı yenidən yoxlayır və konflikt birləşmə işarələrini dəyişdirir. O həm də, markerləri yenidən qurmaq və yenidən həll etmək istəsəniz də faydalı ola bilər.

Siz --conflict ya diff3 ya da merge (standart olandır) keçirə bilərsiniz. Əgər siz onu diff3-ə keçirsəniz, bu zaman Git konflikt markerlərinin bir az fərqli versiyasını istifadə edəcək, nəinki “ours” və “theirs” versiyalarını, həm də daha çox kontekst vermək üçün “base” versiyasını əlavə edəcək.

$ git checkout --conflict=diff3 hello.rb

Bunu işlədikdən sonra əvəzində fayl belə görünəcək:

#! /usr/bin/env ruby

def hello
<<<<<<< ours
  puts 'hola world'
||||||| base
  puts 'hello world'
=======
  puts 'hello mundo'
>>>>>>> theirs
end

hello()

Bu formatı bəyənsəniz, merge.conflictstyle parametrini diff3-ə qoyaraq gələcək birləşdirmə konfliktləri üçün standart olaraq təyin edə bilərsiniz.

$ git config --global merge.conflictstyle diff3

git checkout əmri variantları birləşdirmədən yalnız bir tərəfi və ya digərini seçmək üçün həqiqətən sürətli bir yol olan --ours--theirs seçimlərini də istifadə edə bilərsiniz.

Bu, sadəcə bir tərəfi seçə biləcəyiniz və ya müəyyən bir faylları başqa bir branch-dan birləşdirmək istədiyiniz ikili sənədlərin ixtilafları üçün xüsusilə faydalı ola bilər. Yəni, birləşdirmədən və əmr vermədən əvvəl müəyyən faylları bir tərəfdən və ya digər tərəfdən yoxlamaq olar.

Birləşdirmə Log-u

Birləşmə konfliktlərink həll edərkən başqa bir faydalı vasitə isə git log-dur. Bu, konfliktlərə səbəb olan mövzularda kontekst əldə etməyə kömək edə bilər. İki inkişaf xəttinin eyni kod sahəsinə toxunduğunu xatırlamaq üçün bir az tarixə nəzər salmaq bəzən faydalı ola bilir.

Bu birləşmədə iştirak edən hər iki branch-a daxil olan bütün unikal əmrlərin tam siyahısını əldə etmək üçün Üçqat Nöqtə-da öyrəndiyimiz “triple dot” sintaksisindən istifadə edə bilərik.

$ 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'

Bu iştirak olunan altı ümumi tapşırığın, habelə hər bir inkişafın hansı xətt üzərində olmasının gözəl bir siyahısıdır.

Daha konkret kontekst vermək üçün bunu daha da asanlaşdıra bilərik. git log-a --merge seçimini əlavə etsək, yalnız konflikt yaradan bir fayla toxunan birləşmənin hər iki tərəfindəki əmrləri göstərəcəkdir.

$ git log --oneline --left-right --merge
< 694971d Update phrase to 'hola world'
> c3ffff1 Change text to 'hello mundo'

Bunun əvəzinə -p seçimi ilə işləsəniz, konfliktlə nəticələnən fayldan yalnız fərqi əldə edirsiniz. Bu, nəyə görə bir şeyin zidd olduğunu və onu daha ağıllı şəkildə həll etməyinizi başa düşməyinizə kömək etmək üçün lazım olan konteksti verməkdə həqiqətən faydalı ola bilər.

Kombinə olunmuş Diff Formatı

Git-in uğurlu olan birləşmə nəticələri mərhələli olduğundan, konflikt birləşdirmə vəziyyətində git diff işlətsəniz hələ də konflikdə olanı əldə edəcəksiniz. Bu hələ nəyi həll etməli olduğunuzu görmək üçün faydalı ola bilər.

Birləşdirmə konfliktindən sonra birbaşa git diff işlətdiyiniz zaman sizə olduqca unikal fərqli fərqli bir çıxış formatında məlumat verəcəkdir.

$ 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()

Format “Combined Diff” adlanır və hər bir sətrin yanında iki məlumat sütunu verir. Birinci sütun, bu sətir “ours” branch-mızla işləyən qovluğunuzdakı fayl arasında fərqli olduğunu (əlavə edilmiş və ya silinmiş) göstərir, ikinci sütun isə “theirs” branch ilə iş qovluğunun surətinin eyni olduğunu göstərir.

Beləliklə, bu misalda görə bilərsiniz ki, <<<<<<< and >>>>>>> xətləri işləmə nüsxəsindədir, lakin birləşmənin hər iki tərəfində deyil. Bu o mənanı verir ki, birləşmə vasitəsi kontekstimiz üçün onları oraya yapışdırıb saxladı, lakin biz onları silməyi gözləyirdik.

Konflikti həll edib yenidən git diff tətbiq etsək, eyni şeyi görəcəyik, amma bu bir az daha faydalıdır.

$ 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()

Bu bizə göstərir ki, “hola world' işləmə nüsxəsində deyil, amma bizim tərəfimizdə idi, ``hello mundo” isə onların tərəfində idi, amma işləmə nüsxəsində deyildi və nəhayət “hello mundo” hər iki tərəfdə olmadığını halda işləyən nüsxədə idi. Bu qərar vermədən əvvəl nəzərdən keçirmək üçün faydalı ola bilər.

Siz bütün birləşdirmələrdə faktdan sonra məsələlərin necə həll olunduğunu görmək üçün git log-dan istifadə edə bilərsiniz. Birləşdirmə git show-u işlətdiyiniz təqdirdə və ya bir git log -p--cc seçimi əlavə etsəniz, Git bu formatı çıxaracaqdır (bu standart olaraq yalnız birləşməmələr üçün patch-ları göstərir)

This shows us that “hola world” was in our side but not in the working copy, that “hello mundo” was in their side but not in the working copy and finally that “hola mundo” was not in either side but is now in the working copy. This can be useful to review before committing the resolution.

$ 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()

Birləşdirməni Ləğv Etmək

Birləşdirmə əmrini necə yaratmağı bildiyiniz üçün səhv etmə ehtimalınız var. Git ilə işləməyin ən yaxşı tərəflərindən biri də odur ki, səhv etmək olar, çünki onları düzəltmək mümkündür (və bir çox hallarda asandır).

Birləşmə əmrləri heç fərqlənmir. Deyək ki, bir mövzu branch-da işə başlamısınız, təsadüfən onu master-ə birləşdirdiniz və indi əmr tarixçəniz belə görünür:

Təsadüfi birləşdirmə commit-i
Figure 138. Təsadüfi birləşdirmə commit-i

İstədiyiniz nəticənin nə olduğundan asılı olaraq bu problemə yaxınlaşmanın iki yolu var.

Referansları Düzəltmək

İstənilməyən birləşmə əməliyyatı yalnız yerli depolarınızda varsa, ən asan və ən yaxşı həll yolu istədiyiniz yeri göstərmələri üçün branch-ları köçürməkdir. Əksər hallarda, git reset --hard HEAD~ ilə səhv edilmiş git merge-i izləsəniz, bu branch göstəricilərini yenidən quracaq və buna görə də onlar belə görünəcəklər:

`git reset --hard HEAD~`-dan sonra tarixçə
Figure 139. git reset --hard HEAD~-dan sonra tarixçə

Geri qayıtmağı Reset Demystified-də izah etdik, buna görə burada nələrin baş verdiyini anlamaq çox çətin olmayacaq. Sürətli bir xatırlatma: reset --hard ümumiyyətlə üç addımdan keçir:

  1. Branch-ın HEAD nöqtələrini bu yerə köçürün. Bu vəziyyətdə, master birləşmədən (C6) əvvəl olduğu yerə köçürmək istəyirik.

  2. İndeksi HEAD kimi göstərin.

  3. İşləmə qovluğunu indeks kimi göstərin.

Bu yanaşmanın mənfi tərəfi ortaq bir depo ilə problem yarada bilən tarixin yenidən yazılmasıdır. Nə baş verə biləcəyi barədə daha çox məlumat almaq üçün Rebasing-in Təhlükələri-ə baxın; qısa versiyası odur ki, başqa insanların yazdığınız əmrləri varsa, yəqin ki, reset etməkdən çəkinməlisiniz. Birləşdirmədən bəri başqa əmrlər yaradılıbsa bu yanaşma da işləməyəcək; refs hərəkət bu dəyişiklikləri itirəcəkdir.

Commit-ləri Tərs Çevirmək

Əgər ətrafdakı branch işarətçisini sizin üçün işləməyəcəksə, Git, hazırda mövcud olandan bütün dəyişiklikləri ləğv edən yeni bir commit vermək seçimi verir. Git ssenaridə bu əməliyyatı “revert” adlandırır və siz onu bu şəkildə çağıracaqsınız:

$ git revert -m 1 HEAD
[master b1d8379] Revert "Merge branch 'topic'"

-m 1 flag-ı hansı valideynin “mainline” olduğunu göstərir və saxlanılmalıdır. HEAD-a (git merge topic) birləşdirməyə çağırdığınız zaman, yeni əmrin iki valideyni olur: birincisi - HEAD (C6), ikincisi - (C4) birləşən branch-ın ucu. Bu vəziyyətdə, bütün məzmunu 1 saylı valideyndən (C6) qorumaqla, 2 saylı valideynə (C4) birləşdirməklə daxil edilmiş bütün dəyişiklikləri geri qaytarmaq istəyirik. Commiti geri alma tarixi belə görünür:

`git revert -m 1`-dən sonrakı tarixçə
Figure 140. git revert -m 1-dən sonrakı tarixçə

Yeni commit ^M C6 ilə eyni məzmuna malikdir, buna görə də əgər buradan başlayaraq heç bir birləşmə baş verməyibsə, hal hazırda başlamamış əmrlər hələ də HEAD-in tarixçəsində qalır. Siz topic-i yenidən master-ə birləşdirməyə çalışsanız, Git çaşacaq:

$ git merge topic
Already up-to-date.

Artıq master-də əlçatmaz bir topic yoxdur. Daha pisi isə, əgər topic-ə iş əlavə etsəniz və yenidən birləşdirsəniz, Git yalnız geri qaytarıldıqdan sonrakı dəyişiklikləri göstərəcək:

Yaxşı olmayan Birləşdirmə Tarixçəsi
Figure 141. Yaxşı olmayan Birləşdirmə Tarixçəsi

Bunun ən yaxşı yolu orijinal birləşməni geri qaytarmaqdır, çünki indi geri qaytarılmış dəyişiklikləri gətirmək, daha sonra isə yeni birləşmə commit-i yaratmaq istəyirsiniz:

$ git revert ^M
[master 09f0126] Revert "Revert "Merge branch 'topic'""
$ git merge topic
Geri çevrilmiş birləşməni yenidən birləşdirdikdən sonrakı tarixçə
Figure 142. Geri çevrilmiş birləşməni yenidən birləşdirdikdən sonrakı tarixçə

Bu nümunədə,M^M`ləğv olunur. `^^M, C3C4 dəyişiklikləri təsirli bir şəkildə birləşdirir və C7-nin dəyişikliklərindəki` C8 birləşir, buna görə də `topic tamamilə birləşdirilmişdir.

Birləşdirmənin Digər Tipləri

Biz indiyə qədər birləşmənin “recursive” strategiyası adlandırılan iki branch-ın normal birləşməsini izah etdik. Bununla birlikdə branch-ları birləşdirməyin başqa yolları da var. Bir neçəsini sürətlə izah edək.

Our və ya Theirs Üstünlükləri

Əvvəla, normal “recursive” birləşmə rejimi ilə edə biləcəyimiz başqa bir faydalı şey də var. ignore-all-spaceignore-space-change variantlarını -X ilə keçdiyini gördük, ancaq Git’in bir tərəfə və ya digərinə konflikt görəndə üstünlük verəcəyini söyləyə bilərik.

Standart olaraq, Git iki branch arasında bir konflikt görəndə, konflikt markerlərini kodunuza əlavə edəcək və faylı konflikt olaraq qeyd edəcək və onu həll etməyə imkan verəcəkdir. Git’in sadəcə konkret bir tərəf seçməsini və konflikti manual olaraq həll etməyinizin əvəzinə digər tərəfi görməməzliyə gəlməsini istəsəniz, birləşdirmə əmrini ya -Xours ya da -Xtheirs-ə verə bilərsiniz.

Git bunu görsə, konflikt markerlərini əlavə etməyəcəkdir. Birləşə bilən fərqlər birləşəcəkdir. Konfliktdə olan hər hansı bir fərq, sadəcə ikili sənədlər daxil olmaqla bütövlükdə göstərdiyiniz tərəfi seçəcəkdir.

Əvvəllər istifadə etdiyimiz “hello world” nümunəsinə qayıtsaq, branch-ımızda birləşməyin konfliktlərə səbəb olduğunu görə bilərik.

$ 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.

Hər şəkildə əgər -Xours və ya `-Xtheirs`işlətsək bu alınmayacaq.

$ 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

Bu vəziyyətdə, o, bir tərəfdən “hello mundo” və digər tərəfdən “hola world” olan sənəddə konflikt nişanları almaq əvəzinə, sadəcə “hola world” olanı seçəcəkdir. Bununla birlikdə, bu branch-dakı digər konfliktli dəyişikliklər uğurla birləşdirilir.

Bu seçim ayrıca fərdi fayl birləşməsi üçün git merge-file --ours kimi bir şey işlədərək əvvəllər gördüyümüz git merge-file əmrinə keçə bilər.

Buna bənzər bir iş görmək istəyirsinizsə, lakin Git-in digər tərəfdən dəyişiklikləri birləşdirməyə cəhd etməməsini istəyirsinizsə, “ours” birləşmə strategiyasından daha sərt bir seçim var. Bu, “ours” rekursiv birləşmə seçimindən fərqlidir.

Bu, əsasən saxta bir birləşmə edəcəkdir. Hər iki branch-la birlikdə valideynlər kimi yeni bir birləşmə qeyd edəcək, ancaq qoşulduğunuz branch-a belə baxmayacaq. Sadəcə cari branch-dakı dəqiq kodu birləşdirmə nəticəsilə qeyd edəcək.

$ git merge -s ours mundo
Merge made by the 'ours' strategy.
$ git diff HEAD HEAD~
$

Gördüyünüz kimi olduğumuz branch-la birləşmənin nəticəsi arasında heç bir fərq yoxdur.

Bu əsasən Git-ə branch-ı daha sonra birləşdirəcək ikən bir branch-ın artıq birləşdirildiyini düşündürmək üçün tez-tez faydalı ola bilər. Məsələn, bir release branch-ını dayandırdığınızı və bunun üçün bir anda yenidən master branch-nıza birləşdirmək istədiyinizi deyək. Bu vaxt master bəzi səhvlər release branch-na geri göndərilməlidir. Siz bugfix branch-ını release branch-na birləşdirə bilərsiniz və eyni zamanda merge -s ours branch-nı master branch-a birləşdirə bilərsiniz (düzəliş artıq olsa da), belə ki, daha sonra yenidən branch-ı birləşdirdiyiniz zaman bugfix-də heç bir konflikt olmur.

Subtree Birləşdirməsi

Subtree birləşdirmə ideyası odur ki, iki layihəniz var və layihələrdən biri digərinin alt kateqoriyası ilə əlaqələndirildiyini əhatə edir. Bir subtree birləşməsi göstərdiyiniz zaman, Git həmin an birinin digərinin subtree-si olduğunu anlamaq və lazımi şəkildə birləşdirmək üçün kifayət qədər ağıllıdır.

Gəlin mövcud bir layihəyə ayrı bir layihə əlavə etmək və sonra ikincinin kodunu birincinin alt bölməsinə birləşdirmək nümunəsini göstərək.

Əvvəlcə Rack tətbiqini layihəmizə əlavə edəcəyik. Rack layihəsini öz layihəmizdə uzaq bir remote olaraq əlavə edəcəyik və sonra öz branch-ında yoxlayacağıq:

$ 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"

İndi rack_branch branch-mızda Rack layihəsinin kökü və master branch-mızda öz layihəmiz var. Birini, daha sonra isə digərini yoxlasanız, fərqli layihə köklərinə sahib olduqlarını görə bilərsiniz:

$ ls
AUTHORS         KNOWN-ISSUES   Rakefile      contrib         lib
COPYING         README         bin           example         test
$ git checkout master
Switched to branch "master"
$ ls
README

Bu qəribə bir anlayışdır. Depodakı branch-ların hamısı eyni layihənin branch-ları olmamalıdır. Çox yaygın deyil, çünki nadir hallarda faydalıdır, lakin branch-ların tamamilə fərqli tarixə sahib olmasını əldə etmək olduqca asandır.

Bu vəziyyətdə, Rack layihəsini alt layihə olaraq master proyektimizə çəkmək istəyirik. Bunu git read-tree ilə Git-də edə bilərik. read-tree və onun dostları haqqında Git’in Daxili İşləri-də daha çox məlumat əldə edəcəksiniz, ancaq indi onun bir branch-ın kök ağacını cari hazırlama sahənizdə və iş qovluğunda oxuduğunu bilin. Yenidən master branch-a qayıtdıq və rack_branch branch-nı əsas layihəmizin master branch-ın rack alt bölməsinə pull edirik:

$ git read-tree --prefix=rack/ -u rack_branch

Commit zamanı, bütün alt Rack fayllarımız alt bölümdə olduğu kimi görünür - sanki biz onları tarball-dan köçürmüşük. Maraqlı olan budur ki, dəyişiklikləri branch-ların birindən digərinə qədər asanlıqla birləşdirə bilərik. Beləliklə, Rack layihəsi yenilənirsə, o branch-a keçərək üstdəki dəyişiklikləri pull edə bilərik:

$ git checkout rack_branch
$ git pull

Sonra biz bu dəyişiklikləri yenidən master branch-mıza birləşdirə bilərik. Dəyişiklikləri pull etmək və commit mesajını qabaqcadan hazırlamaq üçün --squash seçimini, həmçinin recursive birləşmə strategiyasının -Xsubtree seçimini istifadə edin. Rekursiv strategiya burada standartdır, lakin aydınlıq üçün onu da daxil edirik.

$ 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 layihəsindəki bütün dəyişikliklər birləşdirilmiş və yerli olaraq həyata keçirilməyə hazırdır. Siz bunun əksini də edə bilərsiniz - master branch-ınızın rack`subdirectory-sində dəyişikliklər aparın və sonra onları `rack_branch branch-a birləşdirin və onları maintainers-ə təqdim edin və ya yuxarı tərəfə push edin.

Bu, submodule-lərdən istifadə etmədən (biz Alt Modullar-də əhatə edəcəyik) istifadə etmədən submodule-un iş axınına bir qədər bənzəyən bir iş axınına sahib olmaq üçün bir imkan verir. Digər branch layihələri ilə branch-larımızı depolarımızda saxlaya bilərik və onları bəzən layihəmizə birləşdirə bilərik. Bəzi yollarla yaxşıdır, məsələn, bütün kodlar bir yerə sadiqdir. Bununla birlikdə, digər çatışmazlıqları var ki, dəyişiklikləri yenidən birləşdirmək və ya təsadüfən bir-birinə bağlı olmayan bir depozitə basaraq səhv etmək daha az mürəkkəbdir və səhv etmək daha asandır.

Başqa bir qəribəlik də rack alt qovluğunuzdakılar ilə rack_branch branch-nızdakı kod arasındakı fərqi əldə etmək - onları birləşdirməyin lazım olub-olmadığını görməkdir - çünki, normal diff əmrindən istifadə edə bilməyəcəksiniz. Bunun əvəzinə müqayisə etmək istədiyiniz branch-la git diff-tree işlətməlisiniz:

$ git diff-tree -p rack_branch

Və ya, rack alt qovluğunuzda olanı, serverdəki master branch-ı sonuncu dəfə gətirdiyinizlə müqayisə etmək üçün bunu işlədə bilərsiniz:

$ git diff-tree -p rack_remote/master