Git 🌙
Chapters â–Ÿ 2nd Edition

7.13 Utilitaires Git - Replace

Replace

Git manipule des objets immuables mais il fournit un moyen de faire comme s’il pouvait remplacer des objets de sa base de donnĂ©es par d’autres objets.

La commande replace vous permet de spĂ©cifier un objet dans Git et de lui indiquer : « chaque fois que tu vois ceci, fais comme si c’était cette autre chose ». Ceci sert principalement Ă  remplacer un commit par un autre dans votre historique.

Par exemple, supposons que vous avez un Ă©norme historique de code et que vous souhaitez scinder votre dĂ©pĂŽt en un historique court pour les nouveaux dĂ©veloppeurs et un plus important et long pour ceux intĂ©ressĂ©s par des statistiques. Vous pouvez gĂ©nĂ©rer un historique depuis l’autre avec replace en remplaçant le commit le plus ancien du nouvel historique par le dernier commit de l’historique ancien. C’est sympa parce que cela signifie que vous n’avez pas besoin de rĂ©Ă©crire tous les commits du nouvel historique, comme vous devriez le faire pour les joindre tous les deux (Ă  cause de l’effet de lien des SHA-1).

Voyons ce que ça donne. Prenons un dépÎt existant, découpons-le en deux dépÎts, un récent et un historique, puis nous verrons comment les recombiner sans modifier les valeurs SHA-1 du dépÎt récent, grùce à replace.

Nous allons utiliser un dépÎt simple avec cinq commit simples :

$ git log --oneline
ef989d8 fifth commit
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit

Nous souhaitons couper ceci en deux lignes d’historiques. Une ligne ira de first commit Ă  fourth commit et sera la ligne historique. La seconde ligne ira de fourth commit Ă  fifth commit et sera ligne rĂ©cente.

replace1

Bien, la crĂ©ation de la ligne historique est simple, nous n’avons qu’à crĂ©er une branche dans l’historique et la pousser vers la branche master d’un nouveau dĂ©pĂŽt distant.

$ git branch history c6e1e95
$ git log --oneline --decorate
ef989d8 (HEAD, master) fifth commit
c6e1e95 (history) fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
replace2

Maintenant, nous pouvons pousser la nouvelle branche history vers la branche master du nouveau dépÎt :

$ git remote add project-history https://github.com/schacon/project-history
$ git push project-history history:master
DĂ©compte des objets : 12, fait.
Delta compression using up to 2 threads.
Compression des objets : 100% (4/4), fait.
Écriture des objets : 100% (12/12), 907 bytes, fait.
Total 12 (delta 0), reused 0 (delta 0)
DĂ©paquetage des objets : 100% (12/12), fait.
To git@github.com:schacon/project-history.git
 * [nouvelle branche]      history -> master

Bien, notre projet historique est publiĂ©. Maintenant, la partie la plus compliquĂ©e consiste Ă  tronquer l’historique rĂ©cent pour le raccourcir. Nous avons besoin d’un recouvrement pour pouvoir remplacer un commit dans un historique par un Ă©quivalent dans l’autre, donc nous allons tronquer l’historique Ă  fourth commit et fifth commit, pour que fourth commit soit en recouvrement.

$ git log --oneline --decorate
ef989d8 (HEAD, master) fifth commit
c6e1e95 (history) fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit

Il peut ĂȘtre utile de crĂ©er un commit de base qui contient les instructions sur la maniĂšre d’étendre l’historique, de sorte que les autres dĂ©veloppeurs puissent savoir comment s’y prendre s’ils butent sur le premier commit et ont besoin de plus d’histoire. Donc, ce que nous allons faire, c’est crĂ©er un objet commit initial comme base avec les instructions, puis rebaser les commits restants (quatre et cinq) dessus.

Nous avons besoin de choisir un point de dĂ©coupe, qui pour nous est third commit, soit le SHA-1 9c68fdc. Donc, notre commit de base sera crĂ©Ă© sur cet arbre. Nous pouvons crĂ©er notre commit de base en utilisant la commande commit-tree, qui accepte juste un arbre et nous fournit un SHA-1 d’un objet commit orphelin tout nouveau.

$ echo 'get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
Note

La commande commit-tree fait partie de ce qu’on appelle les commandes de « plomberie ». Ce sont des commandes qui ne sont pas destinĂ©es Ă  ĂȘtre utilisĂ©es directement, mais plutĂŽt au sein d'autres commandes Git en tant que petits utilitaires. Dans les occasions oĂč nous faisons des choses plus bizarres que de coutume comme actuellement, elles nous permettent de faire des actions de bas niveau qui ne sont pas destinĂ©es Ă  une utilisation quotidienne. Pour en savoir plus sur les commandes de plomberie, rĂ©fĂ©rez-vous Ă  Plomberie et porcelaine.

replace3

OK, donc maintenant avec un commit de base, nous pouvons rebaser le reste de notre historique dessus avec la commande git rebase --onto. L’argument --onto sera l’empreinte SHA-1 que nous venons tout juste de rĂ©cupĂ©rer avec la commande commit-tree et le point de rebasage sera third commit (le parent du premier commit que nous souhaitons garder, 9c68fdc).

$ git rebase --onto 622e88 9c68fdc
First, rewinding head to replay your work on top of it...
Applying: fourth commit
Applying: fifth commit
replace4

Bien, nous avons donc rĂ©Ă©crit l’historique rĂ©cent Ă  la suite du commit de base qui contient les instructions pour reconstruire l’historique complet. Nous pouvons pousser ce nouvel historique vers un nouveau projet et quand des personnes clonent ce dĂ©pĂŽt, elles ne voient que les deux commits les plus rĂ©cents et un commit avec des instructions.

Inversons les rĂŽles et plaçons-nous dans la position d’une personne qui clone le projet pour la premiĂšre fois et souhaite obtenir l’historique complet. Pour obtenir les donnĂ©es d’historique aprĂšs avoir clonĂ© ce dĂ©pĂŽt tronquĂ©, on doit ajouter un second dĂ©pĂŽt distant pointant vers le dĂ©pĂŽt historique et tout rĂ©cupĂ©rer  :

$ git clone https://github.com/schacon/project
$ cd project

$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
622e88e get history from blah blah blah

$ git remote add project-history https://github.com/schacon/project-history
$ git fetch project-history
From https://github.com/schacon/project-history
 * [nouvelle branche]      master     -> project-history/master

À prĂ©sent, le collaborateur aurait les commits rĂ©cents dans la branche master et les commits historiques dans la branche project-history/master.

$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
622e88e get history from blah blah blah

$ git log --oneline project-history/master
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit

Pour combiner ces deux branches, vous pouvez simplement lancer git replace avec le commit que vous souhaitez remplacer suivi du commit qui remplacera. Donc nous voulons remplacer fourth commit dans la branche master par fourth commit de la branche project-history/master :

$ git replace 81a708d c6e1e95

Maintenant, quand on regarde l’historique de la branche master, il apparaüt comme ceci :

$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit

Sympa, non ? Sans devoir changer tous les SHA-1 en amont, nous avons pu remplacer un commit dans notre historique avec un autre entiÚrement différent et tous les outils normaux (bisect, blame, etc) fonctionnent de maniÚre transparente.

replace5

Ce qui est intĂ©ressant, c’est que fourth commit a toujours un SHA-1 de 81a708d, mĂȘme s’il utilise en fait les donnĂ©es du commit c6e1e95 par lequel nous l’avons remplacĂ©. MĂȘme si vous lancez une commande comme cat-file, il montrera les donnĂ©es remplacĂ©es :

$ git cat-file -p 81a708d
tree 7bc544cf438903b65ca9104a1e30345eee6c083d
parent 9c68fdceee073230f19ebb8b5e7fc71b479c0252
author Scott Chacon <schacon@gmail.com> 1268712581 -0700
committer Scott Chacon <schacon@gmail.com> 1268712581 -0700

fourth commit

Souvenez-vous que le parent réel de 81a708d était notre commit de base (622e88e) et non 9c68fdce comme indiqué ici.

Une autre chose intéressante est que les données sont conservées dans nos références :

$ git for-each-ref
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit	refs/heads/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit	refs/remotes/history/master
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit	refs/remotes/origin/HEAD
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit	refs/remotes/origin/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit	refs/replace/81a708dd0e167a3f691541c7a6463343bc457040

Ceci signifie qu’il est facile de partager notre remplacement avec d’autres personnes, puisque nous pouvons pousser ceci sur notre serveur et d’autres personnes pourront le tĂ©lĂ©charger. Ce n’est pas trĂšs utile dans le cas de la reconstruction d’historique que nous venons de voir (puisque tout le monde tĂ©lĂ©chargerait quand mĂȘme les deux historiques, pourquoi alors les sĂ©parer ?), mais cela peut ĂȘtre utile dans d’autres circonstances.

scroll-to-top