75

我正在尝试学习Git 1.7.11 中添加的新git-subtree命令。添加子树后,我似乎失去了变基的能力。我有一个带有 README 文件的主存储库和一个也有一个 README 文件的库存储库。我将它添加到 lib 目录subtree add

$ git subtree add -P lib/mylib myliborigin master

这工作正常,但现在历史看起来像这样:

*   22c1fe6 (HEAD, master) Merge commit 'b6e698d9f4985825efa06dfdd7bba8d2930cd40e' as 'lib/mylib' - 
|\                                                                                                                
| * b6e698d Squashed 'lib/mylib/' content from commit d7dbd3d
* b99d55b Add readme
* 020e372 Initial

现在,当我想针对我的 repo 进行 rebaseorigin/master并且它失败了,因为 squash 提交直接应用于其不适用的父提交,因为它应用于 repo 的根目录,而不是我在添加时给它的前缀子树。

如果我查看 squash 提交,其原因就很清楚了。没有关于前缀的信息。它只是压缩在一起的原始 mylib 提交。只有下一次合并提交对此一无所知,但 rebase 不会在这里考虑到它。

是否有任何解决方法(除了从不基于子树提交)?

4

8 回答 8

19

这适用于简单的情况:

git rebase --preserve-merges master

感谢评论中的@Techlive Zheng。


你可能会看到

fatal: refusing to merge unrelated histories
Error redoing merge a95986e...

这意味着 git 无法自动应用您的子树。这使您处于@ericpeters 在他的回答中描述的情况。解决方案:

重新添加您的子树(使用您最初使用的相同命令):

git subtree add -P lib lib-origin master

继续变基:

git rebase --continue

你都准备好了!


如果您想知道它是否成功运行,您可以在变基后与原始版本进行比较,以确保您没有更改任何内容:

git diff <ref-before-rebase> <ref-after-rebase> -- .

-- .最后指示 git 仅diff处理当前目录中的文件)。


如果所有其他方法都失败并且您不关心保留提交本身,则可以简单地git cherry-pick使用原始子树提交。

提交消息看起来像Add 'lib/' from commit '9767e6...'——这就是你想要的。

于 2018-01-17T03:52:47.610 回答
11

这是一个老问题,但我的 repo 也遇到了同样的问题,我终于找到了一个完整的解决方案,它(希望)保留了所有子树元数据。

假设我们有这个提交树:

B   (master) Add README.md
|     
A            Initial commit

我们分叉了一个feature分支,其中的子树位于lib/

git remote add -f githublib https://github.com/lib/lib.git
git subtree add --prefix lib/ githublib master --squash

它创建了一个具有两个父级的合并提交 D:我们当前的(B),以及一个与外部 repo 的压缩历史master无关的提交。F该提交还在其提交消息中包含一些git subtree元数据(即git-subtree-dirgit-subtree-split)。

   D     (feature) Merged commit 'F' as 'lib/'
  / \    
 /   F             Squashed 'lib/' content from GGGGGG
B        (master)  Add README.md
|     
A                  Initial commit

稍后,我们分别向两个分支添加一些提交。

   E     (feature) Remove .gitignore from lib/
C  |     (master)  Add LICENSE.md
|  D               Merged commit 'F' as 'lib/'
| / \    
|/   F             Squashed 'lib/' content from GGGGGG
B                  Add README.md
|     
A                  Initial commit

现在我们要重新定位featuremaster. 就是这样:

feature1.逐一挑选提交,featuremaster.

git branch -f feature C
git checkout feature
git cherry-pick D E

E'       (feature) Remove .gitignore from lib/
|
D'                 Merged commit 'F' as 'lib/'
|
|  E               Remove .gitignore from lib/
C  |     (master)  Add LICENSE.md
|  D               Merged commit 'F' as 'lib/'
| / \    
|/   F             Squashed 'lib/' content from GGGGGG
B                  Add README.md
|     
A                  Initial commit

现在我们有了一个 rebase 的等价物,但是我们丢失了所有关于外部 repo 的信息,对于git subtree. 要恢复它:

2.将丢失的父链接添加为嫁接,并重写历史feature使其永久化。

git checkout feature
git replace --graft D' C F
git filter-branch --tag-name-filter cat -- master..

现在我们得到了一张与开头完全相同的图片。旧的提交 D 和 E 仍然存在,但它们可以在以后被垃圾收集。

E'       (feature) Remove .gitignore from lib/
|
D'                 Merged commit 'F' as 'lib/'
|\
| \                
C  \     (master)  Add LICENSE.md
|   \              
|    \   
|     F            Squashed 'lib/' content from GGGGGG
B                  Add README.md
|     
A                  Initial commit

警告:这会改写 的历史feature,所以如果有其他人在这个分支上与你合作,请小心发布它。但是,由于您一开始就想进行变基,因此您可能已经意识到了 :-)

于 2017-02-09T01:37:29.030 回答
10

这不是一个解决方案,但它是我当前使用的解决方法......

使用您的初始示例:

*   22c1fe6 (HEAD, master) Merge commit 'b6e698d9f4985825efa06dfdd7bba8d2930cd40e' as 'lib/mylib' - 
|\                                                                                                                
| * b6e698d Squashed 'lib/mylib/' content from commit d7dbd3d
* b99d55b Add readme
* 020e372 Initial

在子树添加之前以交互方式变基到第二次提交:

$ git rebase -i 020e372

删除两个子树条目并标记先前提交的编辑:

e b99d55b Add readme

保存文件/关闭,然后当它到达“添加自述文件”提交时,运行修改命令:

$ git commit --amend

然后重新添加你的新子树:

$ git subtree add -P lib/mylib myliborigin master

继续变基:

$ git rebase --continue

然后,您的分支应该从 master 重新定位,并且子树将是“正常”的,Squash + Merge 完好无损:

*   22c1fe6 (HEAD, master) Merge commit 'b6e698d9f4985825efa06dfdd7bba8d2930cd40e' as 'lib/mylib' - 
|\                                                                                                                
| * b6e698d Squashed 'lib/mylib/' content from commit d7dbd3d
于 2014-04-15T21:19:38.843 回答
4

Git 2.24.0(2019-11-04 发布)增加了对git rebase --rebase-merges --strategy [strategy]. 因此,现在如果您git rebase --rebase-merges --strategy subtree [branch]在当前分支包含子树合并时运行,它将立即工作。

对于我的项目,我决定我可能不使用git subtree add,而是使用git replace --edit. 我还使用了 Git book v1 过时的“子树”教程,它做同样的事情但很乏味。

于 2019-11-05T10:56:43.187 回答
3

显然这是预期的行为(对于“预期行为”的一些不正当定义。)请参阅: http: //git.661346.n2.nabble.com/subtree-merges-lose-prefix-after-rebase-td7332850.html

并不是说这对任何人都有很大帮助。我也很想为此找到解决方法。

于 2012-11-30T06:33:17.877 回答
2

我有一个类似的问题:我想在添加子树后重新设置基础,并且使用 --preserve-merges 仍然给我留下了合并冲突(由于.gitignore文件和其他冲突)。

就我而言,我并不一定打算使用任何子树功能:我只是引入了一个原本应该是超级项目一部分的存储库。如果它对其他人有帮助,这就是我最终根据我找到的其他相关 答案所做的事情。

假设我在同一个目录中有两个项目,main_project 和 sub_project。我想将 sub_project 拉到 main_project 中名为 sub_project 的目录中,假设两个 repo 都没有名为 sub_project 的目录:

cd main_project
git fetch ../sub_project
git checkout -b sub_project FETCH_HEAD
git filter-branch --prune-empty --tree-filter '
    if [[ ! -e sub_project ]]; then
        mkdir -p sub_project
        git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files sub_project
    fi'
git checkout branch-to-merge-within
git merge sub_project
git branch -d sub_project

如果我发现这种方法有任何问题,我会更新。

于 2013-06-05T21:01:30.767 回答
0

You need to use

git rebase --preserve-merges --preserve-committer --onto new_place start end
于 2012-10-13T01:09:57.720 回答
0

另一种方法是git rebase -i master,然后merged squashed用 (break here) 替换提交b,因此 git rebase 将在原始子树添加提交时停止。在那一刻再次运行git subtree add ...,然后git rebase --continue。您将获得相同的历史记录,但会重新定位到最新的 master。

于 2020-09-27T15:55:57.500 回答