Chapitre 2 sur 13

Les commandes Git indispensables

Laisser un commentaire

Incontestablement, Git est un atout majeur dans le développement. Sa force réside dans sa souplesse d’usage et sa grande puissance. Bien qu’il possède de très nombreuses commandes, seule une partie d’entre elles sont réellement indispensables dans un usage quotidien. Focus sur les commandes Git les plus courantes.

Format des options

Vous verrez tout au long de ce livre que les options existent en deux formats :

# Format strict
git <command> --<option>=<param>

# Format light
git <command> --<option> <param>

En général, Git supporte les deux. Si vous obtenez une erreur, essayez l’autre format.

Configuration de Git

Avant toute chose, configurons rapidement Git. Cela nous permettra de gagner du temps par la suite.

La première chose à faire est de définir notre identité : l’email et le nom d’utilisateur utilisé lorsque nous faisons un commit.

git config --global user.name "Buzut"
git config --global user.email "buzut@mail.com"

Votre nom est de forme libre : “Pseudo” ou “Prénom Nom” par exemple. Nous verrons plus tard qu’il est également possible d’appliquer des configurations différentes à l’échelle d’un projet.

On passe ici le flag --global, pour dire à Git que l’on définit la configuration globale pour l’utilisateur courant. Il existe aussi le paramètre --system, lequel définit la configuration pour tous les utilisateurs du système.

Windows est un peu spécifique quant à la localisatin des fichiers de configuration, mais dans les cas macOS, Linux et Unix :

La configuration est consultable par un simple git config -l. Cela vous permettra de connaître la stratégie utilisée par Git pour la gestion des mots de passe ; le paramètre s’appelle credential.helper.

S’il n’est pas renseigné, cela veut dire que vous aurez à taper le mot de passe à chaque fois si vous utilisez du HTTPS. Si vous êtes sous Mac, il y a de grandes chances que la stratégie soit osxkeychain.

Si vous souhaitez utiliser le stockage natif du mot de passe par Git, il suffit de le lui demander.

git config --global credential.helper cache

Dans le cas des tratégies propres à Windows et macOS, une fois installée (nous avons abordé le sujet dans le chapitre précédent), vous utilisez la commande précédente en remplaçant cache par la stratégie souhaitée.

Créer des alias

Git permet la création d’alias, très commode pour les commandes souvent utilisées ou pour les options que l’on voudrait associer par défaut à une commande.

Comme pour tout élément de la configuration, vous pouvez les définir au niveau système, global ou local.

# Ajoutons quelques commandes en alias
git config --global alias.aa git add -a
git config --global alias.ca git commit -a
git config --global alias.st git status

Vous pouvez maintenant faire un git add -a en tapant git aa et git status en tapant git st. Toutefois, n’oubliez pas qu’en plus des alias de Git – qui requièrent toujours que vous invoquiez git – vous pouvez aussi tirez parti des alias de votre shell. Ainsi, pour ma part, le simple fait d’écrire git xxx s’avère parfois déjà contraignant pour une commande que je tape très souvent. Via un alias de Bash, git add -a se transforme en gaa, bien plus concis !

Demander de l’aide

Notez que pour obtenir de l’aide sur une commande en particulier, vous pouvez toujours le demander à Git.

git commande --help

# ou
git help commande

Création d’un dépôt Git

Les états de Git

Nous connaissons déjà ce schéma, je le remets ici afin que vous l’ayez bien en tête. Dans un flux de travail classique, lorsque l’on commence un nouveau projet, on créé alors un dépôt vide.

git init

À ce stade, aucun fichier n’est alors suivi par Git. Nous sommes dans le cas de notre schéma “Non versionné”.

Ajout de fichiers à l’index

# Dire à git de suivre un fichier
git add <file> <otherfile>

# On peut utiliser les patterns habituels
git add *.js *.jpe?g

# Ajoute tous les fichiers
git add .

À ce stade, le fichier est alors indexé. Vous pouvez dès lors effectuer d’autre modifications en sachant qu’elles n’impacteront pas celles que vous avez déjà enregistrées dans la staging area.

Interprétation du shell

Que vous utilisiez bash, zsh ou autre, le shell procède automatiquement à l’expansion des patterns contenant “*”. De ce fait, il est recommandé de le mettre entre guillemets si vous souhaitez qu’il soit passé tel quel à Git.

Dans un répertoire contenant main.js, test.js et es6/main.js ; git add *.js sera implicitement transformé en git add main.js test.js tandis que git add "*.js" sera passé tel quel par Git : es6/main.js sera alors également ajouté.

Si vous souhaitez enlever un fichier de la zone d’index, il faut utiliser la commande restore, deux options sont alors possibles.

# Simple annulation du git add
git restore --staged fichier

# Restauration du fichier dans son état avant modification
git restore fichier

La première version fait exactement l’inverse de git add, la deuxième version supprime totalement les dernières modifications non commitées, soyez donc vigilent ! Cette commande prend obligatoirement un chemin de fichier. Si vous voulez tout restaurer, pensez au caractère ..

restore et reset

Historiquement, on utilise à la commande reset pour annuler des changements et enlever des fichiers de l’index. Cependant, cette commande possède de très nombreuses possibilités et, mal comprise, elle pouvait mener à des effets désastreux.

Git a ainsi introduit dans la version 2.24 la commande restore afin d’annuler des changements. Toutefois, la commande reset reste tout à fait fonctionnelle. Nous verrons dans un prochain chapitre ses possibilités.

Comme il y a de grandes chances que vous soyez confronté à la commande reset, notamment sur les forums, voici comment l’utiliser pour annuler des changements.

# Enlève simplement les modifications de l'index
# Si un fichier est précisé, cela affecte juste le fichier
# Sinon, cela affecte tout le projet
git reset [fichier]

# Restaure le répertoire de travail dans son état d'avant modif
git reset --hard

Par ailleurs, reset --hard ne peut s’utiliser en précisant un chemin de fichier, cela s’applique donc forcément à tout le projet.

Sauvegarder les modifications

Nous sommes satisfaits de nos modifications, il est donc temps d’effectuer notre premier commit !

git commit

Cette action lance l’éditeur par défaut du système d’exploitation – il est possible de le préciser à Git, c’est l’option core.editor. Vous n’avez plus qu’à entrer votre message de commit et à valider. Il existe aussi une commande courte permettant d’effectuer cette action sans passer par l’éditeur.

# Nous voulons commiter des modifications du README
git commit -m "add documentation to README"

Il est aussi possible de sauter l’étape de staging, pratique lorsque l’on a travaillé sur quelques modifications qui sont toutes à inclure dans le même commit.

# Toutes les modifications des fichiers trackés sont intégrées au commit
git commit -a

# On peut bien entendu combiner les options
git commit -am "add documentation to README"

Effacer et déplacer des fichiers

# Effacer un fichier (arrête le suivi et efface la fichier du répertoire de travail)
git rm monfichier

# Arrêter de suivre un fichier mais ne pas l'effacer
git rm --cached monfichier

# Déplacer des fichiers
git mv fichierSrc fichierDest

En réalité, de ces trois commandes, seule la désindexation est réellement utile. En effet, Git est bien assez sophistiqué pour se rendre compte par lui même qu’un fichier a été déplacé, a changé de nom ou a été supprimé.

Modifier le dernier commit

Il arrive fréquemment qu’on réalise après avoir commité que l’on veut modifier le message de commit ou modifier notre dernier snapshot. C’est possible, il suffit de commiter en passant l’option --amend.

Si des fichiers ont été placés dans la staging, ils seront alors ajoutés au dernier commit. Dans le cas où aucun fichier n’a été ajouté à l’index, l’option --amend servira simplement à modifier le message de commit.

# Ajoute les nouvelles modifications au dernier commit
git commit --amend

# Idem mais en modifiant le message du commit
git commit --amend -m "nouveau message de commit"

Statut du dépôt

Comme on a tendance a faire beaucoup de choses, il est facile de ne plus trop savoir où l’on en est. git status est là pour ça.

# On voit ici que nous n'avons encore rien ajouté à l'index
# Et que nous avons des fichier non suivis
git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
    README.md
    index.html

nothing added to commit but untracked files present (use "git add" to track)

On obient en quelque sorte un rapport de situation. Ajoutons donc de nouvelles modifications.

# On modifie un fichier déjà tracké
echo "console.log('hello world')" >> main.js

# On affiche le statut de nouveau
git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   main.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    README.md
    index.html

no changes added to commit (use "git add" and/or "git commit -a")

# On ajoute donc les nouveaux fichiers et le fichier modifié
git add .

# Une fois de plus, on demande à Git quelle est la situation
git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    new file:   README.md
    new file:   index.html
    modified:   main.js

# On commit alors le tout
git commit -m "ajout du hello world"

# Dernier coup de status pour voir ce qu'il en est
git status
On branch master
nothing to commit, working tree clean

Lorsque vous voulez profiter d’un statut plus concis, l’option -s, --short en version longue, est la bienvenue.

Ignorer des fichiers

Les exemples de situations dans lesquelles vous ne voulez pas inclure un fichier ne manquent pas : fichier de config avec informations sensibles ou unique à un environnement précis, fichiers propres à un OS qui n’apportent rien au projet (hello les .DS_store et Thumbs.db), mais aussi fichiers de build (on ne voudra stocker que les fichiers sources). Bref, les exemples sont multiples.

Il existe un fichier de configuration dédié à cet effet : le .gitignore. Ce fichier se trouve n’importe où dans le répertoire d’un dépôt Git et s’appliquera au répertoire courant et à tous ses sous-répertoires.

Tous les fichiers et dossiers listés dans le .gitignore seront tout bonnement ignorés par Git. La syntaxe est simple :

Enfin, au delà du .gitignore par projet, il est possible d’en créer directement au niveau de la configuration globale. Cela permet par exemple d’ignorer des fichiers et répertoires plus en lien avec le machine d’un développeur que du projet en lui-même. Il s’agit de la configuration core.excludesfile, on lui passe alors le chemin absolu du fichier – souvent nommé .gitignore_global placé dans le répertoire de l’utilisateur (~/home).

Suivi des répertoires vides

À ce stade, nul n’ignore le .gitignore. Mais saviez-vous que Git ne stocke pas les répertoires vides ? Pour palier à cela, lorsqu’un répertoire possède une importance mais est vide dans le répo du projet – logs/ ou config/ sont assez courants – on peut placer un fichier vide afin de forcer Git à prendre le répertoire en compte.

Ce fichier vide est par convention nommé .gitkeep. Il n’a aucune fonction particulière. Pour Git, c’est un fichier comme un autre. Cependant, il est là et indique par son nom que sa seule fonction est de permettre l’indexation de son répertoire parent.

Afficher l’historique des commits

Cette commande est essentielle au flux de travail avec Git. Quels sont les derniers commits ? Quels en sont les auteurs ? Quand cela a-t-il été fait ?

# Exemple du code de ce site
git log
commit 50fce567e20beb8ed0b48562082e4db2c646e327
Author: Buzut <buzut@email.com>
Date:   Wed Sep 11 22:48:38 2019 +0200

    🚧 reorganize css for improved clarity

commit b29c78cb832d69970b79179f6e31c6deab4bcbe8
Author: Buzut <buzut@email.com>
Date:   Wed Sep 11 20:49:53 2019 +0200

    💄 avoid using background shorthand

commit 61c115d9f2d9e109c435a89e0e04f1aaa4d95650
Author: Buzut <buzut@email.com>
Date:   Tue Sep 10 18:55:57 2019 +0200

    💄 replace a color value by a variable

commit 8b5c0e02231c1a2c1ad05a37c6a0a87dbbb825e5
Author: Buzut <buzut@email.com>
Date:   Sun Aug 11 17:31:47 2019 +0200

    🌟 add pages into search (for courses)

commit 36c61b3d91164723bdf13854799da2dc72277aa8
Author: Buzut <buzut@email.com>
Date:   Sun Aug 11 17:31:09 2019 +0200

    🌟 adjust layout with courses for mobile

Par défaut, git log énumère les commits en ordre antéchronologique avec, pour chaque commit, sa somme de contrôle SHA-1, le nom et l’e-mail de l’auteur et la date et le message du commit.

Par ailleurs, il est assez courant de ne vouloir afficher que les modifications relatives à un fichier. Pour ce faire, on passe le chemin du fichier en paramètre de la commande.

Enfin, une option assez intéressante consiste à demander à Git d’afficher les modifications apportées par le commit directement dans le log, il s’agit de l’option -p--patch dans sa version longue.

# On affiche ici l'historique du header de ce site
# avec les hashs courts, des dates relatives et en incluant les modifs
git log --abbrev-commit --date=relative -p themes/buzut/source/styles/header.less
commit 50fce56
Author: Buzut <buzut@email.com>
Date:   3 months ago

    🚧 reorganize css for improved clarity

diff --git a/themes/buzut/source/styles/header.less b/themes/buzut/source/styles/header.less
index dc2e379..6c204f6 100644
--- a/themes/buzut/source/styles/header.less
+++ b/themes/buzut/source/styles/header.less
@@ -1,4 +1,4 @@
-.header {
+.main-header {
     display: block;
     height: 378px;
     padding: 0;

commit b29c78c
Author: Buzut <buzut@email.com>
Date:   3 months ago

    🌟 avoid using background shorthand

diff --git a/themes/buzut/source/styles/header.less b/themes/buzut/source/styles/header.less
index fc5c815..dc2e379 100644
--- a/themes/buzut/source/styles/header.less
+++ b/themes/buzut/source/styles/header.less
@@ -134,7 +134,7 @@ map {
         overflow-y: auto;
         font-size: .7em;
         line-height: 1.4em;
-        background: @white;
+        background-color: @white;
         border: 1px solid @lightGray;
         border-top: none;
         border-radius: 0 0 3px 3px;
@@ -212,7 +212,7 @@ map {
                     height: 2px;
                     visibility: hidden;
                     content: '';
-                    background: #eb5055;
+                    background-color: #eb5055;
                     transition: all .1s ease;
                     transform: scaleX(0);
                 }

Bien entendu, de nombreuses autres options existent tant l’historique est riche. Nous l’explorerons plus en détails dans le chapitre qui lui est consacré.

Utiliser les branches

Les branches constituent une fonctionalité centrale de Git et sont un réel atout pour les développeurs. Dans la gestion de version, deux branches se comportent comme deux univers parallèles. Lors de la création de la branche, celle-ci est dupliquée depuis la branche source. Elles sont en tous points identiques.

En revanche, à partir de cet instant, leurs existances sont indépendantes. Les modifications de la branche A n’impactent pas la branche B et vice-versa.

Schéma montrant deux branches distrinctes

Dans l’exemple ci-dessus, nous n’avons au début qu’une seule branche, la master, sur laquelle sont créés les commits A, B, C et D. La branche feature est créé depuis la master et possède donc exactement le même historique. Cependant, toute modification apportée à partir de cet instant à l’une des branches n’a plus aucune incidence sur l’autre.

Ce mécanisme permet par exemple de développer une nouvelle fonctionalité sur une branche dédiée sans risquer d’altérer le code de la branche principale, qui reste stable à tout moment. Les cas d’usage sont multiples et dépendent beaucoup du type de projet, du nombre de collaborateurs etc.

Voyons donc les principales commandes utiles pour travailler avec les branches. Nous pouvons utiliser le dépôt précédemment initialisé.

Lister et créer des branches

# Lister les branches
git branch
* master

# Créer une branche
git branch <newbranch>

# On liste de nouveau les branches
git branch
* master
  nouvelle_branche

Vous notez que l’étoile à gauche du nom de la branche indique sur quelle branche on se trouve présentement. Par ailleurs, par défaut, lors de l’initialisation du dépôt, Git créé la branche master. Elle n’a aucune propriété particulière et peut même être supprimée par la suite. C’est juste la branche par défaut, par conséquent, la plupart des projets possèdent une master.

Tout projet Git doit impérativement posséder au moins une branche, sans quoi le code n’a nulle part où être. Donc si vous désirez supprimer master, il faut créer une nouvelle branche au préalable.

Changer de branche

Pour changer de branche, il faut impérativement être dans un état propre, c’est à dire que le répertoire de travail ne peut contenir aucun fichier dans un état modifié. Si c’est le cas, il faut d’abord commiter les changements pour pouvoir changer de branche.

Étant donné que le changement de branche modifie le répertoire de travail, tout changement non commité mènerait à la perte des modifications non sauvegardées.

Le changement de branche s’effectue avec la commande switch. Cette commande sert à modifier l’état du répertoire de travail, c’est exactement ce en quoi consiste le changement de branche !

# Passage de la branche master à nouvelle_branche
git switch nouvelle_branche
Switched to branch 'nouvelle_branche'

Vous pouvez effectuer des modifications sur n’importe quel fichier, ajouter ou supprimer des fichiers. Une fois commités, si vous retournez sur la branche master, vous retrouverez votre espace de travail comme vous l’aviez laissé.

switch et checkout

Historiquement, c’est avec la commande checkout que l’on change de branche. Cependant, tout comme reset, cette commande possède de très nombreux usages qui changent selon le contexte. Cela semait la confusion chez bon nombre d’utilisateurs de Git.

Ainsi, depuis la version 2.23, Git propose l’usage de switch au lieu de checkout pour le changement de branche. La commande checkout reste cependant bien présente et tout à fait fonctionnelle.

Fusionner deux branches

Il arrive un moment où l’on souhaite que les changements effectués dans une branche intègrent ou ré-intègrent une autre branche. Cela fait partie du flux de travail classique de Git. La difficulté de cette opération est variable. Si les deux branches ont subi des modifications aux mêmes endroits, Git ne peut pas deviner quels changements doivent être gardés et lesquels sont à jeter.

Il y a alors des conflits et nous devons les résoudre manuellement avant d’indiquer à Git de fusionner les branches. Nous traiterons la gestion des conflits dans le chapitre dédié aux branches. Pour la fusion classique, sans conflit, il n’y a qu’une seule commande à connaître : merge.

# On s'assure d'abord de se placer sur la branche qui reçoit la fusion
git switch master

# Puis on demande à Git de fusionner les modifications d'une autre branche avec la branche courante
git merge <newbranch>
Updating 872ff95..8410530
Fast-forward
 newfile | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 newfile

Git nous indique les modifications apportées par cette fusion ainsi que la stratégie qu’il a utilisé. Il s’agit ici du fast-forward, c’est à dire que le branche fusionnée ne comportait que des commits supplémentaires par rapport à la branche cible. Il lui a donc suffit d’ajouter ces commits à notre branche cible.

Supprimer une branche

Dans sa forme basique, la suppression est très simple, elle se résume à utiliser l’option -d.

git branch -d <branch>

Il ne faut évidemment pas être sur la branche en question lors de sa suppression, auquel cas Git retournera une erreur. Nous verrons d’autres options utiles dans le chapitre dédié aux branches.

Travailler avec les dépôts distants

Les dépôts distants occupent une place centrale dans l’usage collaboratif de Git. Aussi, leur usage est très répandu et présentent de très nombreuses possibilités. D’ailleurs, le prochain chapitre leur est entièrement consacré. Nous allons ici aborder les actions les plus courantes.

Cloner depuis un dépôt distant

L’action la plus couramment effectuée avec les dépôts distants consite à récupérer, ou cloner, un dépôt. Pour ce faire, vous n’avez besoin que des accès en écriture – disponibles pour tous dans le cas de projets publics – et de l’adresse du projet.

En exemple, nous allons cloner le dépôt du projet Git emojis hook. Vous l’avez peut-être constaté dans les exemples précédents, mes commits sont toujours agrémentés d’Emojis. C’est ce projet qui permet d’en automatiser l’insertion. Il est publiquement disponible sur GitHub.

Interface d'un dépôt GitHub
Page d'accueil d'un projet sur GitHub

Toutes les palteformes de gestion de projets Git possèdent une fonctionalité pour cloner les projets. Sur les projets publics comme celui-ci, vous n’avez pas besoin d’être identifié. En revanche, sur un projet privé, dont l’accès est restreint, il faudra montrer pate blanche.

Vous choisissez le protocole de clonage qui vous convient le mieux : HTTPS ou SSH, vous copiez le lien puis vous importez le repo, cela se fait au moyen de la commande clone de Git. Le dépôt est alors cloné dans le répertoire courant.

git clone https://github.com/Buzut/git-emojis-hook.git

# Vérification du clonage
ls git-emojis-hook
LICENCE.md        README.md        commit-msg        prepare-commit-msg

Télécharger n’est pas cloner

Attention, bien que la plupart des plateformes permettent le téléchargement, il ne s’agit pas de clonage. Si vous cliquez sur “Download ZIP” dans GitHub, vous téléchargez le dernier snapshot du projet mais vous n’avez pas de répertoire .git et donc aucune des informations ni fonctionalités apportées par Git.

Pusher ses modifications

Une fois que nous avons ajoutés des commits au projet, on peut les pousser sur le dépôt distant afin de rendre nos modifications accessibles aux autres. Si vous avez les droits en écriture sur le projet, le partage de vos modifications se résumera à une commande.

git push

C’est tout ! Le push envoie les modifications relatives à la branche en cours, donc si vous voulez pusher les modifications de la branche feature, il faut être sur cette dernière.

Nous avons vu dans ce chapitre 80% des commandes dont nous nous servons constamment avec Git. Dans le chapitre suivant, nous allons explorer en détails et découvrir les usages avancés des dépôts distants.

Par ailleurs, certaines commandes courantes mais que vous n’utilisez pas quotidiennement vous échapperont peut-être. Il est parfois utile d’avoir Git Explorer à portée de main lorsque l’on ne retrouve pas une commande simple ou sa syntaxe.

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
```