web-dev-qa-db-fra.com

git checkout <commit-hash> vs git checkout branch

Je jouais avec Git et je suis devenu confus ici.

La TÊTE de la branche develop est à
235a6d8

Quand je fais:

git checkout 235a6d8

de n'importe quelle autre branche ou de develop branche, cela me laisse en tête.
Je ne sais pas pourquoi cela se produit lorsque je vérifie le dernier commit sur cette branche.

Quand je fais:

git checkout develop

Je peux basculer pour développer correctement la branche.

Je ne comprends pas la différence entre git checkout <commit-has> et git checkout branchname.
En quoi sont-ils différents?

9
Tooba Iqbal

A _git checkout <commit-hash>_ , Préparez-vous à travailler au-dessus de _<commit>_, en y détachant HEAD (voir la section " DETACHED HEAD") ), et mise à jour de l'index et des fichiers dans l'arborescence de travail.

Pendant qu'un _git checkout <branch>_ fait un changement: il se prépare à travailler sur _<branch>_, basculez dessus en mettant à jour l'index et les fichiers dans l'arborescence de travail, et en pointant HEAD sur la branche .

Ceci est déroutant.

Mark Longair a documenté cette confusion dans " Pourquoi la commande git pour changer de branche s'appelle" _git checkout_ "? "

Il a également écrit en mai 2012: " La terminologie git la plus déroutante ":

Dans CVS et Subversion, "checkout" crée une nouvelle copie locale du code source lié à ce référentiel.
La commande la plus proche dans Git est "_git clone_".
Cependant, dans git, "_git checkout_" est utilisé pour quelque chose de complètement distinct.
En fait, il a deux modes de fonctionnement largement distincts:

  • Pour basculer HEAD pour pointer vers une nouvelle branche ou validation, dans l'utilisation git checkout _<branch>_. Si _<branch>_ est véritablement une branche locale, cela basculera vers cette branche (c'est-à-dire HEAD pointera vers le nom de la référence) ou s'il se résout autrement en commit, détachera HEAD et pointez-le directement sur le nom de l'objet du commit.
  • Pour remplacer un fichier ou plusieurs fichiers dans la copie de travail et l'index par leur contenu à partir d'un commit particulier ou de l'index.
    Cela se voit dans les utilisations: git checkout -- (update from the index) et _git checkout <tree-ish> --_ (où _<tree-ish>_ est généralement un commit).

Dans mon monde idéal, ces deux modes de fonctionnement auraient des verbes différents, et aucun d'eux ne serait "checkout" .

Eh bien ... C'est pourquoi Git 2.23 (Q3 2019) divisera le paiement en:

  • _git restore_ qui met à jour l'arborescence de travail (et éventuellement l'index)
  • _git switch_ qui peut changer de branche, ou en détacher une si demandé, pour que tous les nouveaux commits soient ajoutés à la pointe de cette branche.
7
VonC

Outre la réponse de VonC (et le changement à venir dans Git 2.23), il convient de noter quelques éléments supplémentaires.

Parce que git checkout Fait plusieurs choses différentes, c'est intrinsèquement déroutant.

  • L'une des tâches de git checkout Consiste à remplir l'index et l'arborescence de travail en fonction de la validation cible. Il le fera chaque fois que cela sera autorisé et nécessaire.

  • Une autre consiste à changer le nom de la branche enregistrée dans HEAD, ou à configurer HEAD comme un HEAD détaché au commit spécifié. Il le fera chaque fois que nécessaire (à condition que la première partie permette l'opération de paiement).

Pour git checkout, Il effectuera l'opération seconde en fonction du nom de la branche ou de l'argument de spécificateur de validation que vous lui donnez. Autrement dit, supposons que nous ayons une variable Shell $var Définie sur un mot non vide mais sensible: elle peut être définie sur master, ou peut-être master^{commit} Ou a23456f ou Origin/develop ou quelque chose du genre. Dans tous les cas, nous exécutons maintenant:

git checkout $var

Quel nom ou ID de hachage va dans HEAD? Eh bien, voici comment git checkout Décide:

  • Tout d'abord, git checkout Essaie de résoudre la chaîne que nous venons de lui donner comme nom de branche. Supposons que nous lui ayons donné master ou develop. Est-ce une branche valide et existante? Si c'est le cas, c'est le nom qui devrait aller dans HEAD. Si la caisse réussit, nous aura changé de branche, à cette branche.

  • Sinon, la chaîne que nous venons de lui donner n'est pas un nom de branche après tout (même si elle commence par un, comme dans master~1 Par exemple). Git essaiera - tentera - de le résoudre en un ID de hachage de validation, comme si git rev-parse. Par exemple, a23456f Ressemble bien à un ID de hachage abrégé. Si c'est est un - s'il y a un objet dans la base de données de Git avec un ID commençant par a23456f - alors Git s'assure que cet ID nomme a commit, plutôt que d'un autre objet.1 S'il s'agit d'un ID de hachage de validation, c'est l'ID de hachage qui doit aller dans HEAD, en tant que HEAD détaché. Si la vérification réussit, nous serons maintenant en mode détaché HEAD, au commit donné.

  • Si aucune de ces tentatives ne fonctionne, git checkout Devinera ensuite que peut-être, $var Était censé être un nom de fichier, et essaiera de résoudre ce problème.2 Mais nous ignorerons ce cas particulier ici.

Beaucoup de noms qui ne le sont pas les noms de branche fonctionnent bien ici. Par exemple, Origin/master Est extrêmement susceptible d'être résolu en un ID de hachage de validation. Si v2.1 Est une balise valide, v2.1 Peut être résolu en un ID de hachage de validation. Dans tous ces cas - chaque fois que le résultat $var N'est pas déjà un nom de branche, mais peut être résolu en un ID de hachage de validation - git checkout Tentera pour faire une extraction détachée-HEAD de ce hachage de validation.

Une fois que git checkout A décidé que vous avez demandé à vérifier un commit particulier, soit comme nom de branche à coller dans un HEAD attaché, soit comme ID de hachage de commit à coller dans un HEAD détaché, puis Git cherche à déterminer si cela est autorisé. Cela peut devenir très compliqué! Voir Extraire une autre branche quand il y a des changements non validés sur la branche actuelle pour des notes détaillées sur si et quand c'est autorisé, et rappelez-vous que --force Dit à Git qu'il devrait faire la vérification de toute façon, même si ces règles ne le feraient pas le permettent.

Le TL; DR, cependant, est qu'un ID de hachage brut est toujours une demande pour entrer dans l'état détaché HEAD. Que ce soit le fera entraîner un détachement HEAD dépend de ce test compliqué "est le paiement autorisé").

Notez également que si vous créez une branche dont le nom pourrait être un ID de hachage, tel que cafedad—, les choses deviennent parfois un peu étranges. Toute commande Git qui essaie de l'utiliser comme un nom de la branche réussira , car elle en est une. Toute commande Git qui essaie de l'utiliser comme un ID de hachage court pourrait réussir, car il pourrait être un identifiant de hachage court valide !

Sauf si vous créez des noms de branche stupidement déroutants, ce cas particulier est rarement un problème, car toutes les commandes Git bien écrites essaient le nom de la branche avant l'ID de hachage court. Par exemple, j'ai créé un nom de branche délibérément stupide en utilisant les six premières lettres d'un hachage existant que j'ai trouvé via git log:

$ git branch f9089e 8dca754b1e874719a732bc9ab7b0e14b21b1bc10
$ git rev-parse f9089e
warning: refname 'f9089e' is ambiguous.
8dca754b1e874719a732bc9ab7b0e14b21b1bc10
$ git branch -d f9089e
Deleted branch f9089e (was 8dca754b1e).

Notez l'avertissement: f9089e A été traité comme un nom de branche, car il a été analysé en 8dca754b1e874719a732bc9ab7b0e14b21b1bc10. Après avoir supprimé le nom de branche stupide, le hachage court analyse à nouveau le hachage complet:

$ git rev-parse f9089e
f9089e8491fdf50d941f071552872e7cca0e2e04

Si vous avez créé un nom de branche qui - accidentellement fonctionne comme un hachage court, tel que babe, decade ou cafedad— vous n'avez probablement que tapez le nom abrégé babe ou cafedad lorsque vous entendez la branche. Si vous voulez dire le commit, vous coupez et collez probablement l'ID de hachage complet avec votre souris, ou autre chose.

Le vrai danger se produit ici lorsque vous créez une branche et une balise avec le même nom. Most Les commandes Git ont tendance à préférer la tag, mais git checkout Préfère la branche. C'est une situation très déroutante. Heureusement, c'est facile à corriger: renommez simplement l'une des deux entités, afin que vos noms de branche et de tag ne se heurtent pas.

(Vous pouvez également jouer avec vous-même en créant un nom de branche qui est exactement le même que certains ID de hachage complet existants. Celui-ci est particulièrement désagréable car complet Les ID de hachage ont tendance à avoir la priorité sur les noms de branche, mais encore une fois, git checkout est une exception à cette règle. Il en va de même pour git branch -d, Heureusement.)


1Il existe quatre types d'objets dans n'importe quel référentiel Git: commits, arbres, blobs et annoté balises. Valider les objets stocke les validations. Les objets arborescents et blob sont principalement destinés à l'usage interne de Git, pour stocker les noms de fichiers de manière quelque peu répertoire-ish et pour stocker les données de fichiers. Les objets balises annotés sont les plus délicats: ils stockent l'ID de hachage d'un autre objet. Git peut être invité à prendre une telle balise et find le commit auquel la balise se connecte. À titre de complication particulière, une balise annotée peut finalement conduire à un arbre ou un objet blob, de sorte que certaines balises peuvent ne pas nommer les validations après tout, mais généralement, la plupart des balises finissent par nommer une validation de toute façon.

Si vous utilisez la commande git rev-parse, Vous pouvez utiliser cette astuce de suffixe ^{commit} Pour dire à Git: assurez-vous que l'objet final a le type commit. Si l'objet immédiat a le type annotated-tag, Git "décollera" (suivra jusqu'à sa destination) la balise pour trouver son commit. S'il ne trouve pas de commit - s'il trouve un arbre ou un blob à la place - git rev-parse Crachera un message d'erreur et échouera l'analyse. Tout cela est conçu pour être exactement ce dont vous avez besoin si vous écrivez votre propre script de fantaisie pour faire quelque chose d'utile avec les commits.

(Ce processus de "pelage" se répète si nécessaire, car la cible d'une balise annotée peut être une autre balise annotée. Le verbe peel ici est destiné à rappeler à l'un de peler un oignon: si vous en trouvez un autre couche d'oignon, éplucher à nouveau. Finalement, vous découvrirez ce qui se trouve au centre de l'oignon. :-))

2Notez que l'expansion de $var Vers ce que $var A été défini est effectuée par le Shell (par exemple, par bash), pas par Git. Cela n'a pas d'importance ici en raison des contraintes que j'ai placées sur ce qui peut être dans $var, Mais dans des cas plus compliqués, c'est le cas.

2
torek

Voici une explication simple:

Le HEAD est simplement un pointeur qui conserve la référence à une branche et un commit.

Il est automatiquement déplacé pour pointer vers le nouveau commit dans cette branche spécifique chaque fois que vous créez un nouveau commit.

Lorsque vous exécutez git checkout <branch name>, le pointeur HEAD pointera vers cette branche et le dernier commit dessus, la pointe de la branche.

Si vous extrayez directement un hachage de validation, vous faites le point HEAD vers un commit spécifique mais vers aucune branche spécifique, ce qui signifie que c'est detached.

Afin d'attacher le HEAD à nouveau, il suffit de retirer une branche et ensuite elle sera attachée à elle et pointera vers son dernier commit, ramenant les choses au comportement normal.

Il y a plus à ce que vous pouvez faire dans un état détaché HEAD mais vous en voyez plus dans docs .

1
rbento