Mistakes! These are not a necessity in software development. But they always find a way to taint the beauty of software development.
So, mistakes are inevitable. How do you recover from your mistakes?
A useful feature of a Version Control System is the ability to undo.
Git is an excellent safety net to give you another chance in your wrong move.
Summary: In this article, I have outlined the mistake scenarios you can get tangled with and the Git techniques that will unwound you from your mistakes.
You have a
node_modules folder that you do not want to be tracked by git.
node_modules folder and now you realize that you forgot to ignore it using a
.gitignore file. You add the
.gitignore file and list the
node_modules folder in it then commit.
But still Git is tracking it(
node_modules). This is because, once a file has been added and committed to Git database, Git will continue tracking changes to it.
So you want to stop Git from tracking the
git rm -r --cached node_modules
You have committed to your local Git Database. Thereafter you realize that you have forgotten to include file changes to that commit(you may have not saved the file). Or you add a new file that you want it tied to the same commit you have committed most recently.
Git can unlock the previous commit and allow you to add your new file.
Just do this:
First, you add the missing file using:
git add newfile.ts
git commit --amend --no-edit
--no-edit flag amends the commit without changing the commit message.
This scenario is similar to the one above. But this time, you have commited to git and you are not very happy with the commit message you specified. You want the message to have more meaning describing what you have done.
Again Git is forgiving to let you fix your previous crappy commit message.
git commit --amend -m "Phew! A better commit message this time"
You are working on your project. For example, you may be refactoring your code to follow better patterns and clean code principles. While refactoring, you break a functionality in the application and cannot get your head around what really went wrong. You now want to go back to the previous state(commit) where the application was working fine so that you can find your bearing again.
So you want to discard all the uncommitted changes you have made and restore your Git working tree back to the state it was in when the last commit took place.
Use the command:
git restore .
Note: This command restores your working copy to the most recent commit in your local git database. Changes that you have not committed will be lost permanently.
You have modified your files in your local git repo. You later realize that changes you made to a particular file(
connect.js) are not so intelligent and should be discarded. But you want to keep changes made to other files.
So you want the file
connect.js to go back to the state it was in the previous commit.
This is possible with Git:
git restore connect.js
Unlike the above command, In this case you want a particular file in your project to go back to a state that is deeper back in time than the previous commit.
So for example if version 3 was your previous commit, you want this particular file to appear as it was in version 2 which is earlier than version 3.
Find and copy the commit hash back in time you want:
git restore --source <commit-hash> <filename>
That simple, right?
You are making new changes to your project and you see an obsolete file. You go ahead and delete the file so you free up space. Sometime later you realize that you need the file you just deleted.
To recover your file:
git restore deleted-file.css
This will present the file to you as it was in the previous commit.
You have made a couple of changes and commits in your local repo branch. You have rewired and now you are asking yourself, "Why did I even make these changes in the first place?"
So you want to forget everything on your current local branch and make it exactly the same as what is in the remote's main branch.
Just undo like:
git reset --hard origin/main
WARNING: Uncommitted changes will be lost permanently.
You have created several new files in your project and also modified some files. You have changed your approach and you now decide that you no longer need any of these new files you have created but stick with the modified file changes.
Git provides you a command to remove untracked files:
git clean -f
git clean command requires a
-f flag implying the force directive(also
--force). Since this operation entails deleting untracked files, it has to require that you are sure about what you are doing because getting rid of uncommitted work in Git is undoable.
By default this command will not remove untracked folders or files specified by
.gitignore. Specify the
-x option to cause the removal of ignored files.
git clean will not operate recursively on directories. This is another safety mechanism to prevent accidental permanent deletion.
To also remove directiories, use
-d flag like:
git clean -df
This will clean untracked files and directories, but will not remove the ones listed in
The project has evolved and you are currently working on two features: adding newsletter and fine-tuning footer-layout. You have created two separate branches:
footer-layout, dedicating each branch to the associated feature: newsletter and foooter-layout respectively.
You are currently adding newsletter feature and do some commits from the
newsletter branch. The next time you come back to your project, you want to work on the footer-layout feature and you forget to switch to the associated
footer-layout branch. You add the footer-layout feature from the wrong
newsletter branch and commit to it.
Later, it comes to your attention that the commit you added to the
newsletter branch actually belongs to the
So you want the commit adding footer-layout feature moved from the
newsletter branch to its rightful
This is what to do in Git:
git checkout newsletter
Find and copy the commit hash of the commit that added the footer-layout feature. Run the command:
Checkout the rightful branch you want to apply with this commit. In our case:
git checkout footer-layout
Then apply the commit:
git cherry-pick <commit-hash>
Then checkout the
git checkout newsletter
And strip it off this unwanted commit, which is the commit that adds the footer-layout:
git reset HEAD~1 --hard
HEAD~1 means that we are resetting our
newsletter branch to move one commit behind the most recent commit and rid the commit we have moved away from. Remember in our case, the latest commit on this
newsletter branch was the one with footer-layout feature.
You think you do not need a feature branch anymore and you swing the trash can on it(delete it). Next time, you are alarmed deleting it was a mistake. Now you are in "panic mode". You want to impossibly go back in time and get it back.
So you want to get back your deleted branch.
This is how you can recover your deleted branch:
Find and copy the commit hash of your deleted your branch which will be listed when you run the command:
Then recreate your branch as:
git branch <branch-name> <deleted-branch-commit-hash>
This will recreate a branch that will contain all the work upto the <deleted-branch-commit-hash>. If <deleted-branch-commit-hash> is not the latest work in the deleted branch, you can still find new commit hash until you get the one with your latest work. Type
git branch and you will see your previously deleted branch listed again. This works even if the branch was deleted in origin(remote).
Also if the <branch-name> is not the name of what you want, simply rename it as
git branch -m <branch-name> <new-branch-name>.
Needless to say, the key to successfully bring back your deleted branch is to find the right commit hash, so name your commits intelligently; it helps alot.
Your team has rolled out a new version of a project. You are all certain all the new features added are an absolute master-class. Just moments later, an urgent call from the team lead dictates that the new version rolled out has a critical flaw and needs to be fixed right away. The engineering team needs to act urgently and sitting down to find the flaw is not a timely response to an urgent matter.
One of the quickest response is to undo the changes to the error prone release.
So you want to undo the changes done to a commit.
This is how to go about it in Git:
Find and copy the commit hash of the commit you want to undo changes(the broken commit):
Revert changes specified by that commit:
git revert <broken-commit-hash>
git revert creates a new commit with the reverted(inverse) changes. All the changes specified in the broken commit will be rolled back(undone) in the new commit. For instance, anything removed in broken commit will be added in the new commit and anything added in the broken commit will be removed in the new commit. A default editor will pop up with default commit message for the "revert commit" about to be created. You can modify is you so wish.
You are happy with new changes in
feature branch. You feel ready to merge
feature branch to your
main branch. So from your
main branch, you merge
feature branch and push your new changes to remote repository.
Moments later, you realize that merge was really not a pretty move at all.
So you want to undo the merge. Git has you covered.
Start by checking out the
git checkout main
git log to get the commit hash of the merge commit.
Then revert like:
git revert -m 1 <merge-commit-hash>
Branches in a merge are the parents of that merge.
Usually you cannot revert a merge because git does not know which side(parent) of the merge should be considered the mainline.
git revert -m 1 specifies the parent number of the mainline and allows revert to reverse the change relative to the specified parent.
Typically, the parent that gets recorded first during a
git merge is the branch you were on when you merged. Which in our error case here, we merged
feature branch while checked out on
main branch. So
-m 1 specifies the
You can rollback your project to a version back in time. This can also be a response to an erroneous commit: whereby you want to go back to a version that was working correctly before the recent broken commit.
WARNING: The git command shown here rewrites history. This is not suitable when collaborating on a software project with others.
So you want to get to a previous version of your project by rewinding your repository's history.
Find and copy the commit hash you desire your project to rewind to:
Reset your repository to that state specified by commit hash:
git reset --hard <commit-hash>
git reset moves the
HEAD pointer(and thereby your working tree) to an older version(<commit-hash>).
Commits that come after this version are removed from the project's history.
--hard flag will reset the staging area and working tree.
--mixed flag like:
git reset --mixed <commit-hash>, will reset your repo back to specified commit hash preserving files in your working tree. Files that were in later commits but not in the commit we've reset to, will become
--mixed flag has a close cousin
--soft, with the difference being the way they handle the index(staging area).
--mixed flag will reset the staging area(index) but not the working tree. So to add untracked files in the next commit, you will have to
git add then
--soft flag will neither reset the staging area nor the working tree. So to add untracked files in the next commit, you only have to
You are glad with the progress of your project. You are going through your project history. You notice a commit message that is blatantly vague.
So you want to change this message to a better message depicting logical semantics(just a message with better explanation).
In Git, you can edit a commit message deeper in history with interactive rebase:
Find and copy the commit hash of the commit you want to change its commit message:
Open a rebase interactive session:
git rebase -i <commit-hash>
An editor will pop up.
From sample interactive rebase session above, you see that there is a typo on line 1:
pick 21c21b6 udating. Let's fix this typo and provide a better descriptive message.
In the editor only specify which actions you want to perform. It can be tempting to rewrite the commit message right now, but that won’t work;
rebase -i ignores everything after the SHA(commit hash) column. The text after that is really just to help you remember what the commit hash is all about.
Since you want to preserve the contents of the commit but edit the commit message, specify
reword action command against the commit you want to change its message; simply just replace the word
pick in the first column with the word
reword (or just
r) as shown below:
Then save and close the editor.
Finally git opens an editor for you to change the commit message. Change the commit message to a better one:
You can finally save and close your editor. The commit will be updated with the new commit message.
An old commit may be bothering you and you want it plucked out from your project history.
So you want this commit to no longer appear in your project history.
You can delete this commit like:
First, find and copy the commit hash of the doomed commit.
git rebase -i <commit-hash>
drop action verb for deleting the commit:
Save and close the editor. The doomed commit is plucked out from the project history.
A situation may occur where you want to add a change to your project. But this change is relevant to a commit back in time when you were handling a specific change better described by the commit message for that commit.
For example, your project has evolved by several commits and lets presumably brand the most recent commit as version 5 of your project. You have created a
file.html. You want this file to be part of a previous commit deeper in history, probably at version 2 of the project.
In Git, you can meld new change(s) into a commit back in time, hence editing an old commit.
These are the steps:
Find and copy the commit hash of the commit you want to mark for editing:
Start a rebase interactive session:
git rebase -i "<commit-hash>^"
NOTE how we are appending a caret(
^) at the end of the <commit-hash>. This way we are telling Git we would like to go back to the original chronology of commits after we are done editing this marked commit. Because when you run
git rebase -i "<commit-hash>^",
git log will show the marked <commit-hash> as the most recent commit.
Running this command, a default editor will pop up. Modify the action verb
edit in the line mentioning our <commit-hash> as illustrated:
Save and close the editor.
Your working tree and project history is now at the state when you made this commit earlier. Run
git log and you will see that <commit-hash> is now your most recent commit. You still have the file you created (
file.html) in your working tree and this is a good chance to ammend it to the most recent commit. Remember at this point, the most recent commit is the commit you want to edit.
So go ahead and ammend like:
git add file.html git commit --amend --no-edit
Now to take the project history back to normal order:
git rebase --continue
This way, you have edited commit specified by <commit-hash> by adding
file.html to it.
WARNING: This will rewrite the history from that point forward. It is not a good idea when working a project in a team setting.
That is how you can turn your mistakes around.
Just a few notes:
When working on a project in a team setting, it is advisable to avoid commands that rewrite history. However, you can rewrite history in your own dedicated branch which only lives in your local repo. The rule is that do not rewrite history of a branch in which others have based there work on. Things could get complicated.
You’re probably familiar with the
git logcommand, which shows a list of commits.
git reflogis similar, but instead shows a list of times when
Your reflog is personal to you. Reflog keeps track of any changes or commits to each branch since cloning a repository.
Git doesn't delete your commits for 90 days by default; you can find them in the reflog. Hence stand another chance in your mistakes.
Thank you for reading✨.
If you found the article useful, you can follow me on twitter.