Ответы:
Использование git rebase. Это общая команда «принять коммиты и добавить их / их к другому родителю (базе)» в Git.
Однако некоторые вещи нужно знать:
Поскольку в SHA для фиксации участвуют их родители, при смене родителя для данного коммита его SHA изменится, равно как и SHA для всех коммитов, которые последуют за ним (более поздним, чем он) в линии разработки.
Если вы работаете с другими людьми и уже опубликовали коммит в том месте, где его вытащили, изменение коммитов, вероятно, является Плохой идеей ™. Это происходит из-за # 1, и, таким образом, возникает путаница, с которой сталкиваются репозитории других пользователей, когда пытаются выяснить, что произошло из-за того, что ваши SHA больше не соответствуют их для "тех же" коммитов. (Подробности см. В разделе «ВОССТАНОВЛЕНИЕ ОТ UPSTREAM REBASE» на связанной странице man.)
Тем не менее, если вы в настоящее время находитесь на ветке с некоторыми коммитами, которые вы хотите переместить к новому родителю, это будет выглядеть примерно так:
git rebase --onto <new-parent> <old-parent>
Это переместит все после <old-parent> текущей ветки <new-parent>вместо того, чтобы сидеть сверху .
--ontoиспользуется для! Документация всегда была мне совершенно неясна, даже после прочтения этого ответа!
git rebase --onto <new-parent> <old-parent>Скажи мне все о том, как использовать rebase --ontoвсе остальные вопросы и документы, которые я до сих пор читал».
rebase this commit on that oneили git rebase --on <new-parent> <commit>. Использование старого родителя здесь не имеет смысла для меня.
git rebase <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 является коммитом слияния.
refs/replace/иерархии ссылок. Если вам просто нужно сделать это один раз, то вы можете сделать это git push yourremote 'refs/replace/*'в исходном хранилище и git fetch yourremote 'refs/replace/*:refs/replace/*'в целевом хранилище. Если вам нужно сделать это несколько раз, вы можете вместо этого добавить эти refspecs в remote.yourremote.pushпеременную config (в исходном хранилище) и remote.yourremote.fetchпеременную config (в целевых хранилищах).
git replace --grafts. Не уверен, когда это было добавлено, но его цель - создать замену, аналогичную коммиту B, но с указанными родителями.
Обратите внимание, что изменение коммита в Git требует, чтобы все коммиты, которые следуют за ним, также должны быть изменены. Это не рекомендуется, если вы опубликовали эту часть истории, и кто-то мог построить свою работу на истории, какой она была до изменений.
Альтернативное решение, git rebaseупомянутое в ответе Амбер, заключается в использовании механизма пересадки (см. Определение прививки Git в глоссарии Git и документации по .git/info/graftsфайлу в документации по макету репозитория Git ), чтобы изменить родительский элемент коммита, проверить, что он исправился с помощью некоторого средства просмотра истории ( gitk, git log --graphи т. д.), а затем используйте git filter-branch(как описано в разделе «Примеры» на его man-странице), чтобы сделать его постоянным (а затем удалите трансплантат и, при необходимости, удалите исходные ссылки, сохраненные в git filter-branchрезервном хранилище, или отмените репозиторий):
echo "$ commit-id $ graft-id" >> .git / info / grafts git filter-branch $ graft-id..HEAD
НОТА !!! Это решение отличается от решения по перебазированию тем, git rebaseчто перебазирует / пересаживает изменения , в то время как решение на основе трансплантатов просто перечитывает коммиты как есть , не принимая во внимание различия между старым родителем и новым родителем!
git replaceпроизошла замена git-трансплантатов (при условии, что у вас есть git 1.6.5 или более поздняя версия).
git-replace+ git-filter-branch? В моем тесте, filter-branchказалось, уважал замену, перебирая все дерево.
git filter-branchпереписывает историю, уважая трансплантаты и замены (но в переписанной истории замены будут спорными); толчок приведет к изменению не вперед-назад. git replaceсоздает на месте переносимые замены; push будет ускоренной перемоткой вперед, но вам нужно будет нажать, refs/replaceчтобы перенести замены, чтобы исправить историю. HTH
Чтобы уточнить ответы выше и бесстыдно подключить свой собственный скрипт:
Это зависит от того, хотите ли вы «перебазировать» или «переучить». Перебазироваться , как это было предложено Амбер , движется вокруг посмотреть различия . 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не будет в истории).
reparentсценарием; это прекрасно работает; настоятельно рекомендуется . Я также рекомендовал бы создать новую branchметку и для checkoutэтой ветви перед вызовом (как метка ветви будет перемещаться).
Ответ @ Jakub определенно помог мне несколько часов назад, когда я пытался сделать то же самое, что и OP.
Тем не менее, git replace --graftтеперь это более простое решение относительно трансплантатов. Кроме того, одной из основных проблем этого решения было то, что ветвь фильтра заставляла меня терять каждую ветку, которая не была объединена с веткой HEAD. Затем git filter-repoвыполнил работу отлично и без нареканий.
$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force
Для получения дополнительной информации: ознакомьтесь с разделом «Пересадка истории» в документации.