Git básico: commit, branch y merge

1. Descripción general

Git es una herramienta de control de versiones que casi todo proyecto usa, pero muchos principiantes solo recuerdan algunos comandos sin entender su esencia. Este artículo cubre tres conceptos fundamentales: commit (guardar una instantánea), branch (una rama de trabajo independiente) y merge (fusionar ramas). Después de leerlo, serás capaz de crear nuevas ramas con confianza, hacer commits correctamente, resolver conflictos al fusionar y saber cuándo usar merge en lugar de rebase.

2. Commit: guardar una instantánea

2.1. ¿Qué es un commit?

Cada commit es una “fotografía” del estado completo de los archivos en un momento específico, junto con un puntero al commit padre. Git no guarda diffs, sino instantáneas — esto hace que el branching y merging sean rápidos y económicos.

# Inicializar repositorio y hacer el primer commit
git init order-service
cd order-service
echo "# Order Service" > README.md
git add README.md
git commit -m "docs: agregar README para Order Service"

Explicación:

  • git init crea un nuevo repositorio, genera el directorio oculto .git.
  • git add README.md lleva el archivo al staging area (área de buffer esperando commit).
  • git commit -m "..." crea una instantánea con un mensaje que describe el cambio.

Verifica el historial:

git log --oneline
a1b2c3d docs: agregar README para Order Service

2.2. Escribir buenos mensajes de commit

Un commit debe responder a la pregunta “¿qué hace este cambio y por qué?”. Convención común (conventional commits):

feat: agregar API para crear pedidos
fix: corregir cálculo incorrecto del total al aplicar cupón
refactor: extraer OrderValidator de OrderService

Nota: Evita mensajes vagos como "update" o "fix bug". Algunos meses después, al revisar git log, no sabrás qué arreglaba ese commit.

2.3. Hacer commit parcial con git add -p

Cuando un archivo tiene múltiples cambios no relacionados, no los commits todos juntos.

git add -p src/main/java/com/nonladev/OrderService.java

Git te preguntará qué “hunk” (porción de cambios) quieres poner en stage, permitiéndote dividirlos en commits más pequeños, fáciles de revisar y revertir si es necesario.

3. Branch: trabajar independientemente en múltiples ramas

3.1. Crear y cambiar de branch

Una rama es solo un puntero ligero que apunta a un commit específico. Crear una rama nueva no copia todo el código, solo crea otro puntero.

# Crear una nueva rama y cambiar a ella
git branch feature/add-payment
git checkout feature/add-payment

# O combinar los dos comandos anteriores en uno (Git ≥ 2.23)
git switch -c feature/add-payment

Explicación:

  • git branch <nombre> crea la rama pero permaneces en la rama anterior.
  • git checkout <nombre> o git switch <nombre> mueve HEAD a esa rama.
  • -c en git switch es un atajo para crear-y-cambiar al mismo tiempo.

3.2. Ver la lista de ramas

git branch -v
* feature/add-payment  d4e5f6a agregar PaymentService skeleton
  main                 a1b2c3d docs: agregar README para Order Service

El asterisco * indica la rama actual (donde está HEAD).

3.3. Nombrar ramas siguiendo convenciones

Nombrar ramas claramente ayuda a todo el equipo a saber qué hace esa rama:

feature/add-payment       # agregar una nueva característica
fix/order-total-wrong     # corrección de error
refactor/extract-validator
hotfix/prod-null-pointer

3.4. Eliminar rama después de terminar

# Eliminar rama local que ya fue fusionada
git branch -d feature/add-payment

# Eliminar rama no fusionada (fuerza eliminar, cuidado de perder commits)
git branch -D feature/add-payment

-d (minúscula) es más seguro porque Git te advertirá si la rama no se ha fusionado en ningún lugar.

4. Merge: fusionar ramas

4.1. Merge de avance rápido (Fast-forward)

Si main no tiene nuevos commits desde que creaste la rama, Git solo mueve el puntero main al último commit de la rama feature — se llama fast-forward.

git switch main
git merge feature/add-payment
Updating a1b2c3d..d4e5f6a
Fast-forward
 PaymentService.java | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

No se crea un nuevo commit de fusión, el historial sigue siendo una línea recta.

4.2. Commit de fusión (3-way merge)

Si ambas ramas tienen commits propios desde el punto de divergencia, Git crea un merge commit con dos padres que fusiona el contenido de ambas ramas.

git switch main
git commit -am "fix: corregir validación de cantidad de items"   # main tiene nuevo commit
git switch feature/add-payment
git switch main
git merge feature/add-payment
Merge made by the 'recursive' strategy.
 PaymentService.java | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

Git crea automáticamente un commit de fusión con un mensaje predeterminado Merge branch 'feature/add-payment'.

4.3. Resolver conflictos en merge

El conflicto ocurre cuando dos ramas modifican la misma línea de código. Ejemplo en la clase OrderService:

// En main
public class OrderService {
    public BigDecimal calculateTotal(Order order) {
        return order.getSubtotal().multiply(BigDecimal.valueOf(1.1)); // agregar impuesto 10%
    }
}
// En feature/add-payment
public class OrderService {
    public BigDecimal calculateTotal(Order order) {
        return order.getSubtotal().subtract(order.getDiscount()); // restar descuento
    }
}

Al fusionar, Git no puede decidir automáticamente cuál mantener, el archivo mostrará marcadores:

public class OrderService {
    public BigDecimal calculateTotal(Order order) {
<<<<<<< HEAD
        return order.getSubtotal().multiply(BigDecimal.valueOf(1.1));
=======
        return order.getSubtotal().subtract(order.getDiscount());
>>>>>>> feature/add-payment
    }
}

Debes editar manualmente para combinar la lógica correctamente (por ejemplo: restar el descuento primero, luego sumar el impuesto), elimina los marcadores <<<<<<<, =======, >>>>>>>, y luego:

git add OrderService.java
git commit

Nota: El commit después de resolver el conflicto no necesita -m, Git abrirá un editor con un mensaje predeterminado que indica claramente que es un commit de fusión — mantén ese mensaje a menos que quieras agregar notas adicionales.

4.4. Merge vs Rebase

Criterio merge rebase
Historial Mantiene el historial, con commit de fusión Reescribe el historial, rama plana
Seguridad No modifica commits anteriores Modifica commits anteriores (cambia hash)
Cuándo usar Rama compartida públicamente (main, develop) Rama personal, quieres historial limpio antes de crear PR
# Rebasar rama feature sobre el main más reciente
git switch feature/add-payment
git rebase main

Nota: No hagas rebase de una rama que ya pusheaste y otros están usando — causarás conflictos masivos cuando intenten hacer pull de nuevo.

5. Probar un escenario de merge con JUnit

Aunque Git no es código Java, puedes escribir tests para verificar que la lógica de negocio después de fusionar dos ramas no se rompe — esta es la razón por la que CI siempre ejecuta tests en el commit de fusión. Ejemplo de OrderService después de resolver el conflicto en la sección 4.3:

public class OrderService {

    public BigDecimal calculateTotal(Order order) {
        BigDecimal afterDiscount = order.getSubtotal().subtract(order.getDiscount());
        return afterDiscount.multiply(BigDecimal.valueOf(1.1));
    }
}
class OrderServiceTest {

    private final OrderService orderService = new OrderService();

    @Test
    void shouldApplyDiscountThenTax() {
        Order order = new Order(
                BigDecimal.valueOf(100),  // subtotal
                BigDecimal.valueOf(10)    // descuento
        );

        BigDecimal total = orderService.calculateTotal(order);

        assertThat(total).isEqualByComparingTo(BigDecimal.valueOf(99.0));
    }
}

Explicación: subtotal 100 menos descuento 10 quedan 90, multiplicado por impuesto 10% resulta 99. Este test es la “red de seguridad” que asegura que fusionar las dos lógicas (impuesto y descuento) no se anulen mutuamente.

✓ shouldApplyDiscountThenTax()

6. Flujo de trabajo sugerido para equipos pequeños

  1. Siempre crea una rama nueva desde main para cada tarea: git switch -c feature/xxx.
  2. Commits pequeños, frecuentes, con mensajes claros.
  3. Antes de crear un Pull Request, actualiza la rama con el main más reciente:
    git switch feature/add-payment
    git fetch origin
    git merge origin/main
  4. Resuelve conflictos (si los hay) localmente y vuelve a ejecutar los tests.
  5. Haz push y crea un PR, deja que CI ejecute tests en el commit de fusión antes de fusionar en main.

Nota: No hagas commits directamente en main cuando trabajes en equipo — siempre usa rama + PR para que haya revisión y CI verifique primero.

7. Errores comunes

  • Olvidar git add antes de hacer commit: Git dice nothing to commit, working tree clean aunque hayas modificado archivos — porque los archivos no están en stage.
  • Fusionar la rama equivocada: siempre ejecuta git status y git branch para verificar dónde estás antes de hacer merge.
  • Marcadores de conflicto olvidados en el código: olvidar eliminar <<<<<<< hará que el código no compile — siempre compila/prueba inmediatamente después de resolver conflictos.
  • Hacer force push en rama compartida: git push --force después de rebasar una rama compartida borrará el historial de tus compañeros. Solo usa --force-with-lease y solo en ramas personales.

8. Conclusión

Commit, branch y merge son las tres operaciones que usas diariamente con Git. Dominar commit ayuda a mantener un historial de código claro, las ramas ayudan a trabajar en paralelo sin interferirse, y entender merge (junto con resolver conflictos) te da confianza al fusionar código de múltiples personas. Practica commits pequeños, usa convenciones de nombres para ramas, y siempre ejecuta tests antes de fusionar en main.

gitversion-controlbranchmergedevops