Économisez 15% sur tous les services d'hébergement

Testez vos compétences et obtenez Réduction sur tout plan d'hébergement

Utilisez le code : Skills Commencer
Sections
Linux Serveurs virtuels

Structure du Référentiel Git : Un Guide Technique Complet

Git est un système de contrôle de version distribué qui stocke l’historique du projet sous forme de graphe acyclique dirigé (DAG) d’objets instantanés immuables. Chaque dépôt Git est construit à partir de trois zones logiques — le répertoire de travail, l’index de staging et le magasin d’objets dans .git/ — ainsi qu’un ensemble de pointeurs légers (branches, tags, remotes) qui naviguent dans cet historique. Comprendre comment ces couches interagissent fait la différence entre utiliser Git mécaniquement et l’utiliser avec une précision chirurgicale.

Si vous hébergez vous-même vos dépôts sur un VPS, maîtriser cette structure interne vous permet de récupérer après des incidents, de concevoir des pipelines CI/CD efficaces et d’auditer chaque octet de l’historique de votre projet sans dépendre d’une plateforme tierce.

Le modèle à trois zones : comment Git déplace les données

Avant de plonger dans les composants individuels, intériorisez le modèle de flux de données qui régit chaque opération Git :

Working Directory  -->  Staging Area (Index)  -->  .git/ Object Store
     (edit)               (git add)                  (git commit)

Les modifications voyagent de gauche à droite lorsque vous construisez un commit, et de droite à gauche lorsque vous restaurez ou réinitialisez. Chaque commande Git est essentiellement une opération de lecture ou d’écriture sur une ou plusieurs de ces zones.

Répertoire de travail

Le répertoire de travail (également appelé arbre de travail) est la vue du système de fichiers de votre projet à un état de checkout spécifique. Lorsque vous exécutez git clone ou git checkout, Git reconstruit les fichiers à partir des objets compressés dans .git/objects/ et les écrit dans ce répertoire.

Les fichiers dans le répertoire de travail existent dans l’un des quatre états suivants :

  • Non suivi — Git n’a jamais vu ce fichier ; il n’existe que sur le disque.
  • Suivi, non modifié — le fichier correspond exactement au dernier instantané commité.
  • Suivi, modifié — le fichier diffère du dernier instantané commité mais n’a pas été stagé.
  • Suivi, supprimé — le fichier a été supprimé du disque mais la suppression n’a pas été stagée.

Une nuance critique qui piège de nombreux développeurs : le répertoire de travail n’est pas une simple copie du dépôt. Git le reconstruit en lisant des objets tree et en décompressant des objets blob. Si .git/ est intact, vous pouvez toujours régénérer le répertoire de travail depuis zéro — l’inverse n’est pas vrai.

Sparse Checkout pour les grands monorepos

Sur des dépôts contenant des dizaines de milliers de fichiers (courant dans les architectures monorepo), vous pouvez limiter les chemins que Git matérialise dans le répertoire de travail :

git sparse-checkout init --cone
git sparse-checkout set services/api services/auth

C’est inestimable sur un VPS avec des E/S disque limitées, car Git évite de décompresser les blobs pour les chemins en dehors du cône.

Zone de staging (Index)

La zone de staging, appelée en interne l’index, est un fichier binaire situé à .git/index. Il agit comme un prochain commit proposé — un instantané mutable qui se situe entre votre répertoire de travail et le magasin d’objets permanent.

git add <file>          # Stage a specific file
git add -p              # Interactively stage hunks within a file
git add -u              # Stage all tracked modifications and deletions
git status              # Compare working directory and index against HEAD
git diff --cached       # Show diff between index and HEAD

Pourquoi l’index existe

L’index résout un problème que les outils VCS plus simples ignorent : les commits partiels. Vous avez peut-être modifié cinq fichiers mais ne souhaitez en inclure que trois dans le prochain commit. L’index vous permet de composer exactement l’instantané que vous avez l’intention d’enregistrer, indépendamment de ce que votre éditeur a ouvert.

Cas limite — corruption de l’index : Si un crash système interrompt un git add, le fichier d’index peut être corrompu. Les symptômes incluent git status qui se bloque ou rapporte des résultats bizarres. Récupération :

rm .git/index
git reset

Git reconstruit l’index à partir de HEAD sans toucher à votre répertoire de travail.

L’index comme registre de conflits de fusion

Lors d’un conflit de fusion, l’index stocke trois versions de chaque fichier en conflit simultanément (étapes 1, 2 et 3 — base, la nôtre, la leur). C’est pourquoi git diff --cached n’affiche rien d’utile en cours de conflit ; vous avez besoin de git diff --cc ou d’un outil de fusion pour inspecter les trois étapes.

Le répertoire .git/ : anatomie du magasin d’objets

Le répertoire .git/ est le dépôt. Tout le reste — le répertoire de travail, les clones distants — en est dérivé. Supprimer .git/ transforme un dépôt en répertoire ordinaire sans historique.

.git/
├── HEAD
├── config
├── description
├── index
├── COMMIT_EDITMSG
├── hooks/
├── info/
├── logs/
│   ├── HEAD
│   └── refs/
├── objects/
│   ├── info/
│   └── pack/
└── refs/
    ├── heads/
    ├── remotes/
    └── tags/

HEAD est un fichier texte brut contenant soit une référence symbolique (pointant vers une branche) soit un hash SHA-1 brut (état HEAD détaché).

cat .git/HEAD
# ref: refs/heads/main        <-- on a branch
# a3f1c9d...                  <-- detached HEAD

Le HEAD détaché n’est pas un état d’erreur — il est intentionnel lorsque vous extrayez un tag ou un commit spécifique pour inspection. Le danger est de faire des commits en HEAD détaché : ces commits ne sont accessibles que via le reflog jusqu’à ce que vous les attachiez à une branche.

git checkout -b rescue-branch   # Attach detached commits to a new branch

config

Le fichier de configuration du dépôt local. Il remplace les paramètres globaux (~/.gitconfig) et système (/etc/gitconfig). Entrées courantes :

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
[remote "origin"]
    url = git@github.com:user/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
    remote = origin
    merge = refs/heads/main

Sur un serveur auto-hébergé, vous modifierez fréquemment ce fichier directement lors de la rotation des URL distantes ou de la configuration de uploadpack.allowReachableSHA1InWant pour les clones partiels.

refs/

Le répertoire refs/ contient des fichiers texte brut, chacun contenant un seul hash SHA-1. Ce sont les pointeurs nommés qui rendent le DAG de Git navigable.

Type de refCheminDescription
Branche localerefs/heads/<name>Pointe vers le commit de pointe d’une branche
Branche de suivi distantrefs/remotes/<remote>/<name>Cache local de la pointe d’une branche distante
Tag légerrefs/tags/<name>Pointe directement vers un objet commit
Tag annotérefs/tags/<name>Pointe vers un objet tag, qui pointe vers un commit
Stashrefs/stashPointe vers le commit de stash

Pour des raisons de performance, Git compresse les refs dans .git/packed-refs une fois qu’un dépôt en accumule beaucoup. Vérifiez toujours les deux emplacements lors de scripts utilisant des refs.

Objets Git : le noyau immuable

Tout ce qui est stocké dans .git/objects/ est adressé par contenu : le nom de fichier est le hash SHA-1 (ou SHA-256 dans les versions plus récentes de Git) du contenu de l’objet. Cela rend Git intrinsèquement résistant à la falsification — modifier un octet change le hash, brisant la chaîne.

Les quatre types d’objets

Type d’objetCe qu’il stockePointe vers
BlobContenu brut du fichier (sans nom de fichier, sans permissions)Rien
TreeListe de répertoire : noms de fichiers, permissions, SHAs blob/treeBlobs et autres trees
CommitAuteur, committeur, horodatage, message, SHA(s) parent(s)Un tree + zéro ou plusieurs commits parents
TagIdentité du tagueur, horodatage, message, signature GPGGénéralement un commit

Inspection directe des objets

# Show the type of any object
git cat-file -t a3f1c9d

# Show the content of any object
git cat-file -p a3f1c9d

# Show the tree of the current HEAD commit
git ls-tree HEAD

# Show a specific blob's content
git show HEAD:src/main.py

Objets libres vs fichiers pack

Initialement, chaque objet est stocké comme un fichier compressé individuel sous .git/objects/<2-char-prefix>/<38-char-suffix>. Ce sont des objets libres. Au fil du temps, Git exécute git gc (collecte des déchets) pour regrouper les objets libres en fichiers pack (.git/objects/pack/*.pack) avec un index correspondant (.pack.idx).

Les fichiers pack utilisent la compression delta — stockant la différence entre des objets similaires plutôt que des copies complètes. Un dépôt contenant des milliers de fichiers texte similaires peut rétrécir considérablement après compression. Sur un VPS avec une capacité NVMe limitée, exécuter git gc --aggressive sur de grands dépôts avant archivage est une pratique standard.

git count-objects -vH    # Show loose object count and disk usage
git gc --aggressive      # Repack aggressively (CPU-intensive)
git verify-pack -v .git/objects/pack/*.idx | sort -k3 -n | tail -20
# Find the 20 largest objects in the pack

Historique des commits : le graphe acyclique dirigé

Chaque objet commit contient exactement un pointeur vers un objet tree (l’instantané du répertoire racine) et zéro ou plusieurs pointeurs vers des commits parents. Cela forme un DAG où :

  • Zéro parent = le commit initial (commit racine)
  • Un parent = un commit normal
  • Deux parents = un commit de fusion
  • Trois parents ou plus = une fusion octopus (rare, utilisée pour intégrer simultanément de nombreuses branches de fonctionnalités)
git log --oneline --graph --all    # Visualize the full DAG
git log --format="%H %P"           # Show each commit's SHA and parent SHA(s)

Immuabilité des commits et réécriture de l’historique

Étant donné que le SHA d’un commit est dérivé de son contenu (y compris les SHAs parents), toute réécriture crée un nouveau commit avec un nouveau SHA. Les opérations comme git rebase, git commit --amend et git filter-repo ne modifient pas l’historique — elles créent un historique parallèle. Les anciens commits restent dans le magasin d’objets jusqu’à la collecte des déchets.

C’est pourquoi forcer le push d’un historique réécrit vers une branche partagée est destructeur : les branches locales des collaborateurs pointent toujours vers l’ancienne chaîne de commits.

Branches : des pointeurs légers

Une branche n’est rien de plus qu’un fichier de 41 octets contenant un hash SHA-1. Créer une branche est instantané quelle que soit la taille du dépôt, car Git n’écrit qu’un seul petit fichier.

git branch feature/auth           # Create branch at current HEAD
git checkout -b feature/auth      # Create and switch in one step
git switch -c feature/auth        # Modern equivalent (Git 2.23+)
git branch -d feature/auth        # Delete (safe: refuses if unmerged)
git branch -D feature/auth        # Delete (force: regardless of merge status)

Fonctionnement interne des branches

cat .git/refs/heads/main
# a3f1c9d8e2b1f4c7d9e0a1b2c3d4e5f6a7b8c9d0

Lorsque vous faites un commit sur une branche, Git écrit le nouveau SHA de commit dans ce fichier. C’est tout ce qu’est « avancer un pointeur de branche ».

Branches de suivi et configuration upstream

Une relation de suivi indique à Git quelle branche distante une branche locale doit comparer pour le rapport de divergence git status et le comportement de git pull.

git branch --set-upstream-to=origin/main main
git branch -vv    # Show tracking relationships and ahead/behind counts

Tags : marqueurs permanents dans l’historique

Les tags marquent des commits spécifiques comme significatifs — généralement des versions logicielles. Contrairement aux branches, les tags ne sont pas déplacés par de nouveaux commits.

FonctionnalitéTag légerTag annoté
StockageUn fichier ref pointant vers un commitUn objet tag dans le magasin d’objets
MétadonnéesAucuneNom du tagueur, email, date, message
Signature GPGNon possiblePris en charge via git tag -s
Recommandé pour les versionsNonOui
Transfert avec git push --tagsOuiOui
git tag v2.1.0                              # Lightweight tag at HEAD
git tag -a v2.1.0 -m "Release 2.1.0"       # Annotated tag
git tag -s v2.1.0 -m "Signed release"      # GPG-signed annotated tag
git push origin --tags                      # Push all tags to remote
git push origin v2.1.0                      # Push a specific tag

Piège critique : git push ne pousse pas les tags par défaut. Les équipes oublient fréquemment cela et publient des notes de version référençant un tag qui n’existe pas sur le remote.

Remotes : collaboration distribuée

Un remote est une URL nommée stockée dans .git/config. Les branches de suivi distant (sous refs/remotes/) sont des instantanés locaux en lecture seule des branches du remote, mis à jour uniquement lorsque vous effectuez explicitement un fetch.

git remote add origin git@github.com:user/repo.git
git remote -v                          # List remotes with URLs
git remote set-url origin <new-url>    # Change a remote URL
git fetch origin                       # Update remote-tracking branches
git fetch --prune                      # Remove stale remote-tracking branches
git push origin main                   # Push local main to remote
git push -u origin feature/auth        # Push and set upstream tracking

Remotes multiples

Un seul dépôt peut suivre plusieurs remotes — courant lors de la maintenance d’un fork aux côtés de l’upstream :

git remote add upstream git@github.com:original/repo.git
git fetch upstream
git merge upstream/main

Lors de l’auto-hébergement de dépôts bare sur un serveur dédié pour votre équipe, chaque développeur ajoute le serveur comme remote et utilise l’authentification par clé SSH pour l’accès en push.

Hooks : application automatisée à chaque événement Git

Les hooks sont des scripts exécutables dans .git/hooks/. Git les appelle à des points définis dans le flux de travail. Ils ne sont pas transférés par git clone ou git push — chaque développeur (ou serveur) doit les installer indépendamment. C’est une source fréquente de confusion dans les environnements d’équipe.

Hooks côté client

HookDéclencheurUtilisation courante
pre-commitAvant l’invite de message de commitLinting, détection de secrets, exécution de tests
prepare-commit-msgAprès la création du message par défautInjecter le nom de branche dans le message
commit-msgAprès que l’utilisateur a écrit le messageAppliquer le format de commit conventionnel
post-commitAprès l’enregistrement du commitNotifications locales
pre-pushAvant l’exécution de git pushExécuter la suite de tests complète
pre-rebaseAvant le démarrage du rebaseEmpêcher le rebase des branches publiées

Hooks côté serveur

HookDéclencheurUtilisation courante
pre-receiveAvant la mise à jour des refsAppliquer la protection des branches, rejeter le force-push
updatePar ref lors de la réceptionApplication de politique par branche
post-receiveAprès la mise à jour de toutes les refsDéclencher CI/CD, envoyer des notifications

Exemple : hook pre-commit pour la détection de secrets

#!/usr/bin/env bash
# .git/hooks/pre-commit

if git diff --cached --name-only | xargs grep -lE '(AKIA|passwords*=|api_keys*=)' 2>/dev/null; then
    echo "ERROR: Potential secret detected in staged files. Commit aborted."
    exit 1
fi
exit 0

Rendez-le exécutable :

chmod +x .git/hooks/pre-commit

Pour la distribution de hooks à l’échelle de l’équipe, utilisez un outil comme Husky (projets Node.js) ou stockez les hooks dans un répertoire hooks/ à la racine du dépôt et créez des liens symboliques lors de la configuration du projet.

Reflog : le filet de sécurité

Le reflog enregistre chaque mouvement de HEAD et des pointeurs de branche, y compris les opérations qui semblent détruire l’historique (réinitialisations hard, rebases, commits amendés). Il est stocké dans .git/logs/.

git reflog                          # Show HEAD movement history
git reflog show main                # Show movement history for a specific branch
git checkout HEAD@{3}               # Check out the state HEAD was in 3 moves ago
git branch recovered HEAD@{5}       # Recover commits by branching from a reflog entry

Les entrées du reflog expirent après 90 jours par défaut (gc.reflogExpire). Sur un serveur de production, envisagez de prolonger cette durée :

git config gc.reflogExpire 180
git config gc.reflogExpireUnreachable 30

Dépôts bare : hébergement côté serveur

Un dépôt bare n’a pas de répertoire de travail. Il contient uniquement le contenu de .git/ au niveau racine. Les dépôts bare sont le format correct pour l’hébergement centralisé — ils acceptent les pushs sans les complications d’une branche extraite.

git init --bare /srv/repos/myproject.git

Lorsque vous poussez vers GitHub, GitLab ou un serveur Git auto-hébergé, vous poussez vers un dépôt bare. Si vous hébergez votre propre serveur Git sur un VPS avec cPanel ou un VPS Linux brut, les dépôts bare sous /srv/repos/ avec accès SSH sont l’architecture standard.

Initialisation d’un dépôt bare partagé

# On the server
git init --bare --shared=group /srv/repos/project.git
chown -R git:developers /srv/repos/project.git

# On a developer's machine
git remote add origin git@yourserver.com:/srv/repos/project.git
git push -u origin main

Stockage des objets Git : taille, intégrité et maintenance

Vérification de l’état du dépôt

git fsck --full          # Verify object integrity (finds dangling and corrupt objects)
git fsck --lost-found    # Write dangling objects to .git/lost-found/

Recherche et suppression des grands objets

Les fichiers binaires volumineux commités accidentellement sont une cause courante de dépôts gonflés. Identifiez-les avant d’utiliser git filter-repo pour les supprimer :

# Find the 10 largest objects by compressed size
git verify-pack -v .git/objects/pack/*.idx 
  | sort -k3 -rn 
  | head -10 
  | awk '{print $1}' 
  | xargs -I{} git cat-file -p {}
# Remove a file from all history (requires git-filter-repo)
git filter-repo --path path/to/large-file.bin --invert-paths

Après le filtrage, tous les collaborateurs doivent re-cloner — leurs dépôts locaux référencent des hashes SHA qui n’existent plus dans l’historique réécrit.

Comparaison : concepts clés du dépôt Git

ConceptTypeMutableStocké dansTransféré par push/fetch
BlobObjetNon.git/objects/Oui (si accessible)
TreeObjetNon.git/objects/Oui (si accessible)
CommitObjetNon.git/objects/Oui (si accessible)
Tag annotéObjetNon.git/objects/Uniquement avec --tags
BrancheRefOui.git/refs/heads/Oui
Branche de suivi distantRefOui (au fetch).git/refs/remotes/Non (cache local)
Tag légerRefNon.git/refs/tags/Uniquement avec --tags
HEADSymref/hashOui.git/HEADNon
IndexFichier binaireOui.git/indexNon
HooksScriptsOui.git/hooks/Non
ReflogJournalOui (expire automatiquement).git/logs/Non

Matrice de décision pratique et points clés

Utilisez cette liste de contrôle lors de la configuration ou de l’audit d’un dépôt Git sur votre infrastructure :

Initialisation du dépôt

  • Utilisez git init --bare --shared=group pour tout dépôt qui recevra des pushs de plusieurs utilisateurs.
  • Stockez les dépôts bare en dehors des répertoires accessibles par le web (jamais sous /var/www/).

Santé du magasin d’objets

  • Exécutez git fsck --full après tout incident de stockage ou erreur de système de fichiers.
  • Planifiez git gc périodiquement sur les dépôts de longue durée ; automatisez-le via cron sur votre serveur.
  • Surveillez la taille des fichiers pack avec git count-objects -vH ; enquêtez si le nombre d’objets libres dépasse 1 000.

Hygiène des branches et des refs

  • Supprimez rapidement les branches fusionnées ; les refs obsolètes s’accumulent et ralentissent les opérations git fetch --prune.
  • Utilisez git fetch --prune dans les pipelines CI pour éviter d’agir sur des branches distantes supprimées.

Déploiement des hooks

  • Ne comptez jamais sur .git/hooks/ pour une politique à l’échelle de l’équipe — les hooks ne sont pas clonés. Utilisez plutôt des hooks pre-receive côté serveur ou une passerelle CI.
  • Auditez les hooks côté serveur après chaque mise à niveau du serveur Git ; les chemins d’interpréteur de hooks peuvent changer.

Sécurité sur les serveurs auto-hébergés

  • Restreignez l’accès SSH à l’utilisateur git avec des commandes forcées (command= dans authorized_keys).
  • Utilisez git-shell comme shell de connexion pour l’utilisateur git afin d’empêcher l’exécution de commandes arbitraires.
  • Associez votre serveur de dépôt à un certificat SSL valide si vous exposez une interface web (Gitea, GitLab, cgit).

Réécriture de l’historique

  • Ne réécrivez jamais l’historique sur des branches partagées avec d’autres sans un plan de migration coordonné.
  • Après git filter-repo, tous les collaborateurs doivent re-cloner ; mettez à jour immédiatement les URL distantes CI/CD.

Reprise après sinistre

  • Prolongez l’expiration du reflog sur les serveurs de production (gc.reflogExpire = 180).
  • Conservez un clone bare secondaire sur un hôte séparé comme sauvegarde ; un simple git fetch depuis le primaire est suffisant.

FAQ

Quelle est la différence entre un dépôt Git bare et non-bare ?

Un dépôt non-bare possède un répertoire de travail où les fichiers sont extraits, ainsi qu’un sous-répertoire .git/ contenant le magasin d’objets. Un dépôt bare contient uniquement le magasin d’objets à sa racine (sans répertoire de travail) et est le format correct pour un serveur partagé qui reçoit des pushs.

Puis-je récupérer des commits après avoir exécuté git reset --hard ?

Oui, tant que les commits n’ont pas été collectés par le garbage collector. Exécutez git reflog pour trouver le SHA du commit que vous souhaitez récupérer, puis git checkout -b recovery-branch <SHA> pour l’attacher à une nouvelle branche. Les entrées du reflog sont conservées pendant 90 jours par défaut.

Pourquoi git push ne transfère-t-il pas mes tags ?

Par conception, git push ne transfère que les commits accessibles depuis les refs que vous poussez explicitement. Les tags sont des refs séparées et doivent être poussés avec git push origin --tags (tous les tags) ou git push origin <tagname> (un tag spécifique).

Que se passe-t-il avec l’index lors d’un conflit de fusion ?

L’index stocke simultanément les trois versions de chaque fichier en conflit : étape 1 (ancêtre commun/base), étape 2 (votre version) et étape 3 (leur version). Le git add normal n’écrit que l’étape 0 (résolue). Tant que tous les conflits ne sont pas résolus et stagés, git commit refusera de continuer.

En quoi les hooks Git diffèrent-ils entre les déploiements côté client et côté serveur ?

Les hooks côté client s’exécutent sur la machine du développeur et ne sont pas appliqués de manière centralisée — tout développeur peut les contourner en supprimant le fichier hook. Les hooks côté serveur (pre-receive, update, post-receive) s’exécutent sur le serveur d’hébergement et ne peuvent pas être contournés par le client, ce qui en fait le point d’application correct pour les politiques de protection des branches, les exigences de révision de code et les déclencheurs CI/CD.

Administration Serveurs dédiés Serveurs virtuels
Linux
Administration DNS Linux