Em inglês simples, o que "git reset" faz?

674

Vi posts interessantes explicando sutilezas git reset.

Infelizmente, quanto mais eu leio sobre isso, mais parece que eu não o entendo completamente. Eu venho de um background em SVN e o Git é um paradigma totalmente novo. Fiquei mercurial facilmente, mas o Git é muito mais técnico.

Eu acho que git resetestá perto hg revert, mas parece que há diferenças.

Então, o que exatamente faz git reset? Inclua explicações detalhadas sobre:

  • as opções --hard, --softe --merge;
  • a notação estranha que você usa HEADcomo HEAD^e HEAD~1;
  • casos de uso e fluxos de trabalho concretos;
  • consequências na cópia de trabalho, HEADe no seu nível de estresse global.
e-satis
fonte
17
Eu acho que uma referência visual do Git fornece uma boa visão do que acontece ao usar comandos git comuns.

Respostas:

992

Em geral, git reseta função de é pegar a ramificação atual e redefini-la para apontar para outro lugar, e possivelmente trazer o índice e a árvore de trabalho. Mais concretamente, se o seu ramo principal (atualmente com check-out) for assim:

- A - B - C (HEAD, master)

e você percebe que deseja que o mestre aponte para B, não para C, você o git reset Bmoverá para lá:

- A - B (HEAD, master)      # - C is still here, but there's no branch pointing to it anymore

Digressão: é diferente de um checkout. Se você executasse git checkout B, obteria o seguinte:

- A - B (HEAD) - C (master)

Você acabou em um estado HEAD desanexado. HEAD, árvore de trabalho, indexe todas as correspondências B, mas o ramo principal foi deixado para trás em C. Se você fizer um novo commit Dnesse momento, conseguirá isso, o que provavelmente não é o que você deseja:

- A - B - C (master)
       \
        D (HEAD)

Lembre-se, redefinir não faz confirmações, apenas atualiza um ramo (que é um ponteiro para uma confirmação) para apontar para uma confirmação diferente. O restante são apenas detalhes do que acontece com seu índice e sua árvore de trabalho.

Casos de uso

Abordo muitos dos principais casos de uso git resetnas descrições das várias opções na próxima seção. Pode realmente ser usado para uma grande variedade de coisas; o encadeamento comum é que todos eles envolvem redefinir a ramificação, índice e / ou árvore de trabalho para apontar para / corresponder a um determinado commit.

Coisas para ter cuidado

  • --hardpode fazer com que você realmente perca o trabalho. Ele modifica sua árvore de trabalho.

  • git reset [options] commitpode fazer com que você (meio que) perca commits. No exemplo de brinquedo acima, perdemos o commit C. Ele ainda está no repositório e você pode encontrá-lo olhando para git reflog show HEADou git reflog show master, mas não é mais acessível a partir de nenhum ramo.

  • O Git exclui permanentemente essas confirmações após 30 dias, mas até então você pode recuperar C apontando uma ramificação para ela novamente ( git checkout C; git branch <new branch name>).

Argumentos

Parafraseando a página de manual, o uso mais comum é o formato git reset [<commit>] [paths...], que redefinirá os caminhos especificados para seu estado a partir do commit fornecido. Se os caminhos não forem fornecidos, a árvore inteira será redefinida e, se o commit não for fornecido, será considerado HEAD (o commit atual). Esse é um padrão comum nos comandos git (por exemplo, checkout, diff, log, embora a semântica exata varie), portanto, não deve ser muito surpreendente.

Por exemplo, git reset other-branch path/to/fooredefine tudo no caminho / to / foo para seu estado em outra ramificação, git reset -- .redefine o diretório atual para seu estado em HEAD e um simples git resetredefine tudo para seu estado em HEAD.

A principal árvore de trabalho e opções de índice

Existem quatro opções principais para controlar o que acontece com sua árvore de trabalho e o índice durante a redefinição.

Lembre-se, o índice é a "área intermediária" do git - é para onde as coisas vão quando você diz git addem preparação para confirmar.

  • --hardfaz tudo corresponder ao commit que você redefiniu. Provavelmente é o mais fácil de entender. Todas as suas alterações locais são derrotadas. Um uso principal é desperdiçar seu trabalho, mas não mudar de consolidação: git reset --hardsignifica git reset --hard HEAD, ou seja, não altere o ramo, mas livre-se de todas as alterações locais. O outro é simplesmente mover uma ramificação de um lugar para outro e manter a árvore de índice / trabalho sincronizada. É esse que realmente pode fazer você perder o trabalho, porque modifica sua árvore de trabalho. Tenha muita certeza de que deseja jogar fora o trabalho local antes de executar qualquer reset --hard.

  • --mixedé o padrão, ou seja, git resetsignifica git reset --mixed. Redefine o índice, mas não a árvore de trabalho. Isso significa que todos os seus arquivos estão intactos, mas quaisquer diferenças entre a confirmação original e a que você redefiniu serão exibidas como modificações locais (ou arquivos não rastreados) com status git. Use isso quando perceber que fez alguns comprometimentos incorretos, mas deseja manter todo o trabalho que fez para poder corrigi-lo e confirmar novamente. Para confirmar, você precisará adicionar arquivos ao índice novamente ( git add ...).

  • --softnão toca no índice ou na árvore de trabalho. Todos os seus arquivos estão intactos --mixed, mas todas as alterações são exibidas como changes to be committedno status git (ou seja, registradas em preparação para confirmação). Use isso quando perceber que fez alguns comprometimentos ruins, mas o trabalho é bom - tudo o que você precisa fazer é reconfirmar de forma diferente. O índice é intocado, portanto, você pode confirmar imediatamente, se desejar - o commit resultante terá todo o mesmo conteúdo que você estava antes de redefinir.

  • --mergefoi adicionado recentemente e tem como objetivo ajudá-lo a interromper uma falha na mesclagem. Isso é necessário porque git merge, na verdade, permitirá que você tente mesclar uma árvore de trabalho suja (uma com modificações locais), desde que essas modificações estejam em arquivos não afetados pela mesclagem. git reset --mergeredefine o índice (como --mixed- todas as alterações aparecem como modificações locais) e redefine os arquivos afetados pela mesclagem, mas deixa os outros em paz. Esperamos que isso restaure tudo como estava antes da má fusão. Você costuma usá-lo como git reset --merge(significado git reset --merge HEAD) porque deseja apenas redefinir a mesclagem e não mover a ramificação. ( HEADainda não foi atualizado, pois a fusão falhou)

    Para ser mais concreto, suponha que você modificou os arquivos A e B e tenta mesclar em uma ramificação que modificou os arquivos C e D. A mesclagem falha por algum motivo e você decide anulá-lo. Você usa git reset --merge. Ele traz C e D de volta ao estado em que estavam HEAD, mas deixa suas modificações em A e B sozinhas, pois elas não fizeram parte da tentativa de mesclagem.

Quer saber mais?

Eu acho que man git reseté realmente muito bom para isso - talvez você precise de um pouco de noção da maneira como o git funciona para que eles realmente se interessem. Em particular, se você reservar um tempo para lê-los cuidadosamente, essas tabelas detalhando os estados dos arquivos no índice e na árvore de trabalho para todas as várias opções e casos são muito úteis. (Mas sim, eles são muito densos - estão transmitindo muitas informações acima de uma forma muito concisa.)

Notação estranha

A "notação estranha" ( HEAD^e HEAD~1) que você mencionou é simplesmente uma abreviação para especificar confirmações, sem ter que usar um nome de hash como 3ebe3f6. Está totalmente documentado na seção "especificando revisões" da página de manual do git-rev-parse, com muitos exemplos e sintaxe relacionada. O sinal de intercalação e o til realmente significam coisas diferentes :

  • HEAD~é a abreviação HEAD~1e significa o primeiro pai do commit. HEAD~2significa o primeiro pai do commit. Pense HEAD~nem "n confirmado antes de HEAD" ou "o ancestral de nona geração de HEAD".
  • HEAD^(ou HEAD^1) também significa o primeiro pai do commit. HEAD^2significa o segundo pai do commit . Lembre-se, um commit de mesclagem normal tem dois pais - o primeiro pai é o commit mesclado e o segundo pai é o commit que foi mesclado. Em geral, as mesclagens podem ter muitos pais arbitrariamente (mesclas de polvos).
  • Os ^e ~operadores podem ser enfiadas em conjunto, como no HEAD~3^2, o segundo progenitor do antepassado-de terceira geração HEAD, HEAD^^2, o segundo progenitor da primeira controladora de HEAD, ou mesmo HEAD^^^, o que é equivalente a HEAD~3.

acento circunflexo e til

Cascabel
fonte
"você usará o git reset para movê-lo para lá." por que você não usa o git checkout para fazer isso?
e-satis
5
@ e-satis: o git checkout moverá HEAD, mas deixe o ramo onde estava. Isto é para quando você deseja mover o ramo.
Cascabel 27/03
Então, se eu entender bem, redefinir B faria: - A - B - C - B (mestre) enquanto o checkout B faria - A - B (mestre)?
e-satis
32
os documentos são bons, mesmo que demore uma eternidade para lê-los e sejam muito densos e demore uma eternidade para verificar se eles dizem que funciona como se você já soubesse como funciona. Não parece que os documentos são bons para mim ...
Kirby
4
Um muito simples e explicação compreensível é dada por esta resposta SO: stackoverflow.com/questions/3528245/whats-the-difference-between-git-reset-mixed-soft-and-hard
Nitin Bansal
80

Lembre-se de que gitvocê tem:

  • o HEADponteiro , que informa em qual commit você está trabalhando
  • a árvore de trabalho , que representa o estado dos arquivos no seu sistema
  • a área de preparação (também chamada de índice ), que "muda" as mudanças para que elas possam ser posteriormente confirmadas juntas

Inclua explicações detalhadas sobre:

--hard, --soft E --merge;

Em ordem crescente de perigo:

  • --softse move, HEADmas não toca na área intermediária ou na árvore de trabalho.
  • --mixed movimentos HEAD e atualiza a área de preparação, mas não a árvore de trabalho.
  • --merge movimentos HEAD , redefine a área intermediária e tenta mover todas as alterações na sua árvore de trabalho para a nova árvore de trabalho.
  • --hardmove HEAD e ajusta a área de preparação e a árvore de trabalho para o novo HEAD, jogando tudo fora.

casos de uso e fluxos de trabalho concretos;

  • Use --softquando quiser mudar para outro commit e consertar as coisas sem "perder o seu lugar". É muito raro você precisar disso.

-

# git reset --soft example
touch foo                            // Add a file, make some changes.
git add foo                          // 
git commit -m "bad commit message"   // Commit... D'oh, that was a mistake!
git reset --soft HEAD^               // Go back one commit and fix things.
git commit -m "good commit"          // There, now it's right.

-

  • Use --mixed(que é o padrão) quando quiser ver como são as coisas em outro commit, mas não deseja perder nenhuma alteração que já tenha.

  • Use --mergequando quiser mudar para um novo local, mas incorpore as alterações que você já possui na árvore de trabalho.

  • Use --hardpara limpar tudo e iniciar uma nova lousa no novo commit.

John Feminella
fonte
2
Esse não é o caso de uso pretendido reset --merge. Não realiza uma mesclagem de três vias. É realmente apenas para redefinir fusões conflitantes, conforme descrito nos documentos. Você vai querer usar checkout --mergepara fazer o que está falando. Se você deseja mover o ramo também, acho que a única maneira é seguir com um checkout / redefinir para arrastá-lo.
Cascabel 27/03
@Jefromi »Sim, não expressei isso muito bem. Por "um novo local", eu quis dizer "um lugar fresco onde você não tem a fusão conflitante".
John Marella
1
Ah entendo. Eu acho que o importante aqui é que, a menos que você realmente saiba o que está fazendo, provavelmente nunca desejará usar reset --mergeoutro alvo além (o padrão) HEAD, porque nos casos além de interromper uma fusão conflitante, isso será descartado. informações que você poderia salvar.
Cascabel 27/03
2
Eu encontrei esta resposta o mais simples e mais útil
Jazzepi
Por favor, adicione informações sobre estes comandos: git resete git reset -- ..
Flimm
35

O post Reset Demystified no blog Pro Git fornece uma explicação muito fácil sobre git resete git checkout.

Após toda a discussão útil na parte superior desse post, o autor reduz as regras para as três etapas simples a seguir:

É basicamente isso. O resetcomando substitui essas três árvores em uma ordem específica, parando quando você pede.

  1. Mova qualquer ramo que HEAD aponte para (pare se --soft)
  2. ENTÃO, faça com que o Índice fique assim (pare aqui, a menos --hard)
  3. ENTÃO, faça com que o Diretório de Trabalho se pareça com esse

Também existem --mergee --keepopções, mas prefiro manter as coisas mais simples por enquanto - isso será para outro artigo.

Daniel Hershcovich
fonte
25

Quando você compromete algo para o git, primeiro você precisa preparar (adicionar ao índice) suas alterações. Isso significa que você deve adicionar ao git todos os arquivos que deseja incluir neste commit antes que o git os considere parte do commit. Vamos primeiro dar uma olhada na imagem de um repositório git: insira a descrição da imagem aqui

então, é simples agora. Temos que trabalhar no diretório de trabalho, criando arquivos, diretórios e tudo. Essas alterações são não rastreadas. Para controlá-los, precisamos adicioná-los ao índice git usando o comando git add . Uma vez que eles são adicionados ao índice git. Agora podemos confirmar essas alterações, se queremos enviá-las para o repositório git.

Mas, de repente, descobrimos ao confirmar que temos um arquivo extra que adicionamos no índice não é necessário para enviar o repositório git. Isso significa que não queremos esse arquivo no índice. Agora a questão é como remover esse arquivo do índice git. Como usamos o git add para colocá-los no índice, seria lógico usar o git rm ? Errado! O git rm simplesmente exclui o arquivo e adiciona a exclusão ao índice. Então o que fazer agora:

Usar:-

git reset

Limpa seu índice, deixa seu diretório de trabalho intocado. (simplesmente desestabilizando tudo).

Ele pode ser usado com várias opções. Existem três opções principais para usar com o git reset: --hard, --soft e --mixed . Isso afeta o que é reiniciado, além do ponteiro HEAD, quando você reinicia.

Primeiro, --hard redefine tudo. Seu diretório atual seria exatamente como seria se você seguisse esse ramo o tempo todo. O diretório de trabalho e o índice são alterados para essa confirmação. Esta é a versão que eu uso com mais frequência. git reset --hard é algo como svn revert .

A seguir, o oposto completo, macio , não redefine a árvore de trabalho nem o índice. Apenas move o ponteiro HEAD. Isso deixa seu estado atual com quaisquer alterações diferentes da confirmação para a qual você está alternando no diretório e "preparada" para confirmação. Se você fizer uma confirmação localmente, mas não a enviar para o servidor git, poderá redefinir a confirmação anterior e confirmar novamente com uma boa mensagem de confirmação.

Finalmente, --mixed redefine o índice, mas não a árvore de trabalho. Portanto, as mudanças ainda estão lá, mas são "sem etapas" e precisam ser adicionadas ao git ou commit do git -a . às vezes usamos isso se comprometemos mais do que pretendíamos com o git commit -a, podemos recuperar o commit com o git reset --mixed, adicionar as coisas que queremos confirmar e apenas confirmar.

Diferença entre git revert e git reset : -


Em palavras simples, git reset é um comando para "corrigir erros não confirmados" e git revert é um comando para "corrigir erros cometidos" .

Isso significa que, se tivermos cometido algum erro em alguma alteração e confirmado e pressionado o mesmo para o git repo, o git revert é a solução. E se identificamos o mesmo erro antes de enviar / confirmar, podemos usar o git reset para corrigir o problema.

Espero que ajude você a se livrar de sua confusão.

amor
fonte
2
Esta é uma boa resposta em inglês simples, conforme solicitado pelo OP.
Episodex 8/17 '13
1
Mesmo que eu tenha perdido isso na sua resposta. O que é git reset HEADpor padrão? --hard, --softOu --mixed? Ótima resposta btw.
Giannis christofakis
1
Ótima resposta, mas eu deixaria mais claro que git reset --hardfará com que você perca alguns dados. E há um ponto que pode estar errado (embora eu não tenha 100% de certeza ... Ainda aprendendo!): Falando sobre --mixedvocê, diz que "às vezes usamos isso se comprometemos mais do que pretendíamos com o git commit -a". Você quis dizer: "se encenássemos mais do que pretendíamos com git stage ."? Se você realmente cometeu, acho que é tarde demais (como você diz no final, o git reset é um comando para "corrigir erros não comunicados")
Fabio diz Reinstate Monica
6

TL; DR

git resetredefine o armazenamento temporário para o último commit. Use --hardtambém para redefinir os arquivos no seu diretório de trabalho até a última confirmação.

VERSÃO MAIS LONGA

Mas isso é obviamente simplista, portanto, as muitas respostas bastante detalhadas. Fazia mais sentido para mim ler git resetno contexto de desfazer mudanças. Por exemplo, veja isto:

Se o git revert for uma maneira "segura" de desfazer alterações, você pode pensar no git reset como o método perigoso. Quando você desfaz com o git reset (e as confirmações não são mais referenciadas por qualquer ref ou reflog), não há como recuperar a cópia original - é uma anulação permanente. É preciso ter cuidado ao usar esta ferramenta, pois é um dos únicos comandos do Git com potencial para perder seu trabalho.

Em https://www.atlassian.com/git/tutorials/undoing-changes/git-reset

e isto

No nível de confirmação, a redefinição é uma maneira de mover a ponta de uma ramificação para uma confirmação diferente. Isso pode ser usado para remover confirmações da ramificação atual.

Em https://www.atlassian.com/git/tutorials/resetting-checking-out-and-reverting/commit-level-operations

Snowcrash
fonte
2

Esteja ciente de que esta é uma explicação simplificada, pretendida como o primeiro passo na tentativa de entender essa funcionalidade complexa.

Pode ser útil para alunos visuais que desejam visualizar como é o estado do projeto após cada um destes comandos:


Para aqueles que usam o Terminal com a cor ativada (git config --global color.ui auto):

git reset --soft A e você verá o material de B e C em verde (preparado e pronto para confirmar)

git reset --mixed A(ou git reset A) e você verá o material de B e C em vermelho (sem estágio e pronto para ser encenado (verde) e depois confirmado)

git reset --hard A e você não verá mais as alterações de B e C em nenhum lugar (será como se elas nunca existissem)


Ou para aqueles que usam um programa GUI como 'Tower' ou 'SourceTree'

git reset --soft A e você verá as coisas de B e C na área 'arquivos preparados' prontas para confirmar

git reset --mixed A(ou git reset A) e você verá as coisas de B e C na área 'arquivos não-estaduais', prontas para serem movidas para encenadas e depois confirmadas

git reset --hard A e você não verá mais as alterações de B e C em nenhum lugar (será como se elas nunca existissem)

timhc22
fonte
1

O checkout aponta o cabeçalho para um commit específico.

Redefinir aponta uma ramificação para uma confirmação específica. (Uma ramificação é um ponteiro para uma confirmação.)

Aliás, se sua cabeça não apontar para um commit que também é apontado por um ramo, você terá uma cabeça desanexada. (acabou por estar errado. Ver comentários ...)

Ian Warburton
fonte
1
Não para nitpick, mas (sim, na verdade é nitpicking, mas vamos adicioná-lo para conclusão) sua terceira frase é tecnicamente falsa. Digamos que seu HEAD esteja apontando para o ramo B, que por sua vez está apontando para confirmar o abc123. Se agora você finalizar a confirmação do abc123, seu HEAD e a ramificação B apontam para confirmar o abc123 E seu HEAD é desanexado. Confirmar neste momento não atualizará a posição da filial B. Você poderia ter dito "se sua cabeça não aponta para um ramo, então você tem uma cabeça destacada"
RomainValeri
@RomainValeri O que o comprometimento fará nessa circunstância?
Ian Warburton
1
A confirmação criaria confirmações que não são referenciadas por nenhuma ramificação, e a ramificação B continuaria apontando para a mesma confirmação abc123, mesmo depois que você confirmava várias vezes depois disso. Isso implica que esses commits se tornariam candidatos à coleta de lixo quando o HEAD parar de apontar para o último commit nesta série "selvagem" de commits.
RomainValeri 8/11