Many of the organizations that use Git—and probably the vast majority of open-source projects as well—employ a workflow centered around the pull request. The pull request is the process by which a potential contributor asks the maintainer of a project to accept their branch and merge it into the project’s mainline. Accepting and merging a pull request should signal the end of the specific contribution, but if things don’t work as planned for some reason, you might need to revert a pull request in Git.
That’s what this post is about: explaining how to revert a merged pull request. Before we go there, we’ll talk about the pull request process itself, explain how it works, and give examples of situations in which you’d need to revert a PR.
With those basics out of the way, we’ll finally tackle the “how.” You’ll learn how to revert a pull request in GitHub, in GitLab, and also in “vanilla” Git, using the git revert command. Let’s get started.
The Pull Request Process: What It Is and How It Works
As promised, let’s start by tackling some fundamentals. Specifically, we’ll open by defining a pull request.
What Is a Git Pull Request?
A pull request is a process by which a contributor to a Git repository offers some code and asks for the project’s maintainer to accept it and merge it. Pull requests are not a feature of the native Git software. Rather, they are a feature of source code hosting providers. First popularized by GitHub, nowadays they’re common in other platforms such as GitLab, BitBucket, and Azure DevOps.
(To make things slightly more confusing—because, why not?—Git does offer a command called request-pull. It predates hosting services such as GitHub and what it does is generate a list of changes in the format of a patch file which gets sent by email.)
How Does a Pull Request Work?
The way a pull request—or merge request, as they’re called in GitLab—works varies depending on the source code hosting service you’re using. I’ll describe what the workflow might look like for a company using a service such as GitLab or Azure DevOps.
- The process starts with the engineer cloning the repository locally.
- Then, the engineer creates a new branch—generally off of the main branch, but this could vary—and adds their commits locally.
- At this point, they can already push their commits to the remote repository. It’s a good practice for backup purposes.
- When the engineer completes the task—or feels like it could use a fresh pair of eyes, even without being completed—they create a new pull request, with a title and a description explaining the reason for the change.
- Though not mandatory, there’s typically a code review process in which one or more people review the changes and give feedback on them before merging them.
- When the reviewers accept the PR, they merge the changes into the destination branch—often deleting the source branch—and mark the pull request as closed.
It should be noted that in practice this process can take a surprisingly long time. In fact, on average code reviews sit idle for 70% of cycle time! That’s why PR merge time is one of the core metrics for companies like Slack.
Why Would You Want to Revert a Pull Request?
Here are some of the reasons why you might need to revert a pull request:
- Merge resulted in a logical conflict. Sometimes a merge results in a logical conflict—i.e. as far as Git’s concerned, the merge happens cleanly (there are no merge conflicts), but changes in both branches result in the introduction of bugs.
- Pull request was made to the wrong branch. This can happen, especially when the team uses a complex branch strategy, such as Git Flow.
- Pull request merged without review. You might want to revert a pull request because the maintainer merged it without proper review. This is highly dangerous! LinearB’s WorkerB bot can alert you in real-time when this happens.
It’s vital to stress that reverting a pull request should be the exception rather than the norm. Having problems like the ones above is a sign your team has deeper issues that it needs to address. For instance, merges resulting in conflict are often caused by pull requests taking too long to be reviewed and merged. Pull request pickup time is an important engineering metric that you should work to improve. With LinearB, you can get a detailed breakdown of your development cycles so that you can determine if valuable time is being wasted by pull requests sitting idle.
How to Revert a Pull Request
With the “what” and “why” out of the way, let’s explore the how. We’ll start by explaining how to revert a pull request in GitHub.
Reverting a Pull Request in GitHub
GitHub offers the functionality of reverting pull requests. To illustrate this, I did the following:
- Using GitHub’s web interface, I created a new repository.
- Still in the UI, I added a few commits to the main branch.
- On the command line, I cloned the repository locally using git fetch.
- I created a new branch called new and added a few commits to it.
- I then pushed the new branch to the remote repository.
- Finally, I created a pull request from new to main, accepted it, and merged it.
On the repository’s page, I go to the pull requests tab and filter to see the closed PRs:
After clicking on the PR, I access the detailed pull request page where I can see the Revert button:
This button does an interesting thing. It offers me the chance to create a new pull request whose changes effectively undo the changes resulting from the merge:
I only have to click on the Create pull request button and I’ll be redirected to the page of the new pull request:
We can see that this pull request contains three commits. I’ll go over there to inspect them:
Interestingly, GitHub created three new commits that undo the three original commits that I added when accepting the pull request (I’ve used the “rebase” option when accepting the PR, which means I’ve preserved the original commits as they were in the new branch.)
Completing the reversion is a walk in the park: just complete the pull request and you’re done.
Reverting a Pull Request in GitLab
GitLab is a bit less resourceful than its competitor when it comes to reverting pull requests. GitHub offers the ability to revert pull requests regardless of the method you used while merging them. Whether you rebased or squashed, used a fast forward merge or a merge commit, GitHub will be able to revert the changes. However, GitLab only shows the Revert option for projects that use git merge (which produces a “merge commit”) when accepting merge requests.
This time I did the same steps I did before to create a merge request with a few commits:
Then I created and accepted the merge request:
Notice the Revert button at the top. After clicking it, I’m prompted to configure a few options:
I’m satisfied with the default options, so I click the Revert button, which brings me to the new merge request page:
The rest is super simple: I finish creating the PR, then merge it, and the original pull request is no more.
Reverting a Pull Request Using Git
What if GitHub and/or GitLab didn’t offer the option to revert a PR? You could still use vanilla Git to revert a pull request. Let’s see how you can do that in two ways.
The Non-Destructive Way: The Git Revert Command
Under the hood, both GitHub and GitLab resort to the git revert functionality. So, there’s nothing stopping you from using the same command.
Here’s a step-by-step guide of how you’d go about it:
- The first step would be to create a branch off of main—or whatever your default branch is.
- Then, you’d use git revert, passing the ID of the commit(s) you wish to revert.
- If the merge was done via the squash or merge commit methods, the situation is easier. Just target the resulting merge commit or squashed commit.
- However, if the merge was done using rebase, you’d have to target all of the individual commits, creating a reversed commit for each.
- Finally, you’d have to create a pull request from your new branch to the default branch.
- After the pull request is accepted, you would have effectively reverted the changes included in the original PR.
The Destructive Way: Git Reset
Git revert is a safe, non-destructive command. When you use it, you don’t rewrite past history. Instead, you create new commits whose changes are the opposite of the ones you’re trying to undo. The new commits and the old ones cancel out, and the changes no longer affect the repository.
However, you might have a legitimate reason to rewrite history on the default branch. If that’s the case, and you understand the risks and have the privileges to overwrite the default branch, you could simply do:
git reset --hard <ID-OF-LAST-GOOD-COMMIT>
Then, you’d just have to force push the change:
git push --force
I can’t stress this enough: only do the above if you’re really sure of what you’re doing. Rewriting public history is a bad practice and you can harm your coworkers. No one will be able to look at the history of the codebase and rely upon the commit messages to see how the codebase has changed over time. Doing a hard reset is a risky operation and you can lose commits—and a codebase’s true history—for good.
Standardize Your Pull Request Process
Generally speaking, reverting a pull request isn’t something you should do often. It is often necessitated by causes that could be avoided. LinearB’s goal-setting frameworks, metrics, and WorkerB automations can help your team successfully adopt a robust code review process that will enable you to consistently ship better code faster.
But it is nice to know that, if the need arises, reverting a pull request is quite easy to do. As you’ve seen, both GitHub and GitLab offer user-friendly ways to perform this operation, which, under the hood, rely on the git revert command.
Even if your favorite source code hosting service doesn’t offer an option to revert pull requests, you can always use the git revert command yourself. As a last possible resort, if you really know what you’re doing, you could also perform a hard reset to “get rid” of the offending commits.