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

时间:2012-10-23 23:41:43

标签: git git-subtree

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

有没有办法合并存储库并保留单个文件历史记录?

8 个答案:

答案 0 :(得分:246)

事实证明,如果您只是尝试将两个存储库粘合在一起并使其看起来像是一直这样,而不是管理外部依赖,那么答案就会简单得多。您只需将遥控器添加到旧的存储库,将它们合并到新的主存储器,将文件和文件夹移动到子目录,提交移动,然后重复所有其他存储库。子模块,子树合并和花哨的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正确排列所有内容。

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

答案 1 :(得分:130)

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

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

    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的主分支中:

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    
  6. 您的存储库将有多个root提交,但这不会造成问题。

答案 2 :(得分:9)

假设您要将存储库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-repofilter-branchdiscouraged)。

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

更多here

答案 3 :(得分:8)

请看看使用

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

将他们生命中的两个历史联系起来。

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

进行修复
git filter-branch --index-filter

使用日志时,请确保使用

“更难找到副本”
git log -CC

通过这种方式,您可以在路径中找到任何文件移动。

答案 4 :(得分:5)

我将solution从@Flimm转为FragmentManager#findFragmentByTag,就像这样(添加到我的 FragmentManager manager = getSupportFragmentManager(); FragmentTransaction transaction = manager.beginTransaction(); Fragment oldFragment = manager.findFragmentByTag(tag); if (oldFragment != null) transaction.replace(R.id.fragment_container, oldFragment, tag); else transaction.replace(R.id.fragment_container, newInstanceOfFragment, tag); transaction.commit(); ):

git alias

答案 5 :(得分: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

利润!

答案 6 :(得分:2)

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

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

    enter image description here

  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
    

    enter image description here

  4. 在master分支中,进行合并以将旧仓库与新仓库合并。

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

    enter image description here

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

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

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

答案 7 :(得分:0)

按照合并两个git历史记录的步骤,将一个repo嵌入另一个repo,并具有一个git历史记录。

  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我的/新的/子目录                    git ls-tree --name-only $ GIT_COMMIT | xargs -I文件mv文件我/新/子目录                fi'

  1. 转到上级仓库
  

cd ../ parent-repo /

  1. 将远程添加到父存储库,指向子存储库的路径
  

git remote add child-remote ../ child-repo /

  1. 获取子存储库
  

git获取儿童远程

  1. 合并历史
  

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

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

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

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

希望这会有所帮助。 祝您编码愉快!