Установка указателя git parent на другого родителя
Если в прошлом у меня был коммит, который указывает на одного из родителей, но я хочу изменить родителя, на которого он указывает, как мне поступить?
4 ответа
С помощью git rebase
, Это общая команда "принять коммиты и добавить их / их к другому родителю (базе)" в Git.
Однако нужно знать кое-что:
Поскольку в SHA для фиксации участвуют их родители, при смене родителя для данного коммита его SHA изменится, как и SHA для всех коммитов, которые последуют за ним (более поздним, чем он) в линии разработки.
Если вы работаете с другими людьми и уже опубликовали коммит в том месте, где он его вытащил, изменение коммитов, вероятно, является плохой идеей ™. Это происходит из-за #1, и, таким образом, возникает путаница, с которой сталкиваются репозитории других пользователей, когда пытаются выяснить, что произошло из-за того, что ваши SHA больше не соответствуют их для "тех же" коммитов. (Подробности см. В разделе "ВОССТАНОВЛЕНИЕ ОТ ИСПОЛЬЗОВАНИЯ UPSTREAM" на связанной странице man.)
Тем не менее, если вы в настоящее время находитесь на ветке с некоторыми коммитами, которые вы хотите переместить к новому родителю, это будет выглядеть примерно так:
git rebase --onto <new-parent> <old-parent>
Это переместит все после <old-parent>
в текущей ветке, чтобы сидеть на вершине <new-parent>
вместо.
Если окажется, что вам нужно избегать перебазирования последующих коммитов (например, потому что переписывание истории было бы несостоятельным), тогда вы можете использовать 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 является коммитом слияния.
Обратите внимание, что изменение коммита в Git требует, чтобы все коммиты, которые следуют за ним, также должны быть изменены.Это не рекомендуется, если вы опубликовали эту часть истории, и кто-то мог построить свою работу на истории, какой она была до изменений.
Альтернативное решение дляgit rebase
в ответе Амбер упоминается использование механизма прививки(см. определение прививки Git в глоссарии Git и документацию .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
НОТА!!! Это решение отличается от решения ReBase тем, что git rebase
будет перебазировать / пересаживать изменения, в то время как решение на основе трансплантатов будет просто переопределять коммиты как есть, не принимая во внимание различия между старым родителем и новым родителем!
Чтобы уточнить приведенные выше ответы и бесстыдно подключить свой собственный скрипт:
Это зависит от того, хотите ли вы "перебазировать" или "переучить". Ребаз, как предлагает Амбер, перемещается по разным ссылкам. Родитель, как предположили Якуб и Крис, двигается вокруг снимков всего дерева. Если вы хотите, чтобы reparent, я предлагаю использовать git reparent
вместо того, чтобы делать работу вручную.
сравнение
Предположим, у вас есть изображение слева, и вы хотите, чтобы оно выглядело как изображение справа:
C'
/
A---B---C A---B---C
Как перебазирование, так и повторное воспитание будут давать одну и ту же картину, но определение C'
отличается. С git rebase --onto A B
, C'
не будет содержать никаких изменений, внесенных B
, С git reparent -p A
, C'
будет идентичным C
(Кроме этого B
не будет в истории).
Определенно ответ @Jakub помог мне несколько часов назад, когда я пробовал то же самое, что и OP.
Однако,git replace --graft
теперь более легкое решение относительно трансплантатов. Кроме того, основная проблема с этим решением заключалась в том, что filter-branch заставлял меня терять каждую ветку, которая не была объединена с веткой HEAD. Потом,git filter-repo
справился со своей работой отлично и безупречно.
$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force
Для получения дополнительной информации: ознакомьтесь с разделом "История пересадки" в документации.