Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Amending

You just found a typo in your last commit. How do you fix it?

A---B---C          <- main (HEAD)

Of course:

git commit --amend

Voilà, and the last commit is changed!

A---B---C'         <- main (HEAD)

This is what I call a beautiful abstraction! We all know that commits are immutable and that git commit --amend is no exception. Indeed, amend did not really change any commit: under the hood, it created a new commit with the same parent as the commit to fix.


A---B---C       <- main (HEAD)
     \
      C'        <- new, without any ref

Then, it reset the current branch ref to it:

        C
       /
A---B
       \
        C'      <- main (HEAD)

The old commit C is still there but it's hidden, so you only see:

A---B---C'      <- main (HEAD)

Git amend switched commits, pulling a fast one on you, giving you the impression that the last commit was editable. And this is the key: abstractions are all about providing a simplified view of the world, where details are kept out of sight.

Under the hood, git commit --amend is equivalent to something like:

git reset --hard HEAD^
git cherry-pick -n ORIG_HEAD
git add .
git commit -C ORIG_HEAD

But you don't need to know. The beauty of Git amend's abstraction is that it captures your intention, and it transparently deals with the internal mechanics.

Neat!

Leaky abstraction

How do you amend the second to last commit? Oops, there's no git commit --amend HEAD~1. Instead, you have to do:

git commit --fixup=HEAD~1
git rebase -i --autosquash HEAD~3

or:

git checkout HEAD~1
git add .
git commit --amend
git cherry-pick main
git branch -f main HEAD
git checkout main

Intimidating... The wonderful surface of abstraction has torn, and underneath you can see all the gears! It's no surprise that few people bother amending the penultimate commit.

What about amending Y?

A---B---C---D---E---F
     \         /
      X---Y---Z

Oh beautiful abstraction, where art thou?

More consistent abstraction

Can we do better? Although internally commits are immutable, when you amend your intention is to edit a commit. In other words, the metaphor underlying Git amend's abstraction is:

The last commit is editable.

Can we embrace this metaphor and make it universal? Doing so, one might conceive a command jj edit to modify any point of the history tree:

A---B---C          <- main (HEAD)

Want to amend C?

jj edit C

Want to amend B?

jj edit B

What if you had:

A---B---C---D---E---F
     \         /
      X---Y---Z

and you wanted to edit Y? You have already literally expressed your intention:

I want to edit Y.

Well, just type it!

jj edit Y

We went back to a beautiful, intuitive UX! jj edit captures your intention, the what, and deals with the necessary internal rebasing, resetting, cherry-picking, whatever, the how.

Of course editing Y implies cascading changes to Z, E and F, but what's the point of giving step-by-step instructions to your versioning system?

In practice

I promised this tutorial would be down-to-earth. So:

  • Install Jujutsu.
  • Crack open a shell.
  • cd to your Git repository.
  • jj git init. No worries, it's safe. Jujutsu happily lives together with Git. You don't need to switch to Jujutsu.
  • Keep working. Keep using Git.
  • Need to amend the penultimate commit or a commit deep down the history tree, and you struggle to do this with Git?
jj edit <SHA1>
# Do your changes
# Then switch back to where you were
git checkout <your-branch>

I'm lying a bit. You might encounter conflicts or other situations you'd better get prepared for. So, better keep reading just a bit more.

I hope you just had your first a-ha moment.