Chapitre 8 sur 13

Gitrefs & gitrev : Désigner commits, révisions et intervalles

Laisser un commentaire

Dans les différents chapitres, nous utilisons des commandes auxquelles nous passons des références de commits. Bien que nous utilisions la plupart du temps les empruntes SHA-1, bien d’autres syntaxes existent.

Nous le savons, chaque commit se voit attribué un identifiant unique sous forme d’une emprunte SHA-1. Cependant, il existe d’autres façons de définir des références de commits. On parle au sens large de rev ou revname, pour révision.

Cette notation peut prendre plusieurs formes :

Cependant, bien que les termes ne soient souvent pas bien distingués, il ne faut pas confondre une ref ou refname avec la revname (aussi appellé gitrevision, gitrev, revname ou rev). Une référence est un pointeur vers une révision.

Par exemple refs/heads/master est une référence au pointeur head de la branche master. La nuance est d’importance car la revname ne change pas tandis que la référence – HEAD par exemple – indique un état fluctuant (le dernier commit de la branche courante pour HEAD).

Les références peuvent être absolue ou symbolique. Lorsque l’on précise master à la ligne de commande, on suppose qu’il s’agit de heads/master, mais cela pourrait être tags/master si vous aviez un tag ainsi nommé.

S’il y a une ambiguité, Git la résout en procédent dans cet ordre (first match) :

Les pointeurs de la racine

HEAD représente, comme nous le savons tous, le commit sur lequel est présentement Git – notre répertoire de travail si l’on est pas en état de HEAD détaché.

FETCH_HEAD se “souvient” de la dernière branche distante sur laquelle on a effectué un fetch.

ORIG_HEAD est utilisé par certaines commandes afin d’enregistrer la HEAD de départ – c’est ce qui permet de faire --abort dans un rebase ou un merge par exemple et de revenir à l’état précédent la commande.

MERGE_HEAD et CHERRY_PICK_HEAD enregistrent les commits en cours de merge lors de l’exécution des commandes éponymes.

Les raccourcis de références

Dans nombres d’opérations, on peut utiliser des racourcis pour préciser la référence à utiliser.

On peut préciser une date <refname>@{<date>} (yesterday, five minutes ago, 6 months ago, 1 year 2 months 1 week 4 days 5 hours 1 second ago ou une date absolue 1990-02-17 14:45:06).

Pour préciser le nième ancêtre d’une référence, il suffit de l’indiquer entre accolades <refname>@{<n>}. feature@{5} ou @{5} désignent respectivement les valeurs de feature ou de master cinq commits avant la head.

D’une écriture presque identique, @{-<n>}, sans <refname>, désigne le point ou était HEAD au nième changement avant le stade actuel. Cela remonte donc les git switch et git checkout.

[<branchname>]@{push} ou @{push} désigne la tracking remote de la branche désignée, en d’autres termes, où irait le push si on faisait un git push. Bien entendu, il s’agit de la copie locale de la remote dans refs/remotes/ et non de la remote elle-même. Nous pouvons aussi nous référer à l’upstream via {upstream}, laquelle est, dans une configuration classique, la même branche que celle pour le push.

Afin d’exprimer des commits de manière relative à un parent, il existe la notation <rev>~[<n>]. Le suffixe ~ indique le parent d’un rev et ~<n> indique le nième ancêtre de cette référence. Ainsi HEAD~5 indique le 5ème commit avant HEAD (le parent du parent du parent…).

On trouve également la notation <rev>^. Elle est assez semblable au ~ mais diffère en certains points. <rev>^ désigne bien le parent de <rev>. Cependant, ^ sert principalement pour désigner des commits de merge. Alors que le ~ remonte toujours sur la branche courante, le ^ permet de préciser le parent souhaité. Prenons un exemple.

A---B---C---D---E master
     \         /
      X---Y---Z

Dans l’illustration ci-dessus, HEAD pointe sur E. HEAD~ désigne D et HEAD~2 désigne C. HEAD^ désigne D également mais HEAD^2 désigne Z. Si l’on veut Y il faudra faire HEAD^2~1 soit, traduit en français “le premier parent du parent numéro 2 de HEAD”.

En effet, lors d’un merge il y a toujours plusieurs parents :

Le premier parent ou parent numéro un est celui de la branche sur laquelle on fusionne tandis que le deuxième parent ou parent numéro deux est celui de la branche fusionnée.

Nous avons vu dans cette section la majeure partie des références utiles dans un usage classique. Cependant, il en existe d’autres. Aussi, n’hésitez pas à consulter la documentation pour de plus amples informations.

Spécifier des intervalles

Dans le chapitre sur l’historique, nous avons parfois implicitement utilisé des intervalles. Voici en condensé différents moyens d’adresser ces derniers :

Ainsi, lorsque l’on passe à git log une simple révision, on lui demande la liste de tous les commits atteignables depuis cette révision. Prenons de nouveau Git emojis en exemple.

# output normal
git log --oneline
a978ce3 (HEAD -> master, origin/master, origin/HEAD) Bump lodash from 4.17.11 to 4.17.15
69e08b3 🚑 add missing return
307bd24 Merge pull request #4 from Buzut/dependabot/npm_and_yarn/js-yaml-3.13.1
7d67a96 Merge pull request #3 from Buzut/dependabot/npm_and_yarn/eslint-utils-1.4.3
d949bc8 Bump js-yaml from 3.12.2 to 3.13.1
285fb22 Bump eslint-utils from 1.3.1 to 1.4.3
e6e8f98 Merge pull request #2 from Buzut/dependabot/npm_and_yarn/mixin-deep-1.3.2
4ea3fbb Bump mixin-deep from 1.3.1 to 1.3.2
3f85ed8 Merge pull request #1 from Buzut/dependabot/npm_and_yarn/knex-0.19.5
a3f3b13 Bump knex from 0.16.3 to 0.19.5
fac6595 📖 update exemple for comments fetching
9777310 🚑 convert slashes to underscores for filenames
b075c22 🌟 update some indexes to be unique
dac440b 💄 remove trailing comma

# On filtre maintenant en demandant les commits atteignables depuis 69e08b3
git log --oneline 69e08b3
69e08b3 🚑 add missing return
307bd24 Merge pull request #4 from Buzut/dependabot/npm_and_yarn/js-yaml-3.13.1
7d67a96 Merge pull request #3 from Buzut/dependabot/npm_and_yarn/eslint-utils-1.4.3
d949bc8 Bump js-yaml from 3.12.2 to 3.13.1
285fb22 Bump eslint-utils from 1.3.1 to 1.4.3
e6e8f98 Merge pull request #2 from Buzut/dependabot/npm_and_yarn/mixin-deep-1.3.2
4ea3fbb Bump mixin-deep from 1.3.1 to 1.3.2
3f85ed8 Merge pull request #1 from Buzut/dependabot/npm_and_yarn/knex-0.19.5
a3f3b13 Bump knex from 0.16.3 to 0.19.5
fac6595 📖 update exemple for comments fetching
9777310 🚑 convert slashes to underscores for filenames
b075c22 🌟 update some indexes to be unique
dac440b 💄 remove trailing comma

Ici, les commits ultérieurs à 69e08b3 ne sont pas atteignables depuis ce dernier. Voyons maintenant les deux syntaxes les plus courantes permettant de travailler avec des intervalles.

Syntaxe double-dot

La syntaxe double-dot signifie que l’on veut toutes les modifications atteignables par l’une des révision mais pas par l’autre. Ainsi, <rev1>..<rev2> inclus tous les commits atteignables depuis <rev2> et non atteignables depuis <rev1>. Cela permet de demander à Git de lister tous les commits entre <rev1> et <rev2>.

Bien entendu, <rev1> et <rev2> peuvent être des commits, mais aussi des branches ou des tags. Ainsi, la question peut devenir : “Quelles sont les modifications de la branche B qui n’ont pas été intégrées à la branche A ?” Ou encore “Quelles sont les modifications de la branche locale qui n’ont pas encore été poussées sur la remote ?”.

Pour l’exemple, on créé une branche nommé “testrev” dans un repo Git (n’importe quel projet fera l’affaire). Dans un premier temps, on n’y fait aucune modification. Les deux branches sont donc en tous points identiques.

git log master..testrev

git log testrev..master

Le git log n’affiche rien, ni dans un sens ni dans l’autre. Les deux branches étant identiques, en toute logique, aucun commit atteignable d’une branche ne l’est pas de l’autre. On ajoute des commits sur les deux branches, nous les appelerons “masterN” et “testrevN” pour plus de clareté.

git log --oneline testrev..master
4a565db (HEAD -> master) master2
6c1e81c master1

git log --oneline master..testrev
14399ce (testrev) testrev2
90fbf29 testrev1

Il est possible d’omettre l’une des révision, dans ce cas, Git considère HEAD implicitement. Notre exemple précédent pourrait donc très bien se passer d’une des deux révisions (selon la branche sur laquelle on est).

Cette syntaxe est un raccourci, git log --oneline master..testrev équivaut à git log --oneline ^master testrev ou encore git log --oneline --not master.

Cette syntaxe longue présente l’avantages de comparaisons comprenant plus de deux références. On pourrait ainsi vouloir afficher la liste des commits présents dans <rev1> ou <rev2> mais pas <rev3>.

git log rev1 rev2 ^rev3

# ou
git log rev1 rev2 --not rev3

Syntaxe triple-dot

La syntaxe triple-dot trouve les modifications atteignables par l’une ou l’autre des références mais pas par les deux. Ils s’agit donc des modifications qui ne sont pas partagées par les branches révisions spécifiées.

git log --oneline master...testrev
4a565db (HEAD -> master) master2
6c1e81c master1
14399ce (testrev) testrev2
90fbf29 testrev1

Comme les nouvelles modifications ne sont pas partagées, elles sont toutes listées. Le résultat est tout autre si l’on fusionne testrev dans master.

git log --oneline master...testrev
f24139f (HEAD -> master) Merge branch 'testrev'
4a565db master2
6c1e81c master1

Comme avec le double-dot, on peut omettre l’un des paramètres.

Commentaires

Rejoignez la discussion !

Vous pouvez utiliser Markdown pour les liens [ancre de lien](url), la mise en *italique* et en **gras**. Enfin pour le code, vous pouvez utiliser la syntaxe `inline` et la syntaxe bloc

```
ceci est un bloc
de code
```