ℹ️ Skipped - page is already crawled
| Filter | Status | Condition | Details |
|---|---|---|---|
| HTTP status | PASS | download_http_code = 200 | HTTP 200 |
| Age cutoff | PASS | download_stamp > now() - 6 MONTH | 0.1 months ago |
| History drop | PASS | isNull(history_drop_reason) | No drop reason |
| Spam/ban | PASS | fh_dont_index != 1 AND ml_spam_score = 0 | ml_spam_score=0 |
| Canonical | PASS | meta_canonical IS NULL OR = '' OR = src_unparsed | Not set |
| Property | Value |
|---|---|
| URL | https://zwischenzugs.com/2020/09/10/five-ways-to-undo-a-commit-in-git/ |
| Last Crawled | 2026-04-05 10:12:46 (1 day ago) |
| First Indexed | 2020-09-11 04:23:14 (5 years ago) |
| HTTP Status Code | 200 |
| Meta Title | Five Ways to Undo a Commit in Git – zwischenzugs.com |
| Meta Description | null |
| Meta Canonical | null |
| Boilerpipe Text | Recently, while showing someone at work a useful Git ‘trick’, I was asked “how many ways are there to undo a bad change in Git?”. This got me thinking, and I came up with a walkthrough similar to the ones I use in
my book
to help embed key Git concepts and principles.
There’s many ways to achieve the result you might want, so this can be a pretty instructive and fertile question to answer.
If you want to follow along, run these commands to set up a simple series of changes to a git repository:
cd $(mktemp -d)
git init
for i in $(seq 1 10)
do
echo "Change $i" >> file
git add file
git commit -m "C${i}"
done
Now you are in a fresh folder with a git repository that has 10 simple changes to a single file (called ‘
file'
) in it. If you run:
git log --oneline --graph --all
at any point in this walkthrough you should be able to see what’s going on.
See
here
if you want to know more about this
git log
command.
1) Manual Reversion
The simplest way (conceptually, at least) to undo a change is to add a new commit that just reverses the change you last made.
First, you need to see exactly what the change was. The ‘
git show
‘ command can give you this:
git show HEAD
The output should show you a diff of what changed in the last entry. The
HEAD
tag always points to a specific commit. If you haven’t done any funny business on your repo then it points to the last commit on the branch you are working on.
Next, you apply the changes by hand, or you can run this command (which effectively removes the last line of the file) to achieve the same result
in this particular context only
:
head -9 file > file2
mv file2 file
and then commit the change:
git commit -am 'Revert C10 manually'
2)
git revert
Sometimes the manual approach is not easy to achieve, or you want to revert a specific commit (ie
not
the previous one on your branch). Let’s say we want to reverse the last-but-
two
commit on our branch (ie the one that added ‘
Change 9
‘ to the file).
First we use the
git rev-list
command to list the previous changes in reverse order, and capture the commit ID we want to the
LASTBUTONE
variable using pipes to
head
and
tail
:
COMMITID=$(git rev-list HEAD | head -3 | tail -1)
Now check that that change is the one you want:
git show ${COMMITID}
which should output:
commit 77c8261da4646d8850b1ac1df16346fbdcd0b074
Author: ian
ian.miell@gmail.com
Date: Mon Sep 7 13:38:42 2020 +0100
C9
diff --git a/file b/file
index 5fc3c46..0f3aaf4 100644
--- a/file
+++ b/file
@@ -6,3 +6,4 @@ Change 5
Change 6
Change 7
Change 8
+Change 9
Now, to reverse that change, run:
git revert ${COMMITID}
and follow the instructions to commit the change. The file should now have reverted the entry for
Change 9
and the last line should be
Change 8
. This operation is easy in this trivial example, but can get complicated if the changes are many and varied.
3) Re-point Your Branch
This method makes an assumption that you can force-push changes to remote branches. This is because it changes the history of the repository to effectively ‘forget’ about the change you just made.
In this walkthrough we don’t have a remote to push to, so it doesn’t apply.
Briefly, we’re going to:
check out the specific commit we want to return to, and
point our branch at that commit
The ‘bad commit’ is still there in our local Git repository, but it has no branch associated with it, so it ‘dangles’ off a branch until we do something with it. We’re actually going to maintain a ‘
rejected-1
‘ branch for that bad commit, because it’s neater.
Let’s first push a bad change we want to forget about:
echo Bad change > file
git commit -am 'Bad change committed'
Now we realise that that change was a bad one. First we make a branch from where we are, so we can more easily get back to the bad commit if we need to:
git branch rejected-1
Now let’s check out the commit
before
the bad one we just committed:
git checkout HEAD^
Right now you have checked out the commit you would like your master branch to be pointed at. But you likely got the scary
detached HEAD
message:
Note: switching to 'HEAD^'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
What this means is that you are on the commit you want to be on, but are not on a branch.
master
is still pointed at the ‘bad’ commit. You can check this with a quick log command:
$ git log --oneline --graph --all --decorate
8f08673 (rejected-1,
master
) Bad change committed
cbc9576 (HEAD) Revert "C9"
ef86963 Revert C10 manually
067bb17 C10
77c8261 C9
6a10d2b C8
1125bde C7
a058fa5 C6
a8392e9 C5
dca0013 C4
46a0b18 C3
3df2db8 C2
84a9d7a C1
Your ‘
HEAD
‘ (ie, where your Git repository is right now) is pointed at the commit before the ‘bad’ one. The
rejected-1
and
master
branches are still pointed at the ‘bad’ commit.
We want the
master
branch to point to where we are right now (
HEAD
). To do this, use
git branch
, but force the branching to override the error we would get because the branch already exists. This is where we start to change the Git repo’s history.
git branch -f master
The log should now show we are on the master branch:
8f08673 (rejected-1) Bad change committed
cbc9576 (HEAD,
master
) Revert "C9"
ef86963 Revert C10 manually
067bb17 C10
77c8261 C9
6a10d2b C8
1125bde C7
a058fa5 C6
a8392e9 C5
dca0013 C4
46a0b18 C3
3df2db8 C2
84a9d7a C1
You should be able to see now why we branched off the
rejected-1
branch earlier. If we want to get back to the ‘bad’ commit, it’s easy to check out that branch. Also, the branch provides an annotation for what the commit is (ie a mistake).
We’re not finished yet, though! The commit you have checked out is now the same as the commit the
master
branch is on, but you still need to tell Git that you want to be on the
master
branch:
git checkout master
Now you have effectively un-done your change. The ‘bad’ change is safely on the
rejected-1
branch, and you can continue your work as if it never happened.
Remember that if you have a remote, then you will need to force-push this change with a
git push -f
. In this walkthrough we don’t have a remote, so we won’t do that.
If you like this, you might like my book
Learn Git the Hard Way
4)
git reset
There’s a more direct way to revert your local repository to a specific commit. This also changes history, as it re-sets the branch you are one back some steps.
Let’s say we want to go back to ‘Change 8’ (with the commit message ‘
C8
‘).
COMMITID=$(git rev-list HEAD | head -5 | tail -1)
echo $COMMITID
Check this is the commit you want by looking at the history:
git log --oneline --graph --all
Finally, use the
git reset
command to . The
--hard
flag tells git that you don’t mind changing the history of the repository by moving the branch tip backwards.
git reset --hard "${COMMITID}"
Now your
HEAD
pointer and
master
branch are pointed at the change you wanted.
5)
git rebase
This time we’re going to use
git rebase
to go back to ‘Change 6’. As before, you first get the relevant commit ID. Then you use the
git rebase
command with the
-i
(interactive) flag to ‘drop’ the relevant commits from your branch.
COMMITID=$(git rev-list HEAD | head -3 | tail -1)
git rebase -i "${COMMITID}"
At this point you’re prompted to decide what to do with the previous commits before continuing. Put a ‘
d
‘ next to the commits you want to forget about.
If you run the
git log
command again:
git log --oneline --graph --all
You’ll see that the commits are still there, but the
master
branch has been moved back to the commit you wanted:
8f08673 (rejected-1) Bad change committed
cbc9576 Revert "C9"
ef86963 Revert C10 manually
067bb17 C10
77c8261 C9
6a10d2b C8
1125bde C7
a058fa5 (HEAD ->
master
) C6
a8392e9 C5
dca0013 C4
46a0b18 C3
3df2db8 C2
84a9d7a C1
This trick can also get you to return your branch to the initial commit without losing the other commits, which is sometimes useful:
git rebase -i $(git rev-list --max-parents=0 HEAD)
This uses the
git rev-list
command and
--max-parents
flag to give you the first commit ID in the history. Dropping all the above commits by putting ‘
d
‘ next to all the commits takes your branch back to the initial commit.
Other git posts
Git Log – the Good Parts
Five Things I Wish I’d Known About
Git
Beyond ‘Punk Rock’ Git
Power Git Log Graphing
Git Hooks the Hard Way
If you like this, you might like one of my books:
Learn Bash the Hard Way
Learn Git the Hard Way
Learn Terraform the Hard Way
Buy in a bundle
here |
| Markdown | [Skip to content](https://zwischenzugs.com/2020/09/10/five-ways-to-undo-a-commit-in-git/#content "Skip to content")
[zwischenzugs.com](https://zwischenzugs.com/)
# Five Ways to Undo a Commit in Git
10 September 2020
by [imiell](https://zwischenzugs.com/author/imiell/ "View all posts by imiell")
Recently, while showing someone at work a useful Git ‘trick’, I was asked “how many ways are there to undo a bad change in Git?”. This got me thinking, and I came up with a walkthrough similar to the ones I use in [my book](https://leanpub.com/learngitthehardway) to help embed key Git concepts and principles.
There’s many ways to achieve the result you might want, so this can be a pretty instructive and fertile question to answer.
If you want to follow along, run these commands to set up a simple series of changes to a git repository:
```
cd $(mktemp -d)
git init
for i in $(seq 1 10)
do
echo "Change $i" >> file
git add file
git commit -m "C${i}"
done
```
Now you are in a fresh folder with a git repository that has 10 simple changes to a single file (called ‘`file'`) in it. If you run:
```
git log --oneline --graph --all
```
at any point in this walkthrough you should be able to see what’s going on.
See [here](https://zwischenzugs.com/2018/03/26/git-log-the-good-parts/) if you want to know more about this `git log` command.
***
## 1\) Manual Reversion
The simplest way (conceptually, at least) to undo a change is to add a new commit that just reverses the change you last made.
First, you need to see exactly what the change was. The ‘`git show`‘ command can give you this:
```
git show HEAD
```
The output should show you a diff of what changed in the last entry. The `HEAD` tag always points to a specific commit. If you haven’t done any funny business on your repo then it points to the last commit on the branch you are working on.
Next, you apply the changes by hand, or you can run this command (which effectively removes the last line of the file) to achieve the same result *in this particular context only*:
```
head -9 file > file2
mv file2 file
```
and then commit the change:
```
git commit -am 'Revert C10 manually'
```
***
## 2\) `git revert`
Sometimes the manual approach is not easy to achieve, or you want to revert a specific commit (ie *not* the previous one on your branch). Let’s say we want to reverse the last-but-*two* commit on our branch (ie the one that added ‘`Change 9`‘ to the file).
First we use the `git rev-list` command to list the previous changes in reverse order, and capture the commit ID we want to the `LASTBUTONE` variable using pipes to `head` and `tail`:
```
COMMITID=$(git rev-list HEAD | head -3 | tail -1)
```
Now check that that change is the one you want:
```
git show ${COMMITID}
```
which should output:
```
commit 77c8261da4646d8850b1ac1df16346fbdcd0b074
Author: ian ian.miell@gmail.com
Date: Mon Sep 7 13:38:42 2020 +0100
C9
diff --git a/file b/file
index 5fc3c46..0f3aaf4 100644
--- a/file
+++ b/file
@@ -6,3 +6,4 @@ Change 5
Change 6
Change 7
Change 8
+Change 9
```
Now, to reverse that change, run:
```
git revert ${COMMITID}
```
and follow the instructions to commit the change. The file should now have reverted the entry for `Change 9` and the last line should be `Change 8`. This operation is easy in this trivial example, but can get complicated if the changes are many and varied.
***
## 3\) Re-point Your Branch
This method makes an assumption that you can force-push changes to remote branches. This is because it changes the history of the repository to effectively ‘forget’ about the change you just made.
In this walkthrough we don’t have a remote to push to, so it doesn’t apply.
Briefly, we’re going to:
- check out the specific commit we want to return to, and
- point our branch at that commit
The ‘bad commit’ is still there in our local Git repository, but it has no branch associated with it, so it ‘dangles’ off a branch until we do something with it. We’re actually going to maintain a ‘`rejected-1`‘ branch for that bad commit, because it’s neater.
Let’s first push a bad change we want to forget about:
```
echo Bad change > file
git commit -am 'Bad change committed'
```
Now we realise that that change was a bad one. First we make a branch from where we are, so we can more easily get back to the bad commit if we need to:
```
git branch rejected-1
```
Now let’s check out the commit *before* the bad one we just committed:
```
git checkout HEAD^
```
Right now you have checked out the commit you would like your master branch to be pointed at. But you likely got the scary `detached HEAD` message:
```
Note: switching to 'HEAD^'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
```
What this means is that you are on the commit you want to be on, but are not on a branch. `master` is still pointed at the ‘bad’ commit. You can check this with a quick log command:
```
$ git log --oneline --graph --all --decorate
8f08673 (rejected-1, master) Bad change committed
cbc9576 (HEAD) Revert "C9"
ef86963 Revert C10 manually
067bb17 C10
77c8261 C9
6a10d2b C8
1125bde C7
a058fa5 C6
a8392e9 C5
dca0013 C4
46a0b18 C3
3df2db8 C2
84a9d7a C1
```
Your ‘`HEAD`‘ (ie, where your Git repository is right now) is pointed at the commit before the ‘bad’ one. The `rejected-1` and `master` branches are still pointed at the ‘bad’ commit.
We want the `master` branch to point to where we are right now (`HEAD`). To do this, use `git branch`, but force the branching to override the error we would get because the branch already exists. This is where we start to change the Git repo’s history.
```
git branch -f master
```
The log should now show we are on the master branch:
```
8f08673 (rejected-1) Bad change committed
cbc9576 (HEAD, master) Revert "C9"
ef86963 Revert C10 manually
067bb17 C10
77c8261 C9
6a10d2b C8
1125bde C7
a058fa5 C6
a8392e9 C5
dca0013 C4
46a0b18 C3
3df2db8 C2
84a9d7a C1
```
You should be able to see now why we branched off the `rejected-1` branch earlier. If we want to get back to the ‘bad’ commit, it’s easy to check out that branch. Also, the branch provides an annotation for what the commit is (ie a mistake).
We’re not finished yet, though! The commit you have checked out is now the same as the commit the `master` branch is on, but you still need to tell Git that you want to be on the `master` branch:
```
git checkout master
```
Now you have effectively un-done your change. The ‘bad’ change is safely on the `rejected-1` branch, and you can continue your work as if it never happened.
Remember that if you have a remote, then you will need to force-push this change with a `git push -f`. In this walkthrough we don’t have a remote, so we won’t do that.
***
***If you like this, you might like my book*** ***[Learn Git the Hard Way](https://leanpub.com/learngitthehardway?p=5148)***
[](https://leanpub.com/learngitthehardway?p=5148)
***
## 4\) `git reset`
There’s a more direct way to revert your local repository to a specific commit. This also changes history, as it re-sets the branch you are one back some steps.
Let’s say we want to go back to ‘Change 8’ (with the commit message ‘`C8`‘).
```
COMMITID=$(git rev-list HEAD | head -5 | tail -1)
echo $COMMITID
```
Check this is the commit you want by looking at the history:
```
git log --oneline --graph --all
```
Finally, use the `git reset` command to . The `--hard` flag tells git that you don’t mind changing the history of the repository by moving the branch tip backwards.
```
git reset --hard "${COMMITID}"
```
Now your `HEAD` pointer and `master` branch are pointed at the change you wanted.
***
## 5\) `git rebase`
This time we’re going to use `git rebase` to go back to ‘Change 6’. As before, you first get the relevant commit ID. Then you use the `git rebase` command with the `-i` (interactive) flag to ‘drop’ the relevant commits from your branch.
```
COMMITID=$(git rev-list HEAD | head -3 | tail -1)
git rebase -i "${COMMITID}"
```
At this point you’re prompted to decide what to do with the previous commits before continuing. Put a ‘`d`‘ next to the commits you want to forget about.
If you run the `git log` command again:
```
git log --oneline --graph --all
```
You’ll see that the commits are still there, but the `master` branch has been moved back to the commit you wanted:
```
8f08673 (rejected-1) Bad change committed
cbc9576 Revert "C9"
ef86963 Revert C10 manually
067bb17 C10
77c8261 C9
6a10d2b C8
1125bde C7
a058fa5 (HEAD -> master) C6
a8392e9 C5
dca0013 C4
46a0b18 C3
3df2db8 C2
84a9d7a C1
```
This trick can also get you to return your branch to the initial commit without losing the other commits, which is sometimes useful:
```
git rebase -i $(git rev-list --max-parents=0 HEAD)
```
This uses the `git rev-list` command and `--max-parents` flag to give you the first commit ID in the history. Dropping all the above commits by putting ‘`d`‘ next to all the commits takes your branch back to the initial commit.
***
### Other git posts
[Git Log – the Good Parts](https://zwischenzugs.com/2018/03/26/git-log-the-good-parts/)
[Five Things I Wish I’d Known About](https://zwischenzugs.com/2018/10/30/five-things-i-wish-id-known-about-git/)[Git](https://zwischenzugs.com/2018/10/30/five-things-i-wish-id-known-about-git/)
[Beyond ‘Punk Rock’ Git](https://zwischenzugs.com/2018/05/14/beyond-punk-rock-git-in-eleven-steps/)
[Power Git Log Graphing](https://zwischenzugs.com/2016/06/04/power-git-log-graphing/)
[Git Hooks the Hard Way](https://zwischenzugs.com/2019/01/09/git-hooks-the-hard-way/)
***
***If you like this, you might like one of my books: [Learn Bash the Hard Way](https://leanpub.com/learnbashthehardway?p=4369)***
***[Learn Git the Hard Way](https://leanpub.com/learngitthehardway?p=4369)***
***[Learn Terraform the Hard Way](https://leanpub.com/learnterraformthehardway)***
[](https://leanpub.com/b/learngitbashandterraformthehardway)
Buy in a bundle [here](https://leanpub.com/b/learngitbashandterraformthehardway)
### *Related*
Categories [Uncategorized](https://zwischenzugs.com/category/uncategorized/)
[The Halving of the Centre: Covid and its Effect on London Property](https://zwischenzugs.com/2020/07/25/the-halving-of-the-centre-covid-and-its-effect-on-london-property/)
[GitOps Decisions](https://zwischenzugs.com/2020/11/30/gitops-decisions/)
## 2 thoughts on “Five Ways to Undo a Commit in Git”
1. Pingback: [Dew Drop – September 11, 2020 (\#3273) \| Morning Dew](https://www.alvinashcraft.com/2020/09/11/dew-drop-september-11-2020-3273/)
2. Pingback: [Five Ways to Undo a Commit in Git \> Seekalgo](https://seekalgo.net/five-ways-to-undo-a-commit-in-git/)
### Leave a comment [Cancel reply](https://zwischenzugs.com/2020/09/10/five-ways-to-undo-a-commit-in-git/#respond)
You must be [logged in](https://zwischenzugs.com/wp-login.php?redirect_to=https%3A%2F%2Fzwischenzugs.com%2F2020%2F09%2F10%2Ffive-ways-to-undo-a-commit-in-git%2F) to post a comment.
## Popular Posts
- [Fine-Tuning an AI – Part I](https://zwischenzugs.com/2025/11/04/fine-tuning-an-ai-part-i/)
- [I’m Writing a New Book\!](https://zwischenzugs.com/2025/07/12/im-writing-a-new-book/)
- [Exploring OSCAL Using Neo4J](https://zwischenzugs.com/2025/03/15/exploring-oscal-using-neo4j/)
- [Does Crossplane Replace Terraform? Part I: the Theory](https://zwischenzugs.com/2024/07/16/does-crossplane-replace-terraform-part-i-the-theory/)
- [In Praise of Low Tech DevEx](https://zwischenzugs.com/2024/07/05/in-praise-of-low-tech-devex/)
- [Fine-Tuning an AI – Part I](https://zwischenzugs.com/2025/11/04/fine-tuning-an-ai-part-i/)
- [I’m Writing a New Book\!](https://zwischenzugs.com/2025/07/12/im-writing-a-new-book/)
- [Exploring OSCAL Using Neo4J](https://zwischenzugs.com/2025/03/15/exploring-oscal-using-neo4j/)
- [Does Crossplane Replace Terraform? Part I: the Theory](https://zwischenzugs.com/2024/07/16/does-crossplane-replace-terraform-part-i-the-theory/)
- [In Praise of Low Tech DevEx](https://zwischenzugs.com/2024/07/05/in-praise-of-low-tech-devex/)
- [At 50 Years Old, Is SQL Becoming a Niche Skill?](https://zwischenzugs.com/2024/06/27/at-50-years-old-is-sql-becoming-a-niche-skill/)
- [OK Cloud, On-Prem is Alright](https://zwischenzugs.com/2024/06/18/ok-cloud-on-prem-is-alright/)
- [What I Learned Using Private LLMs to Write an Undergraduate History Essay](https://zwischenzugs.com/2023/12/27/what-i-learned-using-private-llms-to-write-an-undergraduate-history-essay/)
- [Learn jq the Hard Way,Part IV: Pipes](https://zwischenzugs.com/2023/07/13/learn-jq-the-hard-waypart-iv-pipes/)
- [Learn jq the Hard Way,Part III: Filters](https://zwischenzugs.com/2023/06/27/learn-jq-the-hard-waypart-iii-filters/)
- [Learn jq the Hard Way, Part II: The jq Command](https://zwischenzugs.com/2023/06/27/learn-jq-the-hard-way-part-ii-the-jq-command/)
- [Learn jq the Hard Way, Part I: JSON](https://zwischenzugs.com/2023/06/27/learn-jq-the-hard-way-part-i-json/)
- [Monoliths, Microservervices and Mainframes – Reflections on Amazon Prime Video’s Monolith Move](https://zwischenzugs.com/2023/06/04/monoliths-microservervices-and-mainframes-reflections-on-amazon-prime-videos-monolith-move/)
- [Is it Imperative to be Declarative?](https://zwischenzugs.com/2023/04/27/is-it-imperative-to-be-declarative/)
- [The Biggest Cloud Native Strategy Mistake](https://zwischenzugs.com/2023/03/29/the-biggest-cloud-native-strategy-mistake/)
- [Practical Strategies for Implementing DevSecOps in Large Enterprises](https://zwischenzugs.com/2023/03/19/practical-strategies-for-implementing-devsecops-in-large-enterprises/)
- [A Little Shell Rabbit Hole](https://zwischenzugs.com/2022/09/28/a-little-shell-rabbit-hole/)
- [“Who Should Write the Terraform?”](https://zwischenzugs.com/2022/08/08/who-should-write-the-terraform/)
- [Business Value, Soccer Canteens, Engineer Retention, and the Bricklayer Fallacy](https://zwischenzugs.com/2022/05/29/business-value-management-autonomy-and-engineer-retention/)
- [Five Reasons To Master Git On The Command Line](https://zwischenzugs.com/2022/04/05/five-reasons-to-master-git-on-the-command-line/)
- [How 3D Printing Kindled A Love For Baroque Sculpture](https://zwischenzugs.com/2022/03/13/how-3d-printing-kindled-a-love-for-baroque-sculpture/)
- [grep Flags – The Good Stuff](https://zwischenzugs.com/2022/02/02/grep-flags-the-good-stuff/)
- [Why It’s Great To Be A Consultant](https://zwischenzugs.com/2022/01/17/why-its-great-to-be-a-consultant/)
- [Practical Shell Patterns I Actually Use](https://zwischenzugs.com/2022/01/04/practical-shell-patterns-i-actually-use/)
- [Why I Keep Coming Back to Cynefin](https://zwischenzugs.com/2021/12/06/why-i-keep-coming-back-to-cynefin/)
- [Is Agility Related to Commitment? – Money Flows Part II](https://zwischenzugs.com/2021/10/18/is-agility-related-to-commitment-money-flows-part-ii/)
- [Five Ansible Techniques I Wish I’d Known Earlier](https://zwischenzugs.com/2021/08/27/five-ansible-techniques-i-wish-id-known-earlier/)
- [A ‘Hello World’ GitOps Example Walkthrough](https://zwischenzugs.com/2021/07/31/a-hello-world-gitops-example-walkthrough/)
- [If You Want To Transform IT, Start With Finance](https://zwischenzugs.com/2021/07/12/if-you-want-to-transform-it-start-with-finance/)
- [How To Waste Hundreds of Millions on Your IT Transformation](https://zwischenzugs.com/2021/04/16/how-to-waste-hundreds-of-millions-on-your-it-transformation/)
- [When Should I Interrupt Someone?](https://zwischenzugs.com/2021/03/15/when-should-i-interrupt-someone/)
- [An Incompetent Newbie Takes Up 3D Printing](https://zwischenzugs.com/2021/02/21/an-incompetent-newbie-takes-up-3d-printing/)
- [GitOps Decisions](https://zwischenzugs.com/2020/11/30/gitops-decisions/)
- [Five Ways to Undo a Commit in Git](https://zwischenzugs.com/2020/09/10/five-ways-to-undo-a-commit-in-git/)
- [The Halving of the Centre: Covid and its Effect on London Property](https://zwischenzugs.com/2020/07/25/the-halving-of-the-centre-covid-and-its-effect-on-london-property/)
- [Why Do We Have Dev Rels Now?](https://zwischenzugs.com/2020/07/13/why-do-we-have-dev-rels-now/)
- [The Runbooks Project](https://zwischenzugs.com/2020/06/25/the-runbooks-project/)
- [Some Relatively Obscure Bash Tips](https://zwischenzugs.com/2020/05/09/some-relatively-obscure-bash-tips/)
- [Riding the Tiger: Lessons Learned Implementing Istio](https://zwischenzugs.com/2020/05/05/riding-the-tiger-lessons-learned-implementing-istio/)
- [The Astonishing Prescience of Nam June Paik](https://zwischenzugs.com/2020/01/04/the-astonishing-prescience-of-nam-june-paik/)
- [Notes on Books Read in 2019](https://zwischenzugs.com/2020/01/01/notes-on-books-read-in-2019/)
- [The First Non-Bullshit Book About Culture I’ve Read](https://zwischenzugs.com/2019/11/27/the-first-non-bullshit-book-about-culture-ive-read/)
- [Why Everyone Working in DevOps Should Read The Toyota Way](https://zwischenzugs.com/2019/11/06/why-everyone-working-in-devops-should-read-the-toyota-way/)
- [Surgically Busting the Docker Cache](https://zwischenzugs.com/2019/09/27/surgically-busting-the-docker-cache/)
- [Software Security Field Guide for the Bewildered](https://zwischenzugs.com/2019/09/22/software-security-field-guide-for-the-bewildered/)
- [The Lazy Person’s Guide to the Info Command](https://zwischenzugs.com/2019/09/04/the-lazy-persons-guide-to-the-info-command/)
- [A Hot Take on GitHub Actions](https://zwischenzugs.com/2019/08/30/a-hot-take-on-github-actions/)
- [Seven God-Like Bash History Shortcuts You Will Actually Use](https://zwischenzugs.com/2019/08/25/seven-god-like-bash-history-shortcuts-you-will-actually-use/)
- [How Long Will It Take For The Leavers To Leave?](https://zwischenzugs.com/2019/08/15/how-long-will-it-take-for-the-leavers-to-leave/)
- [Goodbye Docker: Purging is Such Sweet Sorrow](https://zwischenzugs.com/2019/07/27/goodbye-docker-purging-is-such-sweet-sorrow/)
- [Seven Surprising Bash Variables](https://zwischenzugs.com/2019/05/11/seven-surprising-bash-variables/)
- [The Missing Readline Primer](https://zwischenzugs.com/2019/04/23/the-missing-readline-primer/)
- [Apple’s HQ, Ruskin, Gothic Architecture, and Agile](https://zwischenzugs.com/2019/04/12/apples-hq-ruskin-gothic-architecture-and-agile/)
- [Eight Obscure Bash Options You Might Want to Know About](https://zwischenzugs.com/2019/04/03/eight-obscure-bash-options-you-might-want-to-know-about/)
- [‘AWS vs K8s’ is the new ‘Windows vs Linux’](https://zwischenzugs.com/2019/03/25/aws-vs-k8s-is-the-new-windows-vs-linux/)
- [Pranking the Bash Binary](https://zwischenzugs.com/2019/03/16/pranking-the-bash-binary/)
- [Bash Startup Explained](https://zwischenzugs.com/2019/02/27/bash-startup-explained/)
- [Git Hooks the Hard Way](https://zwischenzugs.com/2019/01/09/git-hooks-the-hard-way/)
- [Notes on Books Read in 2018](https://zwischenzugs.com/2019/01/02/notes-on-books-read-in-2018/)
- [Six Ways to Level Up Your nmap Game](https://zwischenzugs.com/2018/11/25/six-ways-to-level-up-your-nmap-game/)
- [Five Things I Wish I’d Known About Git](https://zwischenzugs.com/2018/10/30/five-things-i-wish-id-known-about-git/)
- [Eleven bash Tips You Might Want to Know](https://zwischenzugs.com/2018/10/12/eleven-bash-tips-you-might-want-to-know/)
- [Learn Bash Debugging Techniques the Hard Way](https://zwischenzugs.com/2018/10/09/learn-bash-debugging-techniques-the-hard-way/)
- [Why Are Enterprises So Slow?](https://zwischenzugs.com/2018/10/02/why-are-enterprises-so-slow/)
- [Anatomy of a Linux DNS Lookup – Part V – Two Debug Nightmares](https://zwischenzugs.com/2018/09/13/anatomy-of-a-linux-dns-lookup-part-v-two-debug-nightmares/)
- [Anatomy of a Linux DNS Lookup – Part IV](https://zwischenzugs.com/2018/08/06/anatomy-of-a-linux-dns-lookup-part-iv/)
- [Anatomy of a Linux DNS Lookup – Part III](https://zwischenzugs.com/2018/07/06/anatomy-of-a-linux-dns-lookup-part-iii/)
- [Anatomy of a Linux DNS Lookup – Part II](https://zwischenzugs.com/2018/06/18/anatomy-of-a-linux-dns-lookup-part-ii/)
- [Anatomy of a Linux DNS Lookup – Part I](https://zwischenzugs.com/2018/06/08/anatomy-of-a-linux-dns-lookup-part-i/)
- [A Docker Image in Less Than 1000 Bytes](https://zwischenzugs.com/2018/05/22/a-docker-image-in-less-than-1000-bytes/)
- [Autotrace – Debug on Steroids](https://zwischenzugs.com/2018/05/21/autotrace-debug-on-steroids/)
- [Beyond ‘Punk Rock Git’ in Eleven Steps](https://zwischenzugs.com/2018/05/14/beyond-punk-rock-git-in-eleven-steps/)
- [Sandboxing Docker with Google’s gVisor](https://zwischenzugs.com/2018/05/05/sandboxing-docker-with-googles-gvisor/)
- [Unprivileged Docker Builds – A Proof of Concept](https://zwischenzugs.com/2018/04/23/unprivileged-docker-builds-a-proof-of-concept/)
- [Learn Git Rebase Interactively](https://zwischenzugs.com/2018/04/05/learn-git-rebase-interactively/)
- [Terminal Perf Graphs in one Command](https://zwischenzugs.com/2018/03/27/terminal-perf-graphs-in-one-command/)
- [git log – the Good Parts](https://zwischenzugs.com/2018/03/26/git-log-the-good-parts/)
- [Five Key Git Concepts Explained the Hard Way](https://zwischenzugs.com/2018/03/14/five-key-git-concepts-explained-the-hard-way/)
- [Create Your Own Git Diagrams](https://zwischenzugs.com/2018/03/08/create-your-own-git-diagrams/)
- [Five Things I Did to Change a Team’s Culture](https://zwischenzugs.com/2018/02/24/5-things-i-did-to-change-a-teams-culture/)
- [Centralise Your Bash History](https://zwischenzugs.com/2018/02/05/centralise-your-bash-history/)
- [How (and Why) I Run My Own DNS Servers](https://zwischenzugs.com/2018/01/26/how-and-why-i-run-my-own-dns-servers/)
- [Ten More Things I Wish I'd Known About bash](https://zwischenzugs.com/2018/01/21/ten-more-things-i-wish-id-known-about-bash/)
- [Download a Free Sample of Learn Bash the Hard Way](https://zwischenzugs.com/2018/01/18/download-a-free-sample-of-learn-bash-the-hard-way/)
- [Ten Things I Wish I’d Known About bash](https://zwischenzugs.com/2018/01/06/ten-things-i-wish-id-known-about-bash/)
- [Project Management as Code with Graphviz](https://zwischenzugs.com/2017/12/18/project-management-as-code-with-graphviz/)
- [How to Manually Clear Locks in Jenkins](https://zwischenzugs.com/2017/12/11/how-to-manually-clear-locks-in-jenkins/)
- [How I Manage My Time](https://zwischenzugs.com/2017/12/03/how-i-manage-my-time/)
- [Ten Things I Wish I’d Known About Chef](https://zwischenzugs.com/2017/11/25/ten-things-i-wish-id-known-about-chef/)
- [Vagrant and Ohai / Chef IP Address Hack](https://zwischenzugs.com/2017/11/18/__trashed-4/)
- [‘Towards a National Computer Grid’ – Electronic Computers, 1965](https://zwischenzugs.com/2017/11/11/towards-a-national-computer-grid-electronic-computers-1965/)
- [A Complete Chef Infrastructure on Your Laptop](https://zwischenzugs.com/2017/10/31/a-complete-chef-infrastructure-on-your-laptop/)
- [Ten Things I Wish I’d Known Before Using Vagrant](https://zwischenzugs.com/2017/10/27/ten-things-i-wish-id-known-before-using-vagrant/)
- [A Checklist for Docker in the Enterprise (Updated)](https://zwischenzugs.com/2017/10/23/a-checklist-for-docker-in-the-enterprise-updated/)
- [OpenShift 3.6 DNS In Pictures](https://zwischenzugs.com/2017/10/21/openshift-3-6-dns-in-pictures/)
- [Puppeteer – Headless Chrome in a Container](https://zwischenzugs.com/2017/10/16/puppeteer-headless-chrome-in-a-container/)
- [My 20-Year Experience of Software Development Methodologies](https://zwischenzugs.com/2017/10/15/my-20-year-experience-of-software-development-methodologies/)
- [A Non-Cloud Serverless Application Pattern Using Git and Docker](https://zwischenzugs.com/2017/08/07/a-non-cloud-serverless-application-pattern-using-git-and-docker/)
- [Run Your Own AWS APIs on OpenShift](https://zwischenzugs.com/2017/07/31/run-your-own-aws-apis-on-openshift/)
- [Dockerized Headless Chrome Example](https://zwischenzugs.com/2017/07/15/dockerized-headless-chrome-example/)
© 2026 zwischenzugs.com • Built with [GeneratePress](https://generatepress.com/) |
| Readable Markdown | Recently, while showing someone at work a useful Git ‘trick’, I was asked “how many ways are there to undo a bad change in Git?”. This got me thinking, and I came up with a walkthrough similar to the ones I use in [my book](https://leanpub.com/learngitthehardway) to help embed key Git concepts and principles.
There’s many ways to achieve the result you might want, so this can be a pretty instructive and fertile question to answer.
If you want to follow along, run these commands to set up a simple series of changes to a git repository:
```
cd $(mktemp -d)
git init
for i in $(seq 1 10)
do
echo "Change $i" >> file
git add file
git commit -m "C${i}"
done
```
Now you are in a fresh folder with a git repository that has 10 simple changes to a single file (called ‘`file'`) in it. If you run:
```
git log --oneline --graph --all
```
at any point in this walkthrough you should be able to see what’s going on.
See [here](https://zwischenzugs.com/2018/03/26/git-log-the-good-parts/) if you want to know more about this `git log` command.
***
## 1\) Manual Reversion
The simplest way (conceptually, at least) to undo a change is to add a new commit that just reverses the change you last made.
First, you need to see exactly what the change was. The ‘`git show`‘ command can give you this:
```
git show HEAD
```
The output should show you a diff of what changed in the last entry. The `HEAD` tag always points to a specific commit. If you haven’t done any funny business on your repo then it points to the last commit on the branch you are working on.
Next, you apply the changes by hand, or you can run this command (which effectively removes the last line of the file) to achieve the same result *in this particular context only*:
```
head -9 file > file2
mv file2 file
```
and then commit the change:
```
git commit -am 'Revert C10 manually'
```
***
## 2\) `git revert`
Sometimes the manual approach is not easy to achieve, or you want to revert a specific commit (ie *not* the previous one on your branch). Let’s say we want to reverse the last-but-*two* commit on our branch (ie the one that added ‘`Change 9`‘ to the file).
First we use the `git rev-list` command to list the previous changes in reverse order, and capture the commit ID we want to the `LASTBUTONE` variable using pipes to `head` and `tail`:
```
COMMITID=$(git rev-list HEAD | head -3 | tail -1)
```
Now check that that change is the one you want:
```
git show ${COMMITID}
```
which should output:
```
commit 77c8261da4646d8850b1ac1df16346fbdcd0b074
Author: ian ian.miell@gmail.com
Date: Mon Sep 7 13:38:42 2020 +0100
C9
diff --git a/file b/file
index 5fc3c46..0f3aaf4 100644
--- a/file
+++ b/file
@@ -6,3 +6,4 @@ Change 5
Change 6
Change 7
Change 8
+Change 9
```
Now, to reverse that change, run:
```
git revert ${COMMITID}
```
and follow the instructions to commit the change. The file should now have reverted the entry for `Change 9` and the last line should be `Change 8`. This operation is easy in this trivial example, but can get complicated if the changes are many and varied.
***
## 3\) Re-point Your Branch
This method makes an assumption that you can force-push changes to remote branches. This is because it changes the history of the repository to effectively ‘forget’ about the change you just made.
In this walkthrough we don’t have a remote to push to, so it doesn’t apply.
Briefly, we’re going to:
- check out the specific commit we want to return to, and
- point our branch at that commit
The ‘bad commit’ is still there in our local Git repository, but it has no branch associated with it, so it ‘dangles’ off a branch until we do something with it. We’re actually going to maintain a ‘`rejected-1`‘ branch for that bad commit, because it’s neater.
Let’s first push a bad change we want to forget about:
```
echo Bad change > file
git commit -am 'Bad change committed'
```
Now we realise that that change was a bad one. First we make a branch from where we are, so we can more easily get back to the bad commit if we need to:
```
git branch rejected-1
```
Now let’s check out the commit *before* the bad one we just committed:
```
git checkout HEAD^
```
Right now you have checked out the commit you would like your master branch to be pointed at. But you likely got the scary `detached HEAD` message:
```
Note: switching to 'HEAD^'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
```
What this means is that you are on the commit you want to be on, but are not on a branch. `master` is still pointed at the ‘bad’ commit. You can check this with a quick log command:
```
$ git log --oneline --graph --all --decorate
8f08673 (rejected-1, master) Bad change committed
cbc9576 (HEAD) Revert "C9"
ef86963 Revert C10 manually
067bb17 C10
77c8261 C9
6a10d2b C8
1125bde C7
a058fa5 C6
a8392e9 C5
dca0013 C4
46a0b18 C3
3df2db8 C2
84a9d7a C1
```
Your ‘`HEAD`‘ (ie, where your Git repository is right now) is pointed at the commit before the ‘bad’ one. The `rejected-1` and `master` branches are still pointed at the ‘bad’ commit.
We want the `master` branch to point to where we are right now (`HEAD`). To do this, use `git branch`, but force the branching to override the error we would get because the branch already exists. This is where we start to change the Git repo’s history.
```
git branch -f master
```
The log should now show we are on the master branch:
```
8f08673 (rejected-1) Bad change committed
cbc9576 (HEAD, master) Revert "C9"
ef86963 Revert C10 manually
067bb17 C10
77c8261 C9
6a10d2b C8
1125bde C7
a058fa5 C6
a8392e9 C5
dca0013 C4
46a0b18 C3
3df2db8 C2
84a9d7a C1
```
You should be able to see now why we branched off the `rejected-1` branch earlier. If we want to get back to the ‘bad’ commit, it’s easy to check out that branch. Also, the branch provides an annotation for what the commit is (ie a mistake).
We’re not finished yet, though! The commit you have checked out is now the same as the commit the `master` branch is on, but you still need to tell Git that you want to be on the `master` branch:
```
git checkout master
```
Now you have effectively un-done your change. The ‘bad’ change is safely on the `rejected-1` branch, and you can continue your work as if it never happened.
Remember that if you have a remote, then you will need to force-push this change with a `git push -f`. In this walkthrough we don’t have a remote, so we won’t do that.
***
***If you like this, you might like my book*** ***[Learn Git the Hard Way](https://leanpub.com/learngitthehardway?p=5148)***
[](https://leanpub.com/learngitthehardway?p=5148)
***
## 4\) `git reset`
There’s a more direct way to revert your local repository to a specific commit. This also changes history, as it re-sets the branch you are one back some steps.
Let’s say we want to go back to ‘Change 8’ (with the commit message ‘`C8`‘).
```
COMMITID=$(git rev-list HEAD | head -5 | tail -1)
echo $COMMITID
```
Check this is the commit you want by looking at the history:
```
git log --oneline --graph --all
```
Finally, use the `git reset` command to . The `--hard` flag tells git that you don’t mind changing the history of the repository by moving the branch tip backwards.
```
git reset --hard "${COMMITID}"
```
Now your `HEAD` pointer and `master` branch are pointed at the change you wanted.
***
## 5\) `git rebase`
This time we’re going to use `git rebase` to go back to ‘Change 6’. As before, you first get the relevant commit ID. Then you use the `git rebase` command with the `-i` (interactive) flag to ‘drop’ the relevant commits from your branch.
```
COMMITID=$(git rev-list HEAD | head -3 | tail -1)
git rebase -i "${COMMITID}"
```
At this point you’re prompted to decide what to do with the previous commits before continuing. Put a ‘`d`‘ next to the commits you want to forget about.
If you run the `git log` command again:
```
git log --oneline --graph --all
```
You’ll see that the commits are still there, but the `master` branch has been moved back to the commit you wanted:
```
8f08673 (rejected-1) Bad change committed
cbc9576 Revert "C9"
ef86963 Revert C10 manually
067bb17 C10
77c8261 C9
6a10d2b C8
1125bde C7
a058fa5 (HEAD -> master) C6
a8392e9 C5
dca0013 C4
46a0b18 C3
3df2db8 C2
84a9d7a C1
```
This trick can also get you to return your branch to the initial commit without losing the other commits, which is sometimes useful:
```
git rebase -i $(git rev-list --max-parents=0 HEAD)
```
This uses the `git rev-list` command and `--max-parents` flag to give you the first commit ID in the history. Dropping all the above commits by putting ‘`d`‘ next to all the commits takes your branch back to the initial commit.
***
### Other git posts
[Git Log – the Good Parts](https://zwischenzugs.com/2018/03/26/git-log-the-good-parts/)
[Five Things I Wish I’d Known About](https://zwischenzugs.com/2018/10/30/five-things-i-wish-id-known-about-git/)[Git](https://zwischenzugs.com/2018/10/30/five-things-i-wish-id-known-about-git/)
[Beyond ‘Punk Rock’ Git](https://zwischenzugs.com/2018/05/14/beyond-punk-rock-git-in-eleven-steps/)
[Power Git Log Graphing](https://zwischenzugs.com/2016/06/04/power-git-log-graphing/)
[Git Hooks the Hard Way](https://zwischenzugs.com/2019/01/09/git-hooks-the-hard-way/)
***
***If you like this, you might like one of my books: [Learn Bash the Hard Way](https://leanpub.com/learnbashthehardway?p=4369)***
***[Learn Git the Hard Way](https://leanpub.com/learngitthehardway?p=4369)***
***[Learn Terraform the Hard Way](https://leanpub.com/learnterraformthehardway)***
[](https://leanpub.com/b/learngitbashandterraformthehardway)
Buy in a bundle [here](https://leanpub.com/b/learngitbashandterraformthehardway) |
| Shard | 38 (laksa) |
| Root Hash | 14082340772778440038 |
| Unparsed URL | com,zwischenzugs!/2020/09/10/five-ways-to-undo-a-commit-in-git/ s443 |