git merge vs rebase: ¿salió el commit de tu máquina?
Mismo árbol, dos historiales — la prueba de banco donde 5d48940 volvió como 6b2be0a, la advertencia de la rama compartida y la salida por reflog cuando gana el equivocado.
■ space · prosa
Una pregunta lo decide: ¿salió el commit de tu máquina? Trabajo sin push → rebase. Algo que otros puedan haber descargado → merge.
El merge registra lo que pasó — un commit, dos padres, ningún SHA cambia. El rebase reescribe: en el banco de abajo, el commit 5d48940 volvió como 6b2be0a.
¿El rebase salió mal? git reflog + git reset --hard HEAD@{1} devuelve la rama. No se pierde nada.
LOS DOS HISTORIALES
Ambos comandos suben el mismo código; discrepan sobre lo que el historial debe contar después. Ejecuté ambos en un banco desechable con git 2.47.3, los mismos cinco commits cada vez — la pasada de merge conservó cada SHA original y añadió un nudo; la pasada de rebase cambió la identidad de los commits de la rama. Mismo árbol, historial distinto:
fig — los primos son los mismos cambios con nuevos IDs de commit: c3 entró como 5d48940 y salió como 6b2be0a
verificar: git log --oneline --graph → a "|\\\\" significa un merge, una línea recta significa que ganó el rebase
CUANDO EL MERGE SE VUELVE UN FAST-FORWARD
Si main no se ha movido desde que creaste la rama, no hay nada que fusionar — git solo desliza la etiqueta hacia adelante. Eso es un fast-forward, y es por eso que la gente del rebase consigue su línea limpia sin forzar nada: haz rebase de tu rama sobre main, luego el merge se vuelve un fast-forward, y el grafo queda en una sola línea recta. Del banco — cinco commits, cero nudos:
* 67c0442 c4 feature 2 # rebased, then merged — fast-forward
* 6b2be0a c3 feature 1 # new SHAs — 5d48940 before the rebase
* 1398836 c5 main moved on
* facd5a4 c2 main work
* f45b000 c1 base
verificar: git merge --ff-only feature → "Fast-forward" en la salida, sin commit de merge creado
El precio: esos SHAs primos ya no existen en ningún sitio salvo en tu máquina. Hazles push y el clon de un compañero sigue guardando los originales — dos versiones del commit "igual".
¿QUÉ COMMITS SIGUEN SIENDO SOLO TUYOS?
La respuesta es un comando, no una corazonada. git log origin/main..HEAD lista los commits que el origen jamás ha visto — esos son rebasables. Si la lista incluye un commit que otro clon podría tener ya — un push, un pull request abierto, una rama que empujaste y forzaste antes — no es solo tuyo, diga lo que diga tu grafo local. El trabajo sin push es exactamente para lo que existe el rebase: repruébalo sobre la base actualizada y tu pull request se lee como una única secuencia limpia. Por eso también git pull --rebase es un valor por defecto sensato en ramas privadas — reescala tus commits locales sobre lo que haya llegado, en lugar de tejer un nudo de merge cada vez que sincronizas. Fíate de la rama, no del instinto: git fetch origin primero, git status -sb para el par por delante/por detrás.
QUÉ SE ROMPE EN UNA RAMA COMPARTIDA
Cada clon que guarda los commits viejos ahora discrepa del tuyo sobre la realidad. El rebase da a tus commits SHAs nuevos; tu rama empujada y tu rama reescrita ya no comparten ascendencia, así que el próximo push necesita --force — y todo el que descargó la versión vieja tendrá que fusionar su copia de commits que "ya no existen". Sus duplicados reaparecen en cada merge futuro; desenredar eso cuesta una tarde. Los comentarios de revisión anclados a SHAs concretos se despegan en el mismo movimiento — rebase un pull request abierto y el contexto de la revisión se evapora con los viejos IDs de commit. La regla mide una frase, y el libro oficial la lleva como advertencia, no como sugerencia:
No hagas rebase de commits que existan fuera de tu repositorio y sobre los que otras personas puedan haber construido trabajo.— Pro Git, 2ª edición, "Git Branching — Rebasing"
DESHACER UN REBASE
Los commits viejos sobreviven al rebase — el reflog es donde se esconden. El rebase mueve punteros de rama; no borra objetos. La recuperación del banco, después de que un reset --hard tirara dos commits:
$ git reflog | head -4
1398836 HEAD@{0}: reset: moving to HEAD~2
67c0442 HEAD@{1}: merge feature: Fast-forward
1398836 HEAD@{2}: checkout: moving from feature to main
67c0442 HEAD@{3}: rebase (finish): returning to refs/heads/feature
$ git reset --hard HEAD@{1}
HEAD is now at 67c0442 c4 feature 2
Una línea, rama restaurada, trabajo staged y unstaged incluido. Las entradas de reflog caducan a los 90 días por defecto, así que el rescate tiene reloj.
verificar: git reflog | head -3 → la punta previa al rebase está en HEAD@{1}, a un reset de distancia
LAS SEIS SITUACIONES
| Situación | Comando | Por qué gana |
|---|---|---|
| Tu rama de feature, nunca empujada | git rebase main | Historial lineal, una secuencia limpia para revisar |
| Una rama de la que otros descargan | git merge | Sin reescritura de SHAs — sin commits duplicados en el clon de nadie |
| main local divergido, nada empujado | git pull --rebase | Sincroniza sin nudo; tus commits quedan arriba |
| Fork poniéndose al día con upstream | git merge upstream/main | El PR sigue atado a sus commits |
| Hotfix que debe aterrizar ya | git merge --no-ff | Marcador visible de dónde entró el arreglo, incluso en una rama con fast-forward posible |
| El rebase salió mal | git reset --hard HEAD@{1} | El reflog aún guarda la punta previa al rebase (~90 días) |
¿Dónde encaja --squash? Toma los cambios de una rama y los deja staged como un solo montón sin commit — sin commit de merge, sin vínculo con el historial de la rama. El squash es la tercera respuesta, para tirar una rama después de aterrizar su cambio neto; no reescribe nada, solo se niega a registrar los pasos internos de la rama. ¿Y un equipo puede elegir solo uno? Muchos lo hacen: rebase en el trabajo local, merge hacia lo compartido es la política más común, y un "rebase todo" a nivel de repositorio también funciona — mientras ninguna rama compartida se reescriba jamás, la única frase que sobrevive a cada debate de flujo de trabajo. La versión de diez segundos, una última vez: ¿salió el commit de tu máquina? No → rebase. Sí → merge. Cuando gana el equivocado, el reflog tiene la salida.
fin de los dos historiales · nada descargado · todo local