Git
Chapters ▾ 2nd Edition

7.9 Utilitaires Git - Rerere

Rerere

La fonctionalité git rerere est une fonction un peu cachée. Le nom vient de l’anglais reuse recorded resolution («  utiliser les solutions en re gistrées ») et comme son nom l’indique, cela permet de demander à Git de se souvenir comment vous avez résolu un conflit sur une section de diff de manière que la prochaine fois qu’il rencontre le même conflit, il le résolve automatiquement pour vous.

Il existe pas mal de scénarios pour lesquels cette fonctionalité peut se montrer efficace. Un exemple mentionné dans la documentation cite le cas d’une branche au long cours qui finira par fusionner proprement mais ne souhaite pas montrer des fusions intermédiaires. Avec rerere activé, vous pouvez fusionner de temps en temps, résoudre les conflits, puis sauvegarder la fusion. Si vous faites ceci en continu, alors la dernière fusion devrait être assez facile parce que rerere peut quasiment tout faire automatiquement pour vous.

La même tactique peut être utilisée si vous souhaitez rebaser plusieurs fois une branche tout en ne souhaitant pas avoir à gérer les mêmes conflits de rebasage à chaque fois. Ou si vous voulez prendre la branche que vous avez fusionnée et si vous avez eu à corriger des conflits, puis décidez de la rebaser pour finir - vous souhaitez sûrement ne pas avoir à recorriger les mêmes conflits.

Une autre situation similaire apparaît quand vous fusionnez ensemble de temps en temps une série de branches thématiques évolutives dans un sommet testable, comme le projet Git lui-même le fait souvent. Si les tests échouent, vous pouvez rembobiner vos fusions et les rejouer en écartant la branche qui a provoqué l’erreur sans devoir résoudre à nouveau tous les conflits.

Pour activer la fonctionnalité rerere, vous devez simplement lancer le paramétrage :

$ git config --global rerere.enabled true

Vous pouvez aussi l’activer en créant le répertoire .git/rr-cache dans un dépôt spécifique, mais l’activation par ligne de commande reste plus claire et permet d’activer la fonction globalement.

Voyons maintenant un exemple similaire au précédent. Supposons que nous avons un fichier qui contient ceci :

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

Dans une branche, nous changeons « hello » en « hola », puis dans une autre branche nous changeons « world » en « mundo », comme précédemment.

rerere1

Quand nous fusionnons les deux branches ensemble, nous obtenons un conflit de fusion :

$ git merge i18n-world
Fusion automatique de hello.rb
CONFLIT (contenu): Conflit de fusion dans hello.rb
Recorded preimage for 'hello.rb'
La fusion automatique a échoué ; réglez les conflits et validez le résultat.

Vous devriez avoir noté la présence d’un nouvelle ligne Recorded preimage for FILE (« Enregistrement de la pré-image pour FICHIER »). À part ce détail, cela ressemble à un conflit de fusion tout à fait normal. À ce stade, rerere peut déjà nous dire un certain nombre de choses. Normalement, vous lanceriez un git status pour voir l’état actuel des conflits.

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

Cependant, git rerere vous indiquera aussi les conflits pour lesquels il a enregistré la pré-image grâce à git rerere status :

$ git rerere status
hello.rb

Et git rerere diff montrera l’état actuel de la résolution ‑ quel était le conflit de départ et comment vous l’avez résolu.

$ 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

En complément (et bien que ça n’ait pas vraiment à voir avec rerere), vous pouvez utiliser ls-files -u pour voir les fichiers en conflit ainsi que les versions précédentes, à droite et à gauche :

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

Maintenant, vous pouvez le résoudre pour que la ligne de code soit simplement puts 'hola mundo' et vous pouvez relancer la commande rerere diff pour visualiser ce que rerere va mémoriser :

$ 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

Cela indique simplement que quand Git voit un conflit de section dans un fichier hello.rb qui contient « hello mundo » d’un côté et « hola world » de l’autre, il doit résoudre ce conflit en « hola mundo ».

Maintenant, nous pouvons le marquer comme résolu et le valider :

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

Vous pouvez voir qu’il a « enregistré la résolution pour FICHIER » (Recorded resolution for FILE).

rerere2

Maintenant, défaisons la fusion et rebasons plutôt la branche sur la branche master. Nous pouvons déplacer notre branche en arrière en utilisant reset comme vu dans Reset démystifié.

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

Notre fusion est défaite. Rebasons notre branche thématique.

$ git checkout i18n-world
Basculement sur la branche 'i18n-world'

$ git rebase master
Premièrement, rembobinons head pour rejouer votre travail par-dessus...
Application : i18n world
Utilisation de l'information de l'index pour reconstruire un arbre de base...
M       hello.rb
Retour à un patch de la base et fusion à 3 points...
Fusion automatique de hello.rb
CONFLIT (contenu) : Conflit de fusion dans hello.rb
Resolved 'hello.rb' using previous resolution.
Échec d'intégration des modifications.
Le patch a échoué à 0001 i18n world

Ici, nous avons obtenu le conflit de fusion auquel nous nous attendions, mais des lignes supplémentaires sont apparues, en particulier Resolved FILE using previous resolution (FICHIER résolu en utilisant une résolution précédente). Si nous inspectons le fichier hello.rb, il ne contient pas de marqueur de conflit.

$ cat hello.rb
#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

git diff nous montrera comment le conflit a été re-résolu automatiquement :

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

Vous pouvez aussi recréer l’état de conflit du fichier avec la commande 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

Nous avons vu un exemple de ceci dans Fusion avancée. Pour le moment, re-résolvons-le en relançant rerere :

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

def hello
  puts 'hola mundo'
end

Nous avons re-résolu le conflit du fichier automatiquement en utilisant la résolution mémorisée par rerere. Vous pouvez le valider avec add et terminer de rebaser.

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

Dans les cas où vous souhaitez réaliser de nombreuses fusions successives d’une branche thématique ou si vous souhaitez la synchroniser souvent avec master sans devoir gérer des tas de conflits de fusion, ou encore si vous rebasez souvent, vous pouvez activer rerere qui vous simplifiera la vie.