Установка родительского указателя git для другого родителя
Если в прошлом у меня есть фиксация, указывающая на одного родителя, но я хочу изменить родительский элемент, на который он указывает, как бы я это сделал?
Ответы
Ответ 1
Использование git rebase
. Это общий "взять commit (s) и перевести его/их на другую родительскую (базовую)" команду в Git.
Некоторые вещи, которые нужно знать, однако:
-
Поскольку передача SHA связана с их родителями, когда вы меняете родителя данного коммита, его SHA будет изменяться - как и SHA всех коммитов, которые после него (более поздние, чем это) в строке разработки.
-
Если вы работаете с другими людьми, и вы уже подталкивали публичный коммит к тому, куда его потянули, модификация коммита - это, вероятно, Bad Idea ™. Это связано С# 1, и, следовательно, в результате путаницы, с которой сталкиваются хранилища других пользователей, когда вы пытаетесь выяснить, что произошло из-за того, что ваши SHA больше не соответствуют их для "же" коммитов. (Подробности см. В разделе "ВОССТАНОВЛЕНИЕ ИЗ ПЕРЕЗАГРУЗКИ" на связанной странице руководства.)
Тем не менее, если вы в настоящее время находитесь в ветке с некоторыми коммитами, которые хотите переместить в новый родитель, это выглядит примерно так:
git rebase --onto <new-parent> <old-parent>
Это приведет к перемещению всего после <old-parent>
в текущей ветке, чтобы вместо этого стоять поверх <new-parent>
.
Ответ 2
Если окажется, что вам нужно избегать переустановки последующих коммитов (например, потому что переписать историю было бы несостоятельным), вы можете использовать git заменить (доступно в 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/*
).
Если вам не нравится commit-munging (сделанный с sed выше), вы можете создать свою фиксацию фиксации с помощью команд более высокого уровня:
git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -
Большая разница заключается в том, что эта последовательность не распространяется на дополнительных родителей, если B является фиксацией слияния.
Ответ 3
Обратите внимание, что изменение фиксации в Git требует, чтобы все коммиты, которые следуют за ним, должны быть изменены. Это обескураживает, если вы опубликовали эту часть истории, и кто-то мог бы построить свою работу над историей, которая была до изменения.
Альтернативное решение git rebase
, упомянутое в Янтарный ответ, заключается в использовании механизма трансплантатов (см. определение Git трансплантатов в Git Глоссарий и документация файла .git/info/grafts
в Git Репозиторий Layout), чтобы изменить родителя фиксации, убедитесь, что он сделал правильную вещь с помощью некоторого средства просмотра истории (gitk
, git log --graph
и т.д.), А затем используйте git filter-branch
(как описано в разделе "Примеры" его man-страницы), чтобы сделать его постоянным (а затем удалить трансплантат и, при необходимости, удалить исходные ссылки refs, git filter-branch
или репозиторий reclone):
echo "$commit-id $graft-id" >> .git/info/grafts
git filter-branch $graft-id..HEAD
ПРИМЕЧАНИЕ!!!. Это решение отличается от решения по восстановлению тем, что git rebase
будет переустанавливать/пересаживать изменения, тогда как на основе графтов решение просто переписывало бы как, не принимая во внимание различия между старым родителем и новым родителем!
Ответ 4
Чтобы прояснить приведенные выше ответы и бесстыдно подключить мой собственный script:
Это зависит от того, хотите ли вы "rebase" или "reparent". Бабаза, как предложено Amber, перемещается по разным. Повторитель, предложенный Jakub и Chris, перемещает моментальные снимки всего дерева. Если вы хотите отступить, я предлагаю использовать git reparent
, а не выполнять работу вручную.
Сравнение
Предположим, что у вас есть изображение слева, и вы хотите, чтобы он выглядел как картинка справа:
C'
/
A---B---C A---B---C
Оба переупорядочения и повторного воспроизведения будут воспроизводить одно и то же изображение, но определение C'
отличается. Если git rebase --onto A B
, C'
не будет содержать изменений, внесенных в B
. С git reparent -p A
, C'
будет идентичным C
(за исключением того, что B
не будет в истории).