On the twelfth day of Christmas, my true love gave to me… twelve ways to fix mistakes.
In the previous lesson, we learned about stashing changes with Git until you need them so that we could save work in progress while we handled something more pressing. Today, we’re going to learn some ways to fix mistakes with Git.
If you are following our song, “The Twelve Days of Git,” here are the verses so far:
- The 12 Days of Git: Learn Git over the Holidays
- The 12 Days of Git, Day 2: Tracking Files with Git
- The 12 Days of Git, Day 3: Viewing Git History
- The 12 Days of Git, Day 4: Double-checking Changes with Git
- The 12 Days of Git, Day 5: Sharing Changes Remotely with Git
- The 12 Days of Git, Day 6: Making a Copy of a Git Repository
- The 12 Days of Git, Day 7: Keeping Your Git Repository Up-To-Date with Others’ Changes
- The 12 Days of Git, Day 8: Using Git to Experiment Safely
- The 12 Days of Git, Day 9: Using Remote Branches with Git
- The 12 Days of Git, Day 10: Merging Different Versions with Git
- The 12 Days of Git, Day 11: Stashing Changes with Git until You Need Them
Warning: the commands below could cause you to lose work by accident. Be especially careful when using them.
So, far in this series, I have given sample lines that you can put in your files to follow along with the Git commands. Today, I’m not going to do that, because this lesson is about using Git to undo what you’ve done. As such, it doesn’t really matter what you type in your edits.
Checking out the last committed version of a file
Let’s say you have made extension edits to one file, already saved it to disk, but not yet committed it with Git. Then you realize that you have taken an incorrect approach to what you were trying to do. Trying to reverse what you changed by hand is prone to error. Instead, use this version of the
git checkout command.
git checkout <file>
Replace <file> with the actual file name or path to the file. Now, that file will be back to how it was at the time of its last commit. Be careful with this option, because once you checkout the previous version of the file, any uncommitted changes are gone forever.
Resetting your repo to an earlier state
Instead of catching an incorrectly edited file before you committed it, let’s say that you have already made a commit that now regret.
In preparation for fixing something, add a throwaway commit to your repo. Pick one of the files and edit it, add it to the staging area, and commit it. Don’t push it to your remote repo! You should never reset your repo to fix something after you have pushed it to a remote, because others may have already pulled down those changes. Trying to synchronize such a situation later is incredibly hard.
Run our comprehensive log command to see the repo history at this point.
git log --graph --decorate --pretty=oneline --abbrev-commit --all
The first few lines of the output should look like this.
* 6243e90 (HEAD, master) a throwaway commit * d29564c (origin/master) Start locations feature * c0eca91 Change Father Christmas back to Santa
Notice that the HEAD and the local master branch branch are pointed at your throwaway commit.
Now, run the following version of the
git reset command, followed by our log command.
git reset --soft HEAD~1 git log --graph --decorate --pretty=oneline --abbrev-commit --all
The beginning of history should now look like this.
* d29564c (HEAD, origin/master, master) Start locations feature * c0eca91 Change Father Christmas back to Santa
That command told Git to reset the HEAD to one commit before its current position. (That’s what the ~1 means.) What happened to our throwaway commit? The –soft option caused the changes that were in that commit to be staged, ready to be committed again. You can see this if you run
git status. This is handy when you realized that the commit was mostly correct, but you need to modify it slightly and then recommit. Most of the correct work is already staged and ready to go again.
Consider a second scenario. Commit the throwaway work that’s already staged again, and then run the following version of
git reset --mixed HEAD~1
You should see output like the following.
Unstaged changes after reset: M index.html
The files listed will be whichever ones you had changed in your commit, but the effect is the same. The repo’s HEAD now points to the previous commit. The changes from the later commit have been saved. However, as you may have guessed from the output, those changes are no longer staged. You can edit the changes and then stage all your edits and commit.
Finally, let’s consider this case. Stage and commit your throwaway changes again. Now, assume that you realize the last commit is nothing like what you want. You just want to be rid of it. Run the most draconian version of
git reset --hard HEAD~1
The repo’s HEAD now points to the previous commit, but the changes from the later commit are gone. You can start over with a clean slate from where you were before that bad commit.
Reverting a pushed commit
Reset is a handy tool, if you haven’t already pushed your changes to a remote repo. (NEVER reset once you have pushed.)
Go ahead and make that throwaway commit one more time. This time, also use
git push to send it to your remote repo.
Instead of going backwards, we will reverse the changes we just pushed up, using the
git revert command.
git revert HEAD
Notice that here, we are actually reverting the commit we are on, HEAD, instead of going back to a commit before HEAD. Git will open your default text editor and suggest a message for you to use to explain the revert. You can use this default message or write your own explanation. When you save the editor file, Git will create another commit that exactly reverses the changes of the commit you specified. (You can specify an earlier commit to reverse, but the further back you go, the more likely you will have to resolve merge conflicts. The revert commit is just like a regular commit in this regard.)
Now, you can safely
git push this new revert commit to your remote repo, because it will not confuse the history of others who have pulled from your remote.
To see all the options available for the commands we’ve discussed today, check them out in the online Git reference: