Git Interactive Rebase to Alter Commits

in #programming6 years ago

Do you have skeletons in your git history? Maybe you want to re-write that history? I know I've been down that road, and since I've had to look up how to do this several times I thought it was high time to do a quick post.

Erase-The-Past.jpg

For the uninitiated, git is powerful version control software written by Linus Torvalds, who is most famous for writing the Linux kernel. If you are not familiar git, or version control this post will fly over your head.

Poem

Let's start writing a poem that we want under version control. Create a directory with an empty file:

> mkdir -p poem-git-example/poem && cd poem-git-example

Now let's write some poetry:

# poem
Hypocrite lecteur, — mon semblable, — mon frère!

Okay, so far we only have a one-line poem, but it looks like a classic. Let's commit our work:

> git init
> git add .
> git commit -m 'initial commit of poem'

[master (root-commit) 5254598] initial commit of poem
 1 file changed, 1 insertion(+)
 create mode 100644 poem

Let's make some changes. I want to add a title to the poem:

Les Fluers du Mal

Hypocrite lecteur, — mon semblable, — mon frère!

Better put that under version control, too.

> git add .
> git commit -m 'add title'

You should now have two commits. Let's just add one more thing:

Les Fluers du Mal

Hypocrite lecteur, — mon semblable, — mon frère!

- Charles Baudelaire

Okay, so its not mine. In fact, its not even part of the poem I stole it from. Its just one line from the preface but I like it so what? This is about git, so stay on task.

Commit that last change: git add . && git commit -m 'add signature'. We now have three commits and a one-line poem, complete with title and signature (^̮^) But wait! looking back at our poem we have made an embarrassing typo. It should be Fleurs not Fluers! I could just correct the error and commit the changes and move on with my life. If this weren't a contrived example, I would do just that, but this is a contrived example.

So, let's just say we are embarrassed that we left that punctuation out. We don't want that in our git history. What we'd like to do is go back to the original commit, amend the contents of that commit, leave the additions of the title and signature in place, and then carry on.

There are two main ways to integrate changes from one branch into another using git: merging and rebasing. If you are unfamiliar with rebasing you should definitely read up about it. It's a great feature and nobody's git-fu is complete without the rebase. I'll be assuming you have experience with rebasing going forward.

What we really need here to achieve our aims is something called the interactive rebase. We have three total commits, and we need to go back to that second commit where we added the misspelled title. We can use treeish to walk back two commits inclusive from HEAD:

git rebase --interactive HEAD~2

# can also do:
# git rebase -i HEAD~2

# or if the second commit has a SHA: f3626e9
# git rebase -i 'f3626e9^'

This will open an interactive prompt in your default text editor, or the one you defined in your git config. It should look something like this:

pick f3626e9 add title
pick 4bef291 add signature

# Rebase 5254598..4bef291 onto 5254598 (2 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Take note of the helpful comments. Basically, we need to identify the commit we want to edit, change the word pick to edit, and then save the file. If we leave a commit as pick we'll use the changes from that commit as they are. If we say edit we'll be allowed to go make our edits, and rewrite history.

pick f3626e9 add title
edit 4bef291 add signature

# Rebase 5254598..4bef291 onto 5254598 (2 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Save that and you should see some output like this:

Stopped at 4bef291...  add signature
You can amend the commit now, with

  git commit --amend

Once you are satisfied with your changes, run

  git rebase --continue

This means we are free to make our changes. Open up poem and fix that typo Fluers -> Fleurs:

Les Fleurs du Mal

Hypocrite lecteur, — mon semblable, — mon frère!

- Charles Baudelaire

Now, we need to remember what our prompt was telling us. We can amend the commit with: git commit --amend. That's great and will open another prompt in your editor and ask you to update the commit message. In our case, we just want to use the same message, so we're going to add a couple flags to our command to skip ahead:

git commit --all --amend --no-edit

So, we have now updated that second commit with the correct spelling. But if you run git status you'll notice we are in a detached HEAD state. We want to move HEAD back to our third commit where we were. Once again, we can just follow the prompt and run git rebase --continue to return back to the previous commit HEAD.

That's all there is to it! Now, this kind of fancy footwork will end up changing the SHA-1 of that commit and rewrites the history from that point forward. You can break repositories doing things like this, and is best to avoid it when you can. If you have a trunk/master branch that is immutable then you will be blocked from a repo admin from trying something like anyways. Having said all that, I'm sure you can see that this can come in handy in a pinch. Until next time!

| (• ◡•)| (❍ᴥ❍ʋ)