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