Skip to content

Using Fixup Commits

OmniBlade edited this page Feb 12, 2021 · 1 revision

As you develop and test a feature branch you may find you need to make small commits that correct bugs introduced in previous commits in the branch. Ideally these would just have been part of the original commit, so what to do about it?

Do Nothing

The easiest approach is to just live with it and accept that you'll have a git history with lots of little fixup commits. Fair enough, but if you can cut down on the number of trivial commits with only a tiny bit more work isn't that better? Also, if you are wanting to contribute to larger projects, they may insist you squash some of your commits together to be accepted upstream.

Amend Commits

If the commit was the last one you made then you can just add the files with git add as though you were preparing for a new commit and then use git commit --amend to redo the last commit you made with the additional changes. If you already pushed your changes to your Github repo, then you will need to force push such changes as it rewrites the history. This may or may not be fine if you have other people reviewing or contributing to the branch you are developing. git push -f will force the push.

Interactive Rebase

This is covered in Rebasing Branches and again rewrites the history.

Fixup Commits

Fixup commits on their own don't actually make any changes to the history. Instead they declare ahead of time that a commit should be merged into a previous commit during an interactive rebase. While it is possible to do the same thing manually during an interactive rebase, if you have a lot of fix commits to merge and they need to be merged into different commits it can be hard to keep everything straight. The commit process is the same for a normal commit except the command looks like git commit --fixup commithash where the commit hash is the hash of the commit you want to merge this commit into at a later date.

Lets consider a worked example, I have a branch called vqa working on video decoding for the early C&C games and I've made two commits, one contains the headers that define the data structures and function prototypes and one contains the implementations of the audio functions. Running git log shows a history something like the following:

commit 65838ce69376c65027bd65391b9cb36118c9f7ed
Author: SomeGuy <[email protected]>
Date:   Tue Apr 22 23:21:15 2020 +0100

    [VQA] Implements VQA audio code.

commit 958d499d1c9621ed6637de436d4e6d97f4fea44c
Author: SomeGuy <[email protected]>
Date:   Tue Apr 21 22:26:29 2020 +0100

    [VQA] Implements most of the VQA headers.

Now I realised I misnamed something in one of the headers so I need to fix it. I make the fix and add the changed files, but I want it ideally to be part of the headers commit. Now I run git commit --fixup 958d499d1c96 and it creates the fixup commit so the log now looks like the following:

commit 15a6f229b2f920741ce354c285316cbdc413360e
Author: SomeGuy <[email protected]>
Date:   Tue Apr 23 23:13:57 2020 +0100

    fixup! [VQA] Implements most of the VQA headers.

commit 65838ce69376c65027bd65391b9cb36118c9f7ed
Author: SomeGuy <[email protected]>
Date:   Tue Apr 22 23:21:15 2020 +0100

    [VQA] Implements VQA audio code.

commit 958d499d1c9621ed6637de436d4e6d97f4fea44c
Author: SomeGuy <[email protected]>
Date:   Tue Apr 21 22:26:29 2020 +0100

    [VQA] Implements most of the VQA headers.

I'm happy with this so I push it to Github and ask someone to test the code works for them. Oh no! They report there is a bug in the audio code so I fix it, but when I PR the code I want it to be part of the audio code commit. No problem, now I run git commit --fixup 65838ce69376 after I've added the changed files and we have a log that looks as follows:

commit 3ed2daad98aaefd7302019f76d0ea446a5a232f9
Author: SomeGuy <[email protected]>
Date:   Fri Apr 24 23:12:03 2020 +0100

    fixup! [VQA] Implements VQA audio code.

commit 15a6f229b2f920741ce354c285316cbdc413360e
Author: SomeGuy <[email protected]>
Date:   Tue Apr 23 23:13:57 2020 +0100

    fixup! [VQA] Implements most of the VQA headers.

commit 65838ce69376c65027bd65391b9cb36118c9f7ed
Author: SomeGuy <[email protected]>
Date:   Tue Apr 22 23:21:15 2020 +0100

    [VQA] Implements VQA audio code.

commit 958d499d1c9621ed6637de436d4e6d97f4fea44c
Author: SomeGuy <[email protected]>
Date:   Tue Apr 21 22:26:29 2020 +0100

    [VQA] Implements most of the VQA headers.

At this point we haven't made any changes to history so anyone else developing on this branch is happy that they don't have to deal with rewritten histories and can pull the branch normally. Eventually you want to prepare the branch for PR though and we want to merge all the commits we've been diligently marking as fixups into their respective base commits. Before we start we need to ensure the main branch such as vanilla is up to date and that we are on our feature branch. Do to that we do the following:

git checkout vanilla  
git pull upstream vanilla
git checkout feature_branch_name

Now we will perform an interactive rebase with git rebase -i vanilla. Depending on how you have git configured this will open some kind of text editor that should look something like as follows:

pick 958d499 [VQA] Implements most of the VQA headers.
fixup 15a6f22 fixup! [VQA] Implements most of the VQA headers.
pick 65838ce [VQA] Implements VQA audio code.
fixup 3ed2daa fixup! [VQA] Implements VQA audio code.

# Rebase 958d499..3ed2daa onto 710f0f8
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

If you save and close the file with no other action, the rebase will proceed and the two fixup commits will be gone from the history, their changes merged into the relevant original commits as though it was always that way. Now you have a nice tidy history to force push to your repository to make a pull request from to the main repository.

Clone this wiki locally