/ GIT

Inserting a new commit in the Git history

Most tutorials about Git history rewriting state that history should never ever be rewritten. Like all principles, it depends mostly on the exact context. The principle should probably be updated like this:

Public Git history should not be rewritten

The reason is that once the Git history has been pushed, it has been made public: other developers might have started working on top of it. Then, and only then, is rewriting the history an issue. It also means that sometimes, there are reasons to rewrite the history. This is fine as long as commits have not been pushed.

There are a lot of different ways to alter the history. Among them, the one I use regularly is to insert a new commit. For example, when I’m writing code for a new talk, I try to make it so that it follows a natural progression: each commit is a step I want to demo in my talk. But sometimes, it happens I forget a step. Or that I realize later the step is not what is required for a future step. In order to display a clean history for attendees who want to access the repo, I need to update steps early in the history.

Here are the methods I use, with their respective contexts.

Straightforward rebase

When the commit to be inserted - and the rest of the history - have nothing in common, the easiest and fastest way is to add the steps after, and then rebase interactively.

Consider the following Git tree:

A---B---C---D---E
                ^
              master

To rebase interactively, the git rebase -i command is the one to use:

git rebase -i C^

This displays the following:

pick C  Commit files c1.txt and c2.txt
pick D  Commit files d3.txt and d4.txt
pick E  Commit files e5.txt and e6.txt

With one’s favorite text editor, change the lines order:

pick E  Commit files e5.txt and e6.txt
pick C  Commit files c1.txt and c2.txt
pick D  Commit files d3.txt and d4.txt

The final tree looks like this:

A---B---E---C---D
                ^
              master

Split a single commit in two

Another common use-case is to split a single commit step into two different commit steps. Let’s start from the same Git history as above, and use the same rebase command. This time, however, instead of changing the lines order, one needs to edit the commit to be split:

edit C  Commit files c1.txt and c2.txt
pick D  Commit files d3.txt and d4.txt
pick E  Commit files e5.txt and e6.txt

To move all changes belonging to the C commit back to the working tree:

git reset HEAD^

Now, files can be committed individually as desired:

git add c1.txt
git commit -m "Commit file c1.txt"
git add c2.txt
git commit -m "Commit file c2.txt"

Finally, the interactive rebase should continue:

git rebase --continue

This yields the following result:

A---B---C1---C2---D---E
                      ^
                    master

Branch and rebase

From time to time, rebasing is a bit more complex than the two above examples. In that case, the most important is to avoid wrecking havoc with the history. For that reason, and as a precaution, one should first create a branch at the point of insertion:

git checkout C
git branch -b insert

This gives out the following:

A---B---C---D---E
        ^       ^
     insert  master
      HEAD

Now, it’s possible to add an additional commit (or several):

touch insert.txt
git add insert.txt
git commit -m "Commit file insert.txt"

This is the resulting tree:

A---B---C---D---E
         \      ^
          \   master
           \
            N
            ^
          insert

Finally, let’s move the master branch on top of the insert branch that contains the newly-inserted commit. It requires another option with the rebase command, namely --onto: this allows to "pluck" a part of the Git tree from a commit point, to "plant" it on top of another commit.

git checkout master
git rebase --onto insert C      (1)
1 Get the commits between the current branch (master) and C, and move them on top of insert

This is the resulting tree:

A---B---C
         \
          \
           \
            N---D---E
            ^       ^
          insert  master

Or looking at it in another way:

A---B---C---N---D---E
            ^       ^
          insert  master

Ideally, the insert branch should now be deleted, though it’s not strictly necessary:

git branch -d insert

Conclusion

Git is a huge beast: it allows a lot, and in many different ways. To insert a commit in the history, I showed 3 alternatives I regularly use:

  • Simple interactive rebase with a reordering of the commits
  • More comple interactive rebase with the split of an existing commit into several different ones
  • Rebase onto

There might be more. Know your options, and use the one that best fits your use-case.

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Hazelcast. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More