如何在git-subtree添加后重新定义?

时间:2012-10-12 11:35:06

标签: git git-rebase git-subtree

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

将它添加到lib目录
$ 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

现在,当我想要对origin/master重新设置我的仓库并且它失败时,因为压缩提交直接应用于其不适用的父提交,因为它应用于仓库的根而不是前缀我在添加子树时就把它给了它。

如果我看一下壁球提交,原因很明显。没有关于前缀的信息。这只是原始的mylib提交压扁在一起。只有下一个合并提交知道任何有关它的内容,但是rebase不会在此处考虑它。

是否有任何变通方法(除了不再对子树提交进行重新定位)?

8 个答案:

答案 0 :(得分:9)

这不是一个解决方案,但它目前的工作我使用...

使用您的初始示例:

*   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

然后你的分支应该从主人那里重新开始,并且子树将是“正常的”,并且Squash + Merge完好无损:

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

答案 1 :(得分:6)

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

假设我们有这个提交树:

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:我们当前的master(B),以及一个不相关的提交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

现在我们要将feature重新定义到master。以下是:

1。 Cherry-从feature逐个选择提交,在feature之上创建master分支的新副本。

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,但我们已经丢失了git subtree所需的有关外部回购的所有信息。要恢复它:

2。 Add the missing parent link as a graft,并重写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的历史记录,因此如果有其他人在此分支上与您合作,请小心发布它。但是,既然你想首先制作一个rebase,你可能会注意到: - )

答案 2 :(得分:4)

这适用于简单的情况:

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...' - 这就是你想要的那个。

答案 3 :(得分:3)

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

这对任何人都没有多大帮助。我也很乐意为此找到解决方法。

答案 4 :(得分:2)

我有一个类似的问题:我想在做一个子树添加后进行rebase,并且使用--preserve-merges仍然让我发生合并冲突(由于.gitignore文件冲突等等)。

在我的情况下,我并不一定计划使用任何子树功能:我只是简单地引入一个本应属于超级项目的回购。万一它可以帮助其他人,这就是我最终做的,基于我发现的其他related answers

假设我在同一目录中有两个项目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

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

答案 5 :(得分:0)

您需要使用

git rebase --preserve-merges --preserve-committer --onto new_place start end

答案 6 :(得分:0)

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丢弃合并提交的第二个父级。我还使用过the Git book v1's obsolete "subtree" tutorial,它的作用相同,但很乏味。

答案 7 :(得分:0)

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