Chapters ▾ 2nd Edition

7.8 Git Tools - Gevorderde Saamsmelting (Advanced Merging)

Gevorderde Saamsmelting (Advanced Merging)

Saamsmelting (merging) in Git is tipies redelik maklik. Omdat Git dit maklik maak om 'n ander tak verskeie kere in te smelt, beteken dit dat jy 'n baie langlewende tak kan hê, maar jy kan dit op datum hou soos jy aangaan, en gereeld klein konflikte oplos in plaas daarvan om deur een enorme konflik aan die einde van die reeks verras te word.

Soms kom daar egter netelige konflikte voor. Anders as sommige ander weergawebeheerstelsels (version control systems), probeer Git nie te slim wees oor die oplossing van saamsmeltingskonflikte (merge conflicts) nie. Git se filosofie is om slim te wees oor die bepaling van wanneer 'n saamsmeltingsoplossing ondubbelsinnig is, maar as daar 'n konflik is, probeer dit nie slim wees om dit outomaties op te los nie. Daarom, as jy te lank wag om twee takke wat vinnig afwyk saam te smelt, kan jy 'n paar probleme ondervind.

In hierdie afdeling sal ons kyk na wat sommige van daardie probleme kan wees en watter gereedskap Git jou gee om hierdie meer netelige situasies te help hanteer. Ons sal ook sommige van die verskillende, nie-standaard tipes saamsmeltings wat jy kan doen dek, asook kyk hoe om saamsmeltings wat jy reeds gedoen het, ongedaan te maak.

Saamsmeltingskonflikte (Merge Conflicts)

Terwyl ons sommige basiese beginsels oor die oplossing van saamsmeltingskonflikte in Eenvoudige saamsmeltingskonflikte (Basic Merge Conflicts) gedek het, bied Git vir meer komplekse konflikte 'n paar instrumente om jou te help uitvind wat aangaan en hoe om die konflik beter te hanteer.

Eerstens, as dit enigsins moontlik is, probeer seker maak dat jou werkgids (working directory) skoon is voordat jy 'n saamsmelting doen wat konflikte kan hê. As jy werk aan die gang het, lê dit óf vas (commit) na 'n tydelike tak óf bêre dit (stash). Dit sorg dat jy enigiets wat jy hier probeer, ongedaan kan maak (undo). As jy ongestoorde veranderings in jou werkgids het wanneer jy 'n saamsmelting probeer, kan sommige van hierdie wenke jou help om daardie werk te behou.

Kom ons stap deur 'n baie eenvoudige voorbeeld. Ons het 'n super eenvoudige Ruby-lêer wat 'hello world' druk.

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

hello()

In ons bewaarplek skep ons 'n nuwe tak genaamd whitespace en gaan voort om al die Unix-reëleindes (line endings) na DOS-reëleindes te verander, wat in wese elke reël van die lêer verander, maar net met witruimte (whitespace). Dan verander ons die reël “hello world” na “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(-)

Nou skakel ons terug na ons master tak en voeg 'n bietjie dokumentasie vir die funksie by.

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

Nou probeer ons ons whitespace tak insmelt en ons sal konflikte kry as gevolg van die witruimteveranderings.

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

Afbreek van 'n Saamsmelting (Aborting a Merge)

Ons het nou 'n paar opsies. Kom ons dek eers hoe om uit hierdie situasie te kom. As jy dalk nie konflikte verwag het nie en nog nie heeltemal die situasie wil hanteer nie, kan jy eenvoudig die saamsmelting ongedaan maak met git merge --abort.

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

$ git merge --abort

$ git status -sb
## master

Die git merge --abort opsie probeer terugkeer na jou toestand voor jy die saamsmelting geloop het. Die enigste gevalle waar dit dalk nie perfek sal kan doen nie, is as jy ongebêrede (unstashed), onvasgelegde (uncommitted) veranderings in jou werkgids gehad het toe jy dit uitgevoer het; andersins behoort dit goed te werk.

As jy om die een of ander rede net van voor af wil begin, kan jy ook git reset --hard HEAD uitvoer, en jou bewaarplek sal terug wees na die laaste vasgelegde toestand. Onthou dat enige onvasgelegde werk verlore sal gaan, so maak seker jy wil nie enige van jou veranderings hê nie.

Ignorering van Witruimte (Ignoring Whitespace)

In hierdie spesifieke geval is die konflikte witruimteverwante. Ons weet dit omdat die geval eenvoudig is, maar dit is ook redelik maklik om in werklike gevalle te sien wanneer daar na die konflik gekyk word, omdat elke reël aan die een kant verwyder word en aan die ander kant weer bygevoeg word. By verstek sien Git al hierdie reëls as gewysig, so dit kan nie die lêers saamsmelt nie.

Die verstek saamsmeltingstrategie kan egter argumente neem, en 'n paar daarvan gaan oor die behoorlike ignorering van witruimteveranderings. As jy sien dat jy baie witruimte-kwessies in 'n saamsmelting het, kan jy dit eenvoudig afbreek en dit weer doen, hierdie keer met -Xignore-all-space of -Xignore-space-change. Die eerste opsie ignoreer witruimte heeltemal wanneer reëls vergelyk word, die tweede beskou reekse van een of meer witruimtekarakters as ekwivalent.

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

Aangesien die werklike lêerveranderings in hierdie geval nie botsend was nie, smelt alles net mooi saam sodra ons die witruimteveranderings ignoreer.

Dit is 'n lewensredder as jy iemand op jou span het wat daarvan hou om soms alles te herformateer van spasies na keepspasies (tabs) of andersom.

Handmatige Lêer Hersaamsmelting (Manual File Re-merging)

Alhoewel Git witruimte-voorbewerking redelik goed hanteer, is daar ander tipes veranderings wat Git dalk nie outomaties kan hanteer nie, maar skripbare regstellings is. As 'n voorbeeld, kom ons gee voor dat Git nie die witruimteverandering kon hanteer nie en ons dit per hand moes doen.

Wat ons regtig moet doen, is om die lêer wat ons probeer insmelt deur 'n dos2unix program te laat loop voordat ons die werklike lêersaamsmelting probeer. So hoe sou ons dit doen?

Eerstens kom ons in die saamsmeltingskonflik-toestand. Dan wil ons afskrifte kry van ons weergawe van die lêer, hul weergawe (van die tak wat ons insmelt) en die gemeenskaplike weergawe (vanwaar beide kante afgetak het). Dan wil ons óf hul kant óf ons kant regmaak en die saamsmelting weer net vir hierdie enkele lêer probeer.

Om die drie lêerweergawes te kry, is eintlik redelik maklik. Git stoor al hierdie weergawes in die indeks onder “stages” (stadia) wat elkeen nommers aan hulle gekoppel het. Stadium 1 is die gemeenskaplike voorouer, stadium 2 is jou weergawe en stadium 3 is van die MERGE_HEAD, die weergawe wat jy insmelt (“theirs”).

Jy kan 'n afskrif van elkeen van hierdie weergawes van die konflik-lêer onttrek met die git show opdrag en 'n spesiale sintaksis.

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

As jy 'n bietjie meer hardekern (hard core) wil raak, kan jy ook die ls-files -u loodgietersopdrag (plumbing command) gebruik om die werklike SHA-1’s van die Git-blobs vir elkeen van hierdie lêers te kry.

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

Die :1:hello.rb is net 'n kortpad om daardie blob SHA-1 op te soek.

Noudat ons die inhoud van al drie stadia in ons werkgids het, kan ons hul eie (theirs) handmatig regmaak om die witruimtekwessie op te los en die lêer weer in te smelt (re-merge) met die min bekende git merge-file opdrag wat net dit doen.

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

Op hierdie punt het ons die lêer mooi saamgesmelt. Trouens, dit werk eintlik beter as die ignore-space-change opsie omdat dit eintlik die witruimteveranderings voor saamsmelting regmaak in plaas daarvan om dit bloot te ignoreer. In die ignore-space-change saamsmelting het ons eintlik met 'n paar reëls met DOS-reëleindes geëindig, wat dinge gemeng gemaak het.

As jy 'n idee wil kry voordat jy hierdie vaslegging finaliseer oor wat werklik tussen die een of ander kant verander het, kan jy git diff vra om te vergelyk wat in jou werkgids is wat jy op die punt staan om as resultaat van die saamsmelting vas te lê teen enige van hierdie stadia. Kom ons gaan deur hulle almal.

Om jou resultaat te vergelyk met wat jy in jou tak gehad het voor die saamsmelting, met ander woorde, om te sien wat die saamsmelting ingestel het, kan jy git diff --ours uitvoer:

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

So hier kan ons maklik sien dat wat in ons tak gebeur het, dit wat ons werklik aan hierdie lêer instel met hierdie saamsmelting, is die verandering van daardie enkele reël.

As ons wil sien hoe die resultaat van die saamsmelting verskil het van wat aan hul kant was, kan jy git diff --theirs uitvoer. In hierdie en die volgende voorbeeld moet ons -b gebruik om die witruimte te verwyder, want ons vergelyk dit met wat in Git is, nie ons skoongemaakte hello.theirs.rb lêer nie.

$ 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

Ten slotte kan jy sien hoe die lêer van albei kante af verander het met 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()

Op hierdie punt kan ons die git clean opdrag gebruik om die ekstra lêers uit te vee wat ons geskep het om die handmatige saamsmelting te doen maar wat nie meer nodig is nie.

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

Uitcheck van Konflikte (Checking Out Conflicts)

Miskien is ons om die een of ander rede nie tevrede met die oplossing op hierdie punt nie, of miskien het die handmatige redigering van een of albei kante steeds nie goed gewerk nie en benodig ons meer konteks.

Kom ons verander die voorbeeld effens. Vir hierdie voorbeeld het ons twee langlewende takke wat elkeen 'n paar vasleggings daarin het, maar wat 'n wettige inhoudskonflik skep wanneer dit saamgesmelt word.

$ 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

Ons het nou drie unieke vasleggings wat slegs op die master tak leef en drie ander wat op die mundo tak leef. As ons probeer om die mundo tak in te smelt, kry ons 'n konflik.

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

Ons wil graag sien wat die saamsmeltingskonflik is. As ons die lêer oopmaak, sal ons so iets so sien:

#! /usr/bin/env ruby

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

hello()

Beide kante van die saamsmelting het inhoud by hierdie lêer bygevoeg, maar sommige van die vasleggings het die lêer in dieselfde plek gewysig wat hierdie konflik veroorsaak het.

Kom ons verken 'n paar instrumente wat jy nou tot jou beskikking het om te bepaal hoe hierdie konflik ontstaan het. Miskien is dit nie voor die hand liggend hoe presies jy hierdie konflik moet oplos nie. Jy benodig meer konteks.

Een nuttige instrument is git checkout met die --conflict opsie. Dit sal die lêer weer heruitcheck (re-checkout) en die saamsmeltingskonflikmerkers vervang. Dit kan nuttig wees as jy die merkers wil terugstel en weer probeer om dit op te los.

Jy kan --conflict óf diff3 óf merge (wat die verstek is) aangee. As jy dit diff3 gee, sal Git 'n effens ander weergawe van konflikmerkers gebruik, wat vir jou nie net die “ours” en “theirs” weergawes gee nie, maar ook die “base” weergawe inlyn om jou meer konteks te gee.

$ git checkout --conflict=diff3 hello.rb

Sodra ons dit uitvoer, sal die lêer in plaas daarvan so lyk:

#! /usr/bin/env ruby

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

hello()

As jy van hierdie formaat hou, kan jy dit as die verstek stel vir toekomstige saamsmeltingskonflikte deur die merge.conflictstyle instelling op diff3 te stel.

$ git config --global merge.conflictstyle diff3

Die git checkout opdrag kan ook --ours en --theirs opsies neem, wat 'n baie vinnige manier kan wees om net die een of die ander kant te kies sonder om dinge hoegenaamd saam te smelt.

Dit kan veral nuttig wees vir konflikte van binêre lêers waar jy eenvoudig een kant kan kies, of waar jy net sekere lêers van 'n ander tak wil insmelt — jy kan die saamsmelting doen en dan sekere lêers van die een of die ander kant uitcheck voor vaslegging.

Saamsmeltingslogboek (Merge Log)

Nog 'n nuttige instrument by die oplossing van saamsmeltingskonflikte is git log. Dit kan jou help om konteks te kry oor wat tot die konflikte kon bygedra het. Om 'n bietjie geskiedenis te hersien om te onthou hoekom twee lyne van ontwikkeling aan dieselfde area van kode geraak het, kan soms baie nuttig wees.

Om 'n volledige lys te kry van al die unieke vasleggings wat ingesluit was in enige tak betrokke by hierdie saamsmelting, kan ons die “driedubbelpunt” (triple dot) sintaksis gebruik wat ons geleer het in Driedubbelpunt (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'

Dit is 'n mooi lys van die totale ses vasleggings wat betrokke was, sowel as op watter lyn van ontwikkeling elke vaslegging was.

Ons kan dit egter verder vereenvoudig om vir ons baie meer spesifieke konteks te gee. As ons die --merge opsie by git log voeg, sal dit net die vasleggings wys in enige kant van die saamsmelting wat 'n lêer raak wat tans in konflik is.

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

As jy dit in plaas daarvan met die -p opsie uitvoer, kry jy net die diffs na die lêer wat uiteindelik in konflik was. Dit kan werklik nuttig wees om jou vinnig die konteks te gee wat jy nodig het om te help verstaan hoekom iets bots en hoe om dit intelligenter op te los.

Gekombineerde Diff-formaat (Combined Diff Format)

Aangesien Git enige saamsmeltingsresultate voorberei (stages) wat suksesvol is, kry jy slegs wat tans nog in konflik is as jy git diff uitvoer terwyl jy in 'n botsende saamsmeltingstoestand is. Dit kan nuttig wees om te sien wat jy nog moet oplos.

Wanneer jy git diff direk na 'n saamsmeltingskonflik uitvoer, sal dit jou inligting gee in 'n taamlik unieke diff-afvoerformaat.

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

Die formaat word “Combined Diff” genoem en gee vir jou twee kolomme data langs elke reël. Die eerste kolom wys jou of daardie reël verskil (bygevoeg of verwyder) tussen die “ours” tak en die lêer in jou werkgids, en die tweede kolom doen dieselfde tussen die “theirs” tak en jou werkgidskopie.

So in daardie voorbeeld kan jy sien dat die <<<<<<< en >>>>>>> reëls in die werkkopie is, maar nie in enige van die kante van die saamsmelting was nie. Dit maak sin, want die saamsmeltingsinstrument het dit daar ingedruk vir ons konteks, maar dit word van ons verwag om dit te verwyder.

As ons die konflik oplos en git diff weer uitvoer, sal ons dieselfde ding sien, maar dit is 'n bietjie nuttiger.

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

Dit wys ons dat “hola world” aan ons kant was maar nie in die werkkopie nie, dat “hello mundo” aan hul kant was maar nie in die werkkopie nie, en uiteindelik dat “hola mundo” nie aan enige kant was nie maar nou in die werkkopie is. Dit kan nuttig wees om te hersien voordat die resolusie vasgelê word.

Jy kan dit ook van die git log vir enige saamsmelting kry om te sien hoe iets na die tyd opgelos is. Git sal hierdie formaat uitvoer as jy git show op 'n saamsmeltingsvaslegging (merge commit) uitvoer, of as jy 'n --cc opsie by 'n git log -p voeg (wat by verstek slegs pleisters wys vir nie-saamsmeltingsvasleggings).

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

Saamsmeltings Ongedaan Maak (Undoing Merges)

Noudat jy weet hoe om 'n saamsmeltingsvaslegging te skep, sal jy waarskynlik 'n paar per ongeluk maak. Een van die wonderlike dinge van werk met Git is dat dit oukei is om foute te maak, want dit is moontlik (en in baie gevalle maklik) om dit reg te maak.

Saamsmeltingsvasleggings is nie anders nie. Kom ons sê jy het werk op 'n onderwerp-tak (topic branch) begin, dit per ongeluk in master ingesmelt, en nou lyk jou vasleggingsgeskiedenis soos volg:

Accidental merge commit
Figure 168. Accidental merge commit

Daar is twee maniere om hierdie probleem te benader, afhangende van wat jou gewenste uitkoms is.

Maak die verwysings reg (Fix the references)

As die ongewenste saamsmeltingsvaslegging slegs op jou plaaslike bewaarplek bestaan, is die maklikste en beste oplossing om die takke te skuif sodat hulle wys na waar jy wil hê hulle moet. In die meeste gevalle, as jy die foutiewe git merge opvolg met git reset --hard HEAD~, sal dit die takwysers herstel sodat hulle so lyk:

History after `git reset --hard HEAD~`
Figure 169. History after git reset --hard HEAD~

Ons het vroeër in Reset Ontmystifiseer (Reset Demystified) reset gedek, so dit behoort nie te moeilik te wees om uit te vind wat hier aangaan nie. Hier is 'n vinnige opknapping: reset --hard gaan gewoonlik deur drie stappe:

  1. Skuif die tak waarna HEAD wys. In hierdie geval wil ons master skuif na waar dit was voor die saamsmeltingsvaslegging (C6).

  2. Laat die indeks soos HEAD lyk.

  3. Laat die werkgids soos die indeks lyk.

Die nadeel van hierdie benadering is dat dit geskiedenis herskryf, wat problematies kan wees met 'n gedeelde bewaarplek. Gaan kyk na Die Gevare van Rebasing (The Perils of Rebasing) vir meer inligting oor wat kan gebeur; die kort weergawe is dat as ander mense die vasleggings het wat jy herskryf, moet jy waarskynlik reset vermy. Hierdie benadering sal ook nie werk as enige ander vasleggings geskep is sedert die saamsmelting nie; om die verwysings te skuif sal daardie veranderings effektief verloor.

Draai die vaslegging om (Reverse the commit)

As die verskuiwing van die takwysers nie vir jou gaan werk nie, gee Git jou die opsie om 'n nuwe vaslegging te maak wat al die veranderings van 'n bestaande een ongedaan maak. Git noem hierdie aksie 'n “revert” (terugrol), en in hierdie spesifieke scenario sou jy dit so aanroep:

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

Die -m 1 vlag dui aan watter ouer die “hooflyn” (mainline) is en gehou moet word. Wanneer jy 'n saamsmelting na HEAD (git merge topic) aanroep, het die nuwe vaslegging twee ouers: die eerste een is HEAD (C6), en die tweede is die punt van die tak wat ingesmelt word (C4). In hierdie geval wil ons al die veranderings wat ingestel is deur ouer #2 in te smelt (C4) ongedaan maak, terwyl al die inhoud van ouer #1 (C6) behoue bly.

Die geskiedenis met die revert-vaslegging lyk soos volg:

History after `git revert -m 1`
Figure 170. History after git revert -m 1

Die nuwe vaslegging ^M het presies dieselfde inhoud as C6, so as jy hiervandaan begin is dit asof die saamsmelting nooit gebeur het nie, behalwe dat die nou-ongesmelte vasleggings steeds in HEAD se geskiedenis is. Git sal verward wees as jy weer topic in master probeer insmelt:

$ git merge topic
Already up-to-date.

Daar is niks in topic wat nie reeds vanaf master bereikbaar is nie. Wat erger is, is as jy werk by topic voeg en weer insmelt, sal Git slegs die veranderings sedert die ongedaan-gemaakte saamsmelting bring:

History with a bad merge
Figure 171. History with a bad merge

Die beste manier om hierom te kom is om die oorspronklike saamsmelting on-terug-te-rol (un-revert), aangesien jy nou die veranderings wil inbring wat teruggerol (reverted out) was, en dan 'n nuwe saamsmeltingsvaslegging te skep:

$ git revert ^M
[master 09f0126] Revert "Revert "Merge branch 'topic'""
$ git merge topic
History after re-merging a reverted merge
Figure 172. History after re-merging a reverted merge

In hierdie voorbeeld kanselleer M en ^M mekaar uit. ^^M smelt effektief die veranderings van C3 en C4 in, en C8 smelt die veranderings van C7 in, so nou is topic ten volle ingesmelt.

Ander Tipes Saamsmeltings (Other Types of Merges)

Tot dusver het ons die normale saamsmelting van twee takke gedek, wat normaalweg hanteer word met wat die “rekursiewe” (recursive) strategie van saamsmelting genoem word. Daar is egter ander maniere om takke saam te smelt. Kom ons dek 'n paar van hulle vinnig.

Voorkeur vir Onse of Hulle S’n (Our or Theirs Preference)

Eerstens is daar nog 'n nuttige ding wat ons met die normale “rekursiewe” modus van saamsmelting kan doen. Ons het reeds die ignore-all-space en ignore-space-change opsies gesien wat met 'n -X deurgegee word, maar ons kan Git ook sê om die een of die ander kant te bevoordeel as dit 'n konflik sien.

By verstek, wanneer Git 'n konflik sien tussen twee takke wat saamgesmelt word, sal dit saamsmeltingskonflikmerkers in jou kode voeg en die lêer as botsend merk en jou dit laat oplos. As jy verkies dat Git bloot 'n spesifieke kant kies en die ander kant ignoreer in plaas daarvan om jou die konflik handmatig te laat oplos, kan jy vir die merge opdrag óf 'n -Xours óf -Xtheirs aangee.

As Git dit sien, sal dit nie konflikmerkers byvoeg nie. Enige verskille wat saamsmeltbaar is, sal dit saamsmelt. Enige verskille wat bots, sal dit bloot in die geheel die kant kies wat jy spesifiseer, insluitend binêre lêers.

As ons teruggaan na die “hello world” voorbeeld wat ons vroeër gebruik het, kan ons sien dat insmelting in ons tak konflikte veroorsaak.

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

As ons dit egter met -Xours of -Xtheirs uitvoer, gebeur dit nie.

$ 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

In daardie geval, in plaas daarvan om konflikmerkers in die lêer te kry met “hello mundo” aan die een kant en “hola world” aan die ander kant, sal dit bloot “hola world” kies. Alle ander nie-botsende veranderings in daardie tak word egter suksesvol ingesmelt.

Hierdie opsie kan ook deurgegee word na die git merge-file opdrag wat ons vroeër gesien het deur iets soos git merge-file --ours uit te voer vir individuele lêersaamsmeltings.

As jy so iets wil doen, maar nie eers wil hê Git moet probeer om veranderings van die ander kant af in te smelt nie, is daar 'n meer drakoniese opsie, wat die “ours” saamsmelting strategie is. Dit verskil van die “ours” rekursiewe saamsmelting opsie.

Dit sal basies 'n vals saamsmelting doen. Dit sal 'n nuwe saamsmeltingsvaslegging opneem met beide takke as ouers, maar dit sal nie eers na die tak kyk wat jy insmelt nie. Dit sal bloot die presiese kode in jou huidige tak as resultaat van die saamsmelting aanteken.

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

Jy kan sien dat daar geen verskil is tussen die tak waarop ons was en die resultaat van die saamsmelting nie.

Dit kan dikwels nuttig wees om Git basies te mislei om te dink dat 'n tak reeds ingesmelt is wanneer jy later 'n saamsmelting doen. Byvoorbeeld, sê jy het van 'n release tak afgetak en werk daaraan gedoen wat jy op 'n stadium in jou master tak wil terugsmelt. Intussen moet 'n foutregstelling op master teruggeneem word (backported) na jou release tak. Jy kan die foutregstellingstak in die release tak insmelt en ook merge -s ours op dieselfde tak na jou master tak (selfs al is die foutregstelling reeds daar) sodat wanneer jy later die release tak weer insmelt, daar geen konflikte as gevolg van die foutregstelling sal wees nie.

Subboom-saamsmelting (Subtree Merging)

Die idee van 'n subboom-saamsmelting (subtree merge) is dat jy twee projekte het, en een van die projekte karteer na 'n subgids van die ander een. Wanneer jy 'n subboom-saamsmelting spesifiseer, is Git dikwels slim genoeg om uit te vind dat die een 'n subboom van die ander is en smelt dit dienooreenkomstig saam.

Ons sal deur 'n voorbeeld gaan waar ons 'n afsonderlike projek by 'n bestaande projek voeg en dan die kode van die tweede in 'n subgids van die eerste insmelt.

Eerstens sal ons die Rack-toepassing by ons projek voeg. Ons sal die Rack-projek as 'n afgeleë verwysing (remote reference) in ons eie projek byvoeg en dit dan in sy eie tak uittrek (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"

Nou het ons die wortel (root) van die Rack-projek in ons rack_branch tak en ons eie projek in die master tak. As jy die een uittrek en dan die ander, kan jy sien dat hulle verskillende projekwortels het:

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

Hierdie is ietwat van 'n vreemde konsep. Nie al die takke in jou bewaarplek (repository) hoef eintlik takke van dieselfde projek te wees nie. Dit is nie algemeen nie, want dit is selde nuttig, maar dit is redelik maklik om takke te hê wat heeltemal verskillende geskiedenisse bevat.

In hierdie geval wil ons die Rack-projek in ons master projek intrek (pull) as 'n subgids. Ons kan dit in Git doen met git read-tree. Jy sal meer leer oor read-tree en sy vriende in Git Internals, maar vir eers moet jy weet dat dit die wortelboom van een tak in jou huidige voorbereidingsarea (staging area) en werkgids inlees. Ons het pas teruggeskakel na jou master tak, en ons trek die rack_branch tak in die rack subgids van ons master tak van ons hoofprojek in:

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

Wanneer ons vaslê (commit), lyk dit asof ons al die Rack-lêers onder daardie subgids het — asof ons hulle vanuit 'n tarball ingekopieer het. Wat interessant raak, is dat ons redelik maklik veranderings van een van die takke na die ander kan insmelt. Dus, as die Rack-projek opdateer, kan ons stroomopwaartse (upstream) veranderings intrek deur na daardie tak oor te skakel en te pull:

$ git checkout rack_branch
$ git pull

Dan kan ons daardie veranderings teruginsmelt in ons master tak. Om die veranderings in te trek en die vasleggingsboodskap vooraf in te vul, gebruik die --squash opsie, sowel as die rekursiewe saamsmeltingstrategie se -Xsubtree opsie. Die rekursiewe strategie is hier die verstek (default), maar ons sluit dit in vir duidelikheid.

$ 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

Al die veranderings van die Rack-projek is ingesmelt en gereed om plaaslik vasgelê te word. Jy kan ook die teenoorgestelde doen — maak veranderings in die rack subgids van jou master tak en smelt dit dan later in jou rack_branch tak in om dit by die instandhouers in te dien of stroomopwaarts te push.

Dit gee ons 'n manier om 'n werkvloei te hê wat ietwat soortgelyk is aan die submodule-werkvloei sonder om submodules te gebruik (wat ons in Submodules sal dek). Ons kan takke met ander verwante projekte in ons bewaarplek hou en dit af en toe as subbome in ons projek insmelt. Dit is op sommige maniere lekker, byvoorbeeld al die kode word op 'n enkele plek vasgelê. Dit het egter ander nadele deurdat dit 'n bietjie meer kompleks is en makliker is om foute te maak met die herintegrering van veranderings of deur per ongeluk 'n tak na 'n onverwante bewaarplek te push.

Nog 'n effens vreemde ding is dat om 'n verskil (diff) te kry tussen wat jy in jou rack subgids het en die kode in jou rack_branch tak — om te sien of jy hulle moet saamsmelt — kan jy nie die normale diff opdrag gebruik nie. In plaas daarvan moet jy git diff-tree uitvoer met die tak waarmee jy wil vergelyk:

$ git diff-tree -p rack_branch

Of, om dit wat in jou rack subgids is te vergelyk met wat die master tak op die bediener was die laaste keer wat jy afgelaai (fetched) het, kan jy die volgende uitvoer:

$ git diff-tree -p rack_remote/master