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.