Объединить репозиторий git в подкаталог

85

Я хотел бы объединить удаленный репозиторий git в моем рабочем репозитории git в качестве его подкаталога. Я бы хотел, чтобы полученный репозиторий содержал объединенную историю двух репозиториев, а также чтобы каждый файл объединенного репозитория сохранял свою историю, как это было в удаленном репозитории. Я попытался использовать стратегию поддерева, как упоминалось в разделе Как использовать стратегию слияния поддерева , но после выполнения этой процедуры, хотя полученный репозиторий действительно содержит объединенную историю двух репозиториев, отдельные файлы, поступающие с удаленного, не сохранили свою историю (`git log 'на любом из них просто показывает сообщение« Объединенная ветка ... »).

Также я не хочу использовать подмодули, потому что я не хочу, чтобы два объединенных репозитория git больше были отдельными.

Можно ли объединить удаленный репозиторий git в другой как подкаталог с отдельными файлами, поступающими из удаленного репозитория, с сохранением их истории?

Большое спасибо за любую помощь.

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

РЕДАКТИРОВАТЬ 2: В надежде, что я прояснюсь, я даю точные команды, которые я использовал со стратегией git subtree, что приводит к очевидной потере истории файлов удаленного репозитория. Пусть 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на любом из них отображается только сообщение фиксации «Merge B as subdirectory in subdir / Iwant / to / put / Б / дюйм " Их файловая история, как в B, потеряна.

Что , кажется , на работу (так как я новичок на мерзавец я могу ошибаться) , заключается в следующем:

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

Приведенная выше команда для filter-branch взята из git help filter-branch, в котором я только изменил путь к подкаталогу.

Христос
источник
Что gitkговорит об истории? Раньше я успешно использовал git subtree merge. Возможно, вы сможете раскрыть свои точные команды? Я не уверен, что git-filter-branch - правильный подход. Я мог бы порекомендовать попробовать git-fast-export и git-fast-import, чтобы синтезировать новую историю.
Сет Робертсон
После выполнения процедуры поддерева gitkпоказаны два репозитория, объединенных в их подсказках и не связанных в их начальных фиксациях. (Помогло бы я, если бы я разместил скриншоты просмотра истории gitk? Могу ли я?) К сожалению, отдельные файлы удаленного репозитория не сохранили свою историю, если я это сделаю в терминале git log <file-from-remote-repo>. Я смотрю git-fast-exportи git-fast-import; Я новичок в git. Я отредактирую свой вопрос, чтобы показать, какие именно команды я использовал с git subtree. Большое спасибо за ваш ответ.
christosc
@christosc: ваш второй метод сработал красиво и очень просто, большое спасибо! Мне просто пришлось изменить subdir / Iwant / на / put / B / in / и сделать его одинарным (потому что msysgit в Windows, похоже, не поддерживает возврат строки в командах с): git filter-branch --index-filter 'git ls-файлы -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
габурный
@ user1121352 Рад, что помог вам.
christosc
Обычно я следую этому ответу: stackoverflow.com/a/1684694/207791
Виктор Сергиенко,

Ответы:

40

После получения более полного объяснения того, что происходит, я думаю, что понимаю это, и в любом случае у меня есть обходной путь. В частности, я считаю, что обнаружение переименования обманывается слиянием поддерева с --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(в з / а) показывает, что история действительно появляется, мы ее видим. Бег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
# According to Alex Brown and pjvandehaar, newer versions of git need --allow-unrelated-histories
# git merge -s ours --allow-unrelated-histories --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 с переименованием, и я боюсь, что это не пройдет гладко. Но если b уйдет, вы выиграете.

Сет Робертсон
источник
Действительно, это работает @Seth! И мне не пришлось прибегать к переписыванию истории, как с фильтром-ветвью, что делает историю несколько обманчивой (например, при просмотре git log --stat). Также я не заметил --followпереключателя в документации git log; кажется очень удобным с переименованием. Большое спасибо за столь подробный и информативный ответ!
christosc
2
Этот ответ был бы намного полезнее, если бы код примера был разбит на читаемые строки, а не на однострочную строку, разделенную точкой с запятой. ;)
jwadsack
Я хотел бы объединить «b» в «a» с сохранением полной истории. Как я мог это сделать?
emeraldhieu
3
См. Stackoverflow.com/questions/37937984/… для исправления ошибок
Алекс Браун,
3
Как упоминалось в @AlexBrown, в новых версиях gitэтого продукта, fatal: refusing to merge unrelated historiesпоэтому вы должны запускать его git merge -s ours --allow-unrelated-histories --no-commit B/master.
pjvandehaar
61

git-subtree- это сценарий, предназначенный именно для этого варианта использования объединения нескольких репозиториев в один с сохранением истории (и / или разделения истории поддеревьев, хотя это, похоже, не имеет отношения к этому вопросу). Он распространяется как часть дерева git, начиная с выпуска 1.7.11 .

Чтобы объединить репозиторий <repo> при ревизии в <rev>качестве подкаталога <prefix>, используйте git subtree addследующее:

git subtree add -P <prefix> <repo> <rev>

git-subtree реализует стратегию слияния поддеревьев более удобным для пользователя способом.

обратная сторона является то , что в объединенном истории эти файлы без префикса (не в подкаталоге). Скажем, вы объединяете репозиторий aв b. В результате git log a/f1вы увидите все изменения (если они есть), кроме тех, что были в объединенной истории. Ты можешь сделать:

git log --follow -- f1

но при этом не будут отображаться другие изменения, кроме объединенной истории.

Другими словами, если вы не измените a файлы в репозитории b, вам нужно указать --followи путь без префикса. Если вы измените их в обоих репозиториях, то у вас будет 2 команды, ни одна из которых не отображает все изменения.

Подробнее об этом здесь .

Кинан
источник
Ницца! Это именно то, что мне нужно в одной строчке. Спасибо, будущее!
iameli
Это идеальное решение для объединения другого репозитория в мой репозиторий в поднаправлении.
eitch
1
Обратите внимание, что это не будет работать с существующими подкаталогами в <prefix>. Например, чтобы объединить подкаталог, который когда-то был перемещен вручную, в его собственный репозиторий, и вы хотите объединить его обратно.
Ричард Кифер,
8

я хотел

  1. вести линейную историю без явного слияния, и
  2. сделать так, чтобы файлы объединенного репозитория всегда существовали в подкаталоге, и в качестве побочного эффекта заставить 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
hfs
источник
git rebaseпохоже, не разрешает указанные параметры вместе: "ошибка: невозможно объединить интерактивные параметры (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + - -onto) с параметрами am (--committer-date-is-author-date) »
Сэм
Интересно! Попробуй уронить--committer-date-is-author-date . Проверка несовместимости параметров была недавно добавлена ​​в git v2.19.0 ( github.com/git/git/commit/… ). Судя по описанию, это звучит так, как будто --committer-date-is-author-dateраньше его молча игнорировали.
hfs
Вместо того, чтобы использовать старую filter-branchкоманду, используйте git filter-repo --to-subdirectory-filter <dir>ее быстрее и проще.
Виллем
5

Если вы действительно хотите сшить все вместе, ищите прививки. Вы также должны использовать git rebase --preserve-merges --onto. Также есть возможность сохранить дату автора для информации о коммиттере.

Адам Димитрук
источник
@adymitruk Спасибо за ответ. Я новичок в git, поэтому я рассмотрю предлагаемое вами решение. Я пробовал git filter-branchи вроде работает, но может у вас лучше. Я попробую.
christosc
@adymitruk Могу ли я использовать rebase с двумя репозиториями, которые не связаны между собой как ветки? Я имею в виду, что два репозитория, которые я хочу объединить, не имеют общих начальных
коммитов
Спасибо @adymitruk. Я не был уверен, можно ли выполнить ребазирование с двумя несвязанными репозиториями. Это, безусловно, будет полезно…
christosc
Но не бойтесь ветки-фильтра. Это спасало нас много раз. Просто сделайте еще одну предшествующую ветку, и вы всегда сможете вернуться. Это или используйте reflog.
Adam Dymitruk
Понятно ... В любом случае мне лучше почитать документацию по этим концепциям и командам git. Имея лишь небольшой опыт работы с VCS, а именно с svn, я немного ошеломлен git. Но его мощность, похоже, того стоит.
christosc
4

Я нашел следующее решение, подходящее для меня. Сначала я захожу в проект B, создаю новую ветку, в которой уже все файлы будут перемещены в новый подкаталог. Затем я отправляю эту новую ветку в origin. Затем я перехожу в проект A, добавляю и получаю удаленный от B, затем проверяю перемещенную ветку, возвращаюсь в мастер и объединяю:

# 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, поэтому не могу комментировать, является ли это особенно хорошим решением или в нем есть оговорки, но пока все в порядке.

0__
источник
Кажется, что люди здесь поддерживают этот подход: stackoverflow.com/questions/1683531/…
nacross 03
3

Вы пробовали добавить дополнительный репозиторий как подмодуль git? Он не будет объединять историю с содержащим его репозиторием, фактически, это будет независимый репозиторий.

Я упоминаю об этом, потому что вы этого не сделали.

Abizern
источник
1
Спасибо за ответ, Abizern. На самом деле я действительно хочу, чтобы две истории репозитория были объединены в одну; Я больше не хочу, чтобы они были отдельными, поэтому я не упомянул подмодули.
christosc
1

Допустим , вы хотите объединить репозиторий 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

Подробнее об этом здесь .

x-yuri
источник
0

Подобно ответу 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
Застрял
источник