Split Up One Old Git Commit Into Several
I’ve never had to reach this far back in the git history of a project to change things before, but it can be done!
The problem: I ran several tools very early in development on a repository and committed the changes all together. But the project’s code standard required each step to be its own commit…which I hadn’t noticed until it was pointed out during a review ~70 commits later.
The goal: Replace that old commit with three separate commits, one for each tool’s changes, and keep the rest of the history intact.
The solution: Roll back to the previous commit before the problem one. Run the tools again, this time committing the changes after each tool. Reattach the rest of the history on top of those commits.
I figured git rebase was going to be involved, so I went digging, and while I didn’t find anything that was quite what I was looking for, I did find several references that helped me put it together.
NOTE: if each change you want to extract is in a different set of files, you’ll want to use this simpler approach at Code With Hugo.
Anyway, the approach I took is broadly described on Stack Overflow, filled in using Git Tools: Rewriting History. And so:
First: Create the new commits.
Make a backup! Push to a remote, copy the folder, whatever, just make sure you have a way to get back if you mess this up, and a way to compare the final result and make sure everything got included correctly.
Go back to the last good commit before the one you want to change.
git checkout lastgood-hash
- Create a new branch
git checkout -b separating-my-commit
Redo all the steps, committing as you go.
Compare the result to the original commit and make any changes you missed
git diff commit-to-split-hash
Second: Revise your history.
- Go back to your main or master branch
git checkout main
- Start the Rebase interactively at the previous commit.
git rebase -i lastgood-hash
This opens up a text editor with a list of every commit back to lastgood. The ones marked “pick” will be kept.
- Tell Rebase to edit the commit you want to replace by changing the line so it starts with “edit” and saving the file.
pick 1234abcd some other change pick abcd1234 the next commit after the one you're replacing edit zxcv9876 the commit you want to replace pick asdf6543 the last good commit
- Unstage the commit you’re changing.
git reset HEAD ^
- Cherry-pick each new commit you made on the alternate branch, in order. Your local directory will have the end result, not yet committed, so you can avoid merge conflicts either by rolling back the actual folder to the previous commit (which now that I think of it is probably a better approach anyway), or you can tell cherry-pick not to try to merge any local changes.
git cherry-pick --strategy-option theirs new-commit-hash
- Once you’ve gotten through all of the changes, tell Git to continue with the rebase.
git rebase --continue
Hope there aren’t any errors.
Compare the current branch to the backup version. The result should be the same, even though the history has changed!
git diff origin/main
- Overwrite the remote repository.
git push --force
- Apologize to anyone working off of the same remote repository because you just made things more complicated for them.
Why Would You Do This???
If it’s not too far back, you might want to break up the commit so that you could pull one of them into a new branch. I can’t think of a good reason other than coding standards for going this far back, and in fact, by the time I finished, there was already a followup message saying not to worry about it under the circumstances.
Mainly I wanted to see if I could. It was a pain, but it didn’t take as long as I expected, and the fact that it actually worked is kind of cool.
On the off-chance that someone else ever needs to do this, maybe this will help them.