Commits atômicos
Um commit atômico contém exatamente uma alteração lógica. A recompensa não é a organização por si só — são três capacidades concretas que a equipe perde quando os commits são misturados:
- Revisão: um revisor consegue manter uma ideia na cabeça e julgá-la.
- Reversão: uma alteração ruim pode ser desfeita sem levar as boas junto.
- Bisect:
git bisectconsegue nomear o commit que introduziu um bug, o que só funciona se cada commit for uma alteração e cada commit compilar.
Essa última cláusula é a mais frequentemente ignorada. Atômico não significa "o menor possível" — significa "uma alteração completa". Um commit que separa uma função do local de chamada que ela exige é menor, mas não atômico, porque deixa a árvore quebrada.
O fluxo de trabalho
1. Inspecione antes de estagiar qualquer coisa
Entenda a árvore inteira antes de decidir como dividi-la:
git status --porcelain # todo caminho, incluindo não rastreados
git diff # alterações não stageadas
git diff --cached # qualquer coisa já stageada
git log --oneline -15 # onde HEAD está e o estilo local
Resista a git add -A e git commit -a como movimento inicial. Eles são como uma árvore mista se torna um commit misto, e uma vez commitado, dividi-lo é estritamente mais trabalho do que dividi-lo antes. Verifique também alterações que não deveriam ser commitadas — saída de build, arquivos .env, prints de depuração, um arquivo de configuração do editor esquecido.
2. Agrupe alterações em unidades lógicas
O teste mais confiável é linguístico: rascunhe o assunto do commit e, se ele precisar de um "e" para ser preciso, você tem mais de um commit. "Corrigir redirecionamento de login e atualizar lodash" são dois. A pergunta do revisor é o segundo teste: eles precisariam entender a alteração X para julgar a alteração Y? Se não, as alterações são independentes e pertencem separadas.
Separe:
| Manter separado | Por quê |
|---|---|
| Formatação/espaços em branco vs. lógica | Uma reformatação enterra as três linhas reais em 400 linhas de ruído. Esta é a divisão de maior valor isoladamente. |
| Renomeações vs. mudanças de comportamento | Mesma razão — uma renomeação toca tudo e esconde a única linha que importa. |
| Atualizações de dependência vs. código | Cada uma precisa de sua própria história de reversão. |
| Correções de bugs não relacionadas | Reversões independentes, bisects independentes. |
| Refatoração vs. a funcionalidade que a utiliza | Permite que um revisor confirme que a refatoração preserva o comportamento. |
Mantenha junto:
| Manter em um commit | Por quê |
|---|---|
| Uma alteração e seus testes | São uma unidade lógica; testes para código não entregue, ou código sem testes, são cada metade de uma alteração. |
| Uma renomeação e todos os seus locais de chamada | Dividir deixa a árvore quebrada — o oposto de atômico. |
| Uma mudança de assinatura e seus chamadores | O mesmo. |
| Uma alteração e os docs/changelog que a descrevem | Descrevem a mesma decisão e devem reverter juntos. |
3. Ordene os commits para que todos compilam
A sequência importa porque cada commit deve deixar a árvore funcional. Coloque as fundações antes das coisas que as usam: adicione o helper antes do código que o chama, faça a refatoração antes da funcionalidade construída sobre ela, coloque o esquema antes da consulta. Se dois grupos são mutuamente dependentes, isso é um sinal de que são na verdade um único commit.
4. Stagie cada grupo
Use a técnica menos invasiva que funciona. Escalone apenas quando a mais simples não conseguir expressar a divisão:
Nível 1 — stagiue arquivos inteiros. Isso cobre a grande maioria das divisões reais, porque alterações lógicas geralmente vivem em arquivos diferentes:
git add src/auth.py tests/test_auth.py
git commit -m "..."
Nível 2 — separe arquivos inteiros quando algo na árvore poderia ser facilmente commitado por acidente:
git stash push -- requirements.txt # estaciona
# ... commita o resto ...
git stash pop # traz de volta
Nível 3 — divida alterações dentro de um único arquivo. Necessário apenas quando um arquivo realmente contém duas alterações lógicas. A mecânica é complicada e fácil de errar sutilmente, então leia references/splitting-one-file.md antes de tentar.
git add -pnão funciona em ambientes não interativos e falha silenciosamente. Ele imprime o primeiro hunk, atinge EOF no stdin, stagieia nada e sai com código 0. Um código de saída de sucesso aqui significa que nada aconteceu. O mesmo se aplica agit add -i,git commit -pegit checkout -p. Como a falha parece sucesso, o dano geralmente é um commit vazio ou sem a alteração pretendida — então use as técnicas no arquivo de referência e verifique o index comgit diff --cachedantes de commitar.
5. Escreva cada mensagem no estilo do repositório
Corresponda ao histórico circundante em vez de importar uma convenção. Um prefixo feat: inserido em um log de assuntos em prosa simples é ruído, e faz o histórico parecer dois projetos. Leia o estilo local primeiro:
git log --no-merges --format='%s' -n 30 # estilo dos assuntos
git log --no-merges -n 10 --format='%n---%n%s%n%b' # se corpos são usados
Procure por: um prefixo tipo: ou tipo(escopo): (Conventional Commits), um prefixo de ticket como [PROJ-123], capitalização, imperativo vs. passado ("Adiciona" vs. "Adicionou"), comprimento típico do assunto e se corpos são normais ou assuntos ficam sozinhos. Adote o que encontrar. Conventional Commits é um bom padrão apenas quando o log já o usa, ou o repositório ainda não tem histórico e o usuário não tem preferência.
Qualquer que seja o estilo local, o assunto deve dizer o que mudou e o corpo — quando o histórico usa corpos — deve dizer por quê, já que o diff já mostra o quê. Se o repositório tem um CONTRIBUTING.md ou um hook commit-msg, eles substituem a inferência do log.
Seguir uma convenção nunca significa inventar os dados que ela carrega. Se o estilo local incorpora uma referência de ticket ou issue — [PROJ-123], Fixes #456 — e você não recebeu o identificador real, não invente um e não continue a numeração que vê no log. Um [PROJ-124] de aparência plausível é pior do que nenhum prefixo: é permanente, parece rastreabilidade real e provavelmente aponta para um ticket não relacionado de outra pessoa. Omita a referência, combine o resto do estilo e diga em sua resposta que o ID está faltando para que o usuário possa fornecê-lo — o commit ainda não foi enviado e é barato emendar. Peça o número se o usuário estiver por perto.
6. Verifique antes de devolver
git log --oneline -n <contagem> # a forma do que você construiu
git status --porcelain # o que, se houver, sobrou
Confirme que cada commit contém apenas sua própria alteração (git show --stat <sha>), e que nada foi perdido. Se alterações permanecem não commitadas por design, diga explicitamente — uma árvore que o usuário acreditava estar limpa mas não está é o pior resultado aqui, porque ele descobrirá depois e não saberá se foi intencional.
Barreiras de proteção
- Molde como commitar, não se commitar. Se o usuário não pediu um commit, esta skill não torna apropriado criar um, e nunca implica push.
- Nunca descarte trabalho para facilitar uma divisão.
git checkout -- <arquivo>,git reset --hardegit stash dropdestroem trabalho não commitado que nenhum commit ainda contém — é irrecuperável. Se uma técnica precisar de um desses, faça backup do conteúdo primeiro e confirme que o backup existe antes de executá-lo. - Não reescreva história publicada para organizá-la. Dividir commits já enviados significa um force-push que reescreve histórico que outros podem ter puxado; pergunte primeiro.
- Não divida demais para parecer minucioso. Três commits que quebram a compilação são piores do que um que funciona. Na dúvida, prefira o agrupamento que mantém cada commit funcional.
- Se a árvore contém apenas uma alteração lógica, commite como um. Nem toda árvore precisa ser dividida, e dizer isso é um bom resultado.