Git’s commit history is designed to be immutable (for the most part) and track every change in your project so you never lose work. However, sometimes it’s necessary to rewrite Git history, so Git provides a few tools for editing existing commits.
Adding New Changes To Commits
The most common use case for this is when you make a commit message, and then, before pushing to your remote, realize that you messed up and need to make a small change. You could of course just make a second commit, but that’s unnecessary, and also shows all your coworkers your dumb mistake when you eventually push to the remote.
If you’re simply adding changes, you can use git commit –amend. This modifies the most recent commit, and merges in the additional changes that you’ve staged.
First, you’ll need to stage your changes:
And then amend:
The –no-edit flag will make the command not modify the commit message. If you need to clarify the new changes in a new message, leave this flag out, and you’ll be prompted for the new commit message.
Under the hood, the amend command makes a new commit with the extra changes and then completely replaces the source commit in the Git history. The old commit is still accessible from git reflog (more on that below), but going forward, the new commit is the only one that exists. When you push to a remote repo, there’s no way to know that the commit was amended, it’s a purely local change.
Because of this, you will not want to amend commits that have already been pushed, as you will run into many problems and need to forcibly push to the remote, which is not good for anybody.
Changing Just The Git Commit Message
If you don’t need to make any changes, and just want to fix a typo, you can run amend without any changes as well:
Unstaging Changes From Commits
Git’s amend command only works if you’re strictly adding changes. When we say “added,” we don’t just mean new lines of code; changing a line of code is also adding changes. Removing a line of code, or an entire file, is also a change being added, even though it’s removing data from the project when that change is applied.
However, there are also cases where you might want to actually remove changes from commits. For example, say you ran:
And added every change in your repo to the staged changes, and committed it, before realizing, “oh crap! I didn’t mean to commit that one file!” In this case, you would need to send all the changes back to staging, and then manually unstage the files you don’t want to push.
The solution is to perform a reset, removing the commit and sending the changes back. There are a few kinds of resets, but they all involve taking commits from Git’s history and sending them back to either staging, the local directory, or straight to the trash.
In this case, a soft reset is what you want, which will send all changes back to staging. You can use the following shorthand to reset to the commit behind the HEAD, otherwise you will need to grab the reference from git reflog:
Then, you will need to remove the file you don’t want committed. The way to do this is actually also a reset, in this case, a mixed reset on a specific file:
This works because resetting this one file will remove the changes from staging, and it won’t get committed when you redo the commit.
You can also do a mixed reset on the whole repo, and git add all the files except the one you don’t want. This is easier to do if you’re using a GUI Git client.
Need To Undo/Remove a Commit? Use Reverting
Reverting a commit is the easiest way of removing changes. Basically, it takes all the changes from the target commit, and applies the opposite of them. If you created a file, it’s removed. If you removed a line of code, that code is added back. It’s the Git-approved way to “remove” or “undo” a commit, as the original is still kept in the git history.
To use it, run git log to view the commits:
Copy the reference ID, and then revert the commit:
If you just got stuck in vim, press Q, and maybe run git config –global core.editor “nano”.
Use Rebasing For Anything More Complicated
Rebasing is essentially a method of moving commits around in your repository. Rather than merging, rebase rewrites git history to move individual commits to a new location. The original commits are left dangling and are removed from the official Git history, though they’re still there in git reflog.
We won’t get into the exact details of it here, but if you’re interested in how it works, you can read our guide to using git rebase.
RELATED: What is Git Rebase and How Is it Different than Merging?
What If You Want To Go Back?
Luckily, Git keeps a record of every single change, even when you break the rules and rewrite history.
Every time your branch tip is updated for any reason, Git stores the state of the directory contents before the update in the Reference Log, or reflog. You can view the log with git reflog:
Many of these will be actual commits, but it also includes other changes. If you need to go back to any individual change, you can perform a hard reset:
This provides a nice safety net, but you should be warned that reflog only tracks changes that were actually committed, not just staged, and it only tracks movements of the branch tip. Additionally, reflog only keeps entries for 90 days. After that, you’ll only be able to reset back to actual commits.