loading...

Don't Amend, Fix

tmr232 profile image Tamir Bahar ・3 min read

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

Discussion

pic
Editor guide
Collapse
freeatnet profile image
Arseniy ✌️ Ivanov

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}`'

Collapse
tmr232 profile image
Tamir Bahar Author

This is great, thanks!

Collapse
dmerejkowsky profile image
Dimitri Merejkowsky

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 your git rebase commands.

Collapse
tmr232 profile image
Tamir Bahar Author

Thanks!

That's true. Just make sure to add --no-autosquash to the git mri alias to get the same behavior.

Collapse
supremebeing7 profile image
Mark J. Lehman

Cool tip!

Just to clarify, it's git config --global rebase.autosquash true; having the = throws an invalid key error.

Collapse
thepracticaldev profile image
dev.to staff

Wow, this is great. I am going to immediately modify my workflow in this direction.

Collapse
ben profile image
Ben Halpern

Woops, commented from the staff account. ¯_(ツ)_/¯

Collapse
engineercoding profile image
Wesley Ameling

Basically a fix commit here, as you probably could've changed it in the database ;)

Collapse
nipafx profile image
Nicolai Parlog

I don't quite get why I wouldn't use the much simpler git amend (alias for git 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.

Collapse
tmr232 profile image
Tamir Bahar Author

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.

Collapse
l_giraudel profile image
Loïc Giraudel

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...

Collapse
geovanisouza92 profile image
λ • Geovani de Souza

Another alternative is git rebase -i and marking the "Added First Module" with "edit". Then make the change, add, commit and rebase --continue

Collapse
lunks profile image
Pedro Nascimento

Also 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 with First in its message.

Collapse
Sloan, the sloth mascot
Comment deleted
Collapse
dimpiax profile image
Dmytro Pylypenko

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.

Collapse
bgadrian profile image
Adrian B.G.

Cool, haven't used fixup yet.

Just saying if you hate things because they hide history you should fight against rebase too

Collapse
tmr232 profile image
Tamir Bahar Author

It's the hiding history while I work part that bothers me.