Option with rebase + drop
As Romain suggested "rebase is another", here is one way to do that, assuming you do want the end result of @-A-B-C-D-E
to be @-E
, as Lasse asked.
I offer this just as another tool in your toolbelt: it is not the simpler solution to this problem. It does however allow you to delete commits that are not in sequence (delete A, C, E and preserve B, D, for example):
git rebase -i HEAD~6
which will open your editor (probably vi
) with a buffer that looks like this:
pick 4231648cb4 Some previous commit
pick 4bccf2ce81 A some message
pick 7b4cd5ff17 B some message
pick faa44efb7c C some message
pick 0ce0525a79 D some message
pick f104648cc3 E some message
# Rebase 76eb9131b5..ed71142fcb onto 4231648cb4 (6 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.
#
# Note that empty commits are commented out
Yes, the order of the commits from top to bottom is in reverse temporal order (the reverse of git log
) with the most recent at the bottom. That's why "lines are executed from top to bottom" - from the oldest to the most recent.
Following the instructions, change the word pick
to d
(or drop
) on the lines you want to remove.
pick 4231648cb4 Some previous commit
d 4bccf2ce81 A some message
d 7b4cd5ff17 B some message
d faa44efb7c C some message
d 0ce0525a79 D some message
pick f104648cc3 E some message
If you've made an unrecoverable mistake, like deleting a line, abort by quitting without saving (:q!
) and try again.
If it all looks good, save and quit the buffer (:wq
) and continue rebasing until your branch has been fixed up.
If something wonky happens after that (like you changed a commit hash to one that doesn't exist, or rebase stops to do something and you don't know why) you can abort the rebase completely with git rebase --abort
which will bring you back to your initial state.
If your branch looks correct, force push.
git push -f
An important note on force pushing
Likely little-known, but the default push strategy before git 2 is matching
, meaning when you git push
, it will push all your local branches with matching remote branch names, not just your current branch.
So when you git push -f
it will force push all of your branches (this happened to a colleague just yesterday). Check with git config --global push.default
. That means that if you toyed with some other branch, it could force push it too.
I would suggest changing the default push strategy to simple
if it's not already that. This is the default as of git 2.
Branch protection
If you're using a centrally-hosted git solution like Stash/BitBucket, Gitlab or Github, they all offer so-called "branch protection" rules to prevent, among other things, developers from force-pushing to branches.
Add a rule to prevent force pushing to master
and probably release
branches.