How To Revert a Series of Git Commits?


Sometimes, I need to revert a series of commits that I've already pushed, doing a git hard reset (git reset --hard) is not an option, as someone may already have new commits based on mine.

For example, assume that I've made a few commits like below:

65a2c62 * commit 10
25cad43 * commit 9
72ad583 * commit 8
ceebf9a * commit 7
acf8a11 * commit 6
28d526f * commit 5
63af1e2 * commit 4
982c71c * commit 3
0fb4c2d * commit 2
acf9da1 * commit 1
b5f9933 * commit 0

For whatever reason, I need to "drop" the changes made by commit 6 to commit 10, that is, go back to "commit 5" without deleting these commits.

How to do that?

One way is to use git revert 28d526f..HEAD to reversely revert the commits (see git-revert), which results in below commit history:

66808e5 * Revert "commit 6"
2661e48 * Revert "commit 7"
db86ec6 * Revert "commit 8"
fde9cb5 * Revert "commit 9"
3bf102f * Revert "commit 10"
65a2c62 * commit 10
25cad43 * commit 9
72ad583 * commit 8
ceebf9a * commit 7
acf8a11 * commit 6
28d526f * commit 5
63af1e2 * commit 4
982c71c * commit 3
0fb4c2d * commit 2
acf9da1 * commit 1

It works, but it makes too many additional commits. In many cases, that's not what I want. So I'd instead make only one commit.

So is there a better way? The other day, I found out that the soft reset is perfect for this case.

But how is it possible? Generally, git reset is for moving HEAD around the commits and resetting the index and working tree. The magic is that a soft reset can reset HEAD to a specific commit, but leave the index and working tree untouched.

If I soft-reset HEAD from "commit 5" to "commit 10", it will move HEAD directly to "commit 10", but let the index as-is so that the index has the diff from "commit 10" to "commit 5" at the same time. (Difference between git reset soft, mixed and hard - davidzych.com illustrates different kinds of reset in detail.)

Here are the steps:

git branch master-bak    # create a backup branch in case commits lost
git reset --hard 28d526f # the commit 5
git reset --soft master-bak
git commit -m "Revert the commits [6,10]"
git diff 28d526f..HEAD   # confirm that it doesn't have any diffs

P.S. It's a bit boring to prepare git commits above, so I bake a helper script to do that:

#!/bin/bash

COUNT=0
REPO=""
if [ $# -ne 2 ]
then
    echo "Usage: $0 <REPO-DIR> <COUNT>"
    exit 1
fi
REPO=$1
COUNT=$2

echo "Creating the repo and cd'ing into it..."
mkdir -p $REPO && cd $REPO
[ ! -d .git/ ] && git init .

echo "Making commits..."
for i in `seq 0 $COUNT`;
do
    echo "modification made by commit $i" >> demo.txt
    git add demo.txt
    git commit -m "commit $i"
done

P.P.S. It's much easier to do that with Magit, a Git porcelain inside Emacs, check out this video to see how:


See also

comments powered by Disqus