DevToolBoxGRATIS
Blogg

Git Rebase vs Merge: Når skal du bruke hva

12 minby DevToolBox

Understanding the difference between git rebase and git merge is essential for every developer working with Git. Both commands integrate changes from one branch into another, but they do so in fundamentally different ways that affect your project history, team workflow, and conflict resolution process. This guide covers both strategies in depth with visual diagrams, practical examples, and team workflow recommendations.

How Git Merge Works

Git merge creates a new merge commit that has two parent commits, combining the histories of both branches. The original branch history is preserved exactly as it happened. This is a non-destructive operation.

When you run git merge feature from the main branch, Git finds the common ancestor of both branches, creates a new commit that combines the changes, and advances the main branch pointer to this new merge commit.

How Git Rebase Works

Git rebase rewrites history by moving your branch commits to the tip of another branch. Instead of creating a merge commit, rebase replays each commit from your branch on top of the target branch, creating brand new commits with different SHA hashes.

When you run git rebase main from a feature branch, Git identifies the common ancestor, temporarily removes your feature commits, fast-forwards to the tip of main, and then re-applies your commits one by one on top.

Visual Comparison

BEFORE (both branches have diverged from common ancestor C2):

          C5---C6---C7  (feature)
         /
  C1---C2---C3---C4     (main)

AFTER git merge feature (from main):

          C5---C6---C7
         /            \
  C1---C2---C3---C4----M8  (main) ← merge commit M8 has 2 parents
                            (feature still points to C7)

AFTER git rebase main (from feature):

  C1---C2---C3---C4                     (main)
                   \
                    C5'---C6'---C7'     (feature) ← new commits (different SHAs)

  Then fast-forward merge:
  C1---C2---C3---C4---C5'---C6'---C7'  (main, feature) ← linear history

Feature Comparison

Aspectgit mergegit rebase
HistoryPreserves all history + merge commitLinear history (no merge commits)
Commit SHAsOriginal SHAs preservedNew SHAs created (rewritten)
SafetyNon-destructive (never rewrites history)Destructive (rewrites commit history)
Conflict resolutionResolve once in merge commitMay resolve per-commit during replay
Team-friendlySafe for shared branchesDangerous on shared branches
RevertingEasy (revert merge commit)Harder (find original commits)
git bisectCan be noisy with merge commitsClean linear history helps bisect
Graph visualizationComplex graph with branchesClean single line

Git Merge in Detail

Fast-Forward Merge

When the target branch has no new commits since the feature branch diverged, Git can simply move the branch pointer forward. No merge commit is created.

# Fast-forward merge (no merge commit created)
git checkout main
git merge feature

# Before:
# C1---C2 (main)
#        \
#         C3---C4 (feature)

# After:
# C1---C2---C3---C4 (main, feature)
# main pointer simply moved forward

Three-Way Merge

When both branches have diverged (have new commits), Git performs a three-way merge using the common ancestor, the tip of each branch, and creates a merge commit.

# Three-way merge (creates merge commit)
git checkout main
git merge feature

# Before:
#        C3---C4 (feature)
#       /
# C1---C2---C5---C6 (main)

# After:
#        C3---C4
#       /       \
# C1---C2---C5---C6---M7 (main)  ← merge commit M7

--no-ff: Always Create Merge Commit

The --no-ff flag forces Git to create a merge commit even when a fast-forward is possible. This preserves the branch history and makes it clear when features were integrated.

# Force merge commit even when fast-forward is possible
git checkout main
git merge --no-ff feature

# Before:
# C1---C2 (main)
#        \
#         C3---C4 (feature)

# After (with --no-ff):
# C1---C2---------M5 (main)  ← merge commit preserves branch history
#        \       /
#         C3---C4 (feature)

# After (without --no-ff, default):
# C1---C2---C3---C4 (main, feature)  ← no evidence of branch

Git Rebase in Detail

Basic Rebase

A standard rebase replays your commits on top of the target branch.

# Basic rebase workflow
git checkout feature
git rebase main

# Before:
#        C3---C4 (feature)
#       /
# C1---C2---C5---C6 (main)

# After rebase:
#                     C3'---C4' (feature)  ← new commits!
#                    /
# C1---C2---C5---C6 (main)

# Then merge (fast-forward):
git checkout main
git merge feature
# C1---C2---C5---C6---C3'---C4' (main, feature)  ← linear!

Interactive Rebase

Interactive rebase (git rebase -i) lets you modify, squash, reorder, or drop commits before replaying them. This is one of Git's most powerful features for cleaning up commit history.

# Interactive rebase - clean up last 4 commits
git rebase -i HEAD~4

# Editor opens with:
pick abc1234 Add user model
pick def5678 Fix typo in user model
pick ghi9012 Add user validation
pick jkl3456 Fix validation edge case

# Change to:
pick abc1234 Add user model
fixup def5678 Fix typo in user model        # squash into previous, discard message
pick ghi9012 Add user validation
fixup jkl3456 Fix validation edge case      # squash into previous, discard message

# Result: 2 clean commits instead of 4
# "Add user model" (includes typo fix)
# "Add user validation" (includes edge case fix)

# Interactive rebase commands:
# pick   = use commit as-is
# reword = use commit but edit message
# edit   = use commit but stop for amending
# squash = meld into previous commit (keep message)
# fixup  = meld into previous commit (discard message)
# drop   = remove commit entirely

--onto Rebase

The --onto flag lets you rebase a subset of commits onto a different base, which is useful for moving a branch that was based on the wrong parent.

# --onto: Move a branch to a different base
# Scenario: feature-b was branched from feature-a by mistake
#           You want feature-b based on main instead

#        D---E (feature-b)
#       /
# A---B---C (feature-a)
#     |
#     F---G (main)

git rebase --onto main feature-a feature-b

# Result:
#     D'---E' (feature-b)  ← now based on main
#    /
# A---B---C (feature-a)
#     |
#     F---G (main)

Conflict Resolution

Both merge and rebase can produce conflicts, but the experience is different.

Merge Conflicts

During a merge, all conflicts are presented at once. You resolve them in a single commit. If the merge is complex, you may see many conflicting files, but you only need to resolve once.

# Merge conflict workflow
git checkout main
git merge feature
# CONFLICT (content): Merge conflict in src/app.ts
# Automatic merge failed; fix conflicts and then commit

# 1. Open conflicting files and resolve
# 2. Stage resolved files
git add src/app.ts
# 3. Complete the merge
git commit  # creates merge commit with conflict resolution

# Abort merge if needed
git merge --abort

Rebase Conflicts

During a rebase, conflicts may occur at each commit being replayed. You resolve conflicts one commit at a time, which can be tedious if many commits conflict. However, each conflict is typically smaller and easier to understand because it involves only one commit's changes.

# Rebase conflict workflow
git checkout feature
git rebase main
# CONFLICT in commit C3: Merge conflict in src/app.ts

# 1. Resolve conflict in src/app.ts
git add src/app.ts
# 2. Continue rebase to next commit
git rebase --continue
# May hit another conflict in C4...

# CONFLICT in commit C4: Merge conflict in src/utils.ts
git add src/utils.ts
git rebase --continue

# Abort rebase if it gets too complex
git rebase --abort  # returns to pre-rebase state

# Skip a problematic commit during rebase
git rebase --skip

Team Workflow Strategies

Merge-Based Workflow (GitHub Flow)

The most common team workflow uses merge exclusively. Developers create feature branches, open pull requests, and merge via the GitHub/GitLab UI. This approach is safe, simple, and creates a complete audit trail of when features were integrated.

# GitHub Flow (merge-based)
git checkout -b feature/add-auth
# ... make commits ...
git push -u origin feature/add-auth
# Open PR on GitHub
# Review + approve
# Click "Merge pull request" (creates merge commit)
# Or "Squash and merge" (single commit)

Rebase-Before-Merge Workflow

Teams that want clean history use rebase to update feature branches, then merge (with or without --no-ff) to integrate. This keeps the main branch history linear while still recording merge points.

# Rebase-before-merge workflow
git checkout feature/add-auth
# ... make commits ...

# Before opening PR, update with latest main
git fetch origin
git rebase origin/main

# Force push (safe because it's your own branch)
git push --force-with-lease origin feature/add-auth

# Open PR on GitHub
# Merge with --no-ff to record integration point
git checkout main
git merge --no-ff feature/add-auth

Squash and Merge Workflow

Many teams prefer squash-and-merge, which combines all feature branch commits into a single commit on the main branch. GitHub and GitLab both support this as a merge strategy in pull requests.

# Squash and merge (via GitHub UI or CLI)
git checkout main
git merge --squash feature/add-auth
git commit -m "feat: add authentication system"

# Before:
# main: A---B---C
# feature: A---B---D---E---F---G

# After squash-merge:
# main: A---B---C---H  ← H contains all changes from D+E+F+G
# (feature branch can be deleted)

The Golden Rule of Rebasing

Never rebase commits that have been pushed to a shared remote branch. Rebasing rewrites commit history (creates new SHA hashes). If other developers have based their work on the original commits, rebasing will cause conflicts, duplicated commits, and confusion. Only rebase your own local, unpushed commits.

# DANGEROUS: Rebasing a shared branch
git checkout shared-feature
git rebase main
git push --force  # !! This rewrites history for everyone!

# SAFE: Rebasing your own local branch
git checkout my-local-feature
git rebase main
# No push yet, or push --force-with-lease to your own branch

# SAFE: Using --force-with-lease instead of --force
git push --force-with-lease origin my-feature
# Fails if remote has commits you haven't seen

Best Practices

Git Rebase vs Merge Best Practices:

1. Use merge for integrating feature branches into main
   - Creates clear integration points
   - Safe for shared branches
   - Easy to revert entire features

2. Use rebase to keep feature branches up-to-date
   - git rebase main (before opening PR)
   - Creates clean, linear history
   - Makes code review easier

3. Use interactive rebase to clean up before PR
   - Squash fixup commits
   - Reword unclear commit messages
   - Drop debugging commits

4. Use git pull --rebase as default
   - Avoids unnecessary merge commits
   - git config --global pull.rebase true

5. Never rebase shared/pushed commits
   - Only rebase your own unpushed work
   - Use --force-with-lease, never --force

6. Use squash-merge for feature branches
   - One clean commit per feature on main
   - Detailed commits preserved in PR history

7. Use --no-ff for important merges
   - Preserves the fact that a branch existed
   - Makes git log --first-parent useful

Frequently Asked Questions

When should I use rebase vs merge?

Use rebase to update your local feature branch with the latest changes from main before opening a pull request. Use merge (or squash-merge) when integrating feature branches into the main branch, especially on shared branches.

Is rebase dangerous?

Rebase rewrites history, which is dangerous on shared branches. If you only rebase local, unpushed commits, it is perfectly safe. The danger comes from rebasing commits that others have already pulled.

What is squash and merge?

Squash and merge combines all commits from a feature branch into a single commit on the target branch. It creates a clean history like rebase but without rewriting any existing commits. GitHub and GitLab both support this as a pull request merge strategy.

Can I undo a rebase?

Yes. Git keeps a reference log (reflog) of all branch pointer changes. You can find the pre-rebase state with git reflog and reset to it with git reset --hard HEAD@{n}. The reflog entries expire after 90 days by default.

Should my team use rebase or merge?

Most teams benefit from a hybrid approach: developers rebase their local feature branches to stay current, then use squash-merge or merge --no-ff when integrating into main. This gives you clean history without the risks of rebasing shared branches.

What about git pull --rebase?

git pull --rebase (or git pull -r) fetches remote changes and rebases your local commits on top instead of creating a merge commit. This is safe and recommended for keeping your local branch clean. You can set it as default with git config --global pull.rebase true.

Related Tools and Guides

𝕏 Twitterin LinkedIn
Var dette nyttig?

Hold deg oppdatert

Få ukentlige dev-tips og nye verktøy.

Ingen spam. Avslutt når som helst.

Try These Related Tools

{ }JSON Formatter

Related Articles

Git Rebase vs Merge: Når bruke hva (med visuelle eksempler)

Forstå forskjellen mellom git rebase og merge. Lær når du skal bruke hva.

Git Commands Cheat Sheet: Essensielle kommandoer for alle utviklere

Komplett Git-kommandojukseark: oppsett, branching, merging, rebasing, stashing og avanserte arbeidsflyter.

Git-branching-strategier: GitFlow vs Trunk-Based vs GitHub Flow

Sammenlign GitFlow, Trunk-Based Development og GitHub Flow. Branch-strukturer, merge-arbeidsflyter, CI/CD-integrasjon og aa velge riktig strategi for teamet ditt.