Usually when I need to delete a commit from a local git repo I use:

git rebase -i

This starts an interactive rebase session for all commits from HEAD..@{u}. Once I’m in the git rebase editor I put a drop on the line that has the commit I want to delete and save the file. Magic. Commit gone.

drop c572357 [LOCAL] Change to be removed

# Rebase dbfcb3a..c572357 onto dbfcb3a (1 command(s))
#
# 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

This is fine if you’re there to drive an editor for an interactive rebase.

The problem with interactivity

The problem with interactive rebase is you can’t easily script it.

At work I help to maintain our “beta cluster” which is a bunch of virtual machines that is supposed to mirror the setup of production. The extent to which production is actually mirrored is debatable; however, it is true that we use the same puppet repository as production. Further, this puppet repository resides on a virtual machine within the cluster that acts as the beta cluster’s puppetmaster.

Often, to test a patch for production puppet, folks will apply patches on top of beta’s local copy of the production puppet repo.

root@puppetmaster# cd /var/lib/git/operations/puppet
root@puppetmaster# git apply --check --3way /home/thcipriani/0001-some.patch
root@puppetmaster# git am --3way /home/thcipriani/0001-some.path
root@puppetmaster# git status
On branch production
Your branch is ahead of 'origin/production' by 6 commits.
  (use "git push" to publish your local commits)

nothing to commit, working directory clean

The problem here is that a lot of these commits are old and busted.

root@puppetmaster# git log -6 --format='%h %ai'
ad4f419 2016-08-25 20:46:23 +0530
d3e9ab8 2016-09-05 15:49:48 +0100
59b7377 2016-08-30 16:21:39 -0700
5d2609e 2016-08-17 14:17:02 +0100
194ee1c 2016-07-19 13:19:54 -0600
057c9f7 2016-01-07 17:11:12 -0800

It’d be nice to have a script that periodically cleans up these patches based on some criteria. This script would have to potentially be able to remove, say, 59b7377 while leaving all the others in-tact, and it would have to do so without any access to git rebase -i or any user input.

Non-interactive commit deletion

The way to do this is with git rebase --onto.

Selective quoting of man git-rebase

SYNOPSIS
    git rebase [--onto <newbase>] [<upstream>]

DESCRIPTION
    All changes made by commits in the current branch but that are not
    in <upstream> are saved to a temporary area. This is the same set
    of commits that would be shown by git log <upstream>..HEAD

    The current branch is reset to <newbase> --onto option was supplied.

    The commits that were previously saved into the temporary area are
    then reapplied to the current branch, one by one, in order.

The commits we want are all the commits surrounding 59b7377 without 59b7377.

root@puppetmaster# git log --format='%h %ai' 59b7377..HEAD
ad4f419 2016-08-25 20:46:23 +0530
d3e9ab8 2016-09-05 15:49:48 +0100
root@puppetmaster# git log --format='%h %ai' @{u}..59b7377^
5d2609e 2016-08-17 14:17:02 +0100
194ee1c 2016-07-19 13:19:54 -0600
057c9f7 2016-01-07 17:11:12 -0800

So we really want to take the commits 59b7377..HEAD and rebase them onto the commit before 59b7377 (which is 59b7377^)

root@puppetmaster# git rebase --onto 59b7377^ 59b7377
root@puppetmaster# git log -6 --format='%h %ai'
ad4f419 2016-08-25 20:46:23 +0530
d3e9ab8 2016-09-05 15:49:48 +0100
5d2609e 2016-08-17 14:17:02 +0100
194ee1c 2016-07-19 13:19:54 -0600
057c9f7 2016-01-07 17:11:12 -0800
d5e1340 2016-09-07 23:45:25 +0200

Tada! Commit gone.