Редактировать: подход, основанный на дате, который делает это довольно простым, но предполагает, что один из двух репозиториев будет «контролировать», какие коммиты поступают из другого репозитория, см. ответ jthill . В итоге вы получаете историю коммитов, которая точно соответствует истории проекта, возможно, уничтожая некоторые из истории тестов. Приведенный ниже ответ более уместен, если вам нужно добавить префикс к обоим наборам историй или вы хотите перемежать их (например, нужны два разных "тестовых" обновления для одной и той же фиксации "проекта").
phd-ответ хорошо, но если бы я делал это сам и хотел сделать его действительно аккуратным и чистым, я бы использовал другой подход.
Если деревья для двух репозиториев не перекрываются, это, безусловно, возможно сделать, и, обходя обычные механизмы Git, переходя прямо к базовым git read-tree
командам, вы можете автоматизировать это. (Именно здесь недавний комментарий VonC отвергает мое утверждение о том, что Git и Mercurial очень похожи, верно: если вы пропустите команды Git верхнего уровня, вы получите то, что вы не можете получить почти так же легко в Mercurial.)
Как и в ответе phd , вы начнете этот процесс, объединив две базы данных фиксации репозитория с помощью git fetch
. (Вы можете сделать это в третьем репо, который я бы порекомендовал, так как это упрощает перезапуск процесса с нуля, если вы решите, что хотите настроить некоторые параметры, или добавив репо A к репо B или репо B к репо А.) Но после этого все расходится.
Теперь у вас есть два независимых DAG коммита:
D--...--K
/ \
A--B--C M--N <-- repoA/master
\ /
E--...--L
O--P--Q--...--Z <-- repoB/master
(Если repoA и repoB имеют более одного наконечника ветви, нарисуйте любую упрощенную диаграмму их коммитов, более подходящую.)
Ваш следующий шаг - перечислить все коммиты в каждом из двух непересекающихся групп DAG, используя git rev-list --topo-order --reverse
и любые другие параметры сортировки, которые вам нравятся. Когда и требуется ли --topo-order
, зависит от топологии и другой информации сортировки, но, как правило, вы хотите, чтобы родительский коммит был указан перед любым из его дочерних элементов.
Учитывая эти два линеаризованных списка хеш-идентификаторов коммитов, теперь у вас есть сложная часть: построение графа новых объединенных деревьев, которые вы хотите зафиксировать. Каждый новый коммит будет создаваться путем объединения одного коммита из каждого из двух старых графов. Если один из графиков является сложным (как для repoA выше) с ответвлениями и слияниями, а другой - нет (как для repoB выше), это может быть особенно сложно.
Я сделал свои собственные настройки для этого, где у меня есть очень простой график:
A--B <-- A/master
O--P <-- B/master
В моей упрощенной настройке я хотел бы сделать свой первый коммит на моем новом мастере коммитом C
, который объединяет деревья A
и O
:
C <-- master
Тогда я хотел бы сделать, как мой второй коммит на master
, комбинацию A
и P
(не A
и O
и не B
и O
либо) и, как мой последний коммит, комбинация B
и P
, так что я получаю:
C--D--E <-- master
with:
C = A+O
D = A+P
E = B+P
Итак, мы находимся в новом пустом хранилище, за исключением того, что мы читали в проектах A и B:
$ git log --all --graph --decorate --format='%h%d %s' --name-status | sed '/^[| ] $/d'
* 7b9921a (B/master) commit-P
| A B/another
* 51955b1 commit O
A B/start
* 69597d3 (A/master) commit-B
| A A/new
* ff40069 commit-A
A A/file
(Я случайно не переносил коммит O, но переносил все остальные. sed
- удалить некоторые пустые строки, которые в действительности не помогают читать.)
$ git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
Теперь мы строим новые коммиты, по одному, используя git read-tree
, чтобы заполнить индекс для совершения коммитов. Начнем с пустого индекса (который у нас есть сейчас):
$ git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
Мы хотим, чтобы наш первый коммит объединил A
и O
, поэтому давайте теперь прочитаем эти два коммита в индекс. Если бы нам пришлось добавить префикс к дереву в A
, мы могли бы сделать это здесь:
$ git read-tree --prefix= ff40069
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0 A/file
$ git read-tree --prefix= 51955b1
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0 A/file
100644 f6284744575ecfc520293b33122d4a99548045e4 0 B/start
Мы можем сделать коммит, который нам нужен:
$ git commit -m combine-A-and-O
[master (root-commit) 7c629d8] combine-A-and-O
2 files changed, 2 insertions(+)
create mode 100644 A/file
create mode 100644 B/start
Теперь нам нужно сделать следующий коммит, а значит, нам нужно построить правильное дерево в индексе. Чтобы сделать это, мы сначала должны очистить это; в противном случае следующий git read-tree --prefix
потерпит неудачу с жалобой на перекрывающиеся файлы и Cannot bind.
Так что теперь мы очищаем индекс, затем читаем коммиты A и P:
$ git read-tree --empty
$ git read-tree --prefix= ff40069
$ git read-tree --prefix= 7b9921a
Если хотите, вы можете проверить результат, используя git ls-file --stage
еще раз:
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0 A/file
100644 d7941926464291df213061d48784da98f8602d6c 0 B/another
100644 f6284744575ecfc520293b33122d4a99548045e4 0 B/start
В любом случае теперь они могут быть зафиксированы как новый коммит:
$ git commit -m 'combine A and P'
[master eb8fa3c] combine A and P
1 file changed, 1 insertion(+)
create mode 100644 B/another
(теперь вы можете видеть, как я получаю непоследовательные переносы :-)). Наконец, мы повторяем процесс, опустошая индекс, читая в двух желаемых фиксациях (B + P) и фиксируя результат:
$ git read-tree --empty
$ git read-tree --prefix= A/master
$ git read-tree --prefix= B/master
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0 A/file
100644 8e0c97794a6e80c2d371f9bd37174b836351f6b4 0 A/new
100644 d7941926464291df213061d48784da98f8602d6c 0 B/another
100644 f6284744575ecfc520293b33122d4a99548045e4 0 B/start
$ git commit -m 'combine B and P'
[master fad84f8] combine B and P
1 file changed, 1 insertion(+)
create mode 100644 A/new
(Я использовал здесь символические имена для получения двух последних коммитов, но хэш-идентификаторы из git rev-list
, конечно, работали бы хорошо.) Теперь мы можем видеть три коммита, все на master
:
$ git log --decorate --oneline --graph
* fad84f8 (HEAD -> master) combine B and P
* eb8fa3c combine A and P
* 7c629d8 combine-A-and-O
и теперь безопасно удалить ссылки A/master
и B/master
(и два пульта дистанционного управления). Есть одна особенность: поскольку мы выполняли всю работу непосредственно в индексе, не заботясь о рабочем дереве, оно все еще совершенно пустое:
$ ls
$ git status -s
D A/file
D A/new
D B/another
D B/start
Чтобы это исправить в конце, нам нужно просто запустить git checkout HEAD -- .
:
$ git checkout HEAD -- .
$ git status -s
$ git status
On branch master
nothing to commit, working tree clean
Как написать собственный скрипт автоматизации
На практике вы, вероятно, захотите использовать git write-tree
и git commit-tree
, а не git commit
, чтобы делать новые коммиты. Вы должны написать небольшой скрипт (на любом языке, который вам нравится) для запуска git rev-list
, чтобы собрать хеш-идентификаторы коммитов для объединения. Сценарий должен проверять эти коммиты - например, просматривая авторство и даты, или содержимое файла, или что-то еще - чтобы решить, как переплетать коммиты. Затем, приняв решение о переплетении и о том, какие структуры ветвления и слияния предоставить, скрипт может начать процесс многократного выполнения следующих шагов:
- Очистить индекс.
- Дергать в дереве из коммита в подграфе из репо-A, с любым подходящим параметром
--prefix
- в вашем случае это --prefix=
, т. Е. Пустая строка, но в других случаях это будет именем каталога с косой чертой).
- Дергать в дереве из коммита в подграфе из репо-B с другим подходящим
--prefix
, чтобы не было коллизий между записями из A
и B
.
- Используйте
git write-tree
, чтобы написать дерево. Его вывод - идентификатор хеша дерева для следующего шага.
- Используйте
git commit-tree
с соответствующими -p
аргументом (ами) для установки родителя (ей) нового коммита. Передайте ему соответствующий (комбинированный или любой другой) текст сообщения о фиксации. Используйте переменные окружения GIT_AUTHOR_NAME
, GIT_AUTHOR_EMAIL
, GIT_AUTHOR_DATE
, GIT_COMMITTER_NAME
, GIT_COMMITTER_EMAIL
и GIT_COMMITTER_DATE
для управления именами и датами авторов и коммиттеров. Выходные данные из git commit-tree
- это хэш-идентификатор, который является родителем некоторого последующего коммита
Когда все завершается, last коммиты, сделанные для любой конкретной ветви или набора ветвей, являются хеш-идентификаторами, которые идут в эти ветви, так что теперь вы можете запустить:
git branch <name> <hash>
для каждого такого хэш-идентификатора.