En la lección anterior usamos git rebase para una sola cosa: cambiar la base de una rama. Pero el mecanismo que hay debajo —descomponer una rama en una lista de commits y volver a aplicarlos uno a uno— permite mucho más si alguien te deja editar esa lista antes de que se ejecute.
Eso es git rebase -i. Git te abre un fichero de texto con los commits que va a reaplicar, uno por línea, y espera a que tú decidas qué hacer con cada uno: dejarlo tal cual, cambiarle el mensaje, unirlo con el anterior, partirlo en dos, moverlo de sitio o borrarlo. Cuando guardas y cierras, Git ejecuta tus instrucciones al pie de la letra.
Es la herramienta que convierte una rama real —con sus dudas, sus vueltas atrás y sus wip— en una secuencia de commits que otra persona puede leer y entender. Bruno la necesita ahora mismo: su rama funcionalidad/exportacion-csv tiene seis commits, tres de ellos llamados arreglo, wip 2 y wip 3, y quiere publicarla para que la revisen.
Y con ella vuelve, más fuerte que nunca, la regla de oro: el rebase interactivo reescribe commits, así que solo se aplica a trabajo que aún no has publicado (o a una rama publicada que solo tú usas y de la que puedes avisar).
Contenido
- Qué es la lista de tareas
- Las órdenes disponibles
- El punto de partida: la rama de Bruno
- Sesión completa: de seis commits a dos
squashfrente afixup- Reordenar y eliminar commits
- Dividir un commit en dos con
edit - Cambiar solo un mensaje con
reword --autosquash:commit --fixupycommit --squash--exec: validar cada commitbreak,label,resetymerge- Qué hacer si te pierdes a mitad
- Qué es la lista de tareas
Al lanzar un rebase interactivo, Git prepara la lista de commits que va a reaplicar y te la abre en tu editor:
pick b1c4f80 Empezar exportacion pick 3d6e9a1 Anadir boton de exportar pick c8f2b47 mas cosas del csv pick 2a7f4c1 arreglo pick 9e3b8d6 wip 2 pick 5c1a9f2 wip 3 # Rebase e91d4a8..5c1a9f2 onto e91d4a8 (6 commands) # # Commands: # p, pick <commit> = use commit # r, reword <commit> = use commit, but edit the commit message # e, edit <commit> = use commit, but stop for amending # s, squash <commit> = use commit, but meld into previous commit # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous # commit's log message # x, exec <command> = run command (the rest of the line) using shell # b, break = stop here (continue rebase later) # d, drop <commit> = remove commit # l, label <label> = label current HEAD with a name # t, reset <label> = reset HEAD to a label # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST.
Tres detalles que hay que asimilar antes de tocar nada:
- El orden es cronológico ascendente: el commit más antiguo arriba. Es al revés que
git log, que muestra el más reciente primero. Esta inversión es la primera fuente de errores de quien empieza. - Las líneas se ejecutan de arriba abajo. Reordenar líneas reordena commits.
- Borrar una línea equivale a
drop: ese commit desaparece. Git te lo avisa en mayúsculas por algo.
Y una precisión sobre el argumento: git rebase -i main significa "reaplica sobre main todo lo que hay en mi rama y no en main", igual que en la lección anterior. Si solo quieres tocar los últimos N commits de la rama en la que estás, sin cambiar de base, se usa:
git rebase -i HEAD~4 # los 4 últimos commits
git rebase -i 8d4e6b2 # todo lo que hay DESPUÉS de ese commitOjo con esto último: git rebase -i <commit> no incluye <commit> en la lista, porque <commit> es la base. Para tocar los últimos cuatro commits, la base es el quinto empezando por el final: HEAD~4.
Si tu editor por defecto no es el que quieres, se cambia con
git config --global core.editor "code --wait"(onano,vim, lo que uses). Se explicó en la lección 01-05, y aquí lo vas a agradecer.
- Las órdenes disponibles
| Orden | Abreviatura | Qué hace |
|---|---|---|
pick |
p |
Aplica el commit tal cual. Es el valor por defecto |
reword |
r |
Aplica el commit y abre el editor para cambiar su mensaje |
edit |
e |
Aplica el commit y se detiene para que lo modifiques (o lo partas) |
squash |
s |
Une el commit con el anterior y abre el editor para combinar los dos mensajes |
fixup |
f |
Une el commit con el anterior y descarta su mensaje |
fixup -C |
— | Une con el anterior pero conserva este mensaje en lugar del anterior |
drop |
d |
Elimina el commit. Equivale a borrar la línea |
exec |
x |
Ejecuta un comando de shell en ese punto de la secuencia |
break |
b |
Se detiene ahí sin hacer nada, para que mires |
label |
l |
Pone un nombre temporal al HEAD actual |
reset |
t |
Vuelve a un label previo |
merge |
m |
Crea un commit de fusión con un label |
Las cuatro primeras cubren el 95 % del uso real. drop y exec aparecen de vez en cuando. break es una comodidad. label, reset y merge solo aparecen cuando usas --rebase-merges para reconstruir un historial con fusiones, y no vamos a necesitarlas en el día a día: basta con saber qué significan si te las encuentras.
Un apunte sobre squash y fixup: unen con el commit anterior de la lista, es decir, con la línea de encima. Por eso nunca pueden ser la primera línea (no hay nada con lo que unirse) y por eso el orden importa tanto.
- El punto de partida: la rama de Bruno
Bruno lleva una semana con la exportación de tareas a CSV. La rama funciona, pero el historial es un desastre:
5c1a9f2 wip 3 9e3b8d6 wip 2 2a7f4c1 arreglo c8f2b47 mas cosas del csv 3d6e9a1 Anadir boton de exportar b1c4f80 Empezar exportacion
Con --stat se ve qué hay realmente en cada uno:
5c1a9f2 wip 3 app.js | 4 ++-- 9e3b8d6 wip 2 app.js | 7 +++++-- 2a7f4c1 arreglo app.js | 2 +- c8f2b47 mas cosas del csv app.js | 18 ++++++++++++++++++ 3d6e9a1 Anadir boton de exportar index.html | 3 +++ estilos.css | 8 ++++++++ b1c4f80 Empezar exportacion app.js | 6 ++++++
Leído con calma, la rama hace dos cosas independientes:
- Una función que convierte la lista de tareas a CSV y la descarga (
app.js): commits 1, 3, 4, 5 y 6. - Un botón en la interfaz que la invoca (
index.html+estilos.css): commit 2.
El objetivo de Bruno es acabar con exactamente esos dos commits, en ese orden, con mensajes que expliquen qué hacen. Y lo importante: el contenido final de la rama no va a cambiar ni una línea. Un rebase interactivo bien hecho reorganiza cómo se cuenta la historia, no lo que hay al final.
Ese es el hash del árbol de la punta de la rama. Al terminar el rebase debe ser el mismo. Es el mejor control de calidad que existe para esta operación.
- Sesión completa: de seis commits a dos
Bruno lanza el rebase interactivo:
Y edita la lista que le aparece. Lo primero es reordenar para juntar lo que va junto, y después decidir la orden de cada línea. Así queda el fichero antes de guardar:
pick b1c4f80 Empezar exportacion squash c8f2b47 mas cosas del csv fixup 2a7f4c1 arreglo fixup 9e3b8d6 wip 2 fixup 5c1a9f2 wip 3 pick 3d6e9a1 Anadir boton de exportar
Léelo de arriba abajo como una receta:
pick b1c4f80— aplica el primer commit tal cual. Será la base del commit final de la exportación.squash c8f2b47— únelo al anterior, y déjame combinar los mensajes.fixup 2a7f4c1— únelo al anterior y tira su mensaje (arreglono aporta nada).fixup 9e3b8d6— igual conwip 2.fixup 5c1a9f2— igual conwip 3.pick 3d6e9a1— el commit del botón, movido al final, se queda como está de momento.
Al guardar y cerrar, Git empieza a ejecutar. En el paso del squash se detiene y abre el editor con los dos mensajes:
# This is a combination of 2 commits. # This is the 1st commit message: Empezar exportacion # This is the commit message #2: mas cosas del csv # Please enter the commit message for your changes.
Bruno borra todo eso y escribe un mensaje decente:
Añadir la exportación de tareas a CSV Genera un fichero CSV con el título, el estado y la fecha de creación de cada tarea y lo descarga desde el navegador sin llamadas al servidor. Las comillas y los puntos y coma del título se escapan según RFC 4180.
Guarda, cierra, y el rebase continúa. Los tres fixup no preguntan nada. Al final:
Falta pulir el mensaje del segundo, pero eso lo vemos en el apartado 8. Antes, la comprobación que prometimos:
Idéntico al de antes del rebase. El contenido de la rama no ha cambiado; solo su historia. Esa es la garantía que convierte esta operación en algo razonable y no en una temeridad.
gitGraph commit id: "e91d4a8" branch antes commit id: "b1c4f80" commit id: "3d6e9a1" commit id: "c8f2b47" commit id: "2a7f4c1" commit id: "9e3b8d6" commit id: "5c1a9f2" checkout main branch despues commit id: "d72e6b8" commit id: "a3f9c14"
squash frente a fixup
squash frente a fixupLas dos unen un commit con el anterior. La única diferencia está en el mensaje, pero determina cuál usar en cada caso:
squash |
fixup |
|
|---|---|---|
| Contenido resultante | El de los dos commits combinado | El de los dos commits combinado |
| Mensaje resultante | Los dos mensajes juntos, editables | Solo el del commit anterior |
| ¿Abre el editor? | Sí | No |
| Cuándo usarlo | Los dos commits aportan información al mensaje | El segundo era un arreglo, una errata, un wip |
| Variante | — | fixup -C conserva el mensaje del segundo |
En la práctica: si el mensaje del commit que absorbes vale algo, squash; si es basura (arreglo, wip, ahora sí, punto y coma), fixup. Y como un squash de cinco commits te obliga a limpiar a mano cinco mensajes en el editor, en cuanto tengas tres o más suele salir a cuenta usar fixup en todos y escribir el mensaje una sola vez con reword.
- Reordenar y eliminar commits
Ya has visto reordenar: en la sesión de Bruno, mover 3d6e9a1 de la segunda posición a la última bastó para agrupar el trabajo por temas.
Eliminar es igual de directo: pon drop delante de la línea, o bórrala entera. Las dos formas son equivalentes; drop es preferible porque deja constancia visible de la intención y evita el borrado accidental de una línea:
pick b1c4f80 Empezar exportacion drop 4c8f1a5 Anadir console.log para depurar pick 3d6e9a1 Anadir boton de exportar
Dos advertencias que valen para las dos operaciones:
- Reordenar y eliminar pueden provocar conflictos. Si el commit
c8f2b47modificaba una línea queb1c4f80acababa de crear y los separas, el segundo ya no encuentra el contexto que esperaba. Se resuelve como cualquier conflicto de rebase (lección 05-01): resolver,git add,git rebase --continue. Recuerda que las carasours/theirssiguen invertidas. - Eliminar un commit del medio deja la rama con menos cambios, no con menos código necesariamente. Si
dropeas el commit que creaba una función y dejas el que la llama, la rama compila mal.--exec(apartado 10) sirve precisamente para detectarlo.
- Dividir un commit en dos con
edit
editEs la operación estrella del rebase interactivo y la que más impresiona la primera vez. Supongamos que Bruno mira d72e6b8 y decide que mezcla dos cosas: la función que genera el CSV y la que dispara la descarga. Quiere partirlo.
Paso 1: marcar el commit con edit.
Git aplica el commit y se detiene:
Stopped at d72e6b8... Añadir la exportación de tareas a CSV You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue
Paso 2: deshacer el commit pero conservar sus cambios.
Esto es lo que hace la magia: git reset sin opciones (equivale a --mixed) mueve HEAD un commit atrás y deja todos los cambios en el directorio de trabajo, sin preparar. El commit ha desaparecido; su contenido sigue en el fichero. Es la única aparición de reset en este módulo; sus tres modos y su uso como herramienta de deshacer se estudian a fondo en la lección 09-02.
Paso 3: preparar y confirmar por partes. Aquí entra git add -p, que aprendiste en la lección 02-04: preparar solo unos trozos del fichero.
Bruno acepta con y los trozos de la función tareasACSV y rechaza con n los de descargarFichero. Después:
[detached HEAD 6e1d9f3] Añadir la conversión de tareas a formato CSV 1 file changed, 14 insertions(+)
Y el resto:
[detached HEAD b8c2a70] Descargar el CSV generado desde el navegador 1 file changed, 9 insertions(+)
Paso 4: continuar.
f04b7e2 Anadir boton de exportar b8c2a70 Descargar el CSV generado desde el navegador 6e1d9f3 Añadir la conversión de tareas a formato CSV
Un commit se ha convertido en dos, sin perder una línea. Fíjate en que el commit posterior (a3f9c14 → f04b7e2) también ha cambiado de hash: al insertar commits antes que él, su padre es otro, y ya sabes lo que eso implica.
Truco: si al hacer
git reset HEAD^te das cuenta de que el reparto no es limpio (los cambios están entremezclados en la misma función), puedes editar los ficheros a mano antes de cadacommit. No estás obligado a que cada mitad sea un subconjunto exacto de la original; solo a que el resultado final sea el mismo. Verifícalo con el truco delrev-parse HEAD^{tree}.
- Cambiar solo un mensaje con
reword
rewordAl commit del botón le falta la tilde y la mayúscula. Para cambiar solo el mensaje, sin tocar nada más:
pick 6e1d9f3 Añadir la conversión de tareas a formato CSV pick b8c2a70 Descargar el CSV generado desde el navegador reword f04b7e2 Anadir boton de exportar
Git aplica los dos primeros, y en el tercero abre el editor con el mensaje actual. Bruno lo cambia por:
Guarda, cierra, y listo:
9c7e5a1 Añadir el botón de exportar a la barra de acciones b8c2a70 Descargar el CSV generado desde el navegador 6e1d9f3 Añadir la conversión de tareas a formato CSV
Tres commits, tres mensajes que explican qué hace cada uno, y una rama lista para publicar y para que la revisen.
Nota para el caso más frecuente: si el commit que quieres reescribir es el último, no hace falta rebase interactivo. git commit --amend (lección 02-04) hace lo mismo con menos ceremonia. reword es para los que están más atrás.
Qué es un buen mensaje de commit —el imperativo, la línea de asunto, el cuerpo, los pies— es el tema de la lección 08-01. Aquí solo nos ocupa el mecanismo de cambiarlo.
--autosquash: commit --fixup y commit --squash
--autosquash: commit --fixup y commit --squashHay un flujo mucho más cómodo que ir marcando fixup a mano, y consiste en decidirlo en el momento de hacer el commit, cuando todavía tienes fresco a qué commit corrige.
Imagina que Bruno está trabajando y descubre una errata en el commit 6e1d9f3, que hizo hace dos días. En lugar de crear un commit llamado arreglo:
Git ha creado un commit normal, pero con un mensaje especial: fixup! seguido del asunto del commit al que corrige. Ese prefijo es una marca que Git sabe interpretar.
2b8f4d1 fixup! Añadir la conversión de tareas a formato CSV 9c7e5a1 Añadir el botón de exportar a la barra de acciones b8c2a70 Descargar el CSV generado desde el navegador 6e1d9f3 Añadir la conversión de tareas a formato CSV
Y ahora, la parte bonita:
La lista que aparece ya viene ordenada y con las órdenes puestas:
pick 6e1d9f3 Añadir la conversión de tareas a formato CSV fixup 2b8f4d1 fixup! Añadir la conversión de tareas a formato CSV pick b8c2a70 Descargar el CSV generado desde el navegador pick 9c7e5a1 Añadir el botón de exportar a la barra de acciones
Bruno solo tiene que guardar y cerrar sin tocar nada. Git ha movido el commit de corrección justo detrás de su destino y lo ha marcado como fixup.
| Comando | Prefijo del mensaje | En el rebase se convierte en | Efecto sobre el mensaje |
|---|---|---|---|
git commit --fixup <sha> |
fixup! |
fixup |
Se descarta el del arreglo |
git commit --squash <sha> |
squash! |
squash |
Se abre el editor para combinarlos |
git commit --fixup=reword:<sha> |
amend! |
fixup -C |
Solo cambia el mensaje del original |
Para no tener que acordarse de --autosquash cada vez:
Y un aviso: --autosquash empareja los commits por el texto del asunto, no por el hash. Si dos commits de la rama tienen exactamente el mismo asunto, el emparejamiento puede ser ambiguo. En la práctica casi nunca pasa, y siempre puedes revisar la lista antes de guardar (que es justo lo que el editor te está pidiendo).
Este flujo —trabajar, corregir con --fixup, y aplanar todo con un rebase -i --autosquash justo antes de publicar— es probablemente la forma más productiva de usar Git a diario. Te libera de pensar en el historial mientras programas, y te deja limpiarlo de golpe al final.
--exec: validar cada commit
--exec: validar cada commitUn historial limpio que no compila no vale de nada. Cuando reordenas, unes o divides commits, es fácil que alguno intermedio quede roto: el que llama a una función que aún no existe, el que importa un módulo que llega dos commits después.
--exec ejecuta un comando después de cada commit reaplicado y detiene el rebase si el comando devuelve un código de error distinto de cero:
Git inserta automáticamente una línea exec tras cada pick:
pick 6e1d9f3 Añadir la conversión de tareas a formato CSV exec npm test pick b8c2a70 Descargar el CSV generado desde el navegador exec npm test pick 9c7e5a1 Añadir el botón de exportar a la barra de acciones exec npm test
Si el segundo npm test falla:
Executing: npm test ... warning: execution failed: npm test You can fix the problem, and then run git rebase --continue
El rebase se queda parado en ese punto, con el repositorio exactamente en el estado de ese commit. Puedes arreglar lo que sea, hacer git commit --amend, y continuar.
También puedes escribir las líneas exec a mano donde te interese, en lugar de en todos los commits. Y no tiene por qué ser una suite de tests; cualquier comprobación rápida vale:
pick 6e1d9f3 Añadir la conversión de tareas a formato CSV exec node --check app.js pick b8c2a70 Descargar el CSV generado desde el navegador exec node --check app.js exec grep -rn "console.log" app.js && exit 1 || exit 0
La última línea es un pequeño guardián: falla si queda algún console.log olvidado. Automatizar este tipo de comprobaciones de forma permanente, para que se ejecuten solas en cada commit, es el tema de los hooks (lección 06-01).
break, label, reset y merge
break, label, reset y mergeTres órdenes menos frecuentes, pero conviene reconocerlas.
break detiene el rebase en ese punto sin hacer nada más. Es una pausa deliberada:
pick 6e1d9f3 Añadir la conversión de tareas a formato CSV break pick b8c2a70 Descargar el CSV generado desde el navegador
Útil para mirar el estado del proyecto en mitad de la secuencia, ejecutar algo a mano o simplemente respirar. Se sigue con git rebase --continue.
label, reset y merge solo aparecen cuando usas git rebase -i --rebase-merges, que reconstruye un historial conservando sus commits de fusión en lugar de aplanarlo. Git genera entonces una lista con este aspecto:
label onto reset onto pick 6e1d9f3 Añadir la conversión de tareas a formato CSV label exportacion reset onto pick 9c7e5a1 Añadir el botón de exportar a la barra de acciones label boton reset onto merge -C 4a7d2f8 exportacion # Fusionar la exportación merge -C 8e3b6c1 boton # Fusionar el botón
Se lee así: label X guarda un marcador en el punto actual, reset X vuelve a ese marcador, y merge -C <sha> X crea un commit de fusión con la rama marcada como X, reutilizando el mensaje del commit <sha>. Es un lenguaje pequeño para describir un grafo. No lo necesitarás casi nunca; basta con no asustarse si aparece.
- Qué hacer si te pierdes a mitad
Un rebase interactivo largo puede convertirse en un laberinto: cuatro conflictos, un edit a medias y la sensación de no saber en qué punto estás. Salidas, de menos a más drástica:
1. Preguntar dónde estás. git status durante un rebase interactivo es extraordinariamente informativo:
interactive rebase in progress; onto e91d4a8 Last commands done (3 commands done): pick 6e1d9f3 Añadir la conversión de tareas a formato CSV fixup 2b8f4d1 fixup! Añadir la conversión de tareas a formato CSV Next commands to do (2 remaining commands): pick b8c2a70 Descargar el CSV generado desde el navegador pick 9c7e5a1 Añadir el botón de exportar a la barra de acciones You are currently rebasing branch 'funcionalidad/exportacion-csv' on 'e91d4a8'.
Te dice lo hecho y lo que falta. La lista viva está además en .git/rebase-merge/git-rebase-todo, y git rebase --edit-todo te la abre para cambiar lo que queda por hacer sin abortar. Es la salida elegante cuando te das cuenta a mitad de que planificaste mal:
2. Ver qué está fallando ahora.
Muestra el commit que se está intentando aplicar, con su diff completo. Muy útil cuando el conflicto no tiene sentido a primera vista.
3. Abortar.
Deshace todo el rebase y devuelve la rama a su estado original, con los hashes originales. Es seguro, es instantáneo y no deja rastro. Ante la duda, aborta: replanificar la lista con la cabeza fría cuesta dos minutos; desenredar un rebase mal llevado, mucho más.
4. Si ya has terminado y el resultado es un desastre. Aquí es donde entra la red de seguridad: los commits originales siguen en la base de datos de objetos y el reflog guarda dónde estaba tu rama antes de empezar. El procedimiento completo para volver atrás es el tema de la lección 09-04. Y el reflejo barato de siempre, que evita tener que usarlo:
git branch copia-antes-del-rebase
git rebase -i main
# ¿Mal? -> git reset --hard copia-antes-del-rebaseErrores Comunes y Consejos
Error 1: leer la lista como si fuera git log. En el editor, el commit más antiguo está arriba. Marcar squash en la línea equivocada por esta inversión es el error número uno.
Error 2: poner squash o fixup en la primera línea. No hay commit anterior con el que unirse y el rebase falla antes de empezar. La primera línea siempre es pick, reword, edit o drop.
Error 3: borrar una línea sin querer. Ese commit desaparece. Usa drop explícito cuando quieras eliminar, y si borras una línea por accidente, cierra el editor sin guardar: Git aborta el rebase.
Error 4: rebasar interactivamente una rama ya publicada y compartida. La regla de oro no tiene excepciones cómodas. Si otras personas trabajan sobre esa rama, no la reescribas.
Error 5: hacer git commit en lugar de git rebase --continue tras resolver un conflicto. Salvo en el flujo de edit (donde sí confirmas tú), quien crea el commit es el rebase.
Error 6: olvidar que el rebase corre los hooks. Si tienes un pre-commit lento, un rebase de veinte commits lo ejecuta veinte veces. --no-verify lo desactiva, con la responsabilidad que eso implica.
Error 7: aplanar todo en un único commit gigante "para que quede limpio". Un commit de 900 líneas que dice "Añadir la exportación" es tan inútil como seis commits llamados wip. El objetivo es que cada commit sea un cambio completo y comprensible, no que haya uno solo.
Consejo 1: comprueba el árbol antes y después. git rev-parse HEAD^{tree} debe coincidir si tu intención era solo reorganizar. Si no coincide, has perdido o duplicado algo.
Consejo 2: adopta --fixup desde hoy. En cuanto tengas el reflejo de corregir con git commit --fixup <sha> en lugar de con un commit llamado arreglo, la limpieza final se vuelve trivial. Activa rebase.autoSquash true.
Consejo 3: haz rebases interactivos pequeños y frecuentes. Limpiar cinco commits es cómodo; limpiar cuarenta, no. Un rebase -i al final de cada jornada de trabajo en una rama es una costumbre excelente.
Consejo 4: usa --exec cuando reordenes o dividas. Es la única forma barata de saber que ningún commit intermedio ha quedado roto.
Consejo 5: revisa la lista antes de guardar, siempre. Ese editor abierto no es un trámite: es tu última oportunidad de ver el plan completo antes de que se ejecute.
Cuándo conviene limpiar y cuánto —si el equipo exige historial lineal, si se aplasta cada rama en un commit al integrarla, si se prohíbe reescribir— no es una decisión técnica sino de política de equipo, y se trata en la lección 08-02: Manteniendo un Historial Limpio.
Ejercicios
Ejercicio 1: la limpieza completa
Crea un repositorio con una rama main (un commit) y una rama funcionalidad/informe con estos cinco commits, en este orden:
Empezar el informe(creainforme.jscon una función)Anadir estilos del informe(creainforme.css)mas cosas(amplíainforme.js)wip(tocainforme.js)arreglo(tocainforme.js)
Con un solo git rebase -i, deja la rama con dos commits: uno con todo lo de informe.js y un mensaje decente, y otro con los estilos. Demuestra con git rev-parse HEAD^{tree} que el contenido final no ha cambiado.
Ejercicio 2: dividir un commit
Partiendo del resultado del ejercicio 1, divide el commit de informe.js en dos: uno con la generación de datos y otro con el pintado. Usa edit, git reset HEAD^ y git add -p.
Ejercicio 3: --fixup y --exec
En una rama nueva con tres commits:
- Introduce a propósito un error de sintaxis en el segundo commit.
- Sigue trabajando y haz un tercer commit normal.
- Corrige el error con
git commit --fixup <sha del segundo>. - Aplícalo con
git rebase -i --autosquash. - Vuelve a lanzar un rebase con
--exec "node --check fichero.js"y comprueba que ahora los tres commits pasan la validación.
Soluciones
Solución 1:
mkdir /tmp/practica-rebase-i && cd /tmp/practica-rebase-i
git init -b main
echo "# Proyecto" > README.md && git add . && git commit -m "Base"
git switch -c funcionalidad/informe
echo "function generarInforme() {}" > informe.js && git add . && git commit -m "Empezar el informe"
echo ".informe { margin: 1rem; }" > informe.css && git add . && git commit -m "Anadir estilos del informe"
echo "function datosInforme() {}" >> informe.js && git commit -am "mas cosas"
echo "// pendiente" >> informe.js && git commit -am "wip"
echo "function pintarInforme() {}" >> informe.js && git commit -am "arreglo"
git rev-parse HEAD^{tree}Lista editada (los commits de informe.js juntos arriba, los estilos al final):
pick 4a1c8e3 Empezar el informe fixup 9d2f7b5 mas cosas fixup 1e6a4c8 wip fixup 7b3d9f2 arreglo pick 5c8e1a4 Anadir estilos del informe
Después, un segundo pase para el mensaje (o marca reword en la primera línea desde el principio):
Mensaje nuevo: Añadir la generación y el pintado del informe.
El árbol coincide con el anotado: solo ha cambiado la historia.
Solución 2:
(El fichero era nuevo en ese commit, así que aparece sin seguimiento. Si ya existiera, saldría como M.)
# Preparar solo la parte de generación de datos
git add -p informe.js # aceptar los trozos de generarInforme/datosInforme
git commit -m "Añadir la generación de los datos del informe"
git add informe.js
git commit -m "Añadir el pintado del informe"
git rebase --continue
git log --oneline main..HEAD9e7c3b1 Anadir estilos del informe 2f8a5d6 Añadir el pintado del informe b41e9c7 Añadir la generación de los datos del informe
Si
git add -pno ofrece trozos separables porque el fichero es nuevo, usagit add -N informe.jsprimero: registra el fichero en el índice como vacío y permite trocear su contenido.
Solución 3:
mkdir /tmp/practica-autosquash && cd /tmp/practica-autosquash
git init -b main
echo "const a = 1;" > app.js && git add . && git commit -m "Base"
git switch -c pruebas
echo "const b = 2;" >> app.js && git commit -am "Añadir b"
echo "const c = ;" >> app.js && git commit -am "Añadir c" # error de sintaxis
echo "const d = 4;" >> app.js && git commit -am "Añadir d"
git log --oneline main..HEAD# 3. Corregir el error apuntando al commit culpable
sed -i 's/const c = ;/const c = 3;/' app.js
git add app.js
git commit --fixup 3f9b2d6La lista viene ya preparada; basta con guardar:
Executing: node --check app.js Executing: node --check app.js Executing: node --check app.js Successfully rebased and updated refs/heads/pruebas.
Si lo hubieras ejecutado antes del --autosquash, el segundo exec habría fallado y el rebase se habría detenido en el commit roto.
Conclusión
El rebase interactivo es el taller de reparación del historial. Lo aprendido:
git rebase -i <base>abre una lista de tareas con los commits que se van a reaplicar, del más antiguo al más reciente, y ejecuta lo que escribas en ella de arriba abajo.- Las cuatro órdenes que usarás siempre son
pick(dejar),reword(cambiar el mensaje),edit(parar para modificar o dividir) ysquash/fixup(unir con el anterior, conservando o descartando el mensaje).dropelimina yexecvalida. - Reordenar líneas reordena commits y permite agrupar por temas lo que se hizo desordenado; borrar una línea elimina el commit.
- Dividir un commit es
edit+git reset HEAD^+ variosgit add -pygit commit+git rebase --continue. Es la operación más potente del conjunto. git commit --fixup <sha>conrebase -i --autosquashconvierte la limpieza en un trámite: marcas las correcciones sobre la marcha y Git las coloca solo. Actívalo conrebase.autoSquash true.--execejecuta una comprobación tras cada commit y detiene el rebase si falla: la única forma barata de garantizar que ningún commit intermedio queda roto.- Si te pierdes:
git statusdice dónde estás,git rebase --edit-todoreplanifica lo que queda,--show-current-patchenseña qué falla y--abortlo deshace todo sin rastro. git rev-parse HEAD^{tree}antes y después es la mejor verificación de que has reorganizado la historia sin alterar el contenido.- Y sobre todo: la regla de oro. Esto se hace antes de publicar.
Lo que viene
Rebase y rebase interactivo trabajan siempre con el conjunto de commits de una rama. Pero a veces lo que necesitas es mucho más quirúrgico: llevarte un commit concreto de un sitio a otro, dejando el resto donde está.
Ana lo va a necesitar mañana. Ha corregido en main un fallo que hace que el foco se pierda al borrar una tarea, y resulta que la versión 1.x que sigue desplegada en el cliente tiene el mismo fallo y vive en una rama de mantenimiento aparte. No quiere fusionar main entera ahí: quiere ese commit y nada más.
Para eso está git cherry-pick, y lo vemos en la lección 05-03: Cherry-Picking de Confirmaciones.
Dominando Git: De Principiante a Avanzado
Módulo 1: Introducción a Git
- ¿Qué es Git?
- Instalando Git
- Terminología Básica de Git
- El Modelo de Datos de Git
- Configurando Git
- Configuración Inicial
Módulo 2: Operaciones Básicas de Git
- Creando un Repositorio
- Clonando un Repositorio
- Flujo de Trabajo Básico de Git
- Preparando y Confirmando Cambios
- Inspeccionando Cambios con git diff
- Visualizando el Historial de Confirmaciones
Módulo 3: Ramas y Fusión
- Entendiendo las Ramas
- Creando y Cambiando Ramas
- Fusionando Ramas
- Estrategias de Fusión
- Resolviendo Conflictos de Fusión
- Gestión de Ramas
Módulo 4: Trabajando con Repositorios Remotos
- Entendiendo los Repositorios Remotos
- Agregando un Repositorio Remoto
- Autenticación con Repositorios Remotos
- Obteniendo y Extrayendo Cambios
- Enviando Cambios
- Rastreando Ramas
Módulo 5: Operaciones Avanzadas de Git
- Rebase
- Rebase Interactivo
- Cherry-Picking de Confirmaciones
- Guardando Cambios Temporales
- Etiquetando Confirmaciones
- Revirtiendo Confirmaciones
Módulo 6: Herramientas y Técnicas de Git
- Usando Git Hooks
- Git Bisect
- Git Blame
- Git Log y Alias
- Submódulos de Git
- Múltiples Copias de Trabajo con git worktree
Módulo 7: Estrategias de Colaboración y Flujo de Trabajo
- Forks y Pull Requests
- Revisiones de Código con Git
- Flujo de Trabajo Git Flow
- GitHub Flow
- Trunk Based Development
- Integración Continua con Git
Módulo 8: Mejores Prácticas y Consejos de Git
- Escribiendo Buenos Mensajes de Confirmación
- Manteniendo un Historial Limpio
- Ignorando Archivos con .gitignore
- Atributos de Fichero con .gitattributes
- Mejores Prácticas de Seguridad
- Consejos de Rendimiento
Módulo 9: Solución de Problemas y Depuración
- Problemas Comunes de Git
- Deshaciendo Cambios
- Resolviendo Divergencias con el Remoto
- Recuperando Confirmaciones Perdidas
- Tratando con Repositorios Corruptos
- Técnicas Avanzadas de Depuración
