Как импортировать существующее хранилище Git в другое?


477

У меня есть 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)

Можно ли это сделать, или я должен использовать субмодули?


2
На Github теперь это можно сделать из веб-интерфейса, когда вы создаете новый
репозиторий

Ответы:


430

Вероятно, самым простым способом было бы перетащить материал 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 добавил слияние поддеревьев, упомянутое в ответе ниже. Я бы, вероятно, использовал этот метод сегодня, хотя, конечно, этот метод все еще работает.


1
Спасибо. Я использовал слегка модифицированную версию вашей техники: я создал «промежуточную» ветку в XXX, где я создал папку ZZZ, и переместил «вещи» в нее. Затем я слил XXX в YYY.
Виджей Патель

1
Это отлично сработало для меня. Единственные изменения, которые я сделал, были: 1) "git branch -d ZZZ" перед push, потому что я не хотел, чтобы эта временная ветвь висела вокруг. 2) «git push» выдавал мне ошибку: «Никаких общих ссылок и ни одной не указано; ничего не делается. Возможно, вам следует указать такую ​​ветку, как« master »». (Источником, к которому я обращался, был пустой пустой репозиторий.) Но «git push --all» работал как чемпион.
CrazyPyro

1
Я хотел получить только папку ZZZ и историю в репозитории YYY: я хотел удалить исходное репозиторий XXX и ветку ZZZ в репо YYY. Я обнаружил, что удаляю ветку ZZZ, так как @CrazyPyro предложил удалить историю - чтобы сохранить ее, я слил ветку ZZZ в master перед удалением.
Оли Студхолм,

4
@SebastianBlask Я только что связался с этим с двумя моими репозиториями и понял, что есть пропущенный шаг, который никто, казалось, никогда не замечал, несмотря на то, что я получал от этого откровения в течение многих лет. :-) Я упомянул слияние с мастером, но на самом деле не показывал. Редактирование сейчас ...
ebneter

2
вы можете добавить что-то вроде этого при перемещении файлов в подпапку: git mv $(ls|grep -v <your foldername>) <your foldername>/ это скопирует все файлы и папки в вашу новую папку
serup

367

Если вы хотите сохранить точную историю коммитов второго репозитория и, следовательно, также сохранить возможность легко объединять восходящие изменения в будущем, то вот метод, который вам нужен. Это приводит к тому, что в ваше хранилище импортируется немодифицированная история поддерева плюс один коммит слияния для перемещения объединенного репозитория в подкаталог.

git remote add XXX_remote <path-or-url-to-XXX-repo>
git fetch XXX_remote
git merge -s ours --no-commit --allow-unrelated-histories 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 log ZZZ/aвам покажут все изменения (если они есть), кроме тех, что в объединенной истории. Ты можешь сделать:

git log --follow -- a

но это не покажет изменения, отличные от истории слияния.

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

Git версии до 2.9 : вам не нужно передавать эту --allow-unrelated-historiesопцию git merge.

Метод в другом ответе, который использует read-treeи пропускает merge -s oursшаг, практически не отличается от копирования файлов с помощью cp и фиксации результата.

Первоначальный источник был взят из справочной статьи github "Слияние поддеревьев" . И еще одна полезная ссылка .


9
похоже, у него нет сохраненной истории ... если я сделаю git logодин из файлов, которые я извлек, я просто увижу один коммит слияния и ничего из его предыдущей жизни в другом репо? Git 1.8.0
Anentropic

8
Ага! если я использую старый путь импортируемого файла, т.е. опускаю подкаталог, в который он был импортирован, то git log выдаст мне историю коммитов, например, git log -- myfileвместоgit log -- rack/myfile
Anentropic

2
@FrancescoFrassinelli, разве это не желательно? Внесение истории является особенностью этого метода.
Патриквацек

4
@FrancescoFrassinelli, если вы не хотите истории, почему бы просто не сделать обычную копию? Я пытаюсь понять, что могло бы привлечь вас к этому методу, если бы не история - это единственная причина, по которой я использовал этот метод!
Patrickvacek

7
Начиная с Git 2.9, вам нужна опция --allow-unrelated-historiesпри слиянии.
stuXnet

113

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 log ZZZ/aвам покажут все изменения (если они есть), кроме тех, что в объединенной истории. Ты можешь сделать:

git log --follow -- a

но это не покажет изменения, отличные от истории слияния.

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

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


4
Если у вас есть каталог для слияния вместо git subtree add -P name-of-desired-prefix ~/location/of/git/repo-without-.git branch-name
чистого

2
Опыт Noob: git (версия 2.9.0.windows.1) отвечает «фатальный: неоднозначный аргумент« HEAD »: неизвестная ревизия или путь не в рабочем дереве», когда я пробовал это в недавно инициализированном, локальном, необработанном репозитории, Но он работал нормально после того, как я действительно запустил новый репозиторий, то есть после добавления простого файла и фиксации обычным способом.
Штейн

Работал красиво по моему сценарию.
Джонни Юта

О, это фантастика.
dwjohnston

Я использовал предложение @Tatsh, и оно сработало для меня
Carmine Tambascia

49

Существует хорошо известный пример этого в самом Git-репозитории, который в сообществе Git известен как « самое крутое слияние за всю историю » (после строки темы, которую Линус Торвальдс использовал в электронном письме к списку рассылки Git, который описывает это слияния). В этом случае gitkграфический интерфейс Git, который теперь является частью собственно Git, фактически был отдельным проектом. Линусу удалось объединить этот репозиторий с репозиторием Git таким образом, чтобы

  • он появляется в хранилище Git, как если бы он всегда разрабатывался как часть Git,
  • вся история сохраняется и
  • он все еще может быть разработан независимо в его старом хранилище, с изменениями, которые просто git pullредактируются.

Электронное письмо содержит шаги, необходимые для воспроизведения, но это не для слабонервных: во-первых, Линус написал Git, поэтому он, вероятно, знает об этом немного больше, чем вы или я, и во-вторых, это было почти 5 лет назад и Гит улучшилась значительно с тех пор, так что, может быть , теперь гораздо проще.

В частности, я думаю, что в настоящее время можно использовать подмодуль gitk, в этом конкретном случае.


3
КСТАТИ. стратегия, используемая для последующих слияний (если таковые имеются), называется слиянием поддеревьев , и существует git-subtreeинструмент стороннего производителя, который может помочь вам в этом: github.com/apenwarr/git-subtree
Якуб Нарембски,

Спасибо, я забыл об этом. subtreeСтратегия слияния, особенно в сочетании с git-subtreeинструментом является хорошей, может быть , даже лучшей альтернативой подмодулей.
Йорг Миттаг

12

Простой способ сделать это - использовать 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 amскорее всего произойдет сбой.
Адам Монсен

1
Небольшая ошибка: git am удаляет все [ ]из сообщения коммита. Таким образом, вы должны использовать другой маркер, чем[bar]
HRJ

Не работал для меня. Получена "ошибка: foobar / mySubDir / test_host1: не существует в индексе. Копия неуспешного исправления находится в: /home/myuser/src/proj/.git/rebase-apply/patch Когда вы решили эту проблему , запустите "git am --continue". Это было после применения 11 патчей (из 60).
oligofren

1
Этот блог имеет аналогичный ответ на несколько иной вопрос (перемещение только выбранных файлов).
Джесси Глик

Я вижу один недостаток, все коммиты добавляются в HEAD целевого репозитория.
Чульц

8

Эта функция клонирует удаленное репо в локальный каталог репо, после объединения все коммиты будут сохранены, 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"

Notices
Paths заменяется на via sed, поэтому после слияния убедитесь, что он перемещен по правильным путям. Параметр существует только с Git> = 2.9.
--allow-unrelated-histories


2
Для людей OS X, установите, gnu-sedчтобы заставить git-add-repoфункцию работать. Еще раз спасибо, Андрей!
ptaylor

7

На основании этой статьи , использование поддерева - это то, что мне помогло, и была перенесена только соответствующая история. Публикация здесь на случай, если кому-то понадобятся шаги (не забудьте заменить заполнители на значения, применимые к вам):

в вашем исходном репозитории разделить подпапку на новую ветку

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


3

Добавив еще один ответ, я думаю, что это немного проще. Извлечение 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


1

Я хотел импортировать только некоторые файлы из другого хранилища (XXX) в моем случае. Поддерево было слишком сложным для меня, и другие решения не работали. Вот что я сделал:

ALL_COMMITS=$(git log --reverse --pretty=format:%H -- ZZZ | tr '\n' ' ')

Это дает вам разделенный пробелами список всех коммитов, которые влияют на файлы, которые я хотел импортировать (ZZZ) в обратном порядке (вам может понадобиться добавить --follow, чтобы также перехватывать переименования). Затем я зашел в целевой репозиторий (YYY), добавил другой репозиторий (XXX) как удаленный, сделал выборку из него и, наконец,:

git cherry-pick $ALL_COMMITS

который добавляет все коммиты в вашу ветку, поэтому вы будете иметь все файлы с их историей и можете делать с ними все, что захотите, как если бы они всегда были в этом хранилище.


1

Посмотрите Базовый пример в этой статье и рассмотрите такое отображение в репозиториях:

  • A<-> YYY,
  • B <-> XXX

После всех действий, описанных в этой главе (после слияния), удалите ветку B-master:

$ git branch -d B-master

Затем нажмите изменения.

Меня устраивает.


0

Я был в ситуации, когда искал, -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

Так что теперь my masterснова синхронизирован upstream/master(и вы можете повторить вышеописанное для любой другой ветви, которую вы также хотите синхронизировать аналогично).


1
А git reset --hard upstream/masterв вашем местном masterотделении сделают эту работу. Таким образом, вы не потеряете локальную ветку - такие вещи, как восходящий поток по умолчанию.
Tomekwi

0

Я могу предложить другое решение (альтернативу 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 ссылку в следующем формате:

  1. Уникальное имя хранилища
  2. Относительный путь к хранилищу (начинается с пути файла .gitlinks)
  3. Git-репозиторий, который будет использоваться в команде git clone.
  4. Пустая строка или строка, начинающаяся с #, не анализируется (рассматривается как комментарий).

Наконец, вы должны обновить ваш корневой репозиторий с примерами:

# 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 рекурсивных субмодулей .


0

Позвольте мне использовать имена 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

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


-1

Я не знаю простого способа сделать это. Вы МОЖЕТЕ сделать это:

  1. Используйте git filter-branch для добавления супер-каталога ZZZ в репозиторий XXX
  2. Нажмите новую ветку в хранилище YYY
  3. Объедините вытолкнутую ветвь со стволом YYY.

Я могу редактировать с деталями, если это звучит привлекательно.


-2

Я думаю, что вы можете сделать это, используя «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)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.