Saltar al contenido

Trunk-Based Development: por qué ganan las branches de corta duración

Cómo el trunk-based development con ramas de vida corta reduce conflictos de merge, acelera la entrega y mantiene main siempre lista para desplegar.

5 min de lectura
Diagrama que compara feature branches de larga duración con las branches de corta duración propias de trunk-based development

Las feature branches de larga duración dan sensación de seguridad. Trabajas de forma aislada, te tomas tu tiempo, evitas romper nada. Pero una semana después intentas hacer merge y terminas medio día resolviendo conflictos. La branch se fue alejando de main mientras trabajabas, y ahora el propio merge es la parte más arriesgada del cambio.

Trunk-based development invierte esta lógica. Todo el mundo hace commit directamente a la rama main (o integra branches de corta duración en uno o dos días). El dolor de la integración desaparece porque apenas hay divergencia. La rama main siempre está lista para desplegar porque cada commit pasa la CI.

Los dos modelos

Existen dos variantes de trunk-based development. Los equipos de hasta una docena de personas, más o menos, pueden hacer commit directamente al trunk. Los equipos más grandes usan branches de corta duración que viven, como máximo, uno o dos días.

shbash
# ❌ Long-lived feature branches — the traditional model
main ─────────────────────────────────────────────
       \                                    /
        feature/auth-redesign ─────────────  (2 weeks)
                 \                  /
                  sub-branch ─────  (1 week)
 
# 3 weeks of divergence, massive merge, manual conflict resolution
shbash
# ✅ Trunk-based: short-lived branches merged within 1-2 days
main ──●──●──●──●──●──●──●──●──●──●──●──●──
       │     │        │  │     │        │
       └─●─┘ └──●──┘  └┘ └─●─┘ └──●──┘
 
# Each branch lives hours to 2 days max
# Small diffs, easy review, minimal conflicts

La métrica clave es la frecuencia de integración. Las branches de larga duración se integran una sola vez, cuando todo el trabajo ya está terminado. Las branches de trunk-based development se integran de forma continua, en pequeños incrementos.

Cómo hacer seguro el trabajo incompleto

La objeción más habitual es: «¿Cómo hago commit de funcionalidades incompletas a main sin romper nada?». La respuesta: feature flags.

tstypescript
// Feature flag implementation — can be as simple as environment config
interface FeatureFlags {
  newCheckoutFlow: boolean;
  betaDashboard: boolean;
  experimentalSearch: boolean;
}
 
function getFeatureFlags(): FeatureFlags {
  return {
    newCheckoutFlow: process.env.FF_NEW_CHECKOUT === 'true',
    betaDashboard: process.env.FF_BETA_DASHBOARD === 'true',
    experimentalSearch: process.env.FF_EXPERIMENTAL_SEARCH === 'true',
  };
}
tsxtsx
// ❌ Keeping half-finished UI in a branch for weeks
// Meanwhile, 5 other developers change the same components
 
// ✅ Merging incomplete work behind a flag — code is on main, hidden from users
function CheckoutPage() {
  const flags = useFeatureFlags();
 
  if (flags.newCheckoutFlow) {
    return <NewCheckout />;  // Work in progress, only visible internally
  }
 
  return <CurrentCheckout />;  // Production users see this
}

El código incompleto llega a producción, pero nunca se ejecuta para los usuarios. Los desarrolladores lo siguen construyendo de forma incremental, haciendo merge de pequeños cambios cada día. Cuando la funcionalidad está lista, simplemente se activa el flag.

Protección de branches y gates de CI

Trunk-based development exige un pipeline de CI sólido. Cada merge debe superar verificaciones automatizadas antes de llegar a main.

ymlyaml
# .github/workflows/ci.yml — required checks for trunk
name: CI
on:
  pull_request:
    branches: [main]
 
jobs:
  quality-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
 
      - name: Install dependencies
        run: npm ci
 
      - name: Type check
        run: npx tsc --noEmit
 
      - name: Lint
        run: npx eslint . --max-warnings 0
 
      - name: Unit tests
        run: npx jest --ci --coverage
 
      - name: Integration tests
        run: npx jest --ci --selectProjects integration
 
      - name: Build
        run: npm run build
ymlyaml
# Branch protection rules (configured in GitHub settings)
# Required for main branch:
#   ✓ Require pull request reviews (1 reviewer)
#   ✓ Require status checks to pass (quality-gate)
#   ✓ Require branches to be up to date before merging
#   ✓ Restrict who can push directly to main

Con estos gates, el código roto no puede llegar a main. La rama main se mantiene siempre en un estado listo para desplegar.

Pull requests pequeños por defecto

Trunk-based development no significa hacer merge de grandes changesets más rápido. Significa dividir el trabajo en changesets más pequeños que tengan sentido cada uno por separado.

shbash
# ❌ One PR that does everything (800 lines changed)
feat: implement user settings page
- Add settings API endpoints
- Create settings form components
- Add form validation
- Update navigation
- Add settings E2E tests
- Update user model with preferences
 
# ✅ Multiple small PRs (each under 200 lines)
# PR 1: Add preferences field to user model + migration
# PR 2: Add GET/PUT /api/settings endpoints
# PR 3: Create SettingsForm component (behind feature flag)
# PR 4: Wire form to API, add validation
# PR 5: Add navigation link (flag-gated)
# PR 6: Enable feature flag, remove old code
shbash
# Stacked PRs workflow — each builds on the previous
git checkout main && git pull
git checkout -b settings/model     # PR 1: schema change
# ... commit, push, open PR
 
git checkout -b settings/api       # PR 2: built on model changes
# ... commit, push, open PR (base: settings/model)
 
git checkout -b settings/ui        # PR 3: built on API changes
# ... commit, push, open PR (base: settings/api)

Los PRs pequeños se revisan más rápido. Un diff de 100 líneas recibe feedback útil en cuestión de horas. Un diff de 800 líneas se queda esperando en la cola durante días y termina recibiendo un «LGTM» de trámite.

Cómo anticiparse a los conflictos de merge

Las branches de corta duración reducen los conflictos, pero no los eliminan. Cuando dos desarrolladores tocan el mismo archivo, conviene hacer pull de main con frecuencia para detectar los conflictos cuanto antes.

shbash
# Pull main into your branch at least once a day
git checkout my-branch
git fetch origin
git rebase origin/main
 
# If conflicts occur, they're small — just today's divergence
# Fix conflicts, then continue
git add .
git rebase --continue
shbash
# ❌ Two-week branch: 15 files conflicted, 3 hours to resolve
# ✅ One-day branch: 1 file conflicted, 5 minutes to resolve

Hacer rebase en lugar de merge mantiene el historial lineal. La rama main se lee como una secuencia de cambios autocontenidos, no como un grafo de merges hecho un espagueti.

Cómo medir el éxito de trunk-based development

Haz seguimiento de estas métricas para comprobar que trunk-based development está funcionando:

tstypescript
// Metrics that indicate healthy trunk-based development
interface TrunkMetrics {
  // How long a branch exists before merging
  branchLifespanHours: number;   // Target: < 48 hours
  
  // Lines changed per PR
  prSize: number;                // Target: < 200 lines
  
  // Time from PR open to merge
  reviewCycleHours: number;      // Target: < 8 hours
  
  // How often main is deployed
  deployFrequency: string;       // Target: daily or more
  
  // Percentage of main commits that pass CI
  greenBuildRate: number;        // Target: > 98%
  
  // Time to fix a broken main
  mainRecoveryMinutes: number;   // Target: < 30 minutes
}
shbash
# Quick check: branches older than 2 days
git for-each-ref --sort=-committerdate refs/remotes/origin \
  --format='%(committerdate:relative) %(refname:short)' \
  | head -20
 
# If you see branches older than 2 days, investigate why
# Common causes: blocked on review, scope too large, unclear requirements

Un equipo que aplica bien trunk-based development tiene un grafo plano: main avanza de forma constante con commits pequeños y frecuentes. Un equipo que lo aplica mal tiene un grafo plano interrumpido de vez en cuando por grandes merges que rompen el build.

La estrategia de transición

Los equipos acostumbrados a branches de larga duración no pueden cambiar de la noche a la mañana. La transición debe ser gradual:

  1. Semana 1-2: impón un máximo de 5 días de vida por branch. Divide las branches largas existentes en fragmentos más pequeños.
  2. Semana 3-4: reduce el máximo a 3 días. Introduce feature flags para el trabajo incompleto.
  3. Semana 5-6: apunta a branches de 1-2 días como objetivo. Exige hacer rebase antes de cada merge.
  4. Semana 7 en adelante: mide las métricas. Ajusta los SLA de revisión para sostener ciclos de merge rápidos.

La infraestructura técnica (gates de CI, feature flags, protección de branches) debe estar lista antes de arrancar la transición. Sin gates de calidad automatizados, hacer commit a main se vuelve arriesgado.

Puntos clave

  1. Las branches de corta duración (1-2 días) eliminan el infierno del merge — diffs pequeños, conflictos pequeños, resolución rápida
  2. Los feature flags hacen seguro el trabajo incompleto en main — el código se despliega, pero no se ejecuta hasta que se activa
  3. Los gates de CI protegen main — tests automatizados, linting y verificación de tipos en cada PR
  4. Los PRs pequeños reciben mejores revisiones — menos de 200 líneas consigue feedback útil, 800 líneas recibe un visto bueno de trámite
  5. Haz rebase de main a diario — detectar los conflictos a tiempo significa resolver un archivo, no quince
  6. Mide la vida útil de las branches — si superan las 48 horas de forma constante, hay que mejorar cómo se descompone el trabajo
Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX