How to Undo Rebase in Git

Brian Bojan Dordevic
About The Author

Brian Dordevic

Brian is Marketing Strategic Planner with a passion for all things digital. Feel free to follow him on Twitter or schedule a consultation call with him.

Rebase is a Git utility that serves for integrating changes from one branch to another. Along with git merge, it is the only change integration utility. While git merge is a forward-moving process, rebase provides powerful history rewriting features. In a way, we can understand rebase as a merge with Git history.

That being said, it may happen that you accidentally performed the git rebase command on a local branch and pushed the changes to remote. So, which steps should you take to undo this action and resolve the problem? In today’s article, our web development agency in Chicago explores ways to undo rebase in Git. We’ll also talk about common rebase use cases and caveats, as well as provide a detailed look at git rebase configuration and execution.

What is rebasing in Git?

Rebase is the process that allows you to move or combine a sequence of commits to a new base commit. In the context of a feature branching workflow, you can visualize the process, as shown in the figure below.

If we view this operation from a content perspective, we can say that rebasing is the action of changing the base of a branch from one commit to another. Developers use rebase to change the starting point of a branch in commit history to the final commit of the main or any other branch. In layman’s terms, rebase allows you to move the starting point of your branch to another point.

Though, at first glance, it may seem that your branch looks the same, it is essential to note that it is actually made of entirely new commits applied to the specific base.

Use cases

Git rebase is most commonly used to maintain a linear history of your project. Let’s say that the master branch of your project has further progressed while you were working on your feature branch. Now, you wish to get those master branch updates to your feature branch and keep your branch’s history clean, so you can later seamlessly merge it back into the master branch. By doing rebase, you’ll get the same outcome as if you were working off the latest master branch.

To better understand the benefits of keeping a clean branch history, imagine the following situation:

A bug appeared on the master branch that prevented a particular feature from functioning as intended. You use the git log to check the history of the master branch. Since you and your team followed the practice of keeping a clean history, you’ll have no problems understanding the data. If you cannot identify when the bug was introduced with the git log command, you’ll likely execute git bisect. Due to the clean history, git bisect can work with a refined set of commits and compare them to find the one that introduced the bug, giving you the chance to act accordingly and promptly.

There are two ways to integrate your feature branch into the master branch:

  • Merging directly
  • Rebasing, then merging

While the first option requires a three-way merge and a merge commit, the second option produces a fast-forward merge and a linear history like in the diagram below.

As you can see, rebasing allows you to integrate upstream changes into your local repo easily.

Note: It is imperative to mention that you should never rebase commits that have already been pushed to a public repository. That action would replace the old commits with new commits, and it would appear that a part of your project history has disappeared.

Different git rebase modes

There are two git rebase modes:

  • Standard
  • Interactive

Interactive git rebase is initiated using an — i argument that stands for interactive. Without this argument, the rebase process runs in standard mode. Let’s use an example to differentiate these two modes.

First, let’s say that we have created a separate feature branch and checked out.

git checkout -b feature_branch main

After editing some files, let’s commit changes.

Git commit -a -m “Adds new feature”

Standard rebase automatically applies your current working branch commits to the HEAD of the passed branch.

git rebase <base>

This command-line rebases the current branch onto the commit reference of your choice. That could be an ID, a branch name, a tag, or a relative reference to HEAD.

By introducing the -i flag, you’ll run an interactive rebasing process. Instead of moving all commits to the new base, this process allows you to clean up your history by removing, splitting, or altering existing commits.

Git rebase — Interactive <base>

You’ll be presented with an editor, which you can use to enter various commands in order to determine how and in which order individual commits will be moved to the new base. Once you have all the actions specified, Git starts playing back commits and applying your commands. Here is a set of commands you cn use during an interactive rebase:

  • p (pick) – use commit
  • r (reword) – use commit, but edit the commit message
  • e (edit) – use commit, but stop for amending
  • s (squash) – use commit, but meld into the previous commit
  • f (fixup) – similar to squash, but discard this commit log message
  • x (exec) – run command (the rest of the line) using shell
  • d (drop) – remove commit

Besides the commands mentioned above, rebase provides additional commands for you to use in more complex situations. Here are some of those:

  • git rebase — d – discards the commit from the final commit block during playback
  • git rebase — p – leaves the commit unchanged as an individual commit in the branches history
  • git rebase — x – executes a command shell script on each marked commit during playback (useful when running codebase’s test suit on specific commits in order to identify regressions (bugs) during a rebase)

With the help of interactive rebasing, you’ll have full control over your project’s history. Developers find this mode extremely useful since they can commit their history in the current state while they are preoccupied with coding and then go back and clean it up.

Interactive rebase allows you to polish your feature branch before merging it into the main codebase, which keeps the master branch history clean and meaningful.

How to undo git rebase?

To undo git rebase, let’s say we have master and feature branches, each with the following chain of commits (we’ll mark commit messages with C to make things more simple and readable):

  • Master: C4 – C2 – C1 – C0
  • Feature: C5 – C3 – C2 – C1 – C0

If we use git log to look at the set of commits in each branch, it may look something like this:

git log — oneline master

6a92e7a C4

259bf36 C2

f33ae68 C1

5043e79 C0

Git log — oneline feature

79768b8 C5

000f9ae C3

259bf36 C2

f33ae68 C1

5043e79 C0

What Git does during rebase is take each commit in one branch and try to replay the differences onto the other branch. Now. Let’s attempt to rebase the feature branch onto the master branch by taking C4 from the master branch and placing it onto the feature branch. This is what it may look like:

Git checkout feature

Git rebase master

First, rewinding head to replay your work on top of it…

Applying: C3

Applying: C5

Once this is done, your chain of commits should look like the following figure shows.

Let’s use git log again to see if we can spot any changes:

Git log — oneline master

6a92e7a C4

259bf36 C2

f33ae68 C1

5043e79 C0

Git log — oneline feature

c4533a5 C5

64f2047 C3

6a92e7a C4

259bf36 C2

f33ae68 C1

5043e79 C0

As you can see, our new C3 and C5 commits differ from the previous ones. They are actually new commits created by making changes from the originals on top of the existing chain on the master branch. Let’s name these new commits C3’ and C5’. The original C3 and C5 commits still exist, but they no longer have a branch pointing to them.

The rebase is finished, and now we decided to use Git to undo rebase. We can do this by entering:

git reset 79768b8

With this command line, we have managed to undo rebase in Git, and our branch is once again pointing to the previous set of commits.

Luckily, we were able to remember what commit the branch used to point to (C5), so it was easier for us to undo git rebase. But, what happens if we cannot recall the commit in question? Fortunately, Git remembers the original commit for most operations that modify pointers like rebase does. It is saved in the .git repository and named ORIG_HEAD. Let’s use the cat command to see the content of the file.

cat .git/ORIG_HEAD

79768b891f47ce06f13456a7e222536ee47ad2fe

If we used git reset once again, the log would show the following:

Git log — oneline feature

79768b8 C5

000f9ae C3

259bf36 C2

f33ae68 C1

5043e79 C0

We can also use git reflog to find this info. By using this command, we’ll access a play-by-play listing of switches or changes to references in our local repository. Let’s try it out:

git reflog

79768b8 HEAD@{0}: reset: moving to 79768b

c4533a5 HEAD@{1}: rebase finished: returning to refs/heads/feature

c4533a5 HEAD@{2}: rebase C5

64f2047 HEAD@{3}: rebase: C3

6a92e7a HEAD@{4}: rebase: checkout master

79768b8 HEAD@{5}: checkout: moving from feature to feature

79768b8 HEAD@{6}: commit: C5

000f9ae HEAD@{7}: checkout: moving from master to feature

6a92e7a HEAD@{8}: commit: C4

259bf36 HEAD@{9}: checkout: moving from feature to master

000f9ae HEAD@{10}: commit: C3

259bf36 HEAD@{11}: checkout: moving from master to feature

259bf36 HEAD@{12}: commit: C2

f33ae68 HEAD@{13}: commit: C1

5043e79 HEAD@{14}: commit (initial): C0

Now that we have our list of changes, we can reset to any of the items. All we need to do is use the git reset command followed by the naming format from the log:

git reset HEAD@{1}

As you can see, making changes in Git becomes much easier once you know that there is an accessible record of the original chain of commits.

Git rebase caveats

While performing git rebase action, it is important to remember that merge conflicts may arise. That usually happens when you have worked on your feature branch for a significant amount of time and diverged from the master branch too much. When you eventually attempt to rebase, the master branch is likely to contain many new commits that may conflict with the changes on your branch. That can easily be avoided by frequently rebasing your feature branch against the master branch and making more frequent commits. If you ever encounter these conflicts, you can always add the — continue or — abort command line arguments to git rebase in order to advance or reset the process.

A more severe caveat is lost commits from the interactive history rewriting process. Using subcommands such as squash and drop will remove those commits from your branch’s log. Though it may look like the commits are gone for good, as we have previously mentioned, you can still find them with git reflog, restore them, and undo git rebase.

Final thoughts

Throughout this article, we have talked about the git rebase action. We have covered the common use cases, different rebase modes, and pitfalls, as well as elaborated on how to undo git rebase in case anything unexpected happens. 

The Git rebase process itself is neither complicated nor dangerous to perform. In fact, one of Git’s main upsides is that it allows you to change and try different things and rapidly undo them if they don’t work the way you expected. The main thing to avoid is to force push the results to a remote branch shared by other users during the history rewriting interactive rebase. That way, you can overwrite other remote users’ work when they attempt to pull changes.

Schedule a call to get in touch with our team, or visit our blog pages to find more articles about Git, as well as check other interesting info, such as our take on the reason why you should switch to custom-coded websites.

Want to start your creative project today? Fill out this form, and let’s discuss your next steps.

Request Your Proposal
complete form icon
Complete Form
Discovery icon
Discovery
get proposal icon
Get a proposal