Ciro Santilli OurBigBook.com $£ Sponsor €¥ 中国独裁统治 China Dictatorship 新疆改造中心、六四事件、法轮功、郝海东、709大抓捕、2015巴拿马文件 邓家贵、低端人口、西藏骚乱
git-tips.bigb
= Git tips
{scope}

This is a quick presentation that goes over some of the most common difficulties people find with <Git>.

= Understand the commit tree
{parent=Git tips}

This is the most important thing to understand Git!

You must:
* be able to visualize the commit tree
* understand how each git command modifies the commit DAG

= It's not a tree, it's actually a DAG
{title2=Directed Acyclic Graph}
{parent=Git tips}

Every <tree (data structure_> is a <directed acyclic graph>.

But not every <directed acyclic graph> is a tree.

Example of a tree (and therefore also a DAG):
``
5
|
4 7
| |
3 6
|/
2
|
1
``
Convention in this presentation: arrows implicitly point up, just like in a `git log`, i.e.:
* 1 is parent of 2
* 2 is parent of 3 and 6
* 3 is parent of 4
and so on.

Example of a DAG that is not a tree:
``
7
|\
4 6
| |
3 5
|/
2
|
1
``
This is not a tree because there are two ways to reach 7:
* 2, 3, 4, 7
* 2, 5, 6, 7

But we often say "tree" intead of "DAG" in the context of Git because DAG sounds ugly.

Example of a graph that is not a DAG:
``
6
^
|
3->4
^  |
|  v
2<-5
^
|
1
``
This one is not acyclic because there is a cycle 2, 3, 4, 5, 2.

= Why is Git a DAG?
{parent=Git tips}

Because a Git commit can have more than 1 parent due to merge commits when you do:
``
git merge
``

It can even have more than 2, there's no limit. Although that is not so common (with good reason, 2 is already one too many): https://softwareengineering.stackexchange.com/questions/314215/can-a-git-commit-have-more-than-2-parents/377903#377903

= Linear history vs branching
{parent=Git tips}

There are two ways to organize a project:
* linear history
* branched history: history with merge commits

Some people like merges, but they are ugly and stupid. Rebase instead and keep linear history.

Linear history:
``
5 master
|
4
|
3
|
2
|
1 first commit
``

Branched history:
``
7   master
|\
| \
6  \
|\  \
| |  |
3 4  5
| |  |
| /  /
|/  /
2  /
| /
1/  first commit
``

Here commits 6 and 7 are the so called "merge commits":
* they have multiple parents:
  * 6 has parents 3 and 4
  * 7 has parents 5 and 6
* they are useless and don't contain any real information

Which type of tree do you think will be easier to understand and maintain?

????

????????????

You may disconnect now if you still like branched history.

= How to visualize the commit tree
{parent=Git tips}

Generate a minimal test repo. You should get in the habit of doing this to test stuff out.
``
#!/usr/bin/env bash

mkdir git-tips
cd git-tips
git init

for i in 1 2 3 4 5; do
  echo $i > f
  git add f
  git commit -m $i
done

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

for i in 6 7; do
  echo $i > f
  git add f
  git commit -m $i
done
``

= gitk
{parent=How to visualize the commit tree}

For the newbs.

Slick? No. But <gitk> does the job, like any one of the other 100 billion free <Git UI> viewers out there

``
gitk master HEAD
``

\Image[https://raw.githubusercontent.com/cirosantilli/media/master/gitk.png]
{height=500}

Many <IDEs> are also implementing this now (e.g. <VS Code>, <Eclipse (IDE)>. Most free IDE GIt implementations are still crap, but that is the future, because you want to edit, view history, edit, view history, commit, edit.

= `git log --graph`
{parent=How to visualize the commit tree}

For the strong.

``
git log --abbrev-commit --decorate --graph --pretty=oneline master HEAD
``

Output:
``
* b4ec057 (master) 5
* 0b37c1b 4
| * fbfbfe8 (HEAD -> my-feature) 7
| * 7b0f59d 6
|/
* 661cfab 3
* 6d748a9 2
* c5f8a2c 1
``

If we also add the `--simplify-by-decoration`, which you very often want want on a real repository with many commits:
``
* b4ec057 (master) 5
| * fbfbfe8 (HEAD -> my-feature) 7
|/
* c5f8a2c 1
``
As we can see, this removes any commit that is neither:
* under a branch or tag
* at the intersection of too branches or tags

= How to modify the commit tree
{parent=Git tips}

Option 1) `git commit`. Doh!!!

Option 2) `git rebase`. Basically allows you to do arbitrary modifications to the tree. The most important ones are:

= git rebase 101
{parent=How to modify the commit tree}

= Move your branch on top of newest master
{parent=git rebase 101}

Before:
``
5 master
|
4 7 my-feature HEAD
| |
3 6
|/
2
|
1
``

Action:
``
git rebase
``

After:
``
7 my-feature HEAD
|
6
|
5 master
|
4
|
3
|
2
|
1
``
Ready to push with linear history!

= Modify contents of an old commit in your branch
{parent=git rebase 101}

Before:
``
7 my-feature HEAD
|
6
|
5 master
|
4
|
3
|
2
|
1
``

Oh, commit 6 was crap:
``
git rebase -i HEAD~2
``

Mark `6` to be modified.

After:
``
7 my-feature HEAD
|
6v2
|
5 master
|
4
|
3
|
2
|
1
``
Better now, ready to push.

Note: history changes change all commits SHAs. All parents are considereEven time is considered. So is commit message/author. And obviously file contents. So now commit "7" will actually have a different SHA.

= Merge two or more commits into one
{parent=Git rebase 101}

Before
``
7 my-feature HEAD
|
6
|
5 master
|
4
|
3
|
2
|
1
``

Oh, commit 6 was just a temporary step, should be put together with commit 7:
``
git rebase -i HEAD~2
``

Mark `6` to be squashed.

After:
``
67 my-feature HEAD
|
5 master
|
4
|
3
|
2
|
1
``
Better now, ready to push.

= Oh, but there are 2 trees: local and remote
{parent=Git tips}

Oh but there are usually 2 trees: local and remote.

So you also have to learn how to observe and modify and sync with the remote tree!

But basically:
``
git fetch
``
to update the remote tree. And then you can use it exactly like any other branch, except you prefix them with the remote (usually `origin/*`), e.g.:
* `origin/master` is the latest fetch of the remote version of `master`
* `origin/my-feature`  is the latest fetch of the remote version of `my-feature`

= Merge conflicts
{parent=Git tips}

= git rebase moves commits one by one
{parent=Merge conflicts}

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: see the two conflicting diffs
{parent=Merge conflicts}

The key to solve conflicts is:
\Q[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`

= Conflict resolution tool
{parent=Merge conflicts}

= `diff3`
{parent=Conflict resolution tool}

`diff3` conflict is basically what you always want to see, either by setting it as the default as per https://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
``

= `git mergetool` with `meld` or `kdiff3`
{parent=Conflict resolution tool}

These are good free newbie GUI options:
``
sudo apt install meld
git mergetool --tool meld

sudo apt install kdiff3
git mergetool --tool kdiff3
``

\Image[https://raw.githubusercontent.com/cirosantilli/media/master/meld.png]
{height=500}

\Image[https://raw.githubusercontent.com/cirosantilli/media/master/kdiff3.png]
{height=500}

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
``

= But which commit from master did we conflict with exactly?
{parent=Conflict resolution tool}

`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: https://stackoverflow.com/questions/18162930/how-can-i-find-out-which-git-commits-cause-conflicts