4. Branching y Merging
Las ramas son una de las características más poderosas de Git. Te permite crear una versión del código principal para trabajar en nuevas funcionalidades, corregir bugs o simplemente jugar, sin afectar el código original. Después, puedes descargar los cambios o incluir el nuevo código en la versión "oficial".
Imagina que tu proyecto es un árbol:
- La rama principal (normalmente llamada
mainomaster), que es el tronco del árbol. - Las ramas de desarrollo (o nuevas características), que son las ramas que salen del tronco.
Cada rama puede evolucionar de forma independiente, incluso crearse otras ramas a partir de ellas. Si un cambio es interesante y aporta valor, se puede unir los nuevos cambios al tronco.
De esta manera puedes:
- Trabajar en nuevas funcionalidades sin romper el código estable.
- Múltiples personas pueden trabajar en paralelo con la misma base de código.
- Experimentar con ideas sin consecuencias. Al borrar una rama, todo lo que hiciste en ella desaparece sin consecuencias.
Para gestionar las ramas usaremos los siguientes comandos:
# Ver todas las ramas
git branch
# Ver todas las ramas (incluyendo remotas)
git branch -a
# Crear una nueva rama
git branch nueva-funcionalidad
# Crear y cambiar a una rama
git branch -c mi-rama
# Eliminar una rama
git branch -d nombre-rama
# Eliminar una rama forzosamente
git branch -D nombre-rama
# Renombrar una rama
git branch -m viejo-nombre nuevo-nombre
git checkout
Cambia entre ramas o restaura archivos.
# Cambiar a una rama existente
git checkout main
# Crear una rama y cambiar a ella
git checkout -b nueva-rama
# Cambiar a un commit específico
git checkout abc1234
# Restaurar un archivo del último commit
git checkout -- archivo.txt
git switch
Un comando más moderno y específico para cambiar ramas (desde Git 2.23).
# Cambiar a una rama
git switch main
# Crear y cambiar a una nueva rama
git switch -c nueva-funcionalidad
# Volver a la rama anterior
git switch -
Recomendación: Usa git switch para cambiar ramas y git checkout para operaciones con archivos.
Flujo de trabajo con ramas
# 1. Crear una rama para tu funcionalidad
git switch -c feature/login
# 2. Hacer cambios
echo "Login component" > login.js
git add login.js
git commit -m "Añade componente de login"
# 3. Hacer más commits según necesites
echo "Tests" > login.test.js
git add login.test.js
git commit -m "Añade tests para login"
# 4. Volver a main
git switch main
# 5. Fusionar tu rama
git merge feature/login
git merge
Fusiona una rama en la rama actual.
# Fusionar una rama
git merge nombre-rama
# Fusionar sin fast-forward (crea un commit de merge)
git merge --no-ff nombre-rama
# Abortar un merge
git merge --abort
Tipos de merge:
Fast-forward: Cuando no hay nuevos commits en la rama base.
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch feature
commit id: "C"
commit id: "D"
checkout main
merge feature tag: "Fast-forward"
Three-way merge: Cuando hay cambios en ambas ramas.
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4', 'git2': '#95e1d3'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch develop
checkout develop
commit id: "C"
branch feature/nueva-funcionalidad
checkout feature/nueva-funcionalidad
commit id: "D"
commit id: "E"
checkout develop
commit id: "F"
merge feature/nueva-funcionalidad tag: "M1"
checkout main
commit id: "G"
merge develop tag: "M2"
Squash merge: Combina todos los commits de la rama en uno solo.
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout feature
commit id: "C"
commit id: "D"
commit id: "E"
checkout main
commit id: "F"
commit id: "S (C+D+E)" type: HIGHLIGHT tag: "Squash"
Merge vs Rebase vs Fast-forward
Existen diferentes formas de integrar cambios de una rama a otra. Cada una tiene sus ventajas y casos de uso.
Fast-forward merge
Es el tipo de merge más simple. Ocurre cuando la rama destino no ha tenido nuevos commits desde que se creó la rama que quieres fusionar. Git simplemente mueve el puntero de la rama hacia adelante.
- No crea un commit de merge
- El historial se mantiene lineal
- Es como si nunca hubieras creado una rama
# main está en commit B
# Creas feature y haces commits C y D
# main sigue en B (nadie ha hecho commits ahí)
git switch main
git merge feature # Fast-forward automático
Resultado: main ahora apunta a D, el historial es A → B → C → D
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch feature
commit id: "C"
commit id: "D"
checkout main
merge feature tag: "Fast-forward (main → D)"
Forzar o evitar fast-forward:
# Evitar fast-forward (siempre crear commit de merge)
git merge --no-ff feature
# Forzar fast-forward (falla si no es posible)
git merge --ff-only feature
Three-way merge (Merge tradicional)
Ocurre cuando ambas ramas tienen commits nuevos. Git crea un nuevo commit que tiene dos padres: el último commit de cada rama.
- Crea un commit de merge (commit con dos padres)
- Preserva todo el historial
- Muestra claramente cuándo se fusionaron ramas
- El historial no es lineal
Cuándo usar:
- Cuando quieres mantener el contexto de que hubo una rama
- En ramas de larga duración (develop, release)
- Cuando trabajas en equipo y quieres ver quién fusionó qué
Ventajas:
- Historial completo y honesto
- Fácil de revertir (solo reviertes el commit de merge)
- No reescribe la historia
Desventajas:
- Historial puede volverse complejo con muchas ramas
- Commits de merge "contaminan" el historial
git switch main
git merge feature # Crea commit de merge si hay cambios en main
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout feature
commit id: "C"
commit id: "D"
checkout main
commit id: "E"
commit id: "F"
merge feature tag: "M (commit de merge)"
Squash merge
Combina todos los commits de una rama en un único commit nuevo antes de fusionarlos. Es como tomar todo el trabajo de la rama y "comprimirlo" en un solo commit.
- Crea un único commit con todos los cambios
- Pierde el historial individual de commits de la rama
- El historial en main queda limpio y simple
- No mantiene la relación con la rama original
Cuándo usar:
- Pull Requests con muchos commits pequeños o de desarrollo
- Cuando el historial detallado de la rama no es importante
- Quieres mantener main con un historial limpio
- Cada commit en main representa una funcionalidad completa
Ventajas:
- Historial de main muy limpio y legible
- Un commit = una funcionalidad completa
- Fácil de revertir (un solo commit)
- Elimina commits intermedios como "fix typo", "WIP", etc.
Desventajas:
- Pierdes el historial detallado de desarrollo
- No puedes hacer cherry-pick de commits individuales
- Dificulta entender cómo se desarrolló la funcionalidad
git switch main
git merge --squash feature
git commit -m "feat: añade nueva funcionalidad completa"
%%{init: {'theme':'base', 'themeVariables': { 'git0': '#ff6b6b', 'git1': '#4ecdc4'}}}%%
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout feature
commit id: "C (WIP)"
commit id: "D (fix typo)"
commit id: "E (add tests)"
checkout main
commit id: "F"
commit id: "S (C+D+E squashed)" type: HIGHLIGHT tag: "Squash"
GitHub usa squash merge como opción por defecto en muchos proyectos. Es muy común en el desarrollo open source.
Rebase
Rebase "reubica" tus commits en la punta de otra rama. En lugar de fusionar, reescribe la historia como si hubieras empezado tu trabajo desde el commit más reciente de la rama destino.
- Reescribe el historial
- Crea commits nuevos (con diferentes hashes)
- El historial se mantiene lineal
- No hay commits de merge
# Antes del rebase:
# main: A → B → E → F
# feature: A → B → C → D
git switch feature
git rebase main
# Después del rebase:
# main: A → B → E → F
# feature: A → B → E → F → C' → D'
# (C' y D' son los commits C y D "reubicados")
Cuándo usar:
- Ramas de funcionalidad locales antes de hacer push
- Quieres un historial limpio y lineal
- Actualizas tu rama con cambios recientes de main
Ventajas:
- Historial lineal y fácil de leer
- No hay commits de merge
- Facilita hacer bisect y revisar cambios
Desventajas:
- Reescribe la historia (nunca hagas rebase de commits públicos)
- Puede ser confuso para principiantes
- Los conflictos se resuelven commit por commit
NUNCA hagas rebase de commits que ya están en repositorios compartidos (pusheados a origin).
Comparación práctica
Escenario: Tienes una rama feature con commits C y D. Mientras tanto, main tiene commits E y F.
Con merge:
git switch main
git merge feature
# Resultado: A → B → E → F → M (merge de C y D)
# ↘ C → D ↗
Con rebase + merge:
git switch feature
git rebase main # Reubica C y D después de F
git switch main
git merge feature # Fast-forward
# Resultado: A → B → E → F → C' → D'
¿Cuál usar?
Usa Fast-forward (dejando que ocurra naturalmente):
- Cambios pequeños y rápidos
- No necesitas preservar el contexto de la rama
Usa Merge (three-way):
- Ramas importantes (develop → main, release → main)
- Quieres preservar el contexto histórico
- Trabajo colaborativo donde el historial importa
- No quieres reescribir historia
Usa Squash merge:
- Pull Requests en proyectos open source
- Features con muchos commits de desarrollo intermedios
- Quieres un historial limpio en main donde cada commit es una funcionalidad
- No necesitas el historial detallado de cómo se desarrolló
Usa Rebase:
- Actualizar tu rama local con cambios de main
- Antes de crear un PR (para tener un historial limpio)
- Ramas de funcionalidad personales
- Quieres un historial lineal
Flujo recomendado:
# Mientras trabajas en tu feature
git switch feature
# Actualiza con cambios de main usando rebase
git fetch origin
git rebase origin/main
# Resuelve conflictos si los hay
git add archivo.txt
git rebase --continue
# Cuando termines, crea PR
# El maintainer decidirá si hacer merge o squash
Ejemplo completo:
# Estrategia 1: Merge tradicional
git switch main
git merge feature/login --no-ff -m "Merge: añade funcionalidad de login"
# Estrategia 2: Rebase + Fast-forward
git switch feature/login
git rebase main # Actualiza la rama
git switch main
git merge feature/login # Fast-forward automático
# Estrategia 3: Squash merge (combina todo en un commit)
git switch main
git merge --squash feature/login
git commit -m "feat: añade sistema completo de login"
# Estrategia 4: Solo fast-forward (falla si no es posible)
git switch main
git merge --ff-only feature/login
Resolución de conflictos
Un conflicto ocurre cuando dos ramas modifican las mismas líneas de código.
Ejemplo de conflicto:
# En rama main
echo "Versión main" > archivo.txt
git add archivo.txt
git commit -m "Actualiza en main"
# Crear y cambiar a nueva rama desde un commit anterior
git switch -c feature
echo "Versión feature" > archivo.txt
git add archivo.txt
git commit -m "Actualiza en feature"
# Intentar fusionar
git switch main
git merge feature
# ¡Conflicto!
Git marcará el conflicto en el archivo:
<<<<<<< HEAD
Versión main
=======
Versión feature
>>>>>>> feature
Resolver el conflicto:
- Abre el archivo y decide qué versión conservar
- Elimina las marcas de conflicto (
<<<<<<<,=======,>>>>>>>) - Guarda el archivo
- Añádelo al staging:
git add archivo.txt - Completa el merge:
git commit
Herramientas para resolver conflictos:
# Ver archivos con conflictos
git status
# Usar una herramienta de merge visual
git mergetool
# Aceptar todos los cambios de una rama
git checkout --theirs archivo.txt # Rama que estás fusionando
git checkout --ours archivo.txt # Rama actual
Estrategias de branching
Feature Branch (Rama por funcionalidad)
La estrategia más simple: una rama por cada funcionalidad.
main
├── feature/login
├── feature/dashboard
└── bugfix/header-typo
# Crear rama de funcionalidad
git switch -c feature/nueva-funcionalidad
# Desarrollar
git add .
git commit -m "Implementa funcionalidad"
# Fusionar cuando esté lista
git switch main
git merge feature/nueva-funcionalidad
Actividad 1
- Crea una rama llamada
feature/nueva-pagina. - En esa rama, crea un archivo
pagina.htmlcon contenido HTML básico. - Haz commit de los cambios.
- Vuelve a la rama
main. - Fusiona la rama
feature/nueva-paginaenmain. - Elimina la rama
feature/nueva-pagina.
Actividad 2
Práctica de conflictos:
- En la rama
main, edita la primera línea deREADME.md. - Haz commit.
- Crea una rama
conflicto-testdesde un commit anterior. - Edita la misma línea del README con texto diferente.
- Haz commit.
- Intenta fusionar
conflicto-testenmain. - Resuelve el conflicto manualmente.
- Completa el merge.
Este trabajo está bajo una licencia Attribution-NonCommercial-NoDerivatives 4.0 International.
Desafíos de programación atemporales y multiparadigmáticos
Te encuentras ante un librillo de actividades, divididas en 2 niveles de dificultad. Te enfrentarás a los casos más comunes que te puedes encontrar en pruebas técnicas o aprender conceptos elementales de programación.
Comprar el libro