Réponses:
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 de rebase --onto
toutes les autres questions et documents que j'ai lus jusqu'à présent.
rebase this commit on that one
ou git 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).
# …---o---A---o---o---…
#
# …---o---B---b---b---…
#
# We want to transplant B to be "on top of" A.
# The tree of descendants from B (and A) can be arbitrarily complex.
replace_first_parent() {
old_parent=$(git rev-parse --verify "${1}^1") || return 1
new_parent=$(git rev-parse --verify "${2}^0") || return 2
new_commit=$(
git cat-file commit "$1" |
sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' |
git hash-object -t commit -w --stdin
) || return 3
git replace "$1" "$new_commit"
}
replace_first_parent B A
# …---o---A---o---o---…
# \
# C---b---b---…
#
# C is the replacement for B.
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 la GIT_NO_REPLACE_OBJECTS
variable d'environnement. Les remplacements peuvent être partagés en poussant refs/replace/*
(en plus de la normale refs/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:
git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -
La grande différence est que cette séquence ne propage pas les parents supplémentaires si B est un commit de fusion.
refs/replace/
hiérarchie des références. Si vous n'avez besoin de le faire qu'une seule fois, vous pouvez le faire git push yourremote 'refs/replace/*'
dans le référentiel source et git 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 à une remote.yourremote.push
variable de configuration (dans le référentiel source) et une remote.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 utilisez git 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 par git filter-branch
, ou reclonez le référentiel):
echo "$ commit-id $ graft-id" >> .git / info / greffes git filter-branch $ graft-id..HEAD
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!
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 pousser refs/replace
pour transférer les remplacements pour avoir l'historique corrigé. HTH
Pour 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.
Supposons que vous ayez l'image à gauche et que vous souhaitiez qu'elle ressemble à l'image de droite:
C'
/
A---B---C A---B---C
Le rebasage et le reparentage produiront la même image, mais la définition de C'
diffère. Avec git rebase --onto A B
, C'
ne contiendra aucune modification introduite par B
. Avec git reparent -p A
, C'
sera identique à C
(sauf que B
ce ne sera pas dans l'histoire).
reparent
script; cela fonctionne à merveille; fortement recommandé . Je recommanderais également de créer une nouvelle branch
é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, a git filter-repo
fait le travail parfaitement et parfaitement.
$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force
Pour plus d'informations: consultez la section "Historique de re-greffage" dans la documentation