Git rebase объединяет ветки с разными подпапками - PullRequest
0 голосов
/ 11 июля 2019

У меня было два хранилища, а затем они были объединены в одно, так что каждое хранилище теперь копируется в ветку в новом / третьем хранилище.Я также переместил все файлы из одного репозитория в подпапку, а другой в другую подпапку, используя фильтр-ветвь.

Итак, branch_A находится в папке «A», а «branch_B» - в папке B », ис точки зрения git, так было всегда.

Я хочу объединить две ветви в одну, чтобы при извлечении случайного коммита из середины истории у меня были обе папки.

Я могу сделать это с помощью rebase, но, что важно, я хочу, чтобы коммиты сохранили дату своего автора, по существу, чередуя истории вместе . Когда я перебазирую, он просто отбрасывает коммиты наконец, и все коммиты имеют сегодняшнюю дату.

Это должно быть выполнимо, потому что, поскольку ветви отслеживают разные папки, не должно быть абсолютно никаких конфликтов.

Как мне это сделать?

Спасибо.

1 Ответ

0 голосов
/ 11 июля 2019

Это можно сделать, да: но это не просто , и только перебазирование не приведет вас туда.

(Я уверен, что это уже то, что вы уже знаете, но стоит переходить шаг за шагом, так как большая часть документации Git оставляет желать лучшего, что оставляет пробелы в понимании людей.)

Помните, что хранилище - это коллекция коммитов. Каждый коммит - или содержит, если мы разобьем его, но давайте пока посмотрим на коммиты как на неповрежденные сущности - снимок дерева исходных текстов, т.е. некоторые данные, плюс некоторая информация о этого снимка, т. е. некоторые метаданные. Сам коммит идентифицируется уникальным идентификатором хэша. Этот хэш-идентификатор - не более чем криптографическая контрольная сумма содержимого коммита. 1 Сама контрольная сумма не может измениться, поскольку это просто контрольная сумма этой строки, а сама строка не собирается меняться, так как именно это и составляет коммит.

Теперь, что самое важное, в метаданных для каждого коммита есть строка parent:

$ git cat-file -p HEAD | grep parent
parent 90d79d71910415387590a733808140e770382b2f

То есть каждый коммит содержит фактический хэш-идентификатор какого-либо другого предыдущего коммита. Некоторые коммиты - merge коммиты - содержат два или более родительских идентификатора и, по крайней мере, один коммит, root коммит, имеет no parent, но большинство коммитов имеют точно один.

Эти родительские хеш-идентификаторы имеют эффект формирования обратной цепочки коммитов в репозитории. Если мы начнем с почти пустого репозитория, содержащего всего три коммита, и назовем их A, B и C вместо того, чтобы использовать их настоящие большие уродливые хеш-идентификаторы, мы можем нарисовать их так:

A <-B <-C

Commit C является самым последним и запоминает фактический хэш-идентификатор своего родителя B. Коммит B - это тот, который мы сделали вторым, и он запоминает фактический хэш-идентификатор своего родителя A. Коммит A является первым, и, будучи первым, он не запомнил предыдущего коммита, поэтому это корневой коммит.

Если хеш-идентификаторы коммитов действительно были бы такими простыми, мы могли бы просто помнить, что есть три коммита, поэтому наш последний должен быть C Но они выглядят и действуют как случайные числа, и нам, простым людям, их невозможно запомнить. Итак, мы используем Git имена ветвей , чтобы запомнить последний коммит в цепочке:

A <-B <-C   <-- master

Имя master запоминает фактический хэш-идентификатор, поэтому имя master указывает на commit C. Между тем C указывает на B и B указывает на A.

Если мы хотим добавить новый коммит, мы просим Git извлечь коммит C, используя имя master. Git помнит, что мы находимся на «ветке» master, прикрепляя специальное имя HEAD к имени master:

A--B--C   <-- master (HEAD)

Если мы хотим добавить новый коммит в новую ветку , мы говорим Git: Создайте имя новой ветви dev, также указывающее на коммит C, и присоедините HEAD до dev. Теперь у нас есть:

A--B--C   <-- dev (HEAD), master

Теперь мы делаем новый коммит обычным способом. Git создает коммит, делая снимок источника, добавляя наше имя и адрес электронной почты, дату и т. Д., И используя хэш-идентификатор commit C в качестве родительского для нового коммита. Новый коммит получает новый, кажущийся случайным образом (но на самом деле полностью детерминированный, когда у нас есть метка даты и времени, наше сообщение журнала и т. Д.) Хэш-идентификатор. Мы просто назовем это commit D хотя:

A--B--C
       \
        D

Волшебная часть происходит сейчас: Git обновляет текущее имя ветки - то, к которому прикреплено специальное имя HEAD - записывая фактический хэш-идентификатор commit D в это. Так что теперь имя dev указывает на D, а master продолжает указывать на C:

A--B--C   <-- master
       \
        D   <-- dev (HEAD)

Когда мы добавляем больше коммитов, в какой-то момент у нас возникает такая ситуация:

          I--J   <-- master
         /
...--G--H
         \
          K--L   <-- dev

Мы бежимgit checkout master, который выбирает коммит J и присоединяет HEAD к master, а затем git merge dev.Операция объединения теперь объединяет работу над master - изменениями между коммитами H и J - с работой над dev, изменениями между коммитами H и L.Если Git может выполнить все объединения самостоятельно, Git также делает окончательный коммит слияния.Этот новый коммит слияния M имеет в качестве своего первого родителя коммит J: зафиксированный нами коммит.Он имеет коммит L, тот, который мы сказали Git слиться, как его второй родитель.Как всегда, Git записывает новый хэш-идентификатор для нового коммита в имя текущей ветви, так что master теперь указывает на новое слияние M:

          I--J
         /    \
...--G--H      M   <-- master
         \    /
          K--L   <-- dev

Теперь мы можем удалить name dev, если хотите, поскольку все коммиты можно найти, начиная с M и работая в обратном направлении.Начиная с M, мы должны идти назад через и J и L.

Обратите внимание, что если мы оставим имя dev на месте,коммиты через H, плюс K и L, находятся на обеих ветвях.Все коммиты в dev теперь также в master.Коммиты I, J и M являются только в master, но остальные находятся в обеих ветвях.До слияния коммиты через H были в обеих ветвях.


1 Технически это контрольная сумма литерального слова commit, размер в байтах метаданныхвключая строку идентификатора хэша дерева для данных, некоторые пробелы и другие байты, а затем байты метаданных.Используйте git cat-file -p HEAD, чтобы увидеть пример коммита: контрольная сумма - это результат добавления префикса к тому, что Python напечатает, если дана директива форматирования:

b'commit {}\0{}'.format(len(content), content)

, где content содержит строку байтов, созданную git cat-file -p здесь.


Извлечение из отдельной истории создает второй корень

Давайте вернемся к хорошему простому исходному репозиторию с тремя фиксациями, только с одной веткой:

A--B--C   <-- master

Теперь давайте git fetch из другого, другого, трехкомпонентного репозитория, который также имеет один master.Это дает нам otherrepo/master, если мы используем имя otherrepo в качестве имени удаленного:

git remote add otherrepo <url>
git fetch otherrepo

приводит к:

A--B--C   <-- master

D--E--F   <-- otherrepo/master

Мы можем сделать наше собственное имя ветвидля этой второй ветви, вместо использования этого имени для удаленного отслеживания otherrepo/master.На самом деле это не имеет значения, но облегчает нашу следующую git filter-branch, поэтому давайте сделаем это:

git branch m2 otherrepo/master

Теперь мы запустим команды git filter-branch, которые перемещают все в подкаталоги.Каждая такая команда копирует часть оригинала фиксирует новые, которые имеют разные хэш-идентификаторы и разные сохраненные снимки, но с одинаковой информацией об авторе и коммиттере и одинаковыми сообщениями журнала.Родительские хэш-идентификаторы указывают на копии, поэтому мы получаем:

A--B--C   [abandoned]

A'-B'-C'  <-- master

D--E--F   [abandoned]

D'-E'-F'  <-- m2

Нам больше не нужны исходные цепочки коммитов, и как только мы отбрасываем refs/original/ имена, которые git filter-branch делает, мы даже не сможем найти оригиналы больше, если мы не укажем где-нибудь их хэш-идентификаторы.Таким образом, мы можем прекратить их рисовать, если захотим, и я это сделаю (но я сохраню отметки «галочка / штрих» на именах однобуквенных коммитов, чтобы обозначить, что это те файлы, которые содержат файлы, переименованные в поддерево).

Теперь вам нужно изобрести желаемый конечный график

Теперь у вас есть:

A'-B'-C'  <-- master

D'-E'-F'  <-- m2

Вы бы хотели бы иметь , возможно:

A"-E"-B"-F"   <-- master

Или, может быть, вы хотели бы иметь:

A"-C"-F"   <-- master

Или, может быть, это что-то совсем другое. довольно ясно, но, возможно, не совсем ясно и, возможно, на самом деле не верно , что вы хотели бы получить новый корневой коммит, вероятно, это A", который является результатом:

  • извлечение дерева из A' и дерева из D' в одно общее дерево
  • фиксация этого с сообщением журнала от любого из A' или D' или, может быть, оба

После этого нового A" коммита вы можете заменить просто поддерево D' на просто поддерево E'. Или, может быть, вы хотите заменить поддерево A' на поддерево B' , а заменить поддерево D' на поддерево E'. Или, может быть, вы хотите третью комбинацию - эта часть не вообще ясна, конечно, не для меня, и, возможно, не для вас тоже. : -)

Так или иначе, ваша работа состоит в том, чтобы выяснить, какие поддеревья вы хотели бы объединить, и какие коммиты вы хотите объединить или использовать. Затем вы можете сделать новый коммит из этого результата. Это будет ваш второй коммит с A" в качестве родителя.

Это повторяет столько коммитов, сколько в цепях. Если цепочки сложные - если master указывает на что-то с расхождениями и слияниями и / или m2 указывает на что-то с расхождениями и / или слияниями, вы должны выяснить, как бы вы хотели объединить эти две истории, в том числе их поддеревья. Это определенно тот случай, когда извлечение любого коммита master повлияет только на поддерево A, а извлечение коммита m2 повлияет только на поддерево B. Но на самом деле брать коммиты в любом порядке и делать новые коммиты с любым из родителей ... , что - трудная работа, которую вы должны решить.

Как только вы поймете , что вы хотите, это довольно простой вопрос программирования, чтобы это произошло. Вы можете попытаться использовать git filter-branch для этого, но может быть проще просто сделать то, что делает git filter-branch: настроить правильное дерево и сделать новые коммиты, используя git write-tree, чтобы записать индекс, а затем git commit-tree -p <parent> [-p <parent> ...] <tree> с правильным сообщением журнала стандартного ввода (или -F файлом с сообщением журнала) и правильными строками GIT_{AUTHOR,COMMITTER}_{NAME,EMAIL,DATE} в среде, чтобы принудительно включить имена автора и коммиттера, адреса электронной почты и метки даты и времени новые коммиты. Сантехническая команда git commit-tree создаст новый коммит и выдаст на своем стандартном выводе идентификатор хеш-функции этого нового коммита. Используйте его в качестве родителя последующего коммита (ов) для создания новых цепочек.

(Обратите внимание, что вы можете использовать git read-tree, чтобы заполнить индекс для git write-tree, и / или вы можете извлечь файлы из коммитов в рабочее дерево более обычным способом, а затем использовать git add для создания новый индекс для записи. См. источник ветки фильтра - это просто гигантский сценарий оболочки - для ознакомления с некоторыми приемами и приемами.)

Когда вы все сделали и создали нужный коммит (-ы) ветвления, используйте git update-ref или более ориентированную на пользователя команду git branch, чтобы принудительно установить конкретное одно или несколько имен веток для хранения ID хеша желаемого коммита (ов). Теперь у вас есть восстановленная история, именно так, как вы хотите.

...