DaleSchool

Merging Branches

Beginner25min

Learning Objectives

  • Merge branches with git merge
  • Understand the difference between fast-forward and merge commits
  • Compare pre- and post-merge history with git log --graph
  • Understand the concept of conflicts in preparation for the next lesson

Working Code

Example 1: Fast-forward merge

Create a branch, do some work, and merge:

git init
echo "# Project" > README.md
git add README.md
git commit -m "feat: initial commit"

git checkout -b feature-about
echo "<h1>About</h1>" > about.html
git add about.html
git commit -m "feat: add about page"

Check the state before merging:

git log --oneline --graph --all

Output:

* a1b2c3d (HEAD -> feature-about) feat: add about page
* e4f5g6h (main) feat: initial commit

Switch to main and merge:

git checkout main
git merge feature-about

Output:

Updating e4f5g6h..a1b2c3d
Fast-forward
 about.html | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 about.html

History after merging:

git log --oneline --graph --all

Output:

* a1b2c3d (HEAD -> main, feature-about) feat: add about page
* e4f5g6h feat: initial commit

main now points to the same commit as feature-about. The history is linear.

Example 2: --no-ff merge (preserving history)

git checkout -b feature-contact
echo "<h1>Contact</h1>" > contact.html
git add contact.html
git commit -m "feat: add contact page"

git checkout main
git merge --no-ff feature-contact -m "merge: merge contact page"

Output:

Merge made by the 'ort' strategy.
 contact.html | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 contact.html

History after merging:

git log --oneline --graph --all

Output:

*   b2c3d4e (HEAD -> main) merge: merge contact page
|\
| * f7g8h9i (feature-contact) feat: add contact page
|/
* a1b2c3d feat: add about page
* e4f5g6h feat: initial commit

--no-ff preserves the branch-and-merge structure in history.

Try It Yourself

When both branches have new commits

When both main and a feature branch have progressed:

# Add a new commit to main
git checkout main
echo "// Main feature" > main-feature.js
git add main-feature.js
git commit -m "feat: add main feature"

# Feature branch also has work
git checkout -b feature-sidebar
echo "<nav>Sidebar</nav>" > sidebar.html
git add sidebar.html
git commit -m "feat: add sidebar"

Check history:

git log --oneline --graph --all

Output:

* j0k1l2m (HEAD -> feature-sidebar) feat: add sidebar
* b2c3d4e (main) merge: merge contact page
...

Since main has moved forward, fast-forward is not possible. Merging creates a merge commit automatically:

git checkout main
git merge feature-sidebar

Output:

Merge made by the 'ort' strategy.
 sidebar.html | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 sidebar.html
git log --oneline --graph --all

Output:

*   m3n4o5p (HEAD -> main) Merge branch 'feature-sidebar'
|\
| * j0k1l2m (feature-sidebar) feat: add sidebar
* | b2c3d4e merge: merge contact page
...

Cleaning up after merge

# Delete merged branches
git branch -d feature-contact
git branch -d feature-sidebar

# Check remaining branches
git branch

"Why?" -- Fast-forward vs Merge Commit

Fast-forward merge

When main has no new commits and only the feature branch has progressed, Git simply moves the pointer forward:

Before:
main -> A -> B
              ^ main
               C -> D
                     ^ feature

After:
main -> A -> B -> C -> D
                     ^ main (pointer moved forward)

The history becomes linear. There's no trace that a branch existed.

Merge commit

When both branches have new commits, or when --no-ff is used, Git creates a new merge commit:

Before:
main -> A -> B -> E
              \
               C -> D <- feature

After:
main -> A -> B -> E -> M (merge commit)
              \     /
               C -> D

The branch structure is preserved in history.

| | Fast-forward | Merge Commit | | ------- | ------------------- | -------------------------- | | History | Linear (clean) | Branch structure preserved | | When | Updating branches | PR merges, collaboration | | Option | Automatic (default) | --no-ff or automatic |

A taste of conflicts (preview for the next lesson)

When two branches modify the same line of the same file differently, a conflict occurs:

# Edit on main
echo "main content" > shared.txt
git add shared.txt && git commit -m "main edit"

# Edit the same file differently on feature
git checkout -b feature-conflict
echo "feature content" > shared.txt
git add shared.txt && git commit -m "feature edit"

git checkout main
git merge feature-conflict

Output:

CONFLICT (content): Merge conflict in shared.txt
Automatic merge failed; fix conflicts and then commit the result.

Resolving this conflict is covered in detail in Lesson 7.

Common Mistakes

Mistake 1: Merging in the wrong direction

# Wrong: merging main INTO feature
git checkout feature-login
git merge main   # main gets merged into feature (unintended direction)

# Correct: merging feature INTO main
git checkout main
git merge feature-login

Remember the direction: "Merge the target branch into the current branch."

Mistake 2: Doing other work during a conflict

# Trying to do other things while conflict is unresolved
git merge feature-conflict   # Conflict occurs
git checkout main   # Error: resolve conflicts first!

# Correct order
# 1. Resolve conflicts in the file
# 2. git add filename
# 3. git commit

Mistake 3: Not knowing how to abort a merge

# If conflict resolution is too complicated or the merge was wrong
git merge --abort   # Returns to the pre-merge state

Deep Dive

Merge strategy options
# Allow only fast-forward (error if not possible)
git merge --ff-only feature-branch

# Always create a merge commit
git merge --no-ff feature-branch

# Squash: combine all feature commits into one and stage them
git merge --squash feature-branch
git commit -m "feat: add feature (squash)"

Agreeing on a consistent merge strategy within your team makes history management easier.

Using git log --all --graph --decorate
# Most detailed graph view
git log --all --graph --oneline --decorate

# Include dates
git log --all --graph --format="%h %ai %s"

# Compare specific branches
git log main..feature-login   # Commits only in feature-login
git log feature-login..main   # Commits only in main
  1. Create a repository and make 2 commits on main.
  2. Create a feature-page branch and add 2 commits.
  3. Switch back to main and run git merge feature-page for a fast-forward merge.
  4. Check the history with git log --oneline --graph --all.
  5. Create a new feature-nav branch, add a new commit to main as well, then run git merge --no-ff feature-nav to create a merge commit. Compare the history.

Q1. What does the git merge --no-ff option do?

  • A) Ignores conflicts and forces the merge
  • B) Creates a merge commit even when fast-forward is possible
  • C) Deletes the branch while merging
  • D) Asks for confirmation before merging