Aller au contenu

Merging branches

Each branch has a purpose: developing a feature, fixing a bug, testing an idea. Once that task is complete, its changes are incorporated into the production version of the project, usually the main branch. This is called merging.

Source and destination

A merge always involves two branches:

Role Meaning Example
Source The branch to merge from: the one with the finished work monitoring
Destination The branch to merge into: the one that receives the work main

The latest commits of the two branches are the parent commits of the merge.

git merge

git merge always merges into the current branch. So the procedure is:

  1. Switch to the destination branch.
  2. Run git merge with the source branch.
git switch main
git merge monitoring

There is no destination argument

Every branch given to git merge is a source: git merge monitoring main, run from another branch, merges both monitoring and main into the branch you are on, and leaves main unchanged. To merge into main, be on main (git merge documentation).

Depending on the history of the two branches, Git merges in one of two ways.

Fast-forward merge

Since monitoring was created, main has not received any new commit. The history is still linear: monitoring is simply main plus one commit.

$ git switch main
Already on 'main'
$ git merge monitoring
Updating 112c587..eeb2e47
Fast-forward
 inventory.csv | 1 +
 services.md   | 1 +
 2 files changed, 2 insertions(+)

Reading the output

Line Meaning
Updating 112c587..eeb2e47 main moves from its latest commit (112c587) to the latest commit of monitoring (eeb2e47)
Fast-forward The merge type: no new commit was needed
inventory.csv \| 1 + Per-file summary: one line added, as in git diff --stat
2 files changed, 2 insertions(+) Total of the changes brought into main

In a fast-forward, Git only moves the main label forward to the last commit of the source branch. No merge commit is created, and the history stays a straight line:

flowchart LR
    subgraph before["Before the merge"]
        direction LR
        b1["112c587<br/>Create homelab inventory"] --> b2["eeb2e47<br/>Add monitoring server"]
        bm(["main"]) --> b1
        bmon(["monitoring"]) --> b2
    end
    subgraph after["After the fast-forward"]
        direction LR
        a1["112c587<br/>Create homelab inventory"] --> a2["eeb2e47<br/>Add monitoring server"]
        am(["main"]) --> a2
        amon(["monitoring"]) --> a2
    end
    before ~~~ after
$ git log --oneline --graph --all
* eeb2e47 Add monitoring server
* 112c587 Create homelab inventory

Three-way merge

Next, Sam documents the nightly backups on a new branch, backups. Meanwhile, a UPS is added to the inventory directly on main. Both branches now have a commit the other does not have: the history has diverged.

%%{init: {"gitGraph": {"showBranches": true, "rotateCommitLabel": false, "mainBranchName": "main"}, "themeVariables": {"git0": "#43a047", "git1": "#1e88e5", "git2": "#fb8c00", "gitBranchLabel0": "#ffffff", "gitBranchLabel1": "#ffffff", "gitBranchLabel2": "#ffffff", "gitInv0": "#ffffff", "commitLabelFontSize": "13px"}}}%%
gitGraph TB:
    commit id: "112c587 Create homelab inventory"
    commit id: "eeb2e47 Add monitoring server"
    branch backups
    commit id: "6f673d0 Document nightly backups"
    checkout main
    commit id: "49500df Add UPS to inventory"
    merge backups id: "d88fecf Merge branch 'backups'"

main cannot simply move forward, since that would lose the UPS commit. Git performs a three-way merge: it compares the two branch tips with their common ancestor (eeb2e47), combines the changes, and records the result in a new merge commit.

$ git switch main
Switched to branch 'main'
$ git merge backups
Merge made by the 'ort' strategy.
 backups.md | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 backups.md

Because a new commit is created, Git opens the text editor with a prepared message, Merge branch 'backups'. Save and close it to finish the merge, or skip the editor with git merge --no-edit backups. 'ort' is the name of Git's default merge strategy; create mode 100644 backups.md means that backups.md is a new file.

The graph shows the two lines of history joining:

$ git log --oneline --graph
*   d88fecf Merge branch 'backups'
|\
| * 6f673d0 Document nightly backups
* | 49500df Add UPS to inventory
|/
* eeb2e47 Add monitoring server
* 112c587 Create homelab inventory

A merge commit is the only kind of commit with two parents. git log shows them on a Merge: line:

$ git log -1
commit d88fecfea962f512358d7cf72a9993e7bb072a7a
Merge: 49500df 6f673d0
Author: Sam Rivera <[email protected]>
Date:   Mon Apr 6 10:06:00 2026 +0200

    Merge branch 'backups'

Fast-forward or three-way?

Situation Merge type New commit?
The destination has no new commits since the source branched off Fast-forward No, the destination label moves forward
Both branches have new commits Three-way merge Yes, a merge commit with two parents

Git chooses automatically. git merge --no-ff <branch> forces a merge commit even when a fast-forward is possible, which keeps a visible trace of the branch in the history. git merge --ff-only <branch> does the opposite: it merges only if a fast-forward is possible, and refuses otherwise.

When the same lines were changed on both sides, the three-way merge cannot decide on its own and stops with a conflict: see Resolving merge conflicts.

Cleaning up merged branches

Once merged, a branch has served its purpose. git branch --merged lists the branches whose commits are all included in the current branch:

$ git branch --merged
  backups
* main
  monitoring

These are safe to delete with -d, which accepts several names:

$ git branch -d monitoring backups
Deleted branch monitoring (was eeb2e47).
Deleted branch backups (was 6f673d0).

The commits stay in the history of main: only the labels are removed.

Summary

Command Result
git switch main then git merge monitoring Merge monitoring (source) into main (destination)
git merge --no-edit backups Merge, accepting the default merge commit message
git merge --no-ff monitoring Always create a merge commit, even if a fast-forward is possible
git merge --ff-only monitoring Merge only if a fast-forward is possible
git log --oneline --graph Display the history with its branches and merges
git branch --merged List the branches already merged into the current branch

Common mistakes

  • Running git merge from the wrong branch. The current branch is the destination. Check git branch first.
  • Expecting git merge source destination to merge into destination. All arguments are sources; the destination is always the current branch.
  • Being surprised by an editor during a merge. A three-way merge creates a commit and asks for its message. Save and close, or use --no-edit.
  • Keeping merged branches forever. Delete them with git branch -d once merged.

Hands-on labs

Three labs, from guided to more autonomous. Each one has its own setup, so you can do them in any order and repeat them as often as you like. Type the commands rather than pasting them, and read every output before moving on: the goal is to build reflexes, not to reach the end.

Lab 1: fast-forward and three-way merges

Objective: perform both kinds of merge, recognise them in the output and in the graph, then clean up.

Prerequisites and initial state: Git installed. The setup creates ~/git-practice/merge with one commit on main.

Setup:

mkdir -p ~/git-practice/merge && cd ~/git-practice/merge
git init -q -b main
git config user.name "Practice User"
git config user.email "[email protected]"
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
git add . && git commit -q -m "Create inventory"

Tasks:

  1. Create a branch nas, add the line nas,192.168.1.20,storage to inventory.csv, and commit with the message Add NAS.
  2. Merge nas into main. Which kind of merge happens?
  3. Create a branch docs from main, create a file README.md containing # Homelab, and commit it with the message Add README.
  4. Back on main, add the line web01,192.168.1.30,web to inventory.csv and commit with the message Add web server.
  5. Merge docs into main without opening an editor. Which kind of merge happens this time?
  6. List the merged branches and delete them.

Expected result and verification:

  • Task 2 prints Fast-forward.
  • Task 5 prints Merge made by the 'ort' strategy.
  • git log --oneline --graph shows Merge branch 'docs' at the top, with two lines of history joining below it.
  • git log -1 shows a Merge: line with two hashes.
  • git branch finally lists only * main, and main contains README.md and an inventory with three machines.
Solution
# 1. Work on a branch
git switch -c nas
echo "nas,192.168.1.20,storage" >> inventory.csv
git commit -am "Add NAS"

# 2. main has not moved: fast-forward
git switch main
git merge nas                     # Updating ...  Fast-forward

# 3. Second branch
git switch -c docs
echo "# Homelab" > README.md
git add README.md
git commit -m "Add README"

# 4. main moves on in the meantime
git switch main
echo "web01,192.168.1.30,web" >> inventory.csv
git commit -am "Add web server"

# 5. Diverged history: three-way merge with a merge commit
git merge --no-edit docs          # Merge made by the 'ort' strategy.
git log --oneline --graph
git log -1                        # Merge: <parent 1> <parent 2>

# 6. Clean up
git branch --merged               # docs, * main, nas
git branch -d nas docs
  • At task 2, main was still at the commit nas started from, so Git only moved the label.
  • At task 5, each branch had a commit the other did not have, so Git created a merge commit with two parents.
  • The two changes touched different files, so the merge needed no help. The next page covers what happens when they touch the same lines.

Clean up when you are done: rm -rf ~/git-practice/merge.

Lab 2: which way round?

Objective: see that the direction of a merge matters, by merging main into a branch first, then the branch into main.

Prerequisites and initial state: Git installed. The setup creates ~/git-practice/direction: main and backups have each received one commit since they split.

Setup:

mkdir -p ~/git-practice/direction && cd ~/git-practice/direction
git init -q -b main
git config user.name "Practice User"
git config user.email "[email protected]"
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
git add . && git commit -q -m "Create inventory"
git switch -q -c backups
printf '#!/bin/sh\nrsync -a /srv/data /mnt/backup\n' > backup.sh
git add . && git commit -q -m "Add backup script"
git switch -q main
echo "nas,192.168.1.20,storage" >> inventory.csv
git commit -q -am "Add NAS"

Tasks: before each merge, write down your prediction: fast-forward or three-way?

  1. Display the graph of all branches.
  2. Your work on backups is not finished, but you want the NAS in it to test the script. Bring main's changes into backups, without opening an editor.
  3. Display the graph. On which branch is the merge commit?
  4. The script is ready. Merge backups into main.
  5. Display the graph again, and delete backups.

Expected result and verification:

  • Task 2 is a three-way merge (Merge made by the 'ort' strategy.): both branches had moved on.
  • Task 3: Merge branch 'main' into backups is on backups; main has not changed.
  • Task 4 is a fast-forward: main is now an ancestor of backups, so Git only moves the label.
  • Task 5: main and backups point to the same merge commit, and git branch -d backups succeeds.
Solution
# 1. Diverged history
git log --oneline --graph --all

# 2. The destination is the current branch: switch to backups first
git switch backups
git merge --no-edit main          # Merge made by the 'ort' strategy.

# 3. Only backups moved
git log --oneline --graph --all   # Merge branch 'main' into backups

# 4. Now main is behind backups: fast-forward
git switch main
git merge backups                 # Fast-forward

# 5. Clean up
git log --oneline --graph --all
git branch -d backups
  • git merge X always means "bring X into the current branch". Running it from the wrong branch merges in the wrong direction.
  • Merging main into a long-lived branch keeps it up to date and lets you solve problems on the branch, before they reach main.
  • After task 2, every commit of main was already inside backups. Nothing had to be combined in task 4, hence the fast-forward.

Clean up when you are done: rm -rf ~/git-practice/direction.

Lab 3: a branch on a branch

Objective: merge a branch that was created from another branch, and see what --merged reports afterwards.

Prerequisites and initial state: Git installed. The setup creates ~/git-practice/stacked with one commit on main.

Setup:

mkdir -p ~/git-practice/stacked && cd ~/git-practice/stacked
git init -q -b main
git config user.name "Practice User"
git config user.email "[email protected]"
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
git add . && git commit -q -m "Create inventory"

Tasks:

  1. Create a branch monitoring, add mon01,192.168.1.50,monitoring to the inventory, and commit with the message Add monitoring server.
  2. From monitoring, create a branch alerts. Create a file alerts.md containing # Alerts, and commit it with the message Add alert rules.
  3. Go back to main, and list the branches that are already merged into it.
  4. Merge only alerts into main. Predict: which commits will main receive?
  5. List the merged branches again, and delete every branch except main with the safe option.

Expected result and verification:

  • Task 3: git branch --merged lists only * main.
  • Task 4 is a fast-forward, and git log --oneline on main shows Add alert rules, Add monitoring server, Create inventory.
  • Task 5: git branch --merged lists alerts, * main, and monitoring; git branch -d alerts monitoring succeeds.
Solution
# 1. First branch
git switch -c monitoring
echo "mon01,192.168.1.50,monitoring" >> inventory.csv
git commit -am "Add monitoring server"

# 2. Second branch, created from the first one
git switch -c alerts
echo "# Alerts" > alerts.md
git add alerts.md
git commit -m "Add alert rules"

# 3. Nothing merged yet
git switch main
git branch --merged               # * main

# 4. alerts contains monitoring's commit too
git merge alerts                  # Fast-forward
git log --oneline                 # 3 commits

# 5. Both branches are now merged
git branch --merged               # alerts, * main, monitoring
git branch -d alerts monitoring
  • A branch contains the whole chain of commits leading to its tip. Merging alerts brings Add monitoring server along, even though monitoring was never merged by name.
  • This is why git branch --merged reports monitoring as merged: all its commits are now in main.
  • Stacking branches is handy, but merging the top one merges everything below it.

Clean up when you are done: rm -rf ~/git-practice/stacked.