2010-03-29 27 views
41

Alors que reset et checkout ont des usages différents la plupart du temps, je ne vois pas quelle différence il y a entre ces deux.Y a-t-il une différence entre "git reset --hard hash" et "git checkout hash"?

Il y en a probablement un ou personne n'aurait pris la peine d'ajouter une option --hard pour faire quelque chose que la base checkout peut faire.

Peut-être qu'il y a une différence dans la façon dont vous verrez l'histoire?

+1

J'ai couvert cela dans une mise à jour à ma réponse à l'une de vos questions précédentes - regardez l'art ascii près du sommet, en particulier où il dit "Digression: ..." (autant que j'aurais aimé plus de rep pour le re-répondre ici) – Cascabel

+0

Je pense que vous pouvez poster votre réponse ici et obtenir un représentant. Si quelqu'un recherche cette connaissance particulière, il ne trouvera pas l'autre message. Celui-ci vise un sujet très spécifique, et il mérite d'avoir sa page séparée. BTW, il semble que vous êtes mon mentor Git :-) harigato, senseï! –

+1

Mais est-ce que je l'obtiens, la différence est que la réinitialisation déplace la branche et non la caisse. –

Répondre

52

Cette réponse est principalement citée de ma réponse à une question précédente: git reset in plain english.

Les deux sont très différents. Ils aboutissent au même état pour votre index et votre arborescence de travail, mais l'historique et la branche en cours ne sont pas identiques.

Supposons que votre histoire ressemble à ceci, avec la branche principale actuellement vérifié:

- A - B - C (HEAD, master) 

et que vous exécutez git reset --hard B. Vous obtiendrez ceci:

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

Vous souhaitez réellement obtenir cet effet si vous utilisez --mixed ou --soft trop - la seule différence est ce qui arrive à votre arbre de travail et de l'index. Dans le cas --hard, l'arborescence et l'index correspondent à B.

Maintenant, supposons que vous exécutiez git checkout B à la place. Vous obtiendrez ceci:

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

Vous avez fini dans un état détaché HEAD. HEAD, arbre de travail, index tous les match B, même que la réinitialisation matérielle, mais la branche principale a été laissée à C. Si vous faites un commit D à ce stade, vous obtenez ce qui est sans doute pas ce que vous voulez:

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

, vous utilisez votre commande, eh bien, vérifiez que commettent. Vous pouvez jouer avec, faire ce que vous voulez, mais vous avez laissé votre branche derrière vous. Si vous souhaitez également déplacer la branche, vous utilisez la réinitialisation.

+5

+1 comme d'habitude. Ce thread (http://marc.info/?l=git&m=120955970704567&w=2) a également ajouté un effet secondaire: si vous êtes au milieu d'une fusion (par exemple, lorsqu'il y a des conflits de fusion, ou après 'git merge - -no-commit'), 'git reset --hard' oublie environ la fusion, mais' git checkout -f' ne le fait pas; par conséquent, un 'commit git 'après que ce dernier créerait un commit de fusion, ce qui n'est généralement pas ce que vous voulez. – VonC

+0

@VonC: et un excellent point supplémentaire de votre part, comme d'habitude! – Cascabel

14

Si la documentation fournie avec Git ne vous aide pas, jetez un oeil à A Visual Git Reference par Mark Lodato.

En particulier si vous comparez git checkout <non-branch> avec git reset --hard <non-branch> (hotlinked):

git checkout master~3 http://marklodato.github.com/visual-git-guide/checkout-detached.svg.png

git reset --hard master~3 http://marklodato.github.com/visual-git-guide/reset-commit.svg.png

Notez que dans le cas de git reset --hard master~3 vous laissez derrière vous une partie de DAG des révisions - certains des Les commits ne sont référencés par aucune branche. Ceux-ci sont protégés pour (par défaut) 30 jours par reflog; ils seraient finalement taillés (enlevés).

6

git-reset hash définit la référence de branche au hachage donné et, facultativement, la vérifie, avec --hard.

git-checkout hash définit l'arbre de travail sur le hachage donné; et à moins que hash soit un nom de branche, vous finirez avec une tête détachée.

en fin de compte, offres de git avec 3 choses:

    working tree (your code) 
------------------------------------------------------------------------- 
        index/staging-area 
------------------------------------------------------------------------- 
     repository (bunch of commits, trees, branch names, etc) 

git-checkout par défaut mises à jour juste l'index et l'arbre de travail, et peut le cas échéant mettre à jour quelque chose dans le référentiel (avec l'option -b)

git-reset Par défaut, met à jour le référentiel et l'index, et éventuellement l'arbre de travail (avec l'option --hard)

Vous pouvez penser au référentiel comme ceci:

HEAD -> master 

refs: 
    master -> sha_of_commit_X 
    dev -> sha_of_commit_Y 

objects: (addressed by sha1) 

    sha_of_commit_X, sha_of_commit_Y, sha_of_commit_Z, sha_of_commit_A .... 

git-reset manipule ce que les références de la branche pointent.

Supposons que votre histoire ressemble à ceci:

  T--S--R--Q [master][dev] 
     /
    A--B--C--D--E--F--G [topic1] 
        \ 
        Z--Y--X--W [topic2][topic3] 

Gardez à l'esprit que les succursales ne sont que des noms qui avance automatiquement lorsque vous vous engagez.

Vous avez donc les branches suivantes:

master -> Q 
dev -> Q 
topic1 -> G 
topic2 -> W 
topic3 -> W 

Et votre branche actuelle est topic2, à savoir les points de la tête aux sujet2.

HEAD -> topic2 

Ensuite, git reset X réinitialisera le nom topic2 pour pointer vers X; ce qui signifie que si vous faites un commit P sur topic2 branche, les choses vont ressembler à ceci:

  T--S--R--Q [master][dev] 
     /
    A--B--C--D--E--F--G [topic1] 
        \ 
        Z--Y--X--W [topic3] 
          \ 
          P [topic2]