collaborated on an ML venture — rebasing function branches, merging experiment notebooks, or cleansing up commits earlier than a pull request — did you ever get to a degree the place you stated: “uh-oh, what did I just do?”
For any information scientist who works in a crew, having the ability to undo Git actions could be a life saver.
This put up provides you with the instruments to rewrite historical past with confidence.
Notes earlier than we begin
- I additionally gave a stay discuss masking the contents of this put up. In case you favor a video (or want to watch it alongside studying) — yow will discover it right here.
- 📕 My guide “Gitting Things Done” is formally out!
Get it on Amazon, as an eBook, or learn it at no cost at
Recording adjustments in Git
Earlier than understanding the way it undo issues in Git, it’s best to first perceive how we file adjustments in Git. In case you already know all of the phrases, be happy to skip this half.
It is vitally helpful to consider Git as a system for recording snapshots of a filesystem in time. Contemplating a Git repository, it has three “states” or “trees”:
Often, after we work on our supply code we work from a working dir. A working dir(ectrory) (or working tree) is any listing on our file system which has a repository related to it. It accommodates the folders and recordsdata of our venture, and in addition a listing known as .git. I described the contents of the .git folder in additional element in a earlier put up.
After you make some adjustments, chances are you’ll wish to file them in your repository. A repository (in brief: repo) is a set of commits, every of which is an archive of what the venture’s working tree appeared like at a previous date, whether or not in your machine or another person’s. A repository additionally consists of issues apart from our code recordsdata, equivalent to HEAD, branches and so on.
In between, have the index or the staging space, these two phrases are interchangeable. After we checkout a department, Git populates the index with all of the file contents that have been final checked out into our working listing and what they appeared like once they have been initially checked out. After we use git commit, the commit is created based mostly on the state of the index.
So the index or the staging space is your playground for the subsequent commit. You’ll be able to work and do no matter you need with the index, add recordsdata to it, take away issues from it, after which solely if you end up prepared prepared, you o forward and decide to the repository.
Time to get arms on 🙌🏻 Use git init to initialize a brand new repository. Write some textual content right into a file known as 1.txt.

Out of the three tree states described above, the place is 1.txt now?
Within the working tree, because it hasn’t but been launched to the index.
-> git init
Initialized empty Git repository in
/residence/omerr/repos/my_repo/.git/
-> echo "Hello world" > 1.txt
With a view to stage it, so as to add it to the index, use git add 1.txt.

Now we are able to use git commit to commit our adjustments to the repository.
-> git add 1.txt
-> git commit -m "Commit 1"
[main (root-commit) c49f4ba] Commit 1
1 file modified, 1 insertion(+)
create mode 100644 my_file.txt
You created a brand new commit object, which features a pointer to a tree describing the whole working tree. On this case, it’s gonna be solely 1.txtinside the root folder. Along with a pointer to the tree, the commit object consists of metadata, equivalent to timestamp and creator’s info. For extra details about the objects in Git (equivalent to commits and timber), try my earlier put up.
(Sure, “check out”, pun meant 😇)
Git additionally tells us the SHA-1 worth of this commit object. In my case it was c49f4ba (that are solely the primary 7 characters of the SHA-1 worth, to avoid wasting house). In case you ran this command in your machine, you’d get a distinct SHA-1 worth, as you’re a completely different creator, and in addition you’d create the commit on a distinct timestamp.
After we initialize the repo, Git creates a brand new department (named fundamental by default). And a department in Git is only a named reference to a commit. So by default, you have got solely the fundamental department. What occurs when you have a number of branches? How does Git know which department is the energetic department?
Git has one other pointer known as HEAD, which factors (often) to a department, which then factors to a commit. By the way in which, beneath the hood, HEAD is only a file. It consists of the title of the department with some prefix.
Time to introduce extra adjustments to the repo!
Now I wish to create one other one. So let’s create a brand new file, and add it to the index, as earlier than:
-> echo "second file" > 2.txt
-> git add 2.txt
Now, it’s time to make use of git commit. Importantly, git commit does two issues:
First, it creates a commit object, so there’s an object inside Git’s inside object database with a corresponding SHA-1 worth. This new commit object additionally factors to the dad or mum commit. That’s the commit that HEAD was pointing to whenever you wrote the git commit command.
-> git commit -m "Commit 2"
[main 43c8b29] Commit 2
1 file modified, 1 insertion(+)
create mode 100644 2.txt
Second, git commit strikes the pointer of the energetic department — in our case, that might be fundamental, to level to the newly created commit object.

Undoing the adjustments
To rewrite historical past, let’s begin with undoing the method of introducing a commit. For that we’ll get to know the command git reset, an excellent highly effective software.
git reset --soft
So the final step you probably did earlier than was to git commit, which really means two issues — Git created a commit object, and moved fundamental, the energetic department. To undo this step, use the command git reset --soft HEAD~1.
The syntax HEAD~1 refers back to the first dad or mum of HEAD. If I had multiple commit within the commit graph, say “Commit 3” pointing to “Commit 2”, which is, in flip, pointing to “Commit 1”. And sayHEAD was pointing to “Commit 3”. You can use HEAD~1 to consult with “Commit 2”, and HEAD~2 would consult with “Commit 1”.
So, again to the command: git reset --soft HEAD~1
This command asks Git to vary no matter HEAD is pointing to. (Be aware: within the diagrams under I take advantage of *HEAD for “whatever HEAD is pointing to”). In our instance, HEAD is pointing to fundamental. So Git will solely change the pointer of fundamental to level to HEAD~1. That’s, fundamental will level to “Commit 1”.
Nonetheless, this command did not have an effect on the state of the index or the working tree. So if you happen to use git standing you will note that 2.txt is staged, identical to earlier than you ran git commit .
-> git reset --soft HEAD~1
-> git standing
On department fundamental
Adjustments to be dedicated:
(use "git restore --staged ..." to unstage)
new file: 2.txt
-> git log
commit 935987552aa5f8d3358f89... (HEAD -> fundamental)
Writer: Omer Rosenbaum <[email protected]>
Date: ...
Commit 1 
What about git log? It is going to begin from HEAD , go to fundamental, after which to “Commit 1”. Discover that because of this “Commit 2” is now not reachable from our historical past.
Does that imply the commit object of “Commit 2” is deleted? 🤔
No, it’s not deleted. It nonetheless resides inside Git’s inside object database of objects.
In case you push the present historical past now, by utilizing git push, Git won’t push “Commit 2” to the distant server, however the commit object nonetheless exists in your native copy of the repository.
Now, commit once more — and use the commit message of “Commit 2.1” to distinguish this new object from the unique “Commit 2”:
-> git commit -m "Commit 2.1"
[main dc0cb50] Commit 2.1
1 file modified, 1 insertion(+)
create mode 100644 2.txt
Why are “Commit 2” and “Commit 2.1” completely different? Even when we used the identical commit message, and despite the fact that they level to the identical tree object (of the basis folder consisting of 1.txt and 2.txt ), they nonetheless have completely different timestamps, as they have been created at completely different instances.
Within the drawing above I stored “Commit 2” to remind you that it nonetheless exists in Git’s inside object database. Each “Commit 2” and “Commit 2.1” now level to “Commit 1″, but only “Commit 2.1” is reachable from HEAD.
git reset –blended
It’s time to go even backward and undo additional. This time, use git reset --mixed HEAD~1 (word: --mixed is the default change for git reset).
This command begins the identical as git reset --soft HEAD~1. Which means it takes the pointer of no matter HEAD is pointing to now, which is the fundamental department, and units it to HEAD~1, in our instance — “Commit 1”.
-> git commit -m "Commit 2.1"
[main dc0cb50] Commit 2.1
1 file modified, 1 insertion(+)
create mode 100644 2.txt
-> git reset --mixed HEAD~1
Subsequent, Git goes additional, successfully undoing the adjustments we made to the index. That’s, altering the index in order that it matches with the present HEAD, the brand new HEAD after setting it in step one. If we ran git reset --mixed HEAD~1 , it means HEAD could be set to HEAD~1 (“Commit 1”), after which Git would match the index to the state of “Commit 1” — on this case, it implies that 2.txt will now not be a part of the index.

It’s time to create a brand new commit with the state of the unique “Commit 2”. This time we have to stage 2.txt once more earlier than creating it:
-> git standing
on department fundamental
Untracked recordsdata:
(use `git add file<...>" to incorporate in what will likely be dedicated)
2.txt
-> git add 2.txt
-> git commit -m "Commit 2.2"
git reset –onerous
Go on, undo much more!
Go forward and run git reset --hard HEAD~1
Once more, Git begins with the --soft stage, setting no matter HEAD is pointing to (fundamental), to HEAD~1 (“Commit 1”).
-> git reset --hard HEAD~1
To this point so good.
Subsequent, shifting on to the --mixed stage, matching the index with HEAD. That’s, Git undoes the staging of 2.txt.

It’s time for the --hard step, the place Git goes even additional and matches the working dir with the stage of the index. On this case, it means eradicating 2.txt additionally from the working dir.

(**Be aware: on this particular case the file is untracked so it received’t be deleted from the file system, it isn’t actually essential to be able to perceive git reset although).
So to introduce a change to Git, you have got three steps. You alter the working dir, the index or the staging space, and then you definitely commit a brand new snapshot with these adjustments. To undo these adjustments:
- If we use
git reset --soft, we undo the commit step. - If we use
git reset --mixed, we additionally undo the staging step. - If we use
git reset --hard, we undo the adjustments to the working dir.
Actual-life situations!
State of affairs #1
So in a real-life situation, write “I love Git” right into a file ( love.txt ), as all of us love Git 😍. Go forward, stage and commit this as effectively:
-> echo "I love Git" > love.txt
-> git add love.txt
-> git commit -m "Commit 2.3"
Oh, oops!
Really, I didn’t need you to commit it.
What I really needed you to do is, is write some extra love phrases on this file earlier than committing it.
What are you able to do?
Nicely, one option to overcome this may be to make use of git reset --mixed HEAD~1, successfully undoing each the committing and the staging actions you took:
-> echo "I love Git" > love.txt
-> git add love.txt
-> git commit -m "Commit 2.3"
-> git reset --mixed HEAD~1
So fundamental factors to “Commit 1” once more, and love.txt is now not part of the index. Nonetheless, the file stays within the working dir. Now you can go forward and add extra content material to it:
-> git reset --mixed HEAD~1
-> echo And I like Temporary Channel >> love.txt
Go forward, stage and commit your file:
-> git add love.txt
-> git commit -m "Commit 2.4"
Nicely finished 👏🏻
You bought this clear, good historical past of “Commit 2.4” pointing to “Commit 1”.
We now have a brand new software in our toolbox, git reset 💪🏻

This software is tremendous, tremendous helpful, and you’ll accomplish nearly something with it. It’s not all the time essentially the most handy software to make use of, but it surely’s able to fixing nearly any rewriting-history situation if you happen to use it rigorously.
For learners, I like to recommend utilizing solely git reset for nearly any time you wish to undo in Git. As soon as you are feeling comfy with it, it’s time to maneuver on to different instruments.
State of affairs #2
Allow us to think about one other case.
Create a brand new file known as new.txt, stage and commit:
-> git add love.txt
-> git commit -m "Commit 2.4"
-> echo "A new file" > new.txt
-> git add new.txt
-> git commit -m "Commit 3"
Oops. Really that’s a mistake. You have been on fundamental and I needed you to create this commit on a function department. My unhealthy 😇
There are two most essential instruments I need you to take from this put up. The second is git reset. The primary and by much more essential one is to whiteboard the present state versus the state you wish to be in.
For this situation, the present state and the specified state appear like so:

You’ll discover three adjustments:
fundamentalfactors to “Commit 3” (the blue one) within the present state, however to “Commit 2.4” within the desired state.functiondepartment doesn’t exist within the present state, but it exists and factors to “Commit 3” within the desired state.HEADfactors tofundamentalwithin the present state, and tofunctionwithin the desired state.
In case you can draw this and you know the way to make use of git reset, you possibly can undoubtedly get your self out of this case.
So once more, crucial factor is to take a breath, and draw this out.
Observing the drawing above, how can we get from the present state to the specified one?
There are a number of alternative ways in fact, however I’ll current one possibility just for every situation. Be happy to mess around with different choices as effectively.
You can begin by utilizing git reset --soft HEAD~1. This might set fundamental to level to the earlier commit, “Commit 2.4”:

Peeking on the current-vs-desired diagram once more, you possibly can see that you simply want a brand new department, proper? You should utilize git change -c function for it, or git checkout -b function (which does the identical factor):
-> git reset --soft HEAD~1
-> git change -c function
Switched to a brand new department 'function'
This command additionally updates HEAD to level to the brand new department.
Because you used git reset --soft, you didn’t change the index, so it at present has precisely the state you wish to commit — how handy! You’ll be able to merely decide to function department:
-> git commit -m "Commit 3.1"
And you bought to the specified state 🎉
State of affairs #3
Prepared to use your information to further circumstances?
Add some adjustments to love.txt, and in addition create new file known as cool.txt. Stage them and commit:
-> echo Some adjustments >> love.txt
-> echo Git is cool > cool.txt
-> git add love.txt
-> git add cool.txt
-> git commit -m "Commit 4"
Oh, oops, really I needed you to create two separate commits, one with every change 🤦🏻
Need to do this one your self?
You’ll be able to undo the committing and staging steps:
-> git reset --mixed HEAD~1
Following this command, the index now not consists of these two adjustments, however they’re each nonetheless in your file system. So now if you happen to solely stage love.txt , you I can commit it individually, after which do the identical for cool.txt:
-> git add love.txt
-> git commit -m "Love"
-> git add cool.txt
-> git commit -m "Cool"
Good 😎
State of affairs #4
Create a brand new file (new_file.txt) with some textual content, and add some textual content to love.txt. Stage each adjustments and commit them:
-> echo A brand new file > new_file.txt
-> echo Some extra textual content >> love.txt
-> git add .
-> git commit -m "More changes"
Oops 🙈🙈
So this time I needed it to be on one other department, however not a new department, quite an already-existing department.
So what are you able to do?
I’ll provide you with a touch. The reply is basically quick and very easy. What can we do first?
No, not reset. We draw. That’s the very first thing to do, as it will make the whole lot else a lot simpler. So that is the present state:

And the specified state?

How do you get from the present state to the specified state, what could be best?
So a method could be to make use of git resetas you probably did earlier than, however there’s one other approach that I want to you to strive.
First, transfer HEAD to level to present department:
-> git change present
Inuitively, what you wish to do, is take the adjustments launched within the blue commit, and apply these adjustments (“copy-paste”) on high of present department. And Git has a software only for that.
To ask Git to take the adjustments launched between this commit and its dad or mum commit and simply apply these adjustments on the energetic department, you need to use git cherry-pick. This command takes the adjustments launched within the specified revision, and apply them to the energetic commit. It additionally creates a brand new commit object, and updates the energetic department to level to this new object.
-> git cherry-pick b8d1a0
Within the instance above I specified the SHA-1 identifier of the created commit, however you might additionally use git cherry-pick fundamental, because the commit whose adjustments we’re making use of is the one fundamental is pointing to.
However we don’t need these adjustments to exist on fundamental department. git cherry-pick solely utilized the adjustments onto the present department. How will you take away them from fundamental?
A technique could be to change again to fundamental, after which use git reset --hard HEAD~1:

You probably did it! 💪🏻
Be aware that git cherry-pick really computes the distinction between the desired commit and its dad or mum, after which applies them on the energetic commit. Which means that typically, Git received’t have the ability to apply these adjustments as chances are you’ll get a battle, however that’s a subject for one more put up. Additionally word that you could ask Git to cherry-pick the adjustments launched in any commit, not solely commits referenced by a department.
We’ve acquired a brand new software, so we’ve got git reset in addition to git cherry-pick beneath our belt.

State of affairs #5
Okay, so one other day, one other repo, one other drawback.
Create a commit:
-> echo That is extra tezt >> love.txt
-> git add love.txt
-> git commit -m "More changes"
And push it to the distant server:
-> git push origin HEADUm, oops 😓…
I simply observed one thing. There’s a typo there. I wrote That is extra tezt as a substitute of That is extra textual content. Whoops. So what’s the large drawback now? I pushed, which implies that another person might need already pulled these adjustments.
If I override these adjustments by utilizing git reset, as we’ve finished to this point, we can have completely different histories and all hell would possibly break free. You’ll be able to rewrite your individual copy of the repo as a lot as you want till you push it. When you push the change, it’s good to be very sure nobody else has fetched these adjustments if you’re going to rewrite historical past. Alternatively, you need to use one other software known as git revert. This command takes the commit you’re offering it with, compute the Diff from its dad or mum commit, identical to git cherry-pick, however this time it computes the reverse adjustments. So if within the specified commit you added a line, the reverse would delete the road, and vice versa.
-> git revert HEAD
git revert created a brand new commit object, which suggests it’s an addition to the historical past. Through the use of git revert you didn’t rewrite historical past. You admitted your previous mistake, and this commit is an acknowledgement that you simply made had a mistake and now you fastened it. Some would say it’s the extra mature approach. Some would say it’s not as clear a historical past you’d get if you happen to used git reset to rewrite the earlier commit. However this can be a option to keep away from rewriting historical past.
Now you can repair the typo and commit once more:
-> echo That is extra textual content >> love.txt
-> git add love.txt
-> git commit -m "More changes"
Your toolbox is now loaded with a brand new shiny software, revert:

State of affairs #6
Get some work finished, write some code, add it to love.txt . Stage this alteration, and commit it:
-> echo ...numerous work... >> love.txt
-> git add love.txt
-> git commit -m "Commit 3"
I did the identical on my machine, and I used the Up arrow key on my keyboard to scroll again to earlier instructions, after which I hit Enter, and… Wow.
Whoops.
git reset --hard HEAD~1
Did I simply use git reset --hard? 😨
What really occurred? Git moved the pointer to HEAD~1, so the final commit, with all of my valuable work is just not reachable from the present historical past. Git additionally unstaged all of the adjustments from the staging space, after which matched the working dir to the state of the staging space. That’s, the whole lot matches this state the place my work is… gone.
Freak out time. Freaking out.
However, actually, is there a purpose to freak out? Not realy… We’re relaxed folks. What can we do? Nicely, intuitively, is the commit actually, actually gone? No. Why not? It nonetheless exists inside the inner database of Git. If I solely knew the place that’s, I might know the SHA-1 worth that identifies this commit, we may restore it. I may even undo the undoing, and reset again to this commit.
So the one factor I really want right here is the SHA-1 of the “deleted” commit.
So the query is, how do I discover it? Would git log be helpful?
Nicely, probably not. git log would go to HEAD, which factors to fundamental, which factors to the dad or mum commit of the commit we’re in search of. Then, git log would hint again by means of the dad or mum chain, that doesn’t embrace the commit with my valuable work.

Fortunately, the very good individuals who created Git additionally created a backup plan for us, and that’s known as the reflog. When you work with Git, everytime you change HEAD, which you are able to do by utilizing git reset, but additionally different instructions like git change or git checkout, Git provides an entry to the reflog.

We discovered our commit! It’s the one beginning with 0fb929e . We are able to additionally relate to it by its “nickname” — HEAD@{1}. So equivalent to Git makes use of HEAD~1 to get to the primary dad or mum of HEAD, and HEAD~2 to consult with the second dad or mum of HEAD and so forth, Git makes use of HEAD@{1} to consult with the primary reflog dad or mum of HEAD, the place HEAD pointed to within the earlier step. We are able to additionally ask git rev-parse to indicate us its worth:
-> git reflog
-> git rev-parse HEAD
8b6da5273f...
-> git rev-parse HEAD@{1}
0fb929e8b2...
One other option to view the reflog is by utilizing git log -g, which asks git log to truly think about the reflog :

We see above that the reflog, simply as HEAD, factors to fundamental, which factors to “Commit 2”. However the dad or mum of that entry within the reflog factors to “Commit 3”.
So to get again to “Commit 3”, you possibly can simply use git reset --hard HEAD@{1} (or the SHA-1 worth of “Commit 3”):

And now if we git log:

We saved the day! 🎉👏🏻
What would occur if I used this command once more? And ran git commit --reset HEAD@{1}? Git would set HEAD to the place HEAD was pointing earlier than the final reset, which means to “Commit 2”. We are able to maintain going all day:
-> git reset --hard HEAD@{1}
HEAD is now at 0fb929e Commit 3
-> git reset --hard HEAD@{1}
HEAD is now at 8b6da42 Commit 2
-> git reset --hard HEAD@{1}
HEAD is now at 0fb929e Commit 3
Taking a look at our toolbox now, it’s loaded with instruments that may enable you resolve many circumstances the place issues go improper in Git:

With these instruments, you now higher perceive how Git works. There are extra instruments that might can help you rewrite historical past particularly, git rebase), however you’ve already realized lots on this put up. In future posts, I’ll dive into git rebase as effectively.
Crucial software, much more essential than the 5 instruments listed on this toolbox, is to whiteboard the present scenario vs the specified one. Belief me on this, it is going to make each scenario appear much less daunting and the answer extra clear.
Be taught extra about Git
I additionally gave a stay discuss masking the contents of this put up. In case you favor a video (or want to watch it alongside studying) — yow will discover it right here.
Typically, my YouTube channel covers many features of Git and its internals, you’re welcome to test it out (pun meant 😇)
Concerning the creator
Omer Rosenbaum is the creator of the Temporary YouTube Channel. He’s additionally a cyber coaching professional and founding father of Checkpoint Safety Academy. He’s the creator of Product-Led Analysis, Gitting Issues Achieved (in English) and Laptop Networks (in Hebrew).




