A few days ago, I was cleaning my git history for a PR I made at my internship. My commits were quite messy: many parts of the code I’d changed multiple times and the earlier commits no longer reflected the current state.
I knew you could reorder commits, squash, amend commit messages, reset the head, etc. and whatever else you needed for a basic git workflow. Before this PR, though, I had no idea you could split a commit into smaller commits, and you could do this by resetting the head and unstaging / staging specific hunks or lines.
Doing this in the git cli sounded like a very not fun time, so I decided to look at two tools: gitk
and git gui
. Gitk
is quite nice because it visually shows you your git history and you can see each commit’s diffs. Git gui
on the other hand looks like a relic of the past, but it gets the job done…except when it doesn’t. Turns out, if you have a new
file in git, a file that’s untracked, and you want to unstage individual lines, you simply can’t do this with git gui
. You don’t get an error message either; just nothing happens. And you seemingly can’t do this with git in general either.
When would this situation occur? Well, in my case I squashed all my commits together so I could split them up the way I wanted. And because I made a new
file, I just couldn’t unstage lines for that new file. Also imagine you need to unstage/stage a few lines in the commit where you first introduce the new file.
I had already pushed my commits and opened a PR, and I really didn’t want to close that PR and open a new branch, nor did I want to commit the entire new file I wrote in one commit, so I made a somewhat hacky way to be able to unstage individual lines in a new file. It basically involves making that new
file change status to modified
so git gui
can do its thing and your git history stays pristine. There are probably (definitely) more elegant or efficient ways of doing this, but I couldn’t find any solution online, so this is how I did it!
Voila:
- git reset HEAD~ –soft (reset head to the commit before the one you want to split)
- OR git rebase -i <parent_of_commit_to_split> and change the
pick
toe
for the commit you want to split, then reset the head~ (and after you’re done splitting the entire commit you can git rebase –continue) - git status (will show you
new file: ........
) - git reset
- git add -N .
- git add -p
- for the hunk you want to edit: e
- remove the lines you don’t want to stage for the new commit
- git add new_file
- git commit -m “??”
- on the file itself, remove everything out of the file except the lines you want to stage (SAVE that code you removed bc you’ll need it later)
- git add new_file
- git status (will show you the file is now
modified
) git gui
- unstage those lines and make your first split commit!
- git rebase -i <parent of ?? commit>
- squash the commit you made in step 14 into
??
(and reword the commit as needed) - now add back those lines you removed in step 7 into the file
- now git status should just show modified so you can split the rest as normal :)