Git
Chapters ▾ 2nd Edition

7.6 Herramientas de Git - Reescribiendo la Historia

Reescribiendo la Historia

Muchas veces, al trabajar con Git, vas a querer confirmar tu historia por alguna razón. Una de las grandes cualidades de Git es que te permite tomar decisiones en el último momento. Puede decidir qué archivos entran en juego antes de comprometerse con el área de ensayo, puedes decidir que no querías estar trabajando en algo todavía con el comando de alijos, y puedes reescribir confirmaciones que ya hayan pasado haciendo parecer que fueron hechas de diferente manera. Esto puede desenvolverse en el cambio de las confirmaciones, cambiando mensajes o modificando los archivos en un cometido, aplastando o dividiendo confirmaciones enteramente – todo antes de que compartas tu trabajo con otras personas.

En esta sección, verás cómo complementar esas tareas tan útiles que harán a la confirmación de tu historia aparecer del modo en el cual quisiste compartirla.

Cambiando la última confirmación

Cambiar la última confirmación es probablemente lo más común que le harás a tu historia. Comúnmente querrás hacer dos cosas en tu última confirmación: cambiar la confirmación del mensaje, o cambiar la parte instantánea que acabas de agregar sumando, cambiando y/o removiendo archivos.

Si solamente quieres cambiar la confirmación del mensaje final, es muy sencillo:

$ git commit --amend

Esto te envía al editor de texto, el cual tiene tu confirmación final, listo para modificarse en el mensaje. Cuando guardes y cierres el editor, el editor escribe una nueva confirmación conteniendo el mensaje y lo asigna a tu última confirmación.

Si ya has cambiado tu última confirmación y luego quieres cambiar la instantánea que confirmaste al agregar o cambiar archivos, porque posiblemente olvidaste agregar un archivo recién creado cuando se confirmó originalmente, el proceso trabaja prácticamente de la misma manera. Tu manejas los cambios que quieras editando el archivo y oprimiendo git add en éste o git rm a un archivo adjunto, y el subsecuente git commit --amend toma tu área de trabajo actual y la vuelve una instantánea para la nueva confirmación.

Debes ser cuidadoso con esta técnica porque puedes modificar los cambios del SHA-1 de la confirmación Es como un muy pequeño rebase – no necesitas modificar tu última confirmación si ya lo has hecho.

Cambiando la confirmación de múltiples mensajes

Para modificar una confirmación que está más atrás en tu historia, deberás aplicar herramientas más complejas Git no tiene una herramienta para modificar la historia, pero puedes usar la herramienta de rebase para rebasar ciertas series de confirmaciones en el HEAD en el que se basaron originalmente en lugar de moverlas a otro. Con la herramienta interactiva del rebase, puedes parar justo después de cada confirmación que quieras modificar y cambiar su mensaje, añadir archivos, o hacer cualquier cosa que quieras Puedes ejecutar el rebase interactivamente agregando el comando -i a git rebase. De igual manera debes indicar que tan atrás quieres regresar para reescribir las confirmaciones escribiendo en el comando cuál confirmación quieres rebasar.

Por ejemplo, si quieres cambiar las confirmaciones de los tres últimos mensajes, o cualquiera de los mensajes de confirmación de ese grupo, proporcionas un argumento para el git rebase -i que quieras modificar de tu última confirmación, el cual es HEAD~2^ o HEAD~3 . Debería ser más fácil el recordar el ~3 porque estás tratando de editar las últimas tres confirmaciones; pero ten en mente que estás designando actualmente cuatro confirmaciones atrás, aparte del último cometido que deseas editar:

$ git rebase -i HEAD~3

Recuerda de Nuevo que este es un comando de rebase – cualquier confirmación incluida en el rango de HEAD~3..HEAD será reescrita, aún si cambias el mensaje o no. No incluyas cualquier confirmación que ya hayas enviado al servidor central – si lo haces esto confundirá a los demás desarrolladores proporcionando una versión alternativa del mismo cambio.

Utilizar este comando te da una lista de las confirmaciones en tu editor de texto que se ve como este:

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file

# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Es importante el notar que estas confirmaciones son escuchadas en el orden contrario del que tú normalmente las verías usando el comando de log. Si utilizaras un comando de log, verías algo como esto.

$ git log --pretty=format:"%h %s" HEAD~3..HEAD
a5f4a0d added cat-file
310154e updated README formatting and added blame
f7f3f6d changed my name a bit

Nótese que el orden está al revés. El rebase interactivo te da un script que va a utilizarse. Este empezará en la confirmación que especificas en la línea de comandos (HEAD~3) y reproducirá los cambios introducidos en cada una de estas confirmaciones de arriba a abajo. Este acomoda los más viejos en la parte de arriba, y va bajando hasta los más nuevos, porque ese será el primero en reproducirse

Necesitaras editar el script para que se detenga en la confirmación que quieres editar. Para hacer eso, cambia la palabra pick por la frase edit para cada una de las confirmaciones en las que quieres que el script se detenga. Por ejemplo, para modificar solamente la tercera confirmación del mensaje, cambiarías el archivo para que se viera algo así:

edit f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file

Cuando guardes y salgas del editor, Git te enviará atrás a la última confirmación en la lisa y te llevará a la línea de comando con el siguiente mensaje:

$ git rebase -i HEAD~3
Stopped at f7f3f6d... changed my name a bit
You can amend the commit now, with

       git commit --amend

Once you’re satisfied with your changes, run

       git rebase --continue

Estas instrucciones te dirán exactamente qué hacer. Type

$ git commit --amend

Cambia la confirmación del mensaje, y sal del editor. Then, run

$ git rebase --continue

Este comando te permitirá aplicar las otras dos confirmaciones automáticamente, y después de esto estás listo. Si decides cambiar y elegir editar en más líneas, puedes repetir estos pasos para cada confirmación que cambies en cada edición. Cada vez, Git se parará, permitiéndote modificar la confirmación y continuar cuando hayas terminado

Reordenando Confirmaciones

De igual manera puedes usar rebases interactivos para reordenar o remover confirmaciones enteramente. Si quieres remover la “added cat-file” confirmación y cambiar el orden en el cual las otras dos confirmaciones son introducidas, puedes cambiar el rebase en el script de esto:

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file

A esto:

pick 310154e updated README formatting and added blame
pick f7f3f6d changed my name a bit

Cuando guardes y salgas del editor, Git recordará tu rama de padres de estas confirmaciones, aplicando 310154e y después f7f3f6d, y después se parará. Cambias efectivamente el orden de esas confirmaciones y eliminas la “added cat-file’’ confirmación completamente.

Unir confirmaciones

También es posible el tomar series de confirmaciones y unirlas todas en una sola confirmación con la herramienta interactiva de rebase. El script pone las instrucciones en el mensaje de rebase:

#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Si, en vez de “`pick” o`“edit”, especificas “squash”, Git aplica a ambos este cambio y los cambia directamente después y hace que las confirmaciones se unan. Entonces, si quieres convertir en una única confirmación estas tres confirmaciones, deberás hacer que el script se vea como esto:

pick f7f3f6d changed my name a bit
squash 310154e updated README formatting and added blame
squash a5f4a0d added cat-file

Cuando guardes y salgas del editor, Git aplicará a los tres el cambio y después te dirigirá en el editor para fusionar los tres mensajes de la confirmación:

# This is a combination of 3 commits.
# The first commit's message is:
changed my name a bit

# This is the 2nd commit message:

updated README formatting and added blame

# This is the 3rd commit message:

added cat-file

Cuando guardes eso, tendrás una única confirmación que introducirá los cambios de las tres previas confirmaciones.

Dividiendo una confirmación

Dividir una confirmación la deshace y después realiza etapas parciales de las confirmaciones tantas veces como confirmaciones desees finalizar. Por ejemplo, suponiendo que quieres dividir la confirmación de en medio de tus tres confirmaciones. En vez de “`updated README formatting and added blame”, quieres dividirla en dos confirmaciones: “updated README formatting” para la primera, y “added blame” para la segunda. Puedes hacer eso en el script rebase -i cambiando la instrucción en la confirmación que quieres dividir a “edit”:

pick f7f3f6d changed my name a bit
edit 310154e updated README formatting and added blame
pick a5f4a0d added cat-file

¿Entonces, cuando el script te envíe a la línea de comandos, tu reseteas esa confirmación, tomas los cambios que se han hecho, y creas múltiples confirmación fuera de ellas? Cuando guardes y salgas del editor, Git te enviará al padre de la primera confirmación en tu lista, aplicando a la primera confirmación (f7f3f6d), a la segunda (310154e) y te enviará directamente a la consola. Ahí, puedes hacer un reseteo mixto de esa confirmación con el git reset HEAD^, el que efectivamente deshace las confirmaciones en los archivos referidos. Ahora puedes organizar y confirmar los archivos hasta que tengas varias confirmaciones y ejecutar git rebase --continue cuando hayas terminado:

$ git reset HEAD^
$ git add README
$ git commit -m 'updated README formatting'
$ git add lib/simplegit.rb
$ git commit -m 'added blame'
$ git rebase --continue

Git aplica la última confirmación (a5f4a0d) en el script, y tu historia quedaría de esta manera:

$ git log -4 --pretty=format:"%h %s"
1c002dd added cat-file
9b29157 added blame
35cfb2b updated README formatting
f3cc40e changed my name a bit

Una vez de Nuevo, esto cambia el SHA-1s de todas tus confirmaciones en tu lista, así que asegúrate de que ninguna confirmación esté en esa lista que ya has puesto en un repositorio compartido.

La opción nuclear: filtrar-ramificar

Existe otra opción en la parte de volver a escribir la historia que puedes usar si necesitas reescribir un gran número de confirmaciones de una manera que se puedan scriptear – de hecho, cambiar tu dirección de e-mail o remover cualquier archivo en las confirmaciones. El comando es filter-branch, y este puede reescribir una gran cantidad de franjas de tu historia, así que probablemente no lo deberías usar a menos que tu proyecto aún no sea público y otra persona no se haya basado en las confirmaciones que estás a punto de reescribir. Como sea, podría ser muy útil. Aprenderás unas cuantas maneras muy comunes de obtener una idea de algunas de las cosas que es capaz de hacer.

Remover un archivo de cada confirmación

Esto ocurre comunmente. Alguien accidentalmente confirma un gran número binario de un archivo con un irreflexivo git add ., y quieres removerlo de todas partes. Suponiendo que accidentalmente confirmaste un archivo que contenía contraseña y quieres volverlo un proyecto abierto. filter-branch es la herramienta que tu probablemente quieres usar para limpiar toda tu historia. Para remover un archivo nombrado passwords.txt de tu historia complete puedes aplicar el comando --tree-filter a filter-branch:

$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten

El --tree-filter inicia el comando específico después de cada revisión del proyecto y éste entonces vuelve a confirmar los resultados. En este caso, deberías remover el archivo llamado passwords.txt de cada instantánea, aún si existe o no. Si quieres remover todas las confirmaciones accidentales del respaldo del editor de archivos, puedes iniciar algo como el git filter-branch --tree-filter 'rm -f *~' HEAD.

Deberías ser capaz de ver la re-escripción de confirmaciones y estructuras de Git y luego debes mover el puntero de la rama al final. Es generalmente una buena idea hacer esto en una parte de prueba de la rama y hacer un hard-reset de tu rama principal después de haber determinado que el resultado es lo que realmente deseas. Para iniciar filter-branch en todas las ramas, puedes poner --all en el comando.

Hacer que un subdirectorio sea la nueva raíz

Suponiendo que has hecho una importación desde otro centro de Sistema de Control y tienes subdirecciones que no tienen ningún sentido (tronco, etiquetas, etc). . Si quieres hacer que el subdirectorio tronco sea el nuevo proyecto de la raíz de cada confirmación, filter-branch te puede ayudar a hacer eso también:

$ git filter-branch --subdirectory-filter trunk HEAD
Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)
Ref 'refs/heads/master' was rewritten

Ahora la raíz de tu nuevo proyecto es la que solía estar en el subdirectorio tronco cada vez. Git automáticamente remueve confirmaciones que no afecten al subdirectorio.

Cambiar la dirección de e-mail globalmente

Otro caso común es que olvides iniciar el git config para poner tu nombre y tu dirección de e-mail antes de que hayas empezado a trabajar, o tal vez quieres abrir un proyecto en el trabajo y cambiar tu e-mail de trabajo por tu e-mail personal. En cualquier caso, puedes cambiar la dirección de e-mail de múltiples confirmaciones en un lote con filter-branch igual. Necesitas ser cuidadoso de cambiar sólo las direcciones de e-mail que son tuyas, de manera que debes usar --commit-filter:

$ git filter-branch --commit-filter '
        if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
        then
                GIT_AUTHOR_NAME="Scott Chacon";
                GIT_AUTHOR_EMAIL="schacon@example.com";
                git commit-tree "$@";
        else
                git commit-tree "$@";
        fi' HEAD

Esto va a través de la re-escripción de cada confirmación para tener tu nuva dirección. Porque cada confirmación contiene el SHA-1 de sus padres, este comando cambia cada confirmación del SHA-1 en tu historia, no solamente aquellos en los cuales el e-mail es el mismo o encaja.