Random learnings and other thoughts from an unashamed geek

My Favourite Git Commands III: Editing History

| Comments

This is part 3 in my Favourite Git Commands series.

Editing History

You should never change a commit that has been shared with someone else - it makes everything very confusing for your collaborators. This is one of the many reasons you should always be aware exactly what you’re committing. However…

It’s often useful to edit, reorder, split or merge your local (unpushed) commits - for example you might want to fix a bug you introduced 2 commits ago that you don’t want anyone to ever know about. EVER. This ability to easily edit history, fix mistakes in commits (for example if you accidentally added a password) and generally control your revision history is one of the things that makes git so powerful in my opinion.

git commit --amend

Sometimes working on the next commit reveals a bug or typo in a previous commit, in that instance it often makes sense to apply a fix to the previous commit directly. This will save time for your collaborators who may immediately spot the mistake, make a note of it or tell you about it, only to find out that you fixed it in the next commit. Better to fix it so no-one else has to process it, I think.

Other times you may notice you forgot to add a file or said n instead of y to one of the git add --patch hunks. To fix these situations simply add any missing files, changes or fixes; review these changes; and then git commit --amend.

You can also use this if you just want to change the commit message and nothing else, but be aware that this still changes the commit’s hash, so you still shouldn’t do this on commits that have been pushed.

git reset --soft HEAD^

This is useful if you messed up so bad you want to remove the last commit and try again - it resets the current branch to the first parent of HEAD and moves the committed hunks back to the staging area. One reason you might want to do this is if you accidentally did a git commit -a and committed more code than you intended to. (Tip: you can move changes from the staging area to the working copy with git reset HEAD [filename].)

If you want to throw away the code altogether then you can swap --soft for --hard (warning: this discards your working copy changes too). Before doing this it might make sense to branch - e.g. git branch messed_up_code - and stash your changes with git stash. When you’re happy you did want to discard that code, you can delete this branch with git branch -d messed_up_code and drop the stash with git stash drop.

git rebase --interactive HEAD~3

Almost everything in git can be achieved with the use of git rebase. Interactive mode is a great introduction to this very powerful mode, and is useful for squashing (merging), reordering, dropping or editing commits. When you run this command, it will allow you to chose various options for each of the last 3 commits (see the help text at the bottom) and then it will guide you through making the changes. Note that you can reorder or drop commits by rearranging or deleting their lines.

For example, to edit the 3rd commit ago, I’d run the above command and then change the relevant pick to edit in the editor that appears, then save and exit. Git will then revert me back to that commit, where I can make changes to the files in the local copy, and then git add --patch and git commit --amend. When I’m happy with the result I simply git rebase --continue and git will apply the future commits on top of my newly edited commit. Recent versions of git very helpfully tell us what we need to do at each stage.

Rebase commands in my version of git are:

  • pick = use commit
  • reword = use commit, but edit the commit message
  • edit = use commit, but stop for amending
  • squash = use commit, but meld into previous commit
  • fixup = like squash, but discard this commit’s log message
  • exec = run command (the rest of the line) using shell

(and don’t forget you can also drop and reorder commits by deleting or rearranging their lines.)