Arquitetura de Branches: Main, Versões e Funcionalidades
A base de uma boa estratégia com Git começa pela definição clara de como os branches (ramificações) serão utilizados dentro do projeto. Sem essa organização, o repositório rapidamente se torna caótico, dificultando manutenção, testes e até mesmo a identificação de bugs.
Na abordagem que vamos adotar — e que é extremamente sólida do ponto de vista profissional — o branch main representa sempre o ambiente de produção. Isso significa que tudo que está presente nele deve ser considerado estável, testado e pronto para uso real. Em sistemas embarcados, por exemplo, isso equivale ao firmware que está efetivamente rodando no dispositivo em campo.
Para garantir essa estabilidade, uma regra fundamental é estabelecida: nenhuma funcionalidade é desenvolvida diretamente no main. Em vez disso, todo novo desenvolvimento ocorre em branches derivados dele.
Criação de Branches a partir do Main
Sempre que uma nova funcionalidade ou versão precisa ser iniciada, o ponto de partida é o main. Isso garante que o novo desenvolvimento esteja baseado na versão mais estável do sistema.
O fluxo típico começa com a atualização do repositório local:
git checkout main
git pull origin main
Aqui, o comando git checkout main muda o contexto de trabalho para o branch principal, enquanto o git pull origin main garante que você está com a versão mais recente disponível no repositório remoto.
A partir disso, cria-se um novo branch:
git checkout -b feature/nome-da-funcionalidade
Esse comando cria e já muda automaticamente para o novo branch. A convenção feature/ é opcional, mas altamente recomendada, pois ajuda a organizar visualmente o repositório.
Se a mudança for maior, envolvendo uma versão completa do sistema, pode-se usar algo como:
git checkout -b release/v1.2.0
Perceba que aqui já estamos introduzindo um padrão importante: branches de versão (release) e branches de funcionalidade (feature).
Branch por Funcionalidade vs Branch por Versão
A estratégia que você descreveu combina dois níveis de organização:
O primeiro nível é o branch de funcionalidade, que representa uma alteração específica no sistema. Isso pode ser, por exemplo, a implementação de um driver, uma nova task no FreeRTOS ou uma mudança no protocolo de comunicação.
O segundo nível é o branch de versão, que agrega várias funcionalidades que farão parte de uma futura liberação do sistema.
Na prática, isso permite algo extremamente poderoso: você pode desenvolver várias funcionalidades em paralelo, testá-las individualmente e depois decidir quais entram em determinada versão.
Por exemplo:
git checkout -b feature/adc-dma-improvement
git checkout -b feature/bluetooth-stack-update
git checkout -b feature/webserver-optimization
Depois, todas essas funcionalidades podem ser integradas em um branch de versão:
git checkout -b release/v2.0.0
git merge feature/adc-dma-improvement
git merge feature/bluetooth-stack-update
Esse modelo é muito utilizado em projetos complexos porque permite um controle fino sobre o que entra ou não em produção.
Mesclagem (Merge) com o Main
Quando uma versão está pronta e validada, ela é finalmente integrada ao main. Esse é um momento crítico, pois representa a promoção do código para produção.
O processo normalmente segue:
git checkout main
git pull origin main
git merge release/v2.0.0
Dependendo da política do projeto, pode-se usar também:
git merge --no-ff release/v2.0.0
O parâmetro --no-ff força a criação de um commit de merge, mesmo que o Git pudesse fazer um “fast-forward”. Isso é importante para manter o histórico mais claro, evidenciando quando uma versão foi integrada.
Após o merge:
git push origin main
Nesse momento, o código já está oficialmente em produção.
Atualização entre Versões em Paralelo
Um ponto muito interessante da sua estratégia — e que muitos desenvolvedores negligenciam — é o cenário onde duas versões estão sendo trabalhadas simultaneamente.
Imagine que você tenha:
- Uma versão atual sendo finalizada (
release/v1.5) - Uma nova versão já em desenvolvimento (
release/v2.0)
Quando a versão 1.5 for finalizada e integrada ao main, é essencial propagar essas alterações para a versão 2.0:
git checkout release/v2.0
git merge main
Isso garante que correções e melhorias não se percam entre versões.
Esse tipo de prática é extremamente importante em sistemas embarcados, onde correções de bugs críticos precisam ser mantidas em múltiplas linhas de desenvolvimento.
Considerações Arquiteturais
A organização de branches está diretamente ligada à arquitetura do software. Em projetos bem estruturados, especialmente com uso de FreeRTOS, é comum que cada funcionalidade isolada em um branch também esteja bem isolada no código.
Por exemplo, cada task pode estar em seu próprio módulo:
/tasks
/adc_task
/communication_task
/control_task
Isso reduz conflitos de merge e facilita o desenvolvimento paralelo, pois diferentes desenvolvedores podem atuar em áreas distintas do sistema sem interferência direta.
Além disso, essa separação melhora a rastreabilidade: ao olhar um commit, é possível entender claramente qual parte do sistema foi alterada.