Chapters ▾ 2nd Edition

7.8 Git-verktyg - Avancerad sammanslagning

Avancerad sammanslagning

Sammanslagning i Git är vanligtvis ganska enkel. Eftersom Git gör det lätt att sammanfoga en annan gren flera gånger betyder det att du kan ha en mycket långlivad gren men hålla den uppdaterad längs vägen, lösa små konflikter ofta i stället för att bli överraskad av en enorm konflikt i slutet.

Ibland uppstår dock knepiga konflikter. Till skillnad från vissa andra versionshanteringssystem försöker Git inte vara överdrivet smart med att lösa sammanslagningskonflikter. Gits filosofi är att vara smart när en sammanslagningslösning är entydig, men om det finns en konflikt försöker den inte vara smart om att lösa den automatiskt. Därför kan du, om du väntar för länge med att sammanfoga två grenar som snabbt glider isär, stöta på vissa problem.

I det här avsnittet går vi igenom vad några av dessa problem kan vara och vilka verktyg Git ger dig för att hantera mer knepiga situationer. Vi tar också upp några olika, icke‑standardiserade typer av sammanslagningar du kan göra, samt hur du tar dig ur sammanslagningar du redan gjort.

Sammanslagningskonflikter

Även om vi täckte några grunder om att lösa sammanslagningskonflikter i Grundläggande sammanslagningskonflikter, ger Git några verktyg för mer komplexa konflikter som hjälper dig att förstå vad som händer och hantera konflikten bättre.

Först och främst, om det alls är möjligt, se till att din arbetskatalog är ren innan du gör en sammanslagning som kan ha konflikter. Om du har arbete på gång, checka in det i en tillfällig gren eller lägg undan det. Det gör att du kan ångra allt du försöker här. Om du har osparade ändringar i arbetskatalogen när du försöker sammanfoga kan några av dessa tips hjälpa dig att bevara arbetet.

Låt oss gå igenom ett mycket enkelt exempel. Vi har en superenkel Ruby‑fil som skriver ut strängen hello world.

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

hello()

I vårt kodförråd skapar vi en ny gren med namnet whitespace och går vidare till att ändra alla Unix‑radslut till DOS‑radslut, vilket i praktiken ändrar varje rad i filen men bara i blanktecken. Sedan ändrar vi raden “hello world” till “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(-)

Nu byter vi tillbaka till vår master‑gren och lägger till lite dokumentation för funktionen.

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

Nu försöker vi sammanfoga in vår whitespace‑gren och får konflikter på grund av blankteckensändringarna.

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

Avbryta en sammanslagning

Vi har nu några alternativ. Först går vi igenom hur du tar dig ur den här situationen. Om du kanske inte väntade dig konflikter och inte vill hantera läget ännu kan du helt enkelt backa ur sammanslagningen med git merge --abort.

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

$ git merge --abort

$ git status -sb
## master

Alternativet git merge --abort försöker återgå till ditt läge innan du körde sammanslagningen. De enda fall där det kanske inte kan göra det helt perfekt är om du hade undanlagda, oincheckade ändringar i arbetskatalogen när du körde det, annars brukar det fungera bra.

Om du av någon anledning bara vill börja om kan du också köra git reset --hard HEAD, så återgår ditt kodförråd till det senaste checkade in läget. Kom ihåg att allt oincheckat arbete kommer att gå förlorat, så se till att du inte vill behålla några av dina ändringar.

Ignorera blanktecken

I det här specifika fallet är konflikterna relaterade till blanktecken. Vi vet det eftersom fallet är enkelt, men det är också ganska lätt att se i verkliga fall när man tittar på konflikten eftersom varje rad tas bort på ena sidan och läggs till igen på den andra. Som standard ser Git alla dessa rader som ändrade, så den kan inte sammanfoga filerna.

Standardstrategin för sammanslagning kan ta argument, och några av dem handlar om att ignorera blankteckensändringar korrekt. Om du ser att du har många blankteckensproblem i en sammanslagning kan du helt enkelt avbryta den och göra om den, den här gången med -Xignore-all-space eller -Xignore-space-change. Det första alternativet ignorerar blanktecken helt när rader jämförs, det andra behandlar sekvenser av ett eller flera blanktecken som likvärdiga.

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

Eftersom filändringarna i det här fallet inte var i konflikt sammanfogas allt fint när vi ignorerar blankteckensändringarna.

Det här är en räddare i nöden om du har någon i teamet som ibland gillar att omformatera allt från mellanslag till tabbar eller tvärtom.

Manuell omsammanfogning av filer

Även om Git hanterar förbehandling av blanktecken ganska bra finns det andra typer av ändringar som Git kanske inte kan hantera automatiskt, men som går att fixa med skript. Som exempel låtsas vi att Git inte kunde hantera blankteckensändringen och att vi behövde göra det för hand.

Det vi egentligen behöver göra är att köra filen vi försöker sammanfoga genom ett dos2unix‑program innan vi försöker sammanfoga filen på riktigt. Så hur skulle vi göra det?

Först hamnar vi i sammanslagningskonflikt‑läge. Sedan vill vi ta kopior av vår version av filen, deras version (från grenen vi sammanfogar in) och den gemensamma versionen (från där båda sidor grenades). Sedan vill vi fixa antingen deras sida eller vår sida och försöka sammanfoga igen för just den filen.

Att få de tre filversionerna är faktiskt ganska enkelt. Git lagrar alla dessa versioner i indexet under "steg" som vart och ett har ett nummer kopplat till sig. Steg 1 är den gemensamma anfadern, steg 2 är din version och steg 3 är från MERGE_HEAD, versionen du sammanfogar in (“theirs”/"deras").

Du kan extrahera en kopia av var och en av dessa versioner av konfliktfilen med kommandot git show och en särskild syntax.

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

Om du vill gå lite mer på djupet kan du också använda lågnivåkommandot ls-files -u för att få de faktiska SHA‑1‑värdena för Git‑blobbarna för var och en av dessa filer.

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

:1:hello.rb är bara en förkortning för att slå upp den blob‑SHA‑1:an.

Nu när vi har innehållet från alla tre steg i vår arbetskatalog kan vi manuellt fixa deras version för att lösa blankteckensproblemet och sammanfoga filen igen med det mindre kända kommandot git merge-file som gör just det.

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

Vid det här laget har vi snyggt sammanfogat filen. Faktum är att det här fungerar bättre än alternativet ignore-space-change eftersom det faktiskt åtgärdar blankteckensändringarna före sammanslagning i stället för att bara ignorera dem. I sammanslagningen med ignore-space-change hamnade vi faktiskt med några rader med DOS‑radslut, vilket gav en blandning.

Om du vill få en uppfattning innan du slutför den här incheckningen om vad som faktiskt ändrades mellan den ena sidan och den andra kan du be git diff jämföra det som ligger i din arbetskatalog och som du är på väg att checka in som resultat av sammanslagningen med något av dessa steg. Låt oss gå igenom dem allihop.

För att jämföra ditt resultat med det du hade i din gren före sammanslagningen, med andra ord, för att se vad sammanslagningen introducerade, kan du köra 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()

Här ser vi alltså att det som hände i vår gren, det vi faktiskt inför i filen med denna sammanslagning, är att en enda rad ändras.

Om vi vill se hur resultatet av sammanslagningen skiljer sig från deras sida kan du köra git diff --theirs. I detta och följande exempel måste vi använda -b för att ta bort blanktecken eftersom vi jämför med det som ligger i Git, inte vår städade hello.theirs.rb‑fil.

$ 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

Till sist kan du se hur filen har ändrats från båda sidor med 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()

Vid det här laget kan vi använda kommandot git clean för att städa bort de extra filer vi skapade för den manuella sammanslagningen men inte längre behöver.

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

Checka ut konflikter

Kanske är vi inte nöjda med lösningen av någon anledning, eller så fungerade inte manuell redigering av ena eller båda sidor så bra och vi behöver mer kontext.

Låt oss ändra exemplet lite. I det här exemplet har vi två långlivade grenar som båda har några incheckningar i sig men skapar en legitim innehållskonflikt vid sammanslagning.

$ 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

Vi har nu tre unika incheckningar som bara finns på master‑grenen och tre andra som bara finns på mundo‑grenen. Om vi försöker sammanfoga mundo‑grenen får vi en konflikt.

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

Vi vill se vad sammanslagningskonflikten är. Om vi öppnar filen ser vi något i stil med detta:

#! /usr/bin/env ruby

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

hello()

Båda sidorna av sammanslagningen lade till innehåll i filen, men några av incheckningarna ändrade filen på samma ställe, vilket orsakade konflikten.

Låt oss undersöka ett par verktyg som du nu har tillgång till för att avgöra hur konflikten uppstod. Kanske är det inte självklart hur du ska fixa den. Du behöver mer kontext.

Ett hjälpsamt verktyg är git checkout med flaggan --conflict. Det kommer att lägga ut filen igen och ersätta konfliktmarkörerna. Det kan vara användbart om du vill återställa markörerna och försöka lösa dem igen.

Du kan skicka --conflict antingen diff3 eller merge (som är standard). Om du skickar diff3 använder Git en något annorlunda variant av konfliktmarkörer, som inte bara ger dig “ours” och “theirs” utan också “base”‑versionen infogad för att ge mer kontext.

$ git checkout --conflict=diff3 hello.rb

När vi har kört det kommer filen att se ut så här i stället:

#! /usr/bin/env ruby

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

hello()

Om du gillar detta format kan du ställa in det som standard för framtida sammanslagningskonflikter genom att sätta merge.conflictstyle till diff3.

$ git config --global merge.conflictstyle diff3

Kommandot git checkout kan också ta flaggorna --ours och --theirs, vilket kan vara ett riktigt snabbt sätt att bara välja ena sidan eller den andra utan att sammanfoga alls.

Det kan vara särskilt användbart för konflikter i binärfiler där du helt enkelt väljer ena sidan, eller när du bara vill sammanfoga vissa filer från en annan gren — då kan du göra sammanslagningen och sedan lägga ut vissa filer från ena sidan eller den andra innan du checkar in.

Sammanslagningslogg

Ett annat användbart verktyg när du löser sammanslagningskonflikter är git log. Det kan hjälpa dig att få kontext om vad som kan ha bidragit till konflikterna. Att se lite historik för att minnas varför två utvecklingslinjer rörde samma kodområde kan ibland vara väldigt hjälpsamt.

För att få en fullständig lista över alla unika incheckningar som ingick i någon av grenarna i denna sammanslagning kan vi använda "trippelpunkts"‑syntaxen som vi lärde oss i Trippelpunkt.

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

Det är en trevlig lista över de sex incheckningar som ingår, samt vilken utvecklingslinje varje incheckning låg på.

Vi kan förenkla det ytterligare för att få mycket mer specifik kontext. Om vi lägger till flaggan --merge till git log kommer den bara att visa de incheckningar på vardera sida som rör en fil som för närvarande är i konflikt.

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

Om du kör det med flaggan -p i stället får du bara diffarna till filen som hamnade i konflikt. Det kan vara mycket hjälpsamt för att snabbt ge dig den kontext du behöver för att förstå varför något kolliderar och hur du kan lösa det smartare.

Sammanfogat diff‑format

Eftersom Git köar alla sammanslagningsresultat som är lyckade kommer git diff i ett sammanslagningsläge med konflikter bara visa det som fortfarande är i konflikt. Det kan vara hjälpsamt för att se vad du fortfarande måste lösa.

När du kör git diff direkt efter en sammanslagningskonflikt får du information i ett ganska unikt diff‑format.

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

Formatet kallas "Kombinerad diff" och ger dig två kolumner med data bredvid varje rad. Den första kolumnen visar om raden är annorlunda (tillagd eller borttagen) mellan “ours”‑grenen och filen i din arbetskatalog och den andra kolumnen gör samma sak mellan “theirs”‑grenen och din arbetskatalogskopia.

I det här exemplet kan du se att <<<<<<< och >>>>>>>‑raderna finns i arbetskopian men inte fanns på någon av sidorna i sammanslagningen. Det är logiskt eftersom sammanslagningsverktyget satte in dem för kontext, men vi förväntas ta bort dem.

Om vi löser konflikten och kör git diff igen ser vi samma sak, men det är lite mer användbart.

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

Det visar att “hola world” fanns på vår sida men inte i arbetskopian, att “hello mundo” fanns på deras sida men inte i arbetskopian, och att “hola mundo” nu finns i arbetskopian trots att den inte fanns på någon sida. Det här kan vara användbart att granska innan du checkar in lösningen.

Du kan också få detta från git log för vilken sammanslagning som helst för att se hur något löstes i efterhand. Git skriver ut formatet om du kör git show på en sammanslagningsincheckning, eller om du lägger till flaggan --cc till git log -p (som standard bara visar diffar för icke‑sammanslagningsincheckningar).

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

Ångra sammanslagningar

Nu när du vet hur du skapar en sammanslagningsincheckning kommer du förmodligen att göra några av misstag. En av de fina sakerna med att arbeta i Git är att det är okej att göra misstag, eftersom det är möjligt (och i många fall enkelt) att rätta dem.

Sammanslagningsincheckningar är inget undantag. Låt oss säga att du började arbeta på en ämnesgren, råkade sammanfoga den in i master, och nu ser din incheckningshistorik ut så här:

Oavsiktlig sammanslagningsincheckning
Figur 153. Oavsiktlig sammanslagningsincheckning

Det finns två sätt att angripa problemet, beroende på vilket slutresultat du vill ha.

Fixa referenserna

Om den oönskade sammanslagningsincheckningen bara finns i ditt lokala kodförråd är den enklaste och bästa lösningen att flytta grenarna så att de pekar dit du vill. I de flesta fall, om du följer den misslyckade git merge‑körningen med git reset --hard HEAD~, återställer det grenpekare så att de ser ut så här:

Historik efter `git reset --hard HEAD~`
Figur 154. Historik efter git reset --hard HEAD~

Vi täckte reset i Nollställning förklarad, så det borde inte vara så svårt att förstå vad som händer här. Här är en snabb uppfräschning: reset --hard går vanligtvis igenom tre steg:

  1. Flytta grenen som HEAD pekar på. I det här fallet vill vi flytta master dit den var före sammanslagningsincheckningen (C6).

  2. Få indexet att se ut som HEAD.

  3. Få arbetskatalogen att se ut som indexet.

Nackdelen med denna metod är att den skriver om historik, vilket kan vara problematiskt i ett delat kodförråd. Se Farorna med grenflyttning för mer om vad som kan hända; kortversionen är att om andra människor har incheckningarna du skriver om bör du förmodligen undvika reset. Den här metoden fungerar inte heller om andra incheckningar har skapats sedan sammanslagningen; att flytta referenserna skulle i praktiken förlora dessa ändringar.

Gör en omvänd incheckning

Om det inte fungerar att flytta grenpekare ger Git dig möjlighet att skapa en ny incheckning som ångrar alla ändringar från en befintlig. Git kallar denna operation för en "återställning", och i just det här scenariot skulle du köra den så här:

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

Flaggan -m 1 anger vilken förälder som är "huvudlinjen" och ska behållas. När du sammanfogar in i HEAD (git merge topic) har den nya incheckningen två föräldrar: den första är HEAD (C6), och den andra är spetsen på grenen som sammanfogas in (C4). I det här fallet vill vi ångra alla ändringar som introducerades av sammanslagningen av förälder #2 (C4), samtidigt som vi behåller allt innehåll från förälder #1 (C6).

Historiken med återställningsincheckningen ser ut så här:

Historik efter `git revert -m 1`
Figur 155. Historik efter git revert -m 1

Den nya incheckningen ^M har exakt samma innehåll som C6, så härifrån är det som om sammanslagningen aldrig hände, förutom att de nu osammanfogade incheckningarna fortfarande finns i HEAD‑historiken. Git blir förvirrat om du försöker sammanfoga topic i master igen:

$ git merge topic
Already up-to-date.

Det finns ingenting i topic som inte redan är nåbart från master. Värre är att om du lägger till arbete i topic och sammanfogar igen kommer Git bara ta in ändringarna sedan den återställda sammanslagningen:

Historik med en dålig sammanslagning
Figur 156. Historik med en dålig sammanslagning

Det bästa sättet runt detta är att ångra återställningen av den ursprungliga sammanslagningen, eftersom du nu vill ta in de ändringar som ångrats, och sedan skapa en ny sammanslagningsincheckning:

$ git revert ^M
[master 09f0126] Revert "Revert "Merge branch 'topic'""
$ git merge topic
Historik efter att sammanfoga om en återställd sammanslagning
Figur 157. Historik efter att sammanfoga om en återställd sammanslagning

I det här exemplet tar M och ^M ut varandra. ^^M sammanfogar i praktiken ändringarna från C3 och C4, och C8 sammanfogar ändringarna från C7, så nu är topic helt sammanfogad.

Andra typer av sammanslagningar

Hittills har vi täckt den normala sammanslagningen av två grenar, som normalt hanteras med den rekursiva strategin för sammanslagning. Det finns dock andra sätt att sammanfoga grenar. Låt oss gå igenom några av dem snabbt.

Ställa in vår eller deras

Först och främst finns det en annan användbar sak vi kan göra med det normala rekursiva läget för sammanslagning. Vi har redan sett alternativen ignore-all-space och ignore-space-change som skickas med -X, men vi kan också säga åt Git att favorisera den ena sidan eller den andra när den ser en konflikt.

Som standard, när Git ser en konflikt mellan två grenar som sammanfogas, lägger den till konfliktmarkörer i din kod och markerar filen som i konflikt och låter dig lösa den. Om du hellre vill att Git helt enkelt väljer en viss sida och ignorerar den andra sidan i stället för att låta dig manuellt lösa konflikten kan du skicka kommandot merge med antingen -Xours eller -Xtheirs.

Om Git ser detta kommer den inte att lägga till konfliktmarkörer. Alla skillnader som går att sammanfoga kommer den att sammanfoga. Alla skillnader som kolliderar kommer den helt enkelt att välja den sida du angav i sin helhet, inklusive binärfiler.

Om vi går tillbaka till “hello world”‑exemplet vi använde tidigare kan vi se att sammanslagning av vår gren ger konflikter.

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

Men om vi kör det med -Xours eller -Xtheirs gör den inte det.

$ 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

I det fallet, i stället för konfliktmarkörer i filen med “hello mundo” på ena sidan och “hola world” på den andra, kommer den helt enkelt välja “hola world”. Alla andra icke‑konfliktande ändringar på grenen sammanfogas dock framgångsrikt.

Detta alternativ kan också skickas till kommandot git merge-file vi såg tidigare genom att köra något som git merge-file --ours för enskilda filsammanfogningar.

Om du vill göra något sådant men inte ens vill att Git ska försöka sammanfoga ändringar från den andra sidan finns ett mer drastiskt alternativ, nämligen "ours"‑sammanslagningsstrategin. Detta skiljer sig från "ours"‑alternativet för den rekursiva sammanslagningen.

Detta gör i praktiken en låtsassammanfogning. Den registrerar en ny sammanslagningsincheckning med båda grenar som föräldrar, men den tittar inte ens på grenen du sammanfogar in. Den registrerar helt enkelt som resultatet av sammanslagningen exakt koden i din nuvarande gren.

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

Du kan se att det inte finns någon skillnad mellan grenen vi var på och resultatet av sammanslagningen.

Det här kan vara användbart när du vill få Git att behandla en gren som redan sammanfogad vid en senare sammanslagning. Till exempel, säg att du bröt ut en utgåvegren och har gjort lite arbete i den som du vill sammanfoga tillbaka in i din master‑gren vid något tillfälle. Under tiden behöver en felrättning i master bakportas till din utgåvegren. Du kan sammanfoga felrättningsgrenen in i utgåvegren och också köra merge -s ours med samma gren in i din master‑gren (även om rättningen redan finns där) så att när du senare sammanfogar utgåvegren igen finns det inga konflikter från felrättningen.

Sammanfoga delträd

Idén med delträdsammanfogning är att du har två projekt, och att det ena projektet motsvarar en underkatalog i det andra. När du anger en delträdsammanfogning är Git ofta smart nog att räkna ut att det ena är ett delträd av det andra och sammanfoga därefter.

Vi går igenom ett exempel där vi lägger in ett separat projekt i ett befintligt projekt och sedan sammanfogar koden från det andra in i en underkatalog till det första.

Först lägger vi till Rack‑applikationen i vårt projekt. Vi lägger till Rack‑projektet som en fjärrreferens i vårt eget projekt och växlar sedan till det i en egen gren:

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

Nu har vi roten till Rack‑projektet i vår gren rack_branch och vårt eget projekt i grenen master. Om du växlar till den ena och sedan den andra kan du se att de har olika projektrötter:

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

Det här är ett lite udda koncept. Alla grenar i ditt kodförråd måste inte vara grenar av samma projekt. Det är inte vanligt, eftersom det sällan är hjälpsamt, men det är ganska enkelt att låta grenar innehålla helt olika historiker.

I det här fallet vill vi dra in Rack‑projektet i vårt master‑projekt som en underkatalog. Det kan vi göra i Git med git read-tree. Du kommer att lära dig mer om read-tree och dess vänner i Git bakom kulisserna, men för tillfället räcker det att veta att den läser rotträdet från en gren in i din nuvarande köyta och din arbetskatalog. Vi bytte just tillbaka till grenen master, och vi drar in grenen rack_branch i underkatalogen rack i vår master‑gren i huvudprojektet:

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

När vi checkar in ser det ut som att vi har alla Rack‑filer under den underkatalogen — ungefär som om vi kopierat in dem från en tar‑fil. Det som blir intressant är att vi ganska enkelt kan sammanfoga ändringar från den ena grenen till den andra. Så om Rack‑projektet uppdateras kan vi dra in uppströmsändringar genom att byta till den grenen och dra:

$ git checkout rack_branch
$ git pull

Sedan kan vi sammanfoga dessa ändringar tillbaka till vår master‑gren. För att dra in ändringarna och förifylla incheckningsmeddelandet använder du flaggan --squash, samt den rekursiva sammanslagningsstrategins -Xsubtree‑alternativ. Den rekursiva strategin är standard här, men vi tar med den för tydlighet.

$ 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

Alla ändringar från Rack‑projektet sammanfogas och är redo att checkas in lokalt. Du kan också göra tvärtom — göra ändringar i underkatalogen rack i din master‑gren och sedan sammanfoga dem in i din rack_branch‑gren senare för att skicka dem till förvaltarna eller skicka dem uppströms.

Detta ger oss ett arbetsflöde som liknar undermoduler utan att använda undermoduler (som vi går igenom i Undermoduler). Vi kan behålla grenar med andra relaterade projekt i vårt kodförråd och delträdssammanfoga dem i vårt projekt då och då. Det är trevligt på vissa sätt, till exempel att all kod checkas in på ett enda ställe. Det har dock andra nackdelar, som att det är lite mer komplext och lättare att göra misstag när man återintegrerar ändringar eller råkar skicka en gren till ett orelaterat kodförråd.

En annan lite märklig sak är att om du vill få en diff mellan det du har i din underkatalog rack och koden i din rack_branch‑gren — för att se om du behöver sammanfoga dem — kan du inte använda vanliga git diff. I stället måste du köra git diff-tree med den gren du vill jämföra mot:

$ git diff-tree -p rack_branch

Eller, om du vill jämföra det som finns i din underkatalog rack med vad master‑grenen på servern var när du senast hämtade, kan du köra:

$ git diff-tree -p rack_remote/master