git-subtree 添加后如何变基?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12858199/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-19 07:37:03  来源:igfitidea点击:

How to rebase after git-subtree add?

gitgit-rebasegit-subtree

提问by Epeli

I'm trying to learn the new git-subtreecommand which was added in Git 1.7.11. I seem to lose ability to rebase after I add a subtree. I have the primary repository with README file and a library repository which also has a README file. I add it to lib directory with subtree add:

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

$ git subtree add -P lib/mylib myliborigin master

This works fine, but now the history looks like this:

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

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

Now when I want to rebase my repo against origin/masterand it fails because the squash commit is applied directly against its parent commit which does not apply, because it is applied to the root of the repo and not the prefix I gave it to it when adding the subtree.

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

The reason for this is pretty clear if I look at the squash commit. There is no information about the prefix. It is just the original mylib commits squashed together. Only the next merge commit knows anything about it, but rebase does not take it to account here.

如果我查看壁球提交,原因就很清楚了。没有关于前缀的信息。它只是将原始的 mylib 提交压缩在一起。只有下一次合并提交才知道有关它的任何信息,但 rebase 在这里不考虑它。

Are there any workarounds (besides never rebasing over the subtree commits)?

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

回答by ericpeters

This isn't a solution, but its the current work around I use...

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

Using your initial example:

使用您的初始示例:

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

Rebase interactively to the 2nd commit before the subtree add:

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

$ git rebase -i 020e372

Delete the two subtree entries & mark edit for the prior commit:

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

e b99d55b Add readme

Save file/close, then when it gets to the "Add readme" commit, run the amend command:

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

$ git commit --amend

Then re-add your new subtree:

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

$ git subtree add -P lib/mylib myliborigin master

Continue the rebase:

继续变基:

$ git rebase --continue

Your branch should then be rebased off of master, and the subtree will be "normal" with the Squash + the Merge intact:

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

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

回答by Skiminok

This is an old question, but I just had the same problem with my repo, and I finally found a complete solution, which (hopefully) preserves all the subtree metadata.

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

Suppose we had this commit tree:

假设我们有这个提交树:

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

and we forked a featurebranch with a subtree residing in lib/:

我们分叉了一个feature带有子树的分支lib/

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

It creates a merge commit D with two parents: our current master(B), and an unrelated commit Fwith the squashed history of the external repo. This commit also contains some git subtreemetadata in its commit message (namely, git-subtree-dirand git-subtree-split).

它创建了一个带有两个父项的合并提交 D:我们当前的master(B) 和一个不相关的提交F,其中包含外部 repo 的压缩历史记录。此提交还在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

Later, we add some commits to both branches independently.

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

   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

Now we want to rebase featureonto master. Here's how:

现在我们要变基featuremaster. 就是这样:

1. Cherry-pick the commits from featureone by one to create a new copy of the featurebranch on top of master.

1.feature逐个挑选提交以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

Now we have the equivalent of a rebase, but we've lost all information about the external repo, required for git subtree. To restore it:

现在我们有了一个 rebase 的等价物,但是我们已经丢失了有关外部存储库的所有信息,这是git subtree. 要恢复它:

2. Add the missing parent link as a graft, and rewrite the history of featureto make it permanent.

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

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

And now we get a picture exactly equivalent to the one with started with. The old commits D and E are still out there, but they can be garbage-collected later.

现在我们得到一张与开始时完全相同的图片。旧的提交 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

Warning:This rewrites the history of feature, so be wary of publishing it if anybody else collaborates with you on this branch. However, since you wanted to make a rebase in the first place, you are probably aware of that :-)

警告:这重写了 的历史feature,所以如果其他人在这个分支上与你合作,请小心发布它。但是,由于您首先想进行变基,因此您可能知道这一点:-)

回答by Kevin Cooper

This works in simple cases:

这适用于简单的情况:

git rebase --preserve-merges master

Thanks to @Techlive Zheng in the comments.

感谢@Techlive Zheng 在评论中。



You may see

你可能会看到

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

Which means that git failed to automatically apply your subtree. This puts you in the situation @ericpeters described in his answer. Solution:

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

Re-add your subtree (use the same command you originally used):

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

git subtree add -P lib lib-origin master

Continue the rebase:

继续变基:

git rebase --continue

And you're all set!

一切就绪!



If you're wondering if it worked successfully, you can compare with your original version after rebasing to make sure you didn't change anything:

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

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

(the -- .at the end instructs git to only diffthe files in your current directory).

-- .最后指示 git 只diff访问当前目录中的文件)。



If all else fails and you don't care about preserving the commits themselves, you can simply git cherry-pickthe original subtree commit.

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

The commit message will look something like Add 'lib/' from commit '9767e6...'-- that's the one you want.

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

回答by Nick Hutchinson

Apparently this is expected behaviour (for some perverse definition of "expected behaviour.") See: http://git.661346.n2.nabble.com/subtree-merges-lose-prefix-after-rebase-td7332850.html.

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

Not that this is much help to anyone. I'd love to find a workaround for this too.

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

回答by David Alan Hjelle

I had a similar issue: I wanted to rebase after doing a subtree add, and using --preserve-merges still left me with a merge conflict (due to conflicting .gitignorefiles and others).

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

In my case, I didn't necessarily plan on using any subtree functionality: I was simply pulling in a repo that should have been part of the superproject originally. In case it helps anyone else, here's what I ended up doing, based off of other relatedanswersI found.

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

Suppose I have two projects, main_project and sub_project, in the same directory. I want to pull sub_project into a directory named sub_project within main_project, assuming neither repo has ever had a directory named sub_project:

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

I'll update if I find any problems with this approach.

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

回答by Adam Dymitruk

You need to use

你需要使用

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

回答by nyanpasu64

Git 2.24.0 (released 2019-11-04) added support for git rebase --rebase-merges --strategy [strategy]. So now if you run git rebase --rebase-merges --strategy subtree [branch]when your current branch contains a subtree merge, it will Just Work now.

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

For my project, I've decided I might not use git subtree add, but instead throw away the second parent of the merge commit using git replace --edit. I've also used the Git book v1's obsolete "subtree" tutorial, which does the same thing but is tedious.

对于我的项目,我决定我可能不使用git subtree add,而是使用git replace --edit. 我还使用了 Git book v1's obsolete "subtree" tutorial,它做同样的事情但很乏味。