Merging
git merge brings the work from one branch into another. Depending on how the branches have moved, Git either simply moves the branch label forward (a fast-forward) or creates a merge commit that ties the two lines of history together.
A fast-forward merge
Sam creates a branch for desserts and makes two commits on it, while main stays where it was:
git switch -c desserts echo "Brownies: chocolate, butter, sugar" > brownies.txt git add . && git commit -m "Add brownie recipe" echo "Tiramisu: mascarpone, coffee, sponge" > tiramisu.txt git add . && git commit -m "Add tiramisu recipe"
Switched to a new branch 'desserts' [desserts 25437c2] Add brownie recipe 1 file changed, 1 insertion(+) create mode 100644 brownies.txt [desserts 005385a] Add tiramisu recipe 1 file changed, 1 insertion(+) create mode 100644 tiramisu.txt
To merge, switch to the branch that should receive the work, then name the branch to merge in:
git switch main git merge desserts
Switched to branch 'main' Updating 90c0089..005385a Fast-forward brownies.txt | 1 + tiramisu.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 brownies.txt create mode 100644 tiramisu.txt
"Fast-forward" means no new commit was needed. main hadn't moved since desserts was created, so the desserts commits already follow on from it; Git just slid the main label forward:
git log --oneline --graph
* 005385a (HEAD -> main, desserts) Add tiramisu recipe * 25437c2 Add brownie recipe * 90c0089 Add omelette recipe * c4f5e47 Add pancake recipe
A three-way merge
Now both branches get new commits. Sam starts a soups branch, and meanwhile a fix lands on main:
git switch -c soups echo "Tomato soup: tomatoes, onion, stock" > tomato-soup.txt git add . && git commit -m "Add tomato soup" git switch main echo "Omelette: eggs, butter, chives" > omelette.txt git commit -am "Add chives to omelette"
Switched to a new branch 'soups' [soups 3fd4708] Add tomato soup 1 file changed, 1 insertion(+) create mode 100644 tomato-soup.txt Switched to branch 'main' [main 726f0fb] Add chives to omelette 1 file changed, 1 insertion(+), 1 deletion(-)
git log --oneline --graph --all
* 726f0fb (HEAD -> main) Add chives to omelette | * 3fd4708 (soups) Add tomato soup |/ * 005385a (desserts) Add tiramisu recipe * 25437c2 Add brownie recipe * 90c0089 Add omelette recipe * c4f5e47 Add pancake recipe
The branches have diverged. A fast-forward is impossible, so Git performs a three-way merge: it compares both branch tips with their common ancestor and combines the changes, then records the result in a new merge commit that has two parents.
git merge soups
Merge made by the 'ort' strategy. tomato-soup.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 tomato-soup.txt
Without --no-edit, Git opens your editor so you can adjust the default message "Merge branch 'soups'"; accepting it is normal.
git log --oneline --graph
* 96670ee (HEAD -> main) Merge branch 'soups' |\ | * 3fd4708 (soups) Add tomato soup * | 726f0fb Add chives to omelette |/ * 005385a (desserts) Add tiramisu recipe * 25437c2 Add brownie recipe * 90c0089 Add omelette recipe * c4f5e47 Add pancake recipe
"ort" in the output is the name of Git's default merge strategy; you can ignore it.
Forcing a merge commit
Some teams like every feature to show up as a merge commit, even when a fast-forward is possible, so the history records which commits belonged together. --no-ff does that:
git switch -c breads echo "Soda bread: flour, buttermilk, soda" > soda-bread.txt git add . && git commit -m "Add soda bread" git switch main git merge --no-ff --no-edit breads git log --oneline --graph -4
Switched to a new branch 'breads' [breads 60fdcc6] Add soda bread 1 file changed, 1 insertion(+) create mode 100644 soda-bread.txt Switched to branch 'main' Merge made by the 'ort' strategy. soda-bread.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 soda-bread.txt * f76132b (HEAD -> main) Merge branch 'breads' |\ | * 60fdcc6 (breads) Add soda bread |/ * 96670ee Merge branch 'soups' |\ | * 3fd4708 (soups) Add tomato soup
The opposite, --ff-only, merges only if a fast-forward is possible and refuses otherwise — useful when you want to be sure nothing unexpected gets merged.
Squash merges
git merge --squash branch takes all the changes from a branch and stages them as one set, without making a merge commit. You then commit them yourself as a single ordinary commit. The branch's individual commits don't appear in main's history. Hosting services offer the same thing as "Squash and merge" on pull requests (lesson 36).
After merging
A merged branch has done its job. Delete it to keep the branch list tidy — -d succeeds because its work is in main:
git branch --merged
breads desserts * main soups
git branch -d desserts soups breads
Deleted branch desserts (was 005385a). Deleted branch soups (was 3fd4708). Deleted branch breads (was 60fdcc6).
Merging tips
- Start clean. Commit or stash your changes before merging; Git refuses to merge over uncommitted changes in files it needs to touch.
- Merge often. The longer branches live apart, the more they diverge and the more likely conflicts become. Bringing
maininto your feature branch now and then (git merge mainwhile on the feature branch) keeps it current. - Undoing. Right after a merge that you haven't pushed,
git reset --hard HEAD~1(orORIG_HEAD, which Git sets to where you were before the merge) takes it back. After pushing, usegit revert -m 1(lesson 19).
When both branches change the same lines, Git can't combine them automatically — that's a merge conflict, the topic of the next lesson.
Try it yourself
Create two branches from main. Merge the first with a fast-forward. Then commit on both main and the second branch and merge again — check with git log --oneline --graph that you got a merge commit with two parents.