Skip to main content

Git, de forma fácil: cambia el historial con rebase—parte II

Escrito por
Headshot of Or Kamara

Or Kamara

17 de junio de 2020

0 minutos de lectura

Te damos la bienvenida a la segunda parte sobre cómo trabajar con Git de forma fácil.

En el artículo anterior, repasamos los conceptos básicos de Rebase y dos situaciones comunes que podríamos encontrar al trabajar con Git. Puedes encontrar la primera parte aquí. En esta parte, repasaremos cuatro situaciones comunes más y algunas conclusiones.

¡Empecemos!

Situaciones comunes (¡y cómo resolverlas!)—parte II

Retomemos donde nos quedamos la última vez con algunas situaciones comunes en las que quizás quieras cambiar el historial de tu código, y cómo hacerlo con Git:

1. Dividir un commit antiguo en dos (edit)

Supongamos que acabas de darte cuenta de que cometiste un error al combinar 2 commits (consulta la parte I para obtener más detalles) y quieres deshacer el squash de 17a73c1 en e2952e7, para que el nuevo archivo quede en un commit diferente. La solución sería ejecutar todos los commits hasta el punto específico que necesitas editar, detenerte, hacer los cambios y continuar. En otras palabras, usa la opción edit .

Empezaremos ejecutando git rebase -i master de nuevo, pero esta vez con la opción edit en el commit que queremos dividir (el segundo commit, `6871683`):

pick 682c27f feat: 1st commit
edit 6871683 feat: 2nd commit
pick 1b2f5b1 feat: 3rd commit

Al salir de la pantalla de rebase, veremos lo siguiente:

Stopped at 6871683...  feat: 2nd commit
You can amend the commit now, with

  git commit --amend '-S'

Once you are satisfied with your changes, run

  git rebase --continue

Básicamente, Git ejecutó el primer y el segundo commit, se detuvo y ahora nosotros tenemos el control. ¡Podemos hacer todos los cambios que queramos!

Este es el estado actual de nuestro gráfico de Git:

* 6871683 (HEAD) feat: 2nd commit* 682c27f feat: 1st commit
* 24aa86a (master) Init: Git History

Como mencionamos antes, básicamente queremos dividir el segundo commit en 2 commits distintos, uno por archivo. El primer paso sería restablecer los cambios del segundo commit ejecutando git reset --soft HEAD^. El comando reset cambia el commit al que apunta actualmente la rama HEAD. Al usar la opción --soft, podemos conservar el índice y el directorio de trabajo, mientras que todos los archivos modificados entre el HEAD original y el commit quedan en el área de preparación.

Este es el nuevo estado de Git. Como puedes ver, ambos archivos están guardados en el área de preparación:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
 modified:   b.txt
 new file:   b2.txt

Ahora quitemos b2.txt de la lista de archivos preparados ejecutando git restore --staged b2.txt:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
 modified:   b.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
 b2.txt

Con solo b.txt como archivo preparado, ahora podemos confirmar la primera parte del segundo commit. Luego preparamos b2.txt y confirmamos la segunda parte del segundo commit:

❯ git commit -m "feat: 2nd commit - part 1"
❯ git add b2.txt
❯ git commit -m "feat: 2nd commit - part 2"

Ahora nuestro gráfico de Git se ve así:

* 96127c6 feat: 2nd commit - part 2
* 93260e5 feat: 2nd commit - part 1
* 9882b2f feat: 1st commit
* 24aa86a (master) Init: Git History

El último paso sería continuar el rebase y ejecutar el resto de los commits con:

git rebase --continue. El gráfico final de Git debería verse así:

* 09fe03e (HEAD -> feat/git-history) feat: 3rd commit
* 96127c6 feat: 2nd commit - part 2
* 93260e5 feat: 2nd commit - part 1
* 9882b2f feat: 1st commit
* 24aa86a (master) Init: Git History

2. Modificar todos los archivos de un commit específico (edit)

Probablemente ya conoces la siguiente situación: enviaste todo tu código, perfectamente dividido en varios commits, pero luego te das cuenta de que olvidaste ejecutar el linter. Como explicamos antes, la solución no debería ser crear otro commit llamado fix linting, sino corregir el linting en todos y cada uno de los commits.

Si el cambio es menor, podemos usar la opción squash, pero si hay varios cambios en varios commits, lo mejor será ejecutar nuestro script de linting uno por uno en cada commit (o en los commits específicos que queramos corregir). Para eso, podemos volver a usar la opción edit (como en el ejemplo anterior).

Ejecutemos de nuevo git commit rebase -i y usemos la opción edit en el primer commit (un commit con varios archivos):

edit 9882b2f feat: 1st commit
pick 93260e5 feat: 2nd commit - part 1
pick 96127c6 feat: 2nd commit - part 2
pick 09fe03e feat: 3rd commit

Como antes, Git se detiene después de ejecutar el primer commit:

Stopped at 9882b2f...  feat: 1st commit
You can amend the commit now, with

  git commit --amend '-S'

Once you are satisfied with your changes, run

  git rebase --continue

Ahora podemos ejecutar nuestro linter (por ejemplo, npm run lint) y revisar el estado de nuestro repositorio:

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
 modified:   a.txt
 modified:   a1.txt
 modified:   a2.txt
 modified:   a3.txt
 modified:   a4.txt
 modified:   a5.txt

Como puedes ver, el script del linter modificó el contenido de 6 archivos (a*.txt). Ahora solo tenemos que enmendar el commit actual (preparando y confirmando los cambios) y continuar con el rebase:

❯ git add a*
❯ git commit --amend --no-edit
❯ git rebase --continue
Successfully rebased and updated refs/heads/feat/git-history.

Y el gráfico de Git debería quedar así:

* 8f404fe (HEAD -> feat/git-history) feat: 3rd commit
* 7e37b6c feat: 2nd commit - part 2
* f71ad8e feat: 2nd commit - part 1
* b50f18f feat: 1st commit
* 24aa86a (master) Init: Git History

3. Asegurarte de que las pruebas pasen en cada commit (exec)

Al dividir tu código en varios commits, recuerda que cada commit debe funcionar por sí solo. En otras palabras, después de combinar varios commits en una de las ramas principales, deberías poder volver a cada uno de ellos y esperar que el código compile y que todas las pruebas pasen.

Entonces, ¿cómo puedes validar eso antes de combinar los commits?

¡Ejecutemos manualmente todas las pruebas en cada commit! Es broma, por supuesto: deberíamos hacerlo automáticamente :) Para eso, podemos usar la opción exec .

Esta opción nos ayuda a ejecutar scripts de shell como parte del proceso de rebase. Para hacerlo, solo tenemos que agregar líneas nuevas con el comando exec donde queramos. Por ejemplo, ejecutemos de nuevo git rebase -i master:

pick b50f18f feat: 1st commit
pick f71ad8e feat: 2nd commit - part 1
pick 7e37b6c feat: 2nd commit - part 2
pick 8f404fe feat: 3rd commit

Ahora viene la parte divertida: ejecutar las pruebas (con el script ./run_tests.sh) después de cada commit:

pick b50f18f feat: 1st commit
exec ./run_tests.sh
pick f71ad8e feat: 2nd commit
exec ./run_tests.sh
pick 7e37b6c feat: 2nd commit - part 2
exec ./run_tests.sh
pick 8f404fe feat: 3rd commit
exec ./run_tests.sh

Este es el resultado al salir de la ventana de rebase:

❯ git rebase -i master
Executing: ./run_tests.sh
All tests passed!
Executing: ./run_tests.sh
5 tests failed :( 108 tests passed!
Executing: ./run_tests.sh
All tests passed!
Executing: ./run_tests.sh
All tests passed!
Successfully rebased and updated refs/heads/feat/git-history.

Como puedes ver, Git ejecutó el script de pruebas después de cada commit. Así que ahora sabemos que algo en el commit feat: 2nd commit hace que fallen las pruebas. También es posible agregar un único comando exec después de cada commit ejecutando:

$ git rebase -i --exec "./run_tests.sh"

Lo último (en realidad, lo primero) que debes saber: cómo cancelar

Esto no es una situación, sino más bien una advertencia: ¡el comando rebase puede ser peligroso! Usarlo sin comprender los efectos de los comandos podría arruinar tu trabajo. Por eso, empezaré con 2 recomendaciones:

  • Antes de empezar a modificar los commits, asegúrate siempre de tener una copia de seguridad adecuada de tu repositorio de Git. Lo más sencillo que me gusta hacer es enviar todos mis cambios a una rama remota. Si algo sale mal, siempre puedo volver al código original (con git reset).

  • Asegúrate de entender los comandos. En lugar de usarlos por primera vez en tu código real, pruébalos antes en un repositorio temporal de Git.

Ten en cuenta que siempre puedes detener el proceso de rebase y volver al estado original:

  • En la pantalla inicial de rebase, solo elimina todas las líneas de commits y no cambiará nada.

  • A mitad del proceso de rebase, si por alguna razón te pierdes y no sabes qué hacer, siempre puedes ejecutar git rebase --abort. Este comando detiene el proceso de rebase y devuelve el repositorio a su estado original (antes de ejecutar git rebase -i).

En resumen

Como vimos en los últimos ejemplos, git rebase es un comando potente, sobre todo cuando se usa de forma interactiva. Hay una infinidad de casos en los que podemos usarlo, pero espero haber logrado darte una idea básica de cuándo resulta útil cada opción.

Lo principal que me gustaría que te lleves de este artículo es que uses git rebase con criterio y sin miedo. Sé que al principio puede dar un poco de temor y que quizás quieras que alguien te acompañe mientras presionas la tecla Enter, pero no pasa nada. Si sigues practicando, se volverá parte de tu rutina. Además, estoy seguro de que hay muchas más situaciones, adaptadas a tus casos de uso específicos, en las que este comando resulta útil.

¡Que lo disfrutes!

Comienza con Capture the Flag

Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.

Publicado en:

Leer más

Blog

Verano en los estadios: la gira Snyk Connect Fan Zone

La gira Fan Zone de Snyk llevó talleres de seguridad de IA, networking y competencia amistosa a 8 ciudades y 3 sesiones virtuales. Los asistentes desarrollaron habilidades, compartieron ideas y crecieron juntos.

Blog

Snyk VulnBench JS 1.0: ¿Pueden los LLM encontrar los mismos errores dos veces?

Snyk VulnBench JS 1.0: 300 análisis repetidos muestran que los hallazgos de seguridad de los LLM varían en cada ejecución, mientras que SAST y los modelos detectan distintas vulnerabilidades.

feature insights context
Blog

Cómo construir seguridad de IA junto con nuestros clientes: 5 lecciones del programa de socios de diseño de Evo

Conoce 5 lecciones clave del programa de socios de diseño de Evo de Snyk. Descubre cómo el descubrimiento de IA, la inteligencia de riesgos y la automatización de políticas ayudan a los equipos a proteger la IA generativa y controlar la proliferación de IA a gran escala.