Break stuff. Now.

When Git Can Lie

Probably the closest thing to being god-like

September 5, 2018

One of the biggest advantages of Git is that every change you introduce into the project is recorded in history. It's just like how Redux allows you to implement undo and redo by making state a function time, or how the blockchain keeps a record of every transaction ever done on the network. But what if Git... can lie?

Clarifying "never change history"

Normally, developers tell you to "never change commit history". But it's an ambiguous statement as it can be interpreted as "if you already made the commit, it's a done deal". A more correct way of saying it is "never change public history". The key word here is "public". Manipulating history is only problematic if someone has made work on top of it. The opposite is true for commits that haven't made it to the public. And that's where the magic begins.

History is not chronological

git rebase --interactive is probably the most powerful Git command I was ever taught. It's effectively a bulk version of git cherry-pick. It allows you to manipulate the history of any branch. Reorder commits, merge multiple commits into one, split commits, update commit messages. Yes, that command can do it all.

I use interactive rebase all the time. A common use case for me is moving low-risk commits earlier, and high-risk commits later. That way, I can easily throw them away since they're closer to the tip. Reordering commits with git rebase will update the commit's hash. However, it will not change the commit data especially the date. As a result, commits can be out of order chronologically. Thus, one cannot assume that the commit history is always chronological.

History does not reflect activity

Another fun command is git reset. You've probably read about it in some Stack Overflow answer. It's usually portrayed as the undo-er of commits. Well, it is that and more. In fact, it has three common modes:

  • --soft: uncommits but does not unstage changes.
  • --mixed: uncommits and unstages changes. This is the default.
  • --hard: uncommits and throws away the changes.

When working on large features, I use commits as temporary "checkpoints". This way, I can easily throw away work in progress and roll back to known good code. But the commits made this way are not for public consumption. So just before I sign off on my work, I uncommit all my changes with git reset and then meaningfully recreate and reword my commits. As a result, my changes appear as if they were all made at the last minute. Thus, one cannot assume that the commit history reflects what work was done when.

History does not tell the whole story

The last command in the arsenal is git commit --amend. It's like your regular git commit. But instead of creating a new commit, it appends your changes to the last commit. It will add anything that's currently staged to the last commit. It will even let you update the commit message of the last commit.

git commit --amend also hides work behind another commit. But this time, it hides newer work inside an earlier commit. Say I use git commit --amend because I feel like today's changes make sense bundled together with yesterday's commit. As a result, the work I made today is hidden behind the commit that's dated yesterday. Thus, one cannot assume a commit only contains work done at that point in time.


What I'm effectively saying is that version control cannot accurately tell you who did what and when. It's not a time sheet, it's not a job log,and it's definitely not an activity monitor. The only thing version control can reliably tell you is what which commit did what.

So the next time you see a series of commits that are dated seemingly at the last minute, it's my inner perfectionist at work. Please read my commit messages.