Introdução
No processo de desenvolvimento de software, é comum e até recomendado fazer commits pequenos e frequentes. No entanto, nem todos esses commits são “perfeitos” para serem enviados diretamente para a branch principal (main ou master). Muitas vezes, criamos commits intermediários com código quebrado, funcionalidades incompletas, ou mensagens de commit provisórias (“WIP - Work In Progress”).
O desafio surge quando queremos um histórico de commits limpo, coeso e fácil de entender para a main branch, mas também precisamos da flexibilidade de “salvar” nosso trabalho provisoriamente para testar novas abordagens ou voltar a um ponto específico.
Este post abordará como conciliar essas duas necessidades. Veremos técnicas para gerenciar o desenvolvimento caótico e, em seguida, como “polir” seu histórico de commits usando git rebase interativo e outras ferramentas, garantindo que apenas commits de alta qualidade cheguem ao seu repositório compartilhado.
O Cenário de Desenvolvimento Comum (e Caótico)
Imagine que você está implementando uma nova funcionalidade. Você começa com um commit base e, à medida que avança, faz commits como:
feat: inicio da funcionalidade Xfix: ajusta bug na funcionalidade X (WIP)chore: tenta nova abordagem para X (build quebrado)refactor: refatora parte de Xfeat: X quase prontafix: corrige testes (mas ainda não está perfeito)
Seu histórico de commits local pode se parecer com isto:
A -- B -- C -- D -- E -- F (feature-branch)
Onde C, D, E, F são seus commits intermediários.
O problema é que C, D, E podem conter estados ruins do código ou serem muito granulares. Queremos que eles se tornem um ou dois commits lógicos e funcionais antes de ir para a main.
Técnicas para Gerenciar o Desenvolvimento Provisório
Antes de pensar em organizar, precisamos de formas de “salvar” nosso progresso sem nos comprometer imediatamente com um commit final.
1. git stash - Guardando alterações temporariamente
git stash é seu amigo quando você precisa trocar de branch ou lidar com uma interrupção, mas não quer fazer um commit incompleto. Ele guarda o estado atual do seu diretório de trabalho e do staging area.
# Salva as alterações não commitadas
git stash push -m "Mensagem para o stash"
# Lista os stashes
git stash list
# Aplica o stash mais recente e o remove da lista
git stash pop
# Aplica um stash específico (ex: stash@{1}) e o mantém na lista
git stash apply stash@{1}
Isso permite que você mantenha seu branch limpo enquanto experimenta, sem poluir seu histórico de commits.
2. Commits Provisórios em Branches de Desenvolvimento
É perfeitamente aceitável fazer commits provisórios em um branch de desenvolvimento (ex: feature/minha-nova-funcionalidade). A chave é não enviar esses commits para branches compartilhadas (como main) antes de limpá-los.
Organizando o Histórico de Commits com git rebase Interativo
Uma vez que você alcançou o resultado desejado e seu código está funcional, é hora de “limpar” o histórico de commits. git rebase -i (interactive rebase) é a ferramenta mais poderosa para isso.
Ele permite que você reescreva uma sequência de commits, podendo:
squash/fixup: Combinar múltiplos commits em um único.reword: Alterar a mensagem de um commit.edit: Parar em um commit para fazer alterações adicionais (ex: corrigir um bug, adicionar um arquivo esquecido).drop: Remover um commit do histórico.reorder: Mudar a ordem dos commits.
Como usar git rebase -i
Você precisa especificar o commit anterior ao primeiro commit que você deseja reescrever.
git rebase -i <SHA_DO_COMMIT_ANTERIOR>
# Ou, para os últimos N commits
git rebase -i HEAD~N
Exemplo: Seu histórico:
A -- B -- C (fix: bug) -- D (feat: add X) -- E (fix: X) -- F (chore: adjust) (feature-branch)
Você quer limpar os commits C, D, E, F. O commit anterior é B.
git rebase -i B
# Ou se B for 4 commits atrás do HEAD:
git rebase -i HEAD~4
O Git abrirá seu editor de texto padrão com uma lista dos commits que você selecionou para reescrever, de cima para baixo (os mais antigos primeiro):
pick C fix: bug
pick D feat: add X
pick E fix: X
pick F chore: adjust
# Rebase 27170a4..9d070b4 onto 27170a4 (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (e.g. `git commit --amend`) for each commit
# b, break = stop here (continue rebase later with `git rebase --continue`)
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C | -c <commit>| --no-ff <commit>]
# . create a merge commit and reset HEAD to it (use 'git merge <commit>' normally)
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
Cenário de Limpeza:
Vamos transformar os commits C, D, E, F em um único commit lógico feat: implementa funcionalidade X.
Você editará o arquivo de rebase da seguinte forma:
pick C fix: bug
squash D feat: add X
squash E fix: X
fixup F chore: adjust
pick C: Escolhemos o primeiro commit como base.squash D: CombinaDcomC. O Git abrirá um editor para você combinar as mensagens deCeD.squash E: CombinaEcom o resultado anterior. O Git abrirá um editor para combinar a mensagem.fixup F: CombinaFcom o resultado anterior, mas descarta a mensagem de commit deF. Isso é útil para pequenos ajustes de formatação ou correções rápidas que não precisam de uma mensagem própria.
Após salvar e fechar o editor, o Git pode abrir outro editor para que você combine as mensagens dos commits “squashed”. Você pode escrever uma mensagem de commit limpa, por exemplo: feat: implementa funcionalidade X completa.
Seu histórico final na feature-branch será:
A -- B -- C' (feat: implementa funcionalidade X completa) (feature-branch)
Onde C' é o novo commit que contém todas as alterações de C, D, E e F.
Outras Opções Úteis do Rebase
edit: Useeditse precisar parar em um commit específico para fazer correções ou adicionar algo que foi esquecido.pick C fix: bug edit D feat: add X pick E fix: X pick F chore: adjustO Git pausará no commit
D. Você pode fazer suas alterações,git add ., e depoisgit commit --amendpara incorporar as novas alterações ao commitD. Em seguida,git rebase --continuepara seguir com o rebase.reword: Para apenas alterar a mensagem de um commit:reword C fix: bug pick D feat: add X ...O Git pausará para você editar a mensagem de
C.
Outras Ferramentas para Limpeza Rápida
1. git commit --amend - Ajustando o último commit
Se você acabou de fazer um commit e percebeu que esqueceu um arquivo, ou que a mensagem está errada, git commit --amend é a solução. Ele permite que você “edite” o último commit.
# Faça suas alterações
# git add <ARQUIVOS_ESQUECIDOS>
# Re-comita as alterações e/ou edita a mensagem do commit anterior
git commit --amend --no-edit # Mantém a mensagem de commit existente
git commit --amend # Abre o editor para você mudar a mensagem
2. git reset - Desfazendo commits localmente
git reset é usado para mover o HEAD e, opcionalmente, o staging area e o diretório de trabalho. É útil para desfazer commits que você não quer mais no seu histórico local.
git reset --soft <COMMIT_ID>: Move oHEADpara<COMMIT_ID>, mas mantém as alterações dos commits desfeitos no seu staging area. Útil para “desfazer” commits e depois “re-commitá-los” de forma diferente.git reset --mixed <COMMIT_ID>(padrão): Move oHEADe o staging area para<COMMIT_ID>, mantendo as alterações dos commits desfeitos no seu diretório de trabalho (como arquivos não staged).git reset --hard <COMMIT_ID>: PERIGOSO! Move oHEAD, o staging area e o diretório de trabalho para<COMMIT_ID>, descartando todas as alterações dos commits desfeitos. Use com extrema cautela, pois você perderá seu trabalho.
# Desfaz o último commit, mantendo as alterações no staging area
git reset --soft HEAD~1
# Desfaz o último commit, movendo as alterações para o diretório de trabalho (unstaged)
git reset HEAD~1
# Desfaz o último commit e descarta as alterações (CUIDADO!)
git reset --hard HEAD~1
Considerações Importantes
- Nunca faça rebase em branches que já foram publicadas! Reescrever o histórico de commits de um branch compartilhado pode causar problemas sérios para seus colaboradores. O rebase é para uso em branches locais e não compartilhadas.
- Faça commits frequentes e pequenos: Isso facilita muito a tarefa de rebase e permite que você “salve” seu trabalho sem medo.
- Mensagens de commit: Embora a ideia seja ter commits provisórios com mensagens ruins, esforce-se para que as mensagens dos commits finais (após o rebase) sejam claras, concisas e sigam as convenções do projeto.
Conclusão
A flexibilidade do Git permite que você adote um fluxo de trabalho onde a experimentação e os commits provisórios são parte natural do desenvolvimento. Com ferramentas como git stash, git commit --amend, git reset e, principalmente, o git rebase interativo, você pode transformar um histórico de commits bagunçado em uma narrativa limpa e profissional antes de integrar suas mudanças à main branch. Dominar essas técnicas é crucial para manter um repositório organizado e facilitar a colaboração em equipe.
