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
- La regla de oro del módulo
- Qué hace
git rebaserealmente - Un rebase paso a paso en
gestor-tareas - Qué se conserva y qué cambia en los commits reaplicados
- Rebase frente a merge
git rebase --onto: mover un tramo concreto- Conflictos durante un rebase:
--continue,--skip,--abort - La inversión de
oursytheirs - Romper la regla de oro: qué pasa exactamente
git pull --rebaserebase.autoStashy otras opciones cómodas- La red de seguridad
- 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.
- Qué hace
git rebase realmente
git rebase realmenteLa 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:
- Calcular la lista de commits que están en tu rama y no en la nueva base.
- Guardar el cambio (el diff) que introduce cada uno.
- Situarse en la nueva base.
- Aplicar esos cambios uno a uno, creando un commit nuevo por cada uno.
- 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.
- Un rebase paso a paso en
gestor-tareas
gestor-tareasCarla 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..HEADEsa es exactamente la lista de la que hablábamos: el rango main..HEAD que aprendiste en la lección 02-06. Ahora sí:
* 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 -6commit 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.
- 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) | Sí |
| Fecha de autoría (author date) | Sí |
| 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 logmuestra 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.
- 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:
- 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
mainconrebase. - Merge para juntar historias que ya son públicas. Integrar una rama de funcionalidad terminada en
maines un hecho que merece quedar registrado; ahí un merge (a menudo--no-ff, lección 03-03) es la opción honesta. - 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.
git rebase --onto: mover un tramo concreto
git rebase --onto: mover un tramo concretogit 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:
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 rangodesde..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# 2. El rebase --onto
git rebase --onto main funcionalidad/etiquetas-color funcionalidad/orden-por-fechagitGraph 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 mainEse ú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.
- 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:
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
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:
--skipno 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.--abortes 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 commita mano durante un rebase. Después de resolver y hacergit add, esgit rebase --continuequien crea el commit. Si hacesgit commit,--continuete 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.
- La inversión de
ours y theirs
ours y theirsAquí 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 --continueLo 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 commitUn 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:
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.
- 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.
- Bruno publica
funcionalidad/exportacion-csvcon tres commits:2a7f4c1,9e3b8d6,5c1a9f2. - Carla hace
git fetchy crea su rama a partir de ese trabajo. Ahora tiene esos tres commits en su disco. - Bruno rebasa su rama sobre
mainporquemainha avanzado. Sus tres commits se convierten end8b2e50,71f6c34,ae95d18. - Bruno hace
git push --force-with-lease. El servidor ahora apunta aae95d18. - 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:
- No reescribir ramas compartidas. Si más de una persona trabaja en ella, se integra con merge y punto.
- 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. - 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.
git pull --rebase
git pull --rebaseEn 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:
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 mergespull.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.
rebase.autoStash y otras opciones cómodas
rebase.autoStash y otras opciones cómodasgit rebase exige un directorio de trabajo limpio: si tienes cambios sin confirmar, se niega a empezar.
La solución manual es git stash, rebasar y git stash pop (lección 05-04). La automática:
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.
- 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:
- Que la red existe: un rebase mal hecho es recuperable durante semanas.
- 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-rebaseUna 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:
- Anota los hashes de los dos commits de la rama de funcionalidad.
- Rebásala sobre
main. - Demuestra con comandos que los hashes han cambiado, que la fecha de autoría se ha conservado y que la de confirmación no.
- 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:
- Muestra las tres versiones del fichero desde el índice (etapas
:1:,:2:y:3:) e identifica cuál corresponde a tu commit. - Resuelve quedándote con tu versión usando la opción correcta de
git checkout. - Termina el rebase.
- 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"# 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..HEADEl hash es distinto, la fecha de autoría idéntica y la de confirmación es la del momento del rebase.
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"* 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# 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 commitLa 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# 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 carasours/theirsestán invertidas: la base esours, tu commit estheirs. - 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 --rebaseaplica esta misma idea al sincronizar y elimina los commits de fusión inútiles;rebase.autoStashquita 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
- ¿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
