I need to specify all commits downstream of the 1st commit in a git repo.
I am trying to run this command:
$ git filter-branch --index-filter 'git rm --ignore-unmatch --cached some/path/file' -- 33e0331^..
And I am getting:
fatal: ambiguous argument '33e0331^..': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
Because 33e0331
is the first commit in the repo. How do I do this ?
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).
Just don't specify the starting point. If you say master
, for example, that implies "everything reachable from master
". The ability to include a starting point is only to designate some "early history" to exclude.
While that should address the scenario you've put forward, a more general answer to the title question: You can't. You don't really specify "ranges of commits" (as most people would intuitively define it) at all.
The range notation is a convenience, but it can be misleading. In non-trivial cases, it can be useful to remember that you can specify (a) that everything reachable from a commit should be excluded, or (b) that everything reachable from a commit should be included. There are multiple notations for each. A..B
means "exclude everything reachable from A
, but other than that include everything reachable from B
"; it's a shorthand for something like ^A B
(and, interestingly, it's not actually shorter...)
Now in a simple case, that looks like a range of commits
x1 -- x2 -- A -- O1 -- O2 -- B -- x3 -- x4
In such a simple case, you could kinda-sotta make it an "inclusive range" by using "parent-of" notation as you tried (^A^ B
or A^..B
).
If A
is a root, as in your example:
A -- O1 -- O2 -- B -- x1 -- x2
still A..B
means to include the O
s and the B
. For an "inclusive" range, you would just omit the starting point as in the original form of my answer. B
would include A
, the O
s, and B
.
You can stretch your definition of "range" a little bit:
x1 -- x2 -- A
\
O1 -- O2 -- B
Here, A..B
(which, again, is just ^A B
) would again include just the O
s and the B
. It may seem that the "starting point" is not upstream of B
, but you exclude everything reachable from A
, which includes the x
s
So now, what would an "inclusive range" even mean?
This is a common usage of the ..
notation, because it allows you to peal the commits from a branch away from the commits of its "parent branch". (I say "parent branch" in quotes, because this is not a thing to git. It's just a common interpretation of the branch topology.)
But where you can really lose your mind trying to think of A..B
as a "range" is something like
x1 -- A -- O1 -- O2 -- B <--(master)
/
O3 -- O4
Now why are O3
and O4
not x2
and x3
? They aren't in the "range" from A
to B
...? Well, they aren't reachable from A
, and they are reachable from B
, so they are included. To get the range (as you might intuitively understand it) you would have to say something like ^A ^o4 B
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With