Integrando PRs empilhados no branch base
Use quando vários PRs estão encadeados (PR N baseado no branch do PR N-1) e precisam chegar ao branch base real. O princípio chave: o CI do branch intermediário não é o portão. A proteção do branch base é. Então, redirecione cada camada para o base bem a tempo e deixe o CI nativo validá-lo. Nunca dependa de nomes de branches intermediários codificados em gatilhos de CI.
Repositório: passe -R owner/repo para gh, ou execute a partir do clone (gh repo view --json nameWithOwner -q .nameWithOwner
resolve). Abaixo, R é esse owner/repo.
Princípios
- A ordem de mesclagem é ditada por dependências reais (dependências de conteúdo + arquivos editados por mais de uma camada). Estabeleça essa ordem primeiro; não reordene para ser conveniente.
- Apenas mescle uma camada no branch base, uma de cada vez. Nunca mescle um filho no seu antigo branch pré-requisito.
- Antes que o CI de uma camada seja relevante, mescle o base atual nessa camada para que seu diff se reduza a apenas sua própria mudança e ela veja tudo já integrado.
- O branch de ponta contém cada camada cumulativamente; uma execução de CI na ponta (redirecionada para o base) valida toda a pilha montada. Use-a como uma verificação prévia opcional.
Passo 0: estabelecer ordem e detectar conflitos
Resolva o base real do repositório (branch padrão do remote git, ex.: develop ou main; o arquivo de instruções do agente do usuário
(AGENTS.md / CLAUDE.md) pode nomear bases conhecidas). Para cada PR, teste conflitos contra o
base, não contra seu base empilhado obsoleto (que lerá LIMPO e enganará você):
git fetch origin <base> <head> --quiet
git merge-tree --write-tree origin/<base> origin/<head> | grep -iE 'CONFLICT|changed in both' || echo "limpo vs base"
Regra prática de ordem: conteúdo pré-requisito antes do portão que precisa dele (ex.: limpeza de lint antes de habilitar lint); quando duas camadas editam o mesmo arquivo, a primeira mescla primeiro.
Passo 1..N: por camada, em ordem
PR=<numero>
gh pr edit $PR -R $R --base <base> # redirecionar do branch empilhado
# uma worktree separada para o branch head mantém seu checkout principal limpo:
WT=../<repo>-<head> # qualquer caminho fora do seu checkout principal
git worktree add "$WT" <head> 2>/dev/null || true
git -C "$WT" fetch origin --quiet
git -C "$WT" merge origin/<base> # sincronizar o base; resolver conflitos (veja abaixo)
git -C "$WT" push
gh pr ready $PR -R $R # quando é a camada ativa
gh pr checks $PR -R $R --watch # CI nativo, base=<base>
# após aprovação da revisão apenas:
gh pr merge $PR -R $R --squash --delete-branch
# então vá para a próxima camada (seu redirecionamento+sincronização de base absorverá esta)
Pilhas nativas do GitHub (merge-async)
Um PR feito com o recurso de pilha nativa do GitHub recusa os passos clássicos acima:
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.
Use a API assíncrona, primeiro o PR inferior (o GitHub redireciona o próximo para o base sozinho, aguarde ~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//"")' # poll até não ser "pending"
- Mesclar um PR empilhado dessa forma também mescla cada PR aberto abaixo dele, então mescle apenas a camada que você pretende integrar.
- Após cada squash, a próxima camada geralmente fica SUJA em arquivos que ambos tocaram (um ADR, uma lista de versões): mescle o base nela, resolva, envie, aguarde o CI, então faça merge-async.
- Aprovações sobrevivem a sincronizações de base em repositórios que não descartam revisões obsoletas (verifique
required_pull_request_reviews.dismiss_stale_reviewsna proteção de branch). O portão real é frequentemente a aprovação de CODEOWNERS para cada caminho tocado, não a contagem de aprovações (pr-approval-gatelista as lacunas). - Para manter cada PR aberto atualizado com seu base entre as integrações, execute uma passagem de sincronização de base: mescle o base em cada branch de PR aberto e envie (merges simples, sem force-push).
Resolução de conflitos
- Arquivos de workflow editados por múltiplas camadas (workflows de build reutilizáveis, um workflow orquestrador de CI): mantenha AMBAS as edições, não escolha um lado.
- Colisões de varredura de formatação (uma camada de aplicação de Spotless/Prettier vs mudanças de conteúdo no
base): pegue o CONTEÚDO do base para o arquivo em conflito, então reexecute o formatador nele
(
mvn -pl <module> -am spotless:applyoupnpm fmt). Pegar o lado da varredura descarta mudanças reais; pegar o base sem reformatar falha no portão de formatação. Execuções do formatador precisam do build para resolver (alguns repositórios puxam dependências de um registro privado que precisa de login em nuvem primeiro; atualize-o se o build não conseguir buscar dependências).
Segurança
- Não faça force-push, reescreva histórico ou envie para branches protegidos.
- Siga as convenções de autoria de commit do repositório (alguns proíbem trailers de co-autoria de IA).
- Pause para aprovação de revisão humana antes de cada merge no branch base.
- Se existir um PR de lista de permissões de CI para branch intermediário (o antipadrão que esta skill substitui), recomende fechá-lo; se já foi mesclado, reverta os nomes de branch e tipos de evento adicionados.
Relacionados
opening-a-stacked-pr-sequence: abrir a pilha (bases, ordem de merge) e o reajuste por tipo de merge (stack.sh restack).pr-approval-gate: quais code owners cada camada ainda precisa.resolve-pr-threads-and-conflicts-and-merge: integrar uma camada uma vez que seus comentários e aprovações estão resolvidos.