Un stack es una cadena ordenada de pull requests. Cada capa se apoya en la anterior, y solo el pie de la cadena toca main. En lugar de revisar un cambio grande, revisas varios pequeños, cada uno con su propio diff y su propia discusión. Desde el 30 de julio de 2026 GitHub lo hace de forma nativa, como vista previa pública, sin herramientas de terceros.
Este artículo es un tutorial completo construido sobre un ejemplo continuo: series de numeración de facturas por inquilino, cuatro capas, desde la migración de base de datos hasta el proceso nocturno. Recorre la creación del stack, el rebase en cascada, la fusión de abajo hacia arriba y la condición que va unida a todo ello, esa que se pasa por alto en el primer intento.
Contenido
- El problema: una revisión en la que la migración desaparece
- La idea: una cadena en lugar de un bloque
- Requisitos previos
- Paso 1: crear el stack
- Paso 2: la migración como capa inferior
- Paso 3: apilar las capas superiores
- Paso 4: crear los pull requests
- Paso 5: qué se ve en GitHub
- Paso 6: un cambio abajo del todo
- Paso 7: fusionar de abajo hacia arriba
- El flujo completo de una pieza
- Los comandos de un vistazo
- El punto de inflexión: lo que se fusiona abajo está en producción
- Qué hacen CI y branch protection dentro de un stack
- ¿Stacked pull requests o un pull request grande?
- Stacked pull requests sin la extensión gh
- Vista previa pública: qué falta todavía
- Cuándo compensa un stack y cuándo no
- Preguntas frecuentes
- Conclusión
- Fuentes
El problema: una revisión en la que la migración desaparece
La funcionalidad suena inofensiva. Cada inquilino recibe sus propias series de numeración de facturas, correlativas, por año y con su propio prefijo. En términos de dominio son cuatro cosas que se apoyan una en otra: una tabla y una columna en la base de datos, la lógica de asignación, un endpoint para configurar la serie y un proceso nocturno que cubre después las facturas sin número.
Se construye en una sola rama. Al final se ve así:
$ git diff --stat main
87 files changed, 1284 insertions(+), 96 deletions(-)Este pull request entra en revisión y ahí se queda. No por mala fe: quien abre 87 archivos busca por dónde empezar, y la entrada más fácil siempre es aquella en la que se puede decir algo útil rápido. Es decir, los nombres del DTO, el orden de los campos, un final que falta.
La parte arriesgada está en otro sitio. Vive en un único archivo:
databaseChangeLog:
- changeSet:
id: 2026-08-01-numeracion-facturas
author: velaatlas
changes:
- createTable:
tableName: invoice_number_range
columns:
- column:
name: id
type: bigint
autoIncrement: true
constraints: { primaryKey: true }
- column:
name: tenant_id
type: bigint
constraints: { nullable: false }
- column:
name: prefix
type: varchar(16)
constraints: { nullable: false }
- column:
name: current_value
type: bigint
defaultValueNumeric: 0
constraints: { nullable: false }
- addColumn:
tableName: invoice
columns:
- column:
name: invoice_number
type: varchar(32)
constraints: { nullable: false }Esta migración es la única parte del cambio que no se puede deshacer revirtiendo código. Se ejecuta contra una tabla con datos existentes, y nullable: false sin valor por defecto falla ahí, porque las filas existentes no tienen número de factura. Ese error concreto tiene muchas probabilidades de sobrevivir a una revisión de 87 archivos, porque está entre trabajo rutinario.
Así que el problema no es la diligencia de quien revisa. Es el troceado.
La idea: una cadena en lugar de un bloque
Un stack invierte el troceado. En lugar de una rama hay cuatro, y cada una apunta a su predecesora en lugar de a main:
main
└─ numeracion-schema PR 1 migración, columna nullable
└─ numeracion-dominio PR 2 lógica de asignación en el servicio
└─ numeracion-api PR 3 endpoint y DTO
└─ numeracion-job PR 4 proceso nocturno para datos existentesGitHub llama a la cadena stack, a cada eslabón layer y a la rama de destino de abajo del todo trunk. El diff de cada pull request contiene solo el cambio de esa capa, porque su punto de comparación es la rama inferior y no main.
Para la revisión eso cambia el punto de partida. El pull request con la migración contiene un archivo. Quien lo abre no puede hablar de otra cosa.
Para quien escribe el código cambia el orden de las decisiones: el troceado ocurre antes de que exista el primer commit. Ese es el esfuerzo real del asunto, y ninguna herramienta te lo quita.
Requisitos previos
Los stacked pull requests están en vista previa pública desde el 30 de julio de 2026 y se están desplegando en todos los repositorios. Se manejan en github.com, con la CLI de GitHub y en la app móvil. Los agentes de programación como GitHub Copilot acceden a ellos a través del skill gh-stack.
Para el flujo local necesitas la extensión:
gh extension install github/gh-stackRequiere la CLI de GitHub 2.90.0 o superior y Git 2.20 o superior. Comprueba las versiones con:
gh --version
git --versionAdemás conviene tener listo, antes de empezar en equipo: las reglas de branch protection en la rama por defecto y los workflows de Actions que se ejecutan en pull requests contra ella. Ambos aplican dentro del stack, y conviene haberlos visto una vez en un stack de prueba antes de que afecten a una funcionalidad real.
Paso 1: crear el stack
gh stack init crea el stack en local y prepara la primera capa. Sin argumentos, el comando pregunta de forma interactiva por un nombre de rama y ofrece usar la rama actual como primera capa.
git switch main
git pull
gh stack init --base main--base define el trunk, la rama a la que apuntará la capa inferior. En la mayoría de repositorios es main, en algunos es develop. Sin ese flag, init pregunta de forma interactiva por el nombre de la primera capa y ofrece usar la rama actual para ello. En el ejemplo de abajo esa primera capa se llama numeracion-schema.
Paso 2: la migración como capa inferior
Abajo del todo va lo que todo lo demás necesita. Aquí es la migración. Y precisamente porque está abajo tiene que cumplir una propiedad que no necesitaba dentro del pull request grande: tiene que funcionar por sí sola, sin el código de las capas superiores.
Por eso el changeset dentro de un stack se ve distinto al de antes. La tabla queda como estaba, lo único que cambia es el final:
- addColumn:
tableName: invoice
columns:
- column:
name: invoice_number
type: varchar(32)La diferencia es una línea: falta constraints: { nullable: false }, la columna invoice_number ahora es nullable. Con eso la migración pasa contra datos existentes, y el estado posterior es un estado válido del sistema, aunque nunca se añada una fila más.
Commit y listo:
git add src/main/resources/db/changelog/
git commit -m "Tabla de series y número de factura nullable"Paso 3: apilar las capas superiores
gh stack add coloca una rama nueva encima del stack. Con -A los cambios se preparan por el camino, con -m se hace el commit:
gh stack add numeracion-dominioDespués escribes la lógica de asignación:
@Service
class InvoiceNumberService {
private final NumberRangeRepository ranges;
InvoiceNumberService(NumberRangeRepository ranges) {
this.ranges = ranges;
}
@Transactional
String nextNumber(long tenantId, int year) {
NumberRange range = ranges.lockByTenant(tenantId)
.orElseThrow(() -> new NoNumberRangeException(tenantId));
long value = range.increment();
return "%s-%d-%05d".formatted(range.prefix(), year, value);
}
}Y haces commit:
git add src/main/java/
git commit -m "Asignación del número de factura por inquilino"Las dos capas siguientes se crean igual. gh stack add trae una forma corta que prepara, hace commit y crea la rama en un solo paso:
gh stack add numeracion-api -Am "Endpoint para mantener las series de numeración"
gh stack add numeracion-job -Am "Proceso nocturno asigna números a facturas existentes"Un vistazo al estado:
gh stack viewEl comando muestra la cadena con sus ramas, la posición dentro del stack y, en cuanto existen, los pull requests asociados. --short deja la salida escueta, --json la hace legible por máquina.
Paso 4: crear los pull requests
Hasta aquí todo es local. gh stack submit empuja todas las ramas, crea los pull requests que faltan y los enlaza como stack en GitHub:
gh stack submitLa diferencia entre gh stack push, gh stack submit y gh stack sync merece memorizarse una vez:
| Comando | Empuja ramas | Crea los pull requests que faltan | Hace rebase sobre el trunk |
|---|---|---|---|
gh stack push |
sí | no | no |
gh stack submit |
sí | sí | no |
gh stack sync |
sí | nunca | sí |
gh stack sync es el comando del día a día, para cuando main se ha movido. Trae el estado del remoto, adelanta el trunk, hace rebase de la cadena sobre él, empuja las ramas actualizadas y sincroniza el estado de los pull requests. Nunca abre pull requests nuevos, de eso se encarga submit.
Con --auto, submit activa el auto-merge; con --open abre los pull requests en el navegador.
Paso 5: qué se ve en GitHub
En GitHub, cada pull request del stack lleva una vista general de toda la cadena, incluida su propia posición en ella. Quien revisa no necesita ninguna cuenta adicional ni ninguna extensión de navegador para eso, porque la representación forma parte de la propia interfaz de pull requests.
En la práctica significa: quien revisa la migración ve un archivo y, al lado, la indicación de que tres capas más se apoyan en él. Puede juzgar la migración sin haber leído el resto, y aun así ve a qué pertenece.
Quien venga de Graphite o de un servicio parecido reconoce la representación. La diferencia es que aquí no hay ningún servicio adicional entre el repositorio y la revisión, y nadie del equipo tiene que instalar nada para ver la cadena.
Paso 6: un cambio abajo del todo
Ahora llega la parte que se subestima en el primer stack. La revisión de la migración concluye que falta un índice sobre tenant_id. El cambio pertenece abajo del todo, es decir, a la primera capa:
gh stack bottomCon eso saltas a la capa inferior. Haces el cambio y el commit:
git add src/main/resources/db/changelog/
git commit -m "Índice sobre tenant_id"Y después todo lo que está encima tiene que recoger ese cambio:
gh stack rebaseEsto es un rebase en cascada. La cadena se recorre de abajo hacia arriba, y cada capa se apoya en su predecesora ya actualizada. Así es como un cambio hecho abajo del todo llega a todo lo que está por encima. Después se empuja:
gh stack pushAnte un conflicto el rebase se detiene y nombra los archivos afectados. Lo resuelves como siempre y continúas:
git add <archivo>
gh stack rebase --continue--abort cancela y restaura el estado inicial. Con --downstack y --upstack limitas el rebase a la parte por debajo o por encima de la capa actual, con --no-trunk dejas el trunk fuera.
Aquí está el precio real del procedimiento. Si algo cambia abajo del todo, recorre todas las capas superiores, y un conflicto que afecta a cada capa se resuelve otras tantas veces. No es un fallo de la herramienta, se deriva de que las capas se apoyan unas en otras. Es la razón por la que los stacks profundos rara vez son buena idea.
Paso 7: fusionar de abajo hacia arriba
La fusión tiene que ir obligatoriamente de abajo hacia arriba. El pull request con la migración entra primero en main, luego el dominio, luego el endpoint, luego el proceso.
Dos cosas ocurren solas. Cuando se fusiona la capa inferior, GitHub pone el resto de la cadena al día con el nuevo estado de main, y el siguiente pull request apunta después directamente a la rama por defecto. Si fusionas por el medio, todo lo que está encima sigue abierto y se reapunta igualmente.
Con la CLI fusionas una o varias capas a la vez:
gh stack merge --merge-method mergeLa documentación lista los tres métodos, merge commit igual que squash y rebase. Durante la vista previa privada hubo, eso sí, informes de que squash y rebase podían romper la asociación dentro del stack. Ambos reescriben los commits de la capa inferior, y el rebase en cascada se apoya en ellos. Quien quiera usar squash como norma del repositorio, que lo pruebe antes en un stack de prueba. La merge queue llega por separado y todavía no está en todas partes.
Después limpias en local:
gh stack sync --prune--prune elimina las ramas locales de las capas ya fusionadas y rebasa las restantes sobre el trunk actualizado.
El flujo completo de una pieza
Todo el camino desde main hasta cuatro pull requests enlazados, sin prosa por medio:
gh extension install github/gh-stack
git switch main
git pull
gh stack init --base main
# Capa 1: migración
git add src/main/resources/db/changelog/
git commit -m "Tabla de series y número de factura nullable"
# Capas 2 a 4
gh stack add numeracion-dominio -Am "Asignación del número de factura por inquilino"
gh stack add numeracion-api -Am "Endpoint para mantener las series de numeración"
gh stack add numeracion-job -Am "Proceso nocturno asigna números a facturas existentes"
gh stack view
gh stack submitY el día a día posterior, cuando main se ha movido o se añade algo abajo:
gh stack sync # traer el trunk, rebasar la cadena, sincronizar estado
gh stack bottom # saltar abajo del todo
# ... cambio, git commit ...
gh stack rebase # arrastrar el cambio por todas las capas superiores
gh stack push
gh stack merge --merge-method merge
gh stack sync --prune # limpiar ramas locales de las capas fusionadasLos comandos de un vistazo
| Comando | Para qué sirve | Flags destacables |
|---|---|---|
gh stack init |
Crear un stack en el repositorio | --base |
gh stack add |
Nueva capa encima del stack | -A, -u, -m |
gh stack view |
Ver el stack | --short, --json |
gh stack checkout |
Sacar un stack por número, pull request, URL o rama | |
gh stack modify |
Reestructurar, eliminar, combinar o renombrar capas de forma interactiva | --continue, --abort |
gh stack unstack |
Sacar el stack del seguimiento y deshacerlo en GitHub | --local |
gh stack submit |
Empujar ramas, crear o actualizar pull requests | --auto, --open, --remote |
gh stack sync |
Traer, rebasar, empujar, sincronizar estado | --remote, --prune |
gh stack rebase |
Rebase en cascada por todo el stack | --downstack, --upstack, --no-trunk, --continue, --abort |
gh stack push |
Empujar las ramas activas del stack | --remote |
gh stack link |
Enlazar pull requests existentes como stack en GitHub | --base, --open, --remote |
gh stack merge |
Fusionar una o varias capas | --merge-method, --yes |
gh stack switch |
Cambiar de capa de forma interactiva | |
gh stack up / down |
Una capa arriba o abajo | |
gh stack top / bottom / trunk |
Saltar al principio, al final o al trunk | |
gh stack alias |
Crear una forma corta para un comando | --remove |
Reordenar solo es posible a través de la CLI. gh stack modify inserta capas, las quita, las combina o las renombra. Los equipos que no trabajan con la CLI en local deberían fijar el orden de antemano, porque después no pueden reordenar sin más.
El punto de inflexión: lo que se fusiona abajo está en producción
Aquí una herramienta se convierte en una decisión de diseño.
La capa inferior se fusiona primero. Con eso está en main, y main va al siguiente despliegue, mucho antes de que el stack esté terminado. Entre la fusión de la migración y la del proceso nocturno pueden pasar días, y durante ese tiempo en producción corre un estado que nunca existió en el pull request grande: la base de datos conoce la columna nueva, el código no.
De ahí se deriva una condición que vale para cada capa: cada capa tiene que ser ejecutable y segura por sí sola. Una capa no puede depender de algo que llega dos capas más arriba.
Para la migración eso significa exactamente lo que ya mostraba el changeset de antes: crear la columna, dejarla nullable, el campo obligatorio llega después. Para la lógica de asignación significa que puede desplegarse sin que nadie la invoque. Para el endpoint significa que puede quedarse detrás de un feature flag mientras falte el proceso nocturno.
Es la misma disciplina que exige de todos modos una migración de base de datos sin ventana de mantenimiento: primero ampliar, después cambiar, después limpiar. Quien ya la aplica no tiene nada nuevo que aprender para los stacks. Quien no la aplica se da cuenta aquí por primera vez, y ese es el mejor momento posible.
Un stack se limita a hacer visible este problema, adelantándolo del día del despliegue al momento del troceado.
Qué hacen CI y branch protection dentro de un stack
La preocupación evidente es que las comprobaciones solo alcancen a la capa superior y el resto pase sin revisar. No es así.
Cada capa salva los mismos obstáculos que un pull request suelto contra main: arrancan los mismos workflows, tienen que aprobar los mismos CODEOWNER, también en el pull request que está en mitad de la cadena.
Cuatro capas son por tanto cuatro ejecuciones completas de CI, y además por pasada. Cada gh stack sync y cada rebase en cascada vuelve a lanzar las ejecuciones en todas las capas. Con un main movido son cuatro ejecuciones por cada movimiento del trunk, no cuatro en total. Ese es el segundo precio después del rebase, y pesa con pipelines lentas.
Lo mismo vale para las aprobaciones ya dadas. El rebase en cascada reescribe las ramas de las capas superiores, y si en el repositorio está activo «Dismiss stale pull request approvals», las revisiones de ahí desaparecen después. Cuanto más cambie algo abajo, más veces ocurre. Quien trabaje mucho con stacks revisa esa regla una vez a conciencia, antes de que salte en el día a día. Quien introduzca stacks en un repositorio con rulesets existentes debería probar primero un stack de prueba y mirar qué ocurre de verdad, en lugar de deducirlo de las reglas.
¿Stacked pull requests o un pull request grande?
Un stack no reduce el trabajo. Lo reparte de otra forma, y al hacerlo desplaza esfuerzo de quien revisa a quien escribe.
| Un pull request grande | Stack de varias capas | |
|---|---|---|
| Esfuerzo de troceado | ninguno, todo cae en una rama | ocurre antes del primer commit |
| Revisión por unidad | grande, la atención se reparte mal | pequeña, un tema por capa |
| Revisable en paralelo | no, una discusión para todo | sí, varias personas a la vez |
| Reacción a un cambio abajo | un commit más | rebase en cascada por todas las capas |
| Ejecuciones de CI | una | una por capa |
| Estado tras la primera fusión | todo o nada | los estados intermedios van a producción |
| Riesgo en el detalle | un archivo arriesgado desaparece | el archivo arriesgado tiene su propia revisión |
La fila que inclina la balanza es la penúltima. Un pull request grande es una decisión de todo o nada, un stack es una secuencia de decisiones parciales. Eso es una ventaja cuando las partes tienen sentido por sí solas, y un inconveniente cuando no lo tienen.
Stacked pull requests sin la extensión gh
Por debajo no aparece nada nuevo: ramas como siempre, pull requests como siempre. Lo único nuevo es el enlace que GitHub guarda entre ellos, y ese enlace no obliga a ninguna cadena de herramientas concreta.
Quien gestione la cadena en local con otra herramienta, por ejemplo Jujutsu, Sapling o git-town, la engancha en GitHub con un solo comando:
gh stack linkCon eso los pull requests existentes se enlazan como stack sin ceder la gestión local a gh stack. --base define el trunk. Para las ramas que aún no tienen un pull request abierto, link crea borradores.
Esa es también la respuesta para quienes llevan tiempo haciendo el rebase en cascada con Git a secas. git rebase --update-refs arrastra las puntas de rama de una cadena durante el rebase, en lugar de obligarte a moverlas una a una. La extensión tiene que estar instalada de todos modos, pero solo entra en juego para ese único comando, el día a día se queda con la herramienta de siempre.
Vista previa pública: qué falta todavía
El estado tiene cuatro días, y GitHub indica que la funcionalidad todavía puede cambiar. Lo que hoy falta o no funciona, en concreto:
- Un stack termina en el límite del repositorio. Quien trabaje a través de un fork, como en el flujo clásico de código abierto, se queda con el pull request suelto.
- GitHub Desktop no puede. Quien trabaje ahí necesita la CLI o la interfaz web para los stacks.
- El soporte de merge queue sigue desplegándose. Está previsto, pero puede que aún no esté en tu repositorio.
- Reordenar solo funciona con la CLI. Sin ella, fijas el orden de antemano o reconstruyes el stack.
- Los rebases del lado del servidor generan commits sin firmar. Si tu repositorio exige commits firmados, haz el rebase en local con
gh stack rebase, si no la fusión fallará por una regla que no tiene nada que ver con los stacks.
Aun así deberías probarlo. Solo que no justo con la funcionalidad que tiene que salir el viernes.
Cuándo compensa un stack y cuándo no
Un stack compensa en estos casos:
- El cambio tiene capas naturales que se apoyan unas en otras: esquema, dominio, interfaz, operación.
- Distintas partes necesitan distintos revisores, por ejemplo la migración necesita a alguien distinto que el DTO.
- Una parte es claramente más arriesgada que el resto y debe recibir su propia atención.
- Las partes inferiores están terminadas por sí solas y pueden ir a producción mientras arriba se sigue trabajando.
En estos casos no compensa:
- El cambio es una unidad que solo se puede partir de forma artificial. Entonces las capas son gestión sin beneficio.
- Las capas no producen cada una un estado seguro en producción.
- El stack quedaría profundo. Cada capa adicional cuesta otra ejecución de CI y otra ronda de rebase en cascada.
- El equipo trabaja a través de forks, o la merge queue es obligatoria y todavía no está disponible para stacks.
- La funcionalidad tiene que salir entera o nada. Entonces la propiedad de todo o nada de un pull request grande es justo lo que quieres.
Preguntas frecuentes
¿Qué son los stacked pull requests?
Pull requests dependientes dentro del mismo repositorio, organizados como una cadena. El destino de un pull request no es la rama por defecto, sino la rama de la capa que tiene debajo; únicamente la capa inferior apunta al trunk. Cada capa tiene su propio diff y se revisa por separado, y la fusión va de abajo hacia arriba.
¿Necesito la extensión gh?
Para la gestión local y el rebase en cascada sí, para el stack en sí no. Por debajo hay ramas corrientes y pull requests corrientes. Quien trabaje en local con Jujutsu, Sapling o git-town enlaza la cadena en GitHub con gh stack link.
¿Funciona el squash merge en un stack?
La documentación lo lista junto a merge commit y rebase, y con la CLI eliges el método con gh stack merge --merge-method. Durante la vista previa privada hubo informes de que squash y rebase podían romper la asociación del stack, porque ambos reescriben los commits. En un stack de prueba eso se aclara en cinco minutos. La merge queue se despliega todavía por separado.
¿Puedo fusionar primero una capa intermedia?
No de forma aislada. La fusión de una capa intermedia arrastra todo lo que hay debajo, nunca se salta nada. El resto que queda encima puedes dejarlo abierto, se reapunta automáticamente.
¿Las comprobaciones de CI se ejecutan en cada capa?
Sí. Cada capa salva los mismos obstáculos que un pull request suelto contra la rama por defecto, tanto los workflows como las reglas de aprobación. Cuatro capas significan por tanto cuatro ejecuciones de CI, y cada rebase de la cadena las vuelve a lanzar.
¿Funcionan los stacks entre forks?
No, un stack termina en el límite del repositorio. Para el flujo a través de un fork solo queda el pull request suelto.
¿Cuántas capas puede tener un stack?
GitHub no indica ningún límite superior. El límite práctico se deriva del esfuerzo: cada capa cuesta su propia ejecución de CI, y un cambio abajo del todo recorre como rebase todas las capas superiores. Los conflictos se resuelven entonces otras tantas veces.
¿Y los commits firmados?
Los rebases del lado del servidor generan commits sin firmar. Si tu repositorio exige firmas, haz el rebase en local con gh stack rebase antes de empujar.
Conclusión
Cuatro revisiones pequeñas resultan más agradables que una grande, pero ahí no está el valor de un stack. Está en la pregunta que hay que responder antes de que exista el primer commit: ¿es cada capa segura por sí sola?
Quien pueda responder a esa pregunta ha entendido el cambio. Sin una respuesta, el cambio tampoco se habría entregado limpio como un pull request grande, solo se habría notado más tarde.
Siguiente paso concreto: no cojas la próxima funcionalidad grande, coge un cambio que ya divides mentalmente en dos de todos modos. Instala la extensión, crea dos capas, envíalas con gh stack submit y fusiónalas de abajo hacia arriba. Todo el recorrido se hace en media hora, y después sabes si el troceado aporta algo en tu repositorio.
Fuentes
- GitHub Changelog: Stacked pull requests are now in public preview, 30 de julio de 2026
- GitHub Docs: About stacked pull requests
- GitHub Docs: Quickstart for stacked pull requests
- GitHub Docs: Stacked pull requests CLI commands
- GitHub Docs: Roll out stacked pull requests to your organization
- GitHub Docs: Use other tools with stacked pull requests
Todos los ejemplos de código son propios. La funcionalidad de ejemplo (series de numeración de facturas por inquilino) está construida para el artículo, la mecánica descrita está verificada contra las fuentes indicadas arriba, a agosto de 2026.