DEV Community

Jaakko Kangasharju
Jaakko Kangasharju

Posted on

Fixing Mistakes with git

Sometimes, we all write code that shouldn't become part of the code base. Or at least shouldn't become part of it yet. Git has a number of ways to remove such code, and understanding when to use each makes you a more effective git user. So let's take a look at what you should do depending on what all accidentally happened with the code.

I use git only on the command line, so I show everything in terms of that. Git GUIs may show some of this information in a different way, but I believe the terms are usually not changed, so the commands should be findable.

I didn't commit yet

If you didn't commit the code,

git status

is a helpful command. It shows a list of all changes that haven't been committed yet, and also helpfully tells you what to do to ignore some of the uncommitted changes. The output has these sections:

On branch feature-user-account
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
Enter fullscreen mode Exit fullscreen mode

If the changes you want to ignore are in the first part, the suggested command

git reset HEAD

will bring them to the second group. And for changes in the second group, the suggested command

git checkout --

(the -- is important) will remove all changes from the given files. So make sure all the changes in those files are something you want to get rid of before running it.

I committed but didn't push

If you didn't yet push your changes, they currently live only on your local computer. So you're free to change things any way you like. Git has the command git reset, a form of which we saw above, to reset your current branch to something else. If you just want to get back to the state you were in before the commit, run

git reset HEAD^

which sets your current branch to the state before the most recent commit (that's what the ^ after HEAD means; you can put multiple ^'s to go back more than one commit), and keeps your changes in the uncommitted state.

If you really want to get rid of the latest commit, git reset has a way to do that. If you run

git reset --hard HEAD^

the latest commit will be erased as if it never happened. Any code you had in the commit will be deleted, so only do this if you know the whole commit was a mistake. (You can put anything in place of HEAD^ to reset the branch there; I sometimes use it to reset my local branch to whatever that branch is on the remote server because I want to get rid of anything I did locally on that branch.)

I committed and pushed to my own branch

In software development flows based on feature branches, it is common for a developer to have their "own" branch to work on the feature they're implementing. If no one else is working on a branch, you can treat the remote version of that branch pretty much the same as your local branch. So just do the git reset as above, followed by

git push --force

(called a force push) to make the remote match your local reseted version of the branch.

It is possible for a repository administrator to configure a remote repository to reject force pushes. If this is the case for you, follow the advice under the next heading.

I committed and pushed to a shared branch

The issue with doing a reset and force push on a branch on which multiple people are working is that some of those other people may have pulled the commits that shouldn't have happened and have already built some of their work on top of that. Reacting to a force push in such a situation can get hairy.

Luckily, git also has a way to revert changes without having to change the history. To get rid of the changes in the latest commit, run

git revert HEAD

This will create a new commit that is the exact opposite of the HEAD commit, thus undoing all the changes made there.

As you might expect, you can give any commit identifier to git revert in place of HEAD, and it does what you would expect, namely creates a commit that undoes only the commit you named. This is something you cannot do with a git reset, so it's a useful tool to remember even if your workflow wouldn't normally include using git revert.

I committed and pushed a merge that shouldn't have happened

Say you're working with the feature branch model, so any feature you're working on needs to be merged from your branch to the main development branch. It might happen that you or someone accidentally merges that branch before it was ready to be merged, so you want to undo the merge.

Again, git revert can be used to undo a merge commit. But there's a catch: A merge commit has more than one parent (usually two). Which changes should git revert? The command to use to revert a merge commit is

git revert -m 1 <commit>

where the -m 1 option says to select parent number 1 as the parent to revert to.

Note that the parent number is not a commit identifier. Rather, a merge commit has a line

Merge: 8e2ce2d 86ac2e7
Enter fullscreen mode Exit fullscreen mode

when you look at it with git log or git show. The parent number is the 1-based index of the desired parent on this line, that is, the first identifier is number 1, the second is number 2, and so on. Number 1 is the branch onto which the merge was made, so in a feature branch workflow, undoing a merge means giving option -m 1 to git revert.

I committed and pushed something that shouldn't exist in the repository

Sometimes you keep stuff in your working tree that should not be committed. Do set up an appropriate .gitignore file for your project to avoid accidentally committing such stuff, but it may happen that something new appears that's not caught by your .gitignore and you accidentally end up committing and pushing it. This might be credentials that should be a secret, it might be massive binary files that just make using the repository uncomfortable, or it might be something else.

The way to go here is to follow the advise when you've committed and pushed to your own branch, that is, git reset followed by git push --force. But if you did this on a shared branch, remember to communicate to your team that this happened, so that they can adjust to a force push on a shared branch.

Finally, if you accidentally pushed private keys, credentials, passwords, or the like into a public repository, treat them as compromised. Immediately revoke any credentials, change any passwords, etc.

I want my code back

If the changes you made were just premature and you do want to make them at a later point, here are the ways to recover them based on how you got rid of them:

  • git checkout --: If you did this, your changes are gone. Git never saw them so it cannot help you recover them.
  • git reset HEAD, git reset: The changes remained in your working tree, just in a different state. If you remembered to save them somewhere, fetch them from there.
  • git reset --hard: The changes are gone, but git saw them and might remember them. Look into git reflog (which is too big a topic to get into here, plus I couldn't give good advice on it anyway)
  • git revert: The way to go is another git revert, this time giving the identifier of the commit that was created for the original revert. This gives you fun commit messages "Revert of "Revert of ..."", but it's the cleanest way to go. And if you're undoing a premature merge of a feature branch, remember to do this before merging the correct version of the feature branch. Otherwise, git will get confused as to which changes from the feature branch already exist on the main development branch.

Discussion (2)

broonix profile image

Another tip: Avoid git push --force and use git push --force-with-lease.

jsn1nj4 profile image

Be careful when using a caret ^ instead of a tilde ~ next to commit pointers.