Cédric Temple

Administrateur système en logiciels libres

GIT, gestionnaire de version décentralisé

2019-12-30 » notes pour plus tard

Ce contenu est fourni sous licence CC BY-NC-SA 4.0

Notes sur GIT

Récupérer un repository distant

git clone <url>

Voir les branches

git branch -ar

Se positionner dans une branche

git checkout <nomBranche>

Voir les modifications en cours

git status

Valider (committer) localement des modifications

Valider toutes les modifications :

git commit -a -m '<mon message de commits>'

Valider certaines modifications :

git commit -a -m '<mon message de commits>' <fichier1> <fichier2> ...

Envoyer les modifications sur la branche distante

git push

Si on vient juste de créer la branche locale (ici, généralement, doit être remplacé par le nom de la branche locale pour avoir des noms identiques entre branche locale et branche distante) :

git push -u origin <nomBranche>
# ou, de manière identique mais plus claire :
git push --set-upstream origin <nomBranche>

Envoyer toutes les modifications de toutes les branches locales vers les branches distantes correspondantes :

git push --all

Récupérer les modifications provenant de la branche distante

git pull

Créer une nouvelle branche

Le processus est le suivant :

  1. d’abord se positionner sur la branche depuis laquelle on souhaite diverger
  2. ensuite, créer la nouvelle branche
  3. faire nos modifications
  4. valider nos modifications locales
  5. envoyer sur le serveur distant les modifications locales
git checkout <nomBrancheSource>
git checkout -b <nomNouvelleBranche>
# faire les modifications... puis les valider localement :
git commit -a -m '<mon message de commits>'
git push --set-upstream origin <nomNouvelleBranche>

Annuler des commits locaux

Là, on se trouve dans le cas où l’on a fait un ou plusieurs commits sur la branche locale mais que ces commits n’ont pas encore été poussés vers le repo distant.

Deux approches possibles :

  1. je veux supprimer toute information, toute modification de ce commit
  2. je veux supprimer l’information de commit mais garder les modifications faites à mon code.

Le premier cas est “non mais là, j’ai merdé en fait. Tout ce que j’ai fait est à jeter. Heureusement, je n’ai rien poussé sur le repository principal, les collègues se moqueraient de moi”. L’idée est donc de supprimer toutes les modifications et revenir au code tel qu’il était avant ma modification.

git reset --hard HEAD^1

Le deuxième cas est “ha zut, je me suis trompé, j’aurais du ajouter/retirer tel fichier à mon commit” ou “ha zut, j’ai oublié de modifier tel fichier”.

git reset --mixed HEAD^1

Là, vous pouvez ensuite continuer vos modifications et validez votre commit.

Vous pouvez annuler plusieurs commits consécutifs mais toujours si vous ne les avez pas poussés sur le repo distant. Pour cela, identifier le numéro de commit précédent vers lequel revenir à l’aide de la commande git log puis l’indiquer. Exemple :

cedric@portable:~/GIT/blog.cedrictemple.net-test-git$ git log
commit 696625ea73bbb2e39400d9ed5ccb19b04b306da6 (HEAD -> master)
Author: Cedric Temple <email>
Date:   Mon Dec 30 17:10:28 2019 +0100

    test 2

commit 4ca8357b99af667cc8c31ec517e4ae01e5d405b0
Author: Cedric Temple <email>
Date:   Mon Dec 30 17:10:09 2019 +0100

    test 1

commit d248572d571d1746b808bf762909c623b77a6c74 (origin/master)
Author: Cedric Temple <email>
Date:   Wed Dec 25 12:27:22 2019 +0100

    Ajout de /etc/resolv.conf

commit 0a16f9172c038be6f8867b02257a581342058c9c
Author: Cedric Temple <email>
Date:   Mon Dec 23 18:01:46 2019 +0100
...
...
...
cedric@portable:~/GIT/blog.cedrictemple.net-test-git$ git reset --mixed d248572d571d1746b808bf762909c623b77a6c74

Neutraliser un commit distant

Il n’est pas possible de supprimer un commit distant : en effet, rien ne dit que d’autres ne l’ont pas déjà récupéré, si on le supprime, les autres seront très perturbés. De ce fait, on va neutraliser un commit : on va créer un commit qui inverse un autre commit.

Pour cela, on utilise la commande git revert en passant en argument le numéro de commit que l’on veut neutraliser. Dès lors, GIT va automatiquement créer un nouveau commit inversant le commit précédent.

git revert ID_COMMIT
# exemple :
git revert 4ca8357b99af667cc8c31ec517e4ae01e5d405b0

Dès lors, une fois le commit poussé, l’historique GIT reflétera exactement l’histoire :

  1. un premier commit X a été fait
  2. d’autres commits ont (peut-être) été faits
  3. un autre commit X’ a été fait pour annuler le premier commit X

Ce n’est pas (totalement) magique : si d’autres commits ont modifié les lignes du commit que l’on veut neutraliser, un conflit va se créer. Il faut alors le résoudre manuellement.

Supprimer une branche locale uniquement

Si l’on veut effacer une branche locale uniquement, le processus est la suivant :

  1. d’abord, se positionner sur une autre branche
  2. ensuite, effacer la branche locale
git checkout master
git branch -D todelete

Supprimer une branche

Ici, on veut supprimer la branche nommée “todelete”. Le processus est le suivant :

  1. d’abord, se positionner sur une autre branche
  2. ensuite, effacer la branche locale
  3. enfin, pousser la référence vide (indiquée ici par le caractère ‘:’) sur le serveur.
git checkout master
git branch -D todelete
git push origin :todelete

Nettoyer un repository

Il est possible de nettoyer son repository pour virer toutes les données devenues inutiles. Exemple ici, avec un tout petit repository :

du -sh .
18M     .

git gc
Décompte des objets: 28512, fait.
Delta compression using up to 8 threads.
Compression des objets: 100% (8548/8548), fait.
Écriture des objets: 100% (28512/28512), fait.
Total 28512 (delta 20391), reused 27529 (delta 19639)

du -sh .
9,5M    .

Créer un tag

Un tag est une étiquette pour marque un commit. Généralement, on taggue lorsqu’on crée une version de son logiciel, de sa documentation, …

git tag <nomDuTag> -am 'message pour  les humains'
# exemple :
git tag v1.1 -am 'ma toute nouvelle version v1.1 qui contient ...'
# puis, on doit pousser le tag sur le serveur :
git push --tags
# puis plus tard
git tag v1.2 -am 'ma toute nouvelle version v1.2 qui contient ...'
git push --tags

Lister les tags

git tag --list

Se positionner sur un tag

Pour retrouver le code que l’on a taggué, c’est simple :

# pour identifier le tag
git tag --list
# pour se positionner sur le tag
git checkout <nomTag>
# exemple :
git checkout v1.0

Là, vous êtes en mode “DETACHED HEAD”. Pas de panique : c’est pour vous indiquer que vous n’êtes pas sur une branche mais sur un tag. Du coup, vous ne pouvez pas pousser les modifications sur le serveur distant sans passer par la case “Repartir d’un code taggué pour créer une nouvelle version”.

Repartir d’un code taggué pour créer une nouvelle version

Prenons un exemple pour expliquer le cas d’usage récurrent. Généralement, on crée une version 2.3.8. On pense que cette version corrige tous les bugs de la version 2.3 et on avance sur la version 3.0. Là, après quelques semaines, un bug bloquant apparaît. Il faut le corriger rapidement, sans forcer tout le monde à migrer sur la version 3.0, qui d’ailleurs n’est pas encore prête. Cependant, il faut comprendre qu’un tag n’est pas une branche : on ne peut pas modifier le code d’un tag. Il faut donc créer une nouvelle branche basée sur le tag. Le processus est donc :

  1. identifier le tag
  2. se positionner sur ce tag
  3. créer une nouvelle branche
# pour identifier le tag
git tag --list
# pour se positionner sur le tag
git checkout <nomTag>
# exemple :
git checkout v2.3.8
# création de la nouvelle branche :
git branch -b 2.3.9-branch

Définition d’une politique de nommage des branches et des tags

Il est nécessaire de définir une politique de nommage des branches et des tags. En effet, si vous nommez de la même manière les tags et les branches, vous ne pouvez pas (simplement) vous positionner sur le tag v2.3.8 en utilisant :

git checkout v2.3.0

Vous allez avoir un conflit : une branche et un tag se nomme de la même maniètre (v2.3.0). Comment GIT sait s’il doit utiliser la branche ou le tag ? GIT, par défaut, se positionne sur la branche.

Il est alors nécessaire de définir comment nommer les choses en posant des règles simples comme par exemple :

  • les branches se nomment -develop où ne commence pas par le caractère 'v'. Notre branche va donc se nommer :
    • 2.3.8-develop
  • les tags se nomment v. Par exemple, notre tag va se nommer :
    • v2.3.8

Dès lors, il est plus facile d’identifier la branche ou le tag sans faire d’erreur.

Si jamais on se trompe et que l’on crée un tag qui a le même nom qu’une branche, il est quand même possible de se positionner sur le tag correspondant :

git checkout refs/tags/<nomDuTag>
# exemple si l'on a nommé le tag et la branche v2.3.0 :
# se positionner sur le tag :
git checkout refs/tags/v2.3.0
# se positionner sur la branche locale v2.3.0 :
git checkout refs/heads/v2.3.0
# se positionner sur la branche distante v2.3.0 :
git checkout refs/remotes/origin/v2.3.0

Voir les commits d’une branche

Pour voir les commits d’une branche, se positionner sur cette branche puis :

git log

On voit alors, pour chaque modification, 4 informations :

  1. l’identifiant du commit (un hash SHA)
  2. la date du commit
  3. le message de commit

Récupération de commits d’une autre branche

Lorsqu’on développe, on développe parfois en parallèle plusieurs versions. Donc on développe sur plusieurs branches en même temps. Cependant, on aimerait bien qu’une modification donnée soit reportée d’une branche à l’autre. Avec GIT, c’est faisable simplement. Imaginons le cas suivant pour l’exemple : j’ai deux branches 2.3.8-develop et 2.4.0-develop. J’ai fait un correctif sur la branche 2.3.8-develop que j’aimerai bien appliqué sur 2.4.0-develop. Le processus est le suivant :

  1. se positionner sur la branche 2.3.8-develop
  2. identifier la modification que l’on souhaite pousser sur la branche 2.4.0-develop grâce à git log
  3. se positionner sur la branche 2.4.0-develop
  4. récupérer le commit avec git cherry-pick
git checkout 2.3.8-develop
git log
# j'identifie mon commit en utilisant le hash SHA, par exemple : 84e2a37b46e604189a8bc18eb942775cb5a52987
git checkout 2.4.0-develop
git cherry-pick 84e2a37b46e604189a8bc18eb942775cb5a52987
# j'ai récupéré le commit, je peux le pousser sur le serveur distant :
git push

Bien entendu, cela fonctionne automagiquement lorsqu’il n’y a pas de conflit. S’il y a des conflits, il faut les résoudre manuellement.

Il est aussi possible d’enchaîner les git cherry-pick ou de donner une liste de commits.

Travailler avec une branche d’un autre repository

La grande force est d’être décentralisé. Il est possible de travailler sur des branches provenants d’autres repositories qui peuvent, ou non, provenir d’autres serveurs. Ce mode de fonctionnement est utilisé par GitHub de la façon suivante :

  • on dispose d’un repository principal
  • les développeurs clonent ce repository principal pour disposer chacun de leur propre repository
  • pour chaque nouvelle modification (exemple : “ajout d’un bouton rouge dans écran 3”), un développeur crée une branche dédiée, dans leur propre repository
  • ils committent et poussent (“push”) sur le serveur hébergeant leur propre repository
  • ils font une demande de validation de modification (“pull-request”)
  • si la demande est acceptée, elle arrive sur le repository principal
  • régulièrement, ils resynchronisent leur propre repository avec le repository principal

Pour faire cela avec les commandes GIT :

# clone de mon repository personnel
git clone <serveurGit>/<cheminVersMonRepository> monLogiciel
# je me place dans le répertoire de mon logiciel
cd monLogiciel
# je crée une référence vers le repository principal
git remote add upstream <ServeurGitPrincipal>/<CheminVersRepositoryPrincipal>
# récupération des modifications
git fetch upstream
# je me place sur une branche upstream
git checkout upstream/branch-3.0
# je crée une nouvelle branche locale
git checkout -b branch-3.0
# je dis que cette branche doit exister sur mon serveur
git push --set-upstream origin branch-3.0
# je modifie mes fichiers locaux
...
# je valide mes modifications
git add ...
git commit -am 'mon message de commit'
# je pousse sur mon repository personnel de mon serveur
git push
# dès lors, je peux faire mon pull-request dans GitHub par exemple

Ensuite, une fois que le commit est validé, si je veux me remettre d’équerre avec le code source “officiel”, c’est à dire avec le code source du repository principal, qui peut avoir évoluer par ailleurs, car d’autres développeurs ont ajoutés leurs propres commits :

git fetch upstream
git checkout <la branche de mon repository local que je veux synchroniser>
git merge remote/<la branche du repository principal avec laquelle je veux me resynchroniser>
# Exemple : plus haut, on avait ajouter une modification à branch-3.0 ; donc là, on veut se resynchroniser avec cette branche
# on doit donc faire :
git fetch upstream
git checkout branch-3.0
git merge remote/branch-3.0
# et là, je peux faire mes nouvelles modifications

Fournir une archive d’une version du code

Il est souvent utile de fournir une archive (tar.gz ou zip) du code aux personnes qui vont l’utiliser. Pour cela, on utilise la commande ‘'’git archive’’’ :

# créer une archive depuis un tag
git archive --format=tar --prefix=monProjet-v2.3/ v2.3 | gzip > ../monProjet-v2.3.tar.gz
git archive --format=tar.gz --prefix=monProjet-v2.3/ v2.3 > ../monProjet-v2.3.tar.gz
# pas besoin d'indiquer le format, il est déduit du nom de fichier de sortie
git archive --prefix=monProjet-v2.3/ -o ../monProjet-v2.3.tar.gz v2.3
# créer une archive de la branche courante basée sur le dernier commit
git archive --prefix=monProjet-SNAPSHOT/ -o ../monProjet-SNAPSHOT.tar.gz HEAD

Ce contenu est fourni sous licence CC BY-NC-SA 4.0