Git: Utilisation au quotidien

Quelques notes à brûle-pourpoint sur Git. Elles ont été prises suite à une présentation à laquelle j’ai pu assister sur Git et Gitlab, ma propre expérience, et enfin quelques trucs sympas trouvés sur le net, pour la partie avancée.

Qu’est-ce que Git ?

Une architecture décentralisée. Les nouvelles versions du code peuvent être stockées sur votre poste en local, sur un « serveur dédié », ou sur le poste de votre voisin.

Pour débuter:

# Où trouver de l'aide
git help
# Ou sur une commande en particulier:
git help checkout
git help cherry-pick

Il est important de bien configurer git dès le commencement :

# Editer le fichier de configuration lié au projet en cours
git config -e
# Editer le fichier de configuration de votre poste
git config -e --global
# Définir ou voir le contenu d'une valeur
git config --global user.name "John Doe"
git config --global user.email "john.doe@home"
git config --global http.sslverify "false"
git config --global core.crlf "true"
# ...

Mais avant toute chose, on va commencer par rendre un dossier « versionné »:

git init

On va ensuite configurer git pour récupérer du code sur un serveur distant :

git remote -v, -add, rm, set-url <name> <addr>, ...
git clone configure origin par défaut

Il peut être nécessaire de se créer une clé SSH pour autoriser les communications entre votre PC et le serveur Git :

ssh-keygen -o -t rsa  -b 4096 -C "mon-email@home"
cat ~/.ssh/id-rsa.pub | clip

Votre clé est copiée dans votre presse-papier, il vous reste à le coller dans GitLab par exemple.

Contrôler le contenu des commits

Il existe un système de hook qui permet de contrôler le contenu des messages de commit (forcer la référence à un ticket, forcer un certain nombre de caractères, etc.). Voir les scripts dans le dossier .git/hook precommit. Vous pouvez aussi valider le code, pas uniquement le message de commit (lignes < x caractères, sauts de lignes, …).

Naviguer dans l’historique

HEADC’est le nom du commit le plus récent sur votre branche en cours. Si vous travaillez sur la branche feat-1, que vous avez commit index.html mais pas encore foo.html , HEAD cible le commit sur index.html
HEAD^Là c’est le commit précédent. Si on veut le 4e dernier commit, on pourra taper HEAD^^^, ou plus simplement HEAD~3 (on compte à partir de 0)

Pour voir l’historique, on tapera simplement git log. Mais la commande propose des options intéressantes :

  • git show <commit> : voir le détail d’un commit (git show HEAD^)
  • git log --follow: permet de suivre les renommages des fichiers
  • git log --graph: Affiche l’historique sous forme d’arborescence. On peut utiliser d’autres options comme --stat, --summary, et même l’outil gitk qui affiche une vraie interface graphique cette fois.
  • git fetch <branch> && git log -p HEAD..FETCH_HEAD pour comparer ce qu’on a sur le serveur et notre dernier commit. HEAD...FETCH_HEAD (un point en plus) pour comparer avec les changements en cours.
  • Ne pas hésiter à aller voir l’aide git help log

Ajouter, renommer, supprimer

Les commandes de base pour l’ajout : git add <file>, git add <folder>, … Une option intéressante avec git add -p permet de sélectionner les portions de code qu’on veut ajouter ou pas dans notre index (zone contenant les fichiers prêts à commit). Autrement dit, on peut sélectionner une partie du fichier à commit et une autre partie à garder pour plus tard, ou à commit sur une autre branche. L’option -p est interactive et vous demande pour chaque bout de code s’il faut l’indexer ou non. Le fichier a donc deux versions à la fois, une version v1 dans la zone indexée et une version v2 qui n’est pas indexée. On voit très bien ce qui se passe après un git status. L’utilisation de git bash permet de voir d’un coup d’oeil où on on est avec son jeu de couleurs.

git ne permet pas de commit des répertoires vides. Il faut par exemple créer un fichier .gitkeep (par convention) pour qu’il ne soit plus vide.

Pour la suppression rien à signaler, git détecte la suppression de fichiers avec rm, mais il existe une commant git rm.

Pour le renommage, normalement git le détecte aussi, mais ça peut parfois être plus compliqué. Il faut que le commit (zone de fichiers indexée) contienne à la fois l’ajout et le delete de l’ancien fichier. Sinon git ne pourra pas dire qu’il s’agit d’un renommage au moment du commit. Pour faire toutes ces opérations en une seule fois, on peut utiliser le raccourci git mv, plutôt que de faire un mv old-file.txt new-file.txt && git add old-file.txt && git add new-file.txt.

La zone non indexée s’appelle la zone cache (ou staged). Les fichiers sont suivis par git mais pas indexés pour commit.

Pour comparer ce qu’on est en train de faire avec HEAD, on utilise git diff. Mais pour comparer ce qu’on a déjà indexé avec HEAD, on utilisera git diff --cached.

Ignorer les fichiers

Ne pas détecter les changements

git update-index –assume-unchanged

git update-index --skip-worktree

.gitignore

Ce fichier permet de décrire ce qu’on ne veut pas historiser dans git. IL peut s’agir de code compilé par exemple, ou de fichiers contenant des infos sensibles. Il suffit de mettre le nom de ce qu’on veut ignorer dans ce fichier, un par ligne. Attention par contre :

result => tout fichier ou dossier nommé result, qu'il soit à la racine ou non sera ignoré
result/ => tout dossier nommé result, qu'il soit à la racine ou non sera ignoré
/result/ => le dossier result à la racine sera ignoré
!.gitkeep => Ne pas ignorer les fichiers .gitkeep .Les exceptions se mettent en fin de fichier. Permet de créer un répertoire dont on ne veut pas historiser le contenu. (/result/.gitkeep)

Comment merger ?

Comment faire passer ce qui est dans la branche feat1 sur master ? Il existe git merge <opt> <branch>, qui dispose de beaucoup d’options.

  • L’option no-ff (pas de fast-forward) force le fait de créer un commit une fois le merge terminé. Si master n’a pas évolué depuis qu’on a créé feat1, le merge par défaut ne fera que déplacer le curseur master sur feat1 et on ne distinguera plus les deux branches (une branche n’est jamais qu’un identifiant de commit).
  • Gestion des branches : git branch (liste), git branch -a (ajout sans aller dedans), git checkout -b (ajout et va dedans), git branch -d (delete si la branche ne contient pas de code non mergé), git branch -D (force le delete même si pas mergé).
  • Quelques outils : meld est un outil open-source plutôt sympa (qui mérite un petit paramétrage des espaces et sauts de ligne pour pouvoir bien l’utiliser). vscode s’en sort pas trop mal non plus. Oubliez Eclipse si possible… Je n’ai pas trop utilisé Tortoise ni intelliJ mais ils existent quand même.

Les Alias

Git dispose d’un système d’alias (git config -e, voir la secion alias). Permet d’enregistrer des commandes complexes sous un nom simple. Par exemple git slog pourrait correspondre à git log --pretty-format.

Les Merge-Request

Il va falloir utiliser GitLab pour cela. C’est un environnement destiné à valider les demandes d’évolutions du code par les contributeurs. Cet espace permet de faire du code-review, un espace de discussion est intégré. Les merge-request peuvent également être analysées par des outils tels que Sonar ou Jenkins pour lancer les tests unitaires.

Git rebase

Soit une branche master, et une branche feat1. On développe sur feat1, et pendant ce temps, on a mergé feat2 sur master. Il devient difficile de merger feat1 sur master et des conflits vont se créer facilement.

Le git rebase est fait pour ça. On ne va pas merger feat1 sur master, mais on va repartir de l’état en cours de master (le plus récent, c’est à dire HEAD), et on va rejouer les commits qu’on a faits sur feat1. Ainsi, seules les modifications liées à feat1 vont etre rejouées sur master, et ce de manière unitaire (commit par commit). Si un conflit vient à apparaître, les autres commits sont interrompus tant que le conflit n’est pas réparé. Une fois le conflit réparé (git add <resolved_file>), on peut continuer de rebase (git rebase --continue). une fois le rebase terminé on peut git push.

Le rebase permet de gérer les conflits sur sa branche feature plutôt que sur la branche cible qui aurait pu devenir instable.

Le scénario :

# on part de master et on crée une branche feat1 qui crée/modifie index.html et d'autres fichiers
git checkout master
git checkout -b feat1
git add index.html bar.html
git commit

# on repart de master et on crée une branche feat2 qui crée/modifie aussi index.html et d'autres fichiers
git checkout master
git checkout -b feat2
git add index.html foo.html
git commit
# On merge feat2 sur master car feat2 est terminée.
git checkout master
git merge --no-ff feat2

# on termine feat1, on s'assure d'être à jour par rapport à master, on rejoue les commits à partir de la version actuelle
git checkout feat1
git pull --rebase origin master
# (pull = fetch + merge)
# resole conflict on index.html
git add index.html
git rebase --continue

# feat1 est à jour par rapport à master, on peut merger.
git checkout master
git merge --no-ff feat1
# no problem !
# Pour push les modifications ne pas oublier de forcer, car on a perdu l'arobrescence initiale des commits
git push -f

Si la résolution des conflits devient trop compliquée ou que vous êtes perdu, vous pouvez annuler à tout moment un rebase (tant qu’il n’est pas terminé) : git rebase --abort.

Workflows Git

Il existe plusieurs méthodes de travail reconnues avec Git :

  • Simple Git Branching Model: La branche master reste stable, une feature par branche. Quand la feature est finie, on rebase sur master.

Scénarios usuels

Gérer les hotfix

Vous avez deux branches, une branche pour la prod et un pour les nouveaux développements. Problème: vous devez apporter d’urgence une correction pour la prod, et vous aimeriez qu’elle soit reportée sur la dev.

Une proposition de scénario, il en existe d’autres sur la toile.

# on se met à jour sur la prod
git checkout master
git pull
# on crée une branche de fix et on commit en deux fois
git checkout -b hotfix/myNameIsBond
vim whosMyName.conf
git add whosMyName.conf
git commit -m "Replaced Skywalker by Bond"
vim whosMyName.conf
git add whosMyName.conf
git commit -m "Oops, this is James now"
git push
# git vous demande de créer la branche en remote, faites ce qu'il vous dit
# Si vous avez gitlab, vous pouvez créer une merge -request de la nouvelle branche sur master
# On revient sur la branche de dev pour rejouer notre/nos commits dessus
git checkout dev && git pull
git checkout -b hotfix/myNameIsBond_dev
# on rejoue les commits dans le même ordre sur le hotfix de dev
git cherry-pick hotfix/myNameIsBond~1
# conflit ?
vim whosMyName.conf
git add whosMyName.conf
git cherry-pick --continue
# on passe au 2e commit (donc le plus récent, HEAD)
git cherry-pick hotfix/myNameIsBond~0
# pas de conflit cette fois
git push
# Et on crée la merge-request de hotfix/myNameIsBond_dev sur dev.

Synchroniser le contenu de deux branches

Mettre à jour le contenu des branches à synchroniser par rapport au remote. On va synchroniser la branche « master » et « develop » dans notre exemple :

git checkout master && git pull && git checkout develop && git pull && git checkout master

On est sur master. On va créer une branche dédiée au merge de develop sur master

git checkout -b merge/developSurMaster

On récupère ensuite le contenu de la branche develop et onle sort de l’index :

git checkout develop -- . && git reset

On choisit les changements qu’on veut conserver ou non via l’IDE. On commit push ensuite les changements, puis on crée une merge request.

Ensuite on se met sur la branche develop et on synchronise les éléments de master sur develop. On va repartir de la nouvelle branche merge/developSurMaster pour ne traiter que les éléments non mergés :

git checkout develop && git pull
git checkout merge/developSurMaster -- .
git reset

Git avancé : quelques trucs sympas

Ci-dessous quelques commandes git plutôt sympas auxquelles on ne pense pas forcément. Ne pas hésiter un créer des alias pour les commandes un peu compliquées.

Quelques sources :

Scruter les logs

Liste des contributeurs les plus actifs sur une période donnée (-s : sans afficher la description et compter les commits, -n : trier la sortie, –all : sur toutes les branches, –no-merge : ne pas tenir compte des commits de merge) :

git shortlog -sn --all --no-merges --since='10 weeks' --until '2 weeks'

Retrouver les branches sur lesquelles on a travaillé récemment (refs/heads : branches locales uniquement, –format: format court):

git for-each-ref --count=10 --sort committerdate refs/heads/ --format="%(refname:short)"

Avoir une vue générale (overview) de ce qui s’est passé sur l’ensemble des branches :

git log --all --oneline --no-merges --since="2 weeks"

Retrouver notre activité de la journée :

git log --all --oneline --no-merges --author=my.email@mail.com --since="00:00:00"

Vue détaillée des logs, toutes branches confondues (–decorate : infos supplémentaires sur les commits sur les branches + date stricte) :

git log --graph --all --decorate --stat --date=iso

Comparer les versions de code

Git blame : retrouver qui est à l’origine d’une feature ou d’un bug, c’est à dire qui a modifié telle portion de code (lignes 5 à 10) :

git blame -L5,10 path/to/the/file.java

Générer un changelog (on peut aussi utiliser des hashes de commits) :

git log --oneline --no-merges <last-tag>..HEAD

Ignorer les caractères blancs (espaces, retours à la ligne) pour les diff :

git diff -w
git show -w

Chercher les différences à l’intérieur des lignes, et non ligne par ligne :

git diff --word-diff

Voir ce qu’on est sur le point de récupérer (pull) depuis le serveur :

git checkout <branch>
git fetch
git log --oneline --no-merges HEAD..<remote>/<branch>

Voir ce qu’on est sur le point d’envoyer (push) sur le serveur :

git fetch
git log --oneline --no-merges <remote>/<branch>..HEAD

Gestion des certificats SSL

Il peut arriver que vous soyez amené à devoir importer un certificat dans git pour pouvoir communiquer avec le serveur (push/pull). Par exemple parce que vous avez votre propre serveur (comme gogs installé chez vous), ou que vos proxies vous donnent du fil à retordre.

La solution simple et moche, on désactive la vérification des certificats :

git config --global http.sslVerify false

Une deuxième solution, trouvée sur StackOverflow:

# 1. Vérifier que le certificat ROOT est bien présent dans le fichier de certificats git (voir plus bas)
# 2. Configurer git pour qu'il aille chercher votre certificat dans votre dossier de certificats :
git config --system http.sslCAPath /absolute/path/to/git/certificates

Pour cette deuxième solution, il se peut que vous deviez installer manuellement le certificat ROOT dans git pour que votre certificat soit utilisable. Pas d’autre solution que de le récupérer à la main :

Firefox :
  Allez sur votre serveur git et ouvrez les certificats
  Dans "Chemins d'accès et de certifications" cliquez sur le ou les certificat en haut l'arborescence et téléchargez-les au format base64 (2e option)
  Ajoutez le contenu de de fichier au fichier existant 

cat cgi_b64.cer >> /c/Program\ Files/Git/mingw64/ssl/certs/ca-bundle.crt

Autre commandes qui n’ont pas porté leurs fruits (à retester) :

MY_HOST=my.host.com
CERT_NAME=gogs
GIT_DIR=/mingw64/

# Extraction du certificat dernier niveau 
openssl s_client -showcerts -connect $MY_HOST:443 </dev/null 2>/dev/null|openssl x509 -outform PEM >$CERT_NAME.pem
# Conversion en CRT
openssl.exe x509 -outform der -in $CERT_NAME.pem -out $CERT_NAME.crt
# intégration à GIT
# prev value : http.sslcainfo=C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
git config --system http.sslCAPath /$GIT_DIR/$CERT_NAME

Laisser un commentaire

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