git cherry-drop

A simple way to drop a commit

Today at work, a coworker asked me over Slack how to delete a commit:

  • So I need to exclude a previous commit
  • Do I git cherry-pick and then the commit string?
  • So git cherry-drop abc123
  • Something like that?

Telling him that git cherry-drop doesn't exist, I walked him through how to drop the commit with git rebase -i. But it got me thinking... why not have a git cherry-drop command? A single command is always nicer than going through the whole interactive process.

I use git inject a lot in my workflow, so I thought I'd copy a bit of logic from that to create the cherry-drop alias. As I got going, I realized there was enough code that it was probably cleaner to split it out into a function in ~/.githelpers, and have the alias load that.

After reviewing the --onto option which I rarely use (Pivotal had a very helpful guide), here's what I ended up with:

Added to ~/.githelpers:
cherry_drop() {
  set -e
  REF_TO_DROP=`git show $1 --pretty=format:%H -q`
  HEAD=`git show HEAD --pretty=format:%H -q`
  shift

  # Stash changes if they exist
  if ! git diff-index --quiet HEAD --; then
    git stash && DIRTY=1
  fi

  if [[ $REF_TO_DROP == $HEAD ]]; then
    # Easy way to undo the last commit:
    git reset --hard HEAD
  else
    # Rebase the commit out:
    git rebase --keep-empty --onto $REF_TO_DROP~1 $REF_TO_DROP
  fi

  # Unstash changes if they were stashed:
  [ -n "$DIRTY" ] && git stash pop

  # Great success:
  exit 0
}
The alias:
[alias]
  ..
  cherry-drop = "!. ~/.githelpers && cherry_drop"

In action

Say you're on a branch called foo, and your history looks like this:

$ git log --graph --abbrev-commit --pretty=format:'%h - %d %s' -n4

  * 5b5fa7ecf0 -  (HEAD -> foo) Great commit
  * 205cf5cf76 -  Accidentally commit 1,000 node_modules
  * 01f02a3d6a -  Another good commit
  * 0f0c83af7e -  Good commit

To remove 205cf5cf76, it's a single command:

$ git cherry-drop 205cf5cf76

  First, rewinding head to replay your work on top of it...
  [detached HEAD 4e798f3011] Great commit
  Date: Thu Sep 28 16:54:51 2017 -0600


$ git log --graph --abbrev-commit --pretty=format:'%h - %d %s' -n4

  * 4e798f3011 -  (HEAD -> foo) Great commit
  * 01f02a3d6a -  Another good commit
  * 0f0c83af7e -  Good commit
  * 077f18f7b3 -  Some old Change

Warning

cherry-pick should work to slice out simple commits that don't touch anything else, but...
- If you want to remove a commit that other commits depend on, you'll probably want to avoid this alias and rebase it the usual way.
- If you want to remove multiple commits, it's probably also better to rebase them all out in one go instead of cherry-droping them one-by-one.

Closing

I've added that alias to my dotfiles.

I'll push any future cleanups/fixes there.

Enjoy!

JustinAiken

I'm a Ruby/Rails developer currently working for UserTesting, living in Southern Utah.
More about the author →