[Utility Post] How to escape 3 tricky ‘gituations’

Victor Algaze
7 min readJul 25, 2017

--

Gituation, (noun): A situation, oftentimes unpleasant or confusing, involving git commit histories

This note works through several practical examples of techniques you can use to get yourself out of tricky git-uations. If you’d like to follow along at home with a ‘real’ git history, you can clone the exercise repo using this command in your terminal:

$ git clone https://github.com/valgaze/gituations

Note: If you get stuck or reach an unrecoverable state enter the command $ npm run rebuild to reset the repo’s git history (if you do not have npm installed, you can also run the script rebuild.sh in the scripts directory)

Exercise I: Fear of Commitment

Gituation: You found a typo in your most recent commit message!

If you’re following along at home, cd to the gituations directory and enter the following command to get started:

$ git checkout ex/1

When you run $ git log on the ex/1 branch you should see the commit history — note that the most recent commit message has a typo:

😎 ~/gituations $ git log
commit f7a56344b1813c00168525c58b51ca03b11e654a
Author: gituations <>
Ex1, fix thizzz typo!commit 2f13584ffaefe3967f86998b47d32353e4a4b89c
Author: gituations <>
Initial commit

Solution: Use git commit with the --amend flag

If you use $ git commit with the --amend flag, git will open the most recent commit message with your default editor where you can then alter and save the commit message.

Give it a shot and then run $ git log to confirm that the commit message was indeed changed:

😎 ~/gituations $ git commit --amend ## Opens in editor😎 ~/gituations $ git log
commit f7a56344b1813c00168525c58b51ca03b11e654a
Author: gituations <>
Ex1, fixed!commit 2f13584ffaefe3967f86998b47d32353e4a4b89c
Author: gituations <>
Initial commit

Note: When you amend your commit messages you are ‘rewriting history’ — if you have already pushed to a public repository you can update the commit message by git pushing using the --force flag.

Exercise II: Over-committed

Gituation: There’s too many commits!

While thoughtful/consistent committing is a good git practice, there is certainly such a thing as too many commits. If you’re riding along, run the following command from the gituations directory to checkout the ex/2 branch:

$ git checkout ex/2

If you run $ git log you’ll see a far-too-granular commit history where we add some letters to a list:

😎  ~/gituations $ git log
commit 9c12c96cbe659f54df6a15fe6067eda48cb1a9c3
Author: gituations <>
Ex2, squash those commitscommit e96976edfdbd4e8f6ab9f67eff6f616775d60a4a
Author: gituations <>
Added ecommit 21f987c93e3273398dc1014140949805acb65fb1
Author: gituations <>
Added dcommit d3d3a2f211e1696abf9738b4bd41c947ea87fbb4
Author: gituations <>
Added c, there is something important on this commitcommit 45e05e7ce41c683d93d2c973a27669940ffbd8c3
Author: gituations <>
Added bcommit eb5a5d7fa2a3a162a95c698f04a616a21a8f5305
Author: gituations <>
Added acommit 2f13584ffaefe3967f86998b47d32353e4a4b89c
Author: gituations <>
Initial commit

We need to somehow consolidate the commits from eb5a5d7 to 9c12c96 into one single commit message.

Solution: Squash those commits using git rebase

We can squish those commits together using the mighty $ git rebase command. The rebase command is mainly concerned with moving/reapplying commits. The interactive flag (-i or --interactive) allows us to apply actions (save/discard/edit) to individual commits as needed. The ~ operator lets you specify parent commits.

We can use the command $ git rebase -i HEAD~x to squash together the last x commits.

Since the HEAD is 6 commits away from the initial commit in the repo’s git history, we can run this command which will exclude the very first commit from our squash operation:

$ git rebase -i HEAD~6

On the first screen after the running the rebase command, you’ll see a list of recent commits in your git editor, sorted from oldest to most recent. We want to use the farthest back commit at the top, so leave that as “pick.” Starting from the second commit from the top, change the action from “pick” to “squash” and continue down the list commits until your reach the end. Exit the editor and your terminal should read: Rebasing (x/6)

pick to squash, read more:

If the rebase operation completed successfully, you should then see a second screen where you can edit the commit history down to a single message:

Edit & save commit message

If all went well, now when you run $ git log you should see a much tidier commit history:

😎  ~/gituations $ git log
commit 1f380d8389a5e285c40e569ba6d7f06030f6dd4d
Author: gituations <>
Ex2, Added a bunch of letterscommit 2f13584ffaefe3967f86998b47d32353e4a4b89c
Author: gituations <>
Initial commit

(Reminder: If you break something during the rebase simply run $ npm run rebuild to reset the repo’s git history and re-checkout the branch ex/2)

Warning: Like the --amend flag with git commit, squashing will rewrite git’s history. Ideally you should only rewrite history on your local machine and never rewrite history on pushed histories or those already shared with your collaborators.

Exercise IIa: Under-committed

Gituation: You squashed your commits, but now you need to get one of the commits BACK — 😐😢😱

Say for some reason we wanted to extract the commit where we added the letter “c” to the list. If we run $ git log all we’ll see is our squashed commit history:

😎  ~/gituations $ git log
commit 1f380d8389a5e285c40e569ba6d7f06030f6dd4d
Author: gituations <>
Ex2, Added a bunch of letterscommit 2f13584ffaefe3967f86998b47d32353e4a4b89c
Author: gituations <>
Initial commit

Can we retrieve some of that history?

Solution: Git reflog!

If you run the $ git reflog command you should see something like this:

😎  ~/gituations $ git reflog
53bf0b0 HEAD@{0}: rebase -i (finish): returning to refs/heads/ex/2
53bf0b0 HEAD@{1}: rebase -i (squash): Ex2, Added some letters
445907c HEAD@{2}: rebase -i (squash): # This is a combination of 5 commits.
a6fc1a5 HEAD@{3}: rebase -i (squash): # This is a combination of 4 commits.
590ffad HEAD@{4}: rebase -i (squash): # This is a combination of 3 commits.
f2efb95 HEAD@{5}: rebase -i (squash): # This is a combination of 2 commits.
57c9646 HEAD@{6}: rebase -i (start): checkout HEAD~6
b251a28 HEAD@{7}: checkout: moving from master to ex/2
2f13584 HEAD@{8}: checkout: moving from ex/3 to master
6ad9fdb HEAD@{9}: commit: Ex3, Added comments about something breaking
59cd55b HEAD@{10}: commit: Ex3, Something broke
958face HEAD@{11}: commit: Ex3, working version
81a72ef HEAD@{12}: commit: Ex3, initial scaffolding
2f13584 HEAD@{13}: checkout: moving from master to ex/3
2f13584 HEAD@{14}: checkout: moving from ex/2 to master
b251a28 HEAD@{15}: commit: Ex2, squash those commits
30fb378 HEAD@{16}: commit: Added e
fe5859a HEAD@{17}: commit: Added d
e2b9972 HEAD@{18}: commit: Added c, there is something important on this commit
8b28770 HEAD@{19}: commit: Added b
57c9646 HEAD@{20}: commit: Added a
2f13584 HEAD@{21}: checkout: moving from master to ex/2

The line e2b9972 HEAD@{18} sure looks like our target commit, we can check it out with $ git checkout HEAD@{18} and our repo will then be at e2b9972 where we can inspect the changes or cut a new branch.

Note: Git’s reflog (“reference log”) data does not live alongside commit data, it is stored separately and only exists on your local machine.

Exercise III: Re-Committed

Gituation: A commit introduced a problem and you need to walk it back!

$ git checkout ex/3

Running $ git log you’ll encounter a commit history where the most recent commit introduced a problem:

commit ec8a5268a5794120b3a66d1101332c5344dcf0e2
Author: gituations <>
Ex3, Something’s broken…commit 66aeec54cbe02f8d07274fe0b54c5eb7564e020f
Author: gituations <>
Ex3, working versioncommit 2a5f4c41fbf562d0b94afa1008a9807343c8f502
Author: gituations <>
Ex3, initial scaffoldingcommit 2f13584ffaefe3967f86998b47d32353e4a4b89c
Author: gituations <>
Initial commit

Everything looks good until commit ec8a5268. We want to undo the first commit and fall back to commit 66aeec54. There are two main commands one could use to accomplish this:

$ git revert commitRef # Roll back, preserve bad commits in history

$ git reset commitRef # Roll back, discard bad commits in history

If we target the most recent bad commit and roll back with $ git revert 92dbcdfe6, we will see our commit history now reads as follows:

commit 666853ed20af4b239adb935ac794c31b8157043b
Author: gituations <>
Revert “Ex3, Something’s broken…”This reverts commit ec8a5268a5794120b3a66d1101332c5344dcf0e2.commit ec8a5268a5794120b3a66d1101332c5344dcf0e2
Author: gituations <>
Ex3, Something’s broken…commit e9fd80db7437b76a56c9a0968053a4a8d1ef427e
Author: gituations <>
Ex3, working versioncommit 2a5f4c41fbf562d0b94afa1008a9807343c8f502
Author: gituations <>
Ex3, initial scaffoldingcommit 74f4902185ab997fe398cf0df7d6f96fada1a81d
Author: gituations <>
Initial commit

We have our original troublesome commit and also another new commit that explicitly reverts the existing commit. Note that our commit history did not change.

From this point, if we were to use the $ git reset command (which does rewrite history) and reset back to our working commit — $ git reset e9fd80db — we would rewrite history and our log would look like this:

😎 ~/gituations $ git log
commit e9fd80db7437b76a56c9a0968053a4a8d1ef427e
Author: gituations <>
Ex3, working versioncommit f50517d1c1aa8c5cbc2999c1792b029dc8088cb6
Author: gituations <>
Ex3, initial scaffoldingcommit 74f4902185ab997fe398cf0df7d6f96fada1a81d
Author: gituations <>
Initial commit

Recap

The techniques described in these admittedly contrived examples demonstrate the amount of control one has over git history. Take care when using them on public-facing repositories because rewriting history can cause havoc for others who based their work off of yours:

To recap, we learned how to use the the commands:

  • $ git commit with the --amend flag to edit most recent commit message
  • $ git rebase -i HEAD~x to squash the last x commits
  • $ git reflog to inspect the ‘hidden’ local reference log data
  • $ git revert to roll back a commit without rewriting history
  • $ git reset to roll back commits while rewriting history

Further Reading:

--

--