在子目录中合并git存储库

chr*_*osc 72 git

我想在我的工作git存储库中合并一个远程git存储库作为它的子目录.我希望生成的存储库包含两个存储库的合并历史记录,并且合并存储库的每个文件都保留其在远程存储库中的历史记录.我尝试使用如何使用子树合并策略中提到的子树策略,但是在遵循该过程之后,尽管生成的存储库确实包含两个存储库的合并历史记录,但来自远程存储库的单个文件尚未保留其历史记录(任何一个'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."
Run Code Online (Sandbox Code Playgroud)

在这些命令进入目录subdir/Iwant/to/put/B/in后,我看到B的所有文件,但是git log在其中任何一个文件中只显示提交消息"将子目录合并为子目录/ Iwant/to/put/B /中".它们在B中的文件历史记录丢失了.

什么似乎工作(因为我在混帐初学者我可能是错的)如下:

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
Run Code Online (Sandbox Code Playgroud)

上面的filter-branch命令取自git help filter-branch,我只更改了subdir路径.

kyn*_*nan 55

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

要将<repo>版本的存储库合并<rev>为子目录<prefix>,请使用git subtree add以下命令:

git subtree add -P <prefix> <repo> <rev>
Run Code Online (Sandbox Code Playgroud)

git-subtree 以更加用户友好的方式实现子树合并策略.


Set*_*son 31

在对正在发生的事情进行更全面的解释之后,我想我理解它,无论如何在底层我有一个解决方法.具体来说,我相信正在发生的事情是重命名检测被子树与--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
Run Code Online (Sandbox Code Playgroud)

我们使用几个提交来创建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
Run Code Online (Sandbox Code Playgroud)

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

cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB
Run Code Online (Sandbox Code Playgroud)

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

mkdir bdir
git mv B bdir
git commit -a -m bdir-rename
Run Code Online (Sandbox Code Playgroud)

返回存储库"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"
Run Code Online (Sandbox Code Playgroud)

为了表明他们现在合并:

cd bdir
echo BBB>>B
git commit -a -m BBB
Run Code Online (Sandbox Code Playgroud)

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

git log --follow B
Run Code Online (Sandbox Code Playgroud)

我们在这样做之后得到了历史,但问题是,如果你实际上保留旧的"b"回购并且偶尔从它合并(说它实际上是第三方单独维护的回购)你就遇到了麻烦,因为那个第三方将不会重命名.您必须尝试将新更改合并到您的b版本中并重命名,我担心这不会顺利进行.但如果b消失,你就赢了.

  • 有关错误修复,请参见http://stackoverflow.com/questions/37937984/git-refusing-to-merge-unrelated-histories (3认同)
  • 正如@AlexBrown 提到的,在新版本的 `git` 上,这会产生 `fatal: refusing to merge unrelated histories`,因此你必须运行 `git merge -s ours --allow-unrelated-histories --no-commit B/master`反而。 (3认同)
  • 如果将示例代码分解为可读行而不是单个分号分隔的单行,则此响应将更有帮助.;) (2认同)

Ada*_*ruk 5

如果你真的想把东西缝合在一起,那就看看嫁接.你也应该使用git rebase --preserve-merges --onto.还可以选择保留提交者信息的作者日期.


hfs*_*hfs 5

我想

  1. 保持线性历史记录而无需显式合并,并且
  2. 使其看起来好像合并存储库的文件始终存在于子目录中,并且副作用是git log -- file不使用--follow

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

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

git checkout -b tmp_subdir
Run Code Online (Sandbox Code Playgroud)

然后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'
Run Code Online (Sandbox Code Playgroud)

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

git remote add sourcerepo .../path/to/sourcerepo
git fetch sourcerepo
Run Code Online (Sandbox Code Playgroud)

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

git rebase --preserve-merges --onto master --root sourcerepo/tmp_subdir
Run Code Online (Sandbox Code Playgroud)

您可以检查日志,以确保它确实为您提供了所需的东西。

git log --stat
Run Code Online (Sandbox Code Playgroud)

步骤4:重新设置基准后,您将处于“分离头”状态。您可以将主控快进到新的位置。

git checkout -b tmp_merged
git checkout master
git merge tmp_merged
git branch -d tmp_merged
Run Code Online (Sandbox Code Playgroud)

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

git remote rm sourcerepo
Run Code Online (Sandbox Code Playgroud)

  • 不要使用旧的“filter-branch”命令,而是使用“git filter-repo --to-subdirectory-filter &lt;dir&gt;”,它更快更容易。 (3认同)