Learning Notes - Git rebase vs Git merge

Git merge

Normally, to get the latest update from main branch during development the feature or fix branch, I would checkout to the main branch and git pull the latest commits and then checkout back and run the merge command.

 1git checkout main
 2git pull
 3git checkout FEATURE-BUG-BRANCH
 4git merge --no-ff development
 5
 6or 
 7
 8git checkout main
 9git pull
10git merge FEATURE-BUG-BRANCH main

It works and will create a MERGE commit in the feature branch. It's OK because it's non-destructive operation. But it will always have an extraneous merge commit in the history, which may be fine to have it at the end of development, but not good during the development. So is there a possibility to merge the main branch into our feature branch without a merge commit?

Git rebase

The answer is git rebase. We can try the following:

1git checkout FEATURE-BUG-BRANCH
2git rebase main

This will move the whole feature branch to beginning of the main branch. And instead of creating a merge commit, it will re-write the whole history by making new commits, even there is some merge commits in the feature branch before.

The benefit of rebasing would be, first, there is no unrequired merge commits, second, the git history is quite linear, the main branch would be behind the feature branch. However, all of these should be done when there is only one developer, no collaborator. Because rebasing would re-write the commits, so if you collaborate with other developers, the commits from main branch would be different from the public main branch. That would be a hard situation.

So before using git rebase, ask yourself, is there another developer working together with you on this branch. If the answer is yes, then use merge instead.

What happen when rebase mixed with merge

Let's do some experiment, creating a main and feature branch.

 1> git checkout -b main
 2Switched to a new branch 'main'
 3(main)> touch test.js
 4(main)> git add test.js
 5(main)> git commit -m "init"
 6[main (root-commit) b7c27e8] init
 7 1 file changed, 0 insertions(+), 0 deletions(-)
 8 create mode 100644 test.js
 9
10// feature branch
11(main)> git checkout -b feature
12Switched to a new branch 'feature'
13(feature)> touch feature.js
14(feature)> git add feature.js
15(feature)> git commit -m "feature.js"
16[feature 4d60997] feature.js
17 1 file changed, 0 insertions(+), 0 deletions(-)
18 create mode 100644 feature.js

So we have one commit in both feature and main branch. Let's create one more commit for each branch.

 1(feature)> git co main
 2Switched to branch 'main'
 3> touch main.js
 4(main)> git add main.js
 5(main)> git commit -m "main.js"
 6[main c593f25] main.js
 7 1 file changed, 0 insertions(+), 0 deletions(-)
 8 create mode 100644 main.js
 9
10(main)> git co feature
11Switched to branch 'feature'
12(feature)> touch feature2.js
13(feature)> git add feature2.js
14(feature)> git commit -m "feature2.js"
15[feature 193b518] feature2.js
16 1 file changed, 0 insertions(+), 0 deletions(-)
17 create mode 100644 feature2.js

Now, let's merge the main to the feature and check how the history looks like

 1(feature)> git merge --no-ff main
 2Merge made by the 'ort' strategy.
 3 main.js | 0
 4 1 file changed, 0 insertions(+), 0 deletions(-)
 5 create mode 100644 main.js
 6
 7(feature)> git log --oneline --graph
 8*   e99a53e (HEAD -> feature) Merge branch 'main' into feature
 9|\
10| * c593f25 (main) main.js
11* | 193b518 feature2.js
12* | 4d60997 feature.js
13|/
14* b7c27e8 init

Yes, a merge commit is created with merging. Let's check how it looks like with rebasing

 1(feature)> git co main
 2Switched to branch 'main'
 3(main)> touch main2.js
 4(main)> git add main2.js
 5(main)> git commit -m "main2.js"
 6[main 6c43d0f] main2.js
 7 1 file changed, 0 insertions(+), 0 deletions(-)
 8 create mode 100644 main2.js
 9
10(main)> git co feature
11Switched to branch 'feature'
12(feature)> touch feature3.js
13(feature)> git add feature3.js
14(feature)> git commit -m "feature3.js"
15[feature dc5fbcb] feature3.js
16 1 file changed, 0 insertions(+), 0 deletions(-)
17 create mode 100644 feature3.js
18
19 (feature)> git rebase main
20Successfully rebased and updated refs/heads/feature.
21(feature)> git log --oneline --graph
22* 3caa16c (HEAD -> feature) feature3.js
23* cd53eed feature2.js
24* 595209c feature.js
25* 6c43d0f (main) main2.js
26* c593f25 main.js
27* b7c27e8 init

We can see the feature branch locates on the top of main branch and the history is linear, even we actually have created a merge commit before.

And all the commits in the main branch are kept, while all the commits in feature are, as we said, re-write as new commits, we can see the commit ids/hashes are different.

Let's do one more step, merge the main to feature again.

 1(feature)> git co main
 2Switched to branch 'main'
 3(main)> touch main-after-rebase.js
 4(main)> git add main-after-rebase.js
 5(main)> git commit -m "main after rebase"
 6[main 92bd62e] main after rebase
 7 1 file changed, 0 insertions(+), 0 deletions(-)
 8 create mode 100644 main-after-rebase.js
 9
10 (main)> git co feature
11Switched to branch 'feature'
12(feature)> touch feature-after-main-after-rebase.js
13(feature)> git add feature-after-main-after-rebase.js
14(feature)> git commit -m "feature after main after rebase"
15[feature 2556a80] feature after main after rebase
16 1 file changed, 0 insertions(+), 0 deletions(-)
17 create mode 100644 feature-after-main-after-rebase.js
18
19(feature)> git merge --no-ff main
20Merge made by the 'ort' strategy.
21 main-after-rebase.js | 0
22 1 file changed, 0 insertions(+), 0 deletions(-)
23 create mode 100644 main-after-rebase.js
24(feature)> git log --oneline --graph
25*   f162af2 (HEAD -> feature) Merge branch 'main' into feature
26|\
27| * 92bd62e (main) main after rebase
28* | 2556a80 feature after main after rebase
29* | 3caa16c feature3.js
30* | cd53eed feature2.js
31* | 595209c feature.js
32|/
33* 6c43d0f main2.js
34* c593f25 main.js
35* b7c27e8 init

So the merge commit is created as expected, but it's based on the main2.js commit, not from the beginning.

comments powered by Disqus