🔗 Splitting a Git commit into one commit per file
Sometimes when working on a branch, you end up with a “wip” or “fixup” commit that contains changes to several files:
01a25e6 introduce raccoon library bd197ac modify core to use raccoon 02890e3 add --raccoon option to the CLI f938740 fixes fab9379 add documentation on raccoon features
Our f938740 fixes commit has changes that really belong in the three previous commits. Before merging, we want to squash those changes in the original commits where the correct code should have been in the first place.
The typical way to do this is to use interactive rebase, using git rebase -i.
This is not a post explaining interactive rebase, so check out some other sources before proceeding if you are not familiar with it!
Splitting things from a “fixup” commit can get tedious using git rebase -i in conjunction with the edit option and git add -p, especially when you really know that all changes to a file belong to a certain commit.
Here’s a quick script for the rescue: it is designed to be used during an interactive rebase, and splits the current commit into multiple commits, one with the contents of each file:
#!/usr/bin/env bash message="$(git log --pretty=format:'%s' -n1)" if [ `git status --porcelain --untracked-files=no | wc -l` = 0 ] then git reset --soft HEAD^ fi git status --porcelain --untracked-files=no | while read status file do echo $status $file if [ "$status" = "M" ] then git add $file git commit -n $file -m "$file: $message" elif [ "$status" = "A" ] then git add $file git commit -n $file -m "added $file: $message" elif [ "$status" = "D" ] then git rm $file git commit -n $file -m "removed $file: $message" else echo "unknown status $file" fi done
Save this as split-files.sh (and make it executable with chmod +x split-files.sh).
Now, we proceed with the interactive rebase. When doing an interactive rebase, Git will open a text editor: in the commit you want to split, replace pick with edit:
pick 01a25e6 introduce raccoon library pick bd197ac modify core to use raccoon pick 02890e3 add --raccoon option to the CLI edit f938740 fixes pick fab9379 add documentation on raccoon features # Rebase 01a25e6..fab9379 onto cb370a2 (5 commands) # # Commands: # p, pick= use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # ...
When you save and exit the text editor launched by Git, you will return to the prompt with the repo's HEAD pointing at the commit we will split. Then run ./split-files.sh and then git rebase --continue.
Now launch the interactive rebase again. Your commits should look like this:
pick 01a25e6 introduce raccoon library pick bd197ac modify core to use raccoon pick 02890e3 add --raccoon option to the CLI pick 8369783 src/lib/racoon.foo: fixes pick a3c4e42 src/cli/foobar: fixes pick 108a931 src/core/core.foo: fixes pick fab9379 add documentation on raccoon features # Rebase 01a25e6..fab9379 onto cb370a2 (7 commands) # # Commands: # p, pick= use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # ...
The "fixes" commit in our example was split into three. Now move these new commits around and use the fixup command to merge them to the commit immediately above it:
pick 01a25e6 introduce raccoon library fixup 8369783 src/lib/racoon.foo: fixes pick bd197ac modify core to use raccoon fixup 108a931 src/core/core.foo: fixes pick 02890e3 add --raccoon option to the CLI fixup a3c4e42 src/cli/foobar: fixes pick fab9379 add documentation on raccoon features # Rebase 01a25e6..fab9379 onto cb370a2 (7 commands) # # Commands: # p, pick= use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # ...
Save, exit, and we're done! But a word of warning: when moving commits around make sure there are no other commits that change the same part of the file in between your "fixes" commit and the one you're squashing it into. When in doubt, Gitk and similar tools make it easier to check this before you jump into squashing commits.
If everything went well, our history now looks like this:
8370e83 introduce raccoon library 038c5a3 modify core to use raccoon bb9783a add --raccoon option to the CLI fab9379 add documentation on raccoon features
The SHA hashes of the commits have changed, because they now contain the fixes merged into them, and the separate catch-all "fixes" commit is now gone for good!
Of course this is a bit of an ideal scenario where each file goes neatly into a separate commit. Sometimes changes made to a single file belong in separate commits. In those cases, the solution is a bit more manual, using edit and then git add -p, which is super useful.
And remember, if any moment you messed up, git reflog is your best friend! But this is a topic for another time. Cheers!
Follow
🐘 Mastodon ▪ RSS (English), RSS (português), RSS (todos / all)
Last 10 entries
- A Special Hand
- How to change the nmtui background color
- Receita de Best Pancakes
- That time I almost added Tetris to htop
- Receita de Orange Chicken
- Receita de frango empanado no panko
- Receita de cebola caramelizada
- Receita rápida de crepe
- Finally upgraded FlatPress
- Sobre o boom das commodities