Fusionando PRs apilados en la rama base
Úsalo cuando varios PRs están encadenados (PR N basado en la rama del PR N-1) y necesitan llegar a la rama base real. El principio clave: el CI de la rama intermedia no es la puerta de entrada. La protección de la rama base sí lo es. Así que redirige cada capa a la base justo a tiempo y deja que el CI nativo la valide. Nunca confíes en codificar nombres de ramas intermedias en los desencadenadores de CI.
Repositorio: pasa -R owner/repo a gh, o ejecuta desde el clon (gh repo view --json nameWithOwner -q .nameWithOwner
lo resuelve). Abajo, R es ese owner/repo.
Principios
- El orden de fusión está dictado por dependencias reales (dependencias de contenido + archivos editados por más de una capa). Establece ese orden primero; no lo reordenes para que sea conveniente.
- Solo fusiona una capa en la rama base, una a la vez. Nunca fusiones un hijo en su antigua rama prerequisito.
- Antes de que importe el CI de una capa, fusiona la base actual en esa capa para que su diff se reduzca a solo su propio cambio y vea todo lo ya fusionado.
- La rama punta contiene cada capa de forma acumulativa; una ejecución de CI en la rama punta (redirigida a la base) valida toda la pila ensamblada. Úsala como un pre-vuelo opcional.
Paso 0: establecer el orden y detectar conflictos
Resuelve la base real del repositorio (rama predeterminada del remoto git, p. ej. develop o main; el archivo de instrucciones del agente
(AGENTS.md / CLAUDE.md) puede nombrar bases conocidas). Para cada PR, prueba conflictos contra
la base, no contra su base apilada obsoleta (que se leerá LIMPIA y te engañará):
git fetch origin <base> <head> --quiet
git merge-tree --write-tree origin/<base> origin/<head> | grep -iE 'CONFLICT|changed in both' || echo "clean vs base"
Regla general de orden: contenido prerequisito antes de la puerta que lo necesita (p. ej. limpieza de lint antes de habilitar lint); cuando dos capas editan el mismo archivo, la primera en fusionarse va primero.
Paso 1..N: por capa, en orden
PR=<numero>
gh pr edit $PR -R $R --base <base> # redirige fuera de la rama apilada
# un worktree separado para la rama head mantiene tu checkout principal limpio:
WT=../<repo>-<head> # cualquier ruta fuera de tu checkout principal
git worktree add "$WT" <head> 2>/dev/null || true
git -C "$WT" fetch origin --quiet
git -C "$WT" merge origin/<base> # sincroniza la base; resuelve conflictos (ver abajo)
git -C "$WT" push
gh pr ready $PR -R $R # cuando es la capa activa
gh pr checks $PR -R $R --watch # CI nativo, base=<base>
# solo después de la aprobación de la revisión:
gh pr merge $PR -R $R --squash --delete-branch
# luego pasa a la siguiente capa (su redirección + sincronización de base absorberá esta)
Pilas nativas de GitHub (merge-async)
Un PR creado con la función de pila nativa de GitHub rechaza los pasos clásicos anteriores:
gh pr edit --base:GraphQL: Cannot change the base branch because the pull request is part of a stack. (updatePullRequest)gh pr merge --auto:GraphQL: Auto-merge is not supported for stacked pull requests.gh pr merge:This pull request is part of a stack and must be merged using the asynchronous merge REST API.
Usa la API asíncrona, primero el PR inferior (GitHub redirige el siguiente a la base por sí mismo, dale ~30s):
sha=$(gh pr view $PR -R $R --json headRefOid -q .headRefOid)
u=$(gh api -X PUT repos/$R/pulls/$PR/merge-async -f merge_method=squash -f sha=$sha \
-f merge_action=direct_merge --jq '.details.uuid // .status')
gh api repos/$R/pulls/$PR/merge-async/$u --jq '.status+" "+(.details.message//"")' # espera hasta que no sea "pending"
- Fusionar un PR apilado de esta manera también fusiona cada PR abierto por debajo de él, así que solo fusiona la capa que deseas incorporar.
- Después de cada squash, la siguiente capa a menudo está SUCIA en archivos que ambos tocaron (un ADR, una lista de versiones): fusiona la base en ella, resuelve, haz push, espera al CI, luego fusiona-asíncronamente.
- Las aprobaciones sobreviven a las sincronizaciones de base en repositorios que no descartan revisiones obsoletas (comprueba
required_pull_request_reviews.dismiss_stale_reviewsen la protección de la rama). La puerta real suele ser la aprobación de CODEOWNERS para cada ruta tocada, no el conteo de aprobaciones (pr-approval-gateenumera las brechas). - Para mantener cada PR abierto actualizado con su base entre fusiones, ejecuta un pase de sincronización de base: fusiona la base en cada rama de PR abierto y haz push (fusiones simples, sin force-push).
Resolución de conflictos
- Archivos de flujo de trabajo editados por múltiples capas (flujos de trabajo de compilación reutilizables, un flujo de trabajo orquestador de CI): conserva AMBAS ediciones, no elijas un lado.
- Colisiones de barrido de formato (una capa de barrido de Spotless/Prettier vs cambios de contenido en la
base): toma el CONTENIDO de la base para el archivo en conflicto, luego vuelve a ejecutar el formateador sobre él
(
mvn -pl <modulo> -am spotless:applyopnpm fmt). Tomar el lado del barrido descarta cambios reales; tomar la base sin reformatear falla la puerta de formato. Las ejecuciones del formateador necesitan que la compilación se resuelva (algunos repositorios obtienen dependencias de un registro privado que requiere un inicio de sesión en la nube primero; actualízalo si la compilación no puede obtener dependencias).
Seguridad
- No hagas force-push, reescribas historial ni hagas push a ramas protegidas.
- Sigue las convenciones de autoría de commits del repositorio (algunos prohíben trailers de coautoría de IA).
- Pausa para la aprobación de la revisión humana antes de cada fusión en la rama base.
- Si existe un PR de lista de permitidos de CI de rama intermedia (el antipatrón que esta habilidad reemplaza), recomienda cerrarlo; si ya se fusionó, revierte los nombres de rama y tipos de evento añadidos.
Relacionado
opening-a-stacked-pr-sequence: abrir la pila (bases, orden de fusión) y la corrección de reordenamiento por tipo de fusión (stack.sh restack).pr-approval-gate: qué propietarios de código necesita aún cada capa.resolve-pr-threads-and-conflicts-and-merge: fusionar una capa una vez que sus hilos y aprobaciones están listos.