Это сложная проблема в целом. Существуют конкретные случаи или вырожденные способы применения содержимого подмодуля, которые облегчают его. Один компромисс - который может быть или не быть достаточно хорошим - состоит в том, чтобы просто объединить две истории коммитов в один репозиторий, а затем сделать несколько слегка пугающих преобразований, используя либо git filter-branch
, либо просто автоматический git replace
(хотя с использованием или злоупотреблением). , git replace
как это может привести к проблемам с производительностью).
Вот основная ситуация, то есть то, что вам нужно знать, как ментальные инструменты, прежде чем задумываться об обобщении проблемы. Каждый репозиторий содержит граф коммитов: DAG коммитов с различными точками входа в граф, найденные и, следовательно, сохраненные по именам ветвей. В каждом коммите, который использует субмодуль, коммиты суперпроекта имеют ссылку на один из коммитов субмодуля. Эти ссылки находятся в объекте «дерево», как записи типа gitlink . Git на самом деле не проверяет их, когда дело доходит до сохранения коммитов, поскольку предполагается, что они идентифицируют коммиты в некотором другом хранилище (подмодуле).
Вы можете легко использовать git fetch
для извлечения всего графика подмодуля в хранилище суперпроекта, изменяя имена ветвей подмодуля на разные имена в суперпроекте. (По умолчанию git fetch
используется для создания имен для удаленного слежения, но с некоторой хитростью вы можете легко использовать альтернативное пространство имен. Для решений, которые я предлагаю, имена для удаленного слежения подойдут в любом случае.) Результат, однако, просто у вас есть два отключенных DAG. У коммитов суперпроекта все еще есть деревья с записями gitlink, которые ссылаются на коммиты в другой группе обеспечения доступности баз данных. Эти записи gitlink не сохранят коммиты достижимые , поэтому вы должны сохранить оба набора имен. За исключением того, что все коммиты содержатся в одной базе данных репозитория, на самом деле это вообще не улучшение (и может быть хуже, так как с ним теперь трудно работать).
Вот общая проблема : что Git хранит (есть?) Эти коммиты. Нет отдельного пункта, который мы можем назвать «историей»; история в репозитории Git - это (есть?) коммиты в репозитории. Мы можем визуально увидеть проблему, если нарисовать коммиты. Упростим это до пяти коммитов, от A
до E
в суперпроекте. Буквы в верхнем регистре обозначают фактические хэш-идентификаторы (которые бесполезны для людей):
A--B--C <-- master
\
D--E <-- dev
Теперь давайте поместим шесть коммитов в подпроект, используя строчные буквы, так как это подпроект:
a--b--c--d <-- master
\
e--f <-- issue213
Некоторые коммиты суперпроекта - возможно, все они, но для простоты, скажем, просто C
и E
- имеют внутри себя ссылки на некоторые коммиты подпроекта, поэтому, если мы сгруппируем все коммиты подмодуля в суперпроект , используя имена sub/*
, чтобы запомнить подсказки веток, мы получаем это:
A--B--C <-- master
\ :
D÷-E <-- dev
: :
: :
: :
: :
a--b--c--d <-- sub/master
\
e--f <-- sub/issue213
Предположим, что теперь мы каким-то образом заменим коммиты C
(с его gitlink на b
) и E
(с его gitlink на d
) на коммиты, деревья которых имеют фактические прямые ссылки на объекты дерева для совершает b
и e
. Давайте назовем эти коммиты C'
и E'
. Технически это возможно в Git - мы просто делаем новые коммиты C'
и E'
с нужными деревьями, которые используют деревья в b
и d
соответственно, затем меняем имена master
и dev
для ссылки на коммиты C'
и E'
. Если мы отбросим sub/*
имен, у нас будет это:
A--B--C' <-- master
\
D--E' <-- dev
и если мы теперь git checkout master
, мы получим хорошее рабочее дерево, полное того, что было в оригинальном C
плюс того, что было из подмодуля, полученного из его коммита b
, что используется оригинальный C
, как видно из нашей диаграммы.
Точно так же, если мы теперь git checkout dev
получим хорошее рабочее дерево, полное того, что было в оригинальном E
плюс то, что было из подмодуля, полученного из его коммита d
.
The деревья в этом новом измененном хранилище содержат все источники снимка, которые вы получите, проверив C
-and-submodule или E
-and-submodule. Но фиксирует , которые были в подмодуле, т. Е. История d
, ведущая к c
, возвращающаяся к b
, ведущая к a
, плюс вся ветвь issue213
, состоящая из f
, ведущая к e
, ведущая к c
... ну, эти коммиты ушли! Нет ничего, чтобы представлять их больше.
Более того, нет места, куда вы могли бы вставить их . Где в графе, содержащем коммиты с A
по E
(все прописные), коммиты с a
по f
(все строчные) подходят? Единственный ответ - "никуда": есть нет места, куда они не могут пойти.
Теперь, в определенных случаях, мы можем придумать ответ. Мы можем вставить new коммитов между существующими коммитами, чтобы новые коммиты сохраняли файлы суперпроекта на месте при обновлении файлов субмодуля. Это практично, когда существует топологический вид графа подмодуля, который «вписывается» в топологический вид графа суперпроекта. (Если имеется несколько подмодулей, нам нужна полная топо-сортировка объединения всех графов.) Нет гарантии, что такая ситуация существует, и легко нарисовать случай, когда ее нет:
A--B--C <-- master
: :
: :
:
: :
: :
a--b--c <-- sub/master
Здесь коммит суперпроекта A
относится к последнему коммиту в подпроекте, тогда как коммит суперпроекта C
ссылается на первый коммит в подпроекте. Эти топологии графа не являются композируемыми. 1 Но это может быть тот случай, когда ваши топологии имеют место, и в этом случае вы можете вставлять узлы коммитов по мере необходимости, если вы хотите создать новый граф, который действует как соответствующий надстройка. Я не знаю ни одной программы для этого.
1 Я не уверен, является ли "составным" хорошим термином для этого, но у меня нет времени на поиск литературы. Я имею в виду, что объединение групп DAG может привести к циклам, и я называю такие репозитории «несложимыми». См. Также Эффективный алгоритм объединения двух групп DAG, например, .
Выполнение более сложной работы с компонуемыми подмодулями
Вам нужно будет написать код. ? Это нетривиально и требует немного теории графов. Это не особенно сложно, но я определенно не собираюсь делать это здесь.
Выполнение более простой работы, если усеченная история приемлема
Более простое задание, которое в приведенном выше примере состоит в замене коммита C
на C'
и E
на E'
, является автоматизируемым: перебирать все коммиты, находить их подмодули gitlinks и использовать git replace
заменить объект дерева, который имеет подмодуль, на объект дерева, который использует дерево подмодуля. Это на самом деле заменяет объект дерева, а не объект фиксации, так что история действительно остается такой же, какой была раньше, но теперь у вас будет очень большая коллекция заменяющих объектов. Более того, клонирование хранилища не приведет к клонированию замещающих объектов, поэтому теперь пришло время переписать все коммиты, используя git filter-branch
.
У меня нет удобного рецепта для использования git replace
, как этот, но вы, вероятно, захотите автоматизировать git replace --edit
, установив переменную GIT_EDITOR
в сценарий, который найдет и заменит запись gitlink. (Написание такого сценария будет немного утомительным, но технически не сложным.)
Поскольку git filter-branch
учитывает замены, 2 и никаких других изменений не требуется, вы можете просто запустить git filter-branch --tag-name-filter cat -- --branches --tags
, чтобы выполнить все замены фиксации. (Примечание: сделайте это на клоне, который вы создали специально для экспериментов с replace и filter-branch, чтобы вы могли начать все сначала, если испортили его.) Затем вы можете удалить все ссылки для замены (git for-each-ref --format='delete %(refname)' | git update-ref --stdin
) так как они больше не нужны и теперь делают Git медленным.
2 Хорошо, если только не запустить как git --no-replace-objects filter-branch
.