Como implementar PRs empilhados (Stacked PRs) na prática para quando PRs gigantes atrasam as revisões em semanas
Com a introdução de ferramentas de IA, a velocidade de geração de código aumentou drasticamente. No entanto, a velocidade de revisão não acompanhou esse ritmo. De acordo com o relatório DORA (DevOps Research and Assessment) de 2024, equipes com alta taxa de adoção de IA viram o tamanho dos PRs aumentar em média 154% e o tempo de revisão subir 91%. Como resultado, o ciclo de lançamento ficou ainda mais lento. Quando alguém recebe mais de 1.000 linhas de código para revisar de uma só vez, os olhos de qualquer pessoa ficam cansados. Para acabar com essa bagunça, precisamos migrar para uma estrutura de cadeia de PRs empilhados (Stacked PRs), onde o código é construído em camadas.
Dividindo um bloco de 1.000 linhas em 3 branches
Cortar o código aleatoriamente fará com que a compilação falhe ou as dependências quebrem. É necessário um critério claro. Limite um único PR a no máximo 200 linhas de código alteradas e menos de 10 arquivos modificados, permitindo que o revisor termine em 15 minutos. Divida uma funcionalidade grande em uma cadeia de até 3 branches filhas:
- Layer 1: Schema de banco de dados, Entity, DTO
- Layer 2: Lógica de domínio, implementação de serviço
- Layer 3: Controller, endpoints de API
Siga esta ordem rigorosamente. Fixe também os nomes dos branches no formato feature/<nome-da-funcionalidade>/stack-<etapa>-<descricao>. O processo de decompor um branch de 1.000 linhas já emaranhado (legacy-giant-branch) em uma cadeia é simples.
Primeiro, atualize o branch main e faça backup do original.
`bash
git checkout main
git pull origin main
git checkout legacy-giant-branch
git branch backup/legacy-giant-branch
`
Em seguida, crie o branch Layer 1 a partir de main e aplique via cherry-pick apenas os commits relacionados ao schema.
`bash
git checkout -b feature/order-refactor/stack-1-schema main
git cherry-pick
git push origin feature/order-refactor/stack-1-schema
`
Agora, usando o Layer 1 como pai, você pode estender o Layer 2 e o Layer 3 sequencialmente.
`bash
git checkout -b feature/order-refactor/stack-2-service feature/order-refactor/stack-1-schema
git cherry-pick
git push origin feature/order-refactor/stack-2-service
git checkout -b feature/order-refactor/stack-3-controller feature/order-refactor/stack-2-service
git cherry-pick
git push origin feature/order-refactor/stack-3-controller
`
A escolha da ferramenta também é um fator a se considerar. O CLI puro do Git não custa dinheiro, mas exige muito trabalho manual, enquanto ferramentas dedicadas como o Graphite são convenientes, mas geram custos e dependência de infraestrutura.
| Critério de Avaliação |
Pure Git CLI |
GitHub CLI (gh stack) |
Graphite / External Tools |
| Rastreamento de Dependências |
Configuração manual da Base do branch |
.git local e metadados do GitHub |
Engine remota separada e Banco de Dados local |
| Automação de Rebase |
Trabalho manual em cada branch |
Execução do comando gh stack rebase |
Tratamento automático da cadeia inferior ao alterar commits |
| Curva de Aprendizado |
Alta (Necessário entender os princípios do Git) |
Baixa (Instalação de extensão CLI) |
Média (Necessário se adaptar a uma UI dedicada) |
| Estabilidade Operacional |
100% compatível com o Git padrão |
Em estado de Preview público |
Muito alta |
Evitando conflitos de rebase quando o PR pai é modificado
Ao usar PRs empilhados, o momento mais doloroso é quando surge um pedido de alteração no PR pai. Como o Hash do commit muda, todos os branches filhos anexados abaixo quebram. Se você simplesmente executar um git rebase, os commits serão duplicados. É preciso especificar o intervalo a ser recortado usando a sintaxe git rebase --onto.
Se o Layer 1 foi modificado, vá para o Layer 2 e desloque a partir do ponto de commit do Layer 1 antes da modificação até o HEAD atual, colocando-o em cima do ponto final do novo Layer 1.
`bash
git checkout feature/order-refactor/stack-2-service
git rebase --onto feature/order-refactor/stack-1-schema feature/order-refactor/stack-2-service
git push --force-with-lease
`
Mova o Layer 3 para cima do novo Layer 2 da mesma forma.
`bash
git checkout feature/order-refactor/stack-3-controller
git rebase --onto feature/order-refactor/stack-2-service feature/order-refactor/stack-3-controller
git push --force-with-lease
`
Se você fizer esse trabalho manualmente todas as vezes, os humanos com certeza cometerão erros. Crie um script shell para automatizar o rebase sequencial.
`bash
#!/usr/bin/env bash
set -e
STACK_BRANCHES=(
"feature/order-refactor/stack-1-schema"
"feature/order-refactor/stack-2-service"
"feature/order-refactor/stack-3-controller"
)
TARGET_BASE="main"
git fetch origin
CURRENT_PARENT="$TARGET_BASE"
for BRANCH in "STACKBRANCHES[@]";dogitcheckout"BRANCH"
OLD_BASE=(gitmerge−base"CURRENT_PARENT" "BRANCH"2>/dev/null∣∣echo"")if[−n"OLD_BASE" ]; then
git rebase --onto "CURRENTPARENT""OLD_BASE" "BRANCH"∣∣exit1figitpush−−force−with−leaseorigin"BRANCH"
CURRENT_PARENT="$BRANCH"
done
`
Não se esqueça de ativar a função de histórico de resolução de conflitos nas configurações do Git. Ela lidará automaticamente com conflitos repetitivos.
`bash
git config --global rerere.enabled true
git config --global rerere.autoupdate true
git config --global rebase.autoStash true
git config --global rebase.updateRefs true
`
Bloqueando o desperdício de custo de CI runners e a inversão na ordem de merge
Dividir um PR em 3 gera o problema de rodar os CI runners 3 vezes mais. Não há necessidade de rodar testes de E2E pesados em todos os PRs. Configure o ambiente do GitHub Actions para rodar apenas testes unitários robustos nas camadas inferiores e realizar a validação de integração completa apenas no PR mais alto.
Adicione uma condicional de posição da stack em .github/workflows/ci.yml.
`yaml
name: Stack-Aware CI Pipeline
on:
pull_request:
branches: [ main ]
jobs:
fast-lint-and-unit-test:
name: Fast Validation (All Layers)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Linter & Unit Tests
run: |
npm ci
npm run test:unit
heavy-integration-test:
name: Full E2E Test (Top Layer Only)
runs-on: ubuntu-latest
if: |
github.event.pull_request.stack == null ||
github.event.pull_request.stack.position == github.event.pull_request.stack.size
steps:
- uses: actions/checkout@v4
- name: Run Heavy Integration & E2E Tests
run: |
npm ci
npm run test:e2e
`
Definir regras de proteção de branch em nível organizacional também é essencial.
- Ative a funcionalidade GitHub Merge Queue para forçar que os PRs empilhados aprovados sejam mesclados em ordem.
- Ative 'Require Linear History' em Repository Rulesets para evitar commits de merge residuais.
- Use 'Require Status Checks to Pass Before Merging' para evitar o incidente de misturar um PR superior no main antes que o PR inferior seja mesclado.
Cultura de revisão de equipe e como lidar com quebras de UI após o Squash Merge
Para executar PRs empilhados corretamente, os revisores precisam conseguir ver o panorama geral. Especifique o mapa da stack em .github/PULL_REQUEST_TEMPLATE.md.
`markdown
Stack Overview
Este PR faz parte da cadeia da Stack de Refatoração de Pedidos.
- #101 - feature/order-refactor/stack-1-schema (DB Migration)
- [Current PR] #102 - feature/order-refactor/stack-2-service (Business Logic)
- #103 - feature/order-refactor/stack-3-controller (API Controller)
Current Layer Scope
- Implementação da lógica de alteração de status de pagamento dentro de OrderService
- Processamento de emissão de Domain Event e adição de testes de integração com Repository
Dependency Status
- Base PR: #101 (Must be merged first)
`
Depois que o PR inferior #101 entra no main via Squash Merge, o Diff da UI do GitHub do PR filho #102 explode de repente. Esse é o fenômeno em que até o código de #101 que já foi mesclado é capturado como alteração do PR filho. Isso ocorre porque os SHAs do commit de Squash e do commit pai mudaram no Grafo de Commits do Git. Não entre em pânico e basta subir apenas os commits do branch filho para cima do main usando a ordem abaixo.
`bash
git log --graph --oneline
git checkout feature/order-refactor/stack-2-service
git fetch origin
git rebase --onto origin/main feature/order-refactor/stack-2-service
git push --force-with-lease origin feature/order-refactor/stack-2-service
`
A tarefa de dividir um PR gigante de 1.000 linhas não é apenas uma questão de lidar com técnicas do Git. É mais um trabalho de engenharia para organizar perfeitamente a forma de revisão de código da equipe e toda a cadeia de implantação. Se você encontrar um branch legado bagunçado, faça um backup primeiro e tente cortá-lo em camadas de 3 etapas. Além de reduzir o cansaço do revisor, você mesmo conseguirá controlar o código com muito mais clareza.