Установка родительского указателя 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 не будет в истории).