如何在不合并或重新定位的情况下解决分支上的git冲突

时间:2016-08-25 17:36:53

标签: git github git-merge

我常用的功能/错误分支工作流程如下:

  • 进行更改
  • Rebase to master
  • 推送并制作github拉取请求(PR)
  • 进行更多更改
  • 其他人查看代码并点击github合并按钮

我们假设PR上的合并按钮无法点击,因为我的功能分支现在与master有冲突。此时我通常想要解决与master的冲突,并且我想在我的功能分支上执行,以便我可以让正在查看我的代码的人看到:

  1. 一个很好的差异,没有合并提交和/或来自主
  2. 的随机更改
  3. 我的冲突解决方案
  4. 他们可以点击的合并按钮
  5. 但是,我可能不想重新定位,因为代码审查已经在进行中(有时候我会反思,但有时我想避免它)。

    如何可靠有效地使用git来实现这一目标?

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

    • "只知道"什么需要樱桃挑选或改变以解决冲突(有时......)
    • 试用合并(git merge master;(查看哪个文件存在冲突,然后git注释以查找相关提交); git reset --hard origin/my-feature-branch; git cherry-pick <some commit>(或者可以手动进行一些更改);(重复))

    有时候因为冲突是空的而无法工作,所以我不知道要注释什么来找到正确的提交。编辑:事实上在空冲突中我认为如果没有合并或变基,它就无法解决(但在其他情况下可能会这样 - 见下面的例子)。

    当它发挥作用时,似乎git可能能够以更自动化的方式帮助我做很多工作。

    我也试过了git-imerge - 这似乎并不是为了这个目的而设计的,并且它也会以未处理的异常退出。

    这是一个具体的工作示例,因为这里的答案存在怀疑,有时可以解决分支上的冲突,就像我在这里描述的那样没有合并或重新定位(注意,这并没有显示工作流程的每一步)上面,并且仅演示了在没有合并或变基的情况下解决分支上的冲突&#39;部分):

    $ 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
    

    我相信如果我在樱桃选择中选择了不同的分辨率,我将无法使用该合并命令进行合并(这至少类似于github合并按钮所做的那样)。在这些情况下,通常我要么自己进行合并,要么使用rebase - 但这不是这个问题的关键所在(尽管如果有一些方法可以实现合并按钮可点击性而不会在这些情况下合并主人或变基) #39;听到有趣的事情!)。

3 个答案:

答案 0 :(得分:3)

这是一个完成工作的脚本:

#!/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!"

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

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

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

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

    它应该有效。但是,这可能意味着GitHub Merge按钮不起作用,我认为没有办法告诉GitHub使用-x theirs

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

  2. 如果您有例如bbb上依赖于aaa的提交mastercherry-pick的{​​{1}}将被检测为冲突。我的测试表明,您是否在bbb中保留此类更改或将其丢弃并不重要。 (它似乎也不会影响问题#1。)

  3. 我正在寻找这两个问题的解决方案,但这应该足以让你开始。

答案 1 :(得分:2)

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

所以,即使有一个很好的方法来推送一个简单的补丁,所以你以后得到一个很好的历史,它甚至根本无法在概念上工作,因为你无法解决那些尚未解决的问题。

因此,您实际上只有两种合并或重新定义的选择,以实际引入那些会导致冲突进入您的功能分支的更改。根据您的拉取请求的复杂程度,一种方式可能优于另一种方式,例如当您对其进行重新定义时,历史记录更容易查看,而合并会保留历史记录(包括存在冲突的信息)。因此,选择对您最有意义的内容,或者询问项目所有者您应该做什么。在大多数开源项目中,如果所有者不自行合并更改,他们通常会希望您根据当前主数据重新定义更改。

答案 2 :(得分:0)

这是一种可行的解决方法。它确实包含合并提交,但只包含一个,这是包含冲突解决方案的提交。主要问题是您将从master引入无关的更改。

执行:

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

不要在那时寻找cherry-pick的东西,而是解决任何冲突并提交。

这有一点使你的历史复杂化的缺点,但你将有1个提交来解决冲突,并且提交将在你的功能分支上。

然后,审阅者应该可以使用GitHub的一键合并按钮而不会出现问题。

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

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

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