关于Git子树:合并两个Git存储库而不破坏文件历史记录

Merge two Git repositories without breaking file history

我需要将两个Git存储库合并到一个全新的第三个存储库中。我发现了许多关于如何使用子树合并(例如JakubNar)来实现这一点的描述。BSKI关于如何合并两个Git存储库的答案?)遵循这些指示基本上是可行的,除了当我提交子树时,合并来自旧存储库的所有文件都被记录为新添加的文件。当我执行git log时,我可以从旧的存储库中看到提交历史,但是如果我执行git log ,它只显示该文件的一个提交-子树合并。从以上答案的评论来看,我并不是唯一一个看到这个问题的人,但我没有找到任何已发表的解决方案。

是否有任何方法可以合并存储库并保持单个文件历史完整?


事实证明,如果您只是简单地尝试将两个存储库粘合在一起,并使其看起来一直都是这样,而不是管理一个外部依赖关系,那么答案就简单多了。您只需将远程设备添加到旧的Repo中,将它们合并到新的主服务器中,将文件和文件夹移动到子目录中,提交移动,然后对所有其他Repo重复此操作。子模块、子树合并和花哨的钢筋旨在解决稍有不同的问题,不适合我正在尝试的工作。

下面是将两个存储库粘合在一起的PowerShell脚本示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 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"

显然,如果您愿意的话,您可以将旧的_b合并为旧的_a(这将成为新的组合回购),修改脚本以适应。

如果您还希望引入正在进行的功能分支,请使用以下命令:

1
2
3
# 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的文件将结束在一个子目录中。

  • 将第二个repo添加为远程:

    1
    2
    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
  • 确保您已下载所有secondrepo的提交:

    1
    git fetch secondrepo
  • 从第二个回购分支机构创建本地分支机构:

    1
    git branch branchfromsecondrepo secondrepo/master
  • 将其所有文件移动到子目录中:

    1
    2
    3
    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/"
  • 将第二个分行合并为第一个回购主分行:

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


    请看一下使用

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

    把他们早期的两个历史联系起来。

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

    1
    git filter-branch --index-filter

    使用日志时,请确保使用

    1
    git log -CC

    这样,您就可以在路径中找到文件的任何移动。


    我把这个解决方案从@flimm变成了这样的git alias(添加到我的~/.gitconfig):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [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"


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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"
    }

    如何使用:

    1
    2
    cd current/package
    git-add-repo https://github.com/example/example dir/to/save

    利润!


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

  • 在GitHub中创建新的存储库。

    enter image description here

  • 下载新创建的repo并添加旧的远程存储库。

    1
    2
    3
    4
    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
  • 从旧repo中获取所有文件,以便创建新的分支。

    1
    2
    git fetch OldRepo
    git branch -a

    enter image description here

  • 在主分支中,进行合并以将旧回购与新创建的回购合并。

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

    enter image description here

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

  • 最后,您可以从组合的repos上传文件,并从github安全删除oldrepo。

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


    按照步骤将一个回购嵌入到另一个回购中,通过合并两个git历史记录来拥有一个git历史。

  • 克隆要合并的两个repo。
  • git clone [email protected]:user/parent-repo.git

    git clone [email protected]:user/child-repo.git

  • 转到儿童回购
  • cd child-repo/

  • 运行下面的命令,将路径EDOCX1(出现3次)替换为您想要子repo的目录结构。
  • 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'

  • 转到父回购
  • cd ../parent-repo/

  • 添加远程到父repo,指向子repo的路径
  • git remote add child-remote ../child-repo/

  • 把孩子的报告拿来
  • git fetch child-remote

  • 合并历史记录
  • git merge --allow-unrelated-histories child-remote/master

    如果现在检查父repo中的git日志,则应该合并子repo提交。您还可以看到从提交源指示的标记。

    下面的文章帮助我将一个回购嵌入到另一个回购中,通过合并两个git历史记录,获得了一个git历史。

    http://ericlathrop.com/2014/01/combing-git-stores/

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