Ceci est dû à la limitation de l'algorithme d'origine. Lors de la gestion des merge-commits, l'algorithme d'origine utilise un critère simplifié pour couper les parents non liés. En particulier, il vérifie s'il existe un parent qui a le même arbre. Si un tel parent était trouvé, il réduirait le commit de fusion et utiliserait le commit parent à la place, en supposant que d'autres parents ont des changements sans rapport avec le sous-arbre. Dans certains cas, cela entraînerait la suppression de certaines parties de l'historique, ce qui entraîne des modifications réelles du sous-arbre. En particulier, il supprimerait des séquences de commits, qui toucheraient un sous-arbre, mais aboutiraient à la même valeur de sous-arbre.
Voyons un exemple (que vous pouvez facilement reproduire) pour mieux comprendre comment cela fonctionne. Considérez l'historique suivant (le format de ligne est: commit [tree] subject):
% git log --graph --decorate --pretty=oneline --pretty="%h [%t] %s"
* E [z] Merge branch 'master' into side-branch
|\
| * D [z] add dir/file2.txt
* | C [y] Revert "change dir/file1.txt"
* | B [x] change dir/file1.txt
|/
* A [w] add dir/file1.txt
Dans cet exemple, nous nous séparons dir
. Commits D
et E
ont le même arbre z
, parce que nous avons commit C
, qui a annulé le commit B
, donc la B-C
séquence ne fait rien pour dir
même si elle a des modifications.
Maintenant, faisons le fractionnement. D'abord, nous nous sommes séparés sur commit C
.
% git log `git subtree split -P dir C` ...
* C' [y'] Revert "change dir/file1.txt"
* B' [x'] change dir/file1.txt
* A' [w'] add dir/file1.txt
Ensuite, nous nous sommes séparés sur commit E
.
% git log `git subtree split -P dir E` ...
* D' [z'] add dir/file2.txt
* A' [w'] add dir/file1.txt
Oui, nous avons perdu deux commits. Cela entraîne une erreur lors de la tentative de pousser le deuxième fractionnement, car il n'a pas ces deux validations, qui sont déjà entrées dans l'origine.
Habituellement, vous pouvez tolérer cette erreur en utilisant push --force
, car les commits supprimés ne contiennent généralement pas d'informations critiques. À long terme, le bogue doit être corrigé, de sorte que l'historique de fractionnement aurait en fait tous les commits, qui se touchent dir
, comme prévu. Je m'attendrais à ce que le correctif inclue une analyse plus approfondie des commits parent pour les dépendances cachées.
Pour référence, voici la partie du code d'origine, responsable du comportement.
copy_or_skip()
...
for parent in $newparents; do
ptree=$(toptree_for_commit $parent) || exit $?
[ -z "$ptree" ] && continue
if [ "$ptree" = "$tree" ]; then
# an identical parent could be used in place of this rev.
identical="$parent"
else
nonidentical="$parent"
fi
...
if [ -n "$identical" ]; then
echo $identical
else
copy_commit $rev $tree "$p" || exit $?
fi