DEV Community

Atul Sharma
Atul Sharma

Posted on • Updated on

Git better with fixups

Intro

By now I'm sure many of you are familiar with the Github Flow.

Before I introduce some clever git commands to help maintain a clean git history, it's helpful to review some good git principles.

5 rules to Git Better

If that link is not available the 5 rules are:

  1. Make commits as small as possible
  2. Write meaningful commit messages
  3. Rebase always
  4. Don’t squash everything
  5. Commit instead of commenting out

Alright, now that we have that out of the way let's look at a common situation.

Let's assume the following as a starting point for the suggested workflow in this post:

Preconditions

You want a clean Git history

You don't want to rewrite your git history consistently

Situation

# coding the component

$ git commit -m "add some component"

# missed an edge-case

$ git commit -m "fix missing null check"

# add component to service

$ git commit -m "add component to service"

# updating the component after service tests found a bug

$ git commit -m "fix component due to silly bug"

$ rubocop

# fix linting errors

$ git commit -m "fix linting errors"

# team members do a code review on the PR / MR

$ git commit -m "fixes based on PR comments"

Now this history isn't uncommon (in fact I can't think of many PRs of real substance that don't go through something like this).

This will result in a Git history that looks like this

fixes based on PR comments
fix linting errors
fix component due to silly bug
add component to service
fix missing null check
add some component

But is that really reflective of the work? We don't want the commit history to be littered with "fix linting" commits.

This is actually representative of the work.

add component to service
add some component

Solution

Let's go about these "fixes" in a different way.
We'll use the familiar command git commit but with an interesting flag called (appropriately) fixup.

# coding the component

$ git commit -m "add some component"

# missed an edge-case

$ git commit --fixup <COMMIT HASH OF THE COMMIT "add some component">

# add component to service

$ git commit -m "add component to service"

# updating the component after service tests found a bug

$ git commit --fixup <COMMIT HASH OF THE COMMIT "add component to service">

$ rubocop

# fix linting errors

$ git commit --fixup <COMMIT HASH OF THE COMMIT "add component to service">

# team members do a code review on the PR / MR

$ git commit --fixup <COMMIT HASH OF THE COMMIT "add component to service">

Now if we look at the git log we'll see

fixup! add component to service
fixup! add component to service
fixup! add component to service
add component to service
fixup! add some component
add some component

Instead of regularly committing (and also writing a commit message) we use the --fixup flag to create fixup commits.

Now, before merging the merge request, we issue an interactive rebase with the --autosquash flag.

Note:

GitLab marks MRs that include fixup commits as WIP, so
merging is prevented if you haven't squashed.

$ git rebase --interactive --autosquash \
  <COMMIT HASH OF THE COMMIT "add some component">~1

We'll enter interactive rebase mode with the relevant commits already marked for fixup:

fixup afa6970 fixup! add component to service
fixup 1da5d7c fixup! add component to service
fixup c998b08 fixup! add component to service
pick d9731b5 add component to service
fixup 33d6080 fixup! add some component
pick 73e8a6a add some component

The resulting Git history looks like this:

add component to service
add some component

The final result is a very consistent and readable Git history, where every other developer knows what happens, without knowing about every bug on the journey to this final code.

Summary

In summary, the following two Git commands are necessary to make use of the Git fixup workflow:

$ git commit --fixup <COMMIT THAT NEEDS FIXING>
$ git rebase -i --autosquash <FIRST COMMIT THAT NEEDS FIXING>~1

Oldest comments (4)

Collapse
 
ben profile image
Ben Halpern

Love this

Collapse
 
albertopdrf profile image
Alberto Pérez de Rada Fiol

Very useful, thanks for sharing! 😊

Collapse
 
ro6 profile image
Robert M. Mather

Doesn't rebasing like this (after having pushed, eg for a PR) require a "push - -force" in the end since you rewrote public history in order to do the fixups?

Collapse
 
lbreuss profile image
Leo Breuss

Yes. But... hopefully, you work on a (almost) private feature branch...

Most rebase operations alter the history of the branch, and therefore require a push --force. But that will grow into a habit soon. And as a consequence, this is why you should protect your master branch to not allow it.