我正在尝试学习在Git 1.7.11中添加的新git-subtree命令。添加子树后,我似乎失去了变基的能力。我有一个带有README文件的主存储库和一个库存储库,它也有一个README文件。我用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
现在,当我想要对origin/master
重新设置我的仓库并且它失败时,因为压缩提交直接应用于其不适用的父提交,因为它应用于仓库的根而不是前缀我在添加子树时就把它给了它。
如果我看一下壁球提交,原因很明显。没有关于前缀的信息。这只是原始的mylib提交压扁在一起。只有下一个合并提交知道任何有关它的内容,但是rebase不会在此处考虑它。
是否有任何变通方法(除了不再对子树提交进行重新定位)?
答案 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-dir
和git-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
。以下是:
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
所需的有关外部回购的所有信息。要恢复它:
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
。您将获得相同的历史记录,但会基于最新的母版。