在我的存储库中,我目前有这样的历史:
$ git log --oneline
<commit-id-1> Merge commit '<merged-commit-id-1>' into <branch>
<commit-id-2> Merge commit '<merged-commit-id-2>' into <branch>
...
Run Code Online (Sandbox Code Playgroud)
在哪里<merged-commit-id-1>和<merged-commit-id-2>我从branch之前创建当前的另一个分支合并.现在我想以某种方式加入这些合并提交:
$ git log --oneline
<commit-id> My message about huge successful merge here...
...
Run Code Online (Sandbox Code Playgroud)
我试过了
$ git rebase --preserve-merges -i HEAD~2
Run Code Online (Sandbox Code Playgroud)
有p和s但得到这个错误:
Refusing to squash a merge: ...
Run Code Online (Sandbox Code Playgroud)
(我也在一个新的最简单的存储库中复制它)有没有办法绕过它?
我真正想要的是能够通过原始分支(已更改)的ID合并许多提交,同时逐步解决冲突并且不引入数十个合并提交(merge --squash也不是一个选项,因为我需要保留历史).
例:
$ git init
$ echo 1 > 1; git add 1; git commit -m 1
$ git branch branch
$ echo 2 > 2; git add 2; git commit -m 2
$ echo 3 > 3; git add 3; git commit -m 3
$ git checkout branch
$ echo 4 > 4; git add 4; git commit -m 4
$ git log master --oneline
8222... 3
1f03... 2
... 1
$ git merge 1f03
# in real project here goes some work
$ git merge 8222
# and here, can't merge 8222 right away, because it can be difficult
# now need to clean up merge commits OR merge incrementally in a different way
$ git rebase -i --preserve-merges HEAD~2
p ...
s ...
Refusing to squash a merge: a199... (merge commit for 8222)
Run Code Online (Sandbox Code Playgroud)
torek解决方案:
git update-ref refs/heads/branch `git commit-tree -p branch~N -p 8222 branch^{tree} -m 'Commit message for a new merge commit'`
Run Code Online (Sandbox Code Playgroud)
首先是一些背景......在git中合并有两个功能:
它将两条开发线的“合并基础”与两次提示提交进行比较,然后组合更改,使用半智能1算法。也就是说,git merge other首先计算$base,然后计算2git diff $base HEAD和git diff $base other。这两个差异列表告诉混帐东西,你想要做的$base,例如添加一行foo.txt,从行bar.tex,并更改线路xyz.py。Git 然后尝试保留所有这些更改,如果添加到的行在每个分支中foo.txt以相同的方式添加,或者同一行从中删除,只需保留一份更改副本。bar.tex
它记录了以下事实,以备将来使用,两条开发线合并在一起,并且两条线的所有所需更改现在都存在于“合并提交”中。例如,由此产生的提交适合作为新的合并基础。稍后我们将看到类似的内容(不完全是这样)。
这两个功能以完全不同的方式实现。第一个是通过合并工作树中的差异来完成的,留下任何过于困难的情况供您手动处理。第二个是在您进行合并提交时完成的,通过在新(合并)提交中列出两个3 个“父提交 ID”。
您需要决定的是要保留多少,以何种方式保留。我认为在这些情况下,绘制(部分)提交图非常有帮助。
在这里,在您的示例中,您从一个公共基础开始,我将调用它B(用于基础),然后再进行两次提交master和一次提交branch:
B - C - D <-- master
\
E <-- branch
Run Code Online (Sandbox Code Playgroud)
现在branch你第一次合并提交C:
B - C - D <-- master
\ \
E - F <-- branch
Run Code Online (Sandbox Code Playgroud)
这是否需要手动干预取决于从Bto的更改C,以及从Bto的更改E,但无论如何这都会使新的合并提交F。
然后你要求 git 合并到 commit D。然而,这不再使用 commitB作为合并基础,因为在F向后进行新的合并之后,git 发现合并基础——最近的提交——介于branch和之间master——现在是 commit C。因此,比较是CtoD和Cto F。Git 结合了这些(也许不得不停下来寻求帮助);当有人(你或 git)提交结果时,我们得到:
B - C - D <-- master
\ \ \
E - F - G <-- branch
Run Code Online (Sandbox Code Playgroud)
现在,您要求(我认为)将 F 和 G 压缩为单个合并提交。鉴于 git 的工作方式,您只能通过创建一个新的合并提交,然后branch指向它来实现这一点(删除对的引用G,因此也是唯一的引用F)。
进行这个新的合并提交时需要考虑两件事:
你想要什么树(附加到提交的文件/目录集,将由索引/暂存区组成)?你只能拥有一个!
您要列出哪些父级提交?为了使它在 commit 之后立即出现E,您希望它的“第一个父级”是E. 要使其成为合并提交,它必须至少有一个其他父级。
要选择的树很明显:提交G具有所需的合并结果,因此请使用来自G.
要使用的第二个(也许是第三个)父级不太明显。可以同时列出C and D,给出一个 octopus merge O:
B - C = D <-- master
\ \ \
E --- O <-- branch
Run Code Online (Sandbox Code Playgroud)
但这并没有做任何特别有用的事情,因为它C是D. 如果你做了一个看起来更正常的合并提交M,它只是指向两者E(作为第一个父级)和D)(作为第二个父级),你会得到这个:
B - C - D <-- master
\ \
E ---- M <-- branch
Run Code Online (Sandbox Code Playgroud)
这将与章鱼合并“一样好”:它具有相同的树(我们G每次都从提交中挑选树)并且它具有相同的第一亲(E)。它只有另一个父级(即D),但这会导致C在其历史记录中,因此它不需要直接与C.
(如果你想要一个,它可以有一个明确的联系,但没有特别好的理由给它一个。)
这留下了最后一个问题:实际生成此合并(M或O,无论您喜欢哪个)并branch指向它的机制。
假设您目前处于这种状态,有一个“管道”命令可以直接创建它:
B - C - D <-- master
\ \ \
E - F - G <-- branch
Run Code Online (Sandbox Code Playgroud)
此时你可以运行(注意,这些是未经测试的):
$ commit=$(git commit-tree -p branch~2 -p master branch^{tree} < msg)
Run Code Online (Sandbox Code Playgroud)
其中msg是包含所需提交消息的文件。这会创建 commit M(只有两个父对象,第一个是 commit E,即,branch~2第二个是 commit D,即master)。然后:
$ git update-ref refs/heads/branch $commit
Run Code Online (Sandbox Code Playgroud)
可以M只使用普通的 git "porcelain" 命令来创建,但这需要更多的技巧。首先我们要保存所需的树,即branch^{tree}:
$ tree=$(git rev-parse branch^{tree})
Run Code Online (Sandbox Code Playgroud)
然后,在分支上branch(因为git reset将使用当前分支),告诉 git 备份两次提交。无论我们在这里使用软重置、硬重置还是混合重置都没有关系,我们只是branch暂时倒回标签:
$ git reset HEAD~2
Run Code Online (Sandbox Code Playgroud)
然后,告诉 git 我们正在合并master,但无论如何都不要提交。(这只是为了让 git 设置,.git/MERGE_MSG并且.git/MERGE_HEAD- 您可以直接编写这些文件,而不是执行此部分。)
$ git merge --no-commit master
Run Code Online (Sandbox Code Playgroud)
然后清除工作树和索引中的任何内容,将其全部替换为提交时保存的树G:
$ git rm -rf .; git checkout $tree -- .
Run Code Online (Sandbox Code Playgroud)
(为此,您需要位于工作树的顶级目录中)。这为新提交准备了索引,现在只需要进行最后一次提交:
$ git commit
Run Code Online (Sandbox Code Playgroud)
这将使用我们在执行git reset. 将git reset提示提交为branchbe commit E,所以我们的新提交是 merge-commit M,就像我们使用低级管道命令一样。
(注意:git rebase -i -p不能完全做到这一点,它不够聪明知道什么时候可以,只有当我们首先将其设置为可以时。)
1它不是特别聪明,但它可以很好地处理简单的情况。
2您可以使用git merge-base, 和自己计算base=$(git merge-base other)。但是,对于某些(不常见但并不像人们希望的那样罕见)情况,可能有不止一个合适的合并基础。默认合并算法(“递归”)将合并两个合并基以提出“虚拟基”并使用它。因此,如果您尝试手动完成所有操作,则会存在一些细微的差异。
3可以有两个以上的父项:“合并提交”的定义是具有多个父项的任何提交。但是,多路合并(“章鱼”合并)的完成方式不同(使用“章鱼合并”策略)。所以,在大多数情况下,我在这里忽略了这些。
| 归档时间: |
|
| 查看次数: |
896 次 |
| 最近记录: |