J'ai déjà demandé comment: écraser les deux premiers commits dans un référentiel git.
Bien que les solutions soient plutôt intéressantes et ne soient pas aussi déformantes que d’autres choses chez Git, elles restent un sacré casse-tête si vous devez répéter la procédure plusieurs fois tout au long du développement de votre projet.
Donc, je préférerais ne souffrir qu'une seule fois, puis pouvoir utiliser pour toujours le rebase interactif standard.
Ce que je veux faire, alors, est d’avoir un commit initial vide qui existe uniquement dans le but d’être le premier. Pas de code, pas de rien. Je prends juste de la place pour que ça puisse être la base de rebase.
Ma question est donc la suivante: avoir un référentiel existant, comment puis-je insérer un nouveau commit vide avant le premier et faire avancer tous les autres?
Il est probablement préférable de créer un nouveau commit totalement vide, sans effets secondaires, en utilisant directement la tuyauterie de Git. En procédant ainsi, vous éviterez les effets secondaires: ne touchez pas la copie de travail ni l’index, ne supprimez aucune branche temporaire à nettoyer, etc. Ainsi:
Pour créer un commit, nous avons besoin d'une arborescence de répertoires. Nous allons donc en créer une vide:
tree=`git hash-object -wt tree --stdin < /dev/null`
Nous pouvons maintenant envelopper un commit:
commit=`git commit-tree -m 'root commit' $tree`
Et maintenant nous pouvons nous baser sur cela:
git rebase --onto $commit --root master
Et c'est tout. Vous pouvez réorganiser tout cela en une ligne si vous connaissez suffisamment votre Shell.
(NB: en pratique, j’utiliserais maintenant filter-branch
. Éditerons cela plus tard.)
Voici une implémentation plus propre de la même solution, dans la mesure où elle fonctionne sans la nécessité de créer un référentiel supplémentaire, de fusionner des 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 avez fini sur master
avec son historique réécrit pour inclure un commit racine vide.
NB: sur les anciennes versions de Git qui n'ont pas le --Orphan
passez à checkout
, vous avez besoin de la plomberie pour créer une branche vide:
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
Fusion des réponses d'Aristote Pagaltzis et 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 nous avons constaté que pour un référentiel volumineux (> 5 000 validations), la branche filtre fonctionne mieux que rebase pour plusieurs raisons: 1) elle est plus rapide 2) elle ne nécessite aucune intervention humaine en cas de conflit de fusion. 3) il peut réécrire les étiquettes - en les préservant. Notez que filter-branch fonctionne car il n’ya aucune question sur le contenu de chaque commit, c’est exactement la même chose 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 <sha of newroot>/"' --tag-name-filter cat master
Notez que les options '--tag-name-filter cat' signifient que les balises seront réécrites pour pointer vers les commits nouvellement créés.
J'ai utilisé des morceaux de la réponse d'Aristote et de Kent avec succès:
# 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 <sha of newroot>/"' --tag-name-filter cat -- --all
# clean up
git checkout master
git branch -D newroot
# make sure your branches are OK first before this...
git for-each-ref --format="%(refname)" refs/original/ | \
xargs -n 1 git update-ref -d
Cela réécrira également toutes les branches (pas seulement master
) en plus des balises.
Je pense que l'utilisation de git replace
et git filter-branch
est une meilleure solution que d’utiliser un git rebase
:
L'idée derrière cela est de:
git filter-branch
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 de faire une action que vous n'aviez jamais faite auparavant 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 essayez à 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 branches:
git filter-branch -- --all
Maintenant, vous devez voir 2 histoires, l’ancien et le nouveau (voir l’aide sur filter-branch
pour plus d'informations). Vous pouvez comparer les 2 et vérifier à nouveau si tout va bien. Si vous êtes satisfait, supprimez les fichiers dont vous n'avez plus besoin:
rm -rf ./.git/refs/original
rm -rf ./.git/refs/replace
Vous pouvez revenir à votre branche master
et supprimer la branche temporaire:
git checkout master
git branch -D new-root
Maintenant, tout devrait être fait;)
Je me suis enthousiasmé et j'ai écrit une version "idempotente" de ce script Nice ... il insérera toujours le même commit vierge, et si vous l'exécutez deux fois, cela ne changera pas vos hachages de commit à chaque fois. Alors, voici mon point de vue 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
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 référentiel et d’obtenir les mêmes résultats, de sorte qu’ils soient toujours compatibles ... en cours de test ... oui, cela fonctionne, mais vous devez également supprimer et ajouter vos fichiers. télécommandes à nouveau, par exemple:
git remote rm Origin
git remote add --track master user@Host:path/to/repo
git rebase --root --onto $emptyrootcommit
devrait faire l'affaire facilement
Voici un one-liner simple qui peut être utilisé pour ajouter une validation vide au début d'un référentiel, si vous avez oublié de créer une validation vide immédiatement après "git init":
git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)
Eh bien, voici ce que je suis venu avec:
# 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
Voici mon script bash
basé sur la réponse de Kent avec des améliorations:
master
, une fois terminé;git checkout --Orphan
fonctionne uniquement avec une branche, et non avec un état en tête détachée; elle est donc extraite suffisamment longtemps pour que la nouvelle racine soit validée, puis supprimée.filter-branch
_ (Kent a laissé un espace réservé pour le remplacement manuel);filter-branch
l'opération ne réécrit que les branches locales et non les télécommandes#!/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='[email protected]'
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 branches to insert the new root commit, delete the
# original/* branches left behind, and check out the rewritten initial branch
git filter-branch --parent-filter "sed \"s/^\$/-p $NEWROOT/\"" --tag-name-filter cat -- --branches
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
git checkout "$INITIAL_BRANCH"
Pour changer le commit root:
Commencez par créer le commit que vous voulez.
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 ancien message racine
pick 0294 Un commit au milie
choisissez 5678 que vous voulez mettre à la racine
Vous pouvez ensuite mettre le commit que vous voulez en premier, en le plaçant sur la première ligne. Dans l'exemple:
choisissez 5678 que vous voulez mettre à la racine
Choisissez 1234 ancien message racine
pick 0294 Un commit au milie
Quittez l'éditeur, l'ordre de validation aura changé.
PS: Pour changer l’éditeur utilisé par git, lancez:
git config --global core.editor name_of_the_editor_program_you_want_to_use
Suite de la réponse Aristote Pagaltzis et 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 rapport ne doit contenir aucune modification locale en attente de validation.
Remarque git checkout --Orphan
fonctionnera sur les nouvelles versions de git, je suppose.
Notez la plupart du temps git status
donne des indications utiles.