Redefinição, verificação e reversão
Os comandos git reset, git checkout e git revert são algumas das ferramentas mais úteis em sua caixa de ferramentas do Git. Todos eles permitem que você desfaça algum tipo de alteração em seu repositório, e os dois primeiros comandos podem ser usados para manipular commits ou arquivos individuais.
Por serem muito parecidos, é muito fácil misturar qual comando deve ser usado em qualquer cenário de desenvolvimento. Neste artigo, a gente vai comparar as configurações mais comuns de git reset
, git checkout
e git revert
. Espero que você saia com a confiança necessária para navegar em seu repositório usando qualquer um desses comandos.
É útil pensar em cada comando em termos de seu efeito nos três mecanismos de gerenciamento de estado de um repositório do Git: o diretório de trabalho, o instantâneo de staging e o histórico de commits. Esses componentes às vezes são conhecidos como "As três árvores" do Git. As três árvores são exploradas em profundidade na página git reset. Tenha esses mecanismos em mente ao ler este artigo.
Um checkout é uma operação que move o ponteiro de referência HEAD
para um commit especificado. Para demonstrar melhor esse comportamento, considere o exemplo a seguir:
Material relacionado
Como mover um Repositório do Git completo
VER SOLUÇÃO
Aprenda a usar o Git com o Bitbucket Cloud
Este exemplo demonstra uma sequência de commits no branch main
. A referência HEAD
e a referência do branch main
apontam no momento para o commit d. Agora vamos executar o git checkout b
Esta é uma atualização para a árvore do "histórico de commits". O comando git checkout
pode ser usado em um escopo de nível de commit ou de arquivo. Um checkout no nível do arquivo vai alterar o conteúdo do arquivo para o commit específico.
Uma reversão é uma operação que recebe um commit especificado e cria um novo commit que inverte o commit especificado. O git revert
só pode ser executado em um escopo de nível de commit e não tem funcionalidade no nível do arquivo.
Uma redefinição é uma operação que recebe um commit especificado e redefine as "três árvores" para corresponder ao estado do repositório nesse commit especificado. Uma redefinição pode ser invocada em três modos diferentes, que correspondem às três árvores.
O checkout e a redefinição costumam ser usados para realizar ações de desfazer locais ou privadas. Eles modificam o histórico de um repositório que pode causar conflitos ao enviar para repositórios compartilhados remotos. A reversão é considerada uma operação segura para a ação de desfazer pública, pois cria um novo histórico que pode ser compartilhado remotamente e não substitui o histórico do qual os membros da equipe remota podem depender.
Referência para Git reset, revert e checkout
A tabela abaixo resume os casos de uso mais comuns para todos esses comandos. Mantenha essa referência à mão, pois sem dúvida você vai precisar usar pelo menos algumas delas durante sua carreira no Git.
Comando | Escopo | Casos de uso comuns |
---|---|---|
| Escopo Nível de commit | Casos de uso comuns Descartar confirmações em uma ramificação privada ou descartar alterações sem confirmação |
| Escopo Nível do arquivo | Casos de uso comuns Desfazer o staging de um arquivo |
| Escopo Nível de commit | Casos de uso comuns Alternar entre os branches ou inspecionar instantâneos antigos |
| Escopo Nível do arquivo | Casos de uso comuns Descartar alterações no diretório de trabalho |
| Escopo Nível de commit | Casos de uso comuns Desfazer commits em um branch público |
| Escopo Nível do arquivo | Casos de uso comuns (N/A) |
Operações de nível de commit
Os parâmetros que você passa para git reset
e git checkout
determinam o escopo. Quando você não inclui um caminho de arquivo como parâmetro, eles operam em commits inteiros. É o que a gente vai explorar nesta seção. Observe que o git revert
não tem equivalente no nível do arquivo.
Redefinir um commit específico
No nível do commit, a redefinição é uma maneira de mover a ponta de um branch para um commit diferente, o que pode ser usado para remover commits do branch atual. Por exemplo, o comando a seguir move o branch de hotfix
para trás por dois commits.
git checkout hotfix git reset HEAD~2
Os dois commits que estavam no final do hotfix
agora são commits órfãos, pendentes. Ou seja: eles vão ser excluídos na próxima vez que o Git realizar uma coleta de lixo. Em outras palavras, você está dizendo que quer jogar fora esses commits. Essa situação pode ser vista da seguinte forma:
Esse uso do git reset
é uma maneira simples de desfazer alterações que não foram compartilhadas com mais ninguém. É o seu comando quando você começa a trabalhar em uma função e se pega pensando: “Caramba, o que estou fazendo? Eu deveria começar de novo.”
Além de mover o branch atual, você também pode usar o git reset
para alterar o instantâneo de staging e/ou o diretório de trabalho passando uma das seguintes marcações:
--soft
— O instantâneo de staging e o diretório de trabalho não são alterados de forma alguma.--mixed
— O instantâneo de staging é atualizado para corresponder ao commit especificado, mas o diretório de trabalho não é afetado. Essa é a opção padrão.--hard
— O instantâneo de staging e o diretório de trabalho são atualizados para corresponder ao commit especificado.
É mais fácil pensar nesses modos como definições do escopo de uma operação git reset
. Para ver mais informações, visite a página git reset.
Fazer checkout de commits antigos
O comando git checkout
é usado para atualizar o estado do repositório para um ponto específico no histórico do projeto. Quando transmitido com um nome de branch, ele permite alternar entre branches.
git checkout hotfix
Internamente, tudo o que o comando acima faz é mover o HEAD
para um branch diferente e atualizar o diretório de trabalho para corresponder a essa mudança. Como essa operação tem o potencial de substituir as alterações locais, o Git força você a fazer o commit ou o stash de quaisquer alterações no diretório de trabalho que vão ser perdidas durante a operação de checkout. Ao contrário do git reset
, o git checkout
não move nenhum branch.
Você também pode fazer o check out de commits arbitrários passando a referência do commit em vez de um branch. É exatamente a mesma coisa que o check out de um branch: a referência HEAD
é movida para o commit especificado. Por exemplo, o comando a seguir vai verificar o avô do commit atual:
git checkout HEAD~2
Ele é útil para fazer a inspeção rápida uma versão antiga do seu projeto. No entanto, como não há referência de branch para o HEAD
atual, você fica em um estado de HEAD
desanexado. Essa situação pode ser perigosa se você começar a adicionar novos commits porque não vai ter como voltar para eles depois que você mudar para outro branch. Por esse motivo, você deve sempre criar um novo branch antes de adicionar commits a um HEAD
desanexado.
Desfazer commits públicos com o comando de reverter
Reverter desfaz um commit criando um novo commit. Essa é uma maneira segura de desfazer alterações, pois não há chance de reescrever o histórico de commits. Por exemplo, o comando a seguir vai identificar as alterações contidas no penúltimo commit, criar um novo commit desfazendo essas alterações e anexar o novo commit no projeto existente.
git checkout hotfix git revert HEAD~2
Essa situação pode ser vista da seguinte forma:
Compare esse comportamento com o do git reset
, que altera o histórico de commits existente. Por esse motivo, o git revert
deve ser usado para desfazer alterações em um branch público, e o git reset
deve ser reservado para desfazer alterações em um branch privado.
Você também pode pensar no git revert
como uma ferramenta para desfazer alterações com commit, enquanto o git reset HEAD
é para desfazer alterações sem commit.
Como o git checkout
, o git revert
tem o potencial de sobrescrever arquivos no diretório de trabalho, então ele vai solicitar que você faça o commit ou o stash das alterações que seriam perdidas durante a operação de reversão.
Operações em nível de arquivo
Os comandos git reset
e git checkout
também aceitam um caminho de arquivo opcional como parâmetro. Assim, o comportamento deles tem uma alteração drástica. Em vez de operar em instantâneos inteiros, eles são forçados a limitar suas operações a um único arquivo.
Git Reset de um arquivo específico
Quando invocado com um caminho de arquivo, o git reset
atualiza o instantâneo de staging para corresponder à versão do commit especificado. Por exemplo, esse comando vai buscar a versão do foo.py
no penúltimo commit e o preparar para o próximo commit:
git reset HEAD~2 foo.py
Assim como na versão de nível de commit do git reset
, essa opção costuma ser mais usada com HEAD
em vez de um commit arbitrário. Executar git reset HEAD foo.py
vai desfazer o staging de foo.py
. As alterações que ele contém ainda vão estar presentes no diretório de trabalho.
As marcações --soft
, --mixed
e --hard
não têm nenhum efeito na versão em nível de arquivo do git reset
, pois o instantâneo de staging sempre é atualizado, e o diretório de trabalho nunca é atualizado.
Git Checkout de um arquivo
Fazer checkout de um arquivo é semelhante a usar o git reset
com um caminho de arquivo, exceto pelo fato de que ele atualiza o diretório de trabalho em vez do ambiente de staging. Ao contrário da versão em nível de commit desse comando, essa ação não move a referência HEAD
, o que significa que você não vai mudar de branch.
Por exemplo, o comando a seguir faz com que o foo.py
no diretório de trabalho corresponda ao do penúltimo commit:
git checkout HEAD~2 foo.py
Assim como a invocação no nível do commit do git checkout
, essa opção pode ser usada para inspecionar versões antigas de um projeto, mas o escopo é limitado ao arquivo especificado.
Se você fizer o staging e o commit do arquivo com checkout, o efeito vai ser o de “reverter” para a versão antiga desse arquivo. Observe que assim você remove todas as alterações subsequentes no arquivo, enquanto o comando git revert
desfaz apenas as alterações introduzidas pelo commit especificado.
Como o git reset
, ele costuma ser usado com HEAD
como referência de commit. Por exemplo, git checkout HEAD foo.py
tem o efeito de descartar alterações que não passaram por staging para foo.py
. Esse é um comportamento semelhante ao de git reset HEAD --hard
, mas opera apenas no arquivo especificado.
Resumo
Agora você deve ter todas as ferramentas necessárias para desfazer alterações em um repositório do Git. Os comandos git reset, git checkout e git revert podem ser confusos, mas quando você pensa sobre seus efeitos no diretório de trabalho, no instantâneo de staging e no histórico de commits, deve ser mais fácil discernir qual comando se encaixa na tarefa de desenvolvimento em questão.
Compartilhar este artigo
Próximo tópico
Leitura recomendada
Marque esses recursos para aprender sobre os tipos de equipes de DevOps ou para obter atualizações contínuas sobre DevOps na Atlassian.