如何在不合并或变基的情况下解决分支上的 git 冲突

How to resolve git conflicts on branch without merging or rebasing

我平时的feature/bug分支工作流程是这样的:

假设无法单击 PR 上的合并按钮,因为我的功能分支现在与 master 有冲突。在这一点上,我通常想解决与 master 的冲突,我想 在我的功能分支 上这样做,这样我就可以让审查我的代码的人看到:

  1. 一个没有合并提交的很好的 diff and/or 来自 master 的随机更改
  2. 我的冲突解决方案
  3. 他们可以点击的合并按钮

但是,我可能不想变基,因为代码审查已经在进行中(有时我无论如何都会变基,但其他时候我想避免它)。

我怎样才能可靠有效地使用 git 来实现这一目标?

我目前所做的是这些事情的混合:

有时这不起作用,因为冲突是空的,所以我不知道要注释什么才能找到正确的提交。 编辑:实际上是空的-冲突案例我想如果没有合并或变基是不可能解决的(但在其他情况下是可能的——见下面的例子)。

当它确实起作用时,git 似乎可以帮助我以更自动化的方式完成大量工作。

我也试过 git-imerge -- 这似乎不是专门为此目的而设计的,而且它也因未处理的异常而退出。

这是一个具体的工作示例,因为这里的答案存在怀疑,有时可以像我在这里描述的那样解决分支上的冲突而无需合并或变基(请注意,这并没有显示工作流程的每个步骤以上,仅演示 'resolve conflicts on branch without merge or rebase' 部分):

$ mkdir -p conflict-example/upstream
$ cd conflict-example/upstream
$ git init .
Initialised empty Git repository in /tmp/conflict-example/upstream/.git/
$ echo 'changed_only_upstream before' > changed_only_upstream
$ echo 'changed_only_downstream before' > changed_only_downstream
$ echo 'changed_in_both before' > changed_in_both
$ git add .
$ git commit -m 'initial'
[master (root-commit) 23040ea] initial
 3 files changed, 3 insertions(+)
 create mode 100644 changed_in_both
 create mode 100644 changed_only_downstream
 create mode 100644 changed_only_upstream
$ cd ..
$ git clone upstream downstream
Cloning into 'downstream'...
done.
$ cd downstream
$ git checkout -b downstream
Switched to a new branch 'downstream'
$ vim changed_in_both
$ vim changed_only_downstream
$ cat changed_in_both
changed_in_both before
downstream
$ cat changed_only_downstream
changed_only_downstream before
downstream
$ git commit -am 'downstream'
[downstream 6ead47f] downstream
 2 files changed, 2 insertions(+)
$ cd ../upstream
$ vim changed_in_both
$ vim changed_only_upstream
$ cat changed_in_both
changed_in_both before
upstream
$ cat changed_only_upstream
changed_only_upstream before
upstream
$ git commit -m 'upstream conflict' changed_in_both
[master e9ec7c5] upstream conflict
 1 file changed, 1 insertion(+)
$ git commit -m 'upstream non-conflict' changed_only_upstream
[master d4057e0] upstream non-conflict
 1 file changed, 1 insertion(+)
$ cd ../downstream/
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git pull
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
From /tmp/conflict-example/upstream
   23040ea..d4057e0  master     -> origin/master
Updating 23040ea..d4057e0
Fast-forward
 changed_in_both       | 1 +
 changed_only_upstream | 1 +
 2 files changed, 2 insertions(+)
$ git checkout downstream
Switched to branch 'downstream'
$ git merge master
Auto-merging changed_in_both
CONFLICT (content): Merge conflict in changed_in_both
Recorded preimage for 'changed_in_both'
Automatic merge failed; fix conflicts and then commit the result.
$ git merge --abort
$ git log --all --graph --pretty=oneline --abbrev-commit --decorate
* d4057e0 (origin/master, origin/HEAD, master) upstream non-conflict
* e9ec7c5 upstream conflict
| * 6ead47f (HEAD -> downstream) downstream
|/
* 23040ea initial
$ git cherry-pick e9ec7c5
error: could not apply e9ec7c5... upstream conflict
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
$ vim changed_in_both
$ cat changed_in_both
changed_in_both before
upstream
$ git add changed_in_both
$ git commit
Recorded resolution for 'changed_in_both'.
[downstream 7a4f7a7] upstream conflict
 Date: Sat Aug 27 14:41:13 2016 +0100
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --all --graph --pretty=oneline --abbrev-commit --decorate
* 7a4f7a7 (HEAD -> downstream) upstream conflict
* 6ead47f downstream
| * d4057e0 (origin/master, origin/HEAD, master) upstream non-conflict
| * e9ec7c5 upstream conflict
|/
* 23040ea initial
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git merge downstream
Merge made by the 'recursive' strategy.
 changed_only_downstream | 1 +
 1 file changed, 1 insertion(+)
$ git log --all --graph --pretty=oneline --abbrev-commit --decorate
*   c036d60 (HEAD -> master) Merge branch 'downstream'
|\
| * 7a4f7a7 (downstream) upstream conflict
| * 6ead47f downstream
* | d4057e0 (origin/master, origin/HEAD) upstream non-conflict
* | e9ec7c5 upstream conflict
|/
* 23040ea initial

我相信如果我在 cherry-pick 中选择了不同的分辨率,我将无法使用该合并命令进行合并(这至少类似于 github 合并按钮的作用).在那些情况下,通常我要么自己进行合并,要么进行变基——但这不是这个问题的目的(尽管如果有某种方法可以实现合并按钮的可点击性,而无需在这些情况下合并 master 或变基,那会很有趣听说!)。

这是一个可能的解决方法。它确实包括一个合并提交,但只有一个,那就是包含您的冲突解决方案的提交。主要问题是您将从 master.

引入不相关的更改

做:

git checkout my-feature
git merge --no-ff master

此时与其寻找要 cherry-pick 的东西,不如解决所有冲突并提交。

这会使您的历史变得有些复杂,但您将有 1 个解决冲突的提交,并且该提交将在您的功能分支上。

然后审阅者应该能够毫无问题地使用 GitHub 的一键合并按钮。

您的历史记录将如下所示:

*---*---*---B [master]
 \       \ /
  *---*---A [feature]

其中 A 是您解决冲突的提交,B 是由 GitHub 创建的合并提交。

冲突的出现只是因为更改(在 master 分支上)包含在您的功能分支中。因此,如果不从主分支引入这些更改,就无法解决功能分支上的冲突。因为那时它们根本不存在。

因此,即使有一个很好的方法来推送一个简单的补丁,以便您稍后获得一个不错的历史记录,它甚至在概念上都行不通,因为您无法解决尚未存在的问题。

因此,您实际上只有合并或变基两种选择,才能真正将那些会导致冲突的更改引入您的功能分支。根据您的拉取请求的复杂性,一种方式可能优于另一种方式,例如当你变基时历史更容易查看,而合并保持历史(包括存在冲突的信息)完整。所以选择对你最有意义的,或者询问项目所有者你应该做什么。在大多数开源项目中,如果所有者不自己合并更改,他们通常希望您根据当前的母版重新设置您的更改。

这里有一个 脚本可以完成这项工作:

#!/bin/bash

declare -a conflicts

echo "Detecting conflicts..."
for rev in `git rev-list HEAD..master`
do
  git cherry-pick --no-commit $rev > /dev/null 2>&1
  if [ $? -eq 1 ]
  then
    conflicts+=($rev)
  fi
  git reset --hard HEAD > /dev/null
done

for rev in ${conflicts[*]}
do
  git cherry-pick --no-commit $rev > /dev/null 2>&1
  echo "Commit $rev cherry-picked."
  read -p "Resolve conflicts, then press any key to continue: "
done

echo "Done cherry-picking! Commit your changes now!"

运行 这个脚本,每次出现提示时,解决文本编辑器中的任何冲突,然后从另一个 window 执行 git add。完成后可以git commit(根据提示)

到目前为止,在我的测试中,我发现这个脚本有两个问题:

  1. 当我将功能分支合并回 master 时,我遇到了一些小冲突。这些冲突比从 master 合并到功能分支时得到的冲突要小得多。事实上,它们是这样的,你可以这样做:

    git checkout master
    git merge --no-ff feature/my-feature -x theirs
    

    它应该可以工作。但是,这可能意味着 GitHub 合并按钮不起作用,而且我认为没有办法告诉 GitHub 使用 -x theirs.

    我不确定这是否仅取决于所做的相对更改,因此这可能只是由我的特定测试 repo 引起的问题。

  2. 如果你有,例如依赖于 aaa 的提交 bbb,两者都依赖于 master,那么 bbbcherry-pick 将被检测为冲突。我的测试表明,无论您是在 cherry-pick 中保留这样的更改还是丢弃它都没有关系。 (它似乎也不会影响问题 #1。)

我正在寻找这两个问题的解决方案,但这应该足以让您入门。