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
- Create a repository and make 2 commits on main.
- Create a
feature-pagebranch and add 2 commits. - Switch back to main and run
git merge feature-pagefor a fast-forward merge. - Check the history with
git log --oneline --graph --all. - Create a new
feature-navbranch, add a new commit to main as well, then rungit merge --no-ff feature-navto 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