DEV Community

Cover image for Turn around your Git mistakes in 17 ways
Smitter
Smitter

Posted on • Edited on

Turn around your Git mistakes in 17 ways

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.

  1. Stop tracking a tracked file

    You have a node_modules folder that you do not want to be tracked by git.
    You committed 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đŸ€Ź. 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.

  2. 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.

  3. 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"
    
  4. 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.

  5. 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
    
  6. 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?

  7. 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.

  8. 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.

  9. 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.

    Also 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 .gitignore file.

  10. 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 and 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 footer-branch.

    So you want the commit adding footer-layout feature moved from the newsletter branch to its rightful footer-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 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.

  11. 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 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.

  12. 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.

  13. Undo a Git Merge

    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 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 merged feature branch while checked out on main branch. So -m 1 specifies the main branch.

  14. 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 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.

    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 become untracked 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 to git add then git 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 to git commit.

  15. 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.

    Interactive rebase session

    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:

    Reword rebase action

    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:

    Update old commit message

    You can finally save and close your editor. The commit will be updated with the new commit message.

  16. 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:

    Drop a commit

    Save and close the editor. The doomed commit is plucked out from the project history.

  17. 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 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:

    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 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 pick to edit in the line mentioning our <commit-hash> as illustrated:

    Git rebase interactive edit action

    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 when HEAD 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)

Collapse
 
fjones profile image
FJones

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:

  • creates a reflog entry
  • lines up with the rebase workflow
  • has some safeguards for timeline-incompatible changes (i.e. file existed at the time but had entirely different contents)

Similarly, I would avoid the restore command, instead using checkout and reset where appropriate.

Collapse
 
netch80 profile image
Valentin Nechayev

creates a reflog entry

Really, a reflog entry appears for each state at begin and end of each rebase, so they at least can be recovered.

has some safeguards for timeline-incompatible changes (i.e. file existed at the time but had entirely different contents)

That's why, an opposite, a file could be easily edited with the edited commit but not later.

Similarly, I would avoid the restore command, instead using checkout and reset where appropriate.

restore is a new command which just unifies multiple use cases for former checkout, branch and reset under a convenient common name. It hasn't added new functionality. To use restore or older variants is still just a matter of taste.

Collapse
 
smitterhane profile image
Smitter

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

Collapse
 
lucassperez profile image
Lucas Perez

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.

Collapse
 
netch80 profile image
Valentin Nechayev • Edited

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.

Collapse
 
smitterhane profile image
Smitter

Good notes👍. For recipe 9, I am curious why would I add the -x and then manually add -e. Looks like a lot of work

Collapse
 
netch80 profile image
Valentin Nechayev • Edited

I am curious why would I add the -x and then manually add -e.

This 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.

Collapse
 
jessekphillips profile image
Jesse Phillips

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

Collapse
 
robinbastiaan profile image
Robin Bastiaan • Edited

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.

Collapse
 
smitterhane profile image
Smitter

Changing your password is a good security precaution at personal level. I agree👍.

Collapse
 
igormihacic profile image
Igor Mihačič

A very very nice beginner friendly list. Tnx!!!

Cheers

Collapse
 
smitterhane profile image
Smitter

Welcome @igormihacic

Collapse
 
anyanka profile image
Anja

Oh that's a nice overview, thanks!

Collapse
 
smitterhane profile image
Smitter

Welcome Anja, I'll be having something for you again in this platform

Collapse
 
eje_sunay profile image
Eje Sunay

Nice article

Collapse
 
colin-williams-dev profile image
colin-williams-dev

This is so helpful for a beginner, thank you!

Collapse
 
smitterhane profile image
Smitter

Thanks! I'm grateful you found this useful

Collapse
 
efecollins profile image
Efosa Collins EVBOWE

This is the first dev post I'm bookmarking. It's so resourceful.

Collapse
 
smitterhane profile image
Smitter

Thanks

Collapse
 
binayakjha profile image
Binayak Jha

Thanks for sharing !

Collapse
 
smitterhane profile image
Smitter

Welcome again on the next one

Some comments have been hidden by the post's author - find out more