Объединить два репозитория Git, не нарушая историю файлов
Мне нужно объединить два репозитория Git в совершенно новый, третий репозиторий. Я нашел много описаний того, как сделать это, используя слияние поддеревьев (например , ответ Якуба Наренбского " Как объединить два репозитория Git?") И следуя этим инструкциям, в основном, работает, за исключением того, что когда я фиксирую поддерево, объединяются все файлы из старых репозиториев записываются как новые добавленные файлы. Когда я делаю, я вижу историю коммитов из старых репозиториев. git log
, но если я сделаю git log <file>
он показывает только один коммит для этого файла - слияние поддерева. Судя по комментариям к приведенному выше ответу, я не одинок в этой проблеме, но не нашел опубликованных решений для нее.
Есть ли способ объединить репозитории и оставить историю отдельных файлов без изменений?
11 ответов
Оказывается, что ответ гораздо проще, если вы просто пытаетесь склеить два репозитория и сделать так, чтобы все выглядело так, а не управлять внешней зависимостью. Вам просто нужно добавить пульты к своим старым репозиториям, объединить их с новым мастером, переместить файлы и папки в подкаталог, зафиксировать перемещение и повторить для всех дополнительных репо. Подмодули, слияния поддеревьев и причудливые перебазировки предназначены для решения немного другой проблемы и не подходят для того, что я пытался сделать.
Вот пример скрипта Powershell для склеивания двух репозиториев:
# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init
# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
git commit --allow-empty -m "Initial dummy commit"
# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>
# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories
# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}
# Commit the move
git commit -m "Move old_a files into subdir"
# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir –exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"
Очевидно, что вместо этого вы можете объединить old_b со old_a (который становится новым объединенным репо), если вы предпочитаете это сделать - изменить скрипт так, чтобы он подходил.
Если вы также хотите перенести текущие ветви функций, используйте это:
# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress
Это единственная неочевидная часть процесса - это не слияние поддеревьев, а скорее аргумент к обычному рекурсивному слиянию, которое сообщает Git, что мы переименовали цель, и помогает Git правильно выстроить все.
Я написал чуть более подробное объяснение здесь.
Вот способ, который не переписывает историю, поэтому все идентификаторы коммитов остаются действительными. Конечным результатом является то, что файлы второго репо окажутся в подкаталоге.
Добавьте второй репо как удаленный:
cd firstgitrepo/ git remote add secondrepo username@servername:andsoon
Убедитесь, что вы загрузили все коммиты secondrepo:
git fetch secondrepo
Создайте локальную ветку из ветви второго репо:
git branch branchfromsecondrepo secondrepo/master
Переместите все его файлы в подкаталог:
git checkout branchfromsecondrepo mkdir subdir/ git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/ git commit -m "Moved files to subdir/"
Объедините вторую ветку с главной веткой первого репо:
git checkout master git merge --allow-unrelated-histories branchfromsecondrepo
Ваш репозиторий будет иметь более одного корневого коммита, но это не должно создавать проблем.
Скажем, вы хотите объединить репозиторий a
в b
(Я предполагаю, что они расположены рядом друг с другом):
cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a
Если вы хотите поставить a
в подкаталог перед приведенными выше командами выполните следующие действия:
cd a
git filter-repo --to-subdirectory-filter a
cd ..
Для этого вам понадобится git-filter-repo
установлен (filter-branch
не рекомендуется).
Пример слияния двух больших репозиториев, поместив один из них в подкаталог: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731
Подробнее об этом здесь.
Прошло несколько лет, и есть хорошо обоснованные решения, за которые проголосовали, но я хочу поделиться своим, потому что это было немного по-другому, потому что я хотел объединить 2 удаленных репозитория в новый, не удаляя историю из предыдущих репозиториев.
Создайте новый репозиторий в Github.
Загрузите вновь созданный репозиторий и добавьте старый удаленный репозиторий.
git clone https://github.com/alexbr9007/Test.git cd Test git remote add OldRepo https://github.com/alexbr9007/Django-React.git git remote -v
Получить все файлы из старого репозитория, чтобы создать новую ветку.
git fetch OldRepo git branch -a
В основной ветке выполните слияние, чтобы объединить старое репо с вновь созданным.
git merge remotes/OldRepo/master --allow-unrelated-histories
Создайте новую папку для хранения всего нового созданного контента, который был добавлен из OldRepo, и переместите его файлы в эту новую папку.
Наконец, вы можете загрузить файлы из объединенных репозиториев и безопасно удалить OldRepo из GitHub.
Надеюсь, что это может быть полезно для всех, кто занимается слиянием удаленных репозиториев.
Пожалуйста, посмотрите на использование
git rebase --root --preserve-merges --onto
связать две истории в начале своей жизни.
Если у вас есть пути, которые пересекаются, исправьте их
git filter-branch --index-filter
при использовании журнала убедитесь, что вы "находите копии труднее" с
git log -CC
Таким образом, вы найдете любые движения файлов в пути.
Я превратил решение из @Flimm это в git alias
как это (добавлено в мой ~/.gitconfig
):
[alias]
mergeRepo = "!mergeRepo() { \
[ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
git remote add newRepo $1; \
git fetch newRepo; \
git branch \"$2\" newRepo/master; \
git checkout \"$2\"; \
mkdir -vp \"${GIT_PREFIX}$3\"; \
git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"
Эта функция клонирует удаленное репо в локальный каталог репо:
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
Прибыль!
Как объединить 1, 2 или более локальных (или удаленных, но клонированных локально) репозиториев в другой репозиторий, сохраняя при этом историю git
Мне нужно что-то немного отличающееся от других ответов здесь, без установки каких-либо новых инструментов и с более подробной информацией, чтобы лучше понять, что здесь происходит, поэтому вот мой ответ:
Предположим, у вас есть локальная файловая структура. Оба репозитория здесь могут храниться только локально или по удаленному URL-адресу . Инструкции в любом случае одинаковы, при условии, что они также хранятся локально , а это означает, что если это удаленные репозитории, вы уже клонировали их локально.
В настоящее время у вас есть такая структура каталогов:
а вот независимые, автономные, локально хранящиеся или клонированные репозитории git.
Что вы хотите, так это получить следующее:
а здесь только папки (не репозитории и не подрепозитории/подмодули) внутри .
repo1/ # will be deleted when done
...
repo2/ # will be deleted when done
...
new_repo/
.git/
.gitignore
repo1/
(other files and folders)
repo2/
(other files and folders)
Подробные инструкции
Подготовьте старые репозитории. Это можно сделать с любым количеством объединяемых репозиториев, будь то 1, 2 или 100 репозиториев. В этом примере я просто сделаю два репозитория и . Чтобы подготовить их к слиянию в один внешний репозиторий, сохранив при этом их историю, нам нужно сначала поместить их содержимое в подкаталог с тем же именем, что и имена их репозиториев.
Итак, будем исходить из этого:
repo1/ .git/ .gitignore (other files and folders) repo2/ .git/ .gitignore (other files and folders)
к этому:
repo1/ .git/ repo1/ .gitignore (other files and folders) repo2/ .git/ repo2/ .gitignore (other files and folders)
Команды, которые нужно запустить для этого:
# 1. Fix up repo1 cd path/to/repo1 mkdir repo1 # move all non-hidden files and folders into `repo1/` mv * repo1/ # move all hidden files and folders into `repo1/` mv .* repo1/ # Now move the .git dir back to where it belongs, since it was moved by the # command just above mv repo1/.git . # commit all these changes into this repo git add -A git status git commit -m "Move all files & folders into a subdir" # 2. Fix up repo2 (same process as just above, except use `repo2` instead of # `repo1`) cd path/to/repo2 mkdir repo2 mv * repo2/ mv .* repo2/ mv repo2/.git . git add -A git status git commit -m "Move all files & folders into a subdir"
Создайте файл , если необходимо. Если этот репозиторий уже существует, ничего страшного, используйте его как есть и пропустите этот шаг. Если вам нужно создать это как совершенно новый репозиторий, вот как это сделать:
# Create `new_repo` cd path/to/parentdir_of_repo1_and_repo2 mkdir new_repo cd new_repo git init # (Optional, but recommended) rename the main branch from `master` to `main` git branch -m main # set your name and email for just this repo if you haven't done this # globally previously git config user.name "First Last" git config user.email firstlast@gmail.com # create an empty first commit to start a git history git commit --allow-empty -m "Initial empty commit"
Объединить наши фиксированные и
repo2
истории и содержимое репо в файлы .cd path/to/new_repo # -------------------------------------------------------------------------- # 1. Merge repo1, with all files and folders and git history, into new_repo # -------------------------------------------------------------------------- # Add repo1 as a local "remote" named `repo1` # - Note: this assumes that new_repo, repo1, and repo2 are all at the same # directory level and inside the same parent folder. If this is *not* the # case, no problem. Simply change `"../repo1"` below to the proper # relative *or* absolute path to that repo! Ex: `"path/to/repo1"`. git remote add repo1 "../repo1" # View all of your remotes. # - You'll now see `repo1` as a remote which points to the local "URL" # of "../repo1" git remote -v # Fetch all of repo1's files and history into new_repo's .git dir. # - Note that `repo1` here is the name of the remote alias that you just # added above. git fetch repo1 # View the new locally-stored, remote-tracking hidden branch that was just # created for you. # - `git branch -r` will now show a new hidden branch named `repo1/main` or # `repo1/master` or whatever your main branch was named there. git branch -r # Assuming that your new hidden branch that you just fetched is called # `repo1/main`, let's merge that into our currently-checked-out branch now. # - This merges repo1's files and folders and git history into new_repo. # - change `repo1/main` to `repo1/some_other_branch` if you want to merge in # `some_other_branch` instead. # - Immediately after running this command, you will now see a new folder, # `repo1`, with all of its files and folders within it, created inside # the `new_repo` directory. git merge --allow-unrelated-histories repo1/main # Since you have independent sub-folders prepared inside repo1 and repo2, # there will be no conflicts whatsoever. When the git merge editor opens # up, just save and close it to complete the merge. Optionally, add any # comments you wish before saving and closing it. # Now, remove the remote named "repo1", since we no longer need it. git remote remove repo1 # View all remotes to ensure it is now gone git remote -v # See the new commit status. If `repo1` had 100 commits in it, for instance, # `git status` will now show this, since `new_repo` now has those 100 # commits plus this new merge commit in it: # # $ git status # On branch main # Your branch is ahead of 'origin/main' by 101 commits. # (use "git push" to publish your local commits) # # nothing to commit, working tree clean # git status # Push to a remote, if you have one configured git push # You may now optionally manually delete the original `repo1` since it is # merged into `new_repo`. Here's what that command would look like: # - WARNING: get this path correct or you'll delete a whole lot of stuff you # don't mean to! # # rm -r ../repo1 # # -------------------------------------------------------------------------- # 2. Merge repo2 in too. # - This is the exact same process as above, except we use `repo2` instead # of `repo1`. # -------------------------------------------------------------------------- git remote add repo2 "../repo2" git remote -v git fetch repo2 git branch -r git merge --allow-unrelated-histories repo2/main git remote remove repo2 git remote -v git status git push # Optional: `rm -r ../repo2`
(Необязательно) просмотрите новую объединенную историю внутри
new_repo
с :cd path/to/new_repo # Add this really great `git lg` alias from here: # https://coderwall.com/p/euwpig/a-better-git-log git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" # now view the new git history; it will cleanly show you the branch pattern, # and your new contents, including their branch lines git lg
Если ты бежишь
git lg
только после слияния, вот что вы можете увидеть, предполагая, чтоrepo1
в нем было всего 7 коммитов:$ git lg * 32c1d05 - (HEAD -> main, origin/main, origin/HEAD) Merge remote-tracking branch 'repo1/main' (x minutes ago) <Gabriel Staples> |\ | * eca4e6c - Move all files & folders into a subdir (x minutes ago) <Gabriel Staples> | * 92853ac - my repo1 commit message <Gabriel Staples> | * 07fe88e - my repo1 commit message <Gabriel Staples> | * f3b0847 - my repo1 commit message <Gabriel Staples> | * bcb0ca5 - my repo1 commit message <Gabriel Staples> | * 712ec94 - my repo1 commit message <Gabriel Staples> | * 1103fe1 - Initial commit (x days ago) <Gabriel Staples> * 47a663b - my new_repo commit message <Gabriel Staples> * 5d865d0 - my new_repo commit message <Gabriel Staples> * d47c4e1 - Initial commit (x hours ago) <Gabriel Staples>
Рекомендации
- Основная часть моих инструкций была получена здесь: https://blog.jdriven.com/2021/04/how-to-merge-multiple-git-repositories/
- это очень похожий ответ от @x-yuri
- Суперпользователь: Как скопировать с помощью cp, чтобы включить скрытые файлы, скрытые каталоги и их содержимое?
Смотрите также
Помимо ссылок выше, см.:
Выполните шаги для встраивания одного репо в другое репо, имея одну историю Git, объединяя обе истории Git.
- Клонируйте оба репозитория, которые вы хотите объединить.
git clone git@github.com: пользователь / parent-repo.git
git clone git@github.com: пользователь / child-repo.git
- Перейти в детское репо
cd child-repo /
- выполните команду ниже, замените путь
my/new/subdir
(3 случая) со структурой каталогов, где вы хотите иметь дочерний репозиторий.
git filter-branch --prune-empty --tree-filter 'if [! -e мой / новый /subdir ]; затем mkdir -p my/new/subdir git ls-tree - только для имени $GIT_COMMIT | xargs -I файлы mv файлы my / new / subdir fi '
- Перейти в родительское репо
cd../parent-repo/
- Добавить удаленное к родительскому репо, указав путь к дочернему репо
git remote добавить child-remote../child-repo/
- Получить детское репо
git fetch child-remote
- Слить истории
git merge --allow-unrelated-историй child-remote / master
Если вы сейчас проверите журнал git в родительском репо, он должен объединить коммиты дочернего репо. Вы также можете увидеть тег, указывающий из источника коммита.
Приведенная ниже статья помогла мне встроить одно хранилище в другое, создав одну историю Git, объединив обе истории Git.
http://ericlathrop.com/2014/01/combining-git-repositories/
Надеюсь это поможет. Удачного кодирования!
Я создал на основе ответа x-yuri , который использует filter-repo . С помощью репозиторий с некоторыми скриптамимоих скриптов вы можете легко переместить все ветки и теги в ваш новый репозиторий без конфликтов слияния, если вы укажете разные подкаталоги.
Существует еще один, еще более простой способ, просто разветвите вашу основную ветку локально, переместите все файлы в предпочтительную новую структуру пути и зафиксируйте ее. После этого добавьте старое репо как удаленное, как показано выше, и выполните слияние, как показано выше.
В отличие от всех других увиденных здесь решений, вы теперь закончили, так как все файлы уже перемещены.
У меня есть эта суть , которая помогает с этим.