Восстановить историю git из устаревшей версии проекта

Короче говоря:

  1. Проект переносится на новый сервер репозитория git
  2. Кто-то только копирует файлы проекта и отправляет весь проект на новый сервер в качестве начальной фиксации.
  3. Работа продолжается, начиная с этого нового первоначального коммита в течение довольно долгого времени.
  4. Мне удалось найти старую локальную копию устаревшего проекта (до перехода на новый сервер) и хочу вставить старую историю git в текущую версию проекта (до начала текущей истории). В старом локальном проекте есть несколько дополнительных нежелательных коммитов.

Ветви в основном выглядят так:

                 old_master
                /
A--B--C--D--E--F

                  origin/new_master
                 /
init--G--H--I--J

где коммит: new_master -> init = old_master -> D

Таким образом, конечный результат будет примерно таким:

                       origin/new_master
                      /
A--B--C--D--G--H--I--J

Как перебазировать коммиты из другого репозитория с другим история? имеет аналогичную дилемму, связанную с историей, которая решается с помощью выбора вишни. В моей ситуации есть огромное количество коммитов со сложными ветвлениями, которые может быть трудно выбрать. Есть ли эффективный способ сделать это с помощью rebase или rebase --onto?


person Robert James Mieta    schedule 02.03.2020    source источник
comment
В ответе, на который вы ссылаетесь, показано, что вы можете выбирать диапазон коммитов. Это не сделает работу за вас?   -  person mnestorov    schedule 02.03.2020


Ответы (1)


Есть ли эффективный способ сделать это с помощью 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
comment
Очень подробный ответ о прививке, спасибо! Под эффективностью я имел в виду не циклы процессора для выполнения исправления git, а скорее наименьшее количество команд/шагов для достижения результата. Определенно хотел бы добиться связанной истории и не изменять хэши (что в противном случае могло бы оказать неблагоприятное влияние на других людей, сидящих на определенных хешах и ветках проекта) - person Robert James Mieta; 02.03.2020