Icon for gfsd IntelliJ IDEA

How to split a commit with Git?

If you are not fluently rebasing commits, splitting a commit can be the hardest part of your day. Let's change that.

At Symflower we love rebasing commits. This is not a result of us enjoying the suffering of our team members that are not yet fluent with Git, but it makes development, debugging problems and especially reviewing changes enormously easier. However, everyone that ever tried Git knows that changing commits can be difficult. Just mentioning the word rebase often leads to sweat-soaked memories for some people. Naturally, we are adding new tools and aliases to our development environment to make working with Git easier and faster. However, one scenario is still frowned upon: splitting a commit. Let’s change that frown, upside down.

The running example

Git practice requires us to have a running example. The following commands will create a new Git repository and initialize it with some commits that we want to change:

mkdir foobar
cd foobar
git init
touch init.txt
git add init.txt
git commit -m "Init"
echo -e "a\n\nb\n\nc\n" > init.txt
git add init.txt
git commit -m "Initial content"
echo -e "a 1\n\nb 2\n\nc 3\n" > init.txt
git add -u
git commit -m "Some numbers for some letters"
echo "Practice makes perfect" > README.txt
echo "Other change" > other.txt
git add README.txt other.txt
git commit -m "Other change"

The commits of the repository now look like this (your revision hashes look differently):

$ git log --oneline
c6720c3 (HEAD -> master) Other change
6dcb896 Some numbers for some letters
ca80f81 Initial content
da02597 Initial commit

This repository gives us three exciting scenarios to practice:

  1. Splitting a commit into new commits.
  2. Splitting a commit to move parts of its changes.
  3. Splitting a commit to move one or more new files.

Splitting a commit into new commits

In this scenario we want to take the commit “Some numbers for some letters” and split each individual chunk (note: in Git’s terminology “chunks” are called “hunks” 🤷‍♂️) of the change into its own commit. This is useful when you do not want to reuse the original commit (message) at all and just want to split its content.

  1. Checkout a clean version of the running example.
  2. Look up the revision (hash) of the “Some numbers for some letters” commit with git log --oneline. Note that we could also use HEAD as a reference since we know it is the second to last commit. However, we want to use revisions in case you need to apply this scenario on a bigger list of commits.
$ git log --oneline
45bc90d (HEAD -> master) Other change
e30becf Some numbers for some letters
10dd988 Initial commit
  1. Now that we have our revision (e30becf), we need to do an interactive rebase: git rebase -i e30becf^. This opens your editor showing two commits:
pick e30becf Some numbers for some letters
pick 45bc90d Other change
  1. Edit the word “pick” of the “Some numbers for some letters” commit to “edit”, save the file and exit.
  2. Now we are in a rebase right at the commit that we want to split. Since we do not want the commit anymore but only its content, we apply a (mixed) reset of the commit: git reset HEAD~ (you can also do git reset HEAD^ in case you prefer it like that)
  3. The changes are now unstaged again. We can now add each individual chunk using git add --patch following Git’s interactive staging and adding individual commits.

That is how you split a commit into new commits while destroying the original commit.

Splitting a commit to move parts of its changes (destructive version)

In this scenario we want to take the commit “Some numbers for some letters” and move exactly the chunk that added the digit “2” to the “Initial content” commit. This is useful when you want to move one or more changes from a commit to another commit, e.g. because you put changes into the wrong commit. However, we will destroy the original commit and need to create it anew. No worries, the commit does not care.

  1. Checkout a clean version of the running example.
  2. Note down the commit message and look up the revision of the “Some numbers for some letters” commit. Then edit it with an interactive rebase (if you are reading this scenario first, take a look at how to do all of that in the first scenario).
  3. Do a (mixed) reset of the commit: git reset HEAD~
  4. Add the lines with the digits “1” and “3” using git add --patch to the staging area.
$ git add --patch
diff --git a/init.txt b/init.txt
index 6affd1e..66b648f 100644
--- a/init.txt
+++ b/init.txt
@@ -1,6 +1,6 @@
-a
+a 1

-b
+b 2

-c
+c 3

(1/1) Stage this hunk [y,n,q,a,d,s,e,?]? s
Split into 3 hunks.
@@ -1,2 +1,2 @@
-a
+a 1

(1/3) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y
@@ -2,3 +2,3 @@

-b
+b 2

(2/3) Stage this hunk [y,n,q,a,d,K,j,J,g,/,e,?]? n
@@ -4,3 +4,3 @@

-c
+c 3

(3/3) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? y
  1. Do a commit with the original commit message you noted down.
  2. Now we have the original commit without the line with the digit “2”. Let’s add that line using git add --patch and commit it in a new commit.
  3. Exit the rebase using git rebase --continue.
  4. Next, look up the revision of the “Initial content” commit and do an interactive rebase on it.
  5. Finally, move the line of the commit with our “2” digit right after the “Initial content” commit and change the rebase command from “pick” to “fixup”. Then save and exit the editor.
pick fd93065 Initial content
fixup 263e32e MOVE THIS COMMIT
pick ccc9f83 Some numbers for some letters
pick 8f699cf Other change

That’s how you split the contents of a commit and move its parts to another commit while destroying and recreating the original commit message.

Splitting a commit to move parts of its changes (non-destructive version)

Looking at the previous scenario, one can ask: “can we do the same without destroying and recreating the original commit?”. Yes, yes we can!

  1. Checkout a clean version of the running example.
  2. Look up the revision of the “Some numbers for some letters” commit. Then edit it with an interactive rebase (if you are reading this scenario first, take a look at how to do all of that in the first scenario).
  3. Interactively reset just the chunk that we want to split out using git reset --patch HEAD~ by following Git’s interactive instructions.
$ git reset --patch HEAD~
diff --git b/init.txt a/init.txt
index 66b648f..6affd1e 100644
--- b/init.txt
+++ a/init.txt
@@ -1,6 +1,6 @@
-a 1
+a

-b 2
+b

-c 3
+c

(1/1) Apply this hunk to index [y,n,q,a,d,s,e,?]? s
Split into 3 hunks.
@@ -1,2 +1,2 @@
-a 1
+a

(1/3) Apply this hunk to index [y,n,q,a,d,j,J,g,/,e,?]? n
@@ -2,3 +2,3 @@

-b 2
+b

(2/3) Apply this hunk to index [y,n,q,a,d,K,j,J,g,/,e,?]? y
@@ -4,3 +4,3 @@

-c 3
+c

(3/3) Apply this hunk to index [y,n,q,a,d,K,g,/,e,?]? q
  1. Now we have the deleting change for the original commit in our staging area, and the change we want in another commit in the non-staged area. Simply commit the deleting change to the original commit using git commit --amend --no-edit.
  2. Add the change we want to have in another commit using git add --patch and commit it.
  3. Exit the interactive rebase using git rebase --continue.
  4. You can now fixup the commit like in the previous scenario into another commit if you want to.

That is how you can split the content of a commit and move its parts to another commit while leaving the original commit intact.

Splitting a commit to move one or more new files.

In this scenario we want to take the commit “Other change” and move the addition of the “README.txt” file to the “Initial content” commit. This is useful when you have added or removed a file in the wrong commit. Don’t worry, it happens! This is almost exactly the same scenario as the previous one that you just went through.

  1. Do exactly the same scenario as previously for the “Other change” commit but when we reset the commit, instead reset a specific file: git reset HEAD~ README.txt.
  2. Continue with adding the removal chunk to the original commit and the new change to its own commit.

That is how you can move one or more new or even deleted files from one commit to another.

What next?

Phew, Git, am I right? Hopefully you enjoyed reading this article on how to split commits with Git and now feel confident enough to brag about your new Git skills to your coworkers and mates. If you are missing a scenario, just let us know at hello@symflower.com . We’d love to hear from you!

Interested in further Git commit tricks? Check out our post about using git-autofixup to effortlessly correct your Git commits! You’re also most welcome to join our newsletter where we post insights into software development and testing.

Psst, another pro tip! Perhaps you would also enjoy Symflower for IntelliJ IDEA, GoLand, Android Studio or VS Code to help with writing and maintaining software tests.

| 2023-08-09