Есть ли эффективный способ сделать это с помощью rebase
или rebase --onto
?
Не в общем случае, когда может быть ветвление и слияние. (Если история в новом репозитории строго линейна, то вы можете сделать это с помощью простого git rebase --onto
. Это не совсем эффективно, но это просто машинное время, так что кого волнует, насколько оно эффективно?)
Общее решение этой проблемы — прививка через git replace
.
Давайте посмотрим, что произойдет, если вы git fetch
переместите исходный и новый репозиторий в третий репозиторий (иначе совершенно пустой), используя приведенные выше рисунки. В итоге вы получите:
A--B--C--D--E--F <-- old/master
D'--G--H--I--J <-- new/master
(обратите внимание, что в третьем репозитории еще нет своего master
). Вместо того, чтобы вызывать первую фиксацию в цепочке new/master
как init
, я назвал ее D'
, потому что предположительно она имеет тот же моментальный снимок, что и фиксация D
в old/master
, но имеет другой хэш.
Ничто — никакая сила на Земле — не может изменить любой из этих существующих коммитов. Но что, если мы скопируем коммит G
в новый коммит G'
, родителем которого является D
? Тогда мы получаем это:
A--B--C--D--E--F <-- old/master
\
G'
D'--G--H--I--J <-- new/master
На данный момент новый коммит G'
просто висит в репозитории, и мы не можем найти его. Давайте добавим имя, по которому мы сможем найти G'
. А пока назовем его graft
:
A--B--C--D--E--F <-- old/master
\
G' <-- graft
D'--G--H--I--J <-- new/master
Теперь, что, если бы мы могли каким-то образом заставить Git, когда он движется в обратном направлении по цепочке J
-затем-I
-затем-H
-затем-G
-затем-D'
(и затем остановиться), чтобы в самый последний момент он мог, переключиться с G
на его прививку G'
? То есть мы сделаем какое-то соединение пунктиром:
A--B--C--D--E--F <-- old/master
\
G' <-- graft
:
D'--G--H--I--J <-- new/master
и убедите Git запустить git log
как show J
, затем I
, затем H
, затем G'
, затем D
, затем C
, затем B
, затем A
.
Теперь будет похоже, что история читается именно так, хотя на самом деле это не так1.
Именно это и делает git replace
. Он создает замещающие объекты. В случае фиксации замена может принимать форму прививки, например G'
. Вместо того, чтобы использовать магическое имя graft
, Git использует еще более магическое имя refs/replace/hash
, где хеш — это хэш-идентификатор фактического коммита G
. Иногда вам не нужно этого знать, а иногда нужно.
Проблема с такого рода прививкой замены-фиксации заключается в том, что git clone
по умолчанию не клонирует замены.2 Итак, ваш третий репозиторий немного странный при клонировании. Иногда это именно то, что вы хотите, и если это так, это нормально. Иногда это не так, и если это так, рассмотрите возможность использования git filter-branch
или аналогичного для преобразования пересадки в четвертый репозиторий, в котором пересадка теперь является постоянной, потому что фиксации копируются в < em>новые коммиты (с новыми и другими хэш-идентификаторами), в которых настоящая, но переписанная, история использует привитую историю, а не исходную историю.
1Философский вопрос: или нет? История читается так, как история есть, или история читается так, как Git читает ее вам?
2По сути, это ответ Git на философский вопрос в сноске 1. История читается так, как Git читает ее вам, но записывается как настоящая история. история. При клонировании Git клонирует настоящую историю и игнорирует прививку. Вы можете после клонирования попросить Git скопировать трансплантаты тоже, но это не вариант по умолчанию.
Кроме того, вы можете запустить git --no-replace-objects log
и увидеть реальную историю, а при просмотре истории прививки каждый прививка помечается дополнительным украшением, так что вы можете увидеть ее, если внимательно присмотритесь.
person
torek
schedule
02.03.2020