Git Flow: o que aprendi quando os branches dos times se misturaram
Um caso real de branches misturados, os problemas ao subir para a release e um exemplo de Git Flow, com seus benefícios e limites.
Há um tempo, na empresa onde eu trabalho, vi na prática o que acontece quando um time decide que o fluxo de branches é só uma sugestão.
Eu já tinha lido sobre Git Flow. Mas ler é uma coisa. Ter que desatar o nó de quem não seguiu é outra.
Bora contar o que aconteceu.
O que aconteceu
Existia uma ferramenta interna de automação, usada por vários times no mesmo repositório. Cada time tinha o seu branch, e existia um branch de release comum a todos. O combinado era simples:
- Você sobe o seu trabalho para o branch do seu time.
- Quando está pronto, o time promove esse branch para a release.
- Os outros times puxam a release para os seus próprios branches.
A release era o ponto de encontro. Tudo passava por ela.
Só que, no dia a dia, alguns times começaram a fazer outra coisa: mergear direto de um branch de time para o outro. Parecia prático. Eu preciso do que o outro time fez, então puxo daqui e pronto.
Funcionou. Por um tempo.
Quando a conta chegou
Veio um comunicado da liderança pedindo para mostrar os números de uso da ferramenta. Era hora de subir para a release.
E foi aí que apareceram vários problemas ao mesmo tempo. Inclusive com uma pessoa do meu time, e sobrou para mim resolver.
Ao investigar, encontrei o padrão: os merges entre times tinham pulado a promoção combinada, e isso contribuiu para a confusão. A release já não refletia com clareza o que cada time tinha de fato entregue.
Por que misturar branches de times dá errado
O mecanismo é sempre o mesmo.
Quando o time A faz merge no branch do time B, o código do A entra no B do jeito que estava. Mesmo que o A ainda não tenha promovido nada para a release. Mesmo que ninguém tenha revisado.
Depois o time B promove o seu branch. Junto vai o código do A, por um caminho que ninguém previu.
Complica, né?
O Git preserva os commits nesse caminho. Mas três riscos aparecem ao mesmo tempo:
- Rastreabilidade. Fica difícil responder de qual time veio cada mudança e quando ela foi promovida.
- Conflitos. Branches que evoluíram separadamente podem ter alterações incompatíveis, e o merge exige resolução na hora errada.
- Reversão. Se uma mudança precisa voltar, ela já está misturada com o resto.
No fim do dia, a release deixou de ser o ponto de encontro. Virou o lugar onde a bagunça aparecia.
Como o Git Flow resolve isso
O Git Flow foi descrito por Vincent Driessen em 2010, no artigo A successful Git branching model. A ideia central é dar a cada branch uma função.
main: o código pronto para produção, normalmente cada versão publicada. No artigo original, esse branch se chamamaster.develop: o que está sendo integrado para a próxima versão.feature/*: uma funcionalidade, criada a partir dodevelop.release/*: a preparação de uma versão, com correções e ajustes da própria versão.hotfix/*: correção urgente, criada a partir damain.
Repare que não existe caminho de uma feature para outra feature. Tudo passa pelo ponto de integração. Era exatamente isso que o pessoal tinha pulado.
O nosso fluxo não era o Git Flow ao pé da letra. O branch do time fazia um papel parecido com o do develop. E a release do nosso caso era permanente e compartilhada por todos os times, bem diferente do release/* temporário do Git Flow.
Mas o princípio era o mesmo: um ponto central, e todo mundo passa por ele.
Exemplo prático: o fluxo do caso
Imagina que você vai criar a funcionalidade de exportar relatório, no time-a. É um exemplo inventado, só para mostrar o caminho.
Primeiro, você cria a feature a partir do branch do seu time:
git checkout time-agit pull origin time-agit checkout -b feature/exportar-relatorioTrabalha, commita e abre o merge request para o time-a. Depois do review, o merge:
git checkout time-agit merge --no-ff feature/exportar-relatoriogit push origin time-aO --no-ff mantém o registro de que aquele conjunto de commits era uma feature. Isso ajuda muito na hora de reverter.
Quando o time decide que está pronto, vem a promoção para a release. Na prática isso é um merge request, mas o comando equivalente é:
git checkout releasegit pull origin releasegit merge --no-ff time-agit push origin releaseE se o time-b precisa do que o time-a fez? Ele puxa da release, depois da promoção:
git checkout time-bgit pull origin time-bgit fetch origingit merge origin/releasegit push origin time-bO git fetch origin não é enfeite. O git pull origin time-b atualiza o seu branch, mas não garante que o origin/release local esteja em dia. Sem o fetch, você pode fazer o merge com uma referência antiga.
O que não pode acontecer é este comando, rodando dentro do time-b:
git merge origin/time-a # não faça issoFoi esse tipo de merge que encontrei ao investigar o problema que contei.
Quando vale usar o Git Flow
Na minha experiência, ele faz sentido quando:
- Existem versões bem definidas, com número e data de lançamento.
- Você precisa manter versões antigas, aplicando correção em mais de uma.
- Há um processo de homologação entre o código pronto e a produção.
- Vários times mexem no mesmo código e precisam de um ponto de integração controlado.
Repare que o quarto item sozinho não basta. Trunk-based também atende vários times. O que pesa a favor do Git Flow é o ciclo de versões e o suporte em paralelo.
O caso que contei tinha vários times, uma ferramenta compartilhada e uma release como ponto de encontro. Sem disciplina, o fluxo vira decoração.
Quando é excesso de engenharia
Agora o outro lado, porque Git Flow não é para todo mundo.
Este blog, por exemplo, tem um único autor. Tudo vai direto para a main, e a main dispara o deploy automático. Não existe develop, não existe release, e não faria sentido existir.
Se eu criasse todos esses branches aqui, estaria só criando cerimônia.
O próprio Driessen reconheceu isso. Em 2020, ele adicionou uma nota ao artigo original dizendo que, para software com entrega contínua, um fluxo mais simples pode ser melhor. Vale a leitura do GitHub Flow e do Trunk Based Development.
Eu diria que é excesso de engenharia quando:
- Você entrega várias vezes por dia, e a “release” é só o próximo deploy.
- O time é pequeno e todo mundo enxerga o que o outro está fazendo.
- Só existe uma versão em produção, sem suporte a versões antigas.
Minha experiência é com os dois extremos: vários times com uma ferramenta compartilhada, e um projeto pessoal. Do meio-termo, ainda estou aprendendo.
Conclusão
Por fim, o que eu levo desse caso é que o fluxo não é burocracia, é combinado. Ele só funciona se todo mundo respeitar, inclusive naquele dia em que pular a etapa parece mais rápido.
Mergear entre branches de times parecia economizar tempo. Custou muito mais na hora da release.
Antes de escolher Git Flow, GitHub Flow ou trunk-based, escolha o que o seu time consegue seguir de verdade. E me conta: qual fluxo de branches o seu time usa hoje, e quantos já pularam a etapa?
Continue lendo
Git submodules: o que são, como usar e quando vale a pena
O que são os submódulos do Git, por que existem, exemplos de uso com comandos, prós e contras, projetos reais no GitHub e um quiz para decidir se vale a pena.
O que você precisa saber sobre o `git stash`
O git stash é uma maneira de guardar temporariamente suas mudanças enquanto você trabalha em outro lugar. É como colocar suas alterações em uma caixa...
[shorts] Como remover todas as branches locais de forma automatizada usando PowerShell no Windows
Ao trabalhar com repositórios Git, é comum acumular diversas branches locais que já não são mais necessárias. Remover essas branches manualmente pode...