Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit25d70ce

Browse files
fix(agent/agentcontainers): respect ignore files (#19016)
Closes#19011We now use[go-git](https://pkg.go.dev/github.com/go-git/go-git/v5@v5.16.2/plumbing/format/gitignore)'s`gitignore` plumbing implementation to parse the `.gitignore` files andmatch against the patterns generated. We use this to ignore any ignoredfiles in the git repository.Unfortunately I've had to slightly re-implement some of the interfaceexposed by `go-git` because they use `billy.Filesystem` instead of`afero.Fs`.
1 parent5c1bf1d commit25d70ce

File tree

6 files changed

+323
-7
lines changed

6 files changed

+323
-7
lines changed

‎agent/agentcontainers/api.go‎

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ import (
2121

2222
"github.com/fsnotify/fsnotify"
2323
"github.com/go-chi/chi/v5"
24+
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
2425
"github.com/google/uuid"
2526
"github.com/spf13/afero"
2627
"golang.org/x/xerrors"
2728

2829
"cdr.dev/slog"
30+
"github.com/coder/coder/v2/agent/agentcontainers/ignore"
2931
"github.com/coder/coder/v2/agent/agentcontainers/watcher"
3032
"github.com/coder/coder/v2/agent/agentexec"
3133
"github.com/coder/coder/v2/agent/usershell"
@@ -469,13 +471,49 @@ func (api *API) discoverDevcontainerProjects() error {
469471
}
470472

471473
func (api*API)discoverDevcontainersInProject(projectPathstring)error {
474+
logger:=api.logger.
475+
Named("project-discovery").
476+
With(slog.F("project_path",projectPath))
477+
478+
globalPatterns,err:=ignore.LoadGlobalPatterns(api.fs)
479+
iferr!=nil {
480+
returnxerrors.Errorf("read global git ignore patterns: %w",err)
481+
}
482+
483+
patterns,err:=ignore.ReadPatterns(api.ctx,logger,api.fs,projectPath)
484+
iferr!=nil {
485+
returnxerrors.Errorf("read git ignore patterns: %w",err)
486+
}
487+
488+
matcher:=gitignore.NewMatcher(append(globalPatterns,patterns...))
489+
472490
devcontainerConfigPaths:= []string{
473491
"/.devcontainer/devcontainer.json",
474492
"/.devcontainer.json",
475493
}
476494

477-
returnafero.Walk(api.fs,projectPath,func(pathstring,info fs.FileInfo,_error)error {
495+
returnafero.Walk(api.fs,projectPath,func(pathstring,info fs.FileInfo,errerror)error {
496+
iferr!=nil {
497+
logger.Error(api.ctx,"encountered error while walking for dev container projects",
498+
slog.F("path",path),
499+
slog.Error(err))
500+
returnnil
501+
}
502+
503+
pathParts:=ignore.FilePathToParts(path)
504+
505+
// We know that a directory entry cannot be a `devcontainer.json` file, so we
506+
// always skip processing directories. If the directory happens to be ignored
507+
// by git then we'll make sure to ignore all of the children of that directory.
478508
ifinfo.IsDir() {
509+
ifmatcher.Match(pathParts,true) {
510+
returnfs.SkipDir
511+
}
512+
513+
returnnil
514+
}
515+
516+
ifmatcher.Match(pathParts,false) {
479517
returnnil
480518
}
481519

@@ -486,11 +524,11 @@ func (api *API) discoverDevcontainersInProject(projectPath string) error {
486524

487525
workspaceFolder:=strings.TrimSuffix(path,relativeConfigPath)
488526

489-
api.logger.Debug(api.ctx,"discovered dev container project",slog.F("workspace_folder",workspaceFolder))
527+
logger.Debug(api.ctx,"discovered dev container project",slog.F("workspace_folder",workspaceFolder))
490528

491529
api.mu.Lock()
492530
if_,found:=api.knownDevcontainers[workspaceFolder];!found {
493-
api.logger.Debug(api.ctx,"adding dev container project",slog.F("workspace_folder",workspaceFolder))
531+
logger.Debug(api.ctx,"adding dev container project",slog.F("workspace_folder",workspaceFolder))
494532

495533
dc:= codersdk.WorkspaceAgentDevcontainer{
496534
ID:uuid.New(),

‎agent/agentcontainers/api_test.go‎

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http/httptest"
1010
"os"
1111
"os/exec"
12+
"path/filepath"
1213
"runtime"
1314
"slices"
1415
"strings"
@@ -3211,6 +3212,9 @@ func TestDevcontainerDiscovery(t *testing.T) {
32113212
// repositories to find any `.devcontainer/devcontainer.json`
32123213
// files. These tests are to validate that behavior.
32133214

3215+
homeDir,err:=os.UserHomeDir()
3216+
require.NoError(t,err)
3217+
32143218
tests:= []struct {
32153219
namestring
32163220
agentDirstring
@@ -3345,6 +3349,113 @@ func TestDevcontainerDiscovery(t *testing.T) {
33453349
},
33463350
},
33473351
},
3352+
{
3353+
name:"RespectGitIgnore",
3354+
agentDir:"/home/coder",
3355+
fs:map[string]string{
3356+
"/home/coder/coder/.git/HEAD":"",
3357+
"/home/coder/coder/.gitignore":"y/",
3358+
"/home/coder/coder/.devcontainer.json":"",
3359+
"/home/coder/coder/x/y/.devcontainer.json":"",
3360+
},
3361+
expected: []codersdk.WorkspaceAgentDevcontainer{
3362+
{
3363+
WorkspaceFolder:"/home/coder/coder",
3364+
ConfigPath:"/home/coder/coder/.devcontainer.json",
3365+
Status:codersdk.WorkspaceAgentDevcontainerStatusStopped,
3366+
},
3367+
},
3368+
},
3369+
{
3370+
name:"RespectNestedGitIgnore",
3371+
agentDir:"/home/coder",
3372+
fs:map[string]string{
3373+
"/home/coder/coder/.git/HEAD":"",
3374+
"/home/coder/coder/.devcontainer.json":"",
3375+
"/home/coder/coder/y/.devcontainer.json":"",
3376+
"/home/coder/coder/x/.gitignore":"y/",
3377+
"/home/coder/coder/x/y/.devcontainer.json":"",
3378+
},
3379+
expected: []codersdk.WorkspaceAgentDevcontainer{
3380+
{
3381+
WorkspaceFolder:"/home/coder/coder",
3382+
ConfigPath:"/home/coder/coder/.devcontainer.json",
3383+
Status:codersdk.WorkspaceAgentDevcontainerStatusStopped,
3384+
},
3385+
{
3386+
WorkspaceFolder:"/home/coder/coder/y",
3387+
ConfigPath:"/home/coder/coder/y/.devcontainer.json",
3388+
Status:codersdk.WorkspaceAgentDevcontainerStatusStopped,
3389+
},
3390+
},
3391+
},
3392+
{
3393+
name:"RespectGitInfoExclude",
3394+
agentDir:"/home/coder",
3395+
fs:map[string]string{
3396+
"/home/coder/coder/.git/HEAD":"",
3397+
"/home/coder/coder/.git/info/exclude":"y/",
3398+
"/home/coder/coder/.devcontainer.json":"",
3399+
"/home/coder/coder/x/y/.devcontainer.json":"",
3400+
},
3401+
expected: []codersdk.WorkspaceAgentDevcontainer{
3402+
{
3403+
WorkspaceFolder:"/home/coder/coder",
3404+
ConfigPath:"/home/coder/coder/.devcontainer.json",
3405+
Status:codersdk.WorkspaceAgentDevcontainerStatusStopped,
3406+
},
3407+
},
3408+
},
3409+
{
3410+
name:"RespectHomeGitConfig",
3411+
agentDir:homeDir,
3412+
fs:map[string]string{
3413+
"/tmp/.gitignore":"node_modules/",
3414+
filepath.Join(homeDir,".gitconfig"):`
3415+
[core]
3416+
excludesFile = /tmp/.gitignore
3417+
`,
3418+
3419+
filepath.Join(homeDir,".git/HEAD"):"",
3420+
filepath.Join(homeDir,".devcontainer.json"):"",
3421+
filepath.Join(homeDir,"node_modules/y/.devcontainer.json"):"",
3422+
},
3423+
expected: []codersdk.WorkspaceAgentDevcontainer{
3424+
{
3425+
WorkspaceFolder:homeDir,
3426+
ConfigPath:filepath.Join(homeDir,".devcontainer.json"),
3427+
Status:codersdk.WorkspaceAgentDevcontainerStatusStopped,
3428+
},
3429+
},
3430+
},
3431+
{
3432+
name:"IgnoreNonsenseDevcontainerNames",
3433+
agentDir:"/home/coder",
3434+
fs:map[string]string{
3435+
"/home/coder/.git/HEAD":"",
3436+
3437+
"/home/coder/.devcontainer/devcontainer.json.bak":"",
3438+
"/home/coder/.devcontainer/devcontainer.json.old":"",
3439+
"/home/coder/.devcontainer/devcontainer.json~":"",
3440+
"/home/coder/.devcontainer/notdevcontainer.json":"",
3441+
"/home/coder/.devcontainer/devcontainer.json.swp":"",
3442+
3443+
"/home/coder/foo/.devcontainer.json.bak":"",
3444+
"/home/coder/foo/.devcontainer.json.old":"",
3445+
"/home/coder/foo/.devcontainer.json~":"",
3446+
"/home/coder/foo/.notdevcontainer.json":"",
3447+
"/home/coder/foo/.devcontainer.json.swp":"",
3448+
3449+
"/home/coder/bar/.devcontainer.json":"",
3450+
},
3451+
expected: []codersdk.WorkspaceAgentDevcontainer{
3452+
{
3453+
WorkspaceFolder:"/home/coder/bar",
3454+
ConfigPath:"/home/coder/bar/.devcontainer.json",
3455+
Status:codersdk.WorkspaceAgentDevcontainerStatusStopped,
3456+
},
3457+
},
3458+
},
33483459
}
33493460

33503461
initFS:=func(t*testing.T,filesmap[string]string) afero.Fs {
@@ -3397,7 +3508,7 @@ func TestDevcontainerDiscovery(t *testing.T) {
33973508
err:=json.NewDecoder(rec.Body).Decode(&got)
33983509
require.NoError(t,err)
33993510

3400-
returnlen(got.Devcontainers)==len(tt.expected)
3511+
returnlen(got.Devcontainers)>=len(tt.expected)
34013512
},testutil.WaitShort,testutil.IntervalFast,"dev containers never found")
34023513

34033514
// Now projects have been discovered, we'll allow the updater loop
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package ignore
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"errors"
7+
"io/fs"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
12+
"github.com/go-git/go-git/v5/plumbing/format/config"
13+
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
14+
"github.com/spf13/afero"
15+
"golang.org/x/xerrors"
16+
17+
"cdr.dev/slog"
18+
)
19+
20+
const (
21+
gitconfigFile=".gitconfig"
22+
gitignoreFile=".gitignore"
23+
gitInfoExcludeFile=".git/info/exclude"
24+
)
25+
26+
funcFilePathToParts(pathstring) []string {
27+
components:= []string{}
28+
29+
ifpath=="" {
30+
returncomponents
31+
}
32+
33+
forsegment:=rangestrings.SplitSeq(filepath.Clean(path),string(filepath.Separator)) {
34+
ifsegment!="" {
35+
components=append(components,segment)
36+
}
37+
}
38+
39+
returncomponents
40+
}
41+
42+
funcreadIgnoreFile(fileSystem afero.Fs,path,ignorestring) ([]gitignore.Pattern,error) {
43+
varps []gitignore.Pattern
44+
45+
data,err:=afero.ReadFile(fileSystem,filepath.Join(path,ignore))
46+
iferr!=nil&&!errors.Is(err,os.ErrNotExist) {
47+
returnnil,err
48+
}
49+
50+
fors:=rangestrings.SplitSeq(string(data),"\n") {
51+
if!strings.HasPrefix(s,"#")&&len(strings.TrimSpace(s))>0 {
52+
ps=append(ps,gitignore.ParsePattern(s,FilePathToParts(path)))
53+
}
54+
}
55+
56+
returnps,nil
57+
}
58+
59+
funcReadPatterns(ctx context.Context,logger slog.Logger,fileSystem afero.Fs,pathstring) ([]gitignore.Pattern,error) {
60+
varps []gitignore.Pattern
61+
62+
subPs,err:=readIgnoreFile(fileSystem,path,gitInfoExcludeFile)
63+
iferr!=nil {
64+
returnnil,err
65+
}
66+
67+
ps=append(ps,subPs...)
68+
69+
iferr:=afero.Walk(fileSystem,path,func(pathstring,info fs.FileInfo,errerror)error {
70+
iferr!=nil {
71+
logger.Error(ctx,"encountered error while walking for git ignore files",
72+
slog.F("path",path),
73+
slog.Error(err))
74+
returnnil
75+
}
76+
77+
if!info.IsDir() {
78+
returnnil
79+
}
80+
81+
subPs,err:=readIgnoreFile(fileSystem,path,gitignoreFile)
82+
iferr!=nil {
83+
returnerr
84+
}
85+
86+
ps=append(ps,subPs...)
87+
88+
returnnil
89+
});err!=nil {
90+
returnnil,err
91+
}
92+
93+
returnps,nil
94+
}
95+
96+
funcloadPatterns(fileSystem afero.Fs,pathstring) ([]gitignore.Pattern,error) {
97+
data,err:=afero.ReadFile(fileSystem,path)
98+
iferr!=nil&&!errors.Is(err,os.ErrNotExist) {
99+
returnnil,err
100+
}
101+
102+
decoder:=config.NewDecoder(bytes.NewBuffer(data))
103+
104+
conf:=config.New()
105+
iferr:=decoder.Decode(conf);err!=nil {
106+
returnnil,xerrors.Errorf("decode config: %w",err)
107+
}
108+
109+
excludes:=conf.Section("core").Options.Get("excludesfile")
110+
ifexcludes=="" {
111+
returnnil,nil
112+
}
113+
114+
returnreadIgnoreFile(fileSystem,"",excludes)
115+
}
116+
117+
funcLoadGlobalPatterns(fileSystem afero.Fs) ([]gitignore.Pattern,error) {
118+
home,err:=os.UserHomeDir()
119+
iferr!=nil {
120+
returnnil,err
121+
}
122+
123+
returnloadPatterns(fileSystem,filepath.Join(home,gitconfigFile))
124+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package ignore_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/coder/coder/v2/agent/agentcontainers/ignore"
10+
)
11+
12+
funcTestFilePathToParts(t*testing.T) {
13+
t.Parallel()
14+
15+
tests:= []struct {
16+
pathstring
17+
expected []string
18+
}{
19+
{"", []string{}},
20+
{"/", []string{}},
21+
{"foo", []string{"foo"}},
22+
{"/foo", []string{"foo"}},
23+
{"./foo/bar", []string{"foo","bar"}},
24+
{"../foo/bar", []string{"..","foo","bar"}},
25+
{"foo/bar/baz", []string{"foo","bar","baz"}},
26+
{"/foo/bar/baz", []string{"foo","bar","baz"}},
27+
{"foo/../bar", []string{"bar"}},
28+
}
29+
30+
for_,tt:=rangetests {
31+
t.Run(fmt.Sprintf("`%s`",tt.path),func(t*testing.T) {
32+
t.Parallel()
33+
34+
parts:=ignore.FilePathToParts(tt.path)
35+
require.Equal(t,tt.expected,parts)
36+
})
37+
}
38+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp