Croad Langshan Croad Langshan - 3 months ago 10
Git Question

How to resolve git conflicts on branch without merging or rebasing

My usual feature/bug branch workflow is like this:


  • Branch

  • Make changes

  • Rebase to master

  • Push and make github pull request (PR)

  • Make more changes

  • Somebody else reviews code and clicks the github merge button



Let's assume the merge button on the PR can't be clicked because my feature branch now has conflicts with master. At this point I typically want to resolve the conflicts with master, and I want to do that on my feature branch so that I can have the person who is reviewing my code see:


  1. A nice diff that doesn't have merge commits and/or random changes from master

  2. My conflict resolutions

  3. A merge button they can click



However, I may not want to rebase because code review is in progress already (sometimes I rebase anyway, but other times I want to avoid it).

How can I reliably and efficiently use git to achieve that?

What I currently do is a mixture of these things:


  • "Just knowing" what needs cherry picking or changing to resolve the conflict (sometimes...)

  • Trial merges (
    git merge master
    ; (see which file has conflicts, then git annotate to find relevant commit);
    git reset --hard origin/my-feature-branch
    ;
    git cherry-pick <some commit>
    (or maybe make some change by hand); (repeat))



Sometimes that doesn't work because the conflicts are empty, so I don't know what to annotate to find the right commit. Edit: in fact in the empty-conflict case I imagine it's not possible to resolve without merge or rebase (but it is possible in other cases -- see example below).

When it does work, it seems like it's a lot of work that git might be able to help me do in a more automated way.

I've tried
git-imerge
also -- that doesn't seem designed exactly for this purpose, and it also exited with an unhandled exception.

Here is a concrete working example, since there is skepticism in answers here that it is sometimes possible to resolve conflicts on a branch as I describe here without merging or rebasing (note this doesn't show every step of the workflow above, and demonstrates only the 'resolve conflicts on branch without merge or rebase' part):

$ 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


I believe if I had chosen a different resolution in the cherry-pick, I wouldn't have been able to merge using that merge command (which is at least similar to what github merge button does). In those situations, typically I either do the merge myself or rebase -- but that is not what this question is about (though if there's some way to achieve merge button-clickability without merging master or rebasing in those situations too it'd be interesting to hear about!).

Answer

Here is a script that does the job:

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

Run this script, and each time you are prompted, resolve any conflicts in your text editor, and do git add from another window. When you are finished, you can git commit (as prompted).

During my testing so far, I have found two problems with this script:

  1. When I merge the feature branch back to master, I get a couple small conflicts. These conflicts are much smaller than what you get if merging from master to the feature branch. In fact, they are such that you can do:

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

    and it should work. However, this probably means that the GitHub Merge button would not work, and I don't think there's a way to tell GitHub to use -x theirs.

    I'm not sure if this just depends on the relative changes made, so this may just be an issue caused by my particular test repo.

  2. If you have e.g. a commit bbb that depends on aaa, both on master, then the cherry-pick of bbb will be detected as a conflict. My testing indicates that it doesn't matter whether you keep such a change in your cherry-pick or discard it. (It doesn't seem to affect issue #1 either.)

I'm looking for solutions to both of these issues, but that should be enough to get you started.

Comments