git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH 6/8] ref-filter: Add --no-contains option to tag/branch/for-each-ref

From
Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Date
Mar 20, 2017, 09:32 UTC
Message-ID
<CACBZZX6FWjG1bXrk+ee8y=T5=ovxxybfrGzkkDxjskwDzhKPuA@mail.gmail.com>
In-Reply-To
<20170320042519.srtavoxhm3fln5mw@sigill.intra.peff.net>
On Mon, Mar 20, 2017 at 5:25 AM, Jeff King <peff@peff.net> wrote:
Show 10 quoted lines
> On Sat, Mar 18, 2017 at 10:32:54AM +0000, Ævar Arnfjörð Bjarmason wrote:
>
>> Change the tag, branch & for-each-ref commands to have a --no-contains
>> option in addition to their longstanding --contains options.
>>
>> This allows for finding the last-good rollout tag given a known-bad
>> <commit>. Given a hypothetically bad commit cf5c7253e0 the git version
>> revert to can be found with this hacky two-liner:
>
> s/revert to/to &/, I think.
Done.
Show 26 quoted lines
>
>> With this new --no-contains the same can be achieved with:
>> [..]
>
> The goal sounds good to me.
>
>> In addition to those tests, add a test for "tag" which asserts that
>> --no-contains won't find tree/blob tags, which is slightly
>> unintuitive, but consistent with how --contains works & is documented.
>
> Makes sense. In theory we could dig into commits to find trees and blobs
> when the user gives us one. But I kind of doubt anybody really wants it,
> and it's expensive to compute. For the simple cases, --points-at already
> does the right thing.
>
> [more on that below, though...]
>
>> @@ -604,7 +606,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
>>       if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
>>               list = 1;
>>
>> -     if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr)
>> +     if (filter.with_commit || filter.no_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr)
>>               list = 1;
>
> Could we wrap this long conditional?
Done. Will also go through the series as a whole & find other such occurances.
Show 16 quoted lines
>> diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
>> index df41fa0350..a11542c4fd 100644
>> --- a/builtin/for-each-ref.c
>> +++ b/builtin/for-each-ref.c
>> @@ -9,7 +9,7 @@ static char const * const for_each_ref_usage[] = {
>>       N_("git for-each-ref [<options>] [<pattern>]"),
>>       N_("git for-each-ref [--points-at <object>]"),
>>       N_("git for-each-ref [(--merged | --no-merged) [<object>]]"),
>> -     N_("git for-each-ref [--contains [<object>]]"),
>> +     N_("git for-each-ref [(--contains | --no-contains) [<object>]]"),
>>       NULL
>
> I'm not sure if this presentation implies that the two cannot be used
> together. It copies "--merged/--no-merged", but I think those two
> _can't_ be used together (it probably wouldn't be hard to make it work,
> but if nobody cares it may not be worth spending time on).

Yeah this has been bothering me a bit too. I'll change the various --help and synopsis entries to split them up, since they're not mutually exclusive at all.

Show 55 quoted lines
> I also wonder if we need to explicitly document that --contains and
> --no-contains can be used together and don't cancel each other. The
> other option is to pick a new name ("--omits" is the most concise one I
> could think of; maybe that is preferable anyway because it avoids
> negation).
>
>> @@ -457,7 +459,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
>>       if (!cmdmode && !create_tag_object) {
>>               if (argc == 0)
>>                       cmdmode = 'l';
>> -             else if (filter.with_commit || filter.points_at.nr || filter.merge_commit || filter.lines != -1)
>> +             else if (filter.with_commit || filter.no_commit || filter.points_at.nr || filter.merge_commit || filter.lines != -1)
>
> Ditto here on the wrapping. There were a few other long lines, but I
> won't point them all out.
>
>> -             /* We perform the filtering for the '--contains' option */
>> +             /* We perform the filtering for the '--contains' option... */
>>               if (filter->with_commit &&
>> -                 !commit_contains(filter, commit, &ref_cbdata->contains_cache))
>> +                 !commit_contains(filter, commit, filter->with_commit, &ref_cbdata->contains_cache))
>> +                     return 0;
>> +             /* ...or for the `--no-contains' option */
>> +             if (filter->no_commit &&
>> +                 commit_contains(filter, commit, filter->no_commit, &ref_cbdata->no_contains_cache))
>>                       return 0;
>
> This looks nice and simple. Good.
>
>> +# As the docs say, list tags which contain a specified *commit*. We
>> +# don't recurse down to tags for trees or blobs pointed to by *those*
>> +# commits.
>> +test_expect_success 'Does --[no-]contains stop at commits? Yes!' '
>> +     cd no-contains &&
>> +     blob=$(git rev-parse v0.3:v0.3.t) &&
>> +     tree=$(git rev-parse v0.3^{tree}) &&
>> +     git tag tag-blob $blob &&
>> +     git tag tag-tree $tree &&
>> +     git tag --contains v0.3 >actual &&
>> +     cat >expected <<-\EOF &&
>> +     v0.3
>> +     v0.4
>> +     v0.5
>> +     EOF
>> +     test_cmp expected actual &&
>> +     git tag --no-contains v0.3 >actual &&
>> +     cat >expected <<-\EOF &&
>> +     v0.1
>> +     v0.2
>> +     EOF
>> +     test_cmp expected actual
>> +'
>
> The tests mostly look fine, but this one puzzled me. I guess we're
> checking that tag-blob does not contain v0.3. But how could it?
It's a very defensive test, but I'd like to leave it in.

It would be possible, and perhaps efficient in some cases, to implement "--no-contains <commit>" internally as literally something that just ran "--contains <commit>", got the list of tags, stashed them into a hash, and then did a "git tag -l" and printed out anything *not* in the hash.

This test is asserting that we don't somehow regress to such an implementation.
Show 5 quoted lines
> The more interesting test to me is:
>
>   git tag --contains $blob
>
> which should barf on a non-commit.
Will make sure that's tested for.
Show 9 quoted lines
> For the --no-contains side, you could argue that the blob-tag doesn't
> contain the commit, and it should be listed. But it looks like we just
> drop all non-commit tags completely as soon as we start to do a
> contains/not-contains traversal.
>
> I think the more relevant comparison is "--no-merged", and it behaves
> the same way as your new --no-contains. I don't think I saw this
> subtlety in the documentation, though. It might be worth mentioning
> (unless I just missed it).

For --contains we explicitly document "contain the specified commit", i.e. you couldn't expect this to list tree-test, and indeed it doesn't:

    $ git tag tree-test master^{tree}
    $ git tag -l --contains master '*test*'

However the --[no-]merged option says "reachable [...] from the specified commit", which seems to me to be a bit more ambiguous as to whether you could expect it to print tree/blob tags.

Previous: Jeff KingNext: Jeff King
Message 37 of 41 in “Various changes to the "tag" command”
  1. 0/8 Various changes to the "tag" commandÆvar Arnfjörð Bjarmason, Mar 18, 2017
  2. 1/8 tag: Remove a TODO item from the test suiteÆvar Arnfjörð Bjarmason, Mar 18, 2017
  3. Junio C HamanoMar 18, 2017
  4. 0/2 doc/SubmittingPatches: A couple of minor improvementsÆvar Arnfjörð Bjarmason, Mar 18, 2017
  5. 1/2 doc/SubmittingPatches: clarify the casing convention for "area: change..."Ævar Arnfjörð Bjarmason, Mar 18, 2017
  6. Junio C HamanoMar 18, 2017
  7. Ævar Arnfjörð BjarmasonMar 18, 2017
  8. Junio C HamanoMar 18, 2017
  9. 2/2 doc/SubmittingPatches: show how to get a CLI commit summaryÆvar Arnfjörð Bjarmason, Mar 18, 2017
  10. Junio C HamanoMar 18, 2017
  11. Jakub NarębskiMar 19, 2017
  12. Ævar Arnfjörð BjarmasonMar 19, 2017
  13. 5/8 tag: Implicitly supply --list given the -n optionÆvar Arnfjörð Bjarmason, Mar 18, 2017
  14. Jeff KingMar 20, 2017
  15. 2/8 tag: Refactor the options handling code to be less bizarroÆvar Arnfjörð Bjarmason, Mar 18, 2017
  16. Junio C HamanoMar 18, 2017
  17. Ævar Arnfjörð BjarmasonMar 18, 2017
  18. Junio C HamanoMar 18, 2017
  19. Ævar Arnfjörð BjarmasonMar 18, 2017
  20. 7/8 tag: Add tests for --with and --withoutÆvar Arnfjörð Bjarmason, Mar 18, 2017
  21. 8/8 tag: Change --point-at to default to HEADÆvar Arnfjörð Bjarmason, Mar 18, 2017
  22. Junio C HamanoMar 18, 2017
  23. Ævar Arnfjörð BjarmasonMar 18, 2017
  24. 3/8 tag: Change misleading --list <pattern> documentationÆvar Arnfjörð Bjarmason, Mar 18, 2017
  25. Junio C HamanoMar 18, 2017
  26. Ævar Arnfjörð BjarmasonMar 18, 2017
  27. Jeff KingMar 20, 2017
  28. Junio C HamanoMar 20, 2017
  29. Junio C HamanoMar 20, 2017
  30. Ævar Arnfjörð BjarmasonMar 20, 2017
  31. Jeff KingMar 20, 2017
  32. 4/8 tag: Implicitly supply --list given another list-like optionÆvar Arnfjörð Bjarmason, Mar 18, 2017
  33. Jeff KingMar 20, 2017
  34. Ævar Arnfjörð BjarmasonMar 20, 2017
  35. 6/8 ref-filter: Add --no-contains option to tag/branch/for-each-refÆvar Arnfjörð Bjarmason, Mar 18, 2017
  36. Jeff KingMar 20, 2017
  37. Ævar Arnfjörð BjarmasonMar 20, 2017
  38. Jeff KingMar 20, 2017
  39. Ævar Arnfjörð BjarmasonMar 20, 2017
  40. Jeff KingMar 20, 2017
  41. Junio C HamanoMar 20, 2017

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.