Git 🌙
Chapters ▾ 2nd Edition

7.8 Гит алати - Напредно спајање

Напредно спајање

Спајање у програму Гит је у општем случају прилично једноставно. Пошто програм Гит вишеструко спајање неке друге гране чини једноставним, то значи да можете имати веома дуготрајне гране које у ходу можете одржавати ажурним, често решавајући мале конфликте уместо да будете изненађени огромним конфликтом на крају низова.

Међутим, понекад долази до компликованих конфликата. За разлику од неких других система за контролу верзије, програм Гит не покушава да буде превише паметан када дође до решавања конфликта при спајању. Филозофија програма Гит је да буде паметан када одређује да ли је решење спајања недвосмислено, али ако постоји конфликт, да се не прави паметан у вези аутоматског решавања. Дакле, ако сувише дуго чекате да спојите две гране које се брзо разилазе, можете наићи на одређене проблеме.

У овом одељку ћемо представити шта могу бити неки од тих проблема и које алате вам програм Гит нуди као помоћ у обради тих компликованијих ситуација. Такође ћемо представити неке другачије, нестандардне врсте спајања које можете урадити, као и како да се избавите из спајања која сте већ обавили.

Конфликти при спајању

Мада смо прешли неке основе решавања конфликта при спајању у Основни конфликти при спајању, за сложеније конфликте програм Git нуди неколико алата који вам помажу да откријете шта се дешава и како да се успешније носите са конфликтом.

Најпре, ако је то уопште и могуће, покушајте обезбедити да вам је радни директоријум чист пре него што покренете спајања која би могла имати конфликте. Ако имате посао у току, или га комитујте у привремену грану или га сакријте. На тај начин можете вратити на старо све што овде покушате. Ако у радном директоријуму имате несачуване промене када покушате спајање, неки од ових трикова вам могу помоћи да очувате тај рад.

Хајде да прођемо кроз веома једноставан пример. Имамо супер једноставан Руби фајл који исписује ’hello world’.

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

hello()

У нашем репозиторијуму креирамо нову грану под именом whitespace и крећемо да променимо све Јуникс завршетке редова у ДОС завршетке редова, у суштини мењајући сваку линију фајла, али само са празним простором (whitespace). Затим променимо линију „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 покушава да вас врати на старо стање пре покретања спајања. Једини случајеви када ово не би перфектно могла да уради је ако бисте имали несакривене, некомитоване измене у радном директоријум када сте је покренули. У супротном би требало да ради како треба.

Ако из неког разлога једноставно желите да почнете из почетка, можете да покренете и 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” (етапе) и свака од њих има придружен број. Етапа 1 је заједнички предак, етапа 2 је ваша верзија и етапа 3 је из 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 блобова за сваки од ових фајлова.

$ 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 спајању, на крају имамо и неколико линија са ДОС завршетком, чиме је ствар измешана.

Ако желите да стекнете идеју шта је заиста промењено између једне или друге стране пре довршавања овог комита, можете питати 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. Ово може да вам помогне тако што пружа контекст онога што је можда допринело стварању конфликта. Понекада преглед мало историје може да буде од изузетне помоћи да запамтите зашто су две линије развоја утицале на исти део кода.

Да бисте добили потпуну листу свих јединствених комитова који су били део било које гране укључене у ово спајање, можемо да употребимо синтаксу „три тачке” коју смо научили у Трострука тачка.

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

То је фина листа од укупно шест комитова који су умешани, као и на којој грани развоја је био сваки од њих.

Мада ово можемо даље да упростимо тако да добијемо још одређенији контекст. Ако команди git log додамо опцију --merge, она ће приказати само комитове у било којој страни који утичу на фајл који је тренутно у конфликту.

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

Формат се зове „комбинована разлика” („Combined Diff”) и уз сваку од линија вам приказује две колоне података. Прва колона вам приказује да ли се та линија разликује (додата је или уклоњена) између „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 на комиту спајања, или ако команди git log -p додате опцију --cc (која подразумевано приказује само закрпе за комите који нису комити спајања).

$ 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, па сада историја комитова изгледа овако:

Случајни комит спајања
Слика 137. Случајни комит спајања

Постоје два начина да се приступи овом проблему, зависно од тога шта вам је жељени исход.

Исправљање референци

Ако нежељени комит спајања постоји само у вашем локалном репозиторијуму, најлакше и најбоље решење је да померите гране тако показују на жељено место. У већини случајева, ако након погрешног git merge извршите git reset --hard HEAD~, ресетоваћете показиваче грана тако да изгледају овако:

Историја након `git reset --hard HEAD~`
Слика 138. Историја након git reset --hard HEAD~

reset смо објаснили раније у Демистификовани ресет, тако да не би требало да вам буде сувише тешко да схватите шта се овде догађа. Ево брзог подсетника: reset --hard обично пролази кроз три корака:

  1. Померање гране на коју показује HEAD. У овом случају желимо да се master помери на место пре комита спајања (C6).

  2. Сређивање да индекс изгледа као HEAD.

  3. Сређивање да радни директоријум изгледа као индекс.

Мана овог приступа је да поново исписује историју, што може бити проблем за дељени репозиторијум. Погледајте Опасности ребазирања за више о томе шта може да се догоди; укратко, ако други људи имају комитове које преписујете, врло вероватно би требало да избегнете reset. Овај приступ такође неће радити ако су након спајања креирани било који други комитови; померањем референци би се те промене ефективно изгубиле.

Враћање комита

Ако померање показивача грана неће радити у вашем случају, програм Git вам нуди опцију прављења новог комита који поништава све измене које је увео постојећи. Програм Git ову операцију назива „враћање” („revert”) и у овом сценарију бисте је позвали на следећи начин:

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

Заставица -m 1 наводи који родитељ „главне линије” би требало да се задржи. Када позовете спајање у HEAD (git merge topic), нови комит има два родитеља: први је HEAD (C6), а други је врх гране која се спаја (C4). У овом случају, желимо да опозовемо све измене уведене спајањем родитеља #2 (C4), а да истовремено задржимо сав садржај из родитеља #1 (C6).

Након враћања комита историја изгледа овако:

Историја након `git revert -m 1`
Слика 139. Историја након git revert -m 1

Нови комит ^M има потпуно исти садржај као C6, тако да ако се почне одавде, исто је као да се спајање никада није ни догодило, осим што се комитови који нису спајање још увек налазе у историји HEAD.

Програм Гит ће се збунити ако поново покушате да спојите topic у master:

$ git merge topic
Already up-to-date.

У topic грани не постоји ништа што већ није достижно из master гране. Још горе, ако додате рад у topic и поново урадите спајање, програм Git ће унети само измене настале након враћеног спајања:

Историја са лошим спајањем
Слика 140. Историја са лошим спајањем

Најбољи начин да се ово реши је да се поништи враћање оригиналног спајања, јер сада желите да уведете измене које су враћене, па затим креирате нови комит спајања:

$ git revert ^M
[master 09f0126] Revert "Revert "Merge branch 'topic'""
$ git merge topic
Историја након поновног спајања враћеног спајања
Слика 141. Историја након поновног спајања враћеног спајања

У овом примеру се M и ^M поништавају. ^^M ефективно спаја измене из C3 и C4, а C8 спаја измене из C7, тако да је topic сада у потпуности спојена.

Остале врсте спајања

До сада смо представили обично спајање две гране, које се углавном обрађује оним што се назива „рекурзивна” стратегија спајања. Међутим, постоје и други начини да се споје две гране. Хајде да брзо представимо неколико њих.

Предност нашег или њиховог

Пре свега, постоји још једна корисна ствар коју можемо урадити са обичним „рекурзивним” режимом спајања. Већ смо видели опције 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 грану, неће бити конфликта услед исправке бага.

Спајање подстабла

Идеја код спајања подстабла је да имате два пројекта и један од пројеката се мапира у поддиректоријум другог и обрнуто. Када задате спајање подстабла, програм Гит је често довољно паметан да одреди ако је један подстабло оног другог, па да споји на погодан начин.

Проћи ћемо кроз пример додавања одвојеног пројекта у постојећи пројекат, па затим спајање кода другог у поддиректоријум првог.

На почетку, додајмо Rack апликацију нашем пројекту. Rack пројекат додајемо као удаљену референцу у пројекат, па је затим одјављујемо у њену сопствену грану:

$ 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_branch имамо корен Rack пројекта и наш пројекат у master грани. Ако одјавите један па онда други, видећете да им се корени пројекта разликују:

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

Ово је прилично чудан концепт. Не морају све гране у вашем репозиторијуму да буду гране истог пројекта. Није уобичајено, јер је ретко од помоћи, али је прилично једноставно да имате гране које садрже потпуно различите историје.

У овом случају желимо да повучемо Rack пројекат у наш master пројекат као поддиректоријум. У програму Гит то можемо да урадимо помоћу git read-tree. Научићете више о read-tree и његовим другарима у Гит изнутра, али за сада је довољно да знате да она учитава корено стабло једне од грана у ваш текући стејџ и радни директоријум. Управо смо се вратили у вашу master грану и повлачимо rack_branch грану у rack поддиректоријум наше master гране главног пројекта:

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

Када комитујемо, изгледа као да у том поддиректоријуму имамо све Rack фајлове – као да смо их прекопирали из tarball архиве. Оно што је интересантно је да прилично једноставно можемо да спојимо измене из једне гране у другу. Дакле, ако се Rack пројекат ажурира, узводне промене можемо да повучемо тако што се пребацимо на ту грану и повучемо:

$ git checkout rack_branch
$ git pull

Те измене затим можемо да спојимо назад у нашу master грану. Да бисте повукли измене и унапред попунили комит подруку, употребите --squash опцију, као и -Xsubtree опцију рекурзивне стратегије спајања. Рекурзивна стратегија је овде и иначе подразумевана, али је због јаснијег приказа овде наводимо.

$ 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 пројекта су спојене и спремне за локално комитовање. Такође можете да урадите и супротно – направите измене у rack поддиректоријуму ваше master гране па их спојите у rack_branch да их касније предате одржаваоцима или да их гурнете узводно.

Ово нам омогућава начин да имамо сличан процес рада као у случају подмодула само без потребе да се користе подмодули (које ћемо обрадити у Подмодули). У нашем репозиторијуму можемо да држимо гране са осталим повезаним пројектима и да их повремено спајамо као подстабло у наш пројекат. То је на неки начин фино, на пример сав кôд се комитује на једно место. Међутим, постоје и лоше стране јер је донекле комплексно и лакше је да се направе грешке приликом реинтеграције измена или нехотичног гурања гране у неповезани репозиторијум.

Још једна помало чудна ствар је начин на који добијате разлику између онога што се налази у rack поддиректоријуму и кода у rack_branch грани – како бисте видели да ли је потребно да их спојите – не можете употребити обичну diff команду. Уместо ње морате извршити git diff-tree са граном коју желите да упоредите:

$ git diff-tree -p rack_branch

Или, да бисте упоредили оно што се налази у rack поддиректоријуму са оним из master гране на серверу последњи пут кад сте преузели са њега, можете да извршите:

$ git diff-tree -p rack_remote/master
scroll-to-top