Skip to content

Fix .dockerignore wildcard negation directory descent#6927

Open
Honny1 wants to merge 1 commit into
podman-container-tools:mainfrom
Honny1:negation-filter
Open

Fix .dockerignore wildcard negation directory descent#6927
Honny1 wants to merge 1 commit into
podman-container-tools:mainfrom
Honny1:negation-filter

Conversation

@Honny1

@Honny1 Honny1 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Buildah skipped excluded directories when negation patterns contained wildcards (For example: !**/*.go), because the descent check only matched literal prefixes. Extract the literal prefix before the first wildcard and descend when the directory is at or under it; when the prefix is empty, always descend.

Same bug as Docker's classic builder (moby/moby#30018, moby/moby#45608).

Fixes: #6615

What type of PR is this?

/kind api-change
/kind bug
/kind cleanup
/kind deprecation
/kind design
/kind documentation
/kind failing-test
/kind feature
/kind flake
/kind other

What this PR does / why we need it:

How to verify it

Which issue(s) this PR fixes:

Special notes for your reviewer:

Does this PR introduce a user-facing change?

Fixed `.dockerignore`/`.containerignore` handling of wildcard negation patterns (e.g. `!**/*.go`, `!*/*.go`). Previously, files matching these patterns inside excluded directories were incorrectly omitted from the build context.

@Honny1 Honny1 force-pushed the negation-filter branch 2 times, most recently from fcba319 to f65f4ed Compare June 25, 2026 08:26
@Honny1 Honny1 marked this pull request as ready for review June 25, 2026 08:44
@Honny1

Honny1 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

PTAL @podman-container-tools/buildah-maintainers @podman-container-tools/buildah-reviewers

@simonbrauner simonbrauner left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM

Comment thread add.go
// Check for dockerignore-style exclusion of this item.
if rel != "." {
excluded, err := pm.Matches(filepath.ToSlash(rel)) //nolint:staticcheck
excluded, err := pm.IsMatch(filepath.ToSlash(rel))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Probably out of scope, but the deprecated call could be fixed there as well? https://github.com/podman-container-tools/buildah/blob/main/copier/copier.go#L1087

Comment thread add.go Outdated
}
}
return false
return copier.IncludeDirectoryAnyway(path, pm)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is there a reason to have the private function that only delegates to the public (commented) one?

Comment thread tests/conformance/conformance_test.go Outdated
// #6615: see dockerignore-exclude-kind-of-deep-subdir-dot
withoutDocker: true,
fsSkip: []string{"(dir):subdir:mtime"},
compatScratchConfig: types.OptionalBoolFalse,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Was the changed compatScratchConfig from types.OptionalBoolTrue to OptionalBoolFalse because it wasn't needed?

Also, there are removals of failOnExtraFSContent. Is that correct?

Comment thread copier/copier.go Outdated
// descended into because a negation pattern in pm might match files under it.
// It handles literal prefix matches (e.g. !cmd/main.go for dir "cmd") and
// wildcard negations (e.g. !**/*.go, !*/*.go, !src/*/config.yaml).
func IncludeDirectoryAnyway(dirPath string, pm *fileutils.PatternMatcher) bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Something like ShouldDescendExcludedDir might be a more descriptive name.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Also, I think that the and wildcard negations (e.g. !**/*.go, !*/*.go, !src/*/config.yaml) part is not completely correct. !src/*/config.yaml is IMHO handled by the literal prefix match.

The comment might mention that the wildcard-aware check may intentionally overmatch for wildcards in the middle (it's below).

Comment thread copier/copier_test.go Outdated
want: true,
},
{
name: "non-exclusion patterns are ignored",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

TC like?:

		{
			name:     "dir prefix match is ignored",
			path:     "cmds",
			patterns: []string{"!cmd/main.go"},
			want:     false,
		},

@Honny1 Honny1 force-pushed the negation-filter branch from f65f4ed to 8811b09 Compare June 30, 2026 14:18
@Honny1 Honny1 requested review from simek-m and simonbrauner June 30, 2026 14:48
@Honny1

Honny1 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

The failed test is an unrelated flake.

@simek-m simek-m left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All comments reflected, LGTM.

Comment thread copier/copier.go
Comment on lines 1084 to 1086
// Matches uses filepath.FromSlash() to convert candidates before
// checking if they match the patterns it's been given, implying that
// it expects Unix-style paths.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Just a detail, but the comment uses the old function name Matches. It may be misleading now, and the content may no longer be relevant.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agree, then LGTM

@Honny1 Honny1 force-pushed the negation-filter branch from 8811b09 to c9bda3c Compare July 1, 2026 16:36
@Honny1 Honny1 requested review from inknos and simonbrauner July 1, 2026 16:41

@mtrmac mtrmac 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.

(Just a pretty brief look, I didn’t read the tests at all.)

Comment thread copier/copier.go Outdated
// if the directory is at or under that literal prefix, a file beneath this
// directory could match the negation, so keep descending.
if firstWild := strings.IndexAny(slashPattern, "*?["); firstWild >= 0 {
literalPrefix := strings.TrimRight(slashPattern[:firstWild], "/")

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.

What about !cmd/image*? TrimRight will not find the /.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@mtrmac Oh, I thought it's handled with strings.HasPrefix before, but no - only for cmd with !cmd/image*, but not cmd/images with !cmd/image* or !cmd/image*/main.go with cmd/images/main.go.

Comment thread copier/copier.go Outdated
// the literal prefix before the first wildcard and may intentionally
// overmatch (descend into directories that won't ultimately contain matches),
// which is safe because actual file-level matching happens later.
func ShouldDescendExcludedDir(dirPath string, pm *fileutils.PatternMatcher) bool {

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.

This (or some variant of this) should probably be in c/storage/pkg/fileutils instead: c/storage/pkg/archive.tarWithOptionsTo` contains a similar exclusion check with the same problem.

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.

(If we did end up doing this Buildah-only, I think having this in something like internal/exclusions would be better, not to commit to it as a stable API — but that’s a Buildah maintainer decision.)

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.

Agreed, we definitely don't want to add this as an exported API that we have to keep working, even if/after how exclusion matching rules change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have moved that into internal/excludes. Should I create a PR with the fix in c/storage as a follow-up?

@nalind nalind 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.

I'm not very enthusiastic about updating conformance tests which test "ignores" behavior so that they don't use a baseline.

Comment thread copier/copier.go Outdated
Comment thread copier/copier.go Outdated
// the literal prefix before the first wildcard and may intentionally
// overmatch (descend into directories that won't ultimately contain matches),
// which is safe because actual file-level matching happens later.
func ShouldDescendExcludedDir(dirPath string, pm *fileutils.PatternMatcher) bool {

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.

Agreed, we definitely don't want to add this as an exported API that we have to keep working, even if/after how exclusion matching rules change.

Comment thread copier/copier.go Outdated
// checking if they match the patterns it's been given, implying that
// it expects Unix-style paths.
matches, err := pm.Matches(filepath.ToSlash(rel)) //nolint:staticcheck
matches, err := pm.IsMatch(filepath.ToSlash(rel))

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.

IsMatch() does more work to return the same result as Matches(), it's not worth switching which one we use.

Comment thread tests/bud.bats Outdated
@Honny1 Honny1 force-pushed the negation-filter branch from c9bda3c to 01c5159 Compare July 9, 2026 13:51
@Honny1

Honny1 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

I'm not very enthusiastic about updating conformance tests which test "ignores" behavior so that they don't use a baseline.

I am also not a big fan of this, but the root cause is that go-dockerclient sends the build context through moby/go-archive's TarWithOptions, which has the same bug (moby/moby#30018, moby/moby#45608). Docker BuildKit handles it correctly, but since the framework filters client-side before the context reaches BuildKit, we can't get a valid baseline.

@Honny1 Honny1 requested review from mtrmac and nalind July 9, 2026 14:05
Comment thread tests/bud.bats Outdated
run_buildah 1 run myctr ls -l subdir/
# !*/sub1* un-excludes subdir/sub1.txt
run_buildah run myctr ls -l subdir/
run_buildah run myctr ls -l subdir/sub1.txt

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.

This should probably be making sure that nothing else was copied to this directory.

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.

Something like this

run_buildah run myctr find subdir -mindepth 1 -print
assert "$output" = "subdir/sub1.txt"

would make sure that nothing else was copied there.

Comment thread internal/excludes/excludes_test.go
@Honny1 Honny1 force-pushed the negation-filter branch from 01c5159 to 8021cd0 Compare July 14, 2026 12:31
@Honny1

Honny1 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

PTAL @nalind

Comment thread tests/conformance/conformance_test.go Outdated
// Docker BuildKit handles it correctly, but we cannot compare
// through this framework.
// TODO: re-enable Docker comparison when moby/go-archive fixes
// wildcard negation handling in TarWithOptions.

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.

We supply the InputStream field in the options, not the ContextDir, so we do have control over which parts of the build context we transfer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah, thanks, I didn't realize that.

Buildah skipped excluded directories when negation patterns contained
wildcards (For example: !**/*.go), because the descent check only matched
literal prefixes. Extract the literal prefix before the first wildcard
and descend when the directory is at or under it; when the prefix is
empty, always descend.

Same bug as Docker's classic builder (moby/moby#30018, moby/moby#45608).

Fixes: podman-container-tools#6615

Signed-off-by: Jan Rodák <hony.com@seznam.cz>
@Honny1 Honny1 force-pushed the negation-filter branch from 8021cd0 to 636f3ad Compare July 15, 2026 17:14
@Honny1 Honny1 requested a review from nalind July 15, 2026 17:15
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.

dockerignore prevents build from working

6 participants