Content is user-generated and unverified.

Git Rebase Workflow: Keeping Our History Clean

Why Rebase Instead of Merge?

When we rebase our feature branches, we create a clean, linear git history that's easier to read and debug. Instead of messy merge commits cluttering our timeline, each feature appears as a clean sequence of commits on top of the main development line.

Benefits:

  • Cleaner, more readable git history
  • Easier to track down bugs with git bisect
  • Simpler to understand what each feature added
  • Professional-looking commit timeline

❌ Common Mistake: Merging Dev Into Feature Branch

DON'T DO THIS:

bash
git checkout <feature-branch>
git merge dev  # ❌ This creates merge commits

Why this is problematic:

  • Creates merge commits in your feature branch
  • Makes the history non-linear and harder to read
  • If you later try to rebase, you'll get conflicts and duplicate commits
  • Defeats the purpose of having clean history

The visual difference:

Bad approach (merge dev into feature):

dev:     A - B - C - D - E
              \         \
feature:       F - G - M - H

M is a merge commit that clutters history.

Good approach (rebase feature onto dev):

dev:     A - B - C - D - E
                           \
feature:                    F' - G' - H'

Clean, linear history where feature commits appear after dev commits.

Keeping Your Feature Branch Updated During Development

Instead of merging dev into your feature branch, rebase regularly:

Option 1: Regular Rebase Updates (Recommended)

bash
# Do this every few days or when you need latest changes
git checkout dev
git pull origin dev
git checkout <feature-branch>
git rebase dev
git push --force-with-lease

Option 2: Interactive Rebase for Clean-up

bash
# When you want to clean up your commits AND get latest changes
git checkout dev
git pull origin dev
git checkout <feature-branch>
git rebase -i dev  # Opens editor to squash/reorder commits
git push --force-with-lease

Benefits of regular rebasing:

  • Your feature branch stays up-to-date with dev
  • Conflicts are smaller and easier to resolve
  • Final rebase before merging is smoother
  • You catch integration issues early

The Complete Workflow

Final Rebase Before Merging PR

Here's the step-by-step process to rebase your feature branch before merging:

Step 1: Prepare Your Feature Branch

bash
# Make sure you're on your feature branch
git checkout <feature-branch>

# Pull any recent changes from the remote feature branch
git pull

Step 2: Get Latest Changes from Dev

bash
# Switch to dev branch
git checkout dev

# Pull the latest changes
git pull origin dev

Step 3: Rebase Your Feature Branch

bash
# Switch back to your feature branch
git checkout <feature-branch>

# Rebase on top of the latest dev changes
git rebase dev

Step 4: Push Your Rebased Branch

bash
# Force push your rebased changes
git push --force-with-lease

Note: We use --force-with-lease instead of --force as it's safer - it will fail if someone else has pushed changes to your branch that you don't have locally.

Step 5: Merge via GitHub

  • Go to your PR on GitHub
  • Click "Merge pull request"
  • Your changes will now sit cleanly on top of dev!

Handling Rebase Conflicts

Sometimes you'll encounter conflicts during the rebase. Here's how to handle them:

bash
# When conflicts occur during rebase
# 1. Fix conflicts in your editor
# 2. Stage the resolved files
git add <resolved-files>

# 3. Continue the rebase
git rebase --continue

# If you want to abort and start over
git rebase --abort

Quick Reference Command Sequence

For experienced users, here's the quick sequence:

bash
git checkout <feature-branch>
git pull
git checkout dev
git pull origin dev
git checkout <feature-branch>
git rebase dev
git push --force-with-lease
# Then merge via GitHub UI

Advanced: Understanding git pull --rebase

In our workflow above, we could simplify Step 1 by using:

bash
git checkout <feature-branch>
git pull --rebase origin <feature-branch>

What git pull --rebase does:

  • Normal git pull = git fetch + git merge
  • git pull --rebase = git fetch + git rebase

When to use it:

  • When you want to pull remote changes but keep your local commits on top
  • Avoids creating unnecessary merge commits when pulling
  • Keeps your branch history linear

In our workflow: You could use it in Step 1, but regular git pull is fine since we're rebasing the entire branch onto dev anyway.

Understanding --force-with-lease

--force-with-lease is a safer version of --force that includes a safety check:

What --force does:

bash
git push --force  # "Overwrite remote branch, I don't care what's there"

What --force-with-lease does:

bash
git push --force-with-lease  # "Overwrite remote branch, BUT only if it matches what I last saw"

The safety mechanism:

  • Git remembers the last commit you saw on the remote branch
  • If someone else pushed changes since then, --force-with-lease will fail
  • This prevents you from accidentally overwriting a teammate's work

Example scenario:

  1. You start rebasing your feature branch
  2. Meanwhile, a teammate pushes a fix to your feature branch
  3. git push --force would destroy their fix
  4. git push --force-with-lease would fail and warn you

When it fails:

bash
git push --force-with-lease
# Error: Updates were rejected because the remote contains work that you do not have locally

Then you'd need to:

bash
git pull --rebase  # Get teammate's changes
git push --force-with-lease  # Now it works

Getting Started

For your first few PRs: Try this workflow on smaller, less critical features to get comfortable with it.

If you're nervous about force pushing: Create a backup branch first:

bash
git checkout <feature-branch>
git checkout -b <feature-branch>-backup
git checkout <feature-branch>
# Now proceed with rebase workflow

When NOT to Rebase

  • Never rebase shared branches that others are working on
  • Don't rebase commits that have already been merged to dev
  • Skip rebasing for emergency hotfixes where speed matters more than clean history
  • Never merge dev into your feature branch - always rebase your feature branch onto dev instead

Recovering from Merge Commits

If you accidentally merged dev into your feature branch:

Option 1: Reset and Rebase (if you haven't pushed yet)

bash
git checkout <feature-branch>
git reset --hard <last-feature-commit-before-merge>
git rebase dev

Option 2: Create a New Branch (safest)

bash
git checkout <feature-branch>
git checkout -b <feature-branch>-clean
git reset --hard <last-feature-commit-before-merge>
git rebase dev
git push --force-with-lease origin <feature-branch>-clean
# Update your PR to point to the new branch

Troubleshooting Common Issues

"I have merge commits in my feature branch"

  • Follow the recovery steps above
  • In the future, always use git rebase dev instead of git merge dev

"Rebase conflicts are overwhelming"

  • Try rebasing more frequently (every few days)
  • Use git rebase --abort to cancel and ask for help
  • Consider using git rebase -i to squash commits first

"I'm scared of force pushing"

  • Always use --force-with-lease instead of --force
  • Create backup branches before rebasing
  • Start with small, non-critical features

Questions?

If you run into issues or have questions about this workflow, feel free to ask! We can pair program through your first few rebases until you're comfortable.


Reference: Detailed Stack Overflow explanation

Content is user-generated and unverified.
    Git Rebase Workflow Guide for Clean History | Claude