Skip to content

Commit 6066a5f

Browse files
authored
fix(overlays): skip .git directory when applying file-targeting overlays (#64)
1 parent bada177 commit 6066a5f

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

internal/app/azldev/core/sources/overlays.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,12 +438,20 @@ func globNonSpecFiles(destFS opctx.FS, pattern string) ([]string, error) {
438438
return nil, fmt.Errorf("failed to glob for files using pattern %#q:\n%w", pattern, err)
439439
}
440440

441-
// Filter out .spec files. Paths are already in pseudo-absolute format.
441+
// Filter out .spec files and .git directory contents. Paths are already in pseudo-absolute format.
442+
// The .git directory is excluded because overlay glob patterns like "**/*" must not match
443+
// git internal files (packfiles, objects, etc.) which are read-only and binary.
442444
return lo.Filter(candidatePaths, func(path string, _ int) bool {
443-
return !isSpecFile(path)
445+
return !isSpecFile(path) && !isGitInternalPath(path)
444446
}), nil
445447
}
446448

449+
// isGitInternalPath returns true if the given path is inside a .git directory.
450+
// Handles both root-level (.git/HEAD) and nested (subdir/.git/objects) paths.
451+
func isGitInternalPath(path string) bool {
452+
return strings.HasPrefix(path, ".git/") || strings.Contains(path, "/.git/")
453+
}
454+
447455
// newDestFS creates a destination filesystem confined to the given root directory.
448456
// For real filesystems (OsFs), it uses os.Root via rootfs.RootFs for atomic symlink safety.
449457
// For test filesystems (MemMapFs), it uses afero.BasePathFs to confine paths.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package sources
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestIsGitInternalPath(t *testing.T) {
13+
tests := []struct {
14+
path string
15+
expected bool
16+
}{
17+
// Root-level .git directory contents.
18+
{".git/HEAD", true},
19+
{".git/config", true},
20+
{".git/objects/pack/pack-abc.idx", true},
21+
22+
// Pseudo-absolute root-level .git paths (/prefix from BasePathFs).
23+
{"/.git/HEAD", true},
24+
{"/.git/objects/pack/pack-abc.idx", true},
25+
26+
// Nested .git directories (e.g., submodules).
27+
{"subdir/.git/HEAD", true},
28+
{"vendor/lib/.git/objects/pack-abc.idx", true},
29+
30+
// Non-.git paths that should NOT match.
31+
{".gitignore", false},
32+
{".gitattributes", false},
33+
{"foo.spec", false},
34+
{"src/main.go", false},
35+
{"git/config", false},
36+
{"my.gitconfig", false},
37+
}
38+
39+
for _, tt := range tests {
40+
t.Run(tt.path, func(t *testing.T) {
41+
assert.Equal(t, tt.expected, isGitInternalPath(tt.path))
42+
})
43+
}
44+
}

0 commit comments

Comments
 (0)