2012-10-23 59 views
160

我需要将两个Git存储库合并到一个全新的第三个存储库中。我发现了很多关于如何使用子树合并(例如How do you merge two Git repositories?上的Jakub Narębski's answer)执行此操作的说明,并且遵循这些说明的主要工作方式,除了当我提交子树合并时,旧存储库中的所有文件都记录为新添加的文件。当我做git log时,我可以看到来自旧存储库的提交历史记录,但如果我做了git log <file>,它只显示该文件的一个提交 - 子树合并。从上述回答的评论来看,我并不孤单看到这个问题,但我没有发现任何发布的解决方案。在不破坏文件历史的情况下合并两个Git存储库

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

+0

我没有使用Git,但是在Mercurial中,如果需要修改要合并的Repos的文件路径,然后强制将一个回购库导入目标以获取更改集,则首先执行转换,然后然后做不同分支的合并。这是测试和工作;)也许这有助于找到Git的解决方案以及...与子树合并方法相比,我猜测转换步骤是不同的,其中历史被重写,而不是仅仅映射路径(如果我理解正确)。这样可以确保顺利合并,而无需任何特殊的文件路径处理。 – Lucero

+0

我也发现这个问题很有帮助http://stackoverflow.com/questions/1683531/how-to-import-existing-git-repository-into-another – nacross

+0

我创建了一个后续问题。可能很有趣:合并两个Git仓库并保留主记录: http://stackoverflow.com/questions/42161910/merge-two-git-repositories-and-keep-the-master-history –

回答

205

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

下面是一个例子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正确地排列所有东西。

我写了一个稍微更详细的解释here

+10

这个使用'git mv'的解决方案不能很好地工作。当你稍后在一个移动的文件上使用'git log'时,你只能从移动中获得提交。所有以前的历史都会丢失。这是因为'git mv'确实是'git rm; git add'但[在一个步骤](http://stackoverflow.com/a/1094392/959352)。 – mholm815

+11

与Git中的其他移动/重命名操作相同:从命令行可以通过执行'git log --follow'获得所有历史记录,或者所有GUI工具都会自动为您执行此操作。通过子树合并,您**无法获取单个文件的历史记录,据我所知,所以此方法更好。 –

+2

@EricLee当old_b仓库被合并时,我会遇到很多合并冲突。这是预期的吗?我得到CONFLICT(重命名/删除) – Jon

8

请看看使用

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

两个历史早在他们的生活联系起来。

如果您有重叠的路径,解决这些问题了

git filter-branch --index-filter 

当您使用日志,保证你“找到副本难”与

git log -CC 

这样你会发现任何动作文件在路径中。

105

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

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

    cd firstgitrepo/ 
    git remote add secondrepo [email protected]: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 
    

你的仓库将有超过一个根犯,但这不应该成为问题。

+1

第2步对我不起作用:致命的:不是有效的对象名称:'secondrepo/master'。 – Keith

+0

@Keith:确保你已经添加了第二个repo作为名为“secondrepo”的远程,并且该repo有一个名为“master”的分支(可以使用命令'git remote show secondrepo'在远程回购上查看分支。 ) – Flimm

+0

我必须做一个抓取才能把它打下来。在1和2之间,我做了git fetch secondrepo – monkjack

5

我把solution从@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" 
+6

只是好奇:你真的这样做经常足以需要一个别名? –

+1

不,我不记得怎么做,所以别名只是让我记住它的一种方式。 –

+0

是的..但尝试更换电脑,忘记移动别名;) – quetzalcoatl

2

此功能将克隆远程回购到本地回购目录:

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 

利润!

+0

我使用的是zsh而不是bash,以及git的v2.13.0。无论我尝试了什么,我都无法让'git filter-branch --index-filter'工作。通常我会收到一条错误消息,指出.new索引文件不存在。那响铃吗? –

+0

@PatrickBeard我不知道zsh,你可以用上面的函数创建分离文件'git-add-repo.sh',在文件末尾放上'git-add-repo'$ @'''。之后,你可以像使用'cd current/git/package'和'bash path/to/git-add-repo.sh https://github.com/example/example dir/to/save'一样从zsh中使用它。 –

+0

The问题在这里讨论:https://stackoverflow.com/questions/7798142/error-combining-git-repositories-into-subdirs'mv“$ GIT_INDEX_FILE.new”“$ GIT_INDEX_FILE”'有时失败,所以你必须添加一个'如果测试'。 –

相关问题