Petit précis du Git pratique

Git est un très bon logiciel de gestion de versions décentralisé qu’on ne présente plus (et pour ceux qui ne connaissent pas, il est toujours temps d’aller découvrir l’abondante documentation du projet), d’ailleurs créé par Linus Torvalds (qu’on ne présente plus non plus) et qui a depuis fait ses preuves pour gérer des projets d’une taille conséquente (comme, on pourrait le deviner, le kernel Linux).

Mais git est avant tout un outil très riche, et il n’est pas évident de maîtriser toutes ses spécificités – encore faut-il savoir qu’elles existent ! Les commandes Git, reposant en soi sur les mêmes principes que la plupart des VCS (ou logiciels de gestion de versions) et a fortiori des DCVS (VCS décentralisés), proposent beaucoup d’options utiles qui peuvent simplifier la vie des utilisateurs et de ceux qui utilisent un dépôt Git avec vous.

Voici donc un petit hall of fame des options que je suis bien content de connaître dans Git.

Contents

Commit à l’amend

amande

Les messages de commit sont des commentaires que font l’auteur sur un set de modifications qu’il vient d’apporter à un dépôt : ils permettent de mieux comprendre ce que les changements introduits font.

Combien de fois, après avoir écrit un message de commit, je me suis dit « quel idiot, j’ai oublié de parler de ça dans le message de commit ! »…

Heureusement, Git a une option pour ça !

En utilisant l’option –amend de git commit, donc en tapant git commit –amend, on peut modifier le message du dernier commit soumis, sans apporter de nouvelles modifications. Pratique quand on oublie souvent quelque chose !

 

« Commit ALL the files ! »

La procédure classique pour créer une nouvelle itération dans un VCS est d’ajouter les fichiers concernés dans cette modification, puis de rédiger le message de commit décrivant ces modifications. Avec git, cette action se résume à une suite de git add et de git commit.

Mais lorsqu’on est en train de travailler sur un petit nombre de fichiers bien définis, il peut être rébarbatif de devoir ajouter les fichiers concernés avec git add à chaque fois.

Heureusement, Git a une option pour ça !

En utilisant la commande git commit -a, Git crée un nouveau commit avec tous les fichiers ayant été modifiés depuis le dernier commit (attention, ça suppose que ce sont des fichiers qu’il connaît déjà, donc qui ont déjà été ajoutés par git add au moins une fois).

Plus besoin de faire un git add à chaque commit !

60361891

Git en mode « Je préfère garder un backup au cas où »

Pour quelqu’un habitué à la ligne de commande, il paraîtra tout à fait logique que la commande git rm (git remove) permette de supprimer un fichier du gestionnaire de versions.

Problème: cette commande non seulement supprime le fichier aux yeux de Git, mais… le supprime aussi tout court ! Git part du principe que si on veut le supprimer dans le système de versionnage, autant le supprimer « pour de vrai » aussi.

La solution : git rm –cached. Cette petite option bien pratique dans certains cas permet d’effacer le fichier en question de Git, tout en le conservant sur le disque dur.

Repartir sur de bonnes bases

Un  problème commun rencontré lorsque des collaborateurs travaillent sur plusieurs branches Git différentes, mais sur des fonctionnalités potentiellement interdépendantes, est de rester à jour vis-à-vis du travail des autres.

Il y a la solution de régulièrement merge (fusionner) les branches entre elles pour conserver une version à jour du travail des autres. C’est parfois considéré comme une mauvaise pratique car on perd les informations du travail qui  a vraiment été fait sur le dépôt (on crée un commit de merge expliquant juste que la branche x a été mergée, sans autre explication historique). Cela donne par ailleurs un historique de dépôt chaotique dans le cas où plusieurs branches ont mutuellement besoin de se tenir à jour (l’historique est alors pollué par d’innombrables commits de merge totalement inutiles).

Une solution est le rebase : autrement dit, dire à Git de mettre vos changements locaux de côté, (ré-)appliquer les changements externes en premier, puis ajouter les vôtres par-dessus (on top of) ceux-là, de telle sorte qu’ils apparaissent chronologiquement ultérieurs aux changements que vous avez apporté (même si ce n’est pas vrai).

Ainsi, là où on ferait d’habitude un git pull origin autre_branche, on fait plutôt un git pull –rebase origin autre_branche.

C’est une technique puissante servant à garder un historique de commits clean car vos changements apparaissent chronologiquement après ceux de la fonctionnalité dont vous avez besoin (celle de l’autre branche), il n’y a donc aucune ambiguïté sur l’ordre des modifications et surtout, aucun commit superflu servant uniquement à notifier qu’un merge s’est produit.

Bien sûr, ce n’est là qu’une des nombreuses manières de rebase une branche sur une autre. Je vous renvoie à la documentation Git pour plus de détails croustillants.

Note: Le pull rebase est un mouvement assez particulier dans le sens où il réécrit l’historique des commits (il prend des modifications potentiellement ultérieures aux vôtres, les applique et n’applique les vôtres qu’ensuite). C’est pourquoi vous aurez peut-être des problèmes bizarres au moment de push sur une branche distante comme quoi malgré vos changements, votre branche courante est derrière la distante. Cette situation est une des rares où il est tout à fait légitime et (normalement) sans danger de forcer le push avec git push -f.

Kouroukoukou Git stash stash

kouroukou
Alerte mauvais goût si vous reconnaissez les boloss sur cette photo !

Entreprendre des modifications sur la mauvaise branche Git (parce qu’on a pas d’outils qui rendent cette information évidente par exemple), ça arrive…

Lors d’un git checkout sur une autre branche, Git tente de conserver les modifications. Mais lorsque les modifications en question risquent d’être effacées par le checkout, il vous arrête immédiatement (pauvre fou/folle !) :

Il vous propose alors deux choses très intelligentes : soit commit vos changements sur la branche actuelle (les figeant sur l’historique) ou bien les stash.

Stasher les changements en cours peut être intéressant si vous voulez conserver des changements en cours, mais pas encore prêts à faire partie d’un commit.

Pour cela, rien de plus simple : simplement utiliser la commande git stash save. Git va alors sauvegarder tous vos changements qui n’ont pas été commit et les garder dans un coin pour plus tard.

Pour voir vos changements actuellement mis de côté, il faut utiliser la commande git stash list.

Au moment où on veut ré-appliquer les changements mis de côté, il suffit d’utiliser la commande git stash apply : dans le cas où on veut dépiler les derniers changement stockés, la commande sans argument (ou une autre: git stash pop) suffit. Sinon, il est possible de passer en paramètre à la commande apply le nom du changement stashé à appliquer. Si par exemple git stash list affiche :

[cc lang= »text »]

(baron_a @ ~/Projects/ ) $ git stash list
stash@{0}: WIP on test: 1d87ad7 Foo driver: more protection lol from random Thrift exceptions

[/cc]

En utilisant git stash apply stash@{0}, on réappliquera cette liste de changements.

Pratique quand on travaille souvent sur plusieurs branches et qu’on doit parfois aller travailler sur autre chose alors que les nouvelles fonctionnalités ne sont pas encore prêtes.

 

 

« L’important, c’est de bien présenter »

Quelques astuces de présentation des données affichables avec Git (attention il s’agit de doubles tirets dans les flags comme –pretty) :

  • git log –pretty=oneline : afficher, avec une ligne par commit, la liste des commits avec leur hash et leur commentaire
  • git log –graph : afficher les commits sous forme d’un joli graphe qui illustre les  divergences et merges qui ont pu exister entre branches.
  • git rev-list HEAD –count : afficher le nombre de commits enregistrés sur la branche courante

 

Gotta get back in time

"Nom de Zeus Marty ! Ce fichier il était vraiment moche à cette révision-là !"
« Nom de Zeus Marty ! Ce fichier il était vraiment trop moche à cette révision-là ! »

Parfois, on aimerait savoir à quoi ressemblait un fichier en particulier à un commit donné, potentiellement loin en arrière dans l’historique du dépôt.

Une façon simple d’y arriver est d’utiliser git show :

  • git show commit:file

Par exemple pour retrouver à quoi ressemblait le fichier toto.txt il y a 4 commits (sur la branche en cours):

  • git show HEAD~4:toto.txt

 

CaGitaine Crochet

hook

Une fonctionnalité bien pratique de Git (et d’autres gestionnaires…) est de pouvoir lancer des tâches, de vérification par exemple, avant, ou après, une action particulière.

On appelle communément ce genre de mécanisme un hook (ou crochet) : il s’insère dans l’exécution normale des commandes et, en fonction de si ce qu’il tente de faire réussit ou pas, permet de considérer l’opération comme réussie ou non.

Un cas pratique qui m’est déjà arrivé : sur notre projet de fin d’études, qui est un logiciel écrit en Python, nous avions un robot d’intégration continue (Travis) qui vérifiait, à chaque nouveau commit sur le dépôt commun (sur Github), que la norme PEP8 est toujours bien respectée au sein du projet, à l’aide d’un outil automatisé tel que Flake8.

Le problème : dès que quelqu’un envoie du code non-PEP8 compatible, Travis considère que le build est cassé. Comme c’est une situation embêtante à éviter à tout prix, nous aimerions nous assurer avant même d’envoyer le code sur le serveur que ce qui est envoyé est correct (en lançant flake8 localement avant l’envoi par exemple).

C’est là que les Git hooks arrivent à la rescousse !

Mettons qu’on veut faire un pre-commit hook (c’est-à-dire un hook qui se déclenche avant chaque commit) qui fasse échouer la tentative de commit si flake8 renvoie une erreur.

En supposant qu’on est déjà dans le dépôt Git, il faut alors se rendre dans le dossier .git/hooks, et modifier le contenu du fichier pre-commit (dans notre cas, mais vous verrez qu’il y en a bien d’autres !) par ça (dans notre exemple):

flake8 MON_BEAU_DOSSIER

Il ne faut pas oublier de rendre le fichier pre-commit exécutable (oui, un hook n’est rien de plus qu’un script au final…).

Ce hook va lancer ladite commande flake8 avant chaque commande et, cette commande retournant un code d’erreur en cas d’incompatibilité avec la PEP8 détectée, le commit va échouer en indiquant pourquoi.

En plus, si à un moment vous désirez passer outre cette modification, vous pouvez en passant l’option -n (ou –no-verify) à votre commande Git ! (git commit -n par exemple)

Bien sûr ce n’est qu’une infime partie de ce qu’il est possible de faire avec les hooks, remarquez qu’il s’agit là d’un hook côté client (chez vous) mais il en existe aussi côté serveur (là où le dépôt Git distant est situé). Mais afin que cet article n’atteigne pas une longueur homérique, il me faut reporter ceux qui veulent en savoir plus à la doc officielle !

 

 « C’est quoi qu’a changé déjà ?… »

Un problème récurrent quand on ne fait pas attention, c’est qu’en ajoutant des fichiers pour commit (avec git add par exemple), on stage les modifications ( http://githowto.com/staging_changes ), mais une fois ceci fait, l’outil git diff ne permet plus de visualiser les changements depuis le dernier commit : normal, puisqu’il ne liste que les diffs des changements non-stagés !

Pour afficher quand même les changements depuis le dernier commit, on peut demande à Git d’afficher les changements enregistrés dans son cache depuis la dernière révision de HEAD :

  • git diff –cached chaud/cacao/chocolat.py

C’est pourquoi en général on n’add qu’au moment de commit (quand on est sûr que tout passe bien) mais ça peut toujours servir.

 

 L’Origine du Git

origin

Il est parfois utile de savoir retrouver d’où a été cloné un dépôt Git (de quel serveur) qu’on a sur son ordinateur.

Lorsqu’on clone un dépôt Git distant, Git donne par défaut le nom origin à ce point distant.

Pour retrouver le nom du serveur dont on a cloné le dépôt, il suffit donc en général de retrouver l’adresse d’origin. Pour ce faire, vous avez plusieurs alternatives :

  • git config –get remote.origin.url
  • ou encore git remote show origin qui affiche plus d’informations sur l’origine

 

Peur du vide

Un problème que n’importe qui qui a initialisé un dépôt Git à la main a sûrement déjà eu : Git refuse d’initialiser un dépôt avec un commit vide.

Parfois, il est pourtant rassurant de pouvoir le faire.

Heureusement, il existe une option pour ça !

git init
git commit --allow-empty -m "Initial commit"

Et voilà !


Bien entendu, bien d’autres commandes Git intéressantes existent, mais je ne peux pas toutes les lister ici. Je ferai peut-être un nouvel article pour en présenter d’autres…

À vous de découvrir celles qui vous aident le plus dans votre vie de tous les jours ! 🙂

Une réflexion sur « Petit précis du Git pratique »

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *