Changing history with git rebase: How to combine several commits into one

June 10, 2009
When I look at commits on github I'm always impressed at how concise they are. When I read a commit I can understand the intent of the change without getting distracted by the author's journey to get there. In contrast when I look at my commits they tend to be smaller and more incremental and meandering as I work my way down some false starts until I get to the solution I want. I'm guessing that I'm not alone in the way I work and recently discovered git rebase and an helpful tutorial showing how I can continue to work in my meandering style but package my changes to hide the journey before publishing to the world on github.

Let me show you what I mean with some changes I recently made to metric_fu. Over the course of a few days I made 6 small commits as you can see below.


> git log

commit d4b18b16e982ac57741f7a0a12cb085bb9b0e840
Author: Alex Rothenberg
Date: Mon Jun 1 09:55:37 2009 -0400

reverted rakefile

commit ebda1cb67a2f0f0a85e51469d70919fa7c27d318
Author: Alex Rothenberg
Date: Mon Jun 1 09:49:05 2009 -0400

refactoring of verify_dependencies

commit 3d45903b64a772fd09ec07bf69880bdb29ae4944
Author: Alex Rothenberg
Date: Fri May 29 20:40:49 2009 -0400

runtime dependency check for all gems

commit 36e269bf8f0edccfb39cc767182406cdaa16a559
Author: Alex Rothenberg
Date: Fri May 29 20:24:16 2009 -0400

logic for checking dependencies in generator base

commit bb2ee9a983c6adf54a8c95ee5851c6f8d3bffaba
Author: Alex Rothenberg
Date: Fri May 29 19:38:21 2009 -0400

made rcov dependency figure itself out when generating rcov metrics

commit 750b000e5563e917f21eb1b7837e8001fb53f688
Author: Alex Rothenberg
Date: Fri May 29 16:42:12 2009 -0400

removed gem dependency on rcov - to allow use of relevance-rcov or other github forks

commit d6af5089adce9eeed4916a155c3bdaeb4be6771a
Author: Randy Souza
Date: Sat May 16 08:47:37 2009 +0800

Added a simple fix for cases where Saikuro results with nested information
cause metrics:all to crash

Signed-off-by: Jake Scruggs

...


I am going to combine all of these into a single commit using the incredible power of git rebase. I find the last commit I do not want to change (the one made by Randy Souza on May 16th) and issue a git rebase command with that id.


> git rebase --interactive d6af5089adce9eeed4916a155c3bdaeb4be6771a


Now my editor comes up showing the 6 changes since then giving me options of what to do. Its very powerful, I can reorder commits, combine commits, eliminate commits. I'm going back in time to change the past!


pick 750b000 removed gem dependency on rcov - to allow use of relevance-rcov or other github forks
pick bb2ee9a made rcov dependency figure itself out when generating rcov metrics
pick 36e269b logic for checking dependencies in generator base
pick 3d45903 runtime dependency check for all gems
pick ebda1cb refactoring of verify_dependencies
pick d4b18b1 reverted rakefile

# Rebase d6af508..d4b18b1 onto d6af508
#
# Commands:
# p, pick = use commit
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#


In my case I want to combine these into a single commit so I change all but the first pick command to a squash and save.


pick 750b000 removed gem dependency on rcov - to allow use of relevance-rcov or other github forks
squash bb2ee9a made rcov dependency figure itself out when generating rcov metrics
squash 36e269b logic for checking dependencies in generator base
squash 3d45903 runtime dependency check for all gems
squash ebda1cb refactoring of verify_dependencies
squash d4b18b1 reverted rakefile

# Rebase d6af508..d4b18b1 onto d6af508
#
# Commands:
# p, pick = use commit
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#


It now gives me a chance to edit the new commit message which it defaults to the original messages concatenated together. This really is a single commit so if one of the original commits changed a file and a subsequent one undid the change that file will no longer appear in the list of modified files.



# This is a combination of 6 commits.
# The first commit's message is:
removed gem dependency on rcov - to allow use of relevance-rcov or other github forks

# This is the 2nd commit message:

made rcov dependency figure itself out when generating rcov metrics

# This is the 3rd commit message:

logic for checking dependencies in generator base

# This is the 4th commit message:

runtime dependency check for all gems

# This is the 5th commit message:

refactoring of verify_dependencies

# This is the 6th commit message:

reverted rakefile

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Explicit paths specified without -i nor -o; assuming --only paths...
# Not currently on any branch.
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: Rakefile
# modified: lib/base/generator.rb
# modified: lib/generators/flay.rb
# modified: lib/generators/flog.rb
# modified: lib/generators/rcov.rb
# modified: lib/generators/reek.rb
# modified: lib/generators/roodi.rb
# modified: lib/generators/saikuro.rb
# modified: metric_fu.gemspec
# modified: spec/base/generator_spec.rb
# modified: spec/generators/flay_spec.rb
# modified: spec/generators/flog_spec.rb
# modified: spec/generators/reek_spec.rb
#



Let's edit this commit message to read something like


Changed gem dependencies from install-time in gemspec to runtime when each
of the generators is loaded. This allows use of github gems (i.e.
relevance-rcov instead of rcov) and also allows you to install only the
gems for the metrics you plan on using.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Explicit paths specified without -i nor -o; assuming --only paths...
# Not currently on any branch.
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: Rakefile
# modified: lib/base/generator.rb
# modified: lib/generators/flay.rb
# modified: lib/generators/flog.rb
# modified: lib/generators/rcov.rb
# modified: lib/generators/reek.rb
# modified: lib/generators/roodi.rb
# modified: lib/generators/saikuro.rb
# modified: metric_fu.gemspec
# modified: spec/base/generator_spec.rb
# modified: spec/generators/flay_spec.rb
# modified: spec/generators/flog_spec.rb
# modified: spec/generators/reek_spec.rb
#


And that's it! Now when we check our history we see.


> git log

commit 6f389364eb972871867d3a71677b8eb7046541a2
Author: Alex Rothenberg
Date: Fri May 29 16:42:12 2009 -0400

Changed gem dependencies from install-time in gemspec to runtime when each
of the generators is loaded. This allows use of github gems (i.e.
relevance-rcov instead of rcov) and also allows you to install only the
gems for the metrics you plan on using.

commit d6af5089adce9eeed4916a155c3bdaeb4be6771a
Author: Randy Souza
Date: Sat May 16 08:47:37 2009 +0800

Added a simple fix for cases where Saikuro results with nested information
cause metrics:all to crash

Signed-off-by: Jake Scruggs

...


And this commit is how I pushed this patch to github for all the world to see.