Skip to main content
  1. Posts/

Mastering Git Rebase to Older Commit

·5 mins
Git Devops
Table of Contents

Git Rebase
#

In large organizations, development teams frequently work on the same codebase, which leads to common conflicts in pull requests (PRs). Understanding Git’s rebase command in depth—and knowing how to wield advanced options like squash, drop, and rebasing onto specific commits—can help developers keep PRs manageable and the commit history clean. This article explores advanced rebase techniques, with examples of how to rebase, squash, and drop commits to streamline PR management and resolve conflicts effectively.

Why
#

The git rebase command rewrites commit history by moving or combining commits in a linear, sequential fashion, rather than creating a branching structure. For teams handling multiple PRs and branches, using rebase can help:

  1. Keep history clean: Rebase avoids the merging branches, making the commit history appear as a single, linear sequence.
  2. Resolve conflicts early: Frequent rebasing helps identify and resolve conflicts before they become larger issues at merge time.
  3. Improve PR review quality: A clean history with squashed or logically organized commits allows reviewers to understand the changes without the clutter of unnecessary commits.

Use rebase Safely
#

Because rebase rewrites commit history, it’s best to approach it with caution. Here are a few steps to prepare for complex rebase operations:

  1. Create a backup branch: Always create a backup of your branch before starting a complex rebase.

    git checkout feature-branch
    git branch backup-feature-branch
    
  2. Stash uncommitted changes: Ensure your working directory is clean to avoid losing uncommitted work.

    git stash
    

Example Scenario
#

Let’s look at a common example where a feature branch needs to be rebased due to changes in main. The example includes rebasing onto the latest main branch commit, then rebasing back to an earlier commit for testing.

Scenario Outline
#

  1. The development branch was originally based on an older commit C in main.
  2. Several commits were added to dev, now at commit F.
  3. Another team merged a PR into main, advancing it to commit G.
  4. After rebasing dev onto commit G, issues were found during testing, and now we need to rebase back to commit C to isolate the issue.

This example will show how to rebase both forward and backward to a specific commit.

1. Developing
#

Initial Setup and Dev Branch Creation

We start with the main branch on commit C and create a dev branch based on it.

Create a dev branch starting from commit C:

git checkout main
git checkout -b dev

Let’s add several commits on the dev, reaching commit F.

Now the commit history looks like this:

main:     A --- B --- C
                       \
dev:                    C --- F

2. Main branch updated
#

main Advances to a New Commit Due to Another Team’s PR

While you were working on dev, another team completed their work and merged a PR into main, bringing it to a new commit G.

main:     A --- B --- C --- D --- E --- G
                       \
dev:                    C --- F

Your dev is now at commit F based on commit A, but main has been updated to commit G.


3. Rebase to main
#

Rebase dev onto the Latest main (Commit G)

To keep your branch up to date with the latest changes in main, you rebase your dev onto G.

  1. Pull main branch
git checkout main
git pull
  1. Start the rebase process to rebase onto main’s latest commit (G):
git checkout dev
git rebase main
  1. If there are conflicts, Git will prompt you to resolve them. After fixing any conflicts, complete the rebase with:
git rebase --continue

After rebasing, your commit history now looks like this:

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

Your development branch (dev) is now rebased onto the latest main, but testing reveals an issue potentially related to the changes introduced by the PR in G.


4. Rebase to older commits
#

Rebase the Dev Branch Back to Commit A to Isolate the Issue

To determine whether the issue is related to the changes in G, you decide to rebase dev back onto A, isolating it from any recent changes in main.

To rebase dev back onto commit C:

Use rebase --onto to move the base of feature-branch back to C, omitting changes in G.

git rebase --onto <commit-C-hash> <commit-G-hash>

Here’s how this command works:

  • --onto <commit-C-hash>: This tells Git to move the base of the branch back to C.
  • <commit-G-hash>: Specifies where to start moving commits from, effectively ignoring any changes introduced in G.

Git will now replay your dev commits on top of C, as if G and subsequent changes in main didn’t happen.

Now, your branch history looks like this:

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

You can now test the branch to confirm whether the issue was introduced by G or originated elsewhere.


Advanced Rebase Workflow
#

Combining Rebase, Squash, and Drop for a Clean History

In large PRs, you may want to combine these techniques to keep history clean and manageable. Here’s a recommended workflow:

  1. Rebase onto the latest main to stay current with the latest code.
  2. Squash commits to combine related changes into single, meaningful commits.
  3. Drop irrelevant commits to remove unnecessary work-in-progress saves.
  4. Rebase onto specific commits as needed to isolate or manage conflicts.

For a comprehensive cleanup, use an interactive rebase to combine all steps:

git rebase -i HEAD~<number-of-commits>

In the interactive rebase editor, you can squash, drop, and reorder commits to organize history logically.


Summary
#

  1. Rebase feature branch onto the latest main:

    git rebase main
    
  2. Rebase feature branch back to an older commit if issues arise:

    git rebase --onto <old-commit-hash> main
    

Using these rebase techniques helps you manage PRs in large organizations, allowing you to isolate issues, reduce conflicts, and keep your commit history clean. With a deep understanding of rebase, squashing, and dropping commits, developers can avoid merge conflicts, keep history readable, and streamline the development process for smoother collaboration.