You can split commits.

Warren MarkhamWarren Markham
6 min read
You will get value from this article if you want to split a single commit into multiple commits but don't know how. Furthermore, if this article is your first introduction to the git rebase -i command, it begins your journey into some very important tooling: interactive rebasing.
💡
Interactive rebasing helps you rewrite your local history. It can help you change the order of commits, their messages and the files they modify, as well as squash commits together, split commits apart or remove commits entirely.
💪
If you develop skills in rewriting your local history, you can increase the quality and relevance of the commit histories you share with others. You can also increase the intelligibility and rationality of your own local work.

The workflow

This workflow shows you how to split an arbitrary commit, <commit hash>. It does not matter how far back in your history this commit occurred.

The meat of the split commit workflow this article outlines is a Git operations loop: add a changeset, commit the changeset, stash all other changesets, test the isolated changeset, commit any fixes required to make the changeset stable and correct, pop the excluded changesets back onto the working tree.
💡
The Git operations loop demonstrated in this article is not only relevant when splitting commits. It is relevant whenever you have a busy working tree and you want to do controlled, tested and stable commits.

A basic outline

💬
A distilled explanation of the above workflow follows. It might be enough to get you started too. But if you would like a bit more detail, read the next section. If you would like additional elaboration and context, make a request in the comments. I'm happy to expand on the article as needed.
  1. git rebase -i <commit hash>^

  2. change the action for <commit hash> from pick to edit

  3. close the git-rebase-todo editor session

  4. git reset HEAD^

  5. iterate until working tree is clean:

    1. git add

    2. git commit

    3. (optionally) isolate and test changeset:

      1. git stash

      2. execute measures of quality/correctness

      3. commit the fixes to any issues identified

      4. git stash pop

  6. git rebase --continue


A more detailed outline

Ensure the defined range includes the commit to split

1. From the command line, execute the interactive rebase script. Since we want to split <commit hash>, we must ensure it is within the range of the rebase operations. We do that by passing the last commit we want to retain as-is as an argument to the script: git rebase -i <commit hash>^ .This will mean that <commit hash>'s parent lies outside of the range - but crucially, <commit hash> will not.

When used after a commit hash, such as with the expression <suffixed_commit>^, the caret symbol (^) changes the reference to the parent's commit hash. That is, the expression <suffixed commit>^ now refers to the commit that was done before <suffixed commit>. Visually, this is the commit that appears immediately after or below <suffixed commit> in the git log.

Use the editor to nominate which commits to split

2. In the git-rebase-todo file that opens in your editor, mark <commit hash> (and any other commits you want to split) for editing by replacing the "pick" action with the "edit" action. The rebase script will stop on these commits and await your Git commands.


Close git-rebase-todo to execute the listed commands

3. Close the git-rebase-todo file to continue the rebase script's execution.


Use Git operations to modify the commit

4. When the rebase operation stops at <commit hash> and gives you a command line prompt, use Git operations to split the commit into subcommits.

a. Use git reset HEAD^ to rewind <commit hash> to its parent. This puts all changes introduced by <commit hash> back on to the working tree and resets the index (so that there a no staged changes).

When doing interactive rebase operations, Git puts you in a detached HEAD state on stop. Consequently, the commit which the rebase script has stopped on can be accessed via HEAD.

b. Use a staging technique (such as git add) to add the next changeset you want to do a subcommit with to the index.

c. Use a commit technique (such as git commit -m <type: message>) to commit the changeset.


💡
Optionally, during this Git operations step, you can make sure each subcommit is tested and stable by isolating its snapshot from the uncommitted changesets. If it fails a measure of stability and quality, you then correct the issue with an appropriate fix: <message> commit.

d. Use git stash to temporarily move the uncommitted changes off the working tree. This has the effect of isolating the subcommit from all the other changes that were reset onto the working tree by git reset HEAD^.

e. Test the current state of the repository against the measures of stability and quality you use. For example, you can see if the code compiles or if a test suite passes.

f. Use git stash pop to undo git stash and move all the other changes (the changesets that have not been committed yet) back onto the working tree.


g. Iterate steps b through f (add through pop) until the working tree is clean.

h. When the working tree is clean, use git rebase --continue at the command line to advance the rebase operation to the next "edit" action in the git-rebase-todo file.


Apply the Git Operations loop to each nominated commit

5. If you marked other commits with "edit", the rebase operation will stop on them after git rebase --continue; if it does, go to step 4a (git reset HEAD^). Do until done.


👋
Hello, I'm Warren. I've worked in an AWS Data Engineer role at Infosys, Australia. Previously, I was a Disability Support Worker. I'm interested in collaborative workflows and going deeper into TDD, automation and distributed systems.
📆
I am currently studying C at Holberton School Australia.
🐴
"Holberton School Australia is a tech school that trains software engineers through a collaborative, project-based curriculum. Over the course of 9 months, our students learn how to walk, talk, and code like software engineers."
0
Subscribe to my newsletter

Read articles from Warren Markham directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Warren Markham
Warren Markham

I've worked in an AWS Data Engineer role at Infosys, Australia. Previously, I was a Disability Support Worker. I recently completed a course of study in C and Systems Programming with Holberton School Australia. I'm interested in collaborative workflows and going deeper into TDD, automation and distributed systems.