-
Notifications
You must be signed in to change notification settings - Fork 54
Using Fixup Commits
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?
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.
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.
This is covered in Rebasing Branches and again rewrites the history.
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 <someguy@someplace.com>
Date: Tue Apr 22 23:21:15 2020 +0100
[VQA] Implements VQA audio code.
commit 958d499d1c9621ed6637de436d4e6d97f4fea44c
Author: SomeGuy <someguy@someplace.com>
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 <someguy@someplace.com>
Date: Tue Apr 23 23:13:57 2020 +0100
fixup! [VQA] Implements most of the VQA headers.
commit 65838ce69376c65027bd65391b9cb36118c9f7ed
Author: SomeGuy <someguy@someplace.com>
Date: Tue Apr 22 23:21:15 2020 +0100
[VQA] Implements VQA audio code.
commit 958d499d1c9621ed6637de436d4e6d97f4fea44c
Author: SomeGuy <someguy@someplace.com>
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 <someguy@someplace.com>
Date: Fri Apr 24 23:12:03 2020 +0100
fixup! [VQA] Implements VQA audio code.
commit 15a6f229b2f920741ce354c285316cbdc413360e
Author: SomeGuy <someguy@someplace.com>
Date: Tue Apr 23 23:13:57 2020 +0100
fixup! [VQA] Implements most of the VQA headers.
commit 65838ce69376c65027bd65391b9cb36118c9f7ed
Author: SomeGuy <someguy@someplace.com>
Date: Tue Apr 22 23:21:15 2020 +0100
[VQA] Implements VQA audio code.
commit 958d499d1c9621ed6637de436d4e6d97f4fea44c
Author: SomeGuy <someguy@someplace.com>
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.