Skip to content

Interactive Rebasing

David Zurow edited this page Oct 16, 2021 · 4 revisions

The Use Case

Let's start with a simple use case: you want to add a new file to an old commit (with an id of ef1b2b5).

The Git Approach:

Step 1

obtain the id of the commit and begin an interactive rebase like so:

git rebase --interactive ef1b2b5^

The ^ at the end of the commit is to say that you actually want the base of the rebase to be the parent of ef1b2b5, because the base of the rebase itself can't be edited.

This command will bring up a todo file in your editor that describes what the rebase intends on doing:

pick ef1b2b5 README: append info about logs in Docker to FAQ section
pick 817fb53 ci: gox, vendor and you
pick ef1f0e0 blah
pick 29b1b27 blah blah

# Rebase dc8a624..29b1b27 onto dc8a624 (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# 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. At the start of each line is a keyword describing what the rebase will do with a commit.
#
# Note that empty commits are commented out

Step 2

In this case we have three commits that follow commit ef1b2b5. We don't care about those though, we only want to edit ef1b2b5, so we need to change that first 'pick' to an 'edit':

edit ef1b2b5 README: append info about logs in Docker to FAQ section
pick 817fb53 ci: gox, vendor and you
pick ef1f0e0 blah
pick 29b1b27 blah blah

Step 3

Now we can save the file and the rebase will begin. At any time during the rebase we are able to change the todo file.

We get this helpful message from git telling us what to do:

Stopped at ef1b2b5...  README: append info about logs in Docker to FAQ section
You can amend the commit now, with

  git commit --amend

Once you are satisfied with your changes, run

  git rebase --continue

Step 4

Add the new file like so:

echo "file content" > myfile.txt

Step 5

Stage the file and amend commit ef1b2b5 to now have that file with:

git add myfile.txt
git commit --amend --no-rename

you'll be prompted to edit the message if you want but there's a good chance you'll just leave it as-is.

Step 6

Finish up by continuing the rebase like so:

git rebase --continue

If we hit merge conflicts due to your new changes not being compatible with one of the three more recent commits than ef1b2b5 then you'll need to resolve them and then continue again with git rebase --continue.

And then you're done :) Suffice it to say that such a longwinded process is unnecessarily painful.

The Lazygit Approach (long way):

Let's consider the general way of interactively rebasing in lazygit. This will mirror the git approach.

Step 1

Open lazygit in your command line and navigate to the commits panel either by clicking on it, pressing '4' or hitting the right arrow twice.

Step 2

Use the down arrow key to select commit ef1b2b5 and then press 'e' to edit it.

It's worth noting here what is going on. When you hit 'e' on commit ef1b2b5, an interactive rebase was started based on ef1b2b5's parent, with 'edit' being chosen for ef1b2b5 and 'pick' for the other three commits. Those 'picks' that you now see in lazygit correspond to the entries in the todo file for those commits. The actual HEAD pointer is at commit ef1b2b5 hence the 'YOU ARE HERE' arrow.

Also worth noting that the order of commits shown in lazygit is the reverse of the order in the todo file.

Step 3

Create the new file with echo "file content" > myfile.txt and open lazygit again.

Step 4

Stage the file by hitting space and amend the commit by hitting shift+A

Step 5

hit enter to confirm that you want to amend the commit, then flick to the commits view again to verify that the file has been added to that commit

Step 6

Continue the rebase by pressing 'm' to bring up the rebase options and then hitting enter on 'continue'

And you're done! Note that our commit has a new ID because it's got new changes, and all the following commits also have new IDs because they have a new ancestor.

The Lazygit Approach (short way):

If all you want to do is add a new file to an old commit, there is a faster way.

Step 1

Create the new file with echo "file content" > myfile.txt and open lazygit.

Step 2

Stage the file by hitting space then navigate to the commits panel and then the commit we want to amend

Step 3

Amend the commit directly by pressing shift+A and hitting 'enter' on the popup confirmation. This will begin the interactive rebase in the background, commit the changes, and then continue the rebase.

And you're done!

Editing The TODO File

The long way described above is not necessary for our use case given the simplicity of the short approach however it does allow you to edit the todo file. By selecting one of the blue commits and pressing one of 's', 'd', 'e', 'p', 'f', you can set the values in the todo file to 'squash', 'drop', 'edit', 'pick', and 'fixup' respectively:

then once you continue the rebase (again using 'm' to bring up the rebase options and hitting enter on 'continue') the commands will be executed by git. You can also reorder the commits in the todo file by selecing one and pressing ctrl+K to move it up and ctrl+J to move it down:

Shorthand Commands

If you only need to perform one operation on a single commit, using this todo-approach is probably unnecessary. For example, if you want to squash a commit into the commit below, you can just hit 's' on a commit while not in rebase mode and hit enter on the confirmation panel

Behind the scenes this begins an interactive rebase, creates the todo file, sets the command to 'squash', and continues. The same short-hand approach is supported for dropping, squashing, fixing up, and rewording commits, using the 'd', 's', 'f', and 'r' keybindings respectively. You can even shift a commit up one with ctrl+K and down one with ctrl+J

Conflicts

Sometimes you will get merge conflicts after continuing. When this happens you'll get a prompt asking you whether you want to abort (by pressing escape) or resolve the conflicts (by pressing enter).

If you choose to resolve the conflicts, it can be done as you normally would with lazygit (or with your editor) and once they're resolved, a message should appear stating that all conflicts are resolved, and asking whether you'd like to continue.

If you hit enter on this, the rebase will be continued. But if not, if you want to continue it later, you can do so via the rebase options with 'm' again.