Git - Les fondamentaux pour comprendre et collaborer

Séminaire de l’ITI IRMIA++ à l’IRMA

Adrien Krähenbühl ICube - IUT Robert Schuman

Pourquoi utiliser Git ?

Git est un outil avant tout fait par des développeurs (Linus Torvald, 2005) pour des développeurs.

Git permet :

  1. de suivre l’évolution de son code,
  2. de pouvoir travailler à plusieurs sur le même code,
  3. de pouvoir partager facilement son code avec le monde entier.

Il existe d’autres outils similaires… qui tombent en désuétude :

Git, c’est quoi ?

Git est un logiciel de gestion de version de fichiers décentralisé.

Gestion de version
conserver un historique des versions successives de fichiers pour suivre et manipuler leur évolution.
Décentralisé
Chaque copie d’un dépôt contient l’intégralité de l’historique.

Graphe et commits

L’historique d’un répertoire se représente sous la forme d’un graphe.

Chaque nœud est appelé commit et contient :

  • des métadonnées : message, auteur, date de création, etc.
  • une vue instantané (snapshot) de l’état du répertoire à un moment précis

Comment ça fonctionne physiquement ?

Git sauvegarde l’ensemble des commits dans un répertoire .git à la racine de cette arborescence.

Pour pouvoir manipuler une arborescence, Git se base sur 3 éléments :

  1. Son historique stocké dans le répertoire .git
  2. Le reste de l’arborescence (qui constitue l’état courant)
  3. D’éventuelles références vers des copies distantes de l’arborescence

Initialiser un répertoire Git

Git init

git init[Répertoire]
Crée un répertoire .git dans Répertoire ou le répertoire courant s’il n’est pas spécifié.
$ cd /quelque/part/

$ git init MonRepertoireDeBase
Initialized empty Git repository in /quelque/part/MonRepertoireDeBase/.git/

$ ls -a MonRepertoireDeBase
.git

Git config

git config[--global] user.name <myname>
Indique d’utiliser myname comme nom d’auteur des commits
git config[--global] user.email <myemail>
Indique d’utiliser myemail comme email de l’auteur des commits
git config[--global|--local] -l
Liste les options de configuration qui s’applique à cet endroit
$ git config user.name "Adrien Krähenbühl"

$ git config -l
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
user.name=Adrien Krähenbühl
$ cat .git/config
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
[user]
    name = Adrien Krähenbühl

Versionner avec Git

Trois zones principales

Le répertoire de travail (working directory) est le répertoire sur le disque dur. Les fichiers y sont créés/modifiés/supprimés.

La zone de transit (staging area) ou index, est un espace temporaire qui stocke les modifications qui seront commitées.

Le dépôt (répertoire .git) stocke tous les commits.

Les commandes git qui suivent vont permettre de manipuler les fichiers pour les faire passer d’une zone à une autre.

Git status

git status affiche les infos sur l’état courant des fichiers.

$ git status
On branch main
Changes to be committed:
        new file:   NouvelleClasse.java
        modified:   README.md

Changes not staged for commit:
        modified:   Diagramme.puml

Untracked files:
        Test.java
  • Changes to be committed liste les fichiers dans la zone de transit.
  • Changes not staged for commit liste les fichiers du répertoire de travail qui ont été modifiés depuis le dernier commit.
  • Untracked files liste les fichiers qui ne sont pas versionnés.

Git add

git add
permet d’ajouter un fichier à la zone de transit, qu’il soit déjà versionné ou non.
$ git status
On branch main
Changes not staged for commit:
        modified:   MaClasse.java

$ git add MaClasse.java

$ git status
On branch main
Changes to be committed:
        modified:   MaClasse.java

Git commit

git commit[-m "message"]
Permet d’enregistrer un nouvel instantané dans le dépôt avec un message indiquant en quoi consistent les modifications.
$ git status
On branch main
Changes to be committed:
        modified:   MaClasse.java

$ git commit -m "Modification du fichier Maclasse.java"

$ git status
On branch main
nothing to commit, working tree clean

Git log

git log[--oneline] [--graph] [--branches]

Liste les commits avec leurs métadonnées :

  • --oneline : chaque commit condensé en une seule ligne
  • --graph : visualisation du graphe
  • --branches : visualisation de toutes les branches
$ git log
commit 638c310fa5a42839595e9d8499fdb98773feff5e (HEAD -> main)
Author: Adrien Krähenbühl <krahenbuhl@unistra.fr>
Date:   Tua Jan 11 10:02:59 2022 +0100

    Modification du fichier Maclasse.java

$ git log --oneline
638c310 (HEAD -> main) Modification du fichier Maclasse.java

Git restore

git restore<file>
annule les modifications de <file> qui ne sont pas dans la zone de transit
\(\rightarrow\) le fichier est modifié sur le disque dur.
$ cat MaClasse.java
interface MaClasse {}
$ git status
On branch main
Changes not staged for commit:
    modified:   MaClasse.java

no changes added to commit

1. Depuis le dernier commit, MaClasse.java a été modifié.

$ git restore MaClasse.java

$ cat MaClasse.java
class MaClasse {}

$ git status
On branch main
nothing to commit, working tree clean

2. Après git restore, les modifications sont annulées.

Git restore - staged

git restore--staged <file>
retire les modifications de la zone de transit, i.e. les désindexe.
\(\rightarrow\) le fichier n’est pas modifié sur le disque dur
$ cat MaClasse.java
interface MaClasse {}

$ git status
On branch main
Changes to be committed:
    modified:   MaClasse.java

1. Depuis le dernier commit, MaClasse.java a été modifié et indexé.

$ git restore --staged MaClasse.java

$ cat MaClasse.java
interface MaClasse {}

$ git status
On branch main
Changes not staged for commit:
    modified:   MaClasse.java

no changes added to commit

2. Modifications désindexées.

Git rm / Git mv

git mv<file> <destination>

déplace <file> vers <destination>

$ git mv MaClasse.java MaClasse2.java
$ git statusOn branch main
Changes to be committed:
renamed:    MaClasse.java -> MaClasse2.java
git rm<file>

supprime <file>

$ git rm MaClasse.java
$ git status
On branch main
Changes to be committed:
deleted:    MaClasse.java
  • Les fichiers versionnés doivent être manipulés prioritairement avec git
  • Ces commandes impactent Git ET le système de fichiers

Tableau récapitulatif

Manipuler l’histoire

Git checkout

git checkout<commit-id>
Met le répertoire de travail dans l’état du commit d’identifiant unique <commit-id> (calculé par la fonction de hachage SHA-1)
1 - État initial
$ git log --oneline --graph
* 56dd655 (HEAD -> main) Suppression de MaClasse.java
* eaa13b4 Ajout de README.md
* 6d865ec Ajout de Diagramme.puml
* 638c310 Ajout du fichier Maclasse.java
$ ls
Diagramme.puml  README.md

2 - “Retour” au commit 6d865ec

$ git checkout 6d865ec
HEAD is now at 6d865ec Ajout de Diagramme.puml
3 - Nouvel état
$ git log --oneline --graph
* 6d865ec (HEAD) Ajout de Diagramme.puml
* 638c310 Ajout du fichier Maclasse.java
$ ls
Diagramme.puml  MaClasse.java

Le pointeur HEAD

Git utilise un pointeur nommé HEAD pour indiquer où il se trouve dans le graphe des commits.

  • git commit crée un nouveau commit et déplace le pointeur HEAD sur ce nouveau commit
  • git checkout déplace uniquement le pointeur HEAD sur un commit donné.


1 - Départ

2 - git commit -m "..."

3 - git checkout 6d865ec

Les branches

Git branch & switch

Une branche est un pointeur nommé sur un commit.

git branch[-d] <name>
  • Crée une branche <name> au niveau du commit pointé par HEAD.
  • L’option -d (delete) supprime la branche.
git switch<name>
  • Déplace le pointeur HEAD vers la branche <name>.
  • switch est aux branches ce que checkout est aux commits

1. Départ.

2. git branch myBranch

3. git switch myBranch

4. git checkout 6d865ec

5. git branch otherBranch

6. git switch otherBranch

7. git switch myBranch

8. git branch -d otherBranch

Des histoires qui divergent

1. Départ.

2. Céation d’un nouveau fichier OtherClasse.java.

3. git add OtherClasse.java.

4. git commit -m "Ajout de la classe OtherClasse.java"

4. git commit -m "Ajout de la classe OtherClasse.java"

5. Modification du fichier OtherClasse.java.

6. git add OtherClasse.java.

7. git commit -m "Modification du fichier OtherClasse.java"

7. git commit -m "Modification du fichier OtherClasse.java"

À quoi servent les branches ?

Les branches permettent de travailler en parallèle à partir d’une base de code commune.

On peut créer des branches pour :

  • développer une nouvelle fonctionalité
  • corriger une erreur
  • gérer des versions release, beta, etc. de son application

Un dépôt a toujours une branche principale main (anciennement master) qui contient par convention du code fonctionnel.

Convention essentielle entre développeurs :
Ne jamais commiter directement sur la branche principale.

Git merge

git merge<branche>
Fusionne l’histoire de la <branche> avec le la branche pointée par HEAD.

1. Reprenons…

2. git switch myBranch

2. git switch myBranch

3. git merge otherBranch ???

3. git merge otherBranch

4. git branch -d otherBranch

4. git branch -d otherBranch

Le “commit de fusion”

  • git merge crée un commit de fusion.
  • Un commit de fusion a deux parents, les deux commits pointés par les deux branches fusionnées.

Créer un “commit de fusion” est le comportement par défaut de Git, mais il existe d’autres stratégies de fusion.

Les conflits de fusion

Origine des conflits

La fusion peut échouer s’il y a un ou plusieurs fichiers en conflit.

$ git merge otherBranch
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

Message d’erreur indiquant un conflit sur le fichier README.md

Un fichier sera en conflit :

  • si les mêmes lignes ont été modifiées sur les deux branches
  • s’il a été modifié sur une branche et supprimé sur l’autre
  • s’il a été créé sur les deux branches

depuis que les branches ont divergé.

Scénario de conflit

1. Départ.

Fichier README.md

Les partenaires de l'ITI :

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

2. git branch otherBranch

README.md sur…

Les partenaires de l'ITI :

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

myBranch

Les partenaires de l'ITI :

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

otherbranch

3. git switch otherBranch

README.md sur…

Les partenaires de l'ITI :

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

myBranch

Les partenaires de l'ITI :

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

otherbranch

4. Modification de README.md

README.md sur…

Les partenaires de l'ITI :

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

myBranch

ITI partners:

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

otherbranch

5. git add README.md

README.md sur…

Les partenaires de l'ITI :

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

myBranch

ITI partners:

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

otherbranch

6. git commit -m "Traduction du titre en anglais"

README.md sur…

Les partenaires de l'ITI :

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

myBranch

ITI partners:

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

otherbranch

7. git switch myBranch

README.md sur…

Les partenaires de l'ITI :

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

myBranch

ITI partners:

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

otherbranch

8. Modification de README.md

README.md sur…

The ITI's partners:       

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

myBranch

ITI partners:

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

otherbranch

9. git add README.md

README.md sur…

The ITI's partners:       

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

myBranch

ITI partners:

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

otherbranch

10. git commit -m "Traduction anglais britannique"

README.md sur…

The ITI's partners:       

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

myBranch

ITI partners:

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

otherbranch

11. git merge otherBranch ?

README.md sur…

The ITI's partners:       

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

myBranch

ITI partners:

- IRMA
- ICube
- ObAS
- INRIA
- Plateformes

otherbranch

Résoudre un conflit

Git propose dans le répertoire de travail une version temporaire du fichier en conflit contenant les deux versions :

<<<<<<< HEAD
The ITI's partners:       
=======
ITI partners:
>>>>>>> otherBranch

...

à changer en

The ITI's partners:       

...

ou

ITI partners:

...

C’est à vous de régler le conflit.

Pour terminer la fusion, vous devez :

  1. Éditer le fichier pour résoudre les conflits “à la main”
  2. Commiter les fichiers avec git add + git commit

Vous pouvez annuler la fusion à tout moment avec git merge--abort.

Se prémunir des conflits avec .gitignore

Les fichiers .gitignore permettent de déclarer des fichiers à ignorer lors de la création des commits.

.idea/
build/
*.log
MaClasse.java

Exemple de ficher .gitignore

Un fichier .gitignore :

  • est pris en compte dans le répertoire où il est placé et ses sous-répertoires
  • est très utile pour éviter :
    • de diffuser des fichiers propore à chaque utilisateur
    • de gérer des conflits à chaque commit

Exemple de .gitignore

$ ls
README.md
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
    README.md
$ echo "*.md" >> .gitignore
$ ls
README.md  .gitignore
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
    .gitignore
$ git add README.md
The following paths are ignored by one of your .gitignore files:
README.md
hint: Use -f if you really want to add them.

Après l’ajout du fichier .gitignore, le fichier README.md :

  • n’est plus pris en compte par Git
  • ne peut plus être ajouté involontairement

Travailler avec un dépôt distant

Git clone

git clone<dépot> [<répertoire>]
  • Crée une copie (un clône) du <dépôt> dans le répertoire courant ou dans <répertoire> s’il est indiqué.
  • <dépôt> peut être l’URL d’un dépôt Git accessible via le réseau.

Un dépôt distant Base versionné avec Git qui correspondrait à ceci :

$ ls -a Base/
.git  README.md

peut être cloné dans un autre répertoire Clone :

$ git clone git@git.unistra.fr:Base.git Clone
Cloning into 'Clone'...
done.

qui sera une copie exacte de Base :

$ ls -a Clone/
.git  README.md

Git remote

Un dépôt distant (ou remote) est un dépôt suivi par le dépôt local.

git remote[-v]
Liste les dépôts distants suivis.
git remoteadd <nom> <dépôt>
Ajoute une référence <nom> vers le dépôt distant <dépôt>.
$ git remote -v
origin  git@git.unistra.fr:Base.git (fetch)
origin  git@git.unistra.fr:Base.git (push)


Un dépôt distant peut se trouver sur le même ordinateur, le mot “distant” s’oppose simplement au mot “local” du dépôt courant.

Scénario de clonage


1. Soit un dépôt distant


2. git clone git@git.unistra.fr:Base.git Clone


2. git clone git@git.unistra.fr:Base.git Clone


2. git clone git@git.unistra.fr:Base.git Clone


3. git commit -m "Travail en local"


3. git commit -m "Travail en local"

Git push

git push<remote> <branch>
Synchronise (pousse) les modifications de la branche pointée par HEAD vers la <branch> du dépôt <remote>.
$ git push origin main
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 270 bytes | 270.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@git.unistra.fr:Base.git
    9e13dfb..e92d6f0  main -> main


Pousser des modifications locales vers un dépôt distant implique :

  1. de fusionner les changements locaux sur le dépôt distant,
  2. si la fusion réussie, de synchroniser localement la fusion.

Git fetch, merge et pull

$ git pull origin main
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), 254 bytes | 254.00 KiB/s, done.
From git@git.unistra.fr:Base.git
* branch          main     -> FETCH_HEAD
ae9a8b2..a399689  main     -> origin/main
Updating ae9a8b2..a399689
Fast-forward
README.md | 1 +
1 file changed, 1 insertion(+)
git fetch[[<remote>] <branch>]
Ramène les modifications de la <branche> du dépôt <remote> vers le dépôt local. Attention, il n’y a pas de fusion locale.
git merge
Fusionne les modifications déjà “fetchées” avec la branche courante.

git pull = git fetch + git merge

Scénario de pull


1. Reprenons…


2a. Le dépôt distant évolue.


2a. Le dépôt distant évolue.


3a. git fetch origin main


3a. git fetch origin main


4a. git merge origin main


4a. git merge origin main


2b. Le dépôt distant évolue.


3b. git pull origin main


3b. git pull origin main

Bonnes pratiques

  • Toujours puller avant de de pusher pour éviter les conflits et les reésoudre localement.
  • Toujours travailler sur une branche secondaire lorsqu’on travaille à plusieurs sur un même dépôt distant.
  • Committer régulièrement
  • Écrire des messages de commit explicites.