Close

Git refs: An overview

Découvrez les bases de Git avec ce tutoriel sur le thème de l'espace.

Il existe plusieurs façons de faire référence à un commit.

En comprenant les différentes méthodes de référencement d'un commit, vous pouvez exploiter au mieux ces puissantes commandes. Dans ce chapitre, nous décrirons le fonctionnement interne des commandes courantes comme git checkout, git branch et git push en étudiant les différentes méthodes de référencement d'un commit.

Nous verrons également comment récupérer des commits qui semblent « perdus » en y accédant via le mécanisme reflog de Git.


Empreintes


Le moyen le plus direct pour référencer un commit est d'utiliser l'empreinte SHA-1. Celle-ci constitue l'identifiant unique de chaque commit. Vous pouvez trouver l'empreinte de tous vos commits dans la sortie git log.

commit 0c708fdec272bc4446c6cabea4f0022c2b616eba Author: Mary Johnson  Date: Wed Jul 9 16:37:42 2014 -0500 Some commit message

Lorsque vous transmettez le commit à d'autres commandes Git, vous devez uniquement spécifier suffisamment de caractères pour identifier le commit de façon unique. Par exemple, vous pouvez inspecter le commit ci-dessus avec git show en lançant la commande suivante :

git show 0c708f

Il est parfois nécessaire de résoudre une branche, un tag ou une autre référence indirecte dans l'empreinte de commit correspondante. Pour ce faire, vous pouvez utiliser la commande git rev-parse. Le code suivant renvoie l'empreinte du commit pointé par la branche main :

Bases de données
Ressource connexe

Comment déplacer un dépôt Git complet

Logo Bitbucket
DÉCOUVRIR LA SOLUTION

Découvrir Git avec Bitbucket Cloud

git rev-parse main

Il est particulièrement utile lorsque vous écrivez des scripts personnalisés qui acceptent une référence de commit. Au lieu d'analyser la référence de commit manuellement, vous pouvez laisser git rev-parse normaliser l'entrée pour vous.

Réfs


Une réf est un moyen indirect de référencer un commit. Vous pouvez la considérer comme un alias convivial d'une empreinte de commit. C'est le mécanisme interne de Git pour représenter les branches et les tags.

Les réfs sont stockées sous forme de fichiers texte normaux dans le répertoire .git/refs, où .git se nomme généralement .git. Pour explorer les réfs présentes dans l'un de vos dépôts, accédez à .git/refs. Vous devriez voir la structure suivante. Celle-ci contiendra toutefois des fichiers différents en fonction des branches, des tags et des remotes présents dans votre dépôt :

.git/refs/ heads/ main some-feature remotes/ origin/ main tags/ v0.9

Le répertoire heads définit toutes les branches locales de votre dépôt. Chaque nom de fichier correspond au nom de la branche en question et, à l'intérieur du fichier, vous trouverez une empreinte de commit. Cette empreinte est l'emplacement de la pointe de la branche. Pour le vérifier, essayez de lancer les deux commandes suivantes à partir de la racine du dépôt Git :

# Output the contents of `refs/heads/main` file: cat .git/refs/heads/main # Inspect the commit at the tip of the `main` branch: git log -1 main

L'empreinte renvoyée par la commande cat doit correspondre à l'ID de commit affiché par git log.

Pour modifier l'emplacement de la branche main, Git doit simplement changer le contenu du fichier refs/heads/main. De la même manière, il suffit d'écrire une empreinte de commit dans un nouveau fichier pour créer une branche. C'est l'une des raisons pour lesquelles les branches Git sont si légères par rapport aux branches SVN.

Le répertoire tags fonctionne exactement de la même manière, sauf qu'il contient des tags au lieu de branches. Le répertoire remotes liste tous les dépôts distants que vous avez créés avec git remote en tant que sous-répertoires distincts. À l'intérieur de chacun d'entre eux, vous trouverez toutes les branches distantes qui ont été fetchées de votre dépôt.

Specifying refs

Lorsque vous transmettez une réf à une commande Git, vous pouvez définir le nom complet de la réf, ou utiliser un nom abrégé et laisser Git rechercher une réf correspondante. Vous devez être déjà familiarisé avec les noms abrégés pour les réfs, puisque vous l'utilisez à chaque fois que vous appelez une branche par son nom.

git show some-feature

L'argument some-feature dans la commande ci-dessus est en réalité le nom abrégé de la branche. Git corrige cela en refs/heads/some-feature avant de l'utiliser. Vous pouvez également spécifier la réf complète sur la ligne de commande :

git show refs/heads/some-feature

Ceci évite toute ambiguïté quant à l'emplacement de la réf. C'est notamment nécessaire si vous avez un tag et une branche nommés some-feature. Toutefois, si vous utilisez les conventions de dénomination appropriées, l'ambiguïté entre les tags et les branches ne pose généralement aucun problème.

Nous verrons d'autres noms de réf complets dans la section Refspecs.

Packed refs


Pour les dépôts volumineux, Git effectuera périodiquement un nettoyage de type garbage collection pour supprimer les objets inutiles et compresser les réfs dans un fichier unique afin d'optimiser les performances. Vous pouvez forcer cette compression avec la commande garbage collection :

git gc

Cette commande déplace tous les fichiers de tag et de branche présents dans le dossier refs vers un fichier unique nommé packed-refs et situé au-dessus du répertoire .git. Si vous ouvrez ce fichier, vous trouverez un mappage des empreintes de commit vers les réfs :

00f54250cf4e549fdfcafe2cf9a2c90bc3800285 refs/heads/feature 0e25143693cfe9d5c2e83944bbaf6d3c4505eb17 refs/heads/main bb883e4c91c870b5fed88fd36696e752fb6cf8e6 refs/tags/v0.9

À l'extérieur, la fonctionnalité Git normale ne sera pas affectée. Néanmoins, si vous vous demandez pourquoi le dossier .git/refs est vide, sachez que c'est là que se trouvaient les réfs.

Special refs


Outre le répertoire refs, des réfs spéciales sont également situées dans le répertoire .git de niveau supérieur. Celles-ci sont répertoriées ci-dessous :

  • HEAD : le commit/la branche actuellement extrait(e).
  • FETCH_HEAD : la branche la plus récente fetchée dans un dépôt distant.
  • ORIG_HEAD : une référence de sauvegarde du fichier HEAD avant les changements radicaux effectués.
  • MERGE_HEAD : le(s) commit(s) que vous mergez dans la branche courante avec git merge.
  • CHERRY_PICK_HEAD – Le commit que vous sélectionnez.

Ces réfs sont toutes créées et mises à jour par Git lorsque cela s'avère nécessaire. Par exemple, la commande git pull exécute d'abord git fetch, qui met à jour la référence FETCH_HEAD. Elle lance ensuite git merge FETCH_HEAD pour finir de faire un pull des branches fetchées vers le dépôt. Bien évidemment, vous pouvez toutes les utiliser comme n'importe quelle autre réf, ce que vous avez déjà fait, j'en suis sûr, avec HEAD.

Le contenu de ces fichiers diffère en fonction du type et de l'état de votre dépôt. La réf HEAD peut contenir une réf symbolique, qui est simplement une référence à une autre réf au lieu d'une empreinte de commit, ou une empreinte de commit. Examinons par exemple le contenu de HEAD lorsque vous êtes sur la branche main :

git checkout main cat .git/HEAD

Cela génère ref: refs/heads/main, ce qui signifie que HEAD pointe vers la réf refs/heads/main. C'est de cette manière que Git détermine la branche main en cours d'extraction. Si vous passez à une autre branche, le contenu de HEAD est mis à jour pour refléter la nouvelle branche. Mais si vous extrayez un commit au lieu d'une branche, HEAD contient une empreinte de commit au lieu d'une réf symbolique. Ainsi, Git sait qu'il est à l'état « HEAD détachée ».

Dans la plupart des cas, HEAD est la seule référence que vous utilisez directement. Les autres sont généralement utiles si vous écrivez des scripts de niveau inférieur qui doivent être intégrés dans le fonctionnement interne de Git.

Refspecs


Une refspec fait correspondre une branche dans le dépôt local avec une branche dans le dépôt distant. Ainsi, les branches distantes peuvent être gérées en utilisant des commandes Git locales, et il est possible de configurer un comportement avancé pour git push et git fetch.

Une refspec est spécifiée sous la forme suivante : [+]<src>:<dst>. Le paramètre <src> correspond à la branche source dans le dépôt local et le paramètre <dst> à la branche cible dans le dépôt distant. Le signe + (facultatif) permet de forcer le dépôt distant à effectuer une mise à jour sans fast-forward.

Les refspecs peuvent être utilisées avec la commande git push pour attribuer un nom différent à la branche distante. Par exemple, la commande suivante pushe la branche main vers le dépôt distant origin comme un git push ordinaire , mais elle utilise qa-main comme nom de la branche dans le dépôt origin. Elle est utile pour les équipes qualité qui doivent pusher leurs propres branches vers un dépôt distant.

git push origin main:refs/heads/qa-main

You can also use refspecs for deleting remote branches. This is a common situation for feature-branch workflows that push the feature branches to a remote repo (e.g., for backup purposes). The remote feature branches still reside in the remote repo after they are deleted from the local repo, so you get a build-up of dead feature branches as your project progresses. You can delete them by pushing a refspec that has an empty parameter, like so:

git push origin :some-feature

C'est très pratique, car vous n'avez pas besoin de vous connecter à votre dépôt distant et de supprimer manuellement la branche distante. Notez que depuis la version 1.7.0 de Git, vous pouvez utiliser le flag --delete plutôt que la méthode ci-dessus. Le code suivant aura le même effet que la commande ci-dessus :

git push origin --delete some-feature

En ajoutant quelques lignes dans le fichier de configuration Git, vous pouvez utiliser les refspecs pour modifier le comportement de git fetch. Par défaut, git fetch fetche toutes les branches du dépôt distant. La section suivante du fichier .git/config explique ce comportement :

[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/*:refs/remotes/origin/*

La ligne fetch demande à git fetch de télécharger toutes les branches du dépôt origin. Cependant, certains workflows n'ont pas besoin de toutes les branches. Par exemple, de nombreux workflows d'intégration continue s'appuient uniquement sur la branche main. Pour récupérer uniquement la branche main, modifiez la ligne fetch pour correspondre à ce qui suit :

[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main

Vous pouvez aussi configurer git push de façon similaire. Par exemple, si vous souhaitez toujours pusher la branche main vers qa-main dans le remote origin (comme nous l'avons fait auparavant), vous devrez modifier le fichier de configuration comme suit :

[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main push = refs/heads/main:refs/heads/qa-main

Les refspecs vous donnent un contrôle total sur la manière dont les différentes commandes Git transfèrent les branches entre les dépôts. Elles vous permettent de renommer et de supprimer des branches dans votre dépôt local, de récupérer/pusher vers des branches avec des noms différents et de renommer git push et git fetch pour ne fonctionner qu'avec les branches de votre choix.

Relative refs


Vous pouvez également référencer des commits en fonction d'un autre commit. Le caractère ~ vous permet d'atteindre les commits parents. Par exemple le code suivant affiche le grand-parent de HEAD :

git show HEAD~2

Néanmoins, lorsque vous travaillez avec des commits de merge, les choses deviennent un peu plus compliquées. Les commits de merge ayant plusieurs parents, vous pouvez suivre différents chemins. Dans le cas des merges à trois branches, le premier parent est celui de la branche où vous étiez lorsque vous avez fait le merge et le second parent est celui de la branche que vous avez passée à la commande git merge.

Le caractère ~ suit toujours le premier parent d'un commit de merge. Si vous voulez suivre un autre parent, vous devez le spécifier avec le caractère ^. Par exemple, si HEAD est un commit de merge, le code suivant renvoie le second parent de HEAD.

git show HEAD^2

Vous pouvez utiliser plusieurs caractères ^ pour déplacer plusieurs générations. Par exemple, ce code affiche le grand-parent de HEAD (en supposant qu'il s'agit d'un commit de merge) qui est situé sur le second parent.

git show HEAD^2^1

Illustrant le fonctionnement de ~ et ^, la figure suivante montre comment accéder à n'importe quel commit depuis le point A en utilisant des références relatives. Dans certains cas, il existe plusieurs chemins pour atteindre un commit.

Accéder aux commits à l'aide de réfs relatives

Les réfs relatives peuvent être utilisées avec les mêmes commandes qu'une réf normale. Par exemple, toutes les commandes suivantes utilisent une référence relative :

# Only list commits that are parent of the second parent of a merge commit git log HEAD^2 # Remove the last 3 commits from the current branch git reset HEAD~3 # Interactively rebase the last 3 commits on the current branch git rebase -i HEAD~3

The reflog


Le reflog est le filet de sécurité de Git. Il enregistre chacun des changements que vous effectuez dans votre dépôt, que vous ayez commité un instantané ou non. Vous pouvez le considérer comme un historique chronologique de toutes vos actions dans le dépôt local. Pour afficher le reflog, exécutez la commande git reflog. Celle-ci devrait générer quelque chose de similaire à ce qui suit :

400e4b7 HEAD@{0}: checkout: moving from main to HEAD~2 0e25143 HEAD@{1}: commit (amend): Integrate some awesome feature into `main` 00f5425 HEAD@{2}: commit (merge): Merge branch ';feature'; ad8621a HEAD@{3}: commit: Finish the feature

Ceci peut se traduire comme suit :

  • Vous venez d'extraire HEAD~2.
  • Avant cette opération, vous avez modifié un message de commit.
  • Auparavant, vous avez fait un merge de la branche feature dans la branche main.
  • Avant de faire un commit d'un instantané

La syntaxe HEAD{} vous permet de référencer les commits stockés dans le reflog. Son fonctionnement ressemble beaucoup à celui des références HEAD~ dans la section précédente, mais le renvoie à une entrée du reflog au lieu de l'historique des commits.

Vous pouvez l'utiliser pour restaurer l'état qui, autrement, serait perdu. Supposons pas exemple que vous venez de supprimer une nouvelle fonctionnalité avec git reset. Votre reflog devrait ressembler à ce qui suit :

ad8621a HEAD@{0}: reset: moving to HEAD~3 298eb9f HEAD@{1}: commit: Some other commit message bbe9012 HEAD@{2}: commit: Continue the feature 9cb79fa HEAD@{3}: commit: Start a new feature

Les trois commits avant git reset sont désormais libres (dangling), ce qui signifie qu'ils ne peuvent pas être référencés à moins d'utiliser le reflog. Vous vous rendez ensuite compte que vous n'auriez pas dû supprimer tout votre travail. Dans ce cas, il vous suffit d'extraire le commit HEAD@{1} pour restaurer l'état de votre dépôt avant l'exécution de git reset.

git checkout HEAD@{1}

Vous passerez ensuite à l'état HEAD détaché. Vous pouvez créer une nouvelle branche et continuer à travailler sur votre fonctionnalité.

Résumé


Vous êtes désormais familiarisé avec le référencement des commits dans un dépôt Git. Nous avons appris comment les branches et les tags sont stockés en tant que réfs dans le sous-répertoire .git, comment lire un fichier packed-refs, comment HEAD est représenté, comment utiliser les refspecs pour faire un push ou un fetch, et comment utiliser les opérateurs relatifs ~ et ^ pour traverser une hiérarchie de branche.

Nous avons également examiné le reflog, lequel permet de référencer des commits qui ne sont pas disponibles d'une autre manière. C'est une excellente méthode pour se sortir des situations où l'on se dit : « Oups, je n'aurais pas dû faire ça ».

L'objectif étant que vous soyez capable de sélectionner le commit dont vous avez besoin dans un scénario de développement donné. Il est très facile d'exploiter les compétences que vous avez acquises dans cet article avec vos connaissances Git existantes, car certaines des commandes les plus courantes acceptent les réfs comme arguments, notamment git log, git show, git checkout, git reset, git revert, git rebase, et bien d'autres.


Partager cet article

Lectures recommandées

Ajoutez ces ressources à vos favoris pour en savoir plus sur les types d'équipes DevOps, ou pour les mises à jour continues de DevOps chez Atlassian.

Des personnes qui collaborent à l'aide d'un mur rempli d'outils

Le blog Bitbucket

Illustration DevOps

Parcours de formation DevOps

Démos Des démos avec des partenaires d'Atlassian

Fonctionnement de Bitbucket Cloud avec Atlassian Open DevOps

Inscrivez-vous à notre newsletter DevOps

Thank you for signing up