Skip to content

Support recursive glob (.**) in bufimageutil type filters#4571

Open
jhump wants to merge 1 commit into
mainfrom
jh/bufimageutil-recursive-glob
Open

Support recursive glob (.**) in bufimageutil type filters#4571
jhump wants to merge 1 commit into
mainfrom
jh/bufimageutil-recursive-glob

Conversation

@jhump

@jhump jhump commented Jun 11, 2026

Copy link
Copy Markdown
Member

A trailing ".**" on an include or exclude type name now matches the named element and every symbol nested beneath it: a package and all its sub-packages and types, or a message and all its nested types.

(Note that exclusion was already recursive if the root type was a message that contain nested types: we can't realistically keep the nested types if we remove their container.)

This addresses a long-standing TODO since the question of whether including a type should include its children/descendants definitely came up when this functionality was first implemented.

A trailing ".**" on an include or exclude type name is a recursive glob that
matches the named element and every symbol nested beneath it: a package and all
its sub-packages and types, or a message and all its nested types.

Without the glob, behavior is unchanged: a non-recursive package include or
exclude affects only that package's own types, not its sub-packages.
@jhump jhump requested a review from mfridman June 11, 2026 16:51
@github-actions

Copy link
Copy Markdown
Contributor

The latest Buf updates on your PR. Results from workflow Buf CI / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJun 11, 2026, 4:52 PM

@jhump jhump requested a review from emcfarlane June 11, 2026 20:08
@mfridman

Copy link
Copy Markdown
Member

Wdyt about adding a test where the globbed package has no files directly in it?

Something like

  • acme/order/v1/order.proto (package acme.order.v1)
  • acme/order/v2/order.proto (package acme.order.v2)

And WithIncludeTypes("acme.order.**")

With versioned packages this is the most common shape and nobody puts files directly in acme.order, so the glob target has zero files of its own and everything lives in sub-packages. afaics all packages in testdata have their own files, so this path is never exercised (which is the one we care about).

}
if !options.allowImportedTypes {
// if package includes only imported files, then reject
// If the package contains only imported files, then reject. For a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need/want improved error messages to cover this new functionality? E.g.,

Say we have something like

$ ls proto/
acme/order/v1/order.proto    # package acme.order.v1

And the user runs

$ buf build --type acme.order
Failure: inclusion of type "acme.order": type declared in imported module

Would a guard before the if onlyImported be useful, and we could surface something like:

Failure: package "acme.order" contains no types; did you mean "acme.order.**"?

Otherwise that original error is a bit misleading.

for includeType := range options.includeTypes {
includeType := protoreflect.FullName(includeType)
if err := closure.includeType(includeType, imageIndex, options); err != nil {
includeType, recursive := strings.CutSuffix(includeType, ".**")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be a bit more defensive here to prevent things like "foo.**.bar" or ".**" and surface a more meaningful error?

Right now "foo.**.bar" falls through to a literal lookup and produces a confusing not found, and ".**" cuts to an empty name that matches the root package entry, so it silently includes (or excludes) the entire image.The latter is prob unlikely, but the former I could see tripping someone up.

includeType, recursive := strings.CutSuffix(includeType, ".**")
if !protoreflect.FullName(includeType).IsValid() {
    return nil, fmt.Errorf("invalid type name %q", includeType)
}

if !isTopLevel {
continue
}
return fmt.Errorf("inclusion of excluded package %q", pkg.fullName)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want a sentinel error to match on, similar to ErrImageFilterTypeNotFound, but something like ErrImageFilterTypeExcluded.

That way the caller of this package (assume an API) can surface this as an invalid argument error for contradictory filters. There's probably other places too of this kind of cateogry.

@emcfarlane emcfarlane left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe glob of a.** where a.b is excluded and a is empty should error. And we should maybe provide a better error on something like foo.**.bar where a user tries to use an unsupported glob syntax. Do we need to doc this change in the --type strings flags?


// includeChildElements recursively adds the symbols nested beneath the given
// element to the closure. Only messages have such children: their nested
// messages and enums. Other element kinds have nothing to expand.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think extensions are missing here. A services methods and a enums values are always included.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good catch. I thought about this when creating this branch and somehow convinced myself it wasn't necessary. But you're right that it is in fact needed to be correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants