Insérer un commit avant le commit racine dans Git?

J’ai déjà demandé comment écraser les deux premiers commits dans un repository git.

Bien que les solutions soient plutôt intéressantes et pas vraiment aussi hallucinantes que certaines autres choses de git, elles sont encore un peu proverbiales si vous devez répéter la procédure plusieurs fois au cours du développement de votre projet.

Donc, je préfère faire face à la douleur une seule fois, et ensuite être capable de toujours utiliser le rebase interactif standard.

Ce que je veux faire alors, c’est avoir un commit initial vide qui existe uniquement dans le but d’être le premier. Pas de code, rien. Il suffit de prendre de la place pour pouvoir rebaser.

Ma question est la suivante: avec un référentiel existant, comment puis-je insérer un nouveau commit vide avant le premier, et transférer tout le monde vers l’avant?

Mi-2017 réponse

Il est préférable de créer un nouvel engagement complètement vide sans effets secondaires en utilisant directement la plomberie de Git. Cela évite les effets secondaires: ne pas toucher la copie de travail ou l’index, pas de twigs temporaires à nettoyer, etc. Donc:

  1. Pour créer un commit, nous avons besoin d’une arborescence de répertoires, nous en créons une vide d’abord:

    tree=`git hash-object -wt tree --stdin < /dev/null` 
  2. Maintenant, nous pouvons envelopper un commit autour de lui:

     commit=`git commit-tree -m 'root commit' $tree` 
  3. Et maintenant, nous pouvons revenir sur cela:

     git rebase --onto $commit --root master 

Et c'est tout. Vous pouvez réorganiser tout cela en un seul trait si vous connaissez assez bien votre coquille.

(NB: dans la pratique, j'utiliserais maintenant filter-branch . Je le modifierai plus tard.)


Réponse historique (référencé par d'autres réponses)

Voici une implémentation plus propre de la même solution, dans la mesure où elle fonctionne sans avoir besoin de créer un référentiel supplémentaire, de disposer de télécommandes et de corriger une tête détachée:

 # first you need a new empty branch; let's call it `newroot` git checkout --orphan newroot git rm -rf . # then you apply the same steps git commit --allow-empty -m 'root commit' git rebase --onto newroot --root master git branch -d newroot 

Voila, vous vous êtes retrouvé sur master avec son historique réécrit pour inclure un commit root vide.


NB: sur les anciennes versions de Git sans le --orphan switch to checkout , vous avez besoin de la plomberie pour créer une twig vide:

 git symbolic-ref HEAD refs/heads/newroot git rm --cached -r . git clean -f -d 

Fusion des réponses d’Aristote Pagaltzis et d’Uwe Kleine-König et du commentaire de Richard Bronosky.

 git symbolic-ref HEAD refs/heads/newroot git rm --cached -r . git clean -f -d # touch .gitignore && git add .gitignore # if necessary git commit --allow-empty -m 'initial' git rebase --onto newroot --root master git branch -d newroot 

(juste pour tout mettre au même endroit)

J’aime la réponse d’Aristote. Mais trouvé que pour un repository de grande taille (> 5000 commits), la twig de filtrage fonctionne mieux que rebase pour plusieurs raisons 1) il est plus rapide 2) il ne nécessite aucune intervention humaine en cas de conflit de fusion. 3) il peut réécrire les balises – en les préservant. Notez que filter-branch fonctionne car il n’y a pas de question sur le contenu de chaque commit – il est exactement le même qu’avant cette ‘rebase’.

Mes pas sont:

 # first you need a new empty branch; let's call it `newroot` git symbolic-ref HEAD refs/heads/newroot git rm --cached -r . git clean -f -d # then you apply the same steps git commit --allow-empty -m 'root commit' # then use filter-branch to rebase everything on newroot git filter-branch --parent-filter 'sed "s/^\$/-p /"' --tag-name-filter cat master 

Notez que les options ‘–tag-name-filter cat’ signifient que les balises seront réécrites pour pointer vers les nouveaux commits.

J’ai utilisé avec succès des morceaux d’Aristote et de Kent:

 # first you need a new empty branch; let's call it `newroot` git checkout --orphan newroot git rm -rf . git commit --allow-empty -m 'root commit' git filter-branch --parent-filter \ 'sed "s/^\$/-p /"' --tag-name-filter cat -- --all # clean up git checkout master git branch -D newroot # make sure your twigs are OK first before this... git for-each-ref --format="%(refname)" refs/original/ | \ xargs -n 1 git update-ref -d 

Cela permettra également de réécrire toutes les twigs (pas seulement le master ) en plus des balises.

git rebase --root --onto $emptyrootcommit

devrait faire l’affaire facilement

Je suis excité et j’ai écrit une version “idempotente” de ce joli script … il insérera toujours le même commit vide, et si vous le lancez deux fois, cela ne changera pas vos hachages de validation à chaque fois. Alors, voici ma prise sur git-insert-empty-root :

 #!/bin/sh -ev # idempotence achieved! tmp_branch=__tmp_empty_root git symbolic-ref HEAD refs/heads/$tmp_branch git rm --cached -r . || true git clean -f -d touch -d '1970-01-01 UTC' . GIT_COMMITTER_DATE='1970-01-01T00:00:00 +0000' git commit \ --date='1970-01-01T00:00:00 +0000' --allow-empty -m 'initial' git rebase --committer-date-is-author-date --onto $tmp_branch --root master git branch -d $tmp_branch 

Cela vaut-il la complexité supplémentaire? peut-être pas, mais je vais utiliser celui-ci.

Cela DEVRAIT également permettre d’effectuer cette opération sur plusieurs copies clonées du repository, et finir avec les mêmes résultats, donc ils sont toujours compatibles … test … oui, ça marche, mais il faut aussi supprimer et append votre télécommandes à nouveau, par exemple:

 git remote rm origin git remote add --track master user@host:path/to/repo 

Eh bien, voici ce que j’ai imaginé:

 # Just setting variables on top for clarity. # Set this to the path to your original repository. ORIGINAL_REPO=/path/to/original/repository # Create a new repository… mkdir fun cd fun git init # …and add an initial empty commit to it git commit --allow-empty -m "The first evil." # Add the original repository as a remote git remote add previous $ORIGINAL_REPO git fetch previous # Get the hash for the first commit in the original repository FIRST=`git log previous/master --pretty=format:%H --reverse | head -1` # Cherry-pick it git cherry-pick $FIRST # Then rebase the remainder of the original branch on top of the newly # cherry-picked, previously first commit, which is happily the second # on this branch, right after the empty one. git rebase --onto master master previous/master # rebase --onto leaves your head detached, I don't really know why) # So now you overwrite your master branch with the newly rebased tree. # You're now kinda done. git branch -f master git checkout master # But do clean up: remove the remote, you don't need it anymore git remote rm previous 

Je pense que l’utilisation de git replace et de git filter-branch est une meilleure solution que l’utilisation d’un git rebase :

  • meilleure préformance
  • plus facile et moins risqué (vous pouvez vérifier votre résultat à chaque étape et défaire ce que vous avez fait …)
  • fonctionne bien avec plusieurs succursales avec des résultats garantis

L’idée est de:

  • Créer un nouveau commit vide dans le passé
  • Remplacez l’ancien commit root par un commit exactement similaire sauf que le nouveau commit racine est ajouté en tant que parent
  • Vérifiez que tout est comme prévu et exécutez git filter-branch
  • Encore une fois, vérifiez que tout va bien et nettoyez les fichiers git inutiles

Voici un script pour les 2 premières étapes:

 #!/bin/bash root_commit_sha=$(git rev-list --max-parents=0 HEAD) git checkout --force --orphan new-root find . -path ./.git -prune -o -exec rm -rf {} \; 2> /dev/null git add -A GIT_COMMITTER_DATE="2000-01-01T12:00:00" git commit --date==2000-01-01T12:00:00 --allow-empty -m "empty root commit" new_root_commit_sha=$(git rev-parse HEAD) echo "The commit '$new_root_commit_sha' will be added before existing root commit '$root_commit_sha'..." parent="parent $new_root_commit_sha" replacement_commit=$( git cat-file commit $root_commit_sha | sed "s/author/$parent\nauthor/" | git hash-object -t commit -w --stdin ) || return 3 git replace "$root_commit_sha" "$replacement_commit" 

Vous pouvez exécuter ce script sans risque (même si faire une sauvegarde avant d’agir est une bonne idée;)), et si le résultat n’est pas celui attendu, supprimez simplement les fichiers créés dans le dossier .git/refs/replace et essayer à nouveau;)

Une fois que vous avez vérifié que l’état du référentiel correspond à vos attentes, exécutez la commande suivante pour mettre à jour l’historique de toutes les twigs :

 git filter-branch -- --all 

Maintenant, vous devez voir 2 historiques, l’ancien et le nouveau (voir l’aide sur la filter-branch de filter-branch pour plus d’informations). Vous pouvez comparer le 2 et vérifier à nouveau si tout va bien. Si vous êtes satisfait, supprimez les fichiers inutiles:

 rm -rf ./.git/refs/original rm -rf ./.git/refs/replace 

Vous pouvez revenir à votre twig principale et supprimer la twig temporaire:

 git checkout master git branch -D new-root 

Maintenant, tout devrait être fait;)

Voici mon script bash basé sur la réponse de Kent avec des améliorations:

  • il vérifie la twig d’origine, pas seulement master , une fois terminé;
  • J’ai essayé d’éviter la twig temporaire, mais git checkout --orphan ne fonctionne qu’avec une twig, pas un état détaché, donc il est extrait suffisamment longtemps pour que le nouveau root soit validé, puis supprimé;
  • il utilise le hachage de la nouvelle validation racine pendant la filter-branch (Kent a laissé un espace réservé pour le remplacement manuel);
  • l’opération de filter-branch ne réécrit que les twigs locales, pas les télécommandes aussi
  • les métadonnées de l’auteur et du committer sont normalisées pour que le commit racine soit identique dans tous les référentiels.

 #!/bin/bash # Save the current branch so we can check it out again later INITIAL_BRANCH=`git symbolic-ref --short HEAD` TEMP_BRANCH='newroot' # Create a new temporary branch at a new root, and remove everything from the tree git checkout --orphan "$TEMP_BRANCH" git rm -rf . # Commit this empty state with generic metadata that will not change - this should result in the same commit hash every time export GIT_AUTHOR_NAME='nobody' export GIT_AUTHOR_EMAIL='nobody@example.org' export GIT_AUTHOR_DATE='2000-01-01T00:00:00+0000' export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL" export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git commit --allow-empty -m 'empty root' NEWROOT=`git rev-parse HEAD` # Check out the commit we just made and delete the temporary branch git checkout --detach "$NEWROOT" git branch -D "$TEMP_BRANCH" # Rewrite all the local twigs to insert the new root commit, delete the # original/* twigs left behind, and check out the rewritten initial branch git filter-branch --parent-filter "sed \"s/^\$/-p $NEWROOT/\"" --tag-name-filter cat -- --twigs git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d git checkout "$INITIAL_BRANCH" 

Pour basculer la validation racine:

Tout d’abord, créez le commit que vous voulez en premier.

Deuxièmement, changez l’ordre des commits en utilisant:

git rebase -i –root

Un éditeur apparaîtra avec les commits jusqu’à la validation de la racine, comme:

choisissez 1234 vieux message racine

choisir 0294 A commettre au milieu

choisissez 5678 commit que vous voulez mettre à la racine

Vous pouvez ensuite mettre le commit que vous voulez en premier, en le plaçant dans la première ligne. Dans l’exemple:

choisissez 5678 commit que vous voulez mettre à la racine

choisissez 1234 vieux message racine

choisir 0294 A commettre au milieu

Quittez l’éditeur. L’ordre de validation aura changé.

PS: pour changer l’éditeur git utilise, lancez:

git config –global core.editor name_of_the_editor_program_you_want_to_use

Voici un simple traceur qui peut être utilisé pour append un commit vide au début d’un repository, si vous avez oublié de créer un commit vide immédiatement après “git init”:

 git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904) 

Après réponse Aristote Pagaltzis et d’autres mais en utilisant des commandes plus simples

 zsh% git checkout --orphan empty Switched to a new branch 'empty' zsh% git rm --cached -r . zsh% git clean -fdx zsh% git commit --allow-empty -m 'initial empty commit' [empty (root-commit) 64ea894] initial empty commit zsh% git checkout master Switched to branch 'master' zsh% git rebase empty First, rewinding head to replay your work on top of it... zsh% git branch -d empty Deleted branch empty (was 64ea894). 

Notez que votre repository ne doit pas contenir de modifications locales en attente de validation.
Notez que git checkout --orphan fonctionnera avec les nouvelles versions de git, je suppose.
Notez que la plupart du temps, l’ git status donne des indications utiles.

Démarrer un nouveau référentiel

Réglez votre date sur la date de début souhaitée.

Faites tout comme vous le souhaitiez, en ajustant l’heure du système pour que vous réfléchissiez à ce que vous souhaitiez. Extrayez les fichiers du référentiel existant pour éviter de trop nombreuses saisies inutiles.

Lorsque vous arrivez aujourd’hui, échangez les référentiels et vous avez terminé.

Si vous êtes simplement fou (établi) mais raisonnablement intelligent (probablement, parce que vous devez avoir une certaine quantité d’intelligence pour trouver des idées folles comme celle-ci), vous allez écrire le processus.

Cela le rendra également plus agréable lorsque vous décidez que vous voulez que le passé se soit produit d’une autre manière dans une semaine.

Je sais que cet article est ancien, mais cette page est la première lors de la recherche “insertion de commit git” sur Google.

Pourquoi rendre les choses simples compliquées?

Vous avez ABC et vous voulez ABZC.

  1. git rebase -i trunk (ou n’importe quoi avant B)
  2. changer le choix pour éditer sur la ligne B
  3. faites vos changements: git add ..
  4. git commit ( git commit --amend qui va éditer B et ne pas créer Z)

[Vous pouvez créer autant de git commit que vous le souhaitez pour insérer plus de commits. Bien sûr, vous pouvez avoir des problèmes avec l’étape 5, mais résoudre un conflit de fusion avec git est une compétence que vous devriez avoir. Si non, pratique!]

  1. git rebase --continue

Simple, n’est-ce pas?

Si vous comprenez le git rebase , append un commit «root» ne devrait pas poser de problème.

Amusez-vous avec git!