I need to merge two Git repositories into a brand new, third repository. I've found many descriptions of how to do this using a subtree merge (for example Jakub Narębski's answer on How do you merge two Git repositories?) and following those instructions mostly works, except that when I commit the subtree merge all of the files from the old repositories are recorded as new added files. I can see the commit history from the old repositories when I do git log, but if I do git log <file> it shows only one commit for that file - the subtree merge. Judging from the comments on the above answer, I'm not alone in seeing this problem but I've found no published solutions for it.

有没有办法合并存储库,并保持单个文件历史完整?


当前回答

It turns out that the answer is much simpler if you're simply trying to glue two repositories together and make it look like it was that way all along rather than manage an external dependency. You simply need to add remotes to your old repos, merge them to your new master, move the files and folders to a subdirectory, commit the move, and repeat for all additional repos. Submodules, subtree merges, and fancy rebases are intended to solve a slightly different problem and aren't suitable for what I was trying to do.

下面是一个示例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正确地排列所有内容。

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

其他回答

按照以下步骤,将一个repo嵌入到另一个repo中,通过合并两个git历史,从而拥有一个git历史。

克隆你想合并的两个回购。

Git克隆git@github.com:user/parent-repo.git Git克隆git@github.com:user/child-repo.git

转到child repo

cd child-repo /

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

Git filter-branch - pruneempty -tree filter ' 如果[!-e my/new/subdir];然后 Mkdir -p my/new/subdir git ls-tree——name-only $GIT_COMMIT | xargs -I files mv files my/new/subdir fi '

去父回购

cd . . / parent-repo /

为父repo添加一个远程,指向子repo的路径

Git远程添加child-remote ../child-repo/

取回子repo

Git获取子远程

合并历史

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

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

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

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

希望这能有所帮助。 编码快乐!

我把解决方案从@Flimm this变成了一个git别名,就像这样(添加到我的~/.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"

It turns out that the answer is much simpler if you're simply trying to glue two repositories together and make it look like it was that way all along rather than manage an external dependency. You simply need to add remotes to your old repos, merge them to your new master, move the files and folders to a subdirectory, commit the move, and repeat for all additional repos. Submodules, subtree merges, and fancy rebases are intended to solve a slightly different problem and aren't suitable for what I was trying to do.

下面是一个示例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正确地排列所有内容。

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

假设你想要合并存储库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

这里有更多。

请看一下使用方法

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

将他们早年的两段历史联系起来。

如果有重叠的路径,用

git filter-branch --index-filter

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

git log -CC

这样,您将发现路径中文件的任何移动。