Cerrábamos el módulo 4 con una advertencia: a partir de ahora el historial es compartido. Ana, Bruno y Carla trabajan sobre el mismo repositorio de git.ejemplo.es, y cada push deja commits que las otras dos personas pueden descargarse en cualquier momento. Esa es exactamente la circunstancia que convierte a git rebase en la herramienta más útil y más temida de Git.

git rebase sirve para una cosa muy concreta: coger una serie de commits y reaplicarlos sobre otra base. Sirve para que una rama que nació hace tres días vuelva a estar "encima" de main sin ensuciar el historial con fusiones; sirve para reordenar y limpiar tu trabajo antes de enseñárselo a nadie; y sirve para mover una rama que colgaba del sitio equivocado. Lo que la gente teme de ella es su otra cara: no mueve commits, los sustituye por otros nuevos. Y sustituir commits que ya están publicados es, como veremos, una forma bastante eficaz de arruinarle la tarde a tus compañeros.

Esta lección explica el mecanismo con detalle suficiente para que no haya magia: qué objetos se crean, qué hashes cambian, qué se conserva, cómo se resuelven los conflictos que aparecen a mitad y —sobre todo— cuándo conviene usar rebase y cuándo conviene usar merge.

Contenido

  1. La regla de oro del módulo
  2. Qué hace git rebase realmente
  3. Un rebase paso a paso en gestor-tareas
  4. Qué se conserva y qué cambia en los commits reaplicados
  5. Rebase frente a merge
  6. git rebase --onto: mover un tramo concreto
  7. Conflictos durante un rebase: --continue, --skip, --abort
  8. La inversión de ours y theirs
  9. Romper la regla de oro: qué pasa exactamente
  10. git pull --rebase
  11. rebase.autoStash y otras opciones cómodas
  12. La red de seguridad

  1. La regla de oro del módulo

Antes de escribir un solo comando, la regla que gobierna todo el módulo 5:

No reescribas historial que ya has publicado y que otras personas pueden tener descargado.

Todo lo que veremos en las lecciones 05-01, 05-02 y 05-03 crea commits nuevos que sustituyen a otros. Mientras esos commits vivan solo en tu disco, reescribirlos es gratis: nadie más los ha visto. En cuanto los envías a git.ejemplo.es, dejan de ser tuyos: pasan a formar parte de una realidad compartida, y cambiarlos obliga a los demás a rehacer su copia.

La regla tiene una excepción muy acotada y muy conocida: tu propia rama de trabajo, publicada solo para tener copia o para una revisión, que nadie más está usando como base. Es un caso legítimo y habitual, pero exige avisar y exige --force-with-lease (lección 04-05). Volveremos a la regla en cada apartado que reescriba commits, hasta que resulte cargante. Ese es el objetivo.

  1. Qué hace git rebase realmente

La frase que suele repetirse es "rebase mueve tus commits encima de otra rama". Es cómoda y es falsa, y la diferencia importa.

Recuerda el modelo de datos de la lección 01-04: un commit es un objeto inmutable cuyo hash SHA-1 se calcula a partir de todo su contenido, y ese contenido incluye el hash de su padre. Por tanto:

  • Si cambias el padre de un commit, cambia su contenido.
  • Si cambia su contenido, cambia su hash.
  • Si cambia su hash, ya no es el mismo commit: es otro objeto distinto.

Un commit no se puede "mover" igual que no se puede cambiar el pasado. Lo que hace git rebase es:

  1. Calcular la lista de commits que están en tu rama y no en la nueva base.
  2. Guardar el cambio (el diff) que introduce cada uno.
  3. Situarse en la nueva base.
  4. Aplicar esos cambios uno a uno, creando un commit nuevo por cada uno.
  5. Mover la referencia de rama a la punta de la cadena nueva.

Los commits antiguos siguen existiendo en la base de datos de objetos —los objetos de Git no se borran al instante—, pero se quedan sin ninguna rama que los alcance. Son, a efectos prácticos, historia muerta.

gitGraph
   commit id: "c5d9b1e"
   commit id: "b3f7c21"
   branch funcionalidad/etiquetas-color
   commit id: "7c2e5f9"
   commit id: "a4b1d83"
   checkout main
   commit id: "e91d4a8"

Ese es el punto de partida: Carla creó su rama cuando main estaba en b3f7c21, y mientras trabajaba Ana publicó e91d4a8. Después del rebase:

gitGraph
   commit id: "c5d9b1e"
   commit id: "b3f7c21"
   commit id: "e91d4a8"
   branch funcionalidad/etiquetas-color
   commit id: "d5e8f31"
   commit id: "92c7a06"

Fíjate bien en los identificadores: 7c2e5f9 y a4b1d83 han desaparecido del dibujo, y en su lugar hay d5e8f31 y 92c7a06. El contenido de los cambios es el mismo, los mensajes son los mismos, el autor es el mismo. Los commits, no.

  1. Un rebase paso a paso en gestor-tareas

Carla lleva dos días con funcionalidad/etiquetas-color: quiere que cada tarea pueda llevar un color. Mientras tanto, Ana ha publicado un cambio en main. Carla quiere ponerse al día sin crear un commit de fusión.

# 1. Punto de partida: traer lo último del servidor
git switch funcionalidad/etiquetas-color
git fetch origin
git log --oneline --graph --all -6
* e91d4a8 (origin/main, main) Extraer la creación del elemento de tarea a su función
| * a4b1d83 (HEAD -> funcionalidad/etiquetas-color) Pintar la etiqueta de color en el listado
| * 7c2e5f9 Anadir el campo color al modelo de tarea
|/
* b3f7c21 Guardar las tareas en localStorage
* c5d9b1e Documentar la instalacion en el README

La bifurcación está clara: dos commits por un lado, uno por el otro, y b3f7c21 como ancestro común. Comprobemos qué commits se van a reaplicar antes de tocar nada:

# 2. Qué commits va a reaplicar el rebase (los que están en mi rama y no en main)
git log --oneline main..HEAD
a4b1d83 Pintar la etiqueta de color en el listado
7c2e5f9 Anadir el campo color al modelo de tarea

Esa es exactamente la lista de la que hablábamos: el rango main..HEAD que aprendiste en la lección 02-06. Ahora sí:

# 3. El rebase
git rebase main
Successfully rebased and updated refs/heads/funcionalidad/etiquetas-color.
# 4. El resultado
git log --oneline --graph --all -6
* 92c7a06 (HEAD -> funcionalidad/etiquetas-color) Pintar la etiqueta de color en el listado
* d5e8f31 Anadir el campo color al modelo de tarea
* e91d4a8 (origin/main, main) Extraer la creación del elemento de tarea a su función
* b3f7c21 Guardar las tareas en localStorage
* c5d9b1e Documentar la instalacion en el README

Ya no hay bifurcación: una sola línea recta. Y los hashes de los dos commits de Carla han cambiado. Vale la pena verificar con git show que el cambio es idéntico:

# 5. El commit viejo sigue existiendo, aunque ninguna rama lo alcance
git show --stat 7c2e5f9 | head -6
git show --stat d5e8f31 | head -6
commit 7c2e5f9...
Author: Carla Vidal <[email protected]>
Date:   Mon Jul 27 09:14:22 2026 +0200

    Anadir el campo color al modelo de tarea
commit d5e8f31...
Author: Carla Vidal <[email protected]>
Date:   Mon Jul 27 09:14:22 2026 +0200

    Anadir el campo color al modelo de tarea

Mismo autor, misma fecha de autoría, mismo mensaje, mismo diff. Distinto objeto.

Un detalle importante para el final: como la rama de Carla no estaba publicada, aquí no hay nada que negociar. Si lo estuviera, el siguiente git push sería rechazado por non-fast-forward (lección 04-05) y habría que usar --force-with-lease.

  1. Qué se conserva y qué cambia en los commits reaplicados

Esta tabla resuelve la mayoría de las dudas de quien empieza:

Elemento del commit ¿Se conserva tras el rebase?
Mensaje Sí (salvo que lo cambies con -i)
Autor y correo (author)
Fecha de autoría (author date)
Confirmador (committer) No: pasas a serlo tú
Fecha de confirmación (committer date) No: se pone la de ahora
Contenido del cambio (el diff) Sí, si no hay conflicto
Árbol resultante (tree) Depende de la base: casi siempre cambia
Padre (parent) No: ese es el objetivo
Hash No

Dos consecuencias prácticas:

  • git log muestra por defecto la fecha de autoría, así que después de un rebase el historial sigue enseñando las fechas originales. Si quieres ver la de confirmación: git log --pretty=fuller.
  • Si en tu equipo alguien mira "quién confirmó qué", el rebase te pone a ti como committer de commits ajenos. No es un problema, pero conviene saberlo.

  1. Rebase frente a merge

Las dos integran el trabajo de una rama con el de otra. La diferencia no es técnica sino narrativa: qué historia quieres que cuente el repositorio.

Aspecto git merge git rebase
Qué hace Crea un commit nuevo con dos padres Crea commits nuevos, uno por cada original
Historial resultante Bifurcado, con nudos de fusión Lineal
Commits originales Se conservan intactos Se sustituyen (hashes nuevos)
Trazabilidad Refleja lo que pasó de verdad: quién trabajó en paralelo y cuándo se juntó Refleja una versión idealizada: parece que todo se hizo en orden
Momento del conflicto Uno solo, al fusionar; se resuelve una vez Uno por cada commit que choque; puede repetirse
Legibilidad de git log Peor con muchas ramas cortas Mejor: se lee de arriba abajo
git bisect (lección 06-02) Funciona, pero con nudos Más limpio
Seguridad sobre trabajo publicado Total: no reescribe nada Peligrosa: reescribe
Reversibilidad git revert -m 1 (lección 05-06) No aplica: hay que revertir commit a commit

Y el criterio, resumido en tres reglas que sí puedes memorizar:

  1. Rebase para lo que aún es tuyo. Tu rama local, antes de publicarla o antes de pedir su integración: ponla al día sobre main con rebase.
  2. Merge para juntar historias que ya son públicas. Integrar una rama de funcionalidad terminada en main es un hecho que merece quedar registrado; ahí un merge (a menudo --no-ff, lección 03-03) es la opción honesta.
  3. Ante la duda, merge. Es la opción que nunca destruye nada.

Hay equipos que llevan esto al extremo en un sentido ("historial lineal siempre") y otros en el contrario ("nunca reescribir nada"). Las dos posturas son defendibles y las veremos con nombre propio en el módulo 7 (flujos de trabajo) y en la lección 08-02, cuando hablemos de la política de historial limpio. Aquí nos ocupa el mecanismo.

  1. git rebase --onto: mover un tramo concreto

git rebase <base> cubre el 90 % de los casos, pero tiene un supuesto implícito: que quieres reaplicar todo lo que hay entre el ancestro común y tu rama. A veces no es eso lo que quieres.

Le pasó a Carla. Empezó funcionalidad/orden-por-fecha sin darse cuenta de que estaba situada en funcionalidad/etiquetas-color en lugar de en main. Ahora su rama nueva arrastra los dos commits de las etiquetas de color, que no tienen nada que ver.

gitGraph
   commit id: "e91d4a8"
   branch funcionalidad/etiquetas-color
   commit id: "d5e8f31"
   commit id: "92c7a06"
   branch funcionalidad/orden-por-fecha
   commit id: "3f9a2c4"
   commit id: "6b1e7d5"

Quiere que funcionalidad/orden-por-fecha cuelgue directamente de main (que está en e91d4a8) y contenga solo 3f9a2c4 y 6b1e7d5. Para eso está la forma de tres argumentos:

git rebase --onto <nueva-base> <desde> <hasta>

La forma de leerla en voz alta es esta:

  • <hasta> — la rama que quiero mover (si la omites, la rama actual).
  • <desde> — el punto a partir del cual empiezan los commits que quiero llevarme. Este commit no se incluye; funciona como el límite exclusivo de un rango desde..hasta.
  • <nueva-base> — dónde quiero que queden pegados.

Aplicado al caso de Carla:

# 1. Ver qué commits me llevo (misma semántica de rango)
git log --oneline funcionalidad/etiquetas-color..funcionalidad/orden-por-fecha
6b1e7d5 Ordenar las tareas por fecha de creación
3f9a2c4 Guardar la fecha de creación de cada tarea
# 2. El rebase --onto
git rebase --onto main funcionalidad/etiquetas-color funcionalidad/orden-por-fecha
Successfully rebased and updated refs/heads/funcionalidad/orden-por-fecha.
gitGraph
   commit id: "e91d4a8"
   branch funcionalidad/etiquetas-color
   commit id: "d5e8f31"
   commit id: "92c7a06"
   checkout main
   branch funcionalidad/orden-por-fecha
   commit id: "0c4f8a3"
   commit id: "5d2b9e7"

Los dos commits de la fecha se han reaplicado sobre main con hashes nuevos, y la rama de las etiquetas de color se ha quedado donde estaba, intacta.

Otros usos habituales de --onto, para que reconozcas el patrón:

# Quitar los 3 primeros commits de una rama (empezar a contar 3 más allá)
git rebase --onto main main~3 mi-rama

# Llevar los commits de una rama a otra rama distinta
git rebase --onto correccion/urgente main funcionalidad/algo

# Quitar UN commit del medio: todo lo que hay tras él, encima de su padre
git rebase --onto 4e7f2a9 8b6d3c2 main

Ese último es un buen ejercicio mental: 8b6d3c2 es el commit que queremos eliminar, 4e7f2a9 es su padre, y estamos diciendo "coge todo lo que hay después de 8b6d3c2 y pégalo directamente sobre su padre". El rebase interactivo (lección 05-02) tiene una forma mucho más cómoda de hacer lo mismo, pero conviene entender que por debajo es esto.

  1. Conflictos durante un rebase

Un rebase aplica commits uno a uno, así que puede pararse una vez por cada commit. La mecánica de resolución —los marcadores <<<<<<<, git status, editar, git add— es exactamente la que aprendiste en la lección 03-05; no la repetimos. Lo que cambia es cómo se sale del atasco.

Supongamos que Carla rebasa y el segundo commit choca:

git rebase main
Auto-merging app.js
CONFLICT (content): Merge conflict in app.js
error: could not apply 92c7a06... Pintar la etiqueta de color en el listado
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the original branch: run "git rebase --abort".
Could not apply 92c7a06... Pintar la etiqueta de color en el listado
git status
interactive rebase in progress; onto e91d4a8
Last command done (2 commands done):
   pick d5e8f31 Anadir el campo color al modelo de tarea
   pick 92c7a06 Pintar la etiqueta de color en el listado
No commands remaining.
You are currently rebasing branch 'funcionalidad/etiquetas-color' on 'e91d4a8'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to checkout the original branch)

Unmerged paths:
  (use "git restore --staged <file>..." to unstage)
  (use "git add <file>..." to mark resolution)
	both modified:   app.js

Ese bloque de git status es tu panel de control: te dice en qué commit de la lista vas y qué queda. Las tres salidas:

Comando Qué hace Cuándo usarlo
git rebase --continue Confirma el commit con tu resolución y sigue con el siguiente El caso normal, tras resolver y hacer git add
git rebase --skip Descarta por completo el commit conflictivo y sigue Cuando ese cambio ya está en la base y reaplicarlo no aporta nada
git rebase --abort Deshace todo el rebase y devuelve la rama a su estado original Cuando te has liado o el conflicto es peor de lo esperado

Tres advertencias importantes:

  • --skip no es "sáltate el conflicto": es "tira este commit a la basura". Si el commit tenía cambios que no están en ninguna otra parte, los pierdes de la rama resultante. Úsalo solo cuando estés seguro de que su contenido ya está aplicado.
  • --abort es completamente seguro. Devuelve la rama exactamente donde estaba, con los commits originales y sus hashes originales. Cuando dudes, aborta y piensa con el repositorio en calma.
  • No hagas git commit a mano durante un rebase. Después de resolver y hacer git add, es git rebase --continue quien crea el commit. Si haces git commit, --continue te dirá que no queda nada que confirmar (y en versiones antiguas puede dejar la lista descuadrada).

Un detalle útil: si tras resolver el conflicto el resultado es idéntico a la base (es decir, tu commit ya no aporta nada), git rebase --continue lo detecta y te propone saltarlo. Es normal y no indica ningún problema.

  1. La inversión de ours y theirs

Aquí está la trampa que confunde a todo el mundo, incluidos veteranos.

En una fusión normal (lección 03-05), ours es tu rama —donde estás— y theirs es la rama que traes. Intuitivo.

En un rebase se invierte. Y no es un capricho: es la consecuencia lógica del mecanismo. Recuerda que un rebase primero se coloca en la nueva base y luego aplica tus commits encima, uno a uno, como si fueran parches ajenos. Por tanto, en cada paso:

Lado En un merge En un rebase
ours / --ours / etapa :2: Tu rama actual La nueva base (main, lo que ya estaba)
theirs / --theirs / etapa :3: La rama que fusionas Tu commit, el que se está reaplicando
HEAD durante el conflicto Tu rama La base más los commits ya reaplicados

Dicho de forma memorable: durante un rebase, "los suyos" eres tú.

La consecuencia práctica es directa. Si durante un rebase quieres quedarte con tu versión del fichero:

# MAL: esto se queda con la versión de main, no con la tuya
git checkout --ours app.js

# BIEN: durante un rebase, tu commit es "theirs"
git checkout --theirs app.js
git add app.js
git rebase --continue

Lo mismo vale para -X ours / -X theirs cuando se los pasas al rebase, y para los nombres de las etapas del índice (:2: y :3:) que viste en 03-05. Si alguna vez dudas, no adivines: mira el contenido antes de decidir.

git show :2:app.js | head -20    # "ours" = la base
git show :3:app.js | head -20    # "theirs" = tu commit

Un consejo que ahorra muchos disgustos: configura el estilo zdiff3 (lección 03-05) también aquí. Ver el ancestro común dentro del marcador hace la orientación mucho menos dependiente de recordar quién es quién:

git config --global merge.conflictStyle zdiff3

Y si el mismo conflicto se repite commit tras commit dentro del mismo rebase, existe git rerere (reuse recorded resolution), que memoriza cómo resolviste un conflicto y lo aplica solo la próxima vez que aparezca idéntico. Se mencionó en 03-05 y su configuración se sale de esta lección.

  1. Romper la regla de oro: qué pasa exactamente

Veámoslo con nombres y apellidos, porque el mecanismo del desastre explica la regla mejor que cualquier advertencia.

  1. Bruno publica funcionalidad/exportacion-csv con tres commits: 2a7f4c1, 9e3b8d6, 5c1a9f2.
  2. Carla hace git fetch y crea su rama a partir de ese trabajo. Ahora tiene esos tres commits en su disco.
  3. Bruno rebasa su rama sobre main porque main ha avanzado. Sus tres commits se convierten en d8b2e50, 71f6c34, ae95d18.
  4. Bruno hace git push --force-with-lease. El servidor ahora apunta a ae95d18.
  5. Carla hace git pull.

Lo que ve Carla:

 * branch            funcionalidad/exportacion-csv -> FETCH_HEAD
 + ae95d18...5c1a9f2 funcionalidad/exportacion-csv -> origin/funcionalidad/exportacion-csv  (forced update)

Y a partir de aquí, según su configuración de pull:

  • Con pull.ff only (la que configuramos en 01-06): el pull falla con un aviso de divergencia. Es el mejor de los casos: Carla se entera y puede preguntar.
  • Con merge: Git fusiona la cadena vieja con la nueva y aparecen los tres commits duplicados, cada uno dos veces con hashes distintos, más un commit de fusión, más un puñado de conflictos absurdos donde cada cambio choca consigo mismo.
  • Con rebase: Carla reaplica sus commits (que incluyen los tres viejos de Bruno) sobre la cadena nueva, y obtiene el mismo festival de duplicados.

Y si Carla resuelve mal y publica, los commits viejos vuelven al servidor y el rebase de Bruno queda deshecho. Es el ciclo clásico: alguien rebasa, alguien lo revierte sin querer, y el historial acaba con todo por duplicado.

Cómo se evita, en orden de eficacia:

  1. No reescribir ramas compartidas. Si más de una persona trabaja en ella, se integra con merge y punto.
  2. Si la rama es tuya y publicada solo como copia, rebasa cuando quieras, pero avisa antes de forzar y usa siempre --force-with-lease (nunca --force), que se niega si alguien ha publicado algo que tú no tienes.
  3. Si has sido la víctima, la salida es git reset --hard origin/<rama> para descartar tu copia vieja y volver a aplicar lo tuyo encima. La casuística completa, con los pasos para no perder trabajo, está en la lección 09-03.

  1. git pull --rebase

En la lección 04-04 dejamos pendiente esta opción. Ya podemos entenderla del todo.

git pull es fetch + integración. Con --rebase, la integración es un rebase de tus commits locales sobre lo que acabas de traer:

git pull --rebase origin main

Comparación de los tres modos, ahora completa:

Modo Si no has trabajado en local Si has trabajado y hay divergencia Historial
--ff-only Avanza la rama Falla y te avisa Intacto
--no-rebase (merge) Avanza la rama Crea un commit de fusión Con nudos
--rebase Avanza la rama Reaplica tus commits encima Lineal

Por qué gusta tanto: cuando dos personas tocan cosas distintas del mismo proyecto y sincronizan a menudo, pull con merge genera un reguero de commits "Merge branch 'main' of git.ejemplo.es..." que no cuentan absolutamente nada. pull --rebase los elimina.

Y por qué hay que usarlo con cabeza: reescribe tus commits locales. Eso es inofensivo si no los has publicado (que es el caso típico: acabas de hacerlos y aún no ha habido push), y es exactamente el escenario del apartado 9 si sí los has publicado.

Para dejarlo configurado:

# Que 'git pull' siempre rebase, en todos los repositorios
git config --global pull.rebase true

# Solo para una rama concreta
git config branch.main.rebase true

# Recomendable si activas lo anterior: no aplanar las fusiones que TÚ hiciste a propósito
git config --global pull.rebase merges

pull.rebase merges (equivalente a --rebase-merges) merece una nota: por defecto, un rebase descarta los commits de fusión de la cadena que reaplica y aplana todo en una línea. Si tenías fusiones intencionadas en tu trabajo local, merges las conserva reconstruyéndolas.

Si dejas pull.ff only como configuración global (nuestra recomendación de 01-06 y la más segura para aprender), siempre puedes pedir el rebase puntualmente con git pull --rebase. Lo explícito gana.

  1. rebase.autoStash y otras opciones cómodas

git rebase exige un directorio de trabajo limpio: si tienes cambios sin confirmar, se niega a empezar.

error: cannot rebase: You have unstaged changes.
error: Please commit or stash them.

La solución manual es git stash, rebasar y git stash pop (lección 05-04). La automática:

git config --global rebase.autoStash true

Con eso, Git aparta los cambios solo, rebasa y los devuelve al terminar. Si al devolverlos hay conflicto, te avisa y el stash se queda a salvo en la pila. Es una comodidad excelente, y su equivalente para pull es rebase.autoStash combinado con pull.rebase, o git pull --rebase --autostash.

Otras opciones que conviene conocer:

Opción / configuración Qué hace
git rebase -i Rebase interactivo: el tema completo de la lección 05-02
--rebase-merges Conserva los commits de fusión en lugar de aplanarlos
--keep-empty Conserva los commits que quedan vacíos tras reaplicarse
--no-verify No ejecuta los hooks (módulo 6) en cada commit reaplicado
-X ours / -X theirs Resolución automática de conflictos por un lado (¡recuerda la inversión!)
--exec "<comando>" Ejecuta un comando tras cada commit reaplicado (lección 05-02)
git config rebase.updateRefs true Actualiza también las otras ramas que apuntaban a commits del tramo reaplicado
git rebase --show-current-patch Enseña el commit que está fallando ahora mismo

rebase.updateRefs resuelve un problema molesto: si tienes ramas apiladas (b sale de a, c sale de b) y rebasas c, las referencias intermedias se quedaban apuntando a los commits viejos. Con esta opción activada, Git las arrastra.

  1. La red de seguridad

Un rebase puede salir mal: puedes hacer --skip de un commit que no debías, resolver un conflicto al revés, o darte cuenta a los diez minutos de que la base era otra.

Buena noticia: los commits originales siguen ahí. Como vimos en el apartado 2, el rebase no borra objetos, solo deja de referenciarlos, y Git guarda un registro de por dónde ha pasado cada referencia. Ese registro es el reflog, y con él se recupera el estado anterior a un rebase casi siempre.

Este curso dedica la lección 09-04: Recuperando Confirmaciones Perdidas a ese tema, incluido el procedimiento exacto para deshacer un rebase desastroso. Aquí basta con que sepas dos cosas:

  1. Que la red existe: un rebase mal hecho es recuperable durante semanas.
  2. Que no es excusa para saltarse la regla de oro, porque el reflog es local. Recupera tu repositorio; no arregla los de tus compañeros que ya se descargaron los commits que reescribiste.

Y el reflejo barato que sí puedes adoptar hoy: antes de un rebase que te dé respeto, deja una marca.

git branch copia-antes-del-rebase
git rebase main
# Si sale mal: git reset --hard copia-antes-del-rebase
# Si sale bien: git branch -d copia-antes-del-rebase

Una rama son 41 bytes. La tranquilidad, bastante más.

Errores Comunes y Consejos

Error 1: creer que el rebase "mueve" los commits. Los sustituye por otros nuevos con hashes distintos. Todo el peligro del rebase deriva de este hecho, y quien lo interioriza deja de tener sorpresas.

Error 2: rebasar una rama compartida. Si Bruno y Carla trabajan los dos en funcionalidad/exportacion-csv, ninguno de los dos la rebasa. Se integra con merge. La comodidad de un historial lineal no compensa una tarde de duplicados.

Error 3: usar --ours durante un rebase pensando que es tu versión. Es la de la base. Durante un rebase, tú eres theirs. Ante la duda, git show :2:fichero y git show :3:fichero.

Error 4: usar git rebase --skip para "quitarme el conflicto de encima". Descarta el commit entero. Si contenía trabajo real, desaparece de la rama resultante.

Error 5: hacer git commit en medio de un rebase. Quien confirma es git rebase --continue. Tú solo resuelves y haces git add.

Error 6: git push --force en lugar de --force-with-lease. El primero pisa lo que haya en el servidor sin mirar; el segundo se niega si alguien ha publicado algo que tú no tienes. La diferencia se explicó en 04-05 y aquí es donde de verdad importa.

Error 7: rebasar sin fetch previo. git rebase main usa tu main local, que puede llevar dos días desactualizado. El orden correcto es git fetch origin, actualizar main, y entonces rebasar (o rebasar directamente sobre origin/main).

Consejo 1: rebasa pronto y a menudo. Una rama que se pone al día cada día tiene conflictos pequeños; una que lo hace después de dos semanas tiene un conflicto por commit y todos grandes.

Consejo 2: mira siempre git log --oneline main..HEAD antes de rebasar. Esa lista es exactamente lo que se va a reescribir. Si te sorprende su longitud o su contenido, la base que ibas a usar no era la que creías.

Consejo 3: activa rebase.autoStash y merge.conflictStyle zdiff3. Dos líneas de configuración que eliminan dos de las fricciones más habituales.

Consejo 4: si el rebase se pone feo, aborta. git rebase --abort es gratis y siempre funciona. Volver a intentarlo con la cabeza fría, con los commits más pequeños o con --onto bien elegido suele ser mucho más rápido que insistir.

Ejercicios

Ejercicio 1: rebase básico y prueba de la inmutabilidad

Crea un repositorio de prácticas con una rama main y una rama funcionalidad/algo que salga de ella. Añade dos commits a cada una. Después:

  1. Anota los hashes de los dos commits de la rama de funcionalidad.
  2. Rebásala sobre main.
  3. Demuestra con comandos que los hashes han cambiado, que la fecha de autoría se ha conservado y que la de confirmación no.
  4. Demuestra que los commits originales siguen existiendo en la base de datos de objetos.

Ejercicio 2: --onto para desenredar una rama

Reproduce el problema de Carla: crea rama-a sobre main con dos commits, y rama-b sobre rama-a con otros dos. Después, usando git rebase --onto, consigue que rama-b cuelgue de main conteniendo solo sus dos commits propios. Verifica el resultado con git log --graph --all --oneline.

Ejercicio 3: conflicto en rebase y la inversión de caras

Provoca un conflicto durante un rebase (las dos ramas modificando la misma línea de un fichero). Con el rebase parado:

  1. Muestra las tres versiones del fichero desde el índice (etapas :1:, :2: y :3:) e identifica cuál corresponde a tu commit.
  2. Resuelve quedándote con tu versión usando la opción correcta de git checkout.
  3. Termina el rebase.
  4. Repite el experimento desde cero pero abortando con --abort, y comprueba que la rama vuelve a tener sus hashes originales.

Soluciones

Solución 1:

mkdir /tmp/practica-rebase && cd /tmp/practica-rebase
git init -b main
echo "linea base" > f.txt && git add . && git commit -m "Base"

git switch -c funcionalidad/algo
echo "a" >> f.txt && git commit -am "Commit A"
echo "b" >> f.txt && git commit -am "Commit B"

git switch main
echo "otra cosa" > g.txt && git add . && git commit -m "Commit en main 1"
echo "mas" >> g.txt && git commit -am "Commit en main 2"
# 1. Anotar hashes originales
git switch funcionalidad/algo
git log --oneline main..HEAD
9f3c7a1 Commit B
2d8e4b6 Commit A
# 2 y 3. Rebase y comprobación de fechas
git log --pretty='%h | autor: %ad | confirmado: %cd | %s' --date=iso main..HEAD
git rebase main
git log --pretty='%h | autor: %ad | confirmado: %cd | %s' --date=iso main..HEAD
2d8e4b6 | autor: 2026-07-28 10:02:11 +0200 | confirmado: 2026-07-28 10:02:11 +0200 | Commit A
7b1f9d4 | autor: 2026-07-28 10:02:11 +0200 | confirmado: 2026-07-28 10:05:47 +0200 | Commit A

El hash es distinto, la fecha de autoría idéntica y la de confirmación es la del momento del rebase.

# 4. Los commits viejos siguen existiendo
git cat-file -t 2d8e4b6
git show --stat 2d8e4b6 | head -3
commit

Existe como objeto aunque ninguna rama lo alcance. Es lo que hace posible la recuperación de la lección 09-04.

Solución 2:

mkdir /tmp/practica-onto && cd /tmp/practica-onto
git init -b main
echo "base" > f.txt && git add . && git commit -m "Base"

git switch -c rama-a
echo "a1" >> f.txt && git commit -am "A1"
echo "a2" >> f.txt && git commit -am "A2"

git switch -c rama-b
echo "b1" > b.txt && git add . && git commit -m "B1"
echo "b2" >> b.txt && git commit -am "B2"
# Comprobar qué me llevo: solo B1 y B2
git log --oneline rama-a..rama-b
c4a9f21 B2
81d3e07 B1
git rebase --onto main rama-a rama-b
git log --graph --all --oneline
* 5e2b8c4 (HEAD -> rama-b) B2
* a93f16d B1
| * 6f4c2e9 (rama-a) A2
| * 3b7d5a8 A1
|/
* 1c8e4f2 (main) Base

rama-b cuelga de main con sus dos commits y sin rastro de A1 ni A2.

Solución 3:

mkdir /tmp/practica-conflicto-rebase && cd /tmp/practica-conflicto-rebase
git init -b main
printf 'primera\nsegunda\ntercera\n' > f.txt && git add . && git commit -m "Base"

git switch -c mi-rama
sed -i 's/segunda/segunda MIA/' f.txt && git commit -am "Cambio de mi rama"

git switch main
sed -i 's/segunda/segunda DE MAIN/' f.txt && git commit -am "Cambio de main"

git switch mi-rama
git rebase main
CONFLICT (content): Merge conflict in f.txt
error: could not apply 4d9a7c2... Cambio de mi rama
# 1. Las tres versiones desde el índice
git show :1:f.txt   # base común
git show :2:f.txt   # "ours" = main, la nueva base
git show :3:f.txt   # "theirs" = MI commit
primera
segunda
tercera
primera
segunda DE MAIN
tercera
primera
segunda MIA
tercera

La etapa :3: es la mía: durante un rebase, mi commit es theirs.

# 2 y 3. Quedarme con lo mío y continuar
git checkout --theirs f.txt
git add f.txt
git rebase --continue
git log --oneline
b7e3f19 (HEAD -> mi-rama) Cambio de mi rama
5a1c8d3 (main) Cambio de main
2f9b6e4 Base
# 4. El mismo escenario, abortando
git reset --hard b7e3f19    # (repite el montaje desde cero si prefieres)
git switch mi-rama
git log --oneline -1        # anota el hash
git rebase main             # conflicto
git rebase --abort
git log --oneline -1        # mismo hash que antes

--abort restaura la rama exactamente como estaba: mismos commits, mismos hashes, mismo directorio de trabajo.

Conclusión

git rebase deja de dar miedo en cuanto se entiende qué hace de verdad. Lo esencial de esta lección:

  • Rebase no mueve commits: crea commits nuevos. Cambia el padre, luego cambia el hash, luego es otro objeto. Los originales quedan huérfanos pero siguen en la base de datos.
  • git rebase <base> reaplica sobre <base> todo lo que hay en tu rama y no en ella; git rebase --onto <nueva-base> <desde> <hasta> te deja elegir con precisión qué tramo mover y adónde.
  • Rebase frente a merge no es una cuestión técnica sino narrativa: merge cuenta lo que pasó, rebase cuenta una versión ordenada. Rebase para lo que aún es tuyo; merge para juntar historias públicas; ante la duda, merge.
  • Los conflictos de un rebase se resuelven igual que los de una fusión (lección 03-05), pero se sale con --continue, --skip (que descarta el commit) o --abort (que es siempre seguro), y las caras ours/theirs están invertidas: la base es ours, tu commit es theirs.
  • La regla de oro: no reescribas historial publicado que otros puedan tener. Si lo haces, tus compañeros acaban con todo duplicado y la única salida es coordinarse.
  • git pull --rebase aplica esta misma idea al sincronizar y elimina los commits de fusión inútiles; rebase.autoStash quita la fricción de tener cambios sin confirmar.
  • El reflog es la red de seguridad para tu repositorio (lección 09-04), no para el de los demás.

Lo que viene

Hasta ahora hemos usado el rebase para una sola cosa: cambiar la base. Pero el mismo mecanismo —descomponer una rama en una lista de commits y volver a aplicarlos— permite mucho más si te dejan editar esa lista antes de que se ejecute: cambiar el orden, unir dos commits en uno, partir uno en dos, corregir un mensaje o eliminar un commit por completo.

Eso es exactamente lo que hace git rebase -i, y es lo que Bruno necesita para convertir sus commits arreglo, wip 2 y wip 3 en algo que se pueda enseñar sin vergüenza. Lo vemos en la lección 05-02: Rebase Interactivo.

Dominando Git: De Principiante a Avanzado

Módulo 1: Introducción a Git

Módulo 2: Operaciones Básicas de Git

Módulo 3: Ramas y Fusión

Módulo 4: Trabajando con Repositorios Remotos

Módulo 5: Operaciones Avanzadas de Git

Módulo 6: Herramientas y Técnicas de Git

Módulo 7: Estrategias de Colaboración y Flujo de Trabajo

Módulo 8: Mejores Prácticas y Consejos de Git

Módulo 9: Solución de Problemas y Depuración

Módulo 10: Git en el Mundo Real

© Copyright 2026. Todos los derechos reservados