Git
Senior
Quiz-Git-Senior
Git-Workflow
Git-Hooks
Git-Internals
Entretien-Git-Senior
Qcm-Git-Expert
Git-Bisect
Git-Reflog
Gitflow
Git-Submodules
Git-Worktree
Rebase-Interactif
📝
Entretien Git Senior — Workflows, Hooks et Internals
Maîtrisez Git en expert avec 20 questions niveau senior : workflows, hooks, rebase interactif, bisect, reflog et internals. Pour leads et architectes.
Banque de révision : les 60 questions corrigées
Voici l'intégralité des 60 questions de « Entretien Git Senior — Workflows, Hooks et Internals », chacune avec sa bonne réponse et son explication. Le quiz interactif ci-dessus en tire une sélection aléatoire à chaque tentative — utilisez cette liste complète pour réviser.
Explication :
Dans
git rebase -i, squash fond le commit dans le précédent et ouvre l'éditeur pour combiner les deux messages. fixup fait la même fusion mais jette le message du commit fusionné. reword ne fusionne rien, il ne fait que réécrire le message. edit met en pause le rebase pour amender le commit.
squash garde les messages, fixup les jette.
Explication :
Remplacer
pick par drop (ou simplement supprimer la ligne) retire le commit de l'historique reconstruit. Les modifications qu'il portait sont alors perdues, sauf récupération via git reflog. C'est l'équivalent ciblé d'un git reset sur un seul commit du milieu de l'historique.
Supprimer la ligne équivaut à drop.
Explication :
Les deux fusionnent un commit dans son prédécesseur.
squash ouvre l'éditeur pour combiner les messages des deux commits. fixup applique silencieusement les changements et conserve uniquement le message du commit cible, sans ouvrir d'éditeur. C'est pourquoi git commit --fixup=<sha> associé à rebase -i --autosquash automatise le nettoyage.
fixup = squash silencieux.
Explication :
git bisect run <script> exécute le script à chaque étape de la dichotomie. Le code de sortie détermine le verdict : 0 signifie « good », 1 à 124 (sauf 125) signifie « bad », et 125 signifie « skip » (commit non testable). Git converge ainsi vers le premier commit fautif sans intervention manuelle.
Code de sortie 0 = good, 1 = bad, 125 = skip.
Explication :
On lance d'abord
git bisect start, puis on marque la version actuelle cassée avec git bisect bad, puis un commit ancien sain avec git bisect good <sha>. Git checkout alors un commit médian. On répète bad/good jusqu'à isolation. Un git bisect reset final restaure la branche d'origine.
start, bad, good : Git fait le reste par dichotomie.
Explication :
HEAD@{n} est une référence du reflog : elle pointe vers la valeur que HEAD avait n déplacements en arrière. HEAD@{0} est la position actuelle, HEAD@{2} l'avant-avant-dernière. On peut aussi utiliser une date : HEAD@{yesterday}. C'est l'outil clé pour récupérer un état perdu après un reset.
HEAD@{n} = position de HEAD il y a n déplacements.
Explication :
Le commit n'est pas détruit immédiatement : il devient dangling mais reste accessible.
git reflog liste les positions récentes de HEAD ; on y repère le SHA perdu, puis on exécute git reflog
git reset --hard HEAD@{1} ou directement sur le SHA. Tant que git gc n'a pas purgé l'objet (rétention par défaut 90 jours), la récupération est garantie.
Le reflog est ton filet de sécurité local.
Explication :
Le hook
commit-msg reçoit en argument le chemin du fichier temporaire contenant le message. Il peut donc le lire, le valider contre une regex (ex : feat:, fix:) et renvoyer un code non nul pour annuler le commit. pre-commit s'exécute avant que le message soit saisi, il sert plutôt au lint et aux tests.
commit-msg = validation du texte du message.
Explication :
post-receive s'exécute une fois, côté serveur, après que toutes les refs ont été mises à jour. Il reçoit sur stdin les triplets ancien-SHA / nouveau-SHA / nom-de-ref. C'est le point d'entrée classique pour notifier une CI, mettre à jour un site ou déclencher un déploiement. pre-receive, lui, peut rejeter le push avant écriture des refs.
post-receive = déclenchement post-push côté serveur.
Explication :
prepare-commit-msg s'exécute après la génération du message par défaut mais avant l'ouverture de l'éditeur. Il reçoit le chemin du fichier de message et la source (message, merge, squash, template). On l'utilise pour injecter automatiquement un numéro de ticket, un préfixe de branche ou un template. commit-msg, lui, intervient après la saisie pour valider.
prepare-commit-msg agit avant l'éditeur, commit-msg après.
Explication :
Les hooks natifs vivent dans
.git/hooks, qui n'est pas versionné : impossible de les partager nativement avec l'équipe. Husky configure core.hooksPath vers un dossier .husky/ committé, garantissant que chaque développeur exécute les mêmes contrôles après npm install. Cela standardise lint, tests et validation de message à l'échelle du projet.
.git/hooks n'est pas versionné : Husky règle ce problème.
Explication :
Le trunk-based development mise sur des branches très courtes (souvent moins d'un jour) fusionnées fréquemment dans
main, soutenu par des feature flags pour masquer le code incomplet. Gitflow, lui, multiplie les branches de longue durée (develop, release/*, hotfix/*), ce qui retarde l'intégration et augmente les conflits. Le trunk-based est mieux adapté au déploiement continu.
Trunk-based : branches courtes, intégration fréquente.
Explication :
Dans Gitflow,
develop est la branche d'intégration : les branches feature/* y sont fusionnées une fois terminées. Quand un lot de fonctionnalités est prêt, on crée une branche release/* à partir de develop. Les hotfix/* partent de main. main ne contient que des versions livrées et taguées.
develop intègre, main livre, dans Gitflow.
Explication :
Le GitHub Flow est volontairement minimaliste :
main est toujours déployable, chaque travail part d'une branche dédiée, une pull request ouvre la revue et la discussion, puis le merge dans main déclenche le déploiement. Il n'y a pas de branche develop ni release. Le GitLab Flow ajoute, lui, des branches d'environnement (staging, production).
GitHub Flow : main + branches + PR, rien de plus.
Explication :
Git stocke quatre types d'objets : le blob (contenu d'un fichier), le tree (un répertoire, listant blobs et sous-trees), le commit (instantané pointant vers un tree, des parents, auteur et message) et le tag annoté (objet pointant vers un autre objet avec métadonnées). Une ref n'est pas un objet mais un simple pointeur nommé vers un SHA.
blob, tree, commit, tag : les 4 objets de Git.
Explication :
Un objet commit ne stocke pas de diff : il pointe vers un unique
tree représentant l'instantané complet du projet à cet instant, vers zéro, un ou plusieurs commits parent, et contient les lignes author, committer et le message. Les diffs sont calculés à la demande entre deux trees. C'est un modèle d'instantanés, pas de deltas.
Git stocke des instantanés, pas des diffs.
Explication :
Les objets « loose » individuels finissent regroupés dans des packfiles (
.git/objects/pack/*.pack), accompagnés d'un fichier .idx. Le packfile applique une compression delta entre objets similaires, réduisant fortement la taille du dépôt et accélérant les transferts réseau. git gc et git repack génèrent et optimisent ces fichiers.
Packfile = objets compressés en delta pour gagner de l'espace.
Explication :
Des attaques de collision pratiques sur SHA-1 (ex : SHAttered) ont montré qu'on pouvait théoriquement forger deux objets de même empreinte. Git a donc introduit un mode de dépôt utilisant SHA-256, qui n'a pas de collision connue, pour garantir l'intégrité cryptographique de l'historique. La migration reste progressive pour des raisons de compatibilité de l'écosystème.
SHA-256 : réponse aux collisions de SHA-1.
Explication :
git submodule add <url> <chemin> crée ou met à jour le fichier .gitmodules à la racine, qui est versionné et partagé. Il décrit le chemin et l'URL de chaque sous-module. Le dépôt parent enregistre en plus un gitlink : une entrée de tree pointant vers un SHA précis du sous-module. La config locale .git/config ne reçoit l'URL qu'après submodule init.
.gitmodules est versionné, .git/config est local.
Explication :
Un
git clone sans option ne récupère pas le contenu des sous-modules : leurs dossiers restent vides. Il faut ensuite git submodule update --init --recursive, ou cloner directement avec --recurse-submodules. Autre piège : un sous-module se trouve en detached HEAD sur le SHA enregistré, il faut donc faire attention en y committant directement.
clone --recurse-submodules évite les dossiers vides.
Explication :
git worktree add ../hotfix hotfix crée un second répertoire de travail rattaché au même dépôt .git, checkouté sur une autre branche. On peut ainsi corriger un bug urgent sans stash ni perdre l'état de la branche en cours. Chaque worktree a son index et son HEAD, mais partage les objets. Une même branche ne peut être active que dans un seul worktree.
worktree : plusieurs dossiers, un seul dépôt.
Explication :
rerere signifie « reuse recorded resolution ». Activé via
git config rerere.enabled true, il enregistre la façon dont on résout un conflit. Si le même conflit réapparaît plus tard (typiquement lors de rebases répétés d'une branche de longue durée), Git réapplique automatiquement la résolution mémorisée. C'est un gain de temps majeur sur les longues branches.
rerere : Git mémorise et rejoue tes résolutions.
Explication :
git filter-branch est notoirement lent et truffé de pièges (gestion incorrecte des refs, performances catastrophiques sur gros dépôts) ; la documentation Git le déconseille explicitement. git filter-repo, outil externe en Python, est bien plus rapide, sûr et expressif pour purger des fichiers sensibles ou découper un dépôt. Les deux réécrivent l'historique et changent donc tous les SHA.
filter-repo remplace officiellement filter-branch.
Explication :
Un objet dangling n'est référencé par aucune branche, tag ni reflog.
git fsck --lost-found les liste. Ils proviennent souvent de commits abandonnés (rebase, reset, amend). git gc les purge lorsqu'ils dépassent la durée de rétention (gc.pruneExpire, 2 semaines par défaut pour les objets non joignables). Avant gc, ils restent récupérables.
fsck --lost-found révèle les objets orphelins.
Explication :
git fsck (file system check) parcourt la base d'objets pour vérifier leur intégrité : sommes de contrôle, validité des liens entre commits, trees et blobs. Il signale les objets corrompus, manquants (missing) ou orphelins (dangling). C'est l'outil de diagnostic de référence et un point d'entrée pour récupérer du travail perdu via --lost-found.
fsck audite la santé de la base d'objets.
Explication :
Avec
git cherry-pick -x <sha>, Git ajoute au message du nouveau commit une ligne (cherry picked from commit <sha>). Cela documente la traçabilité entre les deux commits, ce qui est précieux lorsqu'on porte un correctif d'une branche à une autre (backport). Sans -x, le lien d'origine n'est pas conservé dans le message.
-x trace l'origine du cherry-pick dans le message.
Explication :
La notation
git cherry-pick A..B applique tous les commits accessibles depuis B mais pas depuis A : A est donc exclu, B inclus. Pour inclure aussi A, on utilise A^..B. C'est la même logique de plage que git log A..B. Cela permet de rejouer plusieurs correctifs consécutifs sur une autre branche en une seule commande.
A..B exclut A, inclut B (comme git log).
Explication :
git rebase --onto <nouvelle-base> <ancienne-base> <branche> rejoue les commits compris entre l'ancienne base (exclue) et la branche, par-dessus la nouvelle base. Ici, les commits de feature situés après feature~3 sont transplantés sur main. C'est l'outil idéal pour détacher une branche d'un mauvais point de départ ou extraire une sous-série de commits.
--onto : transplante précisément une tranche de commits.
Explication :
L'option
-X ours (option de la stratégie recursive/ort) résout automatiquement les zones en conflit en privilégiant notre version, tout en intégrant normalement les parties non conflictuelles de l'autre branche. À ne pas confondre avec la stratégie -s ours, qui ignore totalement le contenu de l'autre branche et ne garde que le nôtre.
-X ours résout les conflits ; -s ours ignore tout l'autre côté.
Explication :
Depuis Git 2.34, la stratégie de merge par défaut est ort (« Ostensibly Recursive's Twin »), qui remplace l'ancienne
recursive. Réécrite en profondeur, ort est plus rapide, plus économe en mémoire, gère mieux les renommages et les conflits répertoire/fichier, et évite d'écrire des fichiers intermédiaires inutiles sur le disque pendant les merges et rebases.
ort = la nouvelle stratégie recursive, plus rapide.
Explication :
--force écrase la branche distante sans condition, au risque de détruire le travail d'un collègue poussé entre-temps. --force-with-lease vérifie d'abord que la ref distante correspond toujours à la valeur qu'on a localement connue ; si quelqu'un a poussé entre-temps, le push est refusé. C'est un garde-fou indispensable pour réécrire l'historique en équipe.
--force-with-lease : un push forcé qui protège des écrasements.
Explication :
git replace <objet> <remplacement> crée une ref dans refs/replace/ indiquant à Git de substituer transparemment un objet par un autre à la lecture. L'historique réel reste intact. C'est utile pour « greffer » deux historiques distincts ou tester une correction sans réécrire les SHA. Les remplacements ne sont pas propagés par défaut lors d'un push.
git replace : substitution virtuelle, sans réécriture.
Explication :
git notes attache des métadonnées à un commit sans changer son SHA : les notes vivent dans une ref dédiée (refs/notes/commits). On peut ainsi ajouter a posteriori des informations (résultat de CI, revue, ticket) sans réécrire l'historique. Les notes ne sont pas poussées/tirées par défaut et nécessitent un refspec explicite.
git notes : métadonnées sans toucher au SHA.
Explication :
La signature (
git commit -S ou la signature SSH via gpg.format=ssh) ajoute une preuve cryptographique que le commit a bien été créé par le détenteur de la clé et n'a pas été altéré. Les plateformes affichent alors un badge « Verified ». C'est une mesure clé contre l'usurpation d'identité d'auteur, triviale autrement puisque user.email est librement configurable.
Signer un commit prouve « qui » et garantit l'intégrité.
Explication :
git sparse-checkout permet de ne matérialiser dans le répertoire de travail qu'un sous-ensemble de chemins, tout en conservant l'historique complet. Très utile sur les monorepos : un développeur ne checkout que le dossier de son équipe. Combiné au mode cone et à un partial clone, on peut même éviter de télécharger les blobs hors périmètre.
sparse-checkout : un working directory partiel sur un monorepo.
Explication :
Un shallow clone (
--depth N) tronque l'historique aux N derniers commits : rapide mais limité pour l'archéologie de code et certains merges. Un partial clone (--filter=blob:none) récupère tout le graphe de commits mais diffère le téléchargement des blobs jusqu'à ce qu'ils soient réellement nécessaires. Le partial clone est plus souple pour les très gros dépôts.
shallow tronque l'historique, partial diffère les blobs.
Explication :
git maintenance orchestre des tâches d'optimisation : mise à jour du commit-graph, prefetch des remotes, repack incrémental, expiration du reflog. git maintenance start les planifie en tâche de fond (cron, systemd, planificateur Windows). C'est l'évolution moderne du git gc automatique, pensée pour garder un gros dépôt performant sans intervention manuelle.
git maintenance : le gc moderne et planifié.
Explication :
En detached HEAD,
HEAD référence directement un SHA de commit au lieu de pointer vers une branche. Cela arrive après git checkout <sha> ou un checkout de tag. Tout nouveau commit créé dans cet état n'est rattaché à aucune branche ; si on change de branche sans le sauvegarder, il devient orphelin. On le préserve avec git switch -c <nom>.
Detached HEAD : crée une branche avant de partir.
Explication :
Dans un monorepo volumineux, le nombre de fichiers et la taille de l'historique dégradent les opérations comme
clone, checkout et status. Les parades incluent sparse-checkout, partial clone, le commit-graph, l'index fsmonitor et git maintenance. Des outils comme Scalar (intégré à Git) regroupent ces optimisations pour rendre les très gros monorepos exploitables.
Monorepo : sparse-checkout + partial clone + commit-graph.
Explication :
.gitattributes applique des comportements par motif de chemin : normalisation des fins de ligne (text=auto, eol), marquage binaire, filtres clean/smudge (utilisés par Git LFS), pilotes de diff/merge personnalisés, ou export-ignore. Versionné, il garantit un traitement cohérent des fichiers entre tous les contributeurs, indépendamment de leur OS.
.gitattributes pilote le traitement par type de fichier.
Explication :
Git LFS (Large File Storage) remplace, via un filtre clean/smudge configuré dans
.gitattributes, le contenu du gros fichier par un petit fichier pointeur texte (OID, taille). Le contenu réel est stocké sur un serveur LFS distinct et téléchargé à la demande au checkout. Cela évite de gonfler l'historique Git avec des binaires lourds.
Git LFS : un pointeur dans Git, le binaire ailleurs.
Explication :
git range-diff compare deux séries de commits entre elles, par exemple l'ancienne et la nouvelle version d'une branche après un rebase ou un amend. Il apparie les commits correspondants et montre comment chacun a évolué. C'est l'outil idéal pour réviser ce qui a changé entre deux itérations d'une pull request ou d'un patch set.
range-diff : « diff de diffs » entre deux versions d'une branche.
Explication :
La merge base est l'ancêtre commun le plus récent des deux têtes à fusionner ; on la calcule avec
git merge-base. L'algorithme de three-way merge compare chaque côté à cette base : si un seul côté a modifié une zone, la modification est prise ; si les deux l'ont changée différemment, un conflit est signalé. La base évite donc d'inventer de faux conflits.
La merge base est le point de référence du three-way merge.
Explication :
Une simple comparaison à deux versions ne peut pas savoir qui a modifié une ligne : toute divergence semble conflictuelle. Le three-way merge introduit une troisième référence, la merge base, ce qui permet de distinguer « modifié d'un seul côté » (intégrable automatiquement) de « modifié des deux côtés » (vrai conflit). Cela réduit drastiquement les faux positifs.
La base commune transforme un faux conflit en intégration auto.
Explication :
Le commit-graph (
.git/objects/info/commit-graph) est un fichier d'index pré-calculé stockant métadonnées et relations de parenté des commits, avec un système de generation numbers. Il évite de désérialiser chaque objet commit lors des parcours du graphe, accélérant fortement git log, git merge-base et les calculs d'accessibilité sur les gros dépôts. git commit-graph write ou git maintenance le génère.
commit-graph : index pré-calculé du graphe de commits.
Explication :
Conventional Commits standardise le message sous la forme
type(scope): description, avec des types comme feat, fix, chore, docs. Un ! ou un pied de message BREAKING CHANGE: signale une rupture. Cette structure permet de générer automatiquement le changelog et de déduire le bump de version SemVer.
feat/fix/chore : la grammaire des Conventional Commits.
Explication :
Dans l'usage standard reliant Conventional Commits et SemVer (
MAJEUR.MINEUR.CORRECTIF) : un fix incrémente le correctif, un feat incrémente la version mineure (nouvelle fonctionnalité rétrocompatible), et un BREAKING CHANGE incrémente la version majeure. Les types docs ou chore n'entraînent en général aucun bump de version publiée.
fix → patch, feat → minor, BREAKING → major.
Explication :
Un tag léger est juste une ref nommée pointant sur un commit, sans métadonnées. Un tag annoté (
git tag -a) crée un véritable objet tag dans la base, contenant le tagueur, la date, un message et, éventuellement, une signature GPG/SSH. Pour des versions officielles, on utilise toujours des tags annotés, traçables et vérifiables.
Releases officielles : toujours un tag annoté.
Explication :
L'action
edit (ou e) interrompt le rebase juste après avoir appliqué le commit concerné. On peut alors modifier les fichiers, scinder le commit, ajouter ou retirer des changements, puis git commit --amend et git rebase --continue. reword ne change que le message ; edit autorise une modification complète du contenu.
edit met le rebase en pause pour retoucher le commit.
Explication :
Pendant une session
git bisect, Git checkout des commits intermédiaires en detached HEAD. Si on oublie git bisect reset, le dépôt reste sur le dernier commit testé au lieu de revenir à la branche initiale, et l'état de bisect persiste. git bisect reset nettoie cet état et restaure HEAD sur la branche de départ.
Termine toujours un bisect par git bisect reset.
Explication :
Une ref est un nom lisible pointant vers un SHA : branches (
refs/heads/), tags (refs/tags/), branches distantes (refs/remotes/). Elles existent comme petits fichiers sous .git/refs/ ou regroupées dans .git/packed-refs. HEAD est une ref symbolique pointant en général vers une branche. Manipuler une ref ne déplace qu'un pointeur, pas les objets.
Une branche est juste une ref : un pointeur vers un SHA.
Explication :
La stratégie octopus est automatiquement sélectionnée par
git merge lorsqu'on fusionne trois branches ou plus simultanément, produisant un commit à plusieurs parents. Elle refuse cependant de continuer si des conflits nécessitent une résolution manuelle. Elle sert surtout à regrouper des branches thématiques indépendantes sans conflit, par exemple dans certaines branches d'intégration.
octopus : merge de 3+ branches sans conflit.
Explication :
git commit --fixup=<sha> crée un commit dont le message commence par fixup! . Lors d'un git rebase -i --autosquash, Git repositionne automatiquement ce commit juste après sa cible et lui affecte l'action fixup. Le développeur n'a plus qu'à valider la todo-list. C'est le workflow recommandé pour corriger proprement un commit déjà revu sans réécrire manuellement la liste.
--fixup + --autosquash = nettoyage d'historique automatisé.
Explication :
git prune supprime de la base les objets inaccessibles (non joignables depuis une ref ou un reflog) dont la date dépasse le seuil d'expiration. C'est une opération bas niveau habituellement déclenchée par git gc, qui combine repack, expiration de reflog et prune. git clean, à ne pas confondre, supprime des fichiers non suivis du working directory, pas des objets.
git prune nettoie les objets ; git clean les fichiers non suivis.
Explication :
Le GitLab Flow enrichit le modèle simple de GitHub Flow avec des branches d'environnement (
staging, pre-production, production). Le code « descend » de main vers ces branches au fur et à mesure de sa promotion. Chaque branche reflète exactement ce qui est déployé sur l'environnement correspondant, ce qui clarifie la traçabilité des déploiements.
GitLab Flow : une branche = un environnement déployé.
Explication :
Par défaut, si la branche cible n'a pas divergé, Git effectue un fast-forward : il avance simplement le pointeur, sans commit de merge.
--no-ff force la création d'un commit de merge explicite, ce qui préserve dans l'historique la trace qu'une branche de fonctionnalité a existé. C'est une convention fréquente pour rendre l'historique lisible par fonctionnalité.
--no-ff garde une trace visible de la branche fusionnée.
Explication :
Le hook client
pre-push s'exécute après que les commits sont prêts mais avant tout transfert réseau. Il reçoit sur stdin les refs et SHA concernés. Renvoyer un code non nul annule le push. On l'emploie typiquement pour lancer la suite de tests ou refuser de pousser sur une branche protégée. pre-receive, lui, s'exécute côté serveur.
pre-push : dernier rempart côté client avant l'envoi.
Explication :
Réécrire l'historique avec
git filter-repo recalcule le contenu des commits : tous les SHA à partir du premier commit modifié changent. Il faut donc un push --force et tous les collaborateurs doivent re-cloner ou réaligner leurs branches. De plus, le fichier sensible peut subsister dans des forks, des caches de plateforme ou le reflog : sa rotation (mot de passe, clé) reste indispensable.
Réécrire l'historique change tous les SHA : coordonne l'équipe.
Explication :
Le fichier
.git/index matérialise la staging area (ou cache) : c'est un instantané structuré du contenu qui constituera le prochain commit. git add y inscrit les fichiers, git commit en construit un objet tree. L'index agit comme zone tampon entre le working directory et l'historique, et permet aussi de gérer les états de conflit pendant un merge.
.git/index = la staging area de Git.
Explication :
Valider les Conventional Commits dans la CI (en plus du hook
commit-msg local) garantit que chaque message respecte la convention, même si un développeur a contourné les hooks. Cette rigueur est la condition d'un pipeline de release automatisé : des outils comme semantic-release analysent les feat/fix/BREAKING CHANGE pour calculer la version SemVer, générer le changelog et publier le tag.
CI + Conventional Commits = versioning et changelog automatiques.