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.

Iago Frota
git

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:

  1. Você sobe o seu trabalho para o branch do seu time.
  2. Quando está pronto, o time promove esse branch para a release.
  3. 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:

  1. Rastreabilidade. Fica difícil responder de qual time veio cada mudança e quando ela foi promovida.
  2. Conflitos. Branches que evoluíram separadamente podem ter alterações incompatíveis, e o merge exige resolução na hora errada.
  3. 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.

  1. main: o código pronto para produção, normalmente cada versão publicada. No artigo original, esse branch se chama master.
  2. develop: o que está sendo integrado para a próxima versão.
  3. feature/*: uma funcionalidade, criada a partir do develop.
  4. release/*: a preparação de uma versão, com correções e ajustes da própria versão.
  5. hotfix/*: correção urgente, criada a partir da main.

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:

Terminal window
git checkout time-a
git pull origin time-a
git checkout -b feature/exportar-relatorio

Trabalha, commita e abre o merge request para o time-a. Depois do review, o merge:

Terminal window
git checkout time-a
git merge --no-ff feature/exportar-relatorio
git push origin time-a

O --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 é:

Terminal window
git checkout release
git pull origin release
git merge --no-ff time-a
git push origin release

E se o time-b precisa do que o time-a fez? Ele puxa da release, depois da promoção:

Terminal window
git checkout time-b
git pull origin time-b
git fetch origin
git merge origin/release
git push origin time-b

O 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:

Terminal window
git merge origin/time-a # não faça isso

Foi 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:

  1. Existem versões bem definidas, com número e data de lançamento.
  2. Você precisa manter versões antigas, aplicando correção em mais de uma.
  3. Há um processo de homologação entre o código pronto e a produção.
  4. 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:

  1. Você entrega várias vezes por dia, e a “release” é só o próximo deploy.
  2. O time é pequeno e todo mundo enxerga o que o outro está fazendo.
  3. 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?

#git #git-flow #branches #versionamento #engenharia