在子目录中合并git存储库

时间:2011-06-21 13:41:13

标签: git

我想将我工作的git存储库中的远程git存储库合并为它的子目录。我希望生成的存储库包含两个存储库的合并历史记录,并且合并存储库的每个文件都保留其在远程存储库中的历史记录。我尝试使用How to use the subtree merge strategy中提到的子树策略,但是在遵循该过程之后,尽管生成的存储库确实包含了两个存储库的合并历史记录,但来自远程存储库的单个文件仍未保留其历史记录(`任何一个git log'只显示一条消息“Merged branch ...”。)。

此外,我不想使用子模块,因为我不希望这两个组合的git存储库再次分开。

是否可以将远程git存储库合并为另一个存储库作为子目录,其中来自远程存储库的单个文件保留其历史记录?

非常感谢您的帮助。

编辑: 我目前正在尝试使用git filter-branch重写合并的存储库历史记录的解决方案。它似乎确实有效,但我需要再测试一下。我将回来报告我的发现。

编辑2: 希望我能让自己更清楚一些,我使用git的子树策略提供了确切的命令,这导致了远程存储库文件的历史记录明显丢失。 设A是我正在使用的git repo和B git repo我想将它作为它的子目录合并到A中。它做了以下事情:

git remote add -f B <url-of-B>
git merge -s ours --no-commit B/master
git read-tree --prefix=subdir/Iwant/to/put/B/in/ -u B/master
git commit -m "Merge B as subdirectory in subdir/Iwant/to/put/B/in."

在这些命令之后进入目录subdir / Iwant / to / put / B / in,我看到B的所有文件,但是其中任何一个上的git log只显示提交消息“Merge B as subirectory在subdir / Iwant / to / put / B / in。“它们在B中的文件历史记录丢失了。

似乎工作(因为我是git的初学者,我可能错了)如下:

git remote add -f B <url-of-B>
git checkout -b B_branch B/master  # make a local branch following B's master
git filter-branch --index-filter \ 
   'git ls-files -s | sed "s-\t\"*-&subdir/Iwant/to/put/B/in/-" |
        GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD 
git checkout master
git merge B_branch

上面的filter-branch命令来自git help filter-branch,我只更改了子目录路径。

8 个答案:

答案 0 :(得分:49)

git-subtree是一个脚本,专门用于将多个存储库合并为一个同时保留历史记录(和/或拆分子树历史记录,尽管这似乎与此问题无关)的用例。它作为git树since release 1.7.11的一部分分发。

要将修订版<repo>的存储库<rev>合并为子目录<prefix>,请使用git subtree add,如下所示:

git subtree add -P <prefix> <repo> <rev>

git-subtree以更加用户友好的方式实现subtree merge strategy

答案 1 :(得分:29)

在对正在发生的事情进行更全面的解释之后,我想我理解它,无论如何在底层我有一个解决方法。具体来说,我相信正在发生的事情是重命名检测被子树与--prefix合并所愚弄。这是我的测试用例:

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA
cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB
cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
git read-tree --prefix=bdir -u B/master
git commit -m "subtree merge B into bdir"
cd bdir
echo BBB>>B
git commit -a -m BBB

我们使用几个提交来创建git目录a和b。我们做了一个子树合并,然后我们做了 新子树中的最终提交。

运行gitk(在z / a中)显示历史记录确实出现,我们可以看到它。正在运行git log表示会显示历史记录。但是,查看特定文件存在问题:git log bdir/B

嗯,我们可以玩一招。我们可以使用--follow查看特定文件的预重命名历史记录。 git log --follow -- B。这很好,但不是很好,因为它无法将合并前的历史与合并后的链接相关联。

我尝试过使用-M和-C,但我无法让它跟随一个特定的文件。

所以,我觉得解决方案是告诉git将在子树合并中发生的重命名。不幸的是git-read-tree对于子树合并非常挑剔,所以我们必须通过一个临时目录,但在我们提交之前就可以消失了。之后,我们可以看到完整的历史。

首先,创建一个“A”存储库并进行一些提交:

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA

其次,创建一个“B”存储库并进行一些提交:

cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB

使这项工作成功的诀窍:强制Git通过创建子目录并将内容移入其中来识别重命名。

mkdir bdir
git mv B bdir
git commit -a -m bdir-rename

返回存储库“A”并获取并合并“B”的内容:

cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
# According to Alex Brown and pjvandehaar, newer versions of git need --allow-unrelated-histories
# git merge -s ours --allow-unrelated-histories --no-commit B/master
git read-tree --prefix= -u B/master
git commit -m "subtree merge B into bdir"

表明他们现在已合并:

cd bdir
echo BBB>>B
git commit -a -m BBB

要证明完整的历史记录保留在连接链中:

git log --follow B

我们在完成此操作后得到了历史记录,但问题是,如果您实际上保留了旧的“b”回购并偶尔从中进行合并(假设它实际上是第三方单独维护的回购),那么您就遇到了麻烦第三方不会进行重命名。您必须尝试将新更改合并到您的b版本中并重命名,我担心这不会顺利进行。但如果b消失,你就赢了。

答案 2 :(得分:5)

如果你真的想把东西缝合在一起,那就抬头嫁接吧。您还应该使用git rebase --preserve-merges --onto。还有一个选项可以保留提交者信息的作者日期。

答案 3 :(得分:4)

我想

  1. 保留线性历史记录而不进行显式合并,并且
  2. 使合并后的存储库的文件看起来总是存在于子目录中,并且副作用是使git log -- file在没有--follow的情况下工作。

第1步:在源存储库中重写历史记录,以使其看起来所有文件始终都存在于子目录下。

为重写的历史记录创建一个临时分支。

git checkout -b tmp_subdir

然后使用How can I rewrite history so that all files, except the ones I already moved, are in a subdirectory?中所述的git filter-branch

git filter-branch --prune-empty --tree-filter '
if [ ! -e foo/bar ]; then
    mkdir -p foo/bar
    git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files foo/bar
fi'

第2步:切换到目标存储库。在源存储库中将源存储库添加为远程存储库并获取其内容。

git remote add sourcerepo .../path/to/sourcerepo
git fetch sourcerepo

步骤3 :使用merge --onto将重写后的源存储库的提交添加到目标存储库的顶部。

git rebase --preserve-merges --onto master --root sourcerepo/tmp_subdir

您可以查看日志,以确保它确实满足您的需求。

git log --stat

第4步:重新设置基准之后,您将处于“分离头”状态。您可以快速掌握新的知识点。

git checkout -b tmp_merged
git checkout master
git merge tmp_merged
git branch -d tmp_merged

步骤5 :最后进行一些清理:删除临时遥控器。

git remote rm sourcerepo

答案 4 :(得分:3)

您是否尝试将额外的存储库添加为git子模块?它不会将历史记录与包含的存储库合并,事实上,它将是一个独立的存储库。

我提到它,因为你没有。

答案 5 :(得分:3)

我发现以下解决方案对我来说是可行的。首先,我进入项目B,创建一个新分支,其中所有文件都将被移动到新的子目录。然后我将这个新分支推到原点。接下来我去项目A,添加并获取B的远程,然后我签出移动的分支,我回到主和合并:

# in local copy of project B
git checkout -b prepare_move
mkdir subdir
git mv <files_to_move> subdir/
git commit -m 'move files to subdir'
git push origin prepare_move

# in local copy of project A
git remote add -f B_origin <remote-url>
git checkout -b from_B B_origin/prepare_move
git checkout master
git merge from_B

如果我转到子目录subdir,我可以使用git log --follow并且仍然有历史记录。

我不是一个git专家,所以我不能评论这是否是一个特别好的解决方案,或者它是否有警告,但到目前为止似乎一切都很好。

答案 6 :(得分:0)

假设您要将存储库a合并到b中(假设它们并排放置):

cd a
git filter-repo --to-subdirectory-filter a
cd ..
cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

为此,您需要安装git-filter-repofilter-branchdiscouraged)。

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

更多here

答案 7 :(得分:0)

类似于hfs的answer我想

  • 保留线性历史记录而无需显式合并和
  • 使合并存储库的文件看起来总是存在于子目录中,并且副作用是使git log -- file在没有--follow的情况下工作。

但是,我选择了更现代的filter-repo(假设new存储库存在并且已签出):

git clone git@host/repo/old.git
cd old
git checkout -b tmp_subdir
git filter-repo --to-subdirectory-filter old

cd ../new
git remote add old ../old
git fetch old
git rebase --rebase-merges --onto main --root old/tmp_subdir --committer-date-is-author-date

如果您想尝试使用--merge -s recursive -X theirs版本解决它,则可能需要(手动)解决冲突或将rebase命令更改为包括theirs

git rebase --rebase-merges --onto main --root old/tmp_subdir --committer-
date-is-author-date --merge -s recursive -X theirs

您最终遇到一个分离的HEAD,因此创建一个新分支并将其合并到main 请注意,现代存储库不应使用“主”分支,而应使用“主”

branch for a more inclusive language.
git checkout -b old_merge
git checkout main
git merge old_merge

清理

git branch -d old_merge
git remote rm old