Git

Git Rebase

Complete guide to Git rebase - understanding, workflows, and best practices.

https://git-scm.com/book/en/v2

Git Rebase: Complete Documentation

What is Git Rebase?

Git rebase is a powerful command that integrates changes from one branch into another by rewriting commit history. Unlike merging, which creates a new commit to join two branches, rebase moves or replays your commits to a new base commit, making it appear as though your work was started from a different point in the repository's history.

When you rebase, Git:

  1. Temporarily removes your commits
  2. Updates your branch to match the target branch
  3. Reapplies your commits one by one on top of the new base

Critical caveat: Rebase creates entirely new commits with different SHA-1 hashes, even though the content may be identical. This means rebased commits are technically different objects in Git's history.


Why Use Git Rebase?

1. Linear, Clean History

Rebase creates a straight line of commits without merge commits, making the project history easier to read and understand.

2. Eliminate Unnecessary Merge Commits

Frequent merges from main into feature branches create cluttered histories full of "Merge branch 'main' into feature" commits. Rebase avoids this noise.

3. Easier to Review

A linear history makes it simpler to use tools like git log, git bisect, and code review interfaces to track down when changes were introduced.

4. Integrate Upstream Changes

Keep your feature branch up-to-date with the latest changes from main or develop without creating merge commits.

5. Prepare Clean Pull Requests

Before submitting a pull request, you can rebase your feature branch onto the latest main to ensure a clean, conflict-free integration.

6. Interactive History Editing

Interactive rebase (git rebase -i) allows you to squash, reorder, edit, or delete commits before sharing your work.


How Git Rebase Works

Basic Concept

Imagine you're working on a feature branch while others are updating main:

Before Rebase:

          C---D   feature
         /
    A---B---E---F   main

Your feature branch (C, D) diverged from main at commit B. Meanwhile, main has moved forward with commits E and F.

After git rebase main:

                  C'---D'   feature
                 /
    A---B---E---F   main

Git has:

  1. Removed commits C and D temporarily
  2. Fast-forwarded your branch to F (the tip of main)
  3. Reapplied C and D as new commits C' and D'

Important: C' and D' have different commit hashes than C and D, even if their content is identical.


Common Rebase Workflows

1. Standard Rebase (Update Feature Branch)

Keep your feature branch current with the latest main:

# On your feature branch
git checkout feature
git rebase main

What happens:

  • Git finds the common ancestor of feature and main
  • Temporarily stores your feature commits
  • Fast-forwards your branch to match main
  • Replays your commits on top

2. Interactive Rebase

Clean up your commit history before merging:

git rebase -i HEAD~3  # Rebase last 3 commits

This opens an editor showing:

pick abc1234 Add user authentication
pick def5678 Fix typo in login form
pick ghi9012 Update authentication tests

Available commands:

  • pick (p) - Keep commit as-is
  • reword (r) - Keep commit, but edit message
  • squash (s) - Combine with previous commit
  • fixup (f) - Like squash, but discard commit message
  • edit (e) - Pause to amend commit
  • drop (d) - Remove commit entirely
  • reorder - Simply rearrange lines

Example - Squashing commits:

pick abc1234 Add user authentication
squash def5678 Fix typo in login form
squash ghi9012 Update authentication tests

This combines all three into a single commit.

3. Rebase with Conflicts

If conflicts occur during rebase:

# Git pauses and shows conflicts
# Edit files to resolve conflicts
git add <resolved-files>
git rebase --continue

# Or skip this commit
git rebase --skip

# Or abort entirely
git rebase --abort

Rebase vs Merge: Visual Comparison

Merge Workflow

          C---D   feature
         /     \
    A---B---E---F---G   main
                    ^
                merge commit

Result: History shows exactly what happened, but can become cluttered with merge commits.

Rebase Workflow

                  C'---D'   feature
                 /
    A---B---E---F   main

Result: Clean linear history, but rewrites commits (changes hashes).


Important Rebase Rules

The Golden Rule of Rebasing

Never rebase commits that have been pushed to a public/shared repository and that other people may have based work on.

Why? Because rebase creates new commit hashes, anyone who has pulled your original commits will have divergent histories, leading to confusion and messy merges.

When to Rebase

✅ Local commits not yet pushed
✅ Cleaning up your own feature branch
✅ Updating your branch with latest main before creating a PR
✅ Your feature branch that only you work on

When NOT to Rebase

❌ Commits already pushed to shared branches
❌ The main or master branch
❌ Public release branches
❌ Branches where others are actively committing


Advanced Rebase Options

Rebase onto a Different Branch

git rebase --onto main server client

Takes commits from client that aren't in server and replays them onto main.

Preserve Merge Commits

git rebase -p main  # or --preserve-merges (deprecated)
git rebase --rebase-merges main  # newer syntax

Autosquash

# Create fixup commit
git commit --fixup=abc1234

# Later, automatically squash it
git rebase -i --autosquash main

Common Use Cases

1. Update Feature Branch Before Merging

git checkout feature
git fetch origin
git rebase origin/main
git push --force-with-lease

2. Clean Up Local Commits

git rebase -i HEAD~5  # Clean last 5 commits
# Squash, reword, reorder as needed

3. Split a Large Commit

git rebase -i HEAD~1
# Mark commit as 'edit'
git reset HEAD^
# Make multiple smaller commits
git rebase --continue

Recovery and Safety

Undo a Rebase

# Find the commit before rebase
git reflog

# Reset to that point
git reset --hard HEAD@{5}

Force Push Safely

After rebasing pushed commits (only on your own branches):

git push --force-with-lease

This prevents overwriting changes if someone else has pushed to the branch.


Best Practices

  1. Communicate with your team about rebasing policies
  2. Rebase frequently to avoid large, complex conflicts
  3. Use interactive rebase to clean up before pushing
  4. Keep commits atomic - one logical change per commit
  5. Write clear commit messages that explain why, not just what
  6. Test after rebasing to ensure nothing broke
  7. Use --force-with-lease instead of --force when force-pushing

Quick Reference

# Basic rebase
git rebase <branch>

# Interactive rebase
git rebase -i <commit>

# Continue after resolving conflicts
git rebase --continue

# Skip current commit
git rebase --skip

# Abort rebase
git rebase --abort

# Rebase onto different base
git rebase --onto <newbase> <oldbase> <branch>

# Preserve merge commits
git rebase --rebase-merges <branch>

Conclusion

Git rebase is a powerful tool for maintaining clean, linear project histories. Used correctly, it makes collaboration smoother and code history more understandable. However, it requires discipline and adherence to the golden rule: never rebase shared commits. When in doubt, use merge for public branches and save rebase for your local, private work.