Как импортировать существующее хранилище Git в другое?
У меня есть Git-репозиторий в папке с именем XXX, и у меня есть второй Git-репозиторий с именем YYY.
Я хочу импортировать репозиторий XXX в репозиторий YYY в виде подкаталога с именем ZZZ и добавить всю историю изменений XXX в YYY.
Структура папок перед:
XXX
|- .git
|- (project files)
YYY
|- .git
|- (project files)
Структура папок после:
YYY
|- .git <-- This now contains the change history from XXX
|- ZZZ <-- This was originally XXX
|- (project files)
|- (project files)
Можно ли это сделать, или я должен использовать субмодули?
17 ответов
Вероятно, самым простым способом было бы перетащить материал XXX в ветку в YYY, а затем объединить его с master:
В ГГГ:
git remote add other /path/to/XXX
git fetch other
git checkout -b ZZZ other/master
mkdir ZZZ
git mv stuff ZZZ/stuff # repeat as necessary for each file/dir
git commit -m "Moved stuff to ZZZ"
git checkout master
git merge ZZZ --allow-unrelated-histories # should add ZZZ/ to master
git commit
git remote rm other
git branch -d ZZZ # to get rid of the extra branch before pushing
git push # if you have a remote, that is
Я на самом деле только что попробовал это с парой моих репозиториев, и это работает. В отличие от ответа Йорга, он не позволит вам продолжать использовать другой репозиторий, но я не думаю, что вы указали это в любом случае.
Примечание: так как это было изначально написано в 2009 году, git добавил слияние поддеревьев, упомянутое в ответе ниже. Я бы, вероятно, использовал этот метод сегодня, хотя, конечно, этот метод все еще работает.
Если вы хотите сохранить точную историю коммитов второго репозитория и, следовательно, также сохранить возможность легко объединять восходящие изменения в будущем, то вот метод, который вам нужен. Это приводит к тому, что в ваше хранилище импортируется немодифицированная история поддерева плюс один коммит слияния для перемещения объединенного репозитория в подкаталог.
git remote add XXX_remote <path-or-url-to-XXX-repo>
git fetch XXX_remote
git merge -s ours --no-commit XXX_remote/master
git read-tree --prefix=ZZZ/ -u XXX_remote/master
git commit -m "Imported XXX as a subtree."
Вы можете отслеживать восходящие изменения, например, так:
git pull -s subtree XXX_remote master
Git самостоятельно выясняет, где находятся корни, прежде чем выполнять слияние, поэтому вам не нужно указывать префикс при последующих слияниях.
GIT 2.9+: команде слияния потребуется опция: --allow-unrelated-histories
, Спасибо @stuXnet!
Метод в другом ответе, который использует read-tree
и пропускает merge -s ours
Шаг по сути не отличается от копирования файлов с помощью cp и фиксации результата.
Первоначальный источник был взят из справочной статьи github "Слияние поддеревьев".
git-subtree
это скрипт, предназначенный именно для этого случая использования объединения нескольких репозиториев в одно при сохранении истории (и / или разбиения истории поддеревьев, хотя это, похоже, не имеет отношения к этому вопросу). Он распространяется как часть дерева мерзавцев с версии 1.7.11.
Объединить репозиторий <repo>
на доработке <rev>
как подкаталог <prefix>
использовать git subtree add
следующее:
git subtree add -P <prefix> <repo> <rev>
git-subtree реализует стратегию слияния поддеревьев более удобным для пользователя способом.
Для вашего случая внутри репозитория YYY вы должны выполнить:
git subtree add -P ZZZ /path/to/XXX.git master
Существует хорошо известный пример этого в самом Git-репозитории, который в сообществе Git известен как " самое крутое слияние за всю историю " (после строки темы, которую Линус Торвальдс использовал в электронном письме в список рассылки Git, который описывает это слияния). В этом случае gitk
Git GUI, который сейчас является частью собственно Git, фактически был отдельным проектом. Линусу удалось объединить этот репозиторий с репозиторием Git таким образом, чтобы
- он появляется в репозитории Git, как если бы он всегда разрабатывался как часть Git,
- вся история сохраняется и
- он все еще может быть разработан независимо в его старом хранилище, с изменениями просто
git pull
редактор
Электронное письмо содержит шаги, необходимые для воспроизведения, но это не для слабонервных: во-первых, Линус написал Git, поэтому он, вероятно, знает об этом немного больше, чем вы или я, а во-вторых, это было почти 5 лет назад и Git значительно улучшился с тех пор, так что, может быть, теперь стало намного проще.
В частности, я думаю, что в настоящее время можно использовать субмодуль gitk, в этом конкретном случае.
Позвольте мне использовать имена a
(на месте XXX
а также ZZZ
) а также b
(на месте YYY
), поскольку это упрощает чтение описания.
Скажем, вы хотите объединить репозиторий 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
Подробнее об этом здесь.
Простой способ сделать это - использовать git format-patch.
Предположим, у нас есть 2 репозитория git foo и bar.
foo содержит:
- foo.txt
- .git
Бар содержит:
- bar.txt
- .git
и мы хотим получить foo, содержащий историю баров и эти файлы:
- foo.txt
- .git
- Foobar / bar.txt
Итак, чтобы сделать это:
1. create a temporary directory eg PATH_YOU_WANT/patch-bar
2. go in bar directory
3. git format-patch --root HEAD --no-stat -o PATH_YOU_WANT/patch-bar --src-prefix=a/foobar/ --dst-prefix=b/foobar/
4. go in foo directory
5. git am PATH_YOU_WANT/patch-bar/*
И если мы хотим переписать все коммиты сообщений из bar, которые мы можем сделать, например, в Linux:
git filter-branch --msg-filter 'sed "1s/^/\[bar\] /"' COMMIT_SHA1_OF_THE_PARENT_OF_THE_FIRST_BAR_COMMIT..HEAD
Это добавит "[bar] " в начале каждого сообщения коммита.
Эта функция клонирует удаленное репо в локальный каталог репо, после объединения все коммиты будут сохранены, git log
будут показаны оригинальные коммиты и правильные пути:
function git-add-repo
{
repo="$1"
dir="$(echo "$2" | sed 's/\/$//')"
path="$(pwd)"
tmp="$(mktemp -d)"
remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"
git clone "$repo" "$tmp"
cd "$tmp"
git filter-branch --index-filter '
git ls-files -s |
sed "s,\t,&'"$dir"'/," |
GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD
cd "$path"
git remote add -f "$remote" "file://$tmp/.git"
git pull "$remote/master"
git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
git remote remove "$remote"
rm -rf "$tmp"
}
Как пользоваться:
cd current/package
git-add-repo https://github.com/example/example dir/to/save
Если вы сделаете небольшие изменения, вы даже можете переместить файлы / каталоги объединенного репо по разным путям, например:
repo="https://github.com/example/example"
path="$(pwd)"
tmp="$(mktemp -d)"
remote="$(echo "$tmp" | sed 's/\///g' | sed 's/\./_/g')"
git clone "$repo" "$tmp"
cd "$tmp"
GIT_ADD_STORED=""
function git-mv-store
{
from="$(echo "$1" | sed 's/\./\\./')"
to="$(echo "$2" | sed 's/\./\\./')"
GIT_ADD_STORED+='s,\t'"$from"',\t'"$to"',;'
}
# NOTICE! This paths used for example! Use yours instead!
git-mv-store 'public/index.php' 'public/admin.php'
git-mv-store 'public/data' 'public/x/_data'
git-mv-store 'public/.htaccess' '.htaccess'
git-mv-store 'core/config' 'config/config'
git-mv-store 'core/defines.php' 'defines/defines.php'
git-mv-store 'README.md' 'doc/README.md'
git-mv-store '.gitignore' 'unneeded/.gitignore'
git filter-branch --index-filter '
git ls-files -s |
sed "'"$GIT_ADD_STORED"'" |
GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD
GIT_ADD_STORED=""
cd "$path"
git remote add -f "$remote" "file://$tmp/.git"
git pull "$remote/master"
git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
git remote remove "$remote"
rm -rf "$tmp"
Извещения
Пути заменяет через sed
, поэтому убедитесь, что после слияния он переместился правильными путями.--allow-unrelated-histories
параметр существует только потому, что git >= 2.9.
Основываясь на этой статье, использование поддерева - это то, что мне помогло, и была перенесена только соответствующая история. Публикация здесь на случай, если кому-то понадобятся шаги (не забудьте заменить заполнители на значения, применимые к вам):
в вашем исходном репозитории разделить подпапку на новую ветку
git subtree split --prefix=<source-path-to-merge> -b subtree-split-result
в месте назначения слияния репо в ветке результатов разделения
git remote add merge-source-repo <path-to-your-source-repository>
git fetch merge-source-repo
git merge -s ours --no-commit merge-source-repo/subtree-split-result
git read-tree --prefix=<destination-path-to-merge-into> -u merge-source-repo/subtree-split-result
проверить ваши изменения и зафиксировать
git status
git commit
Не забудь
Очистить, удалив subtree-split-result
ветка
git branch -D subtree-split-result
Удалите пульт, который вы добавили, чтобы получить данные из репозитория
git remote rm merge-source-repo
Добавив еще один ответ, я думаю, что это немного проще. Извлечение repo_dest выполняется в repo_to_import, а затем выполняется push -set-upstream url:repo_dest master.
Этот метод помог мне импортировать несколько небольших репо в большее.
Как импортировать: repo1_to_import в repo_dest
# checkout your repo1_to_import if you don't have it already
git clone url:repo1_to_import repo1_to_import
cd repo1_to_import
# now. pull all of repo_dest
git pull url:repo_dest
ls
git status # shows Your branch is ahead of 'origin/master' by xx commits.
# now push to repo_dest
git push --set-upstream url:repo_dest master
# repeat for other repositories you want to import
Переименуйте или переместите файлы и директории в нужную позицию в исходном репо перед выполнением импорта. например
cd repo1_to_import
mkdir topDir
git add topDir
git mv this that and the other topDir/
git commit -m"move things into topDir in preparation for exporting into new repo"
# now do the pull and push to import
Метод, описанный по следующей ссылке, вдохновил этот ответ. Мне понравилось это, поскольку это казалось более простым. Но остерегайтесь! Там будут драконы! https://help.github.com/articles/importing-an-external-git-repository git push --mirror url:repo_dest
толкает вашу локальную историю репо и состояние на удаленный (url:repo_dest). НО это удаляет старую историю и состояние удаленного. Веселье наступает!:-E
Вот сценарий, который будет работать сразу же.
#!/bin/bash -xe
# script name: merge-repo.sh
# To merge repositories into the current.
# To see the log of the new repo use 'git log --follow -- unprefixed-filename'
# So if the file is repo/test.cpp use 'git log --follow -- test.cpp'
# I'm not sure how this will work when two files have the same name.
#
# `git branch -a` will show newly created branches.
# You can delete them if you want.
merge_another() {
repo="$1" # url of the remote repo
rn="$2" # new name of the repo, you can keep the same name as well.
git remote add ${rn} ${repo}
git fetch ${rn}
git merge -s ours --no-commit --allow-unrelated-histories ${rn}/master
git read-tree --prefix=${rn}/ -u ${rn}/master
git commit -m "Imported ${rn} as a subtree."
git pull -s subtree ${rn} master
}
merge_another $1 $2
Для запуска скрипта. Перейдите в репо, в котором вы хотите объединить другое репо, и запустите скрипт.
cd base-repo
./merge-repo.sh git@github.com:username/repo-to-be-merged.git repo-to-be-merged-new-name
Толкать:
git push origin master
Посмотрите Базовый пример в этой статье и рассмотрите такое отображение в репозиториях:
A
<->YYY
,B
<->XXX
После всех действий, описанных в этой главе (после слияния), удалите ветку B-master
:
$ git branch -d B-master
Затем нажмите изменения.
Меня устраивает.
Я хотел импортировать только некоторые файлы из другого хранилища (XXX) в моем случае. Поддерево было слишком сложным для меня, и другие решения не работали. Вот что я сделал:
ALL_COMMITS=$(git log --reverse --pretty=format:%H -- ZZZ | tr '\n' ' ')
Это дает вам разделенный пробелами список всех коммитов, которые влияют на файлы, которые я хотел импортировать (ZZZ) в обратном порядке (вам может понадобиться добавить --follow, чтобы также перехватывать переименования). Затем я вошел в целевой репозиторий (YYY), добавил другой репозиторий (XXX) как удаленный, сделал выборку из него и, наконец,:
git cherry-pick $ALL_COMMITS
который добавляет все коммиты в вашу ветку, поэтому вы будете иметь все файлы с их историей и можете делать с ними все, что захотите, как если бы они всегда были в этом хранилище.
Не хватает репутации, чтобы добавить комментарий к ответу x-yuri, но он прекрасно работает и сохраняет историю. Я работал с двумя рабочими локальными репо и получил эту ошибку:
Прерывание: отказ от деструктивной перезаписи истории репо, поскольку это не похоже на новый клон.(ожидается свежеупакованный репо) Вместо этого используйте свежий клон. Если вы все равно хотите продолжить, используйте --force.
Вместо того, чтобы беспокоиться о последствиях
--force
flag, я сначала клонировал репо локально:
cd tempDir
git clone <location of repo to be merged> --no-local
и использовал эту только что клонированную копию для серии команд, которые выложил x-yuri. Наконец, в:
git filter-repo --to-subdirectory-filter a
,
a
- это имя, которое вы даете корневой папке репо, которое вы будете импортировать.
Я был в ситуации, когда искал -s theirs
но, конечно, этой стратегии не существует. Моя история заключалась в том, что я разработал проект на GitHub, а теперь по какой-то причине мой местный master
не может быть объединен с upstream/master
хотя я не внес никаких локальных изменений в эту ветку. (Действительно, не знаю, что там произошло - я думаю, что вверх по течению были сделаны некоторые грязные толчки за кулисами, может быть?)
То, что я в итоге сделал, было
# as per https://help.github.com/articles/syncing-a-fork/
git fetch upstream
git checkout master
git merge upstream/master
....
# Lots of conflicts, ended up just abandonging this approach
git reset --hard # Ditch failed merge
git checkout upstream/master
# Now in detached state
git branch -d master # !
git checkout -b master # create new master from upstream/master
Так что теперь мой master
снова в синхронизации с upstream/master
(и вы можете повторить вышеописанное для любой другой ветви, которую вы также хотите синхронизировать аналогичным образом).
Я могу предложить другое решение (альтернативу git-submodules) для вашей проблемы - инструмент gil (git links)
Это позволяет описывать и управлять сложными зависимостями git-репозиториев.
Также он предоставляет решение проблемы зависимостей git recursive submodules.
Предположим, у вас есть следующие зависимости проекта: пример графика зависимостей репозитория git
Тогда вы можете определить .gitlinks
файл с описанием отношений с репозиториями:
# Projects
CppBenchmark CppBenchmark https://github.com/chronoxor/CppBenchmark.git master
CppCommon CppCommon https://github.com/chronoxor/CppCommon.git master
CppLogging CppLogging https://github.com/chronoxor/CppLogging.git master
# Modules
Catch2 modules/Catch2 https://github.com/catchorg/Catch2.git master
cpp-optparse modules/cpp-optparse https://github.com/weisslj/cpp-optparse.git master
fmt modules/fmt https://github.com/fmtlib/fmt.git master
HdrHistogram modules/HdrHistogram https://github.com/HdrHistogram/HdrHistogram_c.git master
zlib modules/zlib https://github.com/madler/zlib.git master
# Scripts
build scripts/build https://github.com/chronoxor/CppBuildScripts.git master
cmake scripts/cmake https://github.com/chronoxor/CppCMakeScripts.git master
Каждая строка описывает git ссылку в следующем формате:
- Уникальное имя хранилища
- Относительный путь к хранилищу (начинается с пути файла.gitlinks)
- Git-репозиторий, который будет использоваться в команде git clone.
- Пустая строка или строка, начинающаяся с #, не анализируется (рассматривается как комментарий).
Наконец, вам нужно обновить репозиторий с корневым образцом:
# Clone and link all git links dependencies from .gitlinks file
gil clone
gil link
# The same result with a single command
gil update
В результате вы клонируете все необходимые проекты и правильно связываете их друг с другом.
Если вы хотите зафиксировать все изменения в каком-либо репозитории со всеми изменениями в дочерних связанных репозиториях, вы можете сделать это с помощью одной команды:
gil commit -a -m "Some big update"
Команды Pull, Push работают аналогично:
gil pull
gil push
Инструмент Gil (git links) поддерживает следующие команды:
usage: gil command arguments
Supported commands:
help - show this help
context - command will show the current git link context of the current directory
clone - clone all repositories that are missed in the current context
link - link all repositories that are missed in the current context
update - clone and link in a single operation
pull - pull all repositories in the current directory
push - push all repositories in the current directory
commit - commit all repositories in the current directory
Подробнее о проблеме зависимости git рекурсивных субмодулей.
Я не знаю простого способа сделать это. Вы МОЖЕТЕ сделать это:
- Используйте git filter-branch, чтобы добавить суперкаталог ZZZ в репозиторий XXX.
- Нажмите новую ветку в хранилище YYY
- Объедините вытолкнутую ветвь со стволом YYY.
Я могу редактировать с деталями, если это звучит привлекательно.
Я думаю, что вы можете сделать это, используя "git mv" и "git pull".
Я честный мерзавец, так что будьте осторожны с вашим основным хранилищем, но я только что попробовал это в временном каталоге, и это, похоже, работает.
Первое - переименуйте структуру XXX, чтобы она соответствовала тому, как вы хотите, чтобы она выглядела, когда она находится в пределах YYY:
cd XXX
mkdir tmp
git mv ZZZ tmp/ZZZ
git mv tmp ZZZ
Теперь XXX выглядит так:
XXX
|- ZZZ
|- ZZZ
Теперь используйте 'git pull', чтобы получить изменения:
cd ../YYY
git pull ../XXX
Теперь YYY выглядит так:
YYY
|- ZZZ
|- ZZZ
|- (other folders that already were in YYY)