0

What does the --contains option do in git for-each-ref?

Does it only return a reference if its revlist has the commit specified after the --contains option on the command line? (e.g. Does git for-each-ref refs/heads --contains feature/test only return those heads that contain the commit feature/test in their revlist? (i.e. the list of commits between them and the first commit, namely git rev-list <ref>))?

The Git docs (v. 2.31.1; see here) are actually outdated: the Notes section at the bottom states you can use the --merged and --no-merged options together, but this was made incompatible with this commit.

The definition provided in the docs is ambiguous: it reuses the word "contains" without clarifying what is meant by that. A reference is a name that points to only one SHA. The idea of "contains" suggests a reference refers to multiple commits, which only makes sense in the context of its git rev-list.

A commit can come after or before another (simply by time of commit), but if it is not reachable from that commit (i.e. if the commit is not merged into its branch), then the commit has no knowledge of it, so it can't be said to "contain" it.

This is why --merged and --no-merged together made sense: it would return the commits reachable from (at least one of) --merged and none of --no-merged.

What is meant by "contains"?..."has immediate parent X"? "has ancestor X"? "has cousin X"?

UPDATE:

It does seem I was using an older version of Git. Using both --merged and --no-merged together was incompatible in v2.28.1, but is now compatible in v2.31.1. Hence, the Git docs are not outdated.

It also seems this commit was made on Mar 21, 2017, so well before v2.31.1 (not sure which version exactly it was committed with).

Second, it does seem @LeGEC's answer makes sense (vis-a-vis --contains/--no-contains means "give me refs that have this commit after them" and --merged/--no-merged means "give me refs that have this commit before them".

Still, it would be nice if the Git documentation could be given further clarified. Using the word in the definition as they've done is very vague.

Because Git is a Directed Acyclic Graph (DAG), commits point to other commits in linked-list-fashion in one direction: backwards in time towards their ancestors. Hence, the names --merged/--no-merged and their description in terms of "reachability" make sense.

Does "contains" mean "after and reachable" (i.e. children), or simply "after" (i.e. commits made later in time)?

Since this is a plumbing command (which are used in custom scripts), I need to know exactly what it does and how it works so that my scripts won't have any unexpected side effects.

Old Git Version Problem

aynber
  • 22,380
  • 8
  • 50
  • 63
adam.hendry
  • 4,458
  • 5
  • 24
  • 51
  • 2
    Did you check the [documentation](https://git-scm.com/docs/git-for-each-ref)? "--contains[=] Only list refs which contain the specified commit (HEAD if not specified)." – Lasse V. Karlsen May 11 '21 at 19:26
  • aren't you mixing with some other command ? or can you perhaps give a link to your source of information ? With `git for-each-ref`, `--merged` and `--no-merged` are compatible options ; `--contains` and `-no-contains` were added because they add the possibility to describe new things. – LeGEC May 11 '21 at 19:46
  • @LeGEC Please see this commit: https://github.com/git/git/commit/17d6c744dc0d5ed4cd0f228da14239ea2654f05b. As of v2.31.1, using `--merged` with `--no-merged` together was made incompatible. The docs on `git-scm.com` are actually out-of-date (see https://git-scm.com/docs/git-for-each-ref). If you try to run both together in the way mentioned in the notes at the bottom of the page, it no longer works. – adam.hendry May 11 '21 at 22:04
  • @LasseV.Karlsen Yes, but what does "contains" mean? The term is ambiguous because a ref is a SHA (it's a name that points to a SHA). The idea of "contains" suggest there would be multiple commits associated with a single ref, which only makes sense in the context of its `git rev-list`. Otherwise, a ref either is or isn't a SHA (it doesn't contain or point to more than one SHA). – adam.hendry May 11 '21 at 22:07
  • @AHendry : ah ! your question turns out to be "what is a commit in git ?" – LeGEC May 11 '21 at 22:31
  • 1
    @LeGEC No, you're incorrect. I understand that already. Are you saying that `--contains` returns the ref only if one of the ref's _parents_ is the one listed after `--contains`? This is different than if it's any of the ref's _ancestors_. It is also true that a commit "contains" all its ancestors, not just its parents. If `--contains` means only "the ref's parents", the docs need to clarify this. – adam.hendry May 11 '21 at 22:39
  • @LeGEC Also, your answer is still incorrect because using `--merged` and `--no-merged` together no longer works (the docs are incorrect/outdated). – adam.hendry May 11 '21 at 22:42
  • What is meant by "contains"? Is it "has immediate ancestor X"? "has parent X"? "has cousin X"? – adam.hendry May 11 '21 at 22:46
  • TIL : starting from versions 2.13.17, and until version 2.28.1, `git for-each-ref` would not accept `--merged` and `--no-merged` together. – LeGEC May 12 '21 at 07:19

1 Answers1

4

Perhaps I misunderstood your question, please update your question if relevant.


The documentation gives a correct (but perhaps succinct ?) description of these options,
here is one way to word it differently :

  • --merged develop means "comes before develop"
  • --no-merged master means "doesn't come before master"
  • --contains feature1 means "comes after feature1"
  • --no-contains feature2 means "doesn't come after feature2"

These 4 options are actually compatible :

git for-each-ref --merged develop --no-merged master --contains feature1 --no-contains feature2

will list references that :

  • are merged in develop, but not in master yet,
  • contain feature1 (e.g : feature1 was somehow merged into them), and doesn't contain feature2 yet

[update] a ref points to a commit, and a commit has pointers to its parents. With this relation, commits in git form a graph (a DAG actually).

"merged in" (or my incorrect "comes before") means "is an ancestor of", "contains" (or my incorrect "comes after") means "is a descendant of".

You mention git rev-list : if git rev-list commitB lists the sha of commitA, then you can say :

  • commitB contains commitA
  • commitA is merged in commitB
LeGEC
  • 46,477
  • 5
  • 57
  • 104
  • Actually, please see this commit: github.com/git/git/commit/…. As of v2.31.1, using `--merged` with `--no-merged` is incompatible (I've tested and it no longer works for me). Thus, the docs on git-scm.com are actually out-of-date (see git-scm.com/docs/git-for-each-ref). If you try to run both together in the way mentioned in the notes at the bottom of the page, it no longer works. – adam.hendry May 11 '21 at 22:09
  • The docs do not explain it that way (i.e. "come after" or "come before"). They reuse the word itself (i.e. it either "contains" or "doesn't contain" a commit). The term is ambiguous because a ref is a SHA (it's a name that points to a SHA). The idea of "contains" suggests there are multiple commits associated with a single ref, which only makes sense in the context of its `git rev-list`. A commit can come after or before another, but if it is not reachable from that commit (not merged into its branch), then it won't appear in the revlist and it isn't "contained" by it. – adam.hendry May 11 '21 at 22:14
  • Please see my updated question. If this clarifies things for you, please upvote my question. – adam.hendry May 11 '21 at 22:24
  • For "is ancestor" tests, Git simply tries finding the purported ancestor from the purported descendant. (Caveat: relatively recently, Git grew the ability to create additional commit graph cached data to help this go faster; the new code is much fancier.) Note that "is same commit" is considered to be an ancestor. For "is commit Y a descendant of commit X" Git simply inverts the test and tries "is commit X an ancestor of Y". So the *date stamps* are irrelevant, modulo any bugs in any graph-optimizing code. – torek May 12 '21 at 00:57
  • (There were a lot of patches on the Git mailing list in the last year or so containing fixes for fancy code, in which the fixes compute a "corrected commit date" where commits that have earlier dates than their ancestor commits get corrected-commit-dates in the auxiliary data, since it's so tempting to use the actual date values. Given correct corrected-commit-dates, these should work. The problem is that these are 32-bit fields but date/time-stamps are 64 bits!) – torek May 12 '21 at 00:59