Isso ocorre devido à limitação do algoritmo original. Ao lidar com mesclagem-commits, o algoritmo original usa um critério simplificado para cortar pais não relacionados. Em particular, ele verifica se há um pai, que tem a mesma árvore. Se tal pai for encontrado, ele colapsará o commit de mesclagem e usará o commit pai, assumindo que outros pais tenham mudanças não relacionadas à subárvore. Em alguns casos, isso resultaria na eliminação de partes do histórico, que apresentam mudanças reais na subárvore. Em particular, ele descartaria sequências de commits, que tocariam uma subárvore, mas resultariam no mesmo valor de subárvore.
Vamos ver um exemplo (que você pode reproduzir facilmente) para entender melhor como isso funciona. Considere o seguinte histórico (o formato da linha é: 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
Neste exemplo, estamos nos dividindo dir
. Commits D
e E
têm a mesma árvore z
, porque temos commit C
, que desfaz o commit B
, então a B-C
seqüência não faz nada dir
mesmo que tenha alterações nela.
Agora vamos dividir. Primeiro, dividimos no 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
Em seguida, dividimos no commit E
.
% git log `git subtree split -P dir E` ...
* D' [z'] add dir/file2.txt
* A' [w'] add dir/file1.txt
Sim, perdemos dois commits. Isso resulta no erro ao tentar empurrar a segunda divisão, uma vez que não tem esses dois commits, que já entraram na origem.
Normalmente você pode tolerar esse erro usando push --force
, uma vez que commits descartados geralmente não terão informações críticas neles. No longo prazo, o bug precisa ser consertado, então o histórico de divisão teria, na verdade, todos os commits, que tocam dir
, conforme o esperado. Eu esperaria que a correção incluísse uma análise mais profunda dos commits pai para dependências ocultas.
Para referência, aqui está a parte do código original, responsável pelo comportamento.
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