更改 git 合并的方向(合并完成后)
Change direction of git merge (after the merge is done)
我在 git 存储库中有一个分支(例如 Feature-X)。
我所做的是以下内容
git checkout master
git merge Feature-X
我解决了很多冲突。我还没有提交更改。
但是,事实证明我想要的是进行反向合并(即将 master 合并到 Feature-x 中),因为分支可能仍然不稳定。
是否有任何命令可以挽救我在解决冲突时所做的工作,或者我是否需要再次解决,这次是在分支 Feature-X 中?
我在想是否有办法获取当前补丁,但按 "reverse" 顺序在分支 Feature-X 中应用它。例如。当补丁说 X 行更改为 Y 行时,这是相对于 Feature-X 将要掌握的。如果我想做相反的事情,补丁应该说 Y 行更改为 X,对吗?这只是我的想法。任何解决方案都可以。
你可以做几件事:
使用 git rebase -i
手动执行并手动反转提交的顺序。
编写一个脚本,它将遍历 git log --reverse
并按照您希望的顺序更新提交。
下次使用 git rebase --onto
并决定您希望从哪里开始提交
--onto <newbase>
Starting point at which to create the new commits. If the --onto option is not specified, the starting point is . May be any valid commit, and not just an existing branch name.
As a special case, you may use "A...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD.
在 master 上提交合并,在该提交上创建一个新分支,并将 master 分支重置为上一个提交。
git branch temp
git reset --hard HEAD~1
(假设您的工作副本中没有其他您想要保留的内容,您会在重置时丢失它!)
您回到了开始的地方(合并前),但如果您需要,您的冲突解决方案是安全的。
现在再次尝试合并,正确的方式(master -> feature)。如果你幸运的话,这样不会有那么多冲突,你就可以完成它。
如果这对您不利,您可以将您的临时分支合并或挑选到功能中。它在图表上看起来不正确,但这就是价格!
完成后记得删除多余的分支。
TL;DR: 看到最后
最后有个"recipe";向下滚动找到它(然后向上滚动一点找到描述和解释以及更安全的方法)。
好消息和坏消息
从某种意义上说,合并并不完全有一个"direction"。 (当您查看合并时 "see" 的方向是您想象力的产物,加上合并提交的消息,再加上一件更重要的事情,那就是我们的 "bad news"——但这并不是致命的,在结束。)
考虑这张以提交 *
作为合并基础的一对分支的图表:
o--o--...--o <-- branch1
/
...--o--*
\
o--o--...--o <-- branch2
合并("merge as a verb")branch1
和branch2
的过程是通过:
- 比较,即
git diff
,合并基础提交 *
与 branch1
的尖端提交;
- 将合并基础与
branch2
的尖端进行比较;
- 然后,从基础开始,结合两组变化。
因此实际的合并本身应该是这样的:
o--o--...--o
/ \
...--o--* M
\ /
o--o--...--o
(我将合并提交命名为 M
因为我们不知道,或者真的不在乎,什么疯狂的 Git 哈希 ID deadbeef
或 badc0ffee
或其他什么会有。)最终合并提交M
是合并("merge as a noun");它基于 merge-as-a-verb 组合过程存储最终的源代码树。
这是哪个 "direction" 合并?好吧,这至少部分取决于我们贴在上面的标签,不是吗? :-) 请注意,我小心地去掉了 branch1
和 branch2
。让我们把它们放回去:
o--o--...--o <-- branch1?
/ \
...--o--* M <-- branch1? branch2?
\ /
o--o--...--o <-- branch2?
这可能看起来令人困惑。它可能 令人困惑。这是整个问题的重要组成部分。 坏消息是,这不是整个的事情。有些东西我们无法在这些图纸中完全捕捉到。这对您来说可能无关紧要,如果确实如此,那也没什么大不了的;我们将通过再进行一次合并提交来修复它。但现在,让我们继续前进。
进行合并提交
一旦我们自己进行合并提交M
,我们必须选择——或者让Git选择——它在哪个分支上。在一个简单的、无冲突的合并中,我们最终总是 Git 选择这个。 Git 做的事情很简单:无论我们在什么分支上,当我们启动git merge
命令时,都是gets[=208的分支=] 合并提交。所以:
git checkout branch1
git merge branch2
表示最终图片为:
o--o--...--o
/ \
...--o--* M <-- branch1
\ /
o--o--...--o <-- branch2
我们可以 re-draw 作为:
...--o--*--o--...--o--M <-- branch1
\ /
o---...---o <-- branch2
这很明显我们将 branch2 合并到 branch1,反之亦然。尽管如此,附加到提交 M
的 源代码 并不依赖于此 "merge direction".
边栏:合并冲突
就最终结果而言,有冲突的合并与无冲突的合并一样。只是 Git 不能自己进行合并。它停止并让 you 解决冲突,然后 运行 git commit
进行合并提交。最后 git commit
步骤使合并提交 M
.
新的合并提交在 当前分支 上进行,就像任何其他提交一样。这就是 M
在 branch1
上的结局。请注意,合并提交的 消息 还说明了有关哪个分支名称已合并到其他分支名称中的信息 - 但是您有机会在进行提交时编辑它,因此您可以更改它以适应您可能有的任何想法。 :-)
(事实上,这与无冲突的情况没有什么不同:运行 git commit
都进行了最终的合并提交,并且都给了你编辑消息的机会。)
坏消息
坏消息是这些图表省略了一些可能对您重要的东西。 Git 有一个概念 first parent: 像 M
这样的合并提交有两个 parent,所以其中一个是第一个 parent一个不是。1 第一个parent是当你进行合并时,你如何判断你在哪个分支上。
因此,虽然这些图和 merge-as-a-verb 过程 表明不完全是 "merge direction",但这个 first-parent概念证明存在。 如果您将使用这个 first-parent 概念, 您会关心它,并希望把它做好。幸运的是,有一个解决方案。 (事实上,有多种解决方案,但我会先显示 klunky-but-straightforward 个。)
1很明显,另一个就是第二个parent:只有两个parent。然而,Git 允许合并三个或更多 parent 的提交。您可以在需要时枚举所有 parent;但是 Git 认为 第一个 特别重要,并且有 --first-parent
各种命令的标志,以便遵循 "the original branch".
得到我们想要的合并
让我们继续进行 "wrong" 合并提交:
# git add ... (if needed)
git commit
现在我们有:
o--o--...--o <-- [old branch1 tip]
/ \
...--o--* M <-- branch1
\ /
o--o--...--o <-- branch2
在哪里M
的 第一个 parent 是旧的 branch1
小费。
我们现在想要做的是 new 合并提交(具有不同的 ID),与 M
相同除了:
branch2
指向它
- 它的第一个 parent 是
branch2
的尖端
- 它的第二个parent是
branch1
的前一个提示
诀窍是以某种方式保存提交 M
——这很简单,我们将使用一个临时名称,这样我们就不必复制 M
的哈希 ID——然后重置 branch1
:
git branch temp # or git tag temp
git reset --hard HEAD~1 # move branch1 back, dropping M
(请注意,到目前为止,这与 相同)。我们现在有了这张图,除了每个提交附加的 标签 外,它几乎没有变化:
o--o--...--o <-- branch1
/ \
...--o--* M <-- temp
\ /
o--o--...--o <-- branch2
现在再次检查 branch2
和 运行 合并;它会像以前一样因冲突而失败:
git checkout branch2
git merge branch1 # use --no-commit if Git would commit the merge
从某种意义上说,我们尚未进行的提交与合并提交 M
相同——但是一旦我们进行了提交,它的 first parent将是branch2
的当前提示,其秒parent将是branch1
的当前提示。我们只需要获得正确的 source 来完成我们将要进行的提交。
现在我们使用合并 result 我们保存的名称 temp
。这假设您位于树的顶层,因此 .
命名所有内容:
git rm -rf -- . # remove EVERYTHING
git checkout temp -- . # get it all back from temp
我们现在正在使用之前提交的合并结果。我们甚至不需要 git add
任何东西,因为这种形式的 git checkout
会更新索引,所以:
git commit
并且我们根据需要在分支 branch2
上进行了新的合并 M2
:
o--o--...--o__ <-- branch1
/ \ \
...--o--* M \ <-- temp
\ / \
o--o--...--o-----M2 <-- branch2
现在我们可以删除临时分支或标签:
git branch -D temp # or git tag -d temp
我们都完成了。随着临时名称的消失,我们无法再看到原始合并M
。
偷偷摸摸的管道方法
实际上有一个更短的方法来完成所有这些,使用 Git 的 "plumbing" 命令,但它有点棘手。一旦我们拥有了所有 git add
-ed 和 运行 git commit
,我们就有了正确的树,我们只有一个提交的第一个和第二个 parent ID 错误。您可能还希望编辑提交 message,这样看起来您正在将消息中的 branch1 合并到 branch2 中。那么:
# git add ... (if / as needed, as above)
# git commit (make the merge)
git branch -f branch2 $(git log --pretty=format:%B --no-walk HEAD |
git commit-tree -p HEAD^2 -p HEAD^1 -F -)
git reset --hard HEAD^1
git commit-tree
步骤创建了一个新的合并提交,它是我们刚刚创建的副本的副本,但交换了两个 parent。然后我们使用 git branch -f
使 branch2
指向这个提交。 确保你在这一步得到了名字(branch2
)。 HEAD^1
和 HEAD^2
名字指的是两个(第一个和第二个) current commit的parents,当然就是我们刚刚做的merge commit。有正确的树和正确的提交消息文本;它只是有错误的第一个和第二个 parent 哈希值。
一旦我们将新提交安全地添加到 branch2
,我们只需重置当前 (branch1
) 分支以删除我们所做的合并提交。
我在 git 存储库中有一个分支(例如 Feature-X)。
我所做的是以下内容
git checkout master
git merge Feature-X
我解决了很多冲突。我还没有提交更改。
但是,事实证明我想要的是进行反向合并(即将 master 合并到 Feature-x 中),因为分支可能仍然不稳定。
是否有任何命令可以挽救我在解决冲突时所做的工作,或者我是否需要再次解决,这次是在分支 Feature-X 中?
我在想是否有办法获取当前补丁,但按 "reverse" 顺序在分支 Feature-X 中应用它。例如。当补丁说 X 行更改为 Y 行时,这是相对于 Feature-X 将要掌握的。如果我想做相反的事情,补丁应该说 Y 行更改为 X,对吗?这只是我的想法。任何解决方案都可以。
你可以做几件事:
使用
git rebase -i
手动执行并手动反转提交的顺序。编写一个脚本,它将遍历
git log --reverse
并按照您希望的顺序更新提交。下次使用
git rebase --onto
并决定您希望从哪里开始提交
--onto <newbase>
Starting point at which to create the new commits. If the --onto option is not specified, the starting point is . May be any valid commit, and not just an existing branch name.As a special case, you may use "A...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD.
在 master 上提交合并,在该提交上创建一个新分支,并将 master 分支重置为上一个提交。
git branch temp
git reset --hard HEAD~1
(假设您的工作副本中没有其他您想要保留的内容,您会在重置时丢失它!)
您回到了开始的地方(合并前),但如果您需要,您的冲突解决方案是安全的。
现在再次尝试合并,正确的方式(master -> feature)。如果你幸运的话,这样不会有那么多冲突,你就可以完成它。
如果这对您不利,您可以将您的临时分支合并或挑选到功能中。它在图表上看起来不正确,但这就是价格!
完成后记得删除多余的分支。
TL;DR: 看到最后
最后有个"recipe";向下滚动找到它(然后向上滚动一点找到描述和解释以及更安全的方法)。
好消息和坏消息
从某种意义上说,合并并不完全有一个"direction"。 (当您查看合并时 "see" 的方向是您想象力的产物,加上合并提交的消息,再加上一件更重要的事情,那就是我们的 "bad news"——但这并不是致命的,在结束。)
考虑这张以提交 *
作为合并基础的一对分支的图表:
o--o--...--o <-- branch1
/
...--o--*
\
o--o--...--o <-- branch2
合并("merge as a verb")branch1
和branch2
的过程是通过:
- 比较,即
git diff
,合并基础提交*
与branch1
的尖端提交; - 将合并基础与
branch2
的尖端进行比较; - 然后,从基础开始,结合两组变化。
因此实际的合并本身应该是这样的:
o--o--...--o
/ \
...--o--* M
\ /
o--o--...--o
(我将合并提交命名为 M
因为我们不知道,或者真的不在乎,什么疯狂的 Git 哈希 ID deadbeef
或 badc0ffee
或其他什么会有。)最终合并提交M
是合并("merge as a noun");它基于 merge-as-a-verb 组合过程存储最终的源代码树。
这是哪个 "direction" 合并?好吧,这至少部分取决于我们贴在上面的标签,不是吗? :-) 请注意,我小心地去掉了 branch1
和 branch2
。让我们把它们放回去:
o--o--...--o <-- branch1?
/ \
...--o--* M <-- branch1? branch2?
\ /
o--o--...--o <-- branch2?
这可能看起来令人困惑。它可能 令人困惑。这是整个问题的重要组成部分。 坏消息是,这不是整个的事情。有些东西我们无法在这些图纸中完全捕捉到。这对您来说可能无关紧要,如果确实如此,那也没什么大不了的;我们将通过再进行一次合并提交来修复它。但现在,让我们继续前进。
进行合并提交
一旦我们自己进行合并提交M
,我们必须选择——或者让Git选择——它在哪个分支上。在一个简单的、无冲突的合并中,我们最终总是 Git 选择这个。 Git 做的事情很简单:无论我们在什么分支上,当我们启动git merge
命令时,都是gets[=208的分支=] 合并提交。所以:
git checkout branch1
git merge branch2
表示最终图片为:
o--o--...--o
/ \
...--o--* M <-- branch1
\ /
o--o--...--o <-- branch2
我们可以 re-draw 作为:
...--o--*--o--...--o--M <-- branch1
\ /
o---...---o <-- branch2
这很明显我们将 branch2 合并到 branch1,反之亦然。尽管如此,附加到提交 M
的 源代码 并不依赖于此 "merge direction".
边栏:合并冲突
就最终结果而言,有冲突的合并与无冲突的合并一样。只是 Git 不能自己进行合并。它停止并让 you 解决冲突,然后 运行 git commit
进行合并提交。最后 git commit
步骤使合并提交 M
.
新的合并提交在 当前分支 上进行,就像任何其他提交一样。这就是 M
在 branch1
上的结局。请注意,合并提交的 消息 还说明了有关哪个分支名称已合并到其他分支名称中的信息 - 但是您有机会在进行提交时编辑它,因此您可以更改它以适应您可能有的任何想法。 :-)
(事实上,这与无冲突的情况没有什么不同:运行 git commit
都进行了最终的合并提交,并且都给了你编辑消息的机会。)
坏消息
坏消息是这些图表省略了一些可能对您重要的东西。 Git 有一个概念 first parent: 像 M
这样的合并提交有两个 parent,所以其中一个是第一个 parent一个不是。1 第一个parent是当你进行合并时,你如何判断你在哪个分支上。
因此,虽然这些图和 merge-as-a-verb 过程 表明不完全是 "merge direction",但这个 first-parent概念证明存在。 如果您将使用这个 first-parent 概念, 您会关心它,并希望把它做好。幸运的是,有一个解决方案。 (事实上,有多种解决方案,但我会先显示 klunky-but-straightforward 个。)
1很明显,另一个就是第二个parent:只有两个parent。然而,Git 允许合并三个或更多 parent 的提交。您可以在需要时枚举所有 parent;但是 Git 认为 第一个 特别重要,并且有 --first-parent
各种命令的标志,以便遵循 "the original branch".
得到我们想要的合并
让我们继续进行 "wrong" 合并提交:
# git add ... (if needed)
git commit
现在我们有:
o--o--...--o <-- [old branch1 tip]
/ \
...--o--* M <-- branch1
\ /
o--o--...--o <-- branch2
在哪里M
的 第一个 parent 是旧的 branch1
小费。
我们现在想要做的是 new 合并提交(具有不同的 ID),与 M
相同除了:
branch2
指向它- 它的第一个 parent 是
branch2
的尖端
- 它的第二个parent是
branch1
的前一个提示
诀窍是以某种方式保存提交 M
——这很简单,我们将使用一个临时名称,这样我们就不必复制 M
的哈希 ID——然后重置 branch1
:
git branch temp # or git tag temp
git reset --hard HEAD~1 # move branch1 back, dropping M
(请注意,到目前为止,这与
o--o--...--o <-- branch1
/ \
...--o--* M <-- temp
\ /
o--o--...--o <-- branch2
现在再次检查 branch2
和 运行 合并;它会像以前一样因冲突而失败:
git checkout branch2
git merge branch1 # use --no-commit if Git would commit the merge
从某种意义上说,我们尚未进行的提交与合并提交 M
相同——但是一旦我们进行了提交,它的 first parent将是branch2
的当前提示,其秒parent将是branch1
的当前提示。我们只需要获得正确的 source 来完成我们将要进行的提交。
现在我们使用合并 result 我们保存的名称 temp
。这假设您位于树的顶层,因此 .
命名所有内容:
git rm -rf -- . # remove EVERYTHING
git checkout temp -- . # get it all back from temp
我们现在正在使用之前提交的合并结果。我们甚至不需要 git add
任何东西,因为这种形式的 git checkout
会更新索引,所以:
git commit
并且我们根据需要在分支 branch2
上进行了新的合并 M2
:
o--o--...--o__ <-- branch1
/ \ \
...--o--* M \ <-- temp
\ / \
o--o--...--o-----M2 <-- branch2
现在我们可以删除临时分支或标签:
git branch -D temp # or git tag -d temp
我们都完成了。随着临时名称的消失,我们无法再看到原始合并M
。
偷偷摸摸的管道方法
实际上有一个更短的方法来完成所有这些,使用 Git 的 "plumbing" 命令,但它有点棘手。一旦我们拥有了所有 git add
-ed 和 运行 git commit
,我们就有了正确的树,我们只有一个提交的第一个和第二个 parent ID 错误。您可能还希望编辑提交 message,这样看起来您正在将消息中的 branch1 合并到 branch2 中。那么:
# git add ... (if / as needed, as above)
# git commit (make the merge)
git branch -f branch2 $(git log --pretty=format:%B --no-walk HEAD |
git commit-tree -p HEAD^2 -p HEAD^1 -F -)
git reset --hard HEAD^1
git commit-tree
步骤创建了一个新的合并提交,它是我们刚刚创建的副本的副本,但交换了两个 parent。然后我们使用 git branch -f
使 branch2
指向这个提交。 确保你在这一步得到了名字(branch2
)。 HEAD^1
和 HEAD^2
名字指的是两个(第一个和第二个) current commit的parents,当然就是我们刚刚做的merge commit。有正确的树和正确的提交消息文本;它只是有错误的第一个和第二个 parent 哈希值。
一旦我们将新提交安全地添加到 branch2
,我们只需重置当前 (branch1
) 分支以删除我们所做的合并提交。