Git est distribué : votre dossier contient tout l'historique, pas une copie de travail reliée à un serveur.
.git/ : tout l'historique.git/ : tout l'historique.git/ : tout l'historiquepush n'est qu'une synchronisation ; si le serveur disparaît, votre historique reste (contrairement au Google Docs de tout à l'heure).C'est LA slide de la séance. Git jongle en permanence avec trois « états » de votre projet :
| Nom | Où | Ce que c'est |
|---|---|---|
| working tree (répertoire de travail) | vos fichiers sur le disque | ce que vous éditez |
| index (staging area, « zone de préparation ») | .git/index |
le brouillon du prochain commit |
| HEAD | .git/HEAD |
le dernier commit, celui sur lequel vous êtes |
Presque toutes les commandes du jour déplacent des choses entre ces trois cases. Quand une commande vous surprend, demandez-vous : « laquelle des trois vient-elle de toucher ? »
restore recopie la case de droite dans celle de gauche) ;git status compare les trois cases et vous dit ce qui diffère ; git diff montre le détail. L'index vous permet de ne commiter qu'une partie de ce que vous avez modifié.
Les commandes shell du cours (sous Windows, dans Git Bash) :
| Commande | Ce qu'elle fait |
|---|---|
pwd |
affiche le dossier où vous êtes |
cd carnet, cd .. |
entre dans un dossier ; remonte d'un cran |
ls, ls -a |
liste les fichiers ; -a montre aussi les cachés (.git/) |
mkdir carnet, touch f |
crée un dossier ; crée un fichier vide |
cat notes.md |
affiche le contenu d'un fichier |
echo "texte" > f |
écrit dans f, en écrasant son contenu |
echo "texte" >> f |
ajoute une ligne à la fin de f |
grep -r "motif" . |
cherche un texte dans tous les fichiers du dossier |
a && b |
lance b seulement si a a réussi |
Tab complète les noms, ↑ rappelle la commande précédente, Ctrl+C interrompt.
init, status$ mkdir carnet && cd carnet
$ git init -b main
Initialized empty Git repository in /home/vous/carnet/.git/
$ git status
On branch main
No commits yet
nothing to commit (create/copy files and use "git add" to track)
.git/ ; supprimez-le et le projet redevient un dossier ordinaire ;-b main fixe le nom de la première branche (sinon master selon votre configuration) ;git status est la commande à taper avant et après chaque autre commande, le temps d'apprendre.add, commit$ echo "# Carnet de recettes" > README.md
$ git status -s
?? README.md ← inconnu de git (untracked)
$ git add README.md
$ git status -s
A README.md ← dans l'index, prêt à être commité
$ git commit -m "Crée le carnet de recettes"
[main (root-commit) a7d3639] Crée le carnet de recettes
1 file changed, 1 insertion(+)
add copie le fichier dans l'index ; commit prend une photo de l'index et la range dans l'historique, avec un message, un auteur et une date. Le fichier n'a pas bougé sur le disque.
VS Code : vue Source Control (
Ctrl+Shift+G). Le + à côté d'un fichier fait git add ; le message puis Commit font git commit.
Dans un dossier neuf (il servira de bac à sable pour toute la matinée) :
mkdir bac-a-sable && cd bac-a-sable && git init -b mainnotes.md avec une ligne de texte (éditeur ou echo) ;git status, puis git add notes.md, puis git status : lisez la différence ;git commit -m "Première note" ;notes.md, ajoutez une ligne, puis git status : que dit git ?git add notes.md && git commit -m "Deuxième note", puis git log --oneline. Si
git commit ouvre un éditeur inconnu : Échap, :q!, puis git config --global core.editor "code --wait".
git status$ git status
On branch main
Changes to be committed: ← dans l'index (vert)
(use "git restore --staged <file>..." to unstage)
modified: README.md
Changes not staged for commit: ← dans le working tree seulement (rouge)
(use "git add <file>..." to update what will be committed)
modified: recettes/crepes.md
Untracked files: ← git ne les connaît pas encore
notes.tmp
Git vous souffle la commande pour revenir en arrière : lisez les lignes entre parenthèses. La forme courte, git status -s, tient sur deux colonnes : index à gauche, working tree à droite.
VS Code : Staged Changes est l'index, Changes le working tree ; les lettres
U, M, A à droite des fichiers sont celles de status -s.
git log (1/2)$ git log ← le détail : 5 lignes par commit
commit a9bfd1f59efa6179f9a3d33fc937cee75158081e
Author: Raphael Prasquier <raphael@imagine-app.fr>
Date: Thu Sep 10 12:00:00 2026 +0200
Précise la cuisson des crêpes
…
$ git log --oneline ← une ligne par commit
a9bfd1f Précise la cuisson des crêpes
093450a Ajoute la tarte aux pommes et le sommaire
7ac8892 Ajoute la recette des crêpes
a7d3639 Crée le carnet de recettes
Chaque commit est identifié par un hash SHA-1 de 40 caractères ; les 7 premiers suffisent pour le désigner. Au besoin : --stat (fichiers touchés), -p (les diffs), -- fichier (l'histoire d'un seul fichier).
git log (2/2), jusqu'au graphe$ git log --oneline --graph ← dessiné, mais depuis HEAD seulement
$ git log --oneline --graph --all --decorate ← toutes les branches, avec leur nom
* 9e21707 (cuisson) Réduit la cuisson des crêpes
| * 178222f (sauce) Annonce la section sauces
|/ ← une branche qui part
| * 12ed6ff (garniture) Ajoute les garnitures des crêpes
| * a9bfd1f (HEAD -> main) Précise la cuisson ← vous êtes ici
|/
* 093450a Ajoute la tarte aux pommes et le sommaire
* 7ac8892 Ajoute la recette des crêpes
* a7d3639 Crée le carnet de recettes
Trop long à taper ? Un alias, une fois pour toutes (
VS Code dessine ce graphe dans Source Control, section Graph) :
$ git config --global alias.tree "log --oneline --graph --all --decorate"
$ git tree
diff, show$ git diff # working tree ↔ index : ce que je n'ai pas encore "add"
$ git diff --staged # index ↔ HEAD : ce que le prochain commit contiendra
$ git diff HEAD~1 # working tree ↔ avant-dernier commit
$ git show a9bfd1f # un commit : son en-tête + son diff
diff --git a/recettes/crepes.md b/recettes/crepes.md
@@ -4,6 +4,6 @@
- 50 cl de lait
-Cuisson : 2 min par face.
+Cuisson : 3 min par face, à feu vif.
- : la ligne d'avant, + : celle d'après ; @@ -4,6 +4,6 @@ situe le bloc (à partir de la ligne 4, 6 lignes). HEAD~1 désigne le parent de HEAD, HEAD~2 le grand-parent.
VS Code : cliquer sur un fichier dans Source Control ouvre ce diff côte à côte, en couleurs.
.gitignore (1/2)Un fichier texte à la racine, commité, un motif par ligne :
# un commentaire commence la ligne (pas en fin de ligne !)
__pycache__/
.venv/
# les images produites par le code, sauf celle du projet
*.png
!logo.png
git status ;git rm --cached <f> pour l'oublier. VS Code : les fichiers ignorés apparaissent grisés dans l'explorateur.
.gitignore (2/2) : ne l'écrivez pas de tête Ce qu'il faut ignorer ne se devine pas : c'est une convention de vos outils. Improviser expose à des surprises.
| Oublié… | … et la surprise |
|---|---|
.env |
|
.venv/ |
des milliers de fichiers, des centaines de Mo |
.DS_Store |
les fichiers de votre Mac chez tous vos collègues |
uv.lock ignoré à tort |
l'inverse : chacun installe des versions différentes |
Partez donc d'un modèle, puis ajoutez vos lignes à vous :
uv init, npm create vite, cargo new… ;$ git cat-file -p a9bfd1f # « montre-moi cet objet »
tree c52f0cf1c8177960488734248311264a2539ee65
parent 093450a4632ed7c6f08ed84c9b912c924e418ade
author Raphael Prasquier <raphael@imagine-app.fr> 1789034400 +0200
Précise la cuisson des crêpes
Un commit, c'est : un tree (la photo de l'arborescence), son parent, un auteur, un message. Le diff que vous montre git show est recalculé entre deux photos.
Conséquences utiles : revenir à n'importe quel commit est instantané ; deux commits qui contiennent le même fichier le stockent une seule fois. Le détail (.git/objects, blobs, trees) est dans l'annexe « les internes ».
Une branche n'est pas une copie du projet : c'est un fichier de 41 octets dans .git/refs/heads/ qui contient le hash d'un commit. HEAD pointe vers la branche courante.
┌─ garniture
▼
a7d3639 ← 7ac8892 ← 093450a ← a9bfd1f ← 12ed6ff
▲
└─ main ◀── HEAD
À chaque commit, la branche courante avance d'un cran. Créer une branche coûte donc… 41 octets, et il n'y a aucune raison de s'en priver : une branche par idée, par essai, par bug.
switch (et checkout)$ git branch # liste ; * marque la branche courante
$ git branch garniture # crée, sans y aller
$ git switch garniture # y va : HEAD bouge, les fichiers suivent
$ git switch -c sauce # crée ET y va (le plus fréquent)
$ git switch main # retour
$ git branch -d garniture # supprime (refuse si non fusionnée ; -D force)
git switch (2019) remplace l'ancien git checkout <branche>, que vous verrez partout dans les tutoriels et dans learngitbranching. Même effet ; checkout faisait aussi le travail de restore, d'où son remplacement par deux commandes plus claires.
Avant de changer de branche :
git status propre, ou git refusera si vos modifications entrent en collision.
VS Code : le nom de la branche est en bas à gauche ; un clic liste les branches et propose Create new branch…
$ git switch --detach 7ac8892 # ou : git checkout 7ac8892
HEAD is now at 7ac8892 Ajoute la recette des crêpes
$ git status
HEAD detached at 7ac8892
Vous êtes « sur » un commit, plus sur une branche : pratique pour regarder le passé, tester une ancienne version. Cependant, si vous commitez ici, aucune branche n'avance : ces commits ne sont retenus que par le reflog.
Deux sorties propres :
git switch main pour rentrer ;git switch -c essai pour garder ce que vous venez de faire sur une nouvelle branche.Toujours dans bac-a-sable :
git switch -c idees, puis créez idees.md avec une ligne, add, commit ;ls : le fichier est là ; git switch main, puis ls : où est-il passé ?git log --oneline --graph --all : repérez HEAD, main, idees ;cat .git/HEAD, puis cat .git/refs/heads/idees : que contiennent-ils ?git switch --detach HEAD~1, git status, puis revenez sur main.Question : au point 2, pourquoi idees.md a-t-il disparu sans message d'erreur ?
merge fast-forwardSur main, on veut rapatrier garniture. Si main n'a pas bougé depuis la création de la branche, git se contente d'avancer le pointeur : c'est un fast-forward, aucun commit créé.
$ git switch main
$ git merge garniture
Updating a9bfd1f..12ed6ff
Fast-forward
recettes/crepes.md | 5 +++++
1 file changed, 5 insertions(+)
avant : … ← a9bfd1f (main) ← 12ed6ff (garniture)
après : … ← a9bfd1f ← 12ed6ff (main, garniture)
Si les deux branches ont avancé chacune de leur côté, git crée un commit à deux parents qui réunit les deux photos :
$ git merge sauce
Merge made by the 'ort' strategy.
README.md | 4 ++++
$ git log --oneline --graph
* fcd6c7a Merge branch 'sauce'
|\
| * 178222f Annonce la section sauces
* | 12ed6ff Ajoute les garnitures des crêpes
* | a9bfd1f Précise la cuisson des crêpes
|/
* 093450a Ajoute la tarte aux pommes et le sommaire
Git a comparé les deux branches à leur ancêtre commun (093450a) : chacune a changé un fichier différent, il a pris les deux. Pas de conflit.
Un conflit survient quand les deux branches ont modifié la même ligne depuis l'ancêtre commun. Git ne devine pas : il vous laisse trancher.
$ git merge cuisson
Auto-merging recettes/crepes.md
CONFLICT (content): Merge conflict in recettes/crepes.md
Automatic merge failed; fix conflicts and then commit the result.
<<<<<<< HEAD
Cuisson : 3 min par face, à feu vif. ← la version de ma branche (main)
=======
Cuisson : 1 min 30 par face. ← la version de la branche fusionnée
>>>>>>> cuisson
Le reste du fichier est déjà fusionné ; seul ce bloc attend votre décision.
Cuisson : 1 min 30 par face, à feu vif.
git add recettes/crepes.md ;git status doit dire All conflicts fixed but you are still merging ;git commit (le message « Merge branch 'cuisson' » est pré-rempli).Pour renoncer à tout moment : git merge --abort remet le dépôt comme avant le merge.
Un conflit n'est pas une panne : c'est git qui vous pose une question.
VS Code : au-dessus des marqueurs, Accept Current (votre branche), Accept Incoming (l'autre), Accept Both, ou Resolve in Merge Editor ; puis
+ et Commit.
main, changez la première ligne de notes.md, add, commit ;git switch idees, changez cette même première ligne autrement, add, commit ;git switch main && git merge idees : lisez le message ;git status, puis cat notes.md : repérez les trois marqueurs ;git add notes.md, git commit ;git log --oneline --graph : le commit à deux parents est là.Bonus si vous avez fini : refaites l'étape 3 sur une autre ligne et constatez que ce n'est plus un conflit.
rebase : rejouer au lieu de fusionner Même situation que pour le merge commit : main et sauce ont avancé chacune de leur côté. Au lieu de les réunir par un commit à deux parents, rebase rejoue les commits de sauce au bout de main.
rebase en pratique$ git switch sauce
$ git rebase main ← rejoue sauce au bout de main
Successfully rebased and updated refs/heads/sauce.
$ git switch main
$ git merge sauce ← plus rien à réunir : fast-forward
Updating 12ed6ff..be10b51
Fast-forward
git add et git rebase --continue (pas commit) ; git rebase --abort remet tout comme avant ;merge ou rebase ? Les deux sont corrects : merge garde l'histoire telle qu'elle s'est passée, rebase la rend lisible. La règle d'or : on ne rebase que des commits qui n'existent que chez vous. Des commits déjà partagés avec d'autres, on les fusionne. Nous verrons pourquoi en GIT2.
| Je veux… | Commande | Touche |
|---|---|---|
| jeter mes modifications d'un fichier | git restore <f> |
working tree |
retirer un fichier de l'index (annuler add) |
git restore --staged <f> |
index |
| refaire mon dernier commit (message, oubli) | git commit --amend |
HEAD |
| revenir en arrière en gardant le travail | git reset --soft / --mixed |
HEAD (+ index) |
| tout effacer jusqu'à un commit | git reset --hard <c> |
les trois |
| annuler un commit déjà poussé | git revert <c> |
crée un commit |
| retrouver un commit « perdu » | git reflog |
— |
Avant de défaire, une seule question : est-ce que ce commit a déjà été poussé ? Si oui, revert ; sinon, tout est permis.
restore : le working tree et l'index$ echo boom >> README.md
$ git status -s
M README.md
$ git restore README.md # ⚠ irréversible : la modification n'existait nulle part ailleurs
$ git status -s
$ git add README.md # …supposons qu'on l'ait ajouté par erreur
$ git restore --staged README.md # l'index redevient égal à HEAD ; le fichier est intact
$ git restore --source HEAD~1 recettes/tarte.md # ramène la version d'il y a un commit
restore <f> recopie l'index dans le fichier ; restore --staged <f> recopie HEAD dans l'index. Vous reconnaissez les deux flèches "retour" du schéma du trio. Ancienne forme : git checkout -- <f>.
VS Code : la flèche Discard Changes fait
git restore, tout aussi irréversible ; le − sur un fichier indexé fait git restore --staged.
reset : déplacer HEAD (et la branche)git reset <commit> fait reculer la branche courante ; le mode dit ce qu'on fait des deux autres cases.
| Mode | HEAD | index | working tree |
|---|---|---|---|
--soft |
recule | inchangé (les changements restent "add") | inchangé |
--mixed (défaut) |
recule | recule | inchangé (les changements restent, non "add") |
--hard |
recule | recule | recule |
$ git reset --soft HEAD~1 # "décommite" : tout est prêt à être recommité, autrement
$ git reset HEAD~1 # défait le commit et le add ; les fichiers restent modifiés
$ git reset --hard HEAD~1 # le commit disparaît de la branche… mais pas de git (cf. reflog)
revert : annuler sans réécrire$ git revert a9bfd1f
[main f65d87e] Revert "Précise la cuisson des crêpes"
1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline -2
f65d87e Revert "Précise la cuisson des crêpes"
a9bfd1f Précise la cuisson des crêpes
revert ne supprime rien : il crée un nouveau commit qui applique le changement inverse. L'historique reste vrai, et c'est la seule façon correcte d'annuler un commit que d'autres ont déjà récupéré.
reset réécrit le passé ; revert ajoute au présent. Dès qu'un commit est partagé : revert.
reflog : le filet de sécuritéGit note chaque déplacement de HEAD et le garde des semaines (30 à 90 jours), y compris vers des commits que git log ne montre plus :
$ git reset --hard HEAD~2
HEAD is now at 12ed6ff Ajoute les garnitures des crêpes
$ git reflog
12ed6ff HEAD@{0}: reset: moving to HEAD~2
dd98832 HEAD@{1}: commit (merge): Merge branch 'cuisson'
fcd6c7a HEAD@{2}: merge sauce: Merge made by the 'ort' strategy.
$ git reset --hard HEAD@{1} # ou : git reset --hard dd98832
HEAD is now at dd98832 Merge branch 'cuisson'
Retenez bien : un commit n'est jamais perdu tant qu'il figure dans le reflog. Ce qui est perdu, c'est ce qui n'a jamais été commité (restore, reset --hard sur des modifications en cours).
git log --oneline : notez le hash du commit du haut ;git reset --hard HEAD~1 ; git log --oneline : il a disparu, et le fichier avec ;git reflog : retrouvez-le (ligne HEAD@{1}) ;git reset --hard HEAD@{1} ; git log --oneline : il est revenu ;git switch -c avant-reset HEAD@{2} : une branche posée sur un ancien état, pour le garder.Question : à l'étape 2, où était passé ce commit, physiquement ? (indice : ls .git/objects)
Commiter sans regarder. git add . ramasse tout : le .venv/, les données de 300 Mo, le fichier avec la clé API. Réflexe : git status puis git diff --staged avant git commit.
Confondre reset --hard et restore. restore jette des modifications jamais commitées : c'est définitif. reset --hard jette des commits : c'est rattrapable par reflog. Le vrai danger est donc restore (et reset --hard avec du travail non commité).
Réécrire ce qui est partagé. rebase, reset ou --amend sur des commits que d'autres ont déjà : leur histoire et la vôtre ne se ressemblent plus. Réflexe : partagé ⇒ merge et revert.
Bonus : un conflit résolu se termine par git add + git commit, sinon vous restez « en cours de merge » pour toujours.
Le récapitulatif des encadrés vus en chemin, tout est dans la vue Source Control (
Ctrl+Shift+G) :
| Terminal | VS Code |
|---|---|
git status |
la liste Changes / Staged Changes |
git add / git restore --staged |
le + / le − à côté du fichier |
git commit -m |
la boîte de message + ✓ |
git diff |
cliquer sur un fichier modifié |
git switch |
le nom de branche, en bas à gauche |
git tree |
la section Graph de Source Control |
Confortable, et je m'en sers ; mais quand quelque chose "ne marche pas", c'est le terminal qui dit pourquoi.
add puis commit ; status et diff les comparent ;switch déplace HEAD et les fichiers suivent ;merge : fast-forward si l'une des branches n'a pas bougé, sinon commit à deux parents ; un conflit se règle à la main puis add + commit ;rebase : rejoue vos commits au bout d'une autre branche, en ligne droite et avec de nouveaux hashes ; jamais sur des commits partagés ;restore (fichiers), reset (commits locaux), revert (commits partagés), reflog (tout retrouver) ;commit : git status ; avant merge ou rebase : git tree.Sujet complet : tp.md sur le site du cours. En résumé :
.gitignore pour les fichiers générés ;rebase ;revert, puis un reset --hard suivi d'une récupération par reflog ;git-homework ; le TP donne la recette pas à pas (expliquée en GIT2).Rien n'est noté sur ce TP. En revanche, c'est dans git-homework que vous déposerez le bonus.
https://learngitbranching.js.org/?NODEMO=&locale=fr_FR : un git dessiné, à jouer dans le navigateur (il parle checkout, c'est l'ancien nom de switch).
intro1-4, rampup1-4, move1-4, mixed1-5, advanced1-3) ;remote1-8, remoteAdvanced1-8) ;show solution n'est pas compté par le jeu.Pour rendre : sur le site, F12 → Console, tapez copy(exportLevelProgress()), collez dans un fichier solvedMap.json à la racine de git-homework, commit, push. Mercredi 21 octobre 2026, 18 h.
C'est déclaratif : je vous fais confiance. Pas de note de Git : le bonus s'ajoute à votre note de PE.
rebase avant GIT2 ;git help <commande> ou man git-reset : la documentation officielle est bonne, et locale.Prochaine séance, GIT2 (22 octobre) : worktree, dépôts distants (clone, fetch, push, pull), fork et pull request, rebase interactif, atelier collaboratif.
Pour toute question, vous pouvez me contacter par email : raphael@imagine-app.fr.