Jour 2 Day 2 · mardi 28 juillet 2026 Tuesday 28 July 2026 Outils Intermédiaire
Git avancé : rebase, cherry-pick & bisect Advanced Git: rebase, cherry-pick & bisect
Comprendre le modèle objet de Git pour ne plus jamais subir un rebase : merge vs rebase, cherry-pick, bisect et le reflog comme filet de sécurité — les questions Git qui trient les candidats en entretien. Understand Git's object model so a rebase never scares you again: merge vs rebase, cherry-pick, bisect and the reflog as a safety net — the Git questions that separate candidates in interviews.
L’essentiel
Git ne stocke pas des diffs : c’est une base de données de snapshots adressée par contenu. Chaque commit est un instantané complet du projet, identifié par un hash SHA-1 calculé sur son contenu. Toutes les commandes « avancées » — rebase, cherry-pick, bisect — cessent d’être magiques une fois ce modèle compris : elles ne font que créer de nouveaux commits et déplacer des pointeurs. Rien n’est modifié en place, et presque rien n’est vraiment perdu.
En entretien, ces sujets font la différence entre le candidat qui « utilise Git » (add/commit/push) et celui qui le comprend. « Merge ou rebase ? » tombe presque à chaque fois ; y répondre avec le modèle objet en tête change tout.
Comment ça marche
Le modèle objet tient en quatre types, stockés dans .git/objects :
- Blob — le contenu d’un fichier (pas son nom, pas son chemin).
- Tree — un répertoire : une liste de noms pointant vers des blobs et d’autres trees.
- Commit — pointe vers un tree (le snapshot complet), vers son ou ses parents, plus auteur, date et message. Le hash couvre tout cela : changer un caractère du message produit un commit différent.
- Tag annoté — un objet nommé qui pointe vers un commit (typiquement les releases).
Une branche n’est qu’un pointeur : un fichier de 41 octets dans .git/refs/heads/ contenant un hash de commit. HEAD pointe vers la branche courante. Créer une branche est instantané et gratuit — c’est la killer feature de Git, et l’historique complet n’est qu’un graphe orienté acyclique (DAG) de commits.
Merge vs rebase : git merge feature crée un commit de fusion à deux parents et préserve l’historique tel qu’il s’est réellement passé. git rebase main (depuis feature) rejoue chaque commit par-dessus main : de nouveaux commits (nouveaux hashes), un historique linéaire mais réécrit — les anciens commits existent toujours dans .git/objects, plus rien ne pointe dessus.
avant après `git rebase main`
A──B feature
/ ──C──D──E──A'──B' feature
──C──D──E main ▲
main
git merge | git rebase | |
|---|---|---|
| Historique | Réel, non linéaire | Réécrit, linéaire |
| Commits | 1 commit de fusion (2 parents) | Nouveaux commits, nouveaux hashes |
| Lecture & bisect | Plus bruyant | Ligne droite, facile à bisecter |
| Branche partagée | Sûr | Interdit |
| Usage type | Intégrer la PR | Mettre sa branche à jour sur main |
🎤 En entretien — à « merge ou rebase ? », le réflexe qui marque des points : dessiner ce schéma. Deux flèches, cinq commits, et vous montrez que vous comprenez le modèle objet au lieu de réciter une préférence.
Le rebase interactif (git rebase -i HEAD~5) ouvre la liste des commits dans l’éditeur : pick (garder), reword (changer le message), squash/fixup (fusionner avec le précédent), edit (s’arrêter pour amender), drop (supprimer) — et on peut réordonner les lignes. C’est l’outil standard pour nettoyer une branche avant d’ouvrir la pull request.
Une session type :
$ git rebase -i HEAD~4 # retravailler les 4 derniers commits
# --- le fichier todo ouvert dans l'éditeur ---
pick a1b2c3d feat: formulaire de login
reword f4e5d6c fix typo # s'arrêter pour réécrire ce message
squash 9a8b7c6 wip # fusionner dans le commit précédent
drop 3c2b1a0 logs de debug # supprimer ce commit
# sauvegarder et quitter : Git rejoue tout dans l'ordre.
# Ça tourne mal ? `git rebase --abort` remet tout comme avant.
💡 Filet de sécurité — un rebase raté ne détruit rien :
git reflogdonne le hash d’avant l’opération,git reset --hard HEAD@{n}restaure. Le dire spontanément montre que l’outil ne vous fait pas peur.
Concepts clés à maîtriser
- cherry-pick :
git cherry-pick <hash>rejoue un commit précis sur la branche courante (nouveau commit, nouveau hash). Cas d’usage canonique : reporter un hotfix demainvers une branche de release. À doser : si la branche source est mergée ensuite, le même changement existe deux fois dans l’historique. - bisect : recherche dichotomique du commit qui a introduit un bug.
git bisect start,git bisect bad(HEAD est cassé),git bisect good v1.2(cette version marchait) ; Git checkout le commit du milieu, on teste, on répondgoodoubad, et en log₂(n) étapes le coupable est identifié — 1000 commits ≈ 10 tests. Automatisable :git bisect run ./test.sh(exit 0 = good, autre = bad). - reflog :
git reflogjournalise localement tous les déplacements deHEADet des branches (conservés ~90 jours par défaut). Rebase raté,reset --hardmalheureux, branche supprimée : le reflog retrouve le hash d’avant, etgit reset --hard HEAD@{2}restaure. Un commit n’est réellement perdu que lorsqu’il n’est plus référencé nulle part et que le garbage collector est passé. - Stratégies de branches : trunk-based development = branches très courtes (heures, quelques jours) mergées vite sur
main, feature flags pour le code incomplet — la norme avec le CI/CD moderne. Git flow = branchesdevelop,release/*,hotfix/*structurées — pertinent pour du logiciel versionné livré (mobile, embarqué), trop lourd pour du déploiement continu. Savoir expliquer pourquoi trunk-based domine aujourd’hui. - Conventions de commits : Conventional Commits —
feat:,fix:,refactor:,chore:, suffixe!pour un breaking change. Historique lisible, changelog et bump de version automatisables (semantic-release). Message à l’impératif, court en première ligne, le pourquoi dans le corps. --force-with-lease: après un rebase,git pushest rejeté (historique divergent).--forceécrase la branche distante aveuglément ;--force-with-leaseéchoue si quelqu’un a poussé entre-temps. C’est le seul force push acceptable en équipe.
En entretien
« Merge ou rebase ? » — Les deux intègrent des changements, la différence est l’historique produit. Merge préserve la réalité (commit de fusion, historique non linéaire) ; rebase la réécrit pour obtenir un historique linéaire plus lisible et bisectable. Pratique courante : rebase de sa branche locale sur main pour la mettre à jour proprement, puis merge (souvent squash merge) de la PR. Règle d’or à citer spontanément : on ne rebase jamais des commits déjà poussés et partagés.
« Un bug est apparu quelque part dans les 500 derniers commits. Comment trouver lequel ? » — git bisect : on désigne un commit bad et un commit good, Git fait une recherche dichotomique — environ 9 tests suffisent pour 500 commits. Bonus : git bisect run avec un script de test pour automatiser entièrement la chasse.
« Tu as fait un git reset --hard sur le mauvais commit. C’est perdu ? » — Non : git reflog liste toutes les positions récentes de HEAD ; on repère l’entrée d’avant l’erreur puis git reset --hard HEAD@{n}. Ce qui n’est pas récupérable : le travail jamais commité — d’où l’intérêt des commits fréquents et du stash.
« C’est quoi une branche, concrètement ? » — Un pointeur mutable vers un commit, un fichier de 41 octets. L’historique est le DAG des commits ; la branche n’est qu’une étiquette qui avance à chaque nouveau commit. C’est pour ça que brancher est instantané, contrairement à SVN qui copiait des répertoires.
« Quand utiliser cherry-pick ? » — Pour reporter un correctif isolé vers une branche de release, ou récupérer un commit précis d’une branche abandonnée. Pas pour synchroniser des branches entières : c’est le rôle de merge/rebase, et les commits dupliqués compliquent l’historique.
Pièges & idées reçues
⚠️ La règle d’or — on ne rebase jamais une branche partagée : les nouveaux hashes laissent les collègues qui avaient basé leur travail sur les anciens commits avec des historiques divergents pénibles à réconcilier. Rebase uniquement sur ses commits locaux non poussés ; dans le doute, merge.
git push --forcepar réflexe : préférer--force-with-lease— même effet quand tout va bien, mais échoue si la branche distante a bougé depuis le dernier fetch. Écraser le travail d’un collègue avec--force, c’est le faux pas classique.- « Le rebase a supprimé mes commits » — non : les anciens commits ne sont plus référencés mais restent dans
.git/objectset dans le reflog pendant des semaines. git pulln’est pas anodin : c’estfetch+merge, source de commits de merge parasites.git pull --rebase(oupull.rebase=trueen config) garde l’historique local propre.- Squash systématique de toute la branche : pratique pour des PR courtes, mais on perd le découpage en étapes qui facilite le review… et le bisect.
Pour aller plus loin
- Pro Git, chap. 10 — Git Internals : le modèle objet expliqué par le livre de référence (gratuit)
- Les pages officielles git rebase et git bisect
- Conventional Commits : la spec des messages normalisés
- Learn Git Branching : visualiser rebase et cherry-pick en manipulant le graphe soi-même
The essentials
Git does not store diffs: it is a content-addressed database of snapshots. Each commit is a complete snapshot of the project, identified by a SHA-1 hash computed from its content. Every “advanced” command — rebase, cherry-pick, bisect — stops being magic once you understand this model: they only ever create new commits and move pointers. Nothing is modified in place, and almost nothing is truly lost.
In interviews, these topics separate the candidate who “uses Git” (add/commit/push) from the one who understands it. “Merge or rebase?” comes up almost every time; answering it with the object model in mind changes everything.
How it works
The object model fits in four types, stored in .git/objects:
- Blob — the content of a file (not its name, not its path).
- Tree — a directory: a list of names pointing to blobs and other trees.
- Commit — points to a tree (the full snapshot), to its parent(s), plus author, date and message. The hash covers all of it: changing one character of the message produces a different commit.
- Annotated tag — a named object pointing to a commit (typically releases).
A branch is just a pointer: a 41-byte file in .git/refs/heads/ containing a commit hash. HEAD points to the current branch. Creating a branch is instant and free — that’s Git’s killer feature, and the whole history is just a directed acyclic graph (DAG) of commits.
Merge vs rebase: git merge feature creates a merge commit with two parents and preserves history as it actually happened. git rebase main (from feature) replays each commit on top of main: new commits (new hashes), a linear but rewritten history — the old commits still exist in .git/objects, nothing points to them anymore.
before after `git rebase main`
A──B feature
/ ──C──D──E──A'──B' feature
──C──D──E main ▲
main
git merge | git rebase | |
|---|---|---|
| History | Real, non-linear | Rewritten, linear |
| Commits | 1 merge commit (2 parents) | New commits, new hashes |
| Reading & bisect | Noisier | Straight line, easy to bisect |
| Shared branch | Safe | Forbidden |
| Typical use | Integrating the PR | Updating your branch onto main |
🎤 In an interview — when asked “merge or rebase?”, the point-scoring reflex: draw this diagram. Two arrows, five commits, and you show you understand the object model instead of reciting a preference.
Interactive rebase (git rebase -i HEAD~5) opens the list of commits in your editor: pick (keep), reword (change the message), squash/fixup (fold into the previous one), edit (stop to amend), drop (delete) — and you can reorder the lines. It’s the standard tool for cleaning up a branch before opening the pull request.
A typical session:
$ git rebase -i HEAD~4 # rework the last 4 commits
# --- the todo file opened in the editor ---
pick a1b2c3d feat: login form
reword f4e5d6c fix typo # stop to rewrite this message
squash 9a8b7c6 wip # fold into the previous commit
drop 3c2b1a0 debug logs # delete this commit
# save and quit: Git replays everything in order.
# Going wrong? `git rebase --abort` puts everything back.
💡 Safety net — a botched rebase destroys nothing:
git refloggives you the hash from before the operation,git reset --hard HEAD@{n}restores it. Saying this unprompted shows the tool doesn’t scare you.
Key concepts to master
- cherry-pick:
git cherry-pick <hash>replays one specific commit onto the current branch (new commit, new hash). Canonical use case: backporting a hotfix frommainto a release branch. Use in moderation: if the source branch is later merged, the same change exists twice in history. - bisect: binary search for the commit that introduced a bug.
git bisect start,git bisect bad(HEAD is broken),git bisect good v1.2(that version worked); Git checks out the middle commit, you test, answergoodorbad, and in log₂(n) steps the culprit is found — 1000 commits ≈ 10 tests. Automatable:git bisect run ./test.sh(exit 0 = good, anything else = bad). - reflog:
git refloglocally journals every move ofHEADand of branches (kept ~90 days by default). A botched rebase, an unfortunatereset --hard, a deleted branch: the reflog gives you the previous hash, andgit reset --hard HEAD@{2}restores it. A commit is only truly lost once nothing references it anymore and the garbage collector has run. - Branching strategies: trunk-based development = very short-lived branches (hours, a few days) merged quickly into
main, feature flags for incomplete code — the norm with modern CI/CD. Git flow = structureddevelop,release/*,hotfix/*branches — relevant for versioned, shipped software (mobile, embedded), too heavy for continuous deployment. Be able to explain why trunk-based dominates today. - Commit conventions: Conventional Commits —
feat:,fix:,refactor:,chore:,!suffix for a breaking change. Readable history, automatable changelog and version bumping (semantic-release). Imperative mood, short first line, the why in the body. --force-with-lease: after a rebase,git pushis rejected (diverged history).--forceblindly overwrites the remote branch;--force-with-leasefails if someone pushed in the meantime. It’s the only acceptable force push on a team.
In an interview
“Merge or rebase?” — Both integrate changes; the difference is the resulting history. Merge preserves reality (merge commit, non-linear history); rebase rewrites it to get a linear history that’s easier to read and to bisect. Common practice: rebase your local branch onto main to update it cleanly, then merge (often squash merge) the PR. Golden rule to state unprompted: never rebase commits that have already been pushed and shared.
“A bug appeared somewhere in the last 500 commits. How do you find which one?” — git bisect: you mark one bad and one good commit, Git binary-searches — about 9 tests are enough for 500 commits. Bonus: git bisect run with a test script to automate the whole hunt.
“You ran git reset --hard on the wrong commit. Is it gone?” — No: git reflog lists all recent positions of HEAD; find the entry from before the mistake, then git reset --hard HEAD@{n}. What is not recoverable: work that was never committed — hence frequent commits and the stash.
“What is a branch, concretely?” — A mutable pointer to a commit, a 41-byte file. The history is the DAG of commits; the branch is just a label that advances with each new commit. That’s why branching is instant, unlike SVN which copied directories.
“When would you use cherry-pick?” — To backport an isolated fix to a release branch, or to salvage one specific commit from an abandoned branch. Not to synchronize whole branches: that’s what merge/rebase are for, and duplicated commits complicate history.
Pitfalls & misconceptions
⚠️ The golden rule — never rebase a shared branch: the new hashes leave colleagues who based their work on the old commits with diverged histories that are painful to reconcile. Rebase only your local, unpushed commits; when in doubt, merge.
git push --forceas a reflex: prefer--force-with-lease— same effect when all is well, but it fails if the remote branch moved since your last fetch. Wiping out a colleague’s work with--forceis the classic blunder.- “The rebase deleted my commits” — no: the old commits are no longer referenced but remain in
.git/objectsand in the reflog for weeks. git pullis not harmless: it’sfetch+merge, a source of noisy merge commits.git pull --rebase(orpull.rebase=truein config) keeps local history clean.- Systematically squashing entire branches: fine for short PRs, but you lose the step-by-step structure that helps review… and bisect.
Going further
- Pro Git, ch. 10 — Git Internals: the object model explained by the reference book (free)
- The official git rebase and git bisect pages
- Conventional Commits: the spec for standardized messages
- Learn Git Branching: visualize rebase and cherry-pick by manipulating the graph yourself