合并两个Git存储库而不破坏文件历史记录


226

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

有什么办法可以合并存储库并保持单个文件的历史记录不变?


我没有使用Git,但在Mercurial中,我首先进行了必要的转换,以修复要合并的存储库的文件路径,然后将一个存储库强制拉入目标以获取更改集,然后执行不同分支的合并。这是经过测试的并且有效;)也许这也有助于找到Git的解决方案...与子树合并方法相比,我猜转换步骤有所不同,在这里重写历史记录而不是仅映射路径(如果我理解正确)。然后,这可以确保顺利合并,而无需对文件路径进行任何特殊处理。
Lucero 2012年

我也发现这个问题有帮助stackoverflow.com/questions/1683531/...
nacross

我提出了一个后续问题。可能是有趣:合并两个Git仓库,而保持主历史:stackoverflow.com/questions/42161910/...
迪米特里Dewaele

对我
有用

Answers:


269

事实证明,如果您只是试图将两个存储库粘合在一起并使之看起来一直都是这样,而不是管理外部依赖关系,那么答案就简单得多。您只需要向您的旧存储库添加遥控器,将其合并到新的主存储库,将文件和文件夹移动到子目录,提交移动,并对所有其他存储库重复上述操作。子模块,子树合并和精美的rebase旨在解决稍微不同的问题,不适合我尝试做的事情。

这是将两个存储库粘合在一起的示例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正确地排列了所有内容。

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


16
使用此解决方案git mv效果不佳。以后git log在移动的文件之一上使用a 时,只能从移动中获取提交。以前所有的历史都丢失了。这是因为git mv是真的git rm; git add,但一个步骤
mholm815

15
它与Git中的任何其他移动/重命名操作相同:在命令行中,您可以通过执行来获取所有历史记录git log --follow,或者所有GUI工具都可以自动为您执行此操作。据我所知,通过子树合并,您无法获取单个文件的历史记录,因此此方法更好。
埃里克·李

3
@EricLee old_b仓库被合并时,我遇到很多合并冲突。那是预期的吗?我收到了CONFLICT(重命名/删除)的信息
2014年

9
当我尝试“ dir -exclude old_a |%{git mv $ _。Name old_a}”时,我得到sh.exe”:dir:找不到命令,而sh.exe”:git:未找到命令。使用此功能:ls -I old_a | xargs -I'{}'git mv'{}'old_a /
George

5
这是的1(数字1)ls,是的大写“眼睛” xargs。感谢您的提示!
Dominique Vial

149

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

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

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

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


1
第2步对我不起作用:致命:无效的对象名称:“ secondrepo / master”。
基思

@Keith:确保已将第二个存储库添加为名为“ secondrepo”的远程存储,并且该存储库具有一个名为“ master”的分支(您可以使用命令查看远程存储库上的分支git remote show secondrepo
Flimm 2014年

我还必须进行提取以将其降低。在1-2之间,我做了git fetch secondrepo
sksamuel 2014年

@monkjack:我已经编辑了答案,包括一个git fetch步骤。以后随时自行编辑答案。
Flimm 2014年

4
@MartijnHeemels对于旧版本的Git,只需省略--allow-unrelated-histories。请参阅此回答帖子的历史记录。
Flimm

8

几年过去了,并且有一些基于投票的解决方案,但是我想分享我的观点,因为它有点不同,因为我想将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分支中,进行合并以将旧仓库与新仓库合并。

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

    在此处输入图片说明

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

  6. 最后,您可以从组合的仓库中上传文件,并从GitHub安全删除OldRepo。

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


1
这是对我保留git历史有效的唯一解决方案。不要忘记使用删除与旧仓库的远程链接git remote rm OldRepo
Harubiyori


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"

12
只是好奇:您是否真的经常需要别名?
帕克·考茨

1
不,我不记得,但永远都不记得要怎么做,因此别名只是我记住它的一种方式。
Fredrik Erlandsson

1
是的..但是请尝试更换计算机而忘记移动您的别名;)
quetzalcoatl

1
有什么价值$GIT_PREFIX
neowulf33

github.com/git/git/blob/… 通过从原始当前目录运行'git rev-parse --show-prefix'将'GIT_PREFIX'设置为返回。参见linkgit:git-rev-parse [1]。
弗雷德里克·埃兰森

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

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

利润!


我使用的是zsh而不是bash和git v2.13.0。无论我做了什么,我都无法git filter-branch --index-filter上班。通常,我会收到一条错误消息,指出.new索引文件不存在。那会响吗?
帕特里克·比尔德

@PatrickBeard我不知道zsh,您可以git-add-repo.sh使用上面的功能创建单独的文件,在文件末尾放置此行git-add-repo "$@"。之后,你可以用它从zsh的像cd current/git/packagebash path/to/git-add-repo.sh https://github.com/example/example dir/to/save
安德烈Izman

这里讨论了该问题: stackoverflow.com/questions/7798142/… mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"有时会失败,因此您必须添加一个if test
帕特里克·比尔德

1
我不会使用这种方法!我天真地逐字尝试了该脚本(我只能为此怪罪自己),它破坏了我本地的git repo。历史记录看起来几乎是正确的,但是执行git push到Github导致可怕的“ RPC失败; curl 55 SSL_write()返回SYSCALL,errno = 32”错误。我试图修复它,但是它损坏得无法修复。我最终不得不在新的本地仓库中重新构建事物。
梅森

@MasonFreed此脚本创建了一个包含两个仓库的新git历史记录,因此无法将其推送到旧仓库,它需要创建一个新
仓库

2

遵循将两个git历史记录合并在一起而具有一个git历史记录的步骤,将一个repo嵌入另一个仓库中。

  1. 克隆两个要合并的仓库。

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

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

  1. 前往儿童回购

cd child-repo /

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

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文件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-histories子远程/母版

如果现在检查父仓库中的git日志,则应该合并子仓库提交。您还可以查看来自提交源的标记。

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

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

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


第3步对我来说失败,出现语法错误。分号不见了。修复git filter-branch --prune-empty --tree-filter ' if [ ! -e my/new/subdir ]; then mkdir -p my/new/subdir; git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files my/new/subdir; fi'
Yuri L

1

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

这里更多。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.