Объединение двух разных репозиториев в одну историю.
2022.08.19
Предположим, что у нас есть два репозитория — A и B:
Перед нами стоит задача — склеить эти два репозитория в один, наложив историю репозитория B на историю A.
A1 -> A2 -> ... -> AN -> B1 -> B2 -> ... -> BN
Зачем? Такое может потребоваться по историческим причинам, чтобы подменить один репозиторий другим, не теряя историю коммитов в мастере.
Это можно сделать с помощью git-replace и git-filter-repo: * git-replace поможет подменить родителя коммита; * git-filter-repo — альтернатива git-filter-branch — перепишет историю с учётом подмены родителей.
Создадим репозиторий для склеивания и добавим в него исходные репозитории как новые remote:
╰─❯ mkdir C
╰─❯ cd C
╰─❯ git init
╰─❯ git remote add A ../A
╰─❯ git fetch A
╰─❯ git remote add B ../B
╰─❯ git fetch BСоздадим поверх коммита AN ‘шов’, куда будем приклеивать коммит B1:
Этот коммит необязателен, но с ним процесс становится понятнее и проще визуализируется.
Теперь переключимся на коммиты репозитория B и подменим коммиту B1 родителя на только что созданный шов:
Что делает replace --graft? Из документации: >Create a graft commit. A new commit is created with the same content as
То есть, --graft берёт указанный коммит a51a4e5, создаёт точно такой же от родителя a41820c, и подменяет им a51a4e5.
Получаем в нашей ветке сшитые истории:
╭─ ~/git-glue/C
╰─❯ git log --oneline
3ad0733 (HEAD -> master, B/master) BN
e5f7713 B2
a51a4e5 (replaced) B1
a41820c graft
ae455c5 (A/master) AN
dc62217 A2
5e5ca47 A1Результат replace с --graft лучше считать временным, он просто создаёт в refs ссылку с одного коммита на другой. Теперь надо преобразовать replace --graft в настоящий replace:
Теперь перепишем историю с учётом склеенных коммитов через git-filter-repo:
╰─❯ git filter-repo --replace-refs delete-no-add --no-ff --prune-empty never --prune-degenerate neverВ аргументах filter-repo приведены флаги, которые достаточно хорошо перепишут историю, не теряя детей merge-коммитов и т.д.: * --replace-refs delete-no-add вычищает replace и делает их постоянными. * --no-ff, --prune-empty never и --prune-degenerate never позволяет сохранить все коммиты, даже пустые.
После этого получим полноценную сшитую историю, которую через --force можно запушить поверх старой.