As git users, we know that we should "commit early, commit often." While this is a wonderful thing to do, it does mean that from time to time we make a mistake and need to fix a commit. Maybe we forget to git add
a new file, or missed a typo. So we go ahead and git commit --amend
. Problem solved. Great.
But personally, I hate it.
For one thing, amending commits hides history. Once you amend, that past state is gone before you can properly test the new one. True, you can also restore it via git reflog
, but no-one really likes using that. It should be a last resort.
For another thing, amending is very limited. Say I am writing some C code. I write my first module, add it and commit.
git add FirstModule.h
git commit -m "Added FirstModule"
I write my second module, and add it as well.
git add SecondModule.h SecondModule.c
git commit -m "Added SecondModule"
And now, after adding that second commit, I realize that I forgot to commit FirstModule.c
. git commit --amend
to the rescue? Not really. I now have to resort to the black, frightening voodoo magic called git rebase
.
First, we commit the forgotten module
git add FirstModule.c
git commit -m "Added FirstModule.c, forgotten eariler."
And then rebase - git rebase -i HEAD~3
pick 1db8687 Added FirstModule
pick 336941b Added SecondModule
pick 7884909 Added FirstModule.c, forgotten eariler.
Change to
pick 1db8687 Added FirstModule
fixup 7884909 Added FirstModule.c, forgotten eariler.
pick 336941b Added SecondModule
Save & Quit, and we're done.
* 1946e37d105ffebcbd91bb958f8a2fce6160c761 (HEAD -> master) Added SecondModule
| create mode 100644 SecondModule.c
| create mode 100644 SecondModule.h
* 8ffbb9f2915e060a6c4771e13f5a82442743724c Added FirstModule
| create mode 100644 FirstModule.c
| create mode 100644 FirstModule.h
* 815e7bab6ee1fa5bf1df10f5705919b48cbe214c First Commit
Not that hard, is it?
But still, moving between amending and rebasing can be cumbersome. Especially as most of the time there is no real need to rebase and it's easy to forget the process. Enter git commit --fixup
(or --squash
) and git rebase -i --autosquash
.
These commands save us the work of reordering the commits and changing from pick
to fixup
or squash
. Making our rebasing work a lot easier.
I like defining the following aliases:
[alias]
ri = rebase -i --autosquash
mri = rebase -i
fix = commit --fixup
squ = commit --squash
Using those aliases, the rebasing we did earlier would work as follows:
git add FirstModule.c
git fix HEAD~1
git ri HEAD~3
We'd get the following rebase automatically
pick 1db8687 Added FirstModule
fixup 50a3650 fixup! Added FirstModule
pick 336941b Added SecondModule
Exit the editor, and be done with it.
We can use fix
as many times as we want (just go ahead and git fix HEAD -a
) before the rebase. Our log may look funny
* fe0c2a0 (HEAD -> master) fixup! fixup! fixup! fixup! Added SecondModule
* a53cd32 fixup! fixup! fixup! Added SecondModule
* 9c19f2d fixup! fixup! Added SecondModule
* b758a53 fixup! Added SecondModule
* 902d65e Added SecondModule
* 67f1260 Added FirstModule
* 815e7ba First Commit
But the rebase doesn't care
pick 902d65e Added SecondModule
fixup b758a53 fixup! Added SecondModule
fixup 9c19f2d fixup! fixup! Added SecondModule
fixup a53cd32 fixup! fixup! fixup! Added SecondModule
fixup fe0c2a0 fixup! fixup! fixup! fixup! Added SecondModule
Conclusion
Stop using git commit --amend
and start using git fix
(git commit --fixup
) instead. It is a no-fear, low-overhead alternative, and it far more flexible.
Here are the aliases again, in case you want them:
[alias]
ri = rebase -i --autosquash
mri = rebase -i
fix = commit --fixup
squ = commit --squash
Top comments (17)
Here are a few shortcuts in the same vein:
fixup with the default of HEAD:
fu = !bash -c 'git commit --fixup ${1:-HEAD}' -
Find the point of divergence of the current branch from another branch (default master), useful for the following trick:
diverges = !bash -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' -
Finally, rebase with autosquash from the point of divergence from another branch (so that you don't have to count the commits):
ar = !bash -c 'git rebase --autosquash -i `git diverges ${1:-master}`'
This is great, thanks!
Nice article!
Note that you can also use
git config --global rebase.autosquash=true
so that you do not need to add the--autosquash
flag to all yourgit rebase
commands.Thanks!
That's true. Just make sure to add
--no-autosquash
to thegit mri
alias to get the same behavior.Cool tip!
Just to clarify, it's
git config --global rebase.autosquash true
; having the=
throws aninvalid key
error.I don't quite get why I wouldn't use the much simpler
git amend
(alias forgit commit --amend
) for the simple case of adding to the last commit and only do a rebase (remember no unstaged changes for that, may need to stash!) if I really need it.That's a matter of personal preference.
I like having the history of minor changes available. I usually only rebase before a
git push
, and by that time, I try not to have any uncommited changes in any case.I feel that the little extra typing (which can be saved using @freeatnet tip here) is worth it for the extra history and for editing previous commits.
Wow, this is great. I am going to immediately modify my workflow in this direction.
Woops, commented from the staff account. ¯_(ツ)_/¯
Yes, yes and a thousand times yes!
Developers need to learn to commit often and create small commits for their own safe and their mates.
So huge thank you for spreading the word :)
I also wrote an article on this subject few weeks ago: adopteungit.fr/en/methodology/2017...
Another alternative is
git rebase -i
and marking the "Added First Module" with "edit". Then make the change, add, commit and rebase --continueAlso a nice tip that usually goes along with
--fixup
is to find the commit with:/
i.e.git commit --fixup :/First
will find the last commit withFirst
in its message.Cool, haven't used fixup yet.
Just saying if you hate things because they hide history you should fight against rebase too
It's the hiding history while I work part that bothers me.
Thanks for the article, useful information.
But amend is not evil if you use it in right purposes.
ps: never used amend it fix the history, only for append changes on dev commit.