What Does `--contains` Option Do in `git for-each-ref`?

530 views Asked by At

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

1

There are 1 answers

5
LeGEC On BEST ANSWER

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