collaborated on an ML challenge — 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 mentioned: “uh-oh, what did I simply do?”
For any knowledge scientist who works in a crew, having the ability to undo Git actions is usually a life saver.
This put up gives you the instruments to rewrite historical past with confidence.
Notes earlier than we begin
- I additionally gave a dwell speak masking the contents of this put up. If you happen to favor a video (or want to watch it alongside studying) — you’ll find it here.
- 📕 My ebook “Gitting Issues Carried out” is formally out!
Get it on Amazon, as an eBook, or learn it without cost at https://www.freecodecamp.org/news/gitting-things-done-book/
Recording adjustments in Git
Earlier than understanding the way it undo issues in Git, it’s best to first perceive how we report adjustments in Git. If you happen to already know all of the phrases, be at liberty to skip this half.
It is extremely helpful to consider Git as a system for recording snapshots of a filesystem in time. Contemplating a Git repository, it has three “states” or “bushes”:
Normally, 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 incorporates the folders and recordsdata of our challenge, and likewise a listing referred to as .git. I described the contents of the .git folder in additional element in a previous post.
After you make some adjustments, you might wish to report them in your repository. A repository (in brief: repo) is a group of commits, every of which is an archive of what the challenge’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 aside from our code recordsdata, comparable to HEAD, branches and many others.
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 after they have been initially checked out. After we use git commit, the commit is created primarily based on the state of the index.
So the index or the staging space is your playground for the subsequent commit. You possibly can 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 fingers on 🙌🏻 Use git init to initialize a brand new repository. Write some textual content right into a file referred to 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 "Whats up world" > 1.txt

As a way 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 complete 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, comparable to timestamp and creator’s data. For extra details about the objects in Git (comparable to commits and bushes), check out my previous post.
(Sure, “try”, pun supposed 😇)
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). If you happen to ran this command in your machine, you’d get a unique SHA-1 worth, as you’re a totally different creator, and likewise you’d create the commit on a unique timestamp.
After we initialize the repo, Git creates a brand new department (named essential by default). And a branch in Git is just a named reference to a commit. So by default, you’ve gotten solely the essential 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 referred to as HEAD, which factors (often) to a department, which then factors to a commit. By the best way, under the hood, HEAD is just 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 inner object database with a corresponding SHA-1 worth. This new commit object additionally factors to the mother or father commit. That’s the commit that HEAD was pointing to if 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 may be essential, 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, a brilliant highly effective device.
git reset --soft
So the final step you probably did earlier than was to git commit, which truly means two issues — Git created a commit object, and moved essential, the energetic department. To undo this step, use the command git reset --soft HEAD~1.
The syntax HEAD~1 refers back to the first mother or father of HEAD. If I had a couple of 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 would use HEAD~1 to confer with “Commit 2”, and HEAD~2 would confer with “Commit 1”.
So, again to the command: git reset --soft HEAD~1
This command asks Git to alter no matter HEAD is pointing to. (Notice: within the diagrams under I exploit *HEAD for “no matter HEAD is pointing to”). In our instance, HEAD is pointing to essential. So Git will solely change the pointer of essential to level to HEAD~1. That’s, essential will level to “Commit 1”.
Nevertheless, this command did not have an effect on the state of the index or the working tree. So for those who use git standing you will notice that 2.txt is staged, similar to earlier than you ran git commit .
-> git reset --soft HEAD~1
-> git standing
On department essential
Modifications to be dedicated:
(use "git restore --staged ..." to unstage)
new file: 2.txt
-> git log
commit 935987552aa5f8d3358f89... (HEAD -> essential)
Writer: Omer Rosenbaum <[email protected]>
Date: ...
Commit 1

What about git log? It’ll begin from HEAD , go to essential, 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 inner object database of objects.
If you happen to 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” totally different? Even when we used the identical commit message, and regardless that they level to the same tree object (of the basis folder consisting of 1.txt and 2.txt ), they nonetheless have totally different timestamps, as they have been created at totally different occasions.
Within the drawing above I saved “Commit 2” to remind you that it nonetheless exists in Git’s inner object database. Each “Commit 2” and “Commit 2.1” now level to “Commit 1″, however solely “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. That means it takes the pointer of no matter HEAD is pointing to now, which is the essential 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 essential
Untracked recordsdata:
(use `git add file<...>" to incorporate in what can 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 (essential), to HEAD~1 (“Commit 1”).
-> git reset --hard HEAD~1

Up to now 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.

(**Notice: on this particular case the file is untracked so it received’t be deleted from the file system, it isn’t actually essential with the intention to perceive git reset although).
So to introduce a change to Git, you’ve gotten 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 really like Git” right into a file ( love.txt ), as all of us love Git 😍. Go forward, stage and commit this as effectively:
-> echo "I really like Git" > love.txt
-> git add love.txt
-> git commit -m "Commit 2.3"

Oh, oops!
Truly, I didn’t need you to commit it.
What I truly 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 strategy to overcome this is able to be to make use of git reset --mixed HEAD~1, successfully undoing each the committing and the staging actions you took:
-> echo "I really like Git" > love.txt
-> git add love.txt
-> git commit -m "Commit 2.3"
-> git reset --mixed HEAD~1

So essential factors to “Commit 1” once more, and love.txt is now not part of the index. Nevertheless, 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 really like Temporary Channel >> love.txt

Go forward, stage and commit your file:
-> git add love.txt
-> git commit -m "Commit 2.4"

Nicely performed 👏🏻
You bought this clear, good historical past of “Commit 2.4” pointing to “Commit 1”.
We now have a brand new device in our toolbox, git reset 💪🏻

This device is tremendous, tremendous helpful, and you may accomplish nearly something with it. It’s not all the time essentially the most handy device to make use of, nevertheless it’s able to fixing nearly any rewriting-history situation for those who use it rigorously.
For inexperienced persons, 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 contemplate one other case.
Create a brand new file referred to as new.txt, stage and commit:
-> git add love.txt
-> git commit -m "Commit 2.4"
-> echo "A brand new file" > new.txt
-> git add new.txt
-> git commit -m "Commit 3"

Oops. Truly that’s a mistake. You have been on essential and I needed you to create this commit on a function department. My dangerous 😇
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 seem like so:

You’ll discover three adjustments:
essentialfactors 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 toessentialwithin the present state, and tofunctionwithin the desired state.
If you happen to can draw this and you know the way to make use of git reset, you possibly can undoubtedly get your self out of this example.
So once more, a very powerful 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 other ways in fact, however I’ll current one choice just for every situation. Be at liberty to mess around with different choices as effectively.
You can begin by utilizing git reset --soft HEAD~1. This might set essential 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 need to use 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 the moment has precisely the state you wish to commit — how handy! You possibly can 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 data to extra instances?
Add some adjustments to love.txt, and likewise create new file referred to 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, truly I needed you to create two separate commits, one with every change 🤦🏻
Wish to do this one your self?
You possibly can 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 for those who 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 "Extra adjustments"

Oops 🙈🙈
So this time I needed it to be on one other department, however not a new department, fairly an already-existing department.
So what are you able to do?
I’ll offer you a touch. The reply is basically brief 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 all the things 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 technique could be to make use of git resetas you probably did earlier than, however there’s one other means that I want to you to attempt.
First, transfer HEAD to level to current department:
-> git change current

Inuitively, what you wish to do, is take the adjustments launched within the blue commit, and apply these adjustments (“copy-paste”) on prime of current department. And Git has a device only for that.
To ask Git to take the adjustments launched between this commit and its mother or father commit and simply apply these adjustments on the energetic department, you should utilize 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 possibly can additionally use git cherry-pick essential, because the commit whose adjustments we’re making use of is the one essential is pointing to.
However we don’t need these adjustments to exist on essential department. git cherry-pick solely utilized the adjustments onto the current department. How will you take away them from essential?
A technique could be to change again to essential, after which use git reset --hard HEAD~1:

You probably did it! 💪🏻
Notice that git cherry-pick truly computes the distinction between the desired commit and its mother or father, after which applies them on the energetic commit. Because of this generally, Git received’t be capable to apply these adjustments as you might get a battle, however that’s a subject for an additional put up. Additionally word you could ask Git to cherry-pick the adjustments launched in any commit, not solely commits referenced by a department.
We have now acquired a brand new device, so we now have git reset in addition to git cherry-pick below our belt.

State of affairs #5
Okay, so one other day, one other repo, one other downside.
Create a commit:
-> echo That is extra tezt >> love.txt
-> git add love.txt
-> git commit -m "Extra adjustments"

And push it to the distant server:
-> git push origin HEAD
Um, oops 😓…
I simply seen one thing. There’s a typo there. I wrote That is extra tezt as an alternative of That is extra textual content. Whoops. So what’s the massive downside 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 performed to date, we can have totally different histories and all hell would possibly break free. You possibly can rewrite your individual copy of the repo as a lot as you want till you push it. When you push the change, you want to be very sure nobody else has fetched these adjustments if you will rewrite historical past. Alternatively, you should utilize one other device referred to as git revert. This command takes the commit you’re offering it with, compute the Diff from its mother or father commit, similar 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 mounted it. Some would say it’s the extra mature means. Some would say it’s not as clear a historical past you’d get for those who used git reset to rewrite the earlier commit. However it is a strategy 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 "Extra adjustments"

Your toolbox is now loaded with a brand new shiny device, revert:

State of affairs #6
Get some work performed, write some code, add it to love.txt . Stage this transformation, and commit it:
-> echo ...plenty of 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 truly occurred? Git moved the pointer to HEAD~1, so the final commit, with all of my treasured 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, all the things 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 interior database of Git. If I solely knew the place that’s, I’d 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 actually need 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, not likely. git log would go to HEAD, which factors to essential, which factors to the mother or father commit of the commit we’re searching for. Then, git log would hint again via the mother or father chain, that doesn’t embrace the commit with my treasured work.

Fortunately, the very good individuals who created Git additionally created a backup plan for us, and that’s referred to 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 will additionally relate to it by its “nickname” — HEAD@{1}. So comparable to Git makes use of HEAD~1 to get to the primary mother or father of HEAD, and HEAD~2 to confer with the second mother or father of HEAD and so forth, Git makes use of HEAD@{1} to confer with the primary reflog mother or father of HEAD, the place HEAD pointed to within the earlier step. We will 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 strategy to view the reflog is by utilizing git log -g, which asks git log to really contemplate the reflog :

We see above that the reflog, simply as HEAD, factors to essential, which factors to “Commit 2”. However the mother or father 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 will preserve 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 to resolve many instances the place issues go flawed in Git:

With these instruments, you now higher perceive how Git works. There are extra instruments that may assist you to rewrite historical past particularly, git rebase), however you’ve already discovered loads on this put up. In future posts, I’ll dive into git rebase as effectively.
A very powerful device, much more essential than the 5 instruments listed on this toolbox, is to whiteboard the present state of affairs vs the specified one. Belief me on this, it should make each state of affairs appear much less daunting and the answer extra clear.
Study extra about Git
I additionally gave a dwell speak masking the contents of this put up. If you happen to favor a video (or want to watch it alongside studying) — you’ll find it here.
Usually, my YouTube channel covers many facets of Git and its internals, you might be welcome to check it out (pun supposed 😇)
In regards to the creator
Omer Rosenbaum is the creator of the Temporary YouTube Channel. He’s additionally a cyber coaching knowledgeable and founding father of Checkpoint Safety Academy. He’s the creator of Product-Led Research, Gitting Things Done (in English) and Computer Networks (in Hebrew).


