July 15, 2014
If you’ve ever needed to look back on when some change was made in a project, you probably went to the git commit history, which provides an objective narrative of the project’s development. Well, unless you get that Flux Capacitor fluxing and hit 88 miles per hour with these git commands.
git commit –amend
In a nutshell, this command allows you to touch up the most recent commit. Imagine you’ve just created and committed a new README file, and then noticed you made a spelling error somewhere in that file. Creating an entire new commit just to fix the typo seems obnoxious, but you don’t really want to include that change in the next commit (which likely has nothing to do with the README) either. “I wish I just hadn’t made that error in the first place,” you think. With
git commit --amend, you can effectively make git believe you never had. Simply fix the error, stage the change(s), and run
git commit --amend to add the late change as part of the most recent commit.
There is an important caveat to note here. Despite the flag name,
git commit --amend does not simply edit the previous commit; rather, it actually destroys the commit and replaces it with a new one that has all the changes the previous commit had, plus the newly staged changes. On completely solo projects or local branches this technicality doesn’t mean much, but it does affect group repositories. Amending a commit that has been pushed to GitHub will cause your local branch and the remote branch to “diverge”–the GitHub repo will still have the original commit, but you will have the amended commit on your local machine. As a result, regular pushes and pulls will not work properly–you’ll need to force push (if your local amended commit is the correct one to share) or reset hard to the remote branch (if someone else amended a commit and you’re pulling the new one). Neither of these is too terrible, but they do require caution and are best avoided if possible. I try to limit my amending to commits on local branches that haven’t been pushed (let alone merged) yet or “work-in-progress” commits.
Rebasing is quite helpful for team projects. The classic example found on several tutorials provides a great demonstration of when to use rebase: imagine you start working on a new feature on a separate branch. In the middle of your development on the branch, someone discovers a critical security flaw that must be patched immediately. You switch back to your main branch (in this case ) and checkout a new branch. You and your team make all the necessary changes to fix the security breach, and so you go back to and merge in :
git checkout master
git merge security-fix
Phew! Crisis averted. Back to the new feature you were working on. Except now is effectively branched off of an out-of-date state of . Whenever you finish the new feature, merging back to master is going to be a pain--at the very least it will require a pesky merge commit, and additionally you run the risk of having conflicts with the changes that happened in .
The best thing to do is use
git rebase, which will allow you to maintain a more linear commit history. After merging into above, check out the in-progress branch and rebase master:
git checkout new-feature
git rebase master
This moves the changes to the tip of as if the branch had originally been branched off of at that point and not a few commits back (pre ).
Like amending a commit, rebasing will replace old commits with new commits, so again, try to keep it local. Git is typically very rigid for a reason–it is extremely valuable to keep precise track of each and every change over the course of a project’s development. However, these two commands do provide some flexibility that when used properly can help you maintain a more readable commit history that highlights the development of features and isn’t too cluttered with minor details.