7

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 ?

Ankur Agarwal
  • 23,692
  • 41
  • 137
  • 208

3 Answers3

13

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).

torek
  • 448,244
  • 59
  • 642
  • 775
  • just tested this but it doesn't seem to work :/ `$ git revert eab485e2d09076e1cc82485a56c80d729c165b93^@..d71777a7dfa53fa82847119aaede42fbbbf83473 --no-edit fatal: bad revision 'eab485e2d09076e1cc82485a56c80d729c165b93^@..d71777a7dfa53fa82847119aaede42fbbbf83473'` – Elouan Keryell-Even Jun 11 '21 at 08:46
  • However, your other syntax `A^..B` worked perfect, thx :) – Elouan Keryell-Even Jun 11 '21 at 08:49
  • 1
    @ElouanKeryell-Even: interesting: I know `^@..` worked at least once when I tested it (probably back in 2017 when I wrote the answer above). It doesn't *now* though. – torek Jun 11 '21 at 10:41
  • 1
    Additional note: I built Git 2.14.0 (dated around Aug 2017) and it does not seem to work there. I wound it all the way back to Git 2.1.0 and it does not work there either. Maybe it never did! I could swear I tested it. (It works without the `..` syntax, but as such, the overall command will need massaging.) – torek Jun 11 '21 at 10:55
4

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 Os 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 Os, 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 Os 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 xs

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

Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52
  • "Now why are O3 and O4 not x2 and x3?" x2 x3 are not in your diagram. Also can you please fix your sentence please ? A..B should include 01 and 02 and not 03 and 04 since 01 and 02 are not reachable from A but are reachable from B. 03 and 04 are not reachable from both A and B because they are both downstream of A and B. Right ? – Ankur Agarwal Aug 16 '17 at 19:13
0

I your case, you seem to be looking for a way to specify --all.

Here is the <rev-list options> section of git help filter-branch :

   <rev-list options>...
       Arguments for git rev-list. All positive refs included by these options are
       rewritten. You may also specify options such as --all, but you must use --
       to separate them from the git filter-branch options. Implies the section 
       called “Remap to ancestor”.

and some sample usage in the EXAMPLES section.

In your case :

git filter-branch --index-filter 'git rm ...' -- --all
LeGEC
  • 46,477
  • 5
  • 57
  • 104