If you have ever committed something by mistake in git, you can undo it by using git reset.
For example, let's say you have committed something by mistake to the master branch. You can undo the commit with the following steps:
1: first, run git branch old-master to to create a branch called old-master that points to the current master branch's HEAD. Although this isn't necessary to undo, it's a good idea to do this just in case you make a mistake have an easy reference to your work.
2: run git reset HEAD~1 to reset the HEAD of the master branch to 1 commit before the HEAD.
Note: the text code HEAD~1 means "1 commit before the HEAD," and git reset changes the HEAD of the active branch.
In git, branches are just pointers to a commit, and commits have pointers to the commit before them. This means that master points to its HEAD commit, and its HEAD commit points to the commit before the HEAD, and HEAD~1 points to HEAD~2, and HEAD~2 points to HEAD~3, and this goes on and on all the way to the first commit in the repository.
When we run the reset command, git will simply change the active branch's pointer to whatever value we specify. In other words, we're doing this:
currentBranch.currentCommit = currentBranch.currentCommit.previousCommit;
If you have ever programmed anything with a linked list, it should be easy to understand how git works.
After we use the reset command, git will start doing its work thinking it's in the previous commit (because it is now), however, it won't change the files to match what is in your git repository.
That is, when we use git checkout, the files change to match the commit that we are checking out, but when we use git reset, the files are left untouched, and only the git's internal workings change.
This means we can use git reset to make git behave as if it were in the previous commit, forgetting all about the files that we had committed wrong. Now we can simply add those files again and commit to master again.
Advanced Tricks
It's also possible to use git reset to cherry pick changes from one branch into another.
For example, if you git checkout foo-branch, all your files will match foo-branch's current commit. Then you can use git branch temp-branch to create a copy of foo-branch that we can modify without changing foo-branch. If we git checkout temp-branch, and then git reset bar-branch to make temp-branch's HEAD become the same as bar-branch's HEAD, we will have:
foo-branch's files.fish-branch'sHEAD.temp-branchas the active branch.
Then we can select which files from foo-branch we want to add to fish-branch and commit it to temp-branch. After we're done, we can just merge the temp-branch with fish-branch to fast forward its pointer to our new commit.