Si j'ai un commit dans le passé qui pointe vers un parent, mais que je veux changer le parent vers lequel il pointe, comment dois-je procéder?
Utilisation git rebase
. C'est la commande générique "take commit (s) and plop it / them on a different parent (base)" command in Git.
Quelques choses à savoir cependant:
Étant donné que les SHA de validation impliquent leurs parents, lorsque vous changez le parent d'une validation donnée, sa SHA change - tout comme les SHA de toutes les validations qui la suivent (plus récente qu'elle) dans la ligne de développement.
Si vous travaillez avec d'autres personnes et que vous avez déjà poussé le commit en question public vers l'endroit où il l'a tiré, la modification du commit est probablement une mauvaise idée ™. Cela est dû au numéro 1, et donc à la confusion qui en résulte que les référentiels des autres utilisateurs rencontreront lorsqu'ils essaieront de comprendre ce qui s'est passé parce que vos SHA ne correspondent plus aux leurs pour les «mêmes» commits. (Voir la section "RÉCUPÉRATION À PARTIR D'UNE REBASE EN AMONT" de la page de manuel liée pour plus de détails.)
Cela dit, si vous êtes actuellement sur une branche avec des validations que vous souhaitez déplacer vers un nouveau parent, cela ressemblerait à ceci:
git rebase --onto <new-parent> <old-parent>
Cela déplacera tout ce qui suit <old-parent>
dans la branche actuelle pour s'asseoir au-dessus <new-parent>
.
--onto
sert! La documentation a toujours été complètement obscure pour moi, même après avoir lu cette réponse!git rebase --onto <new-parent> <old-parent>
m'a tout expliqué sur l'utilisation derebase --onto
toutes les autres questions et documents que j'ai lus jusqu'à présent.rebase this commit on that one
ougit rebase --on <new-parent> <commit>
. Utiliser le vieux parent ici n'a pas de sens pour moi.git rebase <new-parent>
.S'il s'avère que vous devez éviter de rebaser les validations suivantes (par exemple parce qu'une réécriture de l'historique serait intenable), alors vous pouvez utiliser le remplacement de git (disponible dans Git 1.6.5 et versions ultérieures).
Avec le remplacement ci-dessus établi, toute demande pour l'objet B retournera réellement l'objet C. Le contenu de C est exactement le même que le contenu de B sauf pour le premier parent (mêmes parents (sauf pour le premier), même arbre, même message de validation).
Les remplacements sont actifs par défaut, mais peuvent être désactivés en utilisant l'
--no-replace-objects
option git (avant le nom de la commande) ou en définissant laGIT_NO_REPLACE_OBJECTS
variable d'environnement. Les remplacements peuvent être partagés en poussantrefs/replace/*
(en plus de la normalerefs/heads/*
).Si vous n'aimez pas le commit-munging (fait avec sed ci-dessus), vous pouvez créer votre commit de remplacement en utilisant des commandes de niveau supérieur:
La grande différence est que cette séquence ne propage pas les parents supplémentaires si B est un commit de fusion.
la source
refs/replace/
hiérarchie des références. Si vous n'avez besoin de le faire qu'une seule fois, vous pouvez le fairegit push yourremote 'refs/replace/*'
dans le référentiel source etgit fetch yourremote 'refs/replace/*:refs/replace/*'
dans les référentiels de destination. Si vous devez le faire plusieurs fois, vous pouvez ajouter ces paramètres à uneremote.yourremote.push
variable de configuration (dans le référentiel source) et uneremote.yourremote.fetch
variable de configuration (dans les référentiels de destination).git replace --grafts
. Je ne sais pas quand cela a été ajouté, mais son objectif est de créer un remplacement identique à la validation B mais avec les parents spécifiés.Notez que la modification d'un commit dans Git nécessite que tous les commits qui le suivent également soient modifiés. Ceci est découragé si vous avez publié cette partie de l'histoire et que quelqu'un a pu construire son travail sur l'histoire avant le changement.
La solution alternative à
git rebase
mentionnée dans la réponse d'Amber consiste à utiliser le mécanisme de greffes (voir la définition des greffes Git dans le glossaire Git et la documentation du.git/info/grafts
fichier dans la documentation de mise en page du référentiel Git ) pour changer le parent d'un commit, vérifiez qu'il a fait la chose correcte avec un visualiseur d'historique (gitk
,git log --graph
, etc.) puis utilisezgit filter-branch
(comme décrit dans la section "Exemples" de sa page de manuel) pour le rendre permanent (puis supprimez le greffon, et supprimez éventuellement les références d'origine sauvegardées pargit filter-branch
, ou reclonez le référentiel):REMARQUE !!! Cette solution est différente de la solution de rebasage dans ce
git rebase
serait rebasage / transplantation changements , tandis que la solution basée sur les greffes serait simplement Reparent commits comme il est , ne prend pas en compte les différences entre l' ancien parent et nouveau parent!la source
git replace
a remplacé les greffes git (en supposant que vous ayez git 1.6.5 ou version ultérieure).git-replace
+git-filter-branch
? Dans mon test,filter-branch
semblait respecter le remplacement, rebasant l'arbre entier.git filter-branch
réécrit l'histoire, en respectant les greffes et les remplacements (mais dans l'histoire réécrite, les remplacements seront sans objet); la poussée entraînera un changement non fasf-forward.git replace
crée des remplacements transférables sur place; push sera en avance rapide, mais vous devrez pousserrefs/replace
pour transférer les remplacements pour avoir l'historique corrigé. HTHPour clarifier les réponses ci-dessus et brancher sans vergogne mon propre script:
Cela dépend si vous voulez "rebaser" ou "réparer". Un rebase , comme suggéré par Amber , se déplace autour des diffs . Un réparateur , comme suggéré par Jakub et Chris , se déplace autour d' instantanés de l'arbre entier. Si vous voulez réparer, je suggère d'utiliser
git reparent
plutôt que de faire le travail manuellement.Comparaison
Supposons que vous ayez l'image à gauche et que vous souhaitiez qu'elle ressemble à l'image de droite:
Le rebasage et le reparentage produiront la même image, mais la définition de
C'
diffère. Avecgit rebase --onto A B
,C'
ne contiendra aucune modification introduite parB
. Avecgit reparent -p A
,C'
sera identique àC
(sauf queB
ce ne sera pas dans l'histoire).la source
reparent
script; cela fonctionne à merveille; fortement recommandé . Je recommanderais également de créer une nouvellebranch
étiquette, et àcheckout
cette branche avant d'appeler (car l'étiquette de branche se déplacera).Certainement, la réponse de @ Jakub m'a aidé il y a quelques heures lorsque j'essayais exactement la même chose que l'OP.
Cependant,
git replace --graft
c'est maintenant la solution la plus facile concernant les greffes. De plus, un problème majeur avec cette solution était que la branche de filtre m'a fait perdre toutes les branches qui n'étaient pas fusionnées dans la branche de la tête. Ensuite, agit filter-repo
fait le travail parfaitement et parfaitement.Pour plus d'informations: consultez la section "Historique de re-greffage" dans la documentation
la source