DEV Community

Jesse Phillips
Jesse Phillips

Posted on • Updated on

Interactively Rebase with git

To find out why I think you should be rebaseing interactively read my first Dev.to post.

I like to write articles using a different perspective than what I used to learn or read. It probably goes along with all my other tools not commonly used. I think I found a way to cover this topic differently.

What is an interactive rebase?

It is a program, using a domain specific language with very limited scope.

Domain of Interactive Rebase

The interactive rebase operats with the domain of commits. What is a commit?

Commits consist of changes to content, author, committer, and parents. The primary two which are manipulated are the content changes and parent.

Script Structure

The script is interpreted from top to bottom. There is no control flow or looping.

The script allows for execution pause, where the user can modify files and the script itself in the middle of script execution.

What Commands are in this Language

When interactive rebase starts you are presented with the script in a text editor. At the end of the script is a comment which lists all the commands and their description.

Concider the operations utilized when committing to git. A general workflow could be as follows.

  1. Modify files
  2. Add files to git
  3. Commit files to git

e: edit - this command pauses execution after commit.

What this means is that git will write the commit to its database and await further instructions. This is most useful when combined with amend (a way of modifying the last commit).

r: reword - this command pauses at commit.

What this means is that git will present a text editor with the commit message. You can now modify the message associated with the changes.

Note here that reword pauses earlier than edit however edit is used to modify files which is an earlier step than committing. Understanding amend is important to use edit at its full power.

p: pick - this command will utilize the entire commit and continue execution of the script.

b: break - this is inserted on its own line and just stops scrip execution. Use this if you are wanting to change something before the first commit of your rebase takes place, like if you want to amend a merge commit you're rebaseing onto.

Fixup and Squash

I wanted to pause and call out these next two commands. I believe these are the the most important as they make history rewrite something which can be delayed.

f: fixup - this command combines the changes of this commit, with the previous commit.

Recall that the scrip execution is from the start of the file, this means fixup is combined upward in the file.

s: squash - this command pause on the commit message after combining the changes of this commit, with the previous commit. It would be like fixup-reword if commands could be combined on a commit.

I will stop here, there are additional commands which I have not made use of.

Hidden Commands

These are not so much commands but script modifications which are useful.

Change the parent commit. This is just a weird way of saying apply a commit in a different order. All you need to do is move the commit line in the order you desire, this functionality is critical to using fixup appropriately.

d: drop- this command removes the commit from history. It can also be done by removing the line from the script.

Fixup Commit

I keep coming back to fixup because it is so useful.

git commit --fixup <hash>
Enter fullscreen mode Exit fullscreen mode

This creates a new commit but it will not ask for a message, instead the commit is an instrument to fixup the commit at the provided hash.

For more details and alternatives to read autosquashing git commits. Hint :/

The last key piece to using this is --autosquash.

git rebase -i --autosquash origin/master 
Enter fullscreen mode Exit fullscreen mode

This tells git to move the fixup and squash commits adjacent to the commit they operate on. Or configure it

git config --global rebase.autosquash true
Enter fullscreen mode Exit fullscreen mode

This tool is critical in making merge just a little easier to merge or even rebase.

Squash it All

Sometimes commits build up as you work through a problem and it is not orderly. In this case breaking apart commits may be easier by starting from the beginning.

git reset origin/master 
Enter fullscreen mode Exit fullscreen mode

This will leave your working tree untouched and nothing committed.

Now it is time to employ a partial commit to build out the more descriptive commits.

Discussion (2)

Collapse
stevetaylor profile image
Steve Taylor

The problem with interactive rebasing is that you’re replaying your commits one at a time. When one of your past commits can’t be merged automatically due to conflicts, you’ll be resolving conflicts from an old commit and it will be difficult because you have moved on since then. Subsequent commits are likely to be conflicted as well. It ends up becoming merge hell.

Fortunately there’s an alternative to interactive rebase when you want to squash all your commits into one and rebase on the latest mainline branch commit (assuming develop is the mainline branch):

git fetch
git merge origin/develop

You may have conflicts at this point, but they will be conflicts between your latest work and latest develop commit. If so, resolve them and git commit.

Now do the following:

git reset --soft origin/develop
git commit -m "<your commit message>"

And now you’re exactly one commit ahead of origin/develop.

Collapse
jessekphillips profile image
Jesse Phillips Author

But when you are using rebase to have self contained commits (target a single objective) you end up with fewer conflicts on top of your commits. You still get conflicts, but they're a lot easier to deal with.

Refactoring creates the biggest source of conflict management. But these are things you want to be short lived. It encourages that whole CI thing. It can be a pain, but don't avoid refactoring code you work on.

If you have moved on from your old commits you aren't following good CI practices, I think what you may be referring is that you have identified the direction you're actually taking and your latest approach supercede those other commits. In which case, your squash suggestion is appropriate.

But im generally not that disaplined