Если выясняется, что вам нужно избегать перебазирования последующих коммитов (например, потому что переписывание истории будет невозможно), вы можете использовать git replace (доступно в Git 1.6.5 и выше).
# …---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.
При установленной выше замене любые запросы на объект B фактически возвращают объект C. Содержимое C в точности совпадает с содержимым B, за исключением первого родителя (те же самые родители (кроме первого), то же самое дерево, то же самое сообщение коммита).
Замены активны по умолчанию, но их можно отключить, используя параметр --no-replace-objects
для git (перед именем команды) или установив переменную среды GIT_NO_REPLACE_OBJECTS
. Замены можно отправить, нажав refs/replace/*
(в дополнение к обычному refs/heads/*
).
Если вам не нравится фиксация (сделанная с помощью sed выше), то вы можете создать свой коммит, используя команды более высокого уровня:
git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -
Большая разница в том, что эта последовательность не распространяется на дополнительных родителей, если B является коммитом слияния.