GIT - O melhor controle de versão
Controlar versões de código sempre foi um problema. Antes do Git, equipes usavam pastas com datas, e-mail com anexo ou ferramentas centralizadas que travavam arquivos enquanto alguém editava. Em 2005, Linus Torvalds criou o Git para resolver o problema no kernel do Linux: distribuído, rápido e projetado para trabalho paralelo em escala.
Hoje o Git está em praticamente todo projeto de software. A pesquisa Stack Overflow de 2025 mostra o GitHub, principal plataforma de hospedagem de código, com 81% de adoção entre os respondentes, o que reflete como o Git virou infraestrutura universal do desenvolvimento. O que varia é o domínio das ferramentas, e esse domínio é o que separa quem sofre com merge às sextas-feiras de quem entrega com calma toda semana.
Conceitos fundamentais
| Conceito | O que é |
|---|---|
| VCS (Version Control System, Sistema de Controle de Versão) | Ferramenta que registra o histórico de mudanças de arquivos ao longo do tempo, permitindo reverter, comparar e colaborar |
| Git | VCS distribuído criado por Linus Torvalds em 2005; cada clone do repositório é uma cópia completa do histórico |
| Repositório (repo) | Diretório versionado pelo Git; contém o histórico completo de commits, branches e configurações |
| Branch (cópia) | Cópia isolada do código para desenvolver uma mudança sem afetar a linha principal |
| Commit (registro de alteração) | Snapshot do código em um momento, com uma mensagem descrevendo o que mudou e por quê |
| Merge (mesclagem) | Integração das alterações de uma branch de volta à principal |
| Rebase (reorganização de commits) | Reaplica commits de uma branch sobre outro ponto do histórico, produzindo um histórico linear |
| PR (Pull Request, Pedido de Integração) | Solicitação de mesclagem de uma branch para a main, com revisão obrigatória |
| TBD (Trunk-Based Development, Desenvolvimento baseado no tronco) | Estratégia onde a main é a fonte única da verdade; branches são curtas e focadas |
| Stash (área temporária) | Área que guarda alterações locais sem criar commit, liberando a branch para outra tarefa |
| Squash (compactação de commits) | Agrupa vários commits em um único antes do merge, mantendo o histórico limpo |
| Reflog (registro local de movimentos) | Histórico interno de todas as posições que HEAD ocupou; usado para recuperar commits perdidos |
| CI/CD (Continuous Integration/Delivery, Integração Contínua/Entrega Contínua) | Pipeline que automatiza lint, testes, build e deploy a cada mudança de código |
| Pipeline (sequência de verificações) | Conjunto ordenado de estágios que todo código deve passar; cada estágio é um portão |
| Feature flag (interruptor de funcionalidade) | Configuração que ativa ou desativa uma feature em produção sem novo deploy |
| Pre-commit hook (gancho de pré-commit) | Script executado localmente antes de cada commit para detectar problemas antes do push |
| Fix forward (corrigir para frente) | Estratégia de corrigir bugs com um novo commit e deploy, sem reverter o histórico |
| Rollback (reversão) | Retorno do sistema à versão anterior; reservado para emergências quando fix forward é inviável |
Branches: trabalho isolado
Trunk-Based Development é a estratégia adotada pela maioria dos times de alto desempenho. A regra é simples: a main é a fonte única da verdade. Cada branch deriva dela, tem um único propósito e dura poucos dias antes de voltar via PR.
main → branch → commits → PR → review → merge → main
| Regra | Motivo |
|---|---|
Branch derivada da main | Evita divergência acumulada e conflitos tardios |
| Curta e focada | PR pequeno é mais fácil de revisar e tem menor risco de regressão |
| Um propósito por branch | Misturar feat + fix + refactor dificulta reversão e rastreabilidade |
| Nunca branch de branch | Dependências implícitas complicam o merge |
A nomenclatura segue o padrão <tipo>/<descricao-em-kebab-case>:
❌ Ruim
feature-nova
minha-branch
fix
branch-do-joao-refactor-e-tambem-o-bug-do-login✅ Bom
feat/user-email-verification
fix/order-discount-rounding
docs/git-conventions
refactor/payment-service-splitGitflow ainda tem lugar?
Para a maioria das aplicações web e SaaS com deploy contínuo, TBD é o caminho. O GitFlow (com branches develop, release, hotfix) ainda faz sentido para projetos open-source com múltiplas versões suportadas em paralelo ou produtos enterprise com ciclos de release longos e homologações formais. Fora desses contextos, a complexidade do GitFlow cria mais atrito do que resolve.
Commits: o registro que importa
O commit não documenta o como, documenta o o que e o por quê. Conventional Commits é a convenção mais adotada: um formato simples que torna o histórico navegável por humanos e por ferramentas de CI/CD.
<tipo>[escopo opcional]: <descrição no imperativo, em inglês, sem ponto final>
| Tipo | Quando usar |
|---|---|
feat | Nova funcionalidade visível ao usuário ou sistema |
fix | Correção de bug |
docs | Apenas documentação |
refactor | Mudança interna sem alterar comportamento |
test | Adição ou correção de testes |
perf | Melhoria de performance |
style | Formatação, whitespace, sem mudança de lógica |
chore | Tarefas de manutenção (build, deps, config) |
ci | Mudanças em pipelines de CI/CD |
revert | Reverte um commit anterior |
O escopo é opcional. Use quando o contexto não é óbvio pelo tipo: auth, order, payment, user.
A descrição responde sempre à mesma pergunta: o que esse commit faz? Escreva no imperativo, como se completasse a frase “esse commit vai…”: add email verification, fix discount rounding, remove unused dependency. Nunca no passado (added, fixed) nem no gerúndio (adding, fixing). Inglês, sem maiúscula inicial, sem ponto final, até 72 caracteres.
❌ Ruim
fix bug
update
arrumei o login
WIP
feat: adiciona validação no campo de e-mail do usuário no formulário de cadastro da tela de onboarding✅ Bom
feat(auth): add email verification on signup
fix(order): correct discount rounding for fractional quantities
docs: add git conventions
refactor(payment): extract charge logic into PaymentService
chore: upgrade eslint to v9Pull Requests: a porta da main
O PR é o ponto de verificação antes que qualquer mudança toque a main. Revisão obrigatória, checks verdes e squash no merge são as três práticas que mantêm o histórico saudável.
| Prática | Motivo |
|---|---|
| PR pequeno e focado | Review mais rápido, menor superfície de bug |
| Review obrigatório | Ninguém faz merge do próprio PR sem aprovação |
| Checks verdes antes do merge | CI/CD valida antes de tocar a main |
| Squash antes do merge | Um commit por PR mantém o histórico legível e viabiliza git bisect |
| Menos de 400 linhas como referência | Diffs grandes cansam e geram reviews superficiais |
Antes de abrir o PR, preencha o título no formato Conventional Commits e uma descrição com contexto e decisões não óbvias. O reviewer não precisa perguntar o que você já sabe.
❌ Ruim
título: update
descrição: (vazia)
commits: WIP, fix, arrumei, agora vai, update 2
checks: falhando
linhas alteradas: 1.200✅ Bom
título: feat(auth): add email verification on signup
descrição:
Adiciona verificação de e-mail no cadastro. Token expira em 24h.
Optei por gerar o token no backend para evitar previsibilidade no cliente.
commits: 1 commit limpo via squash
checks: todos verdes
linhas alteradas: 180Rotina prática
O ciclo correto parte da main atualizada, usa uma branch com um único propósito e termina com o PR mergeado e a branch removida.
pull main → nova branch → commits focados → fetch origin → merge origin/main → PR → squash and merge → deletar branch
✅ Passo a passo completo
# 1. atualizar main antes de começar
git checkout main
git pull origin main
# 2. criar branch com um único propósito
git checkout -b feat/user-email-verification
# 3. commits focados durante o trabalho
git add src/auth/email.js
git commit -m "feat(auth): add email verification token generation"
git add src/auth/email.test.js
git commit -m "test(auth): cover token expiry and reuse scenarios"
# 4. incorporar atualizações da main antes do PR
git fetch origin
git merge origin/main
# sem conflitos: merge commit criado automaticamente
# com conflitos: ver seção Troubleshooting
# 5. enviar para review
git push origin feat/user-email-verification
# 6. após o merge: confirmar e deletar
git log origin/main --oneline -3
# -d rejeita se a branch não foi mergeada, proteção extra
git branch -d feat/user-email-verification
git push origin --delete feat/user-email-verificationQuando uma tarefa de maior prioridade interrompe o trabalho em andamento, o Stash guarda o estado sem criar um commit de WIP no histórico.
# guardar o estado atual sem commitar
git stash push -m "wip: email verification form"
# trocar para a tarefa prioritária
git checkout feat/other-priority-task
# ... trabalhar, commitar, abrir PR ...
# voltar para a tarefa original e restaurar
git checkout feat/user-email-verification
git stash pop
Squash: histórico limpo
Um PR com 30 commits fragmentados dificulta git bisect (busca binária de regressão) e git blame. O squash compacta todo o trabalho em um único commit descritivo na hora do merge.
WIP → fix typo → esqueci de salvar → arrumei → squash → feat(auth): add email verification (#42)
No GitHub, clique no dropdown ao lado de “Merge pull request” e selecione Squash and merge. Ajuste a mensagem antes de confirmar.
Para limpar commits locais antes do primeiro push, use o Interactive rebase (rebase interativo):
# compactar os 4 últimos commits locais em um
git rebase -i HEAD~4
# no editor: 'pick' no primeiro, 's' nos demais
# pick abc1234 feat(auth): add email verification token
# s def5678 fix typo
# s ghi9012 WIP
# s jkl3456 forgot test file
Rebase local antes do primeiro push: seguro. Rebase em branch compartilhada: nunca. Reescreve o histórico que outros já baixaram e gera divergências irreconciliáveis.
Troubleshooting
O que não fazer
| Ação | Consequência |
|---|---|
git push --force origin main | Sobrescreve o histórico da main para toda a equipe |
git push --force sem --lease | Pode destruir commits de um colega que chegaram ao remoto enquanto você trabalhava |
git reset --hard sem stash antes | Descarta alterações locais sem chance de recuperação |
git clean -fd sem revisar com -n antes | Remove arquivos não rastreados que podem não estar no .gitignore |
git rebase em branch compartilhada | Reescreve histórico que outros já baixaram; cria divergências |
Inspecione antes de agir:
# ver o que mudou nos últimos 3 commits antes de resetar
git diff HEAD~3
# histórico resumido da branch atual
git log --oneline -10
# ver quais arquivos mudaram em cada commit
git log --stat -5
# simular limpeza sem executar
git clean -n -fd
Recuperando commits perdidos com Reflog
O Reflog registra todas as posições que HEAD ocupou. Funciona como um histórico de desfazer local, independente de push.
# listar todas as posições recentes do HEAD
git reflog
# saída:
# abc1234 HEAD@{0}: rebase: feat(auth): add email verification
# def5678 HEAD@{1}: commit: test(auth): cover token expiry
# ghi9012 HEAD@{2}: commit: feat(auth): add email verification token
# restaurar para um estado anterior
git checkout HEAD@{2}
# criar branch a partir de um commit perdido
git checkout -b recovery/email-verification ghi9012
Conflitos com a main
Se a main avançou enquanto você trabalhava, incorpore as mudanças com um merge commit na branch. O squash no merge do PR limpa tudo depois.
git fetch origin
git merge origin/main
# sem conflitos: Git cria o merge commit automaticamente
# com conflitos: resolver cada arquivo e commitar
git add .
git commit -m "chore: merge main into feat/user-email-verification"
Corrigindo um problema em produção
Quando um bug aparece em produção, o primeiro caminho é criar uma branch de fix a partir da main e entregar via PR. Mantém o histórico avançando sem desfazer mudanças de outros devs.
git checkout main
git pull origin main
git checkout -b fix/user-email-token-expiry
git add src/auth/email.js
git commit -m "fix(auth): correct token expiry on email verification"
git fetch origin
git log origin/main --oneline -3
# PR → squash and merge → confirmar deploy → deletar branch
Se o tempo for crítico e não houver janela para review e deploy, o caminho é reverter.
Revertendo um deploy com problema
git revert não apaga o commit: cria um novo commit que desfaz o efeito. O commit original permanece no histórico.
# reverter o commit problemático na main
git revert <hash-do-squash-commit>
git push origin main
# criar branch de fix a partir da main já revertida
git checkout -b fix/user-email-verification
git add src/auth/email.js
git commit -m "fix(auth): correct token expiry on email verification"
# PR → squash and merge → deploy
CI/CD: do commit ao usuário
CI/CD é o processo que garante que qualquer mudança passe por verificação automática antes de chegar ao usuário.
- CI (Continuous Integration, Integração Contínua): valida qualidade a cada push, com lint, testes e build.
- CD (Continuous Delivery, Entrega Contínua): promove o artefato pelos ambientes até produção com aprovação manual no último estágio.
- CD (Continuous Deployment, Deploy Contínuo): promove até produção sem aprovação manual.
Lint → Segurança → Testes → Build → Deploy Staging → Smoke → Deploy Prod
| Estágio | O que verifica | Critério de falha |
|---|---|---|
| Lint | Estilo e formatação | Qualquer violação |
| Segurança | Secrets expostos, vulnerabilidades conhecidas | Qualquer secret; CVE explorado |
| Testes | Comportamento esperado do sistema | Qualquer falha |
| Build | Compilação e empacotamento do artefato | Qualquer erro |
| Deploy Staging | Promoção para ambiente espelho | Falha no health check |
| Smoke | Fluxo crítico funciona em staging | Qualquer falha no caminho crítico |
| Deploy Prod | Promoção para produção | Aprovação manual ou canary gate |
O artefato que vai para produção é o mesmo que passou por staging. Fazer rebuild entre ambientes invalida a garantia dos testes.
Deploy e release são eventos independentes
merge na main → deploy (automático) → feature flag desativada → release gradual → 100%
Deploy é o ato técnico de colocar o código em produção, acontece automaticamente após merge na main com pipeline verde. Release é o ato de tornar a funcionalidade visível ao usuário, controlado por feature flag, acontece quando o time decide.
| Situação | Ação |
|---|---|
| Feature em desenvolvimento | Sobe desativada, código na main sem risco |
| Feature pronta, aguardando validação | Ativa para % do tráfego ou grupo interno |
| Feature com problema | Desativa sem rollback de código |
| Feature validada | Ativa para 100%, flag removida |
Flags têm prazo de validade. Uma flag que nunca é removida vira débito técnico.
Fix forward vs rollback
Fix forward é a abordagem preferida: a main segue para frente com histórico linear.
bug em prod → PR na main → pipeline → merge → deploy
| Etapa | Ação |
|---|---|
| Identificar | Confirmar o comportamento inesperado via logs e métricas |
| Isolar | Desativar a feature flag se o bug estiver coberto por uma |
| Corrigir | Abrir PR na main com a correção |
| Validar | Pipeline verde: lint, testes, build |
| Entregar | Merge e deploy seguindo o fluxo normal |
| Confirmar | Monitorar logs após deploy para garantir estabilização |
Rollback fica reservado para emergências: sistema indisponível e fix forward inviável no tempo necessário.
Pre-commit: detectar antes do push
Pre-commit hooks detectam problemas antes do commit, onde o custo de correção é mínimo. O CI detecta tarde, após o push, na esteira. O custo do hook deve ficar abaixo de 5 segundos para não criar atrito no fluxo de trabalho.
código staged → lint → auto-fix → commit
Novidades em 2026
O Git não parou. Algumas melhorias recentes que valem atenção:
Git Worktrees: múltiplas branches em paralelo
Git Worktrees permitem ter múltiplos diretórios de trabalho do mesmo repositório ao mesmo tempo, cada um em uma branch diferente, sem precisar fazer checkout. Útil para revisar um PR enquanto trabalha em uma feature, ou testar duas versões lado a lado.
Em julho de 2025, o VS Code recebeu suporte nativo a worktrees na integração Git.
# criar um worktree em uma pasta separada com a branch fix/auth-token
git worktree add ../meu-repo-fix fix/auth-token
# listar worktrees ativos
git worktree list
# remover após o uso
git worktree remove ../meu-repo-fix
Scalar: Git para repositórios grandes
Scalar está integrado ao Git 2.47+ e ativa automaticamente um conjunto de otimizações: sparse-checkout (cone mode), partial clone, multi-pack-index, commit-graph e manutenção em background. Para monorepos grandes, a diferença no tempo de clone e operações do dia a dia é expressiva.
# clonar um repositório grande com todas as otimizações ativas
scalar clone <url-do-repositorio>
Push seguro com —force-with-lease
Substituir --force por --force-with-lease adiciona uma verificação: o push só prossegue se o remoto não avançou desde o seu último fetch. Evita sobrescrever commits de um colega que chegaram ao remoto enquanto você trabalhava.
# seguro: falha se o remoto avançou desde o último fetch
git push --force-with-lease origin feat/user-email-verification
# perigoso: sobrescreve qualquer estado remoto sem verificação
git push --force origin feat/user-email-verification
Assinatura de commits com SSH
O GitHub suporta assinatura de commits com chaves SSH desde 2022, reutilizando a chave que você provavelmente já tem. Uma configuração de uma linha e todo commit fica verificado:
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
TBD como padrão consolidado
O debate “GitFlow vs TBD” perdeu força em 2025. Para aplicações web e SaaS com deploy contínuo, TBD é o padrão consolidado. A pergunta no mercado deixou de ser “qual estratégia de branch usar?” e virou “como automatizar o CI/CD para suportar TBD de verdade?”
Git é fundação. Dominar branches curtas, commits descritivos, PRs pequenos e um pipeline automatizado transforma a entrega de software de evento estressante em rotina confiável. Os comandos estão todos na documentação oficial. Os padrões de processo ficam no code-style.
Happy coding!
Referências
- Conventional Commits
- Trunk-Based Development
- git-scm.com — documentação oficial
- Git Worktrees
- Scalar
- code-style: Git
- code-style: Git avançado
- code-style: CI/CD
Ver todos os artigos →