Por que não existe um git undo? Como podemos desfazer alterações no git?
Como desfazer uma alteração é uma das dúvidas mais comuns a programadores que estão tendo seu primeiro contato com a ferramenta git. E a resposta não é difícil, mas requer que você aprenda um pouco mais sobre o git de forma a entender por que existe mais que uma resposta.
O que é o git?
O git é uma ferramenta de controle de versionamento, uma das mais utilizadas para versionar código, ao lado de outras ferramentas como Mercurial e SVN. Mas o que essa ferramenta realmente faz para nós?
A cada conjunto de alterações que você ou outra pessoa fazem, você cria um commit, que é adicionado ao histórico do seu projeto. Podemos visualizar esse histórico pelo comando git log
:
Agora surge uma das perguntas que nos faz ter várias respostas para essa pergunta: quando você diz que quer desfazer um commit, você quer que ele suma do seu histórico, como se aquele commit nunca tivesse acontecido, ou você quer criar uma nova alteração, revertendo as feitas no seu último commit?
A primeira alternativa é chamada de reset, e a segunda é um commit de reversão. A diferença entre elas é bem grande, então vamos analisar melhor os prós e contras de ambas as soluções.
Fazendo um reset
Para fazer com que uma alteração desapareça completamente do nosso histórico, utilizamos o comando git reset
: ele vai fazer o nosso histórico voltar a um estado específico (no caso, o commit anterior).
Ele pode ser usado normalmente ou com a opção --hard
(existem outras! Mas essas são as mais úteis para nós). Um reset significa que iremos modificar o histórico, mas deixar os arquivos do jeito que estão; já um hard reset faz com que o histórico seja modificado, e todos os arquivos sejam revertidos aos seus estados anteriores.
Atenção! Utilizar o comando reset pode causar problemas em alguns casos, leia a seção “precauções” antes de fazer isso no seu projeto!
Primeiro vamos ver o que acontece com um reset normal. Vamos considerar a seguinte situação: estamos editando o cabeçalho do nosso site para adicionar um botão que te direciona a uma página de busca. Ou seja, temos este código:
Que, unido ao nosso CSS, gera este cabeçalho:
E queremos o alterar para que fique assim:
Para isto, fazemos uma alteração simples ao arquivo, adicionando o link:
Feito o commit, agora nosso repositório está assim:
Mas logo em seguida, descobrimos que fizemos algo errado! Não era para ser “Buscar”, e sim “Entrar”.
Para fazer o reset e reverter essa alteração, usamos o comando git reset
, com o id do commit anterior:
git reset 14814c8
Agora podemos ver como ficou nosso repositório: o commit não está mais lá, mas nossos arquivos continuam com as alterações.
Podemos ver que mesmo o commit não estando mais lá, o link de busca continua:
Hard reset
O hard reset é uma variação do comando de reset padrão, que permite que os arquivos também sejam revertidos ao estado inicial. Repetindo o que fizemos com a opção --hard
:
Ao fim desse passo, nosso cabeçalho voltará a como estava antes, e não terá mais o link de busca!
Precauções no uso do reset
Lembra que falamos que o git é um histórico? Isso é uma característica fundamental dele: ele assume que as coisas não vão “voltar no tempo”, e o histórico vai sempre ser construido adicionando novos commits ao final da branch. Ao fazer um git pull
, o git
não permite que você “volte” para commits anteriores; nem que o histórico mude.
Por que isso importa? Porque, quando você trabalha em uma equipe, várias pessoas podem já ter a sua alteração em seus computadores, e nesse caso, elas receberão um erro ao tentar fazer um git pull
:
Ou seja, caso seja um commit que já esteja numa branch utilizada por outros desenvolvedores, prefira utilizar o git revert
. Caso seja realmente necessário realizar um reset
, todos os desenvolvedores terão de rodar o comando git reset origin/master
, de forma a passar ao estado que foi feito pull de forma forçada.
Criando um commit de reversão
No mesmo cenário do exemplo acima, vamos supor que não podemos realizar um reset, pois o projeto já está em produção, e vários desenvolvedores já puxaram as alterações. Então seguiremos pelo caminho de criação de um commit de reversão, usando o comando git revert
com o id do commit que queremos reverter:
git revert b0d0804
Sendo o último commit, podemos usar o identificador HEAD
(ou HEAD~1
para o penúltimo, etc.):
git revert HEAD
Ao rodar esse comando, o git irá nos pedir uma mensagem para o commit, que será por padrão “Revert <nome dos commits sendo revertidos>”. Após confirmar uma mensagem, podemos ver como ficou nosso log:
O último commit, que é nosso commit de reversão, fez a mudança contrária do commit anterior, que estávamos revertendo; mas o comando revert mantém esse histórico completo. Podemos ver que agora nosso cabeçalho está sem o link de busca:
Resumo
Nossas principais alternativas são realizar um reset ou um commit de reversão. Estes são seus prós e contras:
Reset
Feito através do comando git reset
, é ideal para desfazer commits que só existem localmente, ou seja, antes de ser realizado um push. Pode ser feito preservando os arquivos alterados, ou revertendo todos os arquivos pro estado anterior, usando as opções --soft
ou --hard
, respectivamente.
Vantagens
- Faz com que o commit suma completamente do histórico — pode ser usado para desfazer commits contendo senhas, por exemplo
Atenção: se você fez push de uma senha e/ou token, mude-os imediatamente! Existem bots que escaneiam o GitHub e outros repositórios de código por dados sensíveis. Assuma que qualquer dado sensível que você fez push a um repositório público já foi comprometido.
- Bom para rearranjar os commits locais ou em uma branch de feature na qual somente você esteja trabalhando
Desvantagens
- Altera o histórico de commit: isso significa que, caso já tenha sido realizado um push, você terá que passar
--force
para fazer o push da reversão. Além disso, caso outras pessoas tenham feito o pull do projeto, eles terão um erro e precisarão também passar a opção--force
no pull.
Exemplos
git reset HEAD~1
Volta ao commit anterior ao atual, mantendo os arquivos modificados.
git reset --hard HEAD~1
Volta ao commit anterior ao atual, desfazendo também as modificações aos arquivos.
Commit de reversão
Feito através do comando git revert
, é ideal para desfazer commits depois de um push, ou commits antigos para o qual realizar um rebase poderia ser trabalhoso demais.
Vantagens
- Não muda o histórico de commits, ou seja, não causará problemas na hora do push, ou quando outros integrantes do projeto realizarem um pull.
Desvantagens
- As mudanças revertidas continuam no histórico, acessíveis a qualquer um que clonar o repositório.
Exemplos
git revert HEAD
Desfaz o último commit.
git revert HEAD~1
Desfaz o penúiltimo commit (e deixa o último lá). Podem ocorrer conflitos nessa reversão, que você precisará resolver.
git revert HEAD HEAD~1
Cria commits de reversão para os ultimos 2 commits.
O git é uma ferramenta extremamente útil no processo de desenvolvimento, e ter um entendimento um pouco mais profundo do seu funcionamento pode ser uma grande ajuda no gerenciamento de nossos repositórios, especialmente quando estamos trabalhando em equipe. Espero que este post tenha te ajudado na sua jornada de aprendizado!