Git
Chapters ▾ 2nd Edition

7.9 Herramientas de Git - Rerere

Rerere

La funcionalidad del "git rerere" es una característica oculta. El nombre se refiere a "reuse recorded resolution" y como el nombre lo insinúa, te permite pedirle a Git que recuerde cómo resolviste un conflicto de hunk, así que la próxima vez que vea el mismo conflicto, Git puede resolverlo automáticamente por ti.

Hay una serie de escenarios en los cuales esta funcinalidad podría ser realmente útil. Uno de los ejemplos que es mencionado en el manual es que si te quieres asegurar de que una rama temática longeva se unirá limpiamente, pero no quieres tener un montón de commits de unión por la mitad. Con "rerere" encendido, puedes unir ocacionalmente, resolver los conflictos, y luego revertir la unión. Si haces esto continuamente, entonces la unión final debería ser fácil porque "rerere" puede hacer todo por ti automáticamente.

Esta misma táctica puede ser usada si quieres mantener una rama con rebase, de esta manera no tienes que lidiar con los mismos conflictos de rebase cada vez que lo haces. O si quieres tomar una rama que uniste y arreglar un montón de conflictos y entonces decidir hacer rebase en su lugar - probablemente no tengas que hacer todos los mismos conflictos de nuevo.

Otra situación es cuando unes un montón de ramas temáticas en evolución juntas en una head de pruebas ocacionalmente, como el mismo proyecto Git hace frecuentemente. Si las pruebas fallan, puedes rebobinar las uniones y rehacerlas sin la rama teática que hace fallar a las pruebas sin tener que re-resolver los conflictos de nuevo.

Para activar la funcionalidad "rerere", simplemente tienes que ejecutar este ajuste de configuración:

$ git config --global rerere.enabled true

Puedes encenderlo también creando el directorio ".git/rr-cache" en un repositorio específico, pero el ajuste de configuración es limpiador y puede ser hecho globalmente.

Ahora veamos un ejemplo simple, similar al anterior. Digamos que tenemos un archivo que luce de esta manera:

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

En una rama, cambiamos la palabra "hello" por "hola", entonces, en otra rama cambiamos el "world" por "mundo", justo como antes.

rerere1

Cuando unimos las dos ramas juntas, tendremos un conflicto de unión:

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

Deberías notar la nueva línea "Recorded preimage for FILE" ahí adentro. Si no, debería verse exactamente como un conflicto de unión normal. En este punto, "rerere" puede decirnos algunas cosas. Normalmente, podrías ejecutar "git status" en este punto para ver todo lo que entró en conflicto:

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

Sin embargo, "git rerere" también te dirá lo que ha registrado el estado pre-unión con "git rerere status":

$ git rerere status
hello.rb

Y "git rerere diff" mostrará el estado actual de la resolución - con lo que comenzaste a resolver y lo que has resuelto.

$ 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

Además (y esto no está realmente relacionado a "rerere"), puedes usar "ls-files -u" para ver los archivos que están en conflicto y las versiones anterior, izquierda y derecha:

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

Ahora puedes resolverlo para ser simplemente puts 'hola mundo' y puedes ejecutar el comando "rerere diff" de nuevo para ver lo que "rerere" recordará:

$ 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

Eso básicamente dice, cuando Git ve un conflicto de hunk en un archivo "hello.rb" que tiene "hello mundo" en un lado y "hola world" en el otro, lo resolverá como "hola mundo".

Ahora podemos marcarlo como resuleto y hacerle commit:

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

Ahora podemos ver que "Recorded resolution for FILE" (Registró solución para ARCHIVO).

rerere2

Ahroa, deshagamos esa unión y luego hagámosle rebase en la cima de nuestra rama maestra en su lugar. Podemos tener nuestra rama de vuelta usando "reset" como vimos en Reiniciar Desmitificado.

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

Nuestra unión no está hecha. Ahora hagámos rebase a la rama temática.

$ 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

Ahora, tenemos el mismo conflicto de unión que esperábamos, pero échale un vistaso a la línea "Resolved FILE using previous resolution". Si le echamos un vistazo al archivo, veremos que ya está resuleto, ya no hay marcas de conflicto de unión en él.

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

def hello
  puts 'hola mundo'
end

Además, "git diff" te mostrará cómo fue re-resuleto automáticamente:

$ 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

Puedes también recrear el archivo en conflicto con el comando "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

Vimos un ejemplo de esto en Fusión Avanzada. Por ahora, aunque resolvámoslo sólo ejecuntando "rerere" de nuevo:

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

def hello
  puts 'hola mundo'
end

Hemos re-resuelto el archivo automáticamente usando la resolución en caché "rerere". Ahora puedes añadir y continuar el rebase para completarlo.

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

Entonces, si haces muchas re-uniones, o quieres mantener una rama temática actualizada con tu rama maestra sin un montón de uniones, o haces rebase a menudo, puedes encender "rerere" para ayudar un poco a tu vida.