Существует ли эффективный способ сделать это, используя 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
, но он имеет другой га sh.
Ничего - никакой энергии на Земле - не может изменить любой этих существующих коммитов. Но что, если мы скопируем коммит 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
как , покажем J
, затем I
, затем H
затем G'
, затем D
, затем C
, затем B
, затем A
.
Теперь оно будет выглядеть так, как история читается таким образом, даже если оно не совсем. 1
Это именно то, что делает git replace
. заменяет объекты . В случае коммита замена может принимать форму трансплантата , например G'
. Вместо того, чтобы использовать маги c name graft
, Git использует даже более-маги c name, refs/replace/<em>hash</em>
, где ha sh - идентификатор ha sh фактического коммита G
. Иногда вам не нужно об этом знать, а иногда - вам.
Проблема с этим типом взяточничества с фиксацией замены заключается в том, что git clone
не клон замен, по умолчанию. 2 Так что ваш третий репозиторий немного странен при клонировании. Иногда это именно то, что вы хотите, и если так, это нормально. Иногда это не так, и если это так, рассмотрите возможность использования git filter-branch
или аналогичного конвертирования трансплантата в четвертый репозиторий, в котором трансплантат теперь является постоянным, поскольку коммиты копируются в new фиксирует (с новыми и разными идентификаторами ha sh), в которых real - но переписанный - история использует привитую историю, а не исходную историю.
1 Философский вопрос: или нет? Читает ли история то, как история равна , или история читает то, как Git читает ее вам?
2 Это, по сути, Git ответ на философский вопрос в сноске 1. История читает то, как Git читает ее вам, но записывается как реальная история. При клонировании Git клонирует историю real и игнорирует трансплантат. После клонирования вы можете попросить Git скопировать трансплантаты тоже , но это не по умолчанию.
Кроме этого, вы можете запустить git --no-replace-objects log
и посмотреть реальную историю, и при просмотре привитой истории на каждом трансплантате отмечается дополнительное украшение, так что вы можете увидеть его, если присмотритесь.