In order to solve conflicts, you just have to understand what commit you are trying to move where.
E.g. if from:
5 master
|
4 7 my-feature HEAD
| |
3 6
|/
2
|
1
we do:
git rebase master
what happens step by step is first 6 is moved on top of 5:
6on5 HEAD
|
5 master
|
4                 7 my-feature
|                 |
3                 6
|                 |
2-----------------+
|
1
and then 7 is moved on top of the new 6:
7on5 HEAD
|
6on5
|
5 master
|
4                 7 my-feature
|                 |
3                 6
|                 |
2-----------------+
|
1
All good? so OK, let's move the my-feature to the new 7:
7on5 my-feature HEAD
|
6on5
|
5 master
|
4
|
3
|
2
|
1
The key to solve conflicts is:
You have to understand what are the two commits that touched a given line (one from master, one from features), and then combine them somehow.
Or in other words, at every rebase conflict we have something like:
master-commit    feature-commit
|                |
|                |
base-commit------+
|
|
Therefore there are 2 diffs that you have to understand and reconcile:
  • base-commit to master-commit
  • base-commit to feature-commit
diff3 conflict is basically what you always want to see, either by setting it as the default as per stackoverflow.com/questions/27417656/should-diff3-be-default-conflictstyle-on-git:
git config --global merge.conflictstyle diff3
or as a one off:
git checkout --conflict=diff3
With this, conflicts now show up as:
++<<<<<<< HEAD
 +5
++||||||| parent of 7b0f59d (6)
++3
++=======
+ 6
++>>>>>>> 7b0f59d (6)
7b0f59d is the SHA-2 of commit 6.
instead of the inferior default:
++<<<<<<< ours
 +5
++=======
+ 6
++>>>>>>> theirs
We can also observe the current tree state during resolution:
* b4ec057 (HEAD, master) 5
* 0b37c1b 4
| * fbfbfe8 (my-feature) 7
| * 7b0f59d 6
|/
* 661cfab 3
* 6d748a9 2
* c5f8a2c 1
so we understand that we are now at 5 and that we are trying to apply our commit 6
So it is much clearer what is happening:
  • master changed the code from 3 to 5
  • our feature changed the code from 3 to 6
and so now we have to decide what the new code is that will put both of these together.
Let's say we decide it is 5 + 6 = 11 and continue rebasing:
git add .
git rebase --continue
We now reach:
++<<<<<<< HEAD
 +11
++||||||| parent of fbfbfe8 (7)
++6
++=======
+ 7
++>>>>>>> fbfbfe8 (7)
and the tree looks like:
* ca7f7ff (HEAD) 6
* b4ec057 (master) 5
* 0b37c1b 4
| * fbfbfe8 (my-feature) 7
| * 7b0f59d 6
|/
* 661cfab 3
* 6d748a9 2
* c5f8a2c 1
So we understand that:
  • after the previous step we added commit 6 on top of 5
  • now we are adding 7 on top of the new 6 (which we decided would contain 11)
and after resolving that one we now reach:
* e1aaf20 (HEAD -> my-feature) 7
* ca7f7ff 6
* b4ec057 (master) 5
* 0b37c1b 4
* 661cfab 3
* 6d748a9 2
* c5f8a2c 1
These are good free newbie GUI options:
sudo apt install meld
git mergetool --tool meld

sudo apt install kdiff3
git mergetool --tool kdiff3
https://raw.githubusercontent.com/cirosantilli/media/master/meld.png
https://raw.githubusercontent.com/cirosantilli/media/master/kdiff3.png
Let's make a more interesting conflict:
git-tips-2.sh
#!/usr/bin/env bash

set -eux

add() (
  rm -f f
  for i in `seq 10`; do
    printf "before $i\n\n" >> f
  done
  printf "conflict 1 $1\n\n" >> f
  for i in `seq 10`; do
    printf "middle $i\n\n" >> f
  done
  printf "conflict 2 $2\n\n" >> f
  for i in `seq 10`; do
    printf "after $i\n\n" >> f
  done
  git add f
)

rm -rf git-tips-2
mkdir git-tips-2
cd git-tips-2
git init

for i in 1 2 3; do
  add $i $i
  git commit -m $i
done

add 3 4
git commit -m 4

add 5 4
git commit -m 5

git checkout HEAD~2
git checkout -b my-feature

add 3 6
git commit -m 6

add 7 6
git commit -m 7
git rebase does not tell you that, and that sucks.
We only know which commit from the feature branch caused the problem.
Generally we can guess or it is not needed, but imerge does look promising: stackoverflow.com/questions/18162930/how-can-i-find-out-which-git-commits-cause-conflicts