DevOps, CI/CD y contenedores
Ramas y flujos de trabajo en Git: merge, rebase, Git Flow y trunk-based development
Intermedio10 min de lecturaActualizado
Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés
En Git las ramas no cuestan nada, así que las decisiones importantes son cuánto tiempo viven y cómo se integran. Esta guía explica los merges fast-forward y de tres vías, el rebase y sus reglas, la resolución de conflictos, y después compara los flujos de trabajo que los equipos usan de verdad —ramas de funcionalidad con pull requests, Git Flow, GitHub Flow y trunk-based development— con criterios para saber cuál encaja con cada modelo de publicación.
Lo que aprenderás
- Explicar qué registra un commit de merge y cuándo Git hace un fast-forward en su lugar
- Usar rebase para mantener un historial lineal y saber exactamente cuándo no hacerlo
- Resolver conflictos de merge con método y evitar las formas habituales de estropearlos
- Comparar Git Flow, GitHub Flow y trunk-based development y elegir uno para un equipo
- Aplicar cherry-pick, rebase interactivo y ramas protegidas a situaciones reales de publicación
En Git crear una rama no cuesta nada, lo que desplaza las preguntas interesantes de «¿creamos una rama?» a «¿cuánto vive una rama, cómo vuelve a la línea principal y qué aspecto tiene el historial después?». Respuestas distintas producen un día a día muy distinto: un equipo que hace rebase de ramas cortas sobre main varias veces al día vive en un mundo diferente al de uno que mantiene ramas develop, release/* y hotfix/* con una ceremonia de merge para cada una. Esta guía explica la mecánica —merge, rebase, conflictos— con la precisión suficiente para que puedas predecir qué hará un comando, y después compara los flujos de trabajo que se construyen sobre ella.
Ramas, merge base y fast-forward
Una rama es un puntero a un commit. Cuando dos ramas comparten un ancestro común y una de ellas ha avanzado, Git llama a ese ancestro la merge base. Hacer merge trae a la rama actual los cambios que la otra rama ha acumulado desde la merge base.
Si la rama actual no se ha movido desde la merge base —main no tiene commits nuevos y feature tiene tres—, no hay nada que combinar. Git simplemente adelanta el puntero de main hasta la punta de feature. Esto es un merge fast-forward: no hay commit de merge, el historial es lineal y no queda rastro de que la rama haya existido.
Si ambas ramas han avanzado, Git realiza un merge de tres vías: compara la merge base con cada una de las puntas, aplica los dos conjuntos de cambios y registra el resultado en un commit de merge con dos padres. Ese commit de merge es lo que hace visible la rama en el historial a partir de entonces.
git switch main
git merge feature # fast-forward if possible, merge commit otherwise
git merge --no-ff feature # always create a merge commit, even when a fast-forward was possible
git merge --ff-only feature # refuse to merge unless a fast-forward is possible
--no-ff lo usan los equipos que quieren que cada funcionalidad aparezca como un único commit de merge en git log --first-parent, lo que les permite revertir una funcionalidad entera con git revert -m 1 <merge-commit>. --ff-only lo usan los equipos que exigen un historial lineal y hacen rebase de las ramas antes de integrarlas.
Rebase
git rebase main, ejecutado sobre feature, toma cada commit de feature desde la merge base y lo vuelve a aplicar encima de la punta actual de main, creando commits nuevos con hashes nuevos. El efecto es como si hubieras creado la rama hoy: tus cambios quedan encima de los de todos los demás, el historial es lineal y un merge posterior en main se resuelve con fast-forward.
El rebase es la herramienta adecuada para mantener al día una rama privada. Es la herramienta equivocada para cualquier cosa que ya esté compartida, porque los commits reescritos tienen hashes distintos de los que tus compañeros descargaron, y su siguiente pull produce commits duplicados y conflictos desconcertantes. La regla es breve: nunca hagas rebase de commits que existan fuera de tu repositorio, salvo que todos los implicados lo sepan y estén de acuerdo (la excepción habitual es una rama personal de pull request en la que solo tú haces push).
El rebase interactivo (git rebase -i main) abre la lista de commits que están a punto de reaplicarse y te permite reordenarlos, fusionarlos (squash), editarlos, cambiarles el mensaje o descartarlos. Es la forma de convertir ocho commits «wip» en dos coherentes antes de abrir una pull request.
pick 3f2a1c9 Add DiscountCode entity
squash 8b7d0e2 fix typo
squash c41e77a wip
pick 92ad5b1 Apply discount codes at checkout
reword d05f3a6 tests
Al guardar, Git reaplica los commits según las instrucciones y pide los mensajes cuando hace falta. Combinado con git add -p, el rebase interactivo es lo que permite entregar a los revisores unos pocos commits que hacen, cada uno, una sola cosa.
Resolver conflictos
Un conflicto se produce cuando ambos lados han cambiado las mismas líneas (o uno ha borrado un archivo que el otro ha editado) y Git no puede decidir. Se detiene, marca el archivo y espera.
<<<<<<< HEAD
BigDecimal total = subtotal.add(shipping);
=======
BigDecimal total = subtotal.add(shippingCost).add(tax);
>>>>>>> feature/tax
La parte que queda por encima de ======= es la rama actual (HEAD), y la de debajo, la rama que se está integrando. El procedimiento:
Ver qué está en conflicto
git statusmuestra las rutas bajo «Unmerged paths».git diffenseña los conflictos con los dos lados.Editar cada archivo hasta el resultado deseado
Quita los marcadores y escribe el código que debe existir: a menudo una combinación de ambos lados, a veces ninguno de los dos. Compila y ejecuta los tests antes de seguir.Marcarlo como resuelto
git add <file>le indica a Git que el conflicto está resuelto.Terminar la operación
git merge --continue(ogit commit) en un merge;git rebase --continueen un rebase, que puede presentarte a continuación el siguiente commit en conflicto.--abort, en cualquiera de los dos, vuelve al estado anterior.
Las herramientas de merge (git mergetool, o la vista de tres paneles de IntelliJ IDEA y VS Code) muestran la base, «ours» y «theirs» lado a lado, algo mucho más cómodo que leer marcadores en cuanto el conflicto pasa de una o dos líneas. Activar rerere («reuse recorded resolution») hace que Git recuerde cómo resolviste un conflicto y aplique la misma resolución automáticamente la próxima vez que lo vea, algo impagable cuando haces rebase de una rama larga una y otra vez.
Dos hábitos reducen los conflictos más que cualquier herramienta: integrar main a diario para que la divergencia siga siendo pequeña, y no mezclar reformateos masivos ni movimientos de archivos con cambios de lógica en el mismo commit.
Comparativa de flujos de trabajo
Con la mecánica clara, los flujos de trabajo no son más que recetas sobre cuándo crear ramas y cómo integrarlas.
Ramas de funcionalidad con pull requests
Cada cambio tiene su rama creada desde main, el trabajo se hace ahí y una pull request (merge request en GitLab) reúne los comentarios de revisión y los resultados de CI antes de integrar la rama y borrarla. Es el punto de partida en GitHub, GitLab y Bitbucket. Las variables son la duración de las ramas y el tipo de merge: ramas que duran uno o dos días y se integran con squash o rebase dan un main limpio y lineal; ramas que viven semanas recrean justo los problemas de integración que la integración continua pretende evitar.
GitHub Flow
Un modelo deliberadamente mínimo: main siempre se puede desplegar; cada cambio es una rama y una pull request; hacer merge en main despliega. No hay ramas de release, porque una release es lo que contenga main en ese momento. Encaja con servicios desplegados de forma continua y con equipos que tienen buenos tests automatizados.
Git Flow
El modelo elaborado, con dos ramas permanentes y tres tipos de ramas temporales:
| Rama | Se crea desde | Se integra en | Propósito |
|---|---|---|---|
main | — | — | Solo versiones de producción, cada una etiquetada con su número de versión |
develop | main | main a través de release | Rama de integración para la próxima versión |
feature/* | develop | develop | Funcionalidades individuales |
release/* | develop | main y develop | Estabilizar una versión: subida de versión y solo corrección de bugs |
hotfix/* | main | main y develop | Corrección urgente en producción |
Git Flow responde a preguntas reales para software con versiones numeradas y varias versiones con soporte en manos de los clientes: ¿dónde van las correcciones de la 2.3 mientras la 2.4 se estabiliza y la 2.5 está en desarrollo? Su coste es la ceremonia y las ramas de larga duración —develop puede alejarse de main durante semanas—, y para un servicio web que se despliega varias veces al día no es más que sobrecarga. El propio autor del modelo ha señalado que lo diseñó antes de que la entrega continua se convirtiera en práctica habitual.
Trunk-based development
Todo el mundo hace commit en main (el tronco) al menos una vez al día, ya sea directamente o mediante pull requests que duran horas. Las funcionalidades incompletas se mantienen ocultas con feature flags o construyendo el nuevo camino junto al antiguo («branch by abstraction») en lugar de en una rama. Las ramas de release, si se usan, se crean desde el tronco y solo reciben correcciones mediante cherry-pick, nunca trabajo nuevo. La investigación de DORA asocia este modelo con un mayor rendimiento en la entrega, y es el compañero natural de la integración continua. Exige confiar en la batería de tests, hacer cambios pequeños e invertir en feature flags; a los equipos que no tienen esas bases les resulta incómodo.
Cómo elegir
| Situación | Opción razonable |
|---|---|
| Servicio web desplegado de forma continua, una sola versión en producción | GitHub Flow o trunk-based |
| Producto con versiones numeradas, una sola versión con soporte | Ramas de funcionalidad más una rama de release de corta duración por versión |
| Librería o producto on-premise con varias versiones con soporte | Git Flow o un modelo de «ramas de release con cherry-picks» |
| Equipo grande, monorepo, mucha automatización | Trunk-based con feature flags |
| Aprobación regulada de las publicaciones | Cualquiera de los anteriores, con la aprobación sobre el despliegue, no sobre el merge |
La lección que se repite es que el flujo de trabajo debe seguir al modelo de publicación, y no al revés.
Ramas de release, cherry-picks y tags
Incluso los equipos trunk-based necesitan a veces una rama de release: una versión ya está en manos de los clientes y necesita una corrección mientras main ha seguido avanzando. El patrón consiste en crear release/2.4 a partir del commit etiquetado, corregir el bug primero en main (para que nunca se pierda), después hacer git cherry-pick <hash> en la rama de release, crear ahí el tag v2.4.1 y compilar desde el tag. Corregir primero en main evita el fallo clásico en el que una corrección solo llega a la rama de release y reaparece como regresión en la siguiente versión.
Protege las ramas importantes en la plataforma de alojamiento: exige pull requests, exige que pase la comprobación de CI y prohíbe los force-push y los borrados en main y release/*. Las reglas de protección no cuestan nada y evitan los accidentes más destructivos.
Convenciones de commit
Un modelo de ramas funciona mejor con commits coherentes. Conventional Commits (feat:, fix:, refactor:, con un ámbito opcional y un ! para los cambios incompatibles) es un formato muy extendido que las herramientas pueden analizar para generar changelogs y calcular automáticamente la siguiente versión semántica. Adoptes o no el formato de prefijos, mantén los commits de la rama con sentido tras el rebase, nombra las ramas según su intención (fix/null-shipping-cost, feat/discount-codes) y haz referencia al gestor de incidencias en la pull request en lugar de en cada commit.
Errores habituales
- Hacer rebase de una rama compartida. Tus compañeros se encuentran con commits duplicados y conflictos en su siguiente pull. Haz rebase solo de lo que es tuyo; integra todo lo demás con merge.
- Ramas de funcionalidad de larga duración. Tres semanas de trabajo integradas de golpe producen una maratón de conflictos y esconden bugs de integración. Integra piezas pequeñas protegidas por flags.
- Resolver un conflicto quedándote con «ours» en todas partes. Elegir un lado a ciegas descarta el cambio de la otra persona y reintroduce el bug que ella había corregido. Lee los dos lados.
- Hacer merge de
mainen una rama de funcionalidad una y otra vez, y luego integrarla de vuelta. El historial se convierte en un entramado de commits de merge que oculta qué cambió la funcionalidad. Haz rebase de las ramas privadas o integra desdemaincon moderación. - Hotfix solo en la rama de release. La corrección nunca llega a
mainy el bug vuelve en la siguiente versión. Corrige primero hacia delante y después haz cherry-pick hacia atrás. - Borrar la rama antes de que se integre la pull request. En la mayoría de plataformas, la pull request se cierra con ella. Deja que la plataforma borre la rama al hacer merge.
- Hacer force-push sin
--force-with-lease. Sobrescribe el commit que un compañero subió un minuto antes.
Ideas clave
- Un merge hace fast-forward cuando el destino no se ha movido; si no, crea un commit de merge con dos padres que registra la combinación.
- El rebase recrea los commits encima de otra rama y produce un historial lineal; úsalo en ramas privadas y nunca en commits que otros puedan haber descargado.
- Resuelve los conflictos leyendo ambos lados, editando hasta el resultado deseado y probando, y después
addy--continue;rererey la integración diaria hacen que los conflictos sean raros y pequeños. - GitHub Flow y trunk-based development encajan con el despliegue continuo; Git Flow encaja con productos versionados con varias versiones con soporte; adapta el flujo de trabajo al modelo de publicación.
- Corrige primero en
mainy haz cherry-pick a las ramas de release, etiqueta las versiones con tags anotados y protegemainfrente a force-pushes y merges sin revisar. - Commits y ramas pequeños y bien nombrados, ordenados con rebase interactivo antes de la revisión, hacen más llevadero cualquier flujo de trabajo.
Fuentes y lecturas recomendadas
Preguntas frecuentes
- ¿Hago merge o rebase de mi rama de funcionalidad sobre main?
- Haz rebase mientras la rama sea solo tuya: mantiene el historial lineal y coloca tus commits encima del main más reciente, de modo que el merge final es trivial. En cuanto hayas subido la rama al remoto y otros puedan haber basado trabajo en ella, deja de hacer rebase y haz merge. Muchos equipos combinan ambas cosas: rebase en local para ponerse al día y luego squash-merge o merge con --no-ff a través de una pull request.
- ¿Se sigue recomendando Git Flow?
- Su propio autor ha dicho que encaja con software que se publica en versiones explícitas y con varias versiones con soporte —aplicaciones de escritorio, librerías, productos on-premise— y que a los servicios web desplegados de forma continua les va mejor un modelo más sencillo. Si despliegas desde main varias veces al día, las ramas develop y release de Git Flow añaden ceremonia sin aportar nada.
- ¿Cómo llevo un único commit de corrección de main a una rama de release antigua?
- Usa git cherry-pick con el hash del commit teniendo la rama de release activa. Git aplica el mismo cambio como un commit nuevo en la rama de release. Los cherry-picks funcionan mejor cuando el commit original es pequeño y autocontenido; si depende de otros cambios te encontrarás con conflictos, y suele ser más limpio corregir primero en la rama con soporte más antigua e ir integrándola después en las más nuevas.
- ¿Qué es un squash merge y cuándo es mala idea?
- Un squash merge combina todos los commits de una rama en un solo commit en el destino, lo que da un historial limpio de un commit por funcionalidad. Es una buena opción por defecto para pull requests llenas de commits de trabajo en curso. Es mala idea cuando los commits individuales de la rama tienen sentido por sí mismos —un refactor seguido de un cambio de comportamiento— o cuando vas a volver a hacer merge de la misma rama más adelante, porque Git no puede saber que el commit aplastado venía de esa rama y volverá a reproducir los conflictos.