Chapters ▾ 2nd Edition

7.9 Git-verktyg - Rerere

Rerere

Funktionen git rerere är lite av en dold finess. Namnet står för "reuse recorded resolution" (återanvänd sparad lösning) och, som namnet antyder, låter den dig be Git komma ihåg hur du löste en konflikt i ett diffstycke så att nästa gång den ser samma konflikt kan Git lösa den åt dig automatiskt.

Det finns flera scenarier där detta kan vara riktigt användbart. Ett av exemplen som nämns i dokumentationen är när du vill se till att en långlivad ämnesgren till sist sammanfogas rent, men du inte vill ha en massa mellanliggande sammanslagningsincheckningar som skräpar ned din historik. Med rerere aktiverat kan du göra enstaka sammanslagningar, lösa konflikterna och sedan backa ur sammanslagningen. Om du gör detta kontinuerligt ska den slutliga sammanslagningen vara enkel eftersom rerere kan göra allt åt dig automatiskt.

Samma taktik kan användas om du vill hålla en gren ombaserad så att du inte behöver hantera samma ombaseringskonflikter varje gång du gör det. Eller om du vill ta en gren som du sammanfogade och löste en massa konflikter i och sedan bestämma dig för att ombasera den i stället — du kommer sannolikt inte behöva lösa alla samma konflikter igen.

En annan användning av rerere är när du sammanfogar ett gäng utvecklande ämnesgrenar till ett testbart huvud då och då, precis som Git-projektet ofta gör. Om testerna fallerar kan du spola tillbaka sammanfogningarna och göra om dem utan ämnesgrenen som fick testerna att fallera, utan att behöva lösa konflikterna igen.

För att aktivera rerere behöver du bara köra denna konfigurationsinställning:

$ git config --global rerere.enabled true

Du kan också slå på det genom att skapa katalogen .git/rr-cache i ett specifikt kodförråd, men konfigurationsinställningen är tydligare och aktiverar funktionen globalt för dig.

Nu ska vi titta på ett enkelt exempel, likt det vi såg tidigare. Säg att vi har en fil som heter hello.rb som ser ut så här:

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

I den ena grenen ändrar vi ordet “hello” till “hola”, och i en annan gren ändrar vi “world” till “mundo”, precis som tidigare.

Två grenar som ändrar samma del av samma fil på olika sätt
Figur 158. Två grenar som ändrar samma del av samma fil på olika sätt

När vi sammanfogar de två grenarna får vi en sammanslagningskonflikt:

$ git merge i18n-world
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Recorded preimage for 'hello.rb'
Automatic merge failed; fix conflicts and then commit the result.

Du bör lägga märke till den nya raden Recorded preimage for FILE där. Annars ser det exakt ut som en vanlig sammanslagningskonflikt. Här kan rerere tala om några saker. Normalt skulle du köra git status här för att se vad som är i konflikt:

$ git status
# On branch master
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add <file>..." to mark resolution)
#
#	both modified:      hello.rb
#

Men git rerere kan också tala om vad den har registrerat för för‑sammanslagningsläge med git rerere status:

$ git rerere status
hello.rb

Och git rerere diff visar det aktuella läget för lösningen — vad du började med att lösa och vad du har löst det till.

$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,11 @@
 #! /usr/bin/env ruby

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

Dessutom (och detta är inte egentligen relaterat till rerere) kan du använda git ls-files -u för att se konfliktfilerna och versionerna före, vänster och höger:

$ git ls-files -u
100644 39804c942a9c1f2c03dc7c5ebcd7f3e3a6b97519 1	hello.rb
100644 a440db6e8d1fd76ad438a49025a9ad9ce746f581 2	hello.rb
100644 54336ba847c3758ab604876419607e9443848474 3	hello.rb

Nu kan du lösa det så att det bara blir puts 'hola mundo' och köra git rerere diff igen för att se vad rerere kommer att komma ihåg:

$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,7 @@
 #! /usr/bin/env ruby

 def hello
-<<<<<<<
-  puts 'hello mundo'
-=======
-  puts 'hola world'
->>>>>>>
+  puts 'hola mundo'
 end

Det betyder i praktiken att när Git ser en diffstyckeskonflikt i en hello.rb‑fil som har “hello mundo” på ena sidan och “hola world” på den andra, kommer den att lösa den till “hola mundo”.

Nu kan vi markera den som löst och checka in den:

$ git add hello.rb
$ git commit
Recorded resolution for 'hello.rb'.
[master 68e16e5] Merge branch 'i18n'

Du kan se att den skrev "Recorded resolution for FILE".

Recorded resolution for FILE
Figur 159. Recorded resolution for FILE

Nu ska vi ångra den sammanslagningen och sedan ombasera den ovanpå vår master‑gren i stället. Vi kan flytta tillbaka vår gren med git reset som vi såg i Nollställning förklarad.

$ git reset --hard HEAD^
HEAD is now at ad63f15 i18n the hello

Sammanslagningen är ångrad. Nu ombaserar vi ämnesgrenen.

$ git checkout i18n-world
Switched to branch 'i18n-world'

$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: i18n one word
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Resolved 'hello.rb' using previous resolution.
Failed to merge in the changes.
Patch failed at 0001 i18n one word

Nu fick vi samma sammanslagningskonflikt som förväntat, men titta på raden Resolved FILE using previous resolution. Om vi tittar på filen ser vi att den redan är löst; det finns inga konfliktmarkörer i den.

#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

Dessutom visar git diff hur den automatiskt löstes igen:

$ git diff
diff --cc hello.rb
index a440db6,54336ba..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
Automatiskt löst sammanslagningskonflikt med tidigare lösning
Figur 160. Automatiskt löst sammanslagningskonflikt med tidigare lösning

Du kan också återskapa konfliktläget för filen med git checkout:

$ git checkout --conflict=merge hello.rb
$ cat hello.rb
#! /usr/bin/env ruby

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

Vi såg ett exempel på detta i Avancerad sammanslagning. Men för tillfället löser vi den igen genom att helt enkelt köra git rerere:

$ git rerere
Resolved 'hello.rb' using previous resolution.
$ cat hello.rb
#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

Vi har nu löst filen automatiskt igen med den mellanlagrade lösningen i rerere. Du kan nu lägga till och fortsätta ombaseringen för att slutföra den.

$ git add hello.rb
$ git rebase --continue
Applying: i18n one word

Så om du gör många omsammanfogningar, eller vill hålla en ämnesgren uppdaterad med din master‑gren utan en massa sammanslagningar, eller om du ofta ombaserar, kan du slå på rerere för att göra livet lite enklare.