Слияние двух хранилищ Git без нарушения истории файлов

Мне нужно объединить два хранилища Git в новый, третий репозиторий. Я нашел много описаний того, как это сделать, используя слияние поддерева (например Ответ Jakub Narębski на Как вы сливаете два хранилища Git?) и следуя этим инструкциям, в основном работает, за исключением того, что при компиляции поддерева все файлы из старых репозиториев записываются как новые добавленные файлы. Я могу увидеть историю фиксации из старых репозиториев, когда я делаю git log, но если я делаю git log <file>, она показывает только одну фиксацию для этого файла - слияние поддерева. Судя по комментариям к вышеуказанному ответу, я не одинок в том, что вижу эту проблему, но я не нашел для нее никаких опубликованных решений.

Есть ли способ слияния репозиториев и оставить отдельную историю файлов неповрежденной?

Ответ 1

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

Вот пример скрипта 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 правильно выстраивать все.

Я написал чуть более подробное объяснение здесь.

Ответ 2

Вот способ, который не переписывает какую-либо историю, поэтому все идентификаторы фиксации остаются действительными. Конечным результатом является то, что вторые файлы repo попадут в подкаталог.

  • Добавьте второе репо как удаленное:

    cd firstgitrepo/
    git remote add secondrepo [email protected]: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
    

В вашем репозитории будет более одного коммита root, но это не должно представлять проблемы.

Ответ 3

пожалуйста, ознакомьтесь с использованием

git rebase --root --preserve-merges --onto

чтобы связать две истории на ранней стадии своей жизни.

Если у вас есть пути, которые перекрываются, исправьте их с помощью

git filter-branch --index-filter

когда вы используете журнал, убедитесь, что вы "находите копии сложнее" с помощью

git log -CC

таким образом вы найдете любые перемещения файлов в пути.

Ответ 4

Я превратил решение из @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"

Ответ 5

Эта функция будет клонировать удаленное репо в локальный репозиторий:

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

Profit!

Ответ 6

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

  1. Создайте новый репозиторий в Github.

    enter image description here

  2. Загрузите вновь созданное репо и добавьте старый удаленный репозиторий.

    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
    
  3. Извлеките все файлы из старого репо, чтобы создать новую ветку.

    git fetch OldRepo
    git branch -a
    

    enter image description here

  4. В основной ветке выполните слияние, чтобы объединить старое репо с вновь созданным.

    git merge remotes/OldRepo/master --allow-unrelated-histories
    

    enter image description here

  5. Создайте новую папку для хранения всего нового созданного контента, который был добавлен из OldRepo, и переместите его файлы в эту новую папку.

  6. Наконец, вы можете загружать файлы из комбинированных репозиториев и безопасно удалять OldRepo из GitHub.

Надеюсь, это может быть полезно для всех, кто имеет дело с объединением удаленных репозиториев.

Ответ 7

Следуйте инструкциям по встраиванию одного репо в другое репо, имея одну историю Git, объединив обе гит-истории.

  1. Выполните клонирование обоих репозиториев, которые вы хотите объединить.

git clone [email protected]: user/parent-repo.git

git clone [email protected]: user/child-repo.git

  1. Перейти к дочерней репо

cd child-repo/

  1. выполните команду ниже, замените путь my/new/subdir (3 вхождения) на структуру каталогов, где вы хотите иметь дочернее репо.

git filter-branch - -p rune -e mpty --tree-filter ', если [! -e my/new/subdir]; то mkdir -p my/new/subdir git ls-tree --name-only $ GIT_COMMIT | xargs -I файлы mv файлы my/new/subdir fi '

  1. Перейти к родительскому репо

cd../parent-repo/

  1. Добавление удаленного родительского репо, указывающего путь к дочернему репо

git remote add child-remote../child-repo/

  1. Получить дочернее репо

git fetch child-remote

  1. Объединить истории

git merge --allow-unrelated-history child-remote/master

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

Ниже статья помогла мне внедрить одно репо в другое репо, имея одну единственную историю Git, объединив обе гит-истории.

http://ericlathrop.com/2014/01/combining-git-repositories/

Надеюсь это поможет. Счастливое кодирование!