261

我需要将两个 Git 存储库合并到一个全新的第三个存储库中。我发现了许多关于如何使用子树合并来执行此操作的描述(例如Jakub Narębski关于如何合并两个 Git 存储库的答案?)并且遵循这些说明大部分都有效,除了当我提交子树时合并所有文件来自旧存储库的文件被记录为新添加的文件。当我这样做时,我可以从旧存储库中看到提交历史记录git log,但如果这样做,git log <file>它只会显示该文件的一个提交 - 子树合并。从对上述答案的评论来看,我并不是唯一一个看到这个问题的人,但我没有找到任何已发布的解决方案。

有什么方法可以合并存储库并使单个文件历史记录完好无损?

4

9 回答 9

306

事实证明,如果您只是尝试将两个存储库粘合在一起并使其看起来一直都是这样,而不是管理外部依赖项,那么答案会简单得多。您只需将遥控器添加到旧存储库,将它们合并到新主库,将文件和文件夹移动到子目录,提交移动,然后重复所有其他存储库。子模块、子树合并和花哨的变基旨在解决稍微不同的问题,并不适合我想要做的事情。

这是一个将两个存储库粘合在一起的示例 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
# (the '--fetch' (or '-f') option will make git immediately fetch commits to the local repo after adding the remote)
git remote add --fetch 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 正确排列所有内容。

我在这里写了一个更详细的解释。

于 2013-01-22T23:57:18.230 回答
168

这是一种不会重写任何历史记录的方法,因此所有提交 ID 都将保持有效。最终结果是第二个 repo 的文件最终会放在一个子目录中。

  1. 将第二个 repo 添加为远程:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. 确保您已下载所有 secondrepo 的提交:

    git fetch secondrepo
    
  3. 从第二个 repo 的分支创建一个本地分支:

    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. 将第二个分支合并到第一个 repo 的 master 分支:

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

您的存储库将有多个根提交,但这不应该造成问题。

于 2014-01-07T14:48:18.637 回答
18

几年过去了,有一些基于良好的投票解决方案,但我想分享我的,因为它有点不同,因为我想将 2 个远程存储库合并到一个新的存储库中,而不删除以前存储库中的历史记录。

  1. 在 Github 中创建一个新的存储库。

    在此处输入图像描述

  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
    

    在此处输入图像描述

  4. 在 master 分支中,进行合并以将旧的 repo 与新创建的 repo 合并。

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

    在此处输入图像描述

  5. 创建一个新文件夹来存储从 OldRepo 添加的所有新创建的内容,并将其文件移动到这个新文件夹中。

  6. 最后,您可以从合并的 repos 上传文件并从 GitHub 安全地删除 OldRepo。

希望这对任何处理合并远程存储库的人有用。

于 2018-10-22T15:42:35.070 回答
18

假设您要将存储库合并ab(我假设它们彼此并排):

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鼓励)。

合并 2 个大存储库的示例,将其中一个放入子目录:https ://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

更多关于它的信息

于 2020-05-30T01:05:26.767 回答
7

请看看使用

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

在他们生命的早期将两个历史联系起来。

如果您有重叠的路径,请使用

git filter-branch --index-filter

当您使用日志时,请确保您“更难找到副本”

git log -CC

这样,您将在路径中找到文件的任何移动。

于 2012-10-24T01:56:22.507 回答
7

我将@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"
于 2016-09-06T09:24:11.447 回答
3

此函数会将远程仓库克隆到本地仓库目录中:

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

注意。此脚本可以重写提交,但会保存所有作者和日期,这意味着新提交将有另一个哈希值,如果您尝试将更改推送到远程服务器,它只能使用强制密钥,它也会重写服务器上的提交。所以请在启动前做好备份。

利润!

于 2017-04-11T08:24:06.847 回答
2

按照步骤将一个 repo 嵌入到另一个 repo 中,通过合并两个 git 历史记录来拥有一个单一的 git 历史记录。

  1. 克隆您要合并的两个存储库。

git clone git@github.com:user/parent-repo.git

git clone git@github.com:user/child-repo.git

  1. 转到儿童回购

cd子回购/

  1. 运行以下命令,将路径my/new/subdir(3 次出现)替换为您想要拥有子存储库的目录结构。

git filter-branch --prune-empty --tree-filter ' 如果 [ !-e 我的/新的/子目录]; 然后 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 远程添加子远程 ../child-repo/

  1. 获取子仓库

git 获取子远程

  1. 合并历史

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

如果您现在检查父 repo 中的 git log,它应该合并子 repo 提交。您还可以看到来自提交源的标记。

下面的文章帮助我将一个 repo 嵌入到另一个 repo 中,通过合并两个 git 历史来拥有一个单一的 git 历史。

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

希望这可以帮助。快乐编码!

于 2018-12-06T11:30:36.820 回答
0

还有另一种更简单的方法,只需在本地分支主分支并将所有文件移动到您喜欢的新路径结构中并提交。之后,将您的旧仓库添加为远程,如上所示进行合并。

与这里看到的所有其他解决方案不同,您现在已经完成,因为现在所有文件都已移动。

我有这方面的要点

于 2022-02-21T18:02:39.370 回答