The question was specifically about using git filter-branch
, but the way it was expressed applies to many Git commands. I like to use the term "select with history" here; it's how git log
and git rev-list
and anything else that uses git rev-list
—including git filter-branch
—work. For other Git commands, writing down a name for a single commit just selects the single commit itself, and we must use the exclude..include
syntax or similar to get a whole range of commits:
git cherry-pick A..B
for instance will cherry-pick the commit after A
, and then the commit after that, and so on until it finally reaches commit B
. The very similar:
git revert A..B
will revert commit B
, then the commit before B
, then the commit before that, and so on, but stop and not revert commit A
. (Note that this assumes something about the graph "between" A
and B
: the notion of "between" is rather slippery.)
Mark Adelsberger's answer is the right one for git filter-branch
, and indeed for any Git command that uses the "select commit with history" mode. [Edit: Oops, I see he has augmented his answer since then! Well, this is now even more supplemental.] But what can you do with, e.g., git cherry-pick
, when you want to cherry-pick commit A
too?
There are several obvious answers—well, "obvious" once you grasp the commit-graph-following in the first place, and the funky syntactic operations Git provides with things like A^
:
git cherry-pick A^..B
means "select commit B
and all its history, excluding commit A^
and all of its history" and since A^
means "the parent commit of A
", this usually does the trick. You must, of course, be aware of the way that the exclusion itself works: if there are merges in the commit graph, or commit A
is not an ancestor of commit B
, the exclusion may not produce what you wanted.
Nonetheless, there are some especially tricky cases where one funky Git syntax comes in handy. If commit A
is itself a merge commit, and you want to exclude all of A
's parents without excluding A
itself, there is a syntax for that, documented in the gitrevisions(7) page: A^@
means "all the parents of commit A
, but not commit A
itself".
[Edit, Jun 2021]: This, unfortunately, cannot be combined with the two-dot syntax, but there sometimes is a way to handle it. We need to use git rev-parse
with A^@
first to get the list of commit hash IDs to exclude. Then we need to prefix each hash ID in this list with ^
. Finally, we need to provide this list, and a "positive reference" to B
:
git rev-parse A^@ | sed s/^/^/
achieves the first goal (at least on Unix-like systems using sh or bash), and:
git <command> $(git rev-parse A^@ | sed s/^/^/) B
then runs the given <command>
with the hat-prefixed list of commits. This list is empty when A
is a root commit.
Unfortunately, this doesn't work with git cherry-pick
at all, as it now just sees B
. So for this particular case we just have to run some sort of "is A
a root commit" test first (e.g., use A^..B
). If A^
fails because A
is a root commit, then we can use:
git cherry-pick A A..B
which will first copy commit A
, then all commits after A
up through B
inclusive.
For git revert
, the equivalent would be:
git revert A..B A
when A
is a root commit (but note that if this revert works at all, it just leaves you with a no-files-in-it-at-all commit).