在不破坏文件历史的情况下合并两个Git存储库

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

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

125912 次浏览

请看一下使用方法

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

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

如果有重叠的路径,用

git filter-branch --index-filter

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

git log -CC

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

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

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

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

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

  1. 添加第二个回购作为远程:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. Make sure that you've downloaded all of the secondrepo's commits:

    git fetch secondrepo
    
  3. Create a local branch from the second repo's branch:

    git branch branchfromsecondrepo secondrepo/master
    
  4. Move all its files into a subdirectory:

    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. Merge the second branch into the first repo's master branch:

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

Your repository will have more than one root commit, but that shouldn't pose a problem.

我把@Flimm this的解决方案变成了这样的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. 在Github中创建一个新的存储库。

    enter image description here < / p >

  2. 下载新创建的repo并添加旧的远程库。

    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. Fetch for all the files from the old repo so a new branch gets created.

    git fetch OldRepo
    git branch -a
    

    enter image description here < / p >

  4. 在主分支中,执行merge将旧的repo与新创建的repo合并。

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

    enter image description here < / p >

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

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

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

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

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

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

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

  1. 转到child repo

cd child-repo /

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

git filter-branch——prune-empty——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 ' < / p >

  1. 去父回购

cd . . / parent-repo /

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

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

  1. 取回子repo

Git获取子远程

  1. 合并历史

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

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

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

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

希望这对你有帮助。 编码快乐!< / p >

假设你想将存储库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

更多关于它在这里

我根据使用filter-repox-yuri的答案创建了一个带有脚本的存储库。使用我的脚本,你可以很容易地将所有分支和标签移动到你的新存储库中,如果你指定了不同的子dirs,不会发生合并冲突。