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

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

есть ли способ объединить репозитории и оставить историю отдельных файлов нетронутой?

5 ответов


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

вот пример сценария 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
dir > deleteme.txt
git add .
git commit -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

# Clean up our dummy file because we don't need it any more
git rm .\deleteme.txt
git commit -m "Clean up initial file"

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

Я написал немного более подробное объяснение здесь.


вот способ, который не переписывает историю, поэтому все идентификаторы фиксации останутся действительными. Конечным результатом является то, что файлы второго РЕПО окажутся в подкаталоге.

  1. добавить второе РЕПО в качестве удаленного:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. убедитесь, что вы загрузили все коммиты secondrepo:

    git fetch secondrepo
    
  3. создайте локальную ветвь из ветви второго РЕПО:

    git branch branchfromsecondrepo secondrepo/master
    
  4. переместить все его файлы в подкаталог:

    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/"
    
  5. объединить вторую ветвь в главную ветвь первого РЕПО:

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

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


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

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

связать две истории в начале их жизни.

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

git filter-branch --index-filter

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

git log -CC

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


Я повернул решение из @Flimm это в git alias вот так (добавлено в my ~/.gitconfig):

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo ; \
  git fetch newRepo; \
  git branch \"\" newRepo/master; \
  git checkout \"\"; \
  mkdir -vp \"${GIT_PREFIX}\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"\"; \
  git branch -D \"\"; git remote remove newRepo; \
}; \
mergeRepo"

эта функция клонирует удаленное РЕПО в локальное РЕПО dir:

function git-add-repo
{
    repo=""
    dir="$(echo "" | 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

профит!