W3docs

git reset

Encontre informações sobre o comando git reset, aprenda sobre as três árvores do Git e veja exemplos práticos de uso.

git reset

git reset é o principal comando para desfazer alterações no repositório local. Dependendo do modo escolhido, ele pode remover um arquivo da área de staging, descartar alterações preparadas ou mover um branch para um commit anterior. Por poder reescrever o ponto de referência do branch e descartar trabalho, é um comando poderoso, mas fácil de usar incorretamente.

Esta página explica as três "árvores" em que o git reset opera, percorre seus três modos (--soft, --mixed, --hard) com exemplos e mostra quando usar o git revert em seu lugar.

Quando usar git reset

Use o git reset quando quiser reescrever o histórico local ou a área de staging que ainda não foram compartilhados:

  • Remover um arquivo da área de staging adicionado por engano: git reset <file>.
  • Limpar toda a área de staging para reconstruir o próximo commit do zero: git reset.
  • Descartar commits e alterações locais completamente: git reset --hard <commit>.
  • Combinar ou reescrever os últimos commits antes do push.

Se os commits que você quer desfazer já foram enviados para um branch compartilhado, use o git revert — ele registra um novo commit que reverte as alterações sem reescrever o histórico do qual outras pessoas dependem.

Git reset e as três árvores

O git reset possui três formas de invocação que correspondem aos três sistemas internos de gerenciamento de estado do Git, frequentemente chamados de três árvores do Git:

  • HEAD — o histórico de commits (o snapshot para o qual o branch aponta atualmente).
  • Staging index — as alterações preparadas para o próximo commit.
  • Working directory — os arquivos em disco no seu editor.

Vamos examinar cada um desses sistemas.

O working directory

A primeira árvore é o working directory. Ela representa os arquivos no sistema de arquivos do seu computador que o editor pode modificar. O working directory é sincronizado com um commit específico do projeto obtido via checkout: quando um projeto é obtido, o Git extrai versões descompactadas dos arquivos do repositório para o disco.

Se editarmos um arquivo rastreado, o git status o reporta como uma alteração modificada e não preparada no working directory:

echo 'hello git reset' > edited_file
git status 
#On branch master 
#Changes not staged for commit: 
#(use "git add ..." to update what will be committed) 
#(use "git checkout -- ..." to discard changes in working directory) 
#modified: edited_file

Staging index

A segunda árvore é o staging index, que rastreia as alterações preparadas para o próximo commit. Normalmente, o Git oculta os detalhes internos da área de staging. Você também pode vê-la referenciada por outros nomes: cache, directory cache, staged files ou staging area.

Para inspecionar o staging index diretamente, podemos usar git ls-files -s, uma ferramenta de depuração que imprime as entradas preparadas junto com seus hashes de objetos:

git ls-files -s
#543644 a32de29bb3c1d643328b29ae775ad8c2e48c3256 0 edited_file

Histórico de commits

A terceira árvore é o histórico de commits. O comando git commit pega o que está no staging index e registra como um snapshot permanente no histórico:

git commit -am "edit content of test_file"
#[master ab23324] edit the content of edited_file
#1 file changed, 1 insertion(+)
git status
#On branch master
#nothing to commit, working tree clean

No exemplo acima, você pode ver o novo commit com a mensagem "edit content of test_file". As alterações foram vinculadas ao histórico de commits. Nesse ponto, executar o git status não mostra alterações pendentes em nenhuma das árvores. Ao invocar o git log, você verá o histórico de commits. Depois que as alterações passam pelas três árvores, o git reset pode ser utilizado.

Como funciona

À primeira vista, o comando git reset tem algumas semelhanças com o git checkout, pois ambos operam sobre o HEAD. O comando git checkout opera exclusivamente no ponteiro de referência HEAD, enquanto o git reset move tanto o ponteiro HEAD quanto o ponteiro de referência do branch atual. Você entenderá melhor seu comportamento com a ilustração abaixo:

git reset1

Esta ilustração apresenta a sequência de commits no branch master. Como você pode ver, a referência HEAD e a referência do branch master apontam atualmente para o commit d. Veremos como a imagem muda nos casos de git checkout b e git reset b.

git checkout b

Ao executar o comando git checkout, a referência master continua apontando para o commit d. Quanto à referência HEAD, ela foi movida e passou a apontar para o commit b. Como resultado, o repositório está agora em estado de 'detached HEAD'.

git reset2

git reset b

O comando git reset move tanto o HEAD quanto a referência do branch atual para o commit especificado. Ele também pode alterar o estado do staging index e do working directory. Três opções de linha de comando — --soft, --mixed e --hard — controlam o alcance do reset:

git reset3

Opções Principais

Por padrão, o git reset é executado com os argumentos --mixed e HEAD. Portanto, invocar git reset é equivalente a git reset --mixed HEAD. O HEAD aqui é o commit-alvo — você pode substituí-lo por qualquer referência de commit, como um hash SHA-1 (git reset 0a1b2c3) ou um ponteiro relativo (git reset HEAD~2).

A tabela abaixo resume quais árvores cada modo afeta:

ModoHistórico de commits (HEAD)Staging indexWorking directory
--softmovidoinalteradoinalterado
--mixed (padrão)movidoredefinido para o alvoinalterado
--hardmovidoredefinido para o alvoredefinido para o alvo (alterações perdidas)

Uma forma útil de lembrar: --soft mantém as alterações na área de staging, --mixed as mantém como edições não preparadas e --hard as descarta completamente.

git reset4

--hard

--hard é a opção mais poderosa e a mais perigosa. Ela move a referência do histórico de commits para o commit-alvo e força o staging index e o working directory a corresponderem a esse commit. Quaisquer alterações pendentes no staging index e no working directory são descartadas — e essa perda não pode ser desfeita com git reset.

Aviso: --hard exclui trabalho não confirmado permanentemente. Execute git status primeiro para confirmar o que você está prestes a perder.

Para demonstrar, primeiro crie algumas alterações pendentes:

echo 'test content' > test_file
git add test_file
echo 'modified content' >> edited_file

Um novo arquivo chamado test_file foi criado e adicionado à área de staging, e o conteúdo de edited_file foi modificado no working directory. Vamos verificar o estado do repositório com o comando git status:

git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#new file: test_file
#Changes not staged for commit:
#(use "git add ..." to update what will be committed)
#(use "git checkout -- ..." to discard changes in working directory)
#modified: edited_file

Há agora alterações pendentes em duas árvores: o staging index contém o novo test_file, e o working directory contém as modificações em edited_file. Vamos verificar o estado do staging index:

git ls-files -s
#123126 7a32454a5477b1bf4765946147c49509a431f963 0 test_file
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_file

O test_file foi adicionado ao índice. O edited_file foi atualizado, mas o SHA do staging index (d7d77c1b04b5edd5acfc85de0b592449e5303770) permanece o mesmo. Essas alterações estão no working directory. Elas não foram promovidas ao staging index porque não executamos o comando git add. Agora executamos git reset --hard e inspecionamos o novo estado do repositório:

git reset --hard
#HEAD is now at ab23324 update content of edited_file
git status
#On branch master
#nothing to commit, working tree clean
git ls-files -s
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_file

A opção --hard executou um "hard reset". O Git indica que o HEAD aponta para o commit recente ab23324. Em seguida, o estado do repositório é verificado com git status. O Git indica que não há alterações pendentes. Quanto ao estado do staging index, ele foi redefinido para o ponto anterior à adição de test_file. As alterações em edited_file e a adição de test_file foram eliminadas. Essa perda não pode ser desfeita.

--mixed

--mixed é o modo padrão. Ele move os ponteiros de referência e redefine o staging index para o commit-alvo, mas deixa o working directory intocado. As alterações que foram removidas do índice reaparecem como modificações no working directory, portanto nada é perdido — você simplesmente tem a chance de prepará-las novamente.

echo 'new file content' > test_file
git add test_file
echo 'append content' >> edited_file
git add edited_file
git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#new file: test_file
#modified: edited_file
git ls-files -s
#123126 6a32154a5477b1bf4765946147c49509a4323d32 0 test_file
#123126 3c3262db063f9e9426901092c00a3394b4bd3445 0 edited_file

No exemplo acima, test_file foi adicionado e o conteúdo de edited_file foi modificado, e ambas as alterações foram aplicadas ao staging index com git add. Com esse estado do repositório, é hora de invocar git reset:

git reset --mixed
git status
#On branch master
#Changes not staged for commit:
#(use "git add ..." to update what will be committed)
#(use "git checkout -- ..." to discard changes in working directory)
#modified: edited_file
#Untracked files:
#(use "git add ..." to include in what will be committed)
#test_file
#no changes added to commit (use "git add" and/or "git commit -a")
git ls-files -s
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_file

O --mixed é o modo padrão. Ele tem o mesmo efeito que git reset. O git status mostra que há alterações em edited_file e que test_file é um arquivo não rastreado. Esse é o comportamento exato do --mixed. O staging index foi redefinido e as alterações pendentes foram movidas para o working directory.

--soft

O argumento --soft move os ponteiros de referência e para por aí. Ele não toca o staging index nem o working directory, portanto tudo o que estava confirmado permanece preparado e pronto para ser reconfirmado. Este é o modo indicado quando se deseja combinar vários commits em um só.

git reset --soft
git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#modified: edited_file
git ls-files -s
#123126 32a252710639e5da6b515416fd779d0741e4561a 0 edited_file

Um soft reset move apenas o histórico de commits. Por padrão, ele tem como alvo o HEAD. Vamos criar um novo commit e então tentar um reset --soft com um commit-alvo diferente do HEAD:

git commit -m "add changes to edited_file"

Agora nosso repositório tem três commits. Para encontrar o primeiro, verificamos seu ID na saída do git log:

git log
#commit 62e793f6941c7e0d4ad9a1345a175fe8f45cb9df
#Author: w3docs
#Date: Fri Nov 1 14:02:07 2019 -0800
#add changes to edited_file
#commit ab23324a6da9f0dec51ed16d3d8823f28e1a72a
#Author: w3docs
#Date: Fri Nov 1 11:31:58 2019 -0800
#change content of edited_file
#commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
#Author: w3docs
#Date: Thu Sep 31 18:40:29 2019 -0800
#initial commit

A entrada mais abaixo é o commit inicial; usaremos seu ID como alvo para o soft reset. Primeiro, verifique o estado atual do repositório:

git status && git ls-files -s
#On branch master
#nothing to commit, working tree clean
#123126 32a252710639e5da6b515416fd779d0741e4561a  0 edited_file

Agora podemos fazer o soft reset de volta ao primeiro commit:

git reset --soft 780411da3b47117270c0e3a8d5dcfd11d28d04a4
git status && git ls-files -s
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#modified: edited_file
#123126 32a252710639e5da6b515416fd779d0741e4561a  0 edited_file

No exemplo acima, realizamos um soft reset e invocamos o comando combinado git status e git ls-files, que exibe o estado do repositório. O comando git status mostra que há algumas alterações em edited_file, destacando-as como alterações preparadas para o próximo commit. A saída do git ls-files mostra que o staging index permaneceu inalterado e retém o SHA 32a252710639e5da6b515416fd779d0741e4561a. Vamos examinar o histórico de commits após o soft reset com git log:

git log
#commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
#Author: w3docs
#Date: Thu Sep 31 18:40:29 2019 -0800
#initial commit

A saída agora mostra um único commit no histórico. Como em todas as invocações de git reset, o --soft primeiro redefine a árvore de commits. Ao contrário dos exemplos anteriores de --hard e --mixed que tinham HEAD como alvo, este soft reset moveu a árvore de commits de volta no tempo para um commit mais antigo — enquanto o trabalho em si permaneceu com segurança no staging index.

A diferença entre os comandos reset e revert

O git revert é geralmente uma maneira mais segura de desfazer alterações do que o git reset, porque o git reset pode perder trabalho. Um git reset não exclui imediatamente um commit, mas pode deixar o commit órfão — nenhum branch ou tag aponta para ele, portanto não há como acessá-lo diretamente. O Git eventualmente remove objetos órfãos quando executa seu coletor de lixo (git gc), que por padrão descarta objetos inacessíveis com mais de cerca de 90 dias (entradas do reflog expiram após 90 dias, ou 30 dias para as inacessíveis). Até lá, normalmente é possível recuperar um commit órfão com o comando git reflog.

A outra diferença fundamental: o git revert foi projetado para desfazer commits públicos, já compartilhados, adicionando um novo commit de reversão, enquanto o git reset foi projetado para desfazer alterações locais no working directory e no staging index.

Nunca redefina o histórico publicado

Não execute git reset <commit> quando há snapshots após <commit> que já foram enviados para um repositório compartilhado. Depois de publicar um commit, outros desenvolvedores dependem dele. Reescrever ou excluir commits que colegas já baixaram causará históricos divergentes e conflitos de merge dolorosos. Use git reset apenas em commits que existem exclusivamente no seu repositório local. Para desfazer alterações públicas, use o comando git revert.

Exemplos

Remove um arquivo específico da área de staging sem alterar o working directory — remove o arquivo da área de staging mantendo as edições:

git reset <file>

Redefine toda a área de staging para corresponder ao último commit, deixando o working directory inalterado. Isso remove todos os arquivos da área de staging sem sobrescrever as alterações, para que você possa reconstruir o snapshot preparado do zero:

git reset

Redefine tanto a área de staging quanto o working directory para corresponder ao último commit. Isso descarta todas as alterações não confirmadas no working directory também:

git reset --hard

Move a ponta do branch de volta para um determinado commit e redefine a área de staging para corresponder, mas deixa o working directory intocado:

git reset <commit>

Move a ponta do branch atual de volta para um determinado commit e redefine tanto a área de staging quanto o working directory para corresponder a ele:

git reset --hard <commit>

Removendo commits locais

Como mostrado acima, você pode usar git reset para excluir commits no seu repositório local. No exemplo abaixo, git reset --hard HEAD~2 move o branch atual dois commits para trás, removendo os dois snapshots mais recentes do histórico do projeto:

# Create a new file called `yourname.txt` and add some code to it
# Commit it to the project history
git add yourname.txt
git commit -m "Start to develop a project"
# Edit `yourname.txt` again and change some other tracked files, too
# Commit another snapshot
git commit -a -m "Continue developing"
# Scrap the project and remove the related commits
git reset --hard HEAD~2

Removendo arquivos do staging

Um uso muito comum de git reset é ajustar o que vai para o próximo commit. No exemplo abaixo temos dois arquivos, task.txt e index.txt, que foram ambos preparados. O git reset nos permite remover da área de staging as alterações que não pertencem ao próximo commit, para que cada arquivo possa ser confirmado separadamente:

# Edit task.txt and index.txt
# Stage everything in the current directory
git add .
# Realize that the changes in task.txt and index.txt
# should be committed in different snapshots
# Unstage index.txt
git reset index.txt
# Commit only task.txt
git commit -m "Edit task.txt"
# Commit index.txt in a separate snapshot
git add index.txt
git commit -m "Edit index.txt"

Prática

Prática
Quais são as funcionalidades e opções do comando 'git reset' no Git?
Quais são as funcionalidades e opções do comando 'git reset' no Git?
Was this page helpful?