Por que existem duas maneiras de desfazer um arquivo no Git?

1169

Às vezes, o git sugere git rm --cacheddesestabilizar um arquivo, às vezes git reset HEAD file. Quando devo usar qual?

EDITAR:

D:\code\gt2>git init
Initialized empty Git repository in D:/code/gt2/.git/
D:\code\gt2>touch a

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       a
nothing added to commit but untracked files present (use "git add" to track)

D:\code\gt2>git add a

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   a
#
D:\code\gt2>git commit -m a
[master (root-commit) c271e05] a
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 a

D:\code\gt2>touch b

D:\code\gt2>git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       b
nothing added to commit but untracked files present (use "git add" to track)

D:\code\gt2>git add b

D:\code\gt2>git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   b
#
Senthess
fonte
20
Por quê? Eu diria que é porque a interface de linha de comando do git evoluiu organicamente e nunca foi sujeita a uma grande reestruturação para tornar as coisas consistentes. (Se você não concordar, nota como git rmtanto pode fase uma eliminação e também unstage uma adição )
Roman Starkov
3
@romkyns: Concordo que a interface do Git tem várias esquisitices porque evoluiu organicamente, mas uma remoção é certamente uma função inversa de uma adição, então não é lógico rmdesfazer isso add? Como você acha que rmdeveria se comportar?
Zaz
6
A única resposta real para sua pergunta é que, logo após, git initnão há HEADcomo redefinir.
Miles Rout
Melhores docs para isso: help.github.com/articles/changing-a-remote-s-url
ScottyBlades
4
@Zaz, vou dar a minha opinião. rmimplica exclusão em um contexto unix. Não é o oposto de adicionar ao índice. Uma função para remover arquivos não deve ser sobrecarregada com funções para alterar o estado de preparação. Se houver detalhes de implementação que tornem as combinações convenientes, isso simplesmente indica a falta de uma camada ponderada de abstração no git, o que tornaria a usabilidade clara.
Joshua Goldberg

Respostas:

1893

git rm --cached <filePath> não desestabiliza um arquivo, ele efetivamente prepara a remoção do (s) arquivo (s) do repositório (supondo que ele já foi confirmado anteriormente), mas deixa o arquivo na sua árvore de trabalho (deixando um arquivo não rastreado).

git reset -- <filePath>vai unstage qualquer encenado mudanças para o arquivo fornecido (s).

Dito isto, se você usasse git rm --cachedum novo arquivo preparado, basicamente pareceria que você o desestabilizou, pois nunca havia sido confirmado antes.

Atualizar git 2.24
Nesta versão mais recente do git, você pode usar em git restore --stagedvez de git reset. Veja os documentos do git .

Ryan Stewart
fonte
71
Eu diria que git rm --cacheddesinstala o arquivo, mas não o remove do diretório de trabalho.
Pierre de LESPINAY
4
Para remover um arquivo preparado para adição para que ele não seja mais preparado, certamente pode ser chamado de "desagrupar um arquivo preparado para adição", certo? O resultado final não é uma exclusão em etapas , isso é certo, portanto, acho que o mal-entendido é totalmente compreensível.
Roman Starkov
4
Então, normalmente, alguém usaria git rm --cached <filePath>para remover alguns arquivos do repositório depois de perceber que nunca deveria estar no repositório: provavelmente executando este comando e adicionando os arquivos relevantes ao gitignore. Estou correcto?
Adrien Seja
13
Com tantos votos em perguntas e respostas, eu diria que aparentemente queremos ter um unstagecomando git.
milosmns
4
"git status" aconselha agora: use "git restore --staged <file> ..." para unstage
yucer 28/08/19
334

git rm --cachedé usado para remover um arquivo do índice. No caso em que o arquivo já esteja no repositório, ele git rm --cachedserá removido do índice, deixando-o no diretório ativo e uma confirmação também o removerá do repositório. Basicamente, após a confirmação, você teria desversionado o arquivo e mantido uma cópia local.

git reset HEAD file(que por padrão está usando o --mixedsinalizador) é diferente, pois, no caso em que o arquivo já está no repositório, ele substitui a versão do índice pelo arquivo do repositório (HEAD), efetivamente desestabilizando as modificações nele.

No caso de arquivo não versionado, ele vai desestabilizar o arquivo inteiro, pois o arquivo não estava lá no HEAD. Nesse aspecto git reset HEAD filee git rm --cachedsão iguais, mas não são iguais (conforme explicado no caso de arquivos já existentes no repositório)

Para a questão de Why are there 2 ways to unstage a file in git?- nunca há realmente apenas uma maneira de fazer algo no git. Essa é a beleza disso :)

manojlds
fonte
7
A resposta aceita e essa são ótimas, e explique por que você usaria uma contra a outra. Mas eles não respondem diretamente à pergunta implícita de por que o git sugere dois métodos diferentes. No primeiro caso no exemplo do OP, um git init acabou de ser feito. Nesse caso, o git sugere "git rm --cached" porque nesse momento não há confirmações no repositório e, portanto, o HEAD não é válido. "git reset HEAD - a" produz: "fatal: falha ao resolver 'HEAD' como uma referência válida."
sootsnoot
5
com 'git checkout', você não perderia todas as alterações feitas no arquivo? Isso não é a mesma coisa que desagrupar um arquivo, a menos que eu esteja entendendo errado.
John Deighan
there is never really only one way to do anything in git. that is the beauty of it- Hmm porque ? é sempre ótimo, quando há apenas uma maneira óbvia. isso poupa muito do nosso tempo e da memória no cérebro))
Oto Shavadze
128

Muito simples:

  • git rm --cached <file> faz com que o git pare de rastrear o arquivo completamente (deixando-o no sistema de arquivos, ao contrário do simples git rm*)
  • git reset HEAD <file> desinstala as modificações feitas no arquivo desde a última confirmação (mas não as reverte no sistema de arquivos, ao contrário do que o nome do comando pode sugerir **). O arquivo permanece sob controle de revisão.

Se o arquivo não estava no controle de revisão antes (ou seja, você está desestabilizando um arquivo que você havia git addeditado pela primeira vez), os dois comandos têm o mesmo efeito, daí a aparência de serem "duas maneiras de fazer algo" "

* Lembre-se da ressalva que @DrewT menciona em sua resposta, referente a git rm --cachedum arquivo que foi previamente confirmado no repositório. No contexto desta pergunta, de um arquivo que acabou de ser adicionado e ainda não confirmado, não há com o que se preocupar.

** Por um tempo embaraçosamente longo, fiquei com medo de usar o comando git reset por causa de seu nome - e ainda hoje hoje procuro a sintaxe para garantir que não estrague tudo. ( atualização : finalmente dediquei um tempo para resumir o uso de git resetem uma página tldr ; agora, tenho um modelo mental melhor de como ele funciona e uma referência rápida para quando esqueço alguns detalhes.)

valioso
fonte
Égit rm <file> --cached
neonmate
8
Realmente não acho que a edição de 4 de agosto de 2015 nesta resposta tenha sido uma melhoria geral. Pode ter uma correção técnica fixa (não me sinto qualificado para avaliar isso), mas receio que tenha tornado o tom da resposta muito menos acessível, introduzindo uma linguagem como "desmarca o imperativo de começar a rastrear um arquivo não rastreado no momento. ", e usando jargões como" index "e" HEAD ", precisamente o tipo de coisa que assusta os iniciantes. Se alguém puder, edite para restaurar um idioma mais amigável para iniciantes.
waldyrious
5
Concordo com @waldyrious. A resposta original pode não ter sido tirada diretamente do manual do git, mas respondeu à pergunta em um nível técnico suficiente. Os detalhes técnicos deveriam ter sido esclarecidos nos comentários, não como uma edição que obscurecesse a intenção original.
Simon Robb
Reverti a edição. Acredito que a comunidade tenha validado o suficiente (nos comentários anteriores e nos votos) que a edição foi prejudicial à clareza da resposta.
28416 waldyrious
Nota @DrewT avisa que, se estiver usando rm --cachede empurrando, qualquer pessoa que puxe o mesmo ramo terá os arquivos realmente removidos de sua árvore de trabalho.
Tom Hale
53

Este tópico é um pouco antigo, mas ainda quero adicionar uma pequena demonstração, pois ainda não é um problema intuitivo:

me$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   to-be-added
#   modified:   to-be-modified
#   deleted:    to-be-removed
#

me$ git reset -q HEAD to-be-added

    # ok

me$ git reset -q HEAD to-be-modified

    # ok

me$ git reset -q HEAD to-be-removed

    # ok

# or alternatively:

me$ git reset -q HEAD to-be-added to-be-removed to-be-modified

    # ok

me$ git status
# On branch master
# Changes not staged for commit:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   to-be-modified
#   deleted:    to-be-removed
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   to-be-added
no changes added to commit (use "git add" and/or "git commit -a")

git reset HEAD(sem -q) emite um aviso sobre o arquivo modificado e seu código de saída é 1, que será considerado um erro em um script.

Editar: git checkout HEAD to-be-modified to-be-removedtambém funciona para desestágio, mas remove a alteração completamente do espaço de trabalho

Atualização git 2.23.0: De tempos em tempos, os comandos mudam. Agora git statusdiz:

  (use "git restore --staged <file>..." to unstage)

... que funciona para todos os três tipos de mudança

Daniel Alder
fonte
Obrigado, não ficou totalmente claro nas duas primeiras respostas (provavelmente apenas minha ignorância sobre a terminologia) que o git reset deixou as modificações no arquivo localmente (em oposição ao git checkout que as reverteria).
soupdog
Você deve colocar um aviso no início sobre a versão, porque a versão antiga exclui os arquivos nas novas versões
Luis Mauricio
@LuisMauricio qual comando exclui arquivos?
Daniel Alder
@DanielAlder sry, acabei de testar novamente, ele não exclui, meu erro.
Luis Mauricio
36

se você preparou arquivos acidentalmente que não gostaria de confirmar e deseja ter certeza de que mantém as alterações, também pode usar:

git stash
git stash pop

isso executa uma redefinição para HEAD e reaplica suas alterações, permitindo reestabelecer arquivos individuais para confirmação. isso também é útil se você esqueceu de criar um ramo de recurso para solicitações pull ( git stash ; git checkout -b <feature> ; git stash pop).

ives
fonte
3
Esta é uma solução limpa e muito menos preocupante do que digitar "git rm"
sub-imagem
1
git stashtem outros benefícios relacionados, porque cria entradas no reflog que estão disponíveis no futuro. em caso de dúvida, vá em frente e faça um git stash(por exemplo, git stash save -u "WIP notes to self"(o '-u' é incluir qualquer arquivo novo / não rastreado no commit do stash) ... tente git reflog show stashver a lista de commits do stash e seus sha's. Eu recomendo um shell alias comoalias grs="git reflog show stash"
cweekly 12/02/19
15

Esses 2 comandos têm várias diferenças sutis se o arquivo em questão já estiver no repositório e sob controle de versão (confirmado anteriormente etc.):

  • git reset HEAD <file> desestágena o arquivo no commit atual.
  • git rm --cached <file>desestabilizará o arquivo para confirmações futuras também. É sem estágio até que seja adicionado novamente git add <file>.

E há mais uma diferença importante:

  • Depois de executar git rm --cached <file>e enviar sua ramificação para o controle remoto, qualquer pessoa que puxe sua ramificação do controle remoto receberá o arquivo REALMENTE excluído da pasta, embora no conjunto de trabalho local o arquivo se torne não rastreado (ou seja, não excluído fisicamente da pasta).

Essa última diferença é importante para projetos que incluem um arquivo de configuração em que cada desenvolvedor da equipe possui uma configuração diferente (por exemplo, URL de base, IP ou configuração de porta diferente); portanto, se você estiver usando git rm --cached <file>alguém que puxa sua ramificação, precisará refazer manualmente crie a configuração ou envie-os seus e eles poderão editá-los novamente para as configurações de ip (etc.), porque a exclusão afeta apenas as pessoas que puxam sua ramificação do controle remoto.

DrewT
fonte
10

Digamos que você stageexiba um diretório inteiro via git add <folder>, mas você deseja excluir um arquivo da lista faseada (ou seja, a lista que é gerada durante a execução git status) e manter as modificações no arquivo excluído (você estava trabalhando em algo e ele não está pronto para confirmação, mas você não quer perder seu trabalho ...). Você pode simplesmente usar:

git reset <file>

Ao executar git status, você verá que quaisquer arquivos que você reseté unstagede o restante dos arquivos addedainda estão na stagedlista.

jiminikiz
fonte
10

1

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   a

(use "git rm --cached ..." para desestabilizar)

  • git é um sistema de ponteiros

  • você ainda não tem um commit para alterar seu ponteiro para

  • a única maneira de 'retirar arquivos do bucket apontado' é remover os arquivos que você disse ao git para observar as alterações

2)

D:\code\gt2>git commit -m a
[master (root-commit) c271e05] a
0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 a

git commit -ma

  • você comprometeu, ' salvou '

3)

D:\code\gt2>git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   b
#

(use "git reset HEAD ..." para desmontar)

  • você fez um commit no seu código no momento
  • agora você pode redefinir o ponteiro para o seu commit ' reverter para o último salvamento '
Timothy LJ Stewart
fonte
1
Esta é realmente a única resposta que responde adequadamente à pergunta, IMO. Na verdade, ele responde à pergunta, que não é 'quais são as diferenças entre' git rm --cached '' e 'git reset HEAD', mas 'por que o git fornece inconsistentemente as duas opções?', A resposta é que não há HEAD para redefinir para quando você git initpela primeira vez.
Miles Rout
5

Estou surpreso que ninguém tenha mencionado o reflit git ( http://git-scm.com/docs/git-reflog ):

# git reflog
<find the place before your staged anything>
# git reset HEAD@{1}

O reflog é um histórico do git que não apenas rastreia as alterações no repositório, mas também as ações do usuário (por exemplo, pull, checkout para diferentes ramificações, etc.) e permite desfazer essas ações. Portanto, em vez de desagrupar o arquivo que foi preparado por engano, você pode reverter para o ponto em que não os preparou.

Isso é semelhante, git reset HEAD <file>mas em certos casos pode ser mais granular.

Desculpe - realmente não estou respondendo à sua pergunta, mas apenas apontando mais uma maneira de arquivar arquivos que eu uso com frequência (eu gosto de respostas de Ryan Stewart e Waldyrious.);) Espero que ajude.

Alex
fonte
5

Apenas use:

git reset HEAD <filename>

Isso desinstala o arquivo e mantém as alterações feitas nele, para que você possa, por sua vez, alterar ramificações, se desejar, e git addesses arquivos para outra ramificação. Todas as alterações são mantidas.

Edgar Quintero
fonte
3

Parece-me que git rm --cached <file>remove o arquivo do índice sem removê-lo do diretório em que uma planície git rm <file>faria as duas coisas, assim como um sistema operacional rm <file>remove o arquivo do diretório sem remover seu controle de versão.

ernie.cordell
fonte
1

Somente para as versões 2.23 e acima,

Em vez dessas sugestões, você pode usar git restore --staged <file>para unstageo (s) arquivo (s).

Kaan Taha Köken
fonte
Ele funciona tanto com as opções --stagequanto com --staged.
dhana1310 19/03