Объединить git-репозиторий в подкаталог
Я хотел бы объединить удаленный git-репозиторий с моим рабочим git-репозиторием как его подкаталог. Я хотел бы, чтобы полученный репозиторий содержал объединенную историю двух репозиториев, а также чтобы каждый файл объединенного репозитория сохранял свою историю, как это было в удаленном репозитории. Я попытался использовать стратегию поддерева, как упоминалось в разделе Как использовать стратегию слияния поддерева, но после выполнения этой процедуры, хотя результирующий репозиторий действительно содержит объединенную историю двух репозиториев, отдельные файлы, поступающие из удаленного, не сохранили свою историю (`git log'на любом из них просто показывает сообщение" Объединенная ветвь...").
Также я не хочу использовать подмодули, потому что я не хочу, чтобы два объединенных репозитория git больше не были отдельными.
Можно ли объединить удаленный репозиторий git в другом как подкаталог, при этом отдельные файлы, поступающие из удаленного репозитория, сохраняют свою историю?
Большое спасибо за любую помощь.
РЕДАКТИРОВАТЬ: В настоящее время я пробую решение, которое использует git filter-branch для перезаписи объединенной истории репозитория. Кажется, это работает, но мне нужно проверить это еще немного. Я вернусь, чтобы сообщить о моих выводах.
РЕДАКТИРОВАТЬ 2: В надежде прояснить ситуацию, я даю точные команды, которые я использовал со стратегией поддерева git, что приводит к очевидной потере истории файлов удаленного репозитория. Пусть A будет git-репо, в котором я сейчас работаю, а B - git-репо, которое я хотел бы включить в A в качестве его подкаталога. Было сделано следующее:
git remote add -f B <url-of-B>
git merge -s ours --no-commit B/master
git read-tree --prefix=subdir/Iwant/to/put/B/in/ -u B/master
git commit -m "Merge B as subdirectory in subdir/Iwant/to/put/B/in."
После этих команд и заходя в каталог subdir/Iwant/to/put/B/in, я вижу все файлы B, но git log
на любом из них отображается только сообщение о коммите "Объединить B как подкаталог в subdir/Iwant/to/put/B/in." Их история файлов в том виде, как она есть в B, потеряна.
Кажется, что работает (так как я новичок в git, я могу ошибаться), это следующее:
git remote add -f B <url-of-B>
git checkout -b B_branch B/master # make a local branch following B's master
git filter-branch --index-filter \
'git ls-files -s | sed "s-\t\"*-&subdir/Iwant/to/put/B/in/-" |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
git update-index --index-info &&
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD
git checkout master
git merge B_branch
Команда выше для ответвления фильтра взята из git help filter-branch
, в котором я только изменил путь subdir.
8 ответов
Получив более полное объяснение того, что происходит, я думаю, что понимаю это, и в любом случае внизу у меня есть обходной путь. В частности, я считаю, что обнаружение переименования обманывается слиянием поддерева с --prefix. Вот мой тестовый пример:
mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA
cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB
cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
git read-tree --prefix=bdir -u B/master
git commit -m "subtree merge B into bdir"
cd bdir
echo BBB>>B
git commit -a -m BBB
Мы создаем директории git a и b с несколькими коммитами каждый. Мы выполняем слияние поддерева, а затем делаем окончательный коммит в новом поддереве.
Бег gitk
(в z/a) показывает, что история действительно появляется, мы можем видеть это. Бег git log
показывает, что история действительно появляется. Однако при просмотре определенного файла возникает проблема: git log bdir/B
Ну, есть хитрость, которую мы можем сыграть. Мы можем посмотреть историю переименования определенного файла, используя --follow. git log --follow -- B
, Это хорошо, но не очень, поскольку не удается связать историю предварительного слияния с последующим слиянием.
Я пытался играть с -M и -C, но не смог заставить его следовать одному конкретному файлу.
Итак, решение, я чувствую, состоит в том, чтобы сообщить git о переименовании, которое будет происходить как часть слияния поддерева. К сожалению, git-read-tree довольно суетлив относительно слияний поддеревьев, поэтому нам нужно работать через временный каталог, но это может уйти, прежде чем мы сделаем коммит. После этого мы можем увидеть полную историю.
Сначала создайте репозиторий "А" и сделайте несколько коммитов:
mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA
Во-вторых, создайте репозиторий "B" и сделайте несколько коммитов:
cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB
И хитрость в том, чтобы заставить это работать: заставить Git распознавать переименование, создав подкаталог и перенеся в него содержимое.
mkdir bdir
git mv B bdir
git commit -a -m bdir-rename
Вернитесь в репозиторий "A", получите и объедините содержимое "B":
cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
git read-tree --prefix= -u B/master
git commit -m "subtree merge B into bdir"
Чтобы показать, что они теперь объединены:
cd bdir
echo BBB>>B
git commit -a -m BBB
Чтобы доказать, полная история сохраняется в связной цепочке:
git log --follow B
После этого мы получаем историю, но проблема в том, что если вы на самом деле сохраняете старый репозиторий "b" и время от времени сливаетесь с ним (скажем, это на самом деле репо с отдельным сторонним репо), то у вас проблемы с этим третьим лицом не сделал переименование. Вы должны попытаться объединить новые изменения в вашу версию b с переименованием, и я боюсь, что это не пройдет гладко. Но если б уходит, вы выиграете.
git-subtree
это скрипт, предназначенный именно для этого случая использования объединения нескольких репозиториев в одно при сохранении истории (и / или разбиения истории поддеревьев, хотя это, похоже, не имеет отношения к этому вопросу). Он распространяется как часть дерева мерзавцев с версии 1.7.11.
Объединить репозиторий <repo>
на доработке <rev>
как подкаталог <prefix>
использовать git subtree add
следующее:
git subtree add -P <prefix> <repo> <rev>
git-subtree реализует стратегию слияния поддеревьев более удобным для пользователя способом.
Я хотел
- вести линейную историю без явного слияния и
- сделать так, чтобы файлы объединенного репозитория всегда существовали в подкаталоге, и в качестве побочного эффекта make
git log -- file
работать без--follow
,
Шаг 1: Переписать историю в исходном репозитории, чтобы она выглядела так, как будто все файлы всегда существовали ниже подкаталога.
Создайте временную ветку для переписанной истории.
git checkout -b tmp_subdir
Тогда используйте git filter-branch
как описано в разделе Как переписать историю, чтобы все файлы, кроме уже перемещенных, были в подкаталоге?:
git filter-branch --prune-empty --tree-filter '
if [ ! -e foo/bar ]; then
mkdir -p foo/bar
git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files foo/bar
fi'
Шаг 2: Переключитесь на целевой репозиторий. Добавьте исходный репозиторий как удаленный в целевой репозиторий и извлеките его содержимое.
git remote add sourcerepo .../path/to/sourcerepo
git fetch sourcerepo
Шаг 3: Используйте merge --onto
добавить коммиты переписанного исходного хранилища поверх целевого хранилища.
git rebase --preserve-merges --onto master --root sourcerepo/tmp_subdir
Вы можете проверить журнал, чтобы увидеть, что это действительно дает вам то, что вы хотели.
git log --stat
Шаг 4: После ребазирования вы находитесь в состоянии "отсоединенная ГОЛОВА". Вы можете быстро перенести мастера на новую голову.
git checkout -b tmp_merged
git checkout master
git merge tmp_merged
git branch -d tmp_merged
Шаг 5: Наконец некоторая очистка: Удалите временный пульт.
git remote rm sourcerepo
Если вы действительно хотите сшить вещи вместе, ищите прививки. Вы также должны использовать git rebase --preserve-merges --onto
, Существует также возможность сохранить дату автора для информации о коммитере.
Я нашел следующее решение работоспособным для меня. Сначала я иду в проект B, создаю новую ветку, в которой уже все файлы будут перемещены в новый подкаталог. Затем я подталкиваю эту новую ветку к источнику. Затем я иду в проект A, добавляю и извлекаю пульт B, затем извлекаю перемещенную ветку, возвращаюсь в master и сливаюсь:
# in local copy of project B
git checkout -b prepare_move
mkdir subdir
git mv <files_to_move> subdir/
git commit -m 'move files to subdir'
git push origin prepare_move
# in local copy of project A
git remote add -f B_origin <remote-url>
git checkout -b from_B B_origin/prepare_move
git checkout master
git merge from_B
Если я иду в подкаталог subdir
, Я могу использовать git log --follow
и до сих пор есть история.
Я не эксперт по git, поэтому я не могу комментировать, является ли это особенно хорошим решением или если у него есть предостережения, но пока все выглядит хорошо.
Скажем, вы хотите объединить репозиторий a
в b
(Я предполагаю, что они расположены рядом друг с другом):
cd a
git filter-repo --to-subdirectory-filter a
cd ..
cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a
Для этого вам понадобится git-filter-repo
установлен (filter-branch
не рекомендуется).
Пример слияния двух больших репозиториев, поместив один из них в подкаталог: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731
Подробнее об этом здесь.
Подобно ответу hfs, я хотел
- сохранить линейную историю без явного слияния и
- сделать так, чтобы файлы объединенного репозитория всегда существовали в подкаталоге, и в качестве побочного эффекта сделать
git log -- file
работать без--follow
.
Но я выбрал более современный.
filter-repo
(при условии
new
репо существует и проверено):
git clone git@host/repo/old.git
cd old
git checkout -b tmp_subdir
git filter-repo --to-subdirectory-filter old
cd ../new
git remote add old ../old
git fetch old
git rebase --rebase-merges --onto main --root old/tmp_subdir --committer-date-is-author-date
вам может потребоваться исправить конфликты (вручную) или изменить команду rebase, чтобы включить
--merge -s recursive -X theirs
если вы хотите попробовать решить это с помощью
theirs
версия:
git rebase --rebase-merges --onto main --root old/tmp_subdir --committer-
date-is-author-date --merge -s recursive -X theirs
вы окажетесь на отдельной HEAD, поэтому создайте новую ветку и объедините ее с основной заметкой, что современные репозитории не должны использовать «главную» ветку, а «основную»
branch for a more inclusive language.
git checkout -b old_merge
git checkout main
git merge old_merge
уборка
git branch -d old_merge
git remote rm old
Вы пытались добавить дополнительный репозиторий как подмодуль git? Он не объединит историю с содержащим репозиторий, фактически он будет независимым репозиторием.
Я упоминаю об этом, потому что вы этого не сделали.