Установка указателя git parent на другого родителя - PullRequest
203 голосов
/ 28 сентября 2010

Если в прошлом у меня был коммит, который указывает на одного из родителей, но я хочу изменить того родителя, на которого он указывает, как мне поступить?

Ответы [ 4 ]

362 голосов
/ 28 сентября 2010

Использование git rebase. Это общая команда "принять коммиты и добавить их / их к другому родителю (базе)" в Git.

Однако нужно знать кое-что:

  1. Поскольку в SHA для коммитов участвуют их родители, при изменении родителя данного коммита его SHA изменится, как и SHA для всех коммитов, следующих за ним (более поздний, чем он ) в линии разработки.

  2. Если вы работаете с другими людьми, и вы уже выдвинули коммит, о котором идет речь, публично, к месту, где он его вытащил, изменение фиксации, вероятно, является плохой идеей ™. Это происходит из-за # 1, и, таким образом, возникает путаница, с которой сталкиваются репозитории других пользователей, когда пытаются выяснить, что произошло из-за того, что ваши SHA больше не соответствуют их для "тех же" коммитов. (подробности см. В разделе «ВОССТАНОВЛЕНИЕ ОТ РЕБАЗЫ UPSTREAM» на связанной странице man.)

Тем не менее, если вы в настоящее время находитесь на ветке с некоторыми коммитами, которые хотите переместить к новому родителю, это будет выглядеть примерно так:

git rebase --onto <new-parent> <old-parent>

Это переместит все после <old-parent> в текущей ветви, чтобы занять место вместо <new-parent>.

34 голосов
/ 28 сентября 2010

Если выясняется, что вам нужно избегать перебазирования последующих коммитов (например, потому что переписывание истории будет невозможно), вы можете использовать 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 является коммитом слияния.

30 голосов
/ 28 сентября 2010

Обратите внимание, что изменение коммита в Git требует, чтобы все коммиты, которые следуют за ним, также должны быть изменены. Это не рекомендуется, если вы опубликовали эту часть истории, и кто-то, возможно, построил свою работу на истории, которая была до изменения.

Альтернативное решение для git rebase, упомянутое в Ответ Амбер заключается в использовании механизма grafts (см. Определение Git трансплантатов в Git Glossary и документацию .git/info/grafts файл в Git Repository Layout документация), чтобы изменить родительский элемент коммита, проверить, что он исправил что-либо с помощью некоторого средства просмотра истории (gitk, git log --graph и т. д.), а затем использовать git filter-branch (как описано в разделе «Примеры» его справочной страницы), чтобы сделать его постоянным (а затем удалить трансплантат и, при необходимости, удалить исходные ссылки, подкрепленные git filter-branch, или отложить хранилище) :

echo "$commit-id $graft-id" >> .git/info/grafts
git filter-branch $graft-id..HEAD

ПРИМЕЧАНИЕ !!! Это решение отличается от решения для перебазирования тем, что git rebase будет перебазировать / трансплантировать изменится , в то время как решение на основе трансплантатов будет просто повторять фиксации , как , без учета различий между старым родителем и новым родителем!

22 голосов
/ 02 октября 2013

Чтобы уточнить приведенные выше ответы и бесстыдно подключить мой собственный скрипт:

Это зависит от того, хотите ли вы «перебазировать» или «переучить». rebase , как предлагает Amber , перемещается вокруг diffs . reparent , как предложено Jakub и Chris , перемещается вокруг снимков всего дерева.Если вы хотите переписать, я рекомендую использовать git reparent вместо того, чтобы выполнять работу вручную.

Сравнение

Предположим, у вас есть изображение слева, и вы хотите егочтобы выглядеть как на картинке справа:

                   C'
                  /
A---B---C        A---B---C

Как перебазирование, так и перераспределение дают одно и то же изображение, но определение C' отличается.С git rebase --onto A B, C' не будет содержать никаких изменений, внесенных Bgit reparent -p A, C' будет идентично C (за исключением того, что B не будет в истории).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...