Mistakes! These are not a necessity in software development. But they always find a way to taint the beauty of software development.
Let's make peace that mistakes are inevitable. But how do you bounce back 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.
-
Stop tracking a tracked file
You have a
node_modules
folder that you do not want to be tracked by git.
You committednode_modules
folder and now you realize that you forgot to ignore it using a.gitignore
file. You add the.gitignore
file and list thenode_modules
folder in it then commit.
But still Git is tracking itđ€Ź. This is because, once a file has been added and committed to Git database, Git will continue tracking changes made to it.So you want to stop Git from tracking the
node_modules
folder.
Just run:
git rm -r --cached node_modules
--cached
option prevents removal of the file from the disk as well. -
Modify the Last Commit
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
Then, run:
git commit --amend --no-edit
The
--no-edit
flag amends the commit without changing the commit message. -
Modify the Last 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.
Simply run:
git commit --amend -m "Phew! A better commit message this time"
-
Discarding all Changes in your working directory
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.
-
Discard all changes made to a file
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
-
Restore a file to an old version back in time
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.
With Git:
Find and copy the commit hash back in time you want:
git log
Then:
git restore --source <commit-hash> <filename>
That simple, right?
-
Recover a deleted file(previously committed)
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.
-
Discard all changes in your local repo to exact state in remote
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.
-
Delete Untracked Files
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
The
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.
Alsogit 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
.gitignore
file. -
Switch a commit to a different branch
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:
newsletter
andfooter-layout
, dedicating each branch to the associated feature: newsletter and foooter-layout respectively.
You are currently adding newsletter feature and do some commits from thenewsletter
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 associatedfooter-layout
branch. You add the footer-layout feature from the wrongnewsletter
branch and commit to it.
Later, it comes to your attention that the commit you added to thenewsletter
branch actually belongs to thefooter-branch
.So you want the commit adding footer-layout feature moved from the
newsletter
branch to its rightfulfooter-layout
branch.
This is what to do in Git:Checkout the
newsletter
branch:
git checkout newsletter
Find and copy the commit hash of the commit that added the footer-layout feature. Run the command:
git log
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
newsletter
branch:
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 ournewsletter
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 thisnewsletter
branch was the one with footer-layout feature. -
Resurrect a deleted branch
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:
git reflog
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 asgit 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.
-
Rewind/undo an erroneous commit
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):
git log
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. -
Undo a Git Merge
You are happy with new changes in
feature
branch. You feel ready to mergefeature
branch to yourmain
branch. So from yourmain
branch, you mergefeature
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
main
branch:
git checkout main
Then run
git log
to get the commit hash of the merge commit.
git log
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 mergedfeature
branch while checked out onmain
branch. So-m 1
specifies themain
branch. -
Rollback to an older version
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.
In git:Find and copy the commit hash you desire your project to rewind to:
git log
Reset your repository to that state specified by commit hash:
git reset --hard <commit-hash>
git reset
moves theHEAD
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.The
--hard
flag will reset the staging area and working tree.Using the
--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 becomeuntracked
files.
The--mixed
flag has a close cousin--soft
, with the difference being the way they handle the index(staging area).The
--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 togit add
thengit commit
.
The--soft
flag will neither reset the staging area nor the working tree. So to add untracked files in the next commit, you only have togit commit
. -
Modify an old commit message
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:
git log
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 wordpick
in the first column with the wordreword
(or justr
) 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.
-
Delete an old commit
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 log
Then:
git rebase -i <commit-hash>
Specify a
drop
action verb for deleting the commit:Save and close the editor. The doomed commit is plucked out from the project history.
-
Edit an old commit to add new change(s)
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 afile.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:
git log
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 rungit 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
pick
toedit
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.
Conclusion
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 log
command, which shows a list of commits.git reflog
is similar, but instead shows a list of times whenHEAD
changed.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 this is your grace period to stand another chance in your mistakes.
Thank you for readingâš.
Find me on twitter if nuggets like this is what you need.
Top comments (24)
For 17 I would instead almost always recommend making a new commit on HEAD and then using rebase with the fixup verb to reorder and join the commits. This is for the simple reasons that it:
Similarly, I would avoid the restore command, instead using checkout and reset where appropriate.
Really, a reflog entry appears for each state at begin and end of each rebase, so they at least can be recovered.
That's why, an opposite, a file could be easily edited with the edited commit but not later.
restore
is a new command which just unifies multiple use cases for formercheckout
,branch
andreset
under a convenient common name. It hasn't added new functionality. To userestore
or older variants is still just a matter of taste.Well I think it is a bit of work having to reorder the commits so as to use the fixup action verb.
Then regarding the restore command, it was built for the restoring purpose in mind unlike the checkout command which is like a know it all command
Just as a side note, if you need to reorder and then fixup, you can instead just squash and then rewrite the commit message appropriately. Squash itself puts you in a commit "screen" anyways, so you can easily use the commit message you want.
A good intro, just a few notes:
For commands like
restore
, it is useful to separate paths from options with '--', e.g.git restore -s <commit_id> -- <path1> <path2>
. Shell completion relies on it.For recipe 3, if not to add
-m
, this opens editor. In a case of multiline commit message (typical for a complex project) this is much handier.Recipe 9: it's useful to note exceptions to cleaning, like:
git clean -dfx -e .vscode
.For recipe 11:
git log
works with commit ids as well, so,git log -p <id>
may suggest what was the branch to restore. Anyway, well, good messages are useful... but I'm regularly encountering the same commit sequence over different branches.Good notesđ. For recipe 9, I am curious why would I add the
-x
and then manually add-e
. Looks like a lot of workThis is the case build artifacts (all of them - object files, libraries, autogenerated sources, etc.) are present in the same directory as the working copy. Normally, they are all in .gitignore. Heavy cleaning from build results require deleting all of them. But, if IDE and other locals aren't a level upper, they are to be saved from deletion.
I have some added suggestions to complement what you have.
Use git switch instead of git checkout.
Rather than just dropping changes with a reset, switch into a new branch, commit, then switch out. This ensures if nothing else you'll have a reflog for a bit.
$ git commit --fixup
Or search a commit message instead of hash
$ git commit --fixup :/msg
Good overview of the different options at your disposal! Thank you very much for the article.
I just want to add that not all methods will remove your data from the repository. In normal circumstances that is exactly what you want. However, if you accidentally committed a password for example, this is something to look out for. In that situation it can make sence to revert your commit by rewriting history, but this will only make sence if you have not yet commited your code to the remote repo. If that is the case, you are better off considering your password compromised and start changing your password.
Changing your password is a good security precaution at personal level. I agreeđ.
A very very nice beginner friendly list. Tnx!!!
Cheers
Welcome @igormihacic
Oh that's a nice overview, thanks!
Welcome Anja, I'll be having something for you again in this platform
Nice article
This is so helpful for a beginner, thank you!
Thanks! I'm grateful you found this useful
This is the first dev post I'm bookmarking. It's so resourceful.
Thanks
Thanks for sharing !
Welcome again on the next one
Some comments have been hidden by the post's author - find out more