Content-Length: 34054 | pFad | http://github.com/coder/coder/pull/19016.patch

thub.com From 8f7bb3497d61cb519880f4b6c882a454eb531f28 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 23 Jul 2025 17:33:05 +0000 Subject: [PATCH 01/13] fix(agent/agentcontainers): respect ignore files --- agent/agentcontainers/api.go | 22 ++++++++ agent/agentcontainers/api_test.go | 40 ++++++++++++++ agent/agentcontainers/ignore/dir.go | 68 ++++++++++++++++++++++++ agent/agentcontainers/ignore/dir_test.go | 46 ++++++++++++++++ go.mod | 7 ++- go.sum | 2 + 6 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 agent/agentcontainers/ignore/dir.go create mode 100644 agent/agentcontainers/ignore/dir_test.go diff --git a/agent/agentcontainers/api.go b/agent/agentcontainers/api.go index 10020e4ec5c30..5297e7ffbf10a 100644 --- a/agent/agentcontainers/api.go +++ b/agent/agentcontainers/api.go @@ -21,11 +21,13 @@ import ( "github.com/fsnotify/fsnotify" "github.com/go-chi/chi/v5" + "github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/google/uuid" "github.com/spf13/afero" "golang.org/x/xerrors" "cdr.dev/slog" + "github.com/coder/coder/v2/agent/agentcontainers/ignore" "github.com/coder/coder/v2/agent/agentcontainers/watcher" "github.com/coder/coder/v2/agent/agentexec" "github.com/coder/coder/v2/agent/usershell" @@ -469,13 +471,33 @@ func (api *API) discoverDevcontainerProjects() error { } func (api *API) discoverDevcontainersInProject(projectPath string) error { + patterns, err := ignore.ReadPatterns(api.fs, projectPath) + if err != nil { + return fmt.Errorf("read git ignore patterns: %w", err) + } + + matcher := gitignore.NewMatcher(patterns) + devcontainerConfigPaths := []string{ "/.devcontainer/devcontainer.json", "/.devcontainer.json", } return afero.Walk(api.fs, projectPath, func(path string, info fs.FileInfo, _ error) error { + pathParts := ignore.FilePathToParts(path) + + // We know that a directory entry cannot be a `devcontainer.json` file, so we + // always skip processing directories. If the directory happens to be ignored + // by git then we'll make sure to ignore all of the children of that directory. if info.IsDir() { + if matcher.Match(pathParts, true) { + return fs.SkipDir + } + + return nil + } + + if matcher.Match(pathParts, false) { return nil } diff --git a/agent/agentcontainers/api_test.go b/agent/agentcontainers/api_test.go index 7387d9a17aba9..362a12d5bcf22 100644 --- a/agent/agentcontainers/api_test.go +++ b/agent/agentcontainers/api_test.go @@ -3345,6 +3345,46 @@ func TestDevcontainerDiscovery(t *testing.T) { }, }, }, + { + name: "RespectGitIgnore", + agentDir: "/home/coder", + fs: map[string]string{ + "/home/coder/coder/.git/HEAD": "", + "/home/coder/coder/.gitignore": "y/", + "/home/coder/coder/.devcontainer.json": "", + "/home/coder/coder/x/y/.devcontainer.json": "", + }, + expected: []codersdk.WorkspaceAgentDevcontainer{ + { + WorkspaceFolder: "/home/coder/coder", + ConfigPath: "/home/coder/coder/.devcontainer.json", + Status: codersdk.WorkspaceAgentDevcontainerStatusStopped, + }, + }, + }, + { + name: "RespectNestedGitIgnore", + agentDir: "/home/coder", + fs: map[string]string{ + "/home/coder/coder/.git/HEAD": "", + "/home/coder/coder/.devcontainer.json": "", + "/home/coder/coder/y/.devcontainer.json": "", + "/home/coder/coder/x/.gitignore": "y/", + "/home/coder/coder/x/y/.devcontainer.json": "", + }, + expected: []codersdk.WorkspaceAgentDevcontainer{ + { + WorkspaceFolder: "/home/coder/coder", + ConfigPath: "/home/coder/coder/.devcontainer.json", + Status: codersdk.WorkspaceAgentDevcontainerStatusStopped, + }, + { + WorkspaceFolder: "/home/coder/coder/y", + ConfigPath: "/home/coder/coder/y/.devcontainer.json", + Status: codersdk.WorkspaceAgentDevcontainerStatusStopped, + }, + }, + }, } initFS := func(t *testing.T, files map[string]string) afero.Fs { diff --git a/agent/agentcontainers/ignore/dir.go b/agent/agentcontainers/ignore/dir.go new file mode 100644 index 0000000000000..e9e32f5f9a933 --- /dev/null +++ b/agent/agentcontainers/ignore/dir.go @@ -0,0 +1,68 @@ +package ignore + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/go-git/go-git/v5/plumbing/format/gitignore" + "github.com/spf13/afero" +) + +func FilePathToParts(path string) []string { + components := []string{} + + if path == "" { + return []string{} + } + + for segment := range strings.SplitSeq(filepath.Clean(path), "/") { + if segment != "" { + components = append(components, segment) + } + } + + return components +} + +func readIgnoreFile(fs afero.Fs, path string) ([]gitignore.Pattern, error) { + var ps []gitignore.Pattern + + data, err := afero.ReadFile(fs, filepath.Join(path, ".gitignore")) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, err + } + + for s := range strings.SplitSeq(string(data), "\n") { + if !strings.HasPrefix(s, "#") && len(strings.TrimSpace(s)) > 0 { + ps = append(ps, gitignore.ParsePattern(s, FilePathToParts(path))) + } + } + + return ps, nil +} + +func ReadPatterns(fileSystem afero.Fs, path string) ([]gitignore.Pattern, error) { + ps, _ := readIgnoreFile(fileSystem, path) + + if err := afero.Walk(fileSystem, path, func(path string, info fs.FileInfo, err error) error { + if !info.IsDir() { + return nil + } + + subPs, err := readIgnoreFile(fileSystem, path) + if err != nil { + return err + } + + ps = append(ps, subPs...) + + return nil + }); err != nil { + return nil, err + } + + return ps, nil +} diff --git a/agent/agentcontainers/ignore/dir_test.go b/agent/agentcontainers/ignore/dir_test.go new file mode 100644 index 0000000000000..dad0bce25a657 --- /dev/null +++ b/agent/agentcontainers/ignore/dir_test.go @@ -0,0 +1,46 @@ +package ignore_test + +import ( + "fmt" + "testing" + + "github.com/coder/coder/v2/agent/agentcontainers/ignore" + "github.com/stretchr/testify/require" +) + +func TestFilePathToParts(t *testing.T) { + t.Parallel() + + tests := []struct { + path string + expected []string + }{ + {"/foo/bar/baz", []string{"foo", "bar", "baz"}}, + {"foo/bar/baz", []string{"foo", "bar", "baz"}}, + {"/foo", []string{"foo"}}, + {"foo", []string{"foo"}}, + {"/", []string{}}, + {"", []string{}}, + {"./foo/bar", []string{"foo", "bar"}}, + {"../foo/bar", []string{"..", "foo", "bar"}}, + {"/foo//bar//github.com/baz", []string{"foo", "bar", "baz"}}, + {"foo/bar/", []string{"foo", "bar"}}, + {"/foo/bar/", []string{"foo", "bar"}}, + {"foo/../bar", []string{"bar"}}, + {"./foo/../bar", []string{"bar"}}, + {"/foo/../bar", []string{"bar"}}, + {"foo/./bar", []string{"foo", "bar"}}, + {"/foo/./bar", []string{"foo", "bar"}}, + {"a/b/c/d/e", []string{"a", "b", "c", "d", "e"}}, + {"/a/b/c/d/e", []string{"a", "b", "c", "d", "e"}}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("`%s`", tt.path), func(t *testing.T) { + t.Parallel() + + parts := ignore.FilePathToParts(tt.path) + require.Equal(t, tt.expected, parts) + }) + } +} diff --git a/go.mod b/go.mod index bf367187d488c..bb6d9f259d68c 100644 --- a/go.mod +++ b/go.mod @@ -122,7 +122,7 @@ require ( github.com/fergusstrange/embedded-postgres v1.31.0 github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa github.com/gen2brain/beeep v0.11.1 - github.com/gliderlabs/ssh v0.3.4 + github.com/gliderlabs/ssh v0.3.8 github.com/go-chi/chi/v5 v5.2.2 github.com/go-chi/cors v1.2.1 github.com/go-chi/httprate v0.15.0 @@ -512,10 +512,14 @@ require ( github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/esiqveland/notify v0.13.3 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.16.2 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/hashicorp/go-getter v1.7.8 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/jackmordaunt/icns/v3 v3.0.1 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect @@ -535,5 +539,6 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect google.golang.org/genai v1.12.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect ) diff --git a/go.sum b/go.sum index ff5c603c3db18..cc7e255612582 100644 --- a/go.sum +++ b/go.sum @@ -1102,6 +1102,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ= github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM= +github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= From 5cb9e5c6741d48a80562734f7c14793873645aee Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 23 Jul 2025 17:37:34 +0000 Subject: [PATCH 02/13] chore: run `go mod tidy` --- go.mod | 2 +- go.sum | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bb6d9f259d68c..b6090694010d5 100644 --- a/go.mod +++ b/go.mod @@ -484,6 +484,7 @@ require ( github.com/coder/aisdk-go v0.0.9 github.com/coder/preview v1.0.3-0.20250714153828-a737d4750448 github.com/fsnotify/fsnotify v1.9.0 + github.com/go-git/go-git/v5 v5.16.2 github.com/mark3labs/mcp-go v0.34.0 ) @@ -514,7 +515,6 @@ require ( github.com/esiqveland/notify v0.13.3 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-git/go-git/v5 v5.16.2 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/hashicorp/go-getter v1.7.8 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect diff --git a/go.sum b/go.sum index cc7e255612582..867d5478530f1 100644 --- a/go.sum +++ b/go.sum @@ -1100,8 +1100,6 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= -github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ= -github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM= github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= From e39edf29f15824f3c366b2073dd697284c9de58d Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 23 Jul 2025 17:40:39 +0000 Subject: [PATCH 03/13] chore: appease linter --- agent/agentcontainers/api.go | 2 +- agent/agentcontainers/ignore/dir.go | 6 +++--- agent/agentcontainers/ignore/dir_test.go | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/agent/agentcontainers/api.go b/agent/agentcontainers/api.go index 5297e7ffbf10a..bace76181b7b0 100644 --- a/agent/agentcontainers/api.go +++ b/agent/agentcontainers/api.go @@ -473,7 +473,7 @@ func (api *API) discoverDevcontainerProjects() error { func (api *API) discoverDevcontainersInProject(projectPath string) error { patterns, err := ignore.ReadPatterns(api.fs, projectPath) if err != nil { - return fmt.Errorf("read git ignore patterns: %w", err) + return xerrors.Errorf("read git ignore patterns: %w", err) } matcher := gitignore.NewMatcher(patterns) diff --git a/agent/agentcontainers/ignore/dir.go b/agent/agentcontainers/ignore/dir.go index e9e32f5f9a933..d5e3800846396 100644 --- a/agent/agentcontainers/ignore/dir.go +++ b/agent/agentcontainers/ignore/dir.go @@ -27,10 +27,10 @@ func FilePathToParts(path string) []string { return components } -func readIgnoreFile(fs afero.Fs, path string) ([]gitignore.Pattern, error) { +func readIgnoreFile(fileSystem afero.Fs, path string) ([]gitignore.Pattern, error) { var ps []gitignore.Pattern - data, err := afero.ReadFile(fs, filepath.Join(path, ".gitignore")) + data, err := afero.ReadFile(fileSystem, filepath.Join(path, ".gitignore")) if err != nil && !errors.Is(err, os.ErrNotExist) { return nil, err } @@ -47,7 +47,7 @@ func readIgnoreFile(fs afero.Fs, path string) ([]gitignore.Pattern, error) { func ReadPatterns(fileSystem afero.Fs, path string) ([]gitignore.Pattern, error) { ps, _ := readIgnoreFile(fileSystem, path) - if err := afero.Walk(fileSystem, path, func(path string, info fs.FileInfo, err error) error { + if err := afero.Walk(fileSystem, path, func(path string, info fs.FileInfo, _ error) error { if !info.IsDir() { return nil } diff --git a/agent/agentcontainers/ignore/dir_test.go b/agent/agentcontainers/ignore/dir_test.go index dad0bce25a657..560c5ca7b10c7 100644 --- a/agent/agentcontainers/ignore/dir_test.go +++ b/agent/agentcontainers/ignore/dir_test.go @@ -4,8 +4,9 @@ import ( "fmt" "testing" - "github.com/coder/coder/v2/agent/agentcontainers/ignore" "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/agent/agentcontainers/ignore" ) func TestFilePathToParts(t *testing.T) { From a56c82752722a9375100874f5403813a445d29c2 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 23 Jul 2025 17:53:16 +0000 Subject: [PATCH 04/13] fix: test on windows --- agent/agentcontainers/ignore/dir.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/agentcontainers/ignore/dir.go b/agent/agentcontainers/ignore/dir.go index d5e3800846396..3244e3152795f 100644 --- a/agent/agentcontainers/ignore/dir.go +++ b/agent/agentcontainers/ignore/dir.go @@ -18,7 +18,7 @@ func FilePathToParts(path string) []string { return []string{} } - for segment := range strings.SplitSeq(filepath.Clean(path), "/") { + for segment := range strings.SplitSeq(filepath.Clean(path), string(filepath.Separator)) { if segment != "" { components = append(components, segment) } From f5d16eae5dd6367ca4f0d3ba7914cfd581fe2c21 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 23 Jul 2025 18:45:04 +0000 Subject: [PATCH 05/13] chore: remove initial call the initial call duplicates work done in Walk --- agent/agentcontainers/ignore/dir.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/agentcontainers/ignore/dir.go b/agent/agentcontainers/ignore/dir.go index 3244e3152795f..e6a6c12ed5614 100644 --- a/agent/agentcontainers/ignore/dir.go +++ b/agent/agentcontainers/ignore/dir.go @@ -45,7 +45,7 @@ func readIgnoreFile(fileSystem afero.Fs, path string) ([]gitignore.Pattern, erro } func ReadPatterns(fileSystem afero.Fs, path string) ([]gitignore.Pattern, error) { - ps, _ := readIgnoreFile(fileSystem, path) + var ps []gitignore.Pattern if err := afero.Walk(fileSystem, path, func(path string, info fs.FileInfo, _ error) error { if !info.IsDir() { From e134b47f7929cf5df59035d8425a87e8c36fff32 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 24 Jul 2025 08:36:30 +0000 Subject: [PATCH 06/13] chore: `[]string{}` -> `components` --- agent/agentcontainers/ignore/dir.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/agentcontainers/ignore/dir.go b/agent/agentcontainers/ignore/dir.go index e6a6c12ed5614..e3c2c2ce94208 100644 --- a/agent/agentcontainers/ignore/dir.go +++ b/agent/agentcontainers/ignore/dir.go @@ -15,7 +15,7 @@ func FilePathToParts(path string) []string { components := []string{} if path == "" { - return []string{} + return components } for segment := range strings.SplitSeq(filepath.Clean(path), string(filepath.Separator)) { From 8e6c3f3e431b6aca95e0d51e57d737f22976959d Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 24 Jul 2025 08:36:49 +0000 Subject: [PATCH 07/13] chore: remove duplicated test cases for `TestFilePathToParts` --- agent/agentcontainers/ignore/dir_test.go | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/agent/agentcontainers/ignore/dir_test.go b/agent/agentcontainers/ignore/dir_test.go index 560c5ca7b10c7..2af54cf63930d 100644 --- a/agent/agentcontainers/ignore/dir_test.go +++ b/agent/agentcontainers/ignore/dir_test.go @@ -16,24 +16,15 @@ func TestFilePathToParts(t *testing.T) { path string expected []string }{ - {"/foo/bar/baz", []string{"foo", "bar", "baz"}}, - {"foo/bar/baz", []string{"foo", "bar", "baz"}}, - {"/foo", []string{"foo"}}, - {"foo", []string{"foo"}}, - {"/", []string{}}, {"", []string{}}, + {"/", []string{}}, + {"foo", []string{"foo"}}, + {"/foo", []string{"foo"}}, {"./foo/bar", []string{"foo", "bar"}}, {"../foo/bar", []string{"..", "foo", "bar"}}, - {"/foo//bar//github.com/baz", []string{"foo", "bar", "baz"}}, - {"foo/bar/", []string{"foo", "bar"}}, - {"/foo/bar/", []string{"foo", "bar"}}, + {"foo/bar/baz", []string{"foo", "bar", "baz"}}, + {"/foo/bar/baz", []string{"foo", "bar", "baz"}}, {"foo/../bar", []string{"bar"}}, - {"./foo/../bar", []string{"bar"}}, - {"/foo/../bar", []string{"bar"}}, - {"foo/./bar", []string{"foo", "bar"}}, - {"/foo/./bar", []string{"foo", "bar"}}, - {"a/b/c/d/e", []string{"a", "b", "c", "d", "e"}}, - {"/a/b/c/d/e", []string{"a", "b", "c", "d", "e"}}, } for _, tt := range tests { From d787bf6e1d404251afde8c423d30fa7269bbb94d Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 24 Jul 2025 08:46:20 +0000 Subject: [PATCH 08/13] fix: respect `.git/info/exclude` file --- agent/agentcontainers/api_test.go | 17 +++++++++++++++++ agent/agentcontainers/ignore/dir.go | 18 +++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/agent/agentcontainers/api_test.go b/agent/agentcontainers/api_test.go index 362a12d5bcf22..65d405eabc6a2 100644 --- a/agent/agentcontainers/api_test.go +++ b/agent/agentcontainers/api_test.go @@ -3385,6 +3385,23 @@ func TestDevcontainerDiscovery(t *testing.T) { }, }, }, + { + name: "RespectGitInfoExclude", + agentDir: "/home/coder", + fs: map[string]string{ + "/home/coder/coder/.git/HEAD": "", + "/home/coder/coder/.git/info/exclude": "y/", + "/home/coder/coder/.devcontainer.json": "", + "/home/coder/coder/x/y/.devcontainer.json": "", + }, + expected: []codersdk.WorkspaceAgentDevcontainer{ + { + WorkspaceFolder: "/home/coder/coder", + ConfigPath: "/home/coder/coder/.devcontainer.json", + Status: codersdk.WorkspaceAgentDevcontainerStatusStopped, + }, + }, + }, } initFS := func(t *testing.T, files map[string]string) afero.Fs { diff --git a/agent/agentcontainers/ignore/dir.go b/agent/agentcontainers/ignore/dir.go index e3c2c2ce94208..093d7fa5cd893 100644 --- a/agent/agentcontainers/ignore/dir.go +++ b/agent/agentcontainers/ignore/dir.go @@ -11,6 +11,11 @@ import ( "github.com/spf13/afero" ) +const ( + gitignoreFile = ".gitignore" + gitInfoExcludeFile = ".git/info/exclude" +) + func FilePathToParts(path string) []string { components := []string{} @@ -27,10 +32,10 @@ func FilePathToParts(path string) []string { return components } -func readIgnoreFile(fileSystem afero.Fs, path string) ([]gitignore.Pattern, error) { +func readIgnoreFile(fileSystem afero.Fs, path, ignore string) ([]gitignore.Pattern, error) { var ps []gitignore.Pattern - data, err := afero.ReadFile(fileSystem, filepath.Join(path, ".gitignore")) + data, err := afero.ReadFile(fileSystem, filepath.Join(path, ignore)) if err != nil && !errors.Is(err, os.ErrNotExist) { return nil, err } @@ -47,12 +52,19 @@ func readIgnoreFile(fileSystem afero.Fs, path string) ([]gitignore.Pattern, erro func ReadPatterns(fileSystem afero.Fs, path string) ([]gitignore.Pattern, error) { var ps []gitignore.Pattern + subPs, err := readIgnoreFile(fileSystem, path, gitInfoExcludeFile) + if err != nil { + return nil, err + } + + ps = append(ps, subPs...) + if err := afero.Walk(fileSystem, path, func(path string, info fs.FileInfo, _ error) error { if !info.IsDir() { return nil } - subPs, err := readIgnoreFile(fileSystem, path) + subPs, err := readIgnoreFile(fileSystem, path, gitignoreFile) if err != nil { return err } From 0a438a87ce7192b68bbd1773090a67a5b42a3ba1 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 24 Jul 2025 09:08:16 +0000 Subject: [PATCH 09/13] fix: respect `~/.gitconfig`'s `core.excludesFile` option --- agent/agentcontainers/api.go | 4 +++- agent/agentcontainers/api_test.go | 28 +++++++++++++++++++++++- agent/agentcontainers/ignore/dir.go | 34 +++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/agent/agentcontainers/api.go b/agent/agentcontainers/api.go index bace76181b7b0..1d216ed1ce4e1 100644 --- a/agent/agentcontainers/api.go +++ b/agent/agentcontainers/api.go @@ -471,12 +471,14 @@ func (api *API) discoverDevcontainerProjects() error { } func (api *API) discoverDevcontainersInProject(projectPath string) error { + globalPatterns, err := ignore.LoadGlobalPatterns(api.fs) + patterns, err := ignore.ReadPatterns(api.fs, projectPath) if err != nil { return xerrors.Errorf("read git ignore patterns: %w", err) } - matcher := gitignore.NewMatcher(patterns) + matcher := gitignore.NewMatcher(append(globalPatterns, patterns...)) devcontainerConfigPaths := []string{ "/.devcontainer/devcontainer.json", diff --git a/agent/agentcontainers/api_test.go b/agent/agentcontainers/api_test.go index 65d405eabc6a2..3f852411e4457 100644 --- a/agent/agentcontainers/api_test.go +++ b/agent/agentcontainers/api_test.go @@ -9,6 +9,7 @@ import ( "net/http/httptest" "os" "os/exec" + "path/filepath" "runtime" "slices" "strings" @@ -3211,6 +3212,9 @@ func TestDevcontainerDiscovery(t *testing.T) { // repositories to find any `.devcontainer/devcontainer.json` // files. These tests are to validate that behavior. + homeDir, err := os.UserHomeDir() + require.NoError(t, err) + tests := []struct { name string agentDir string @@ -3402,6 +3406,28 @@ func TestDevcontainerDiscovery(t *testing.T) { }, }, }, + { + name: "RespectHomeGitConfig", + agentDir: homeDir, + fs: map[string]string{ + "/tmp/.gitignore": "node_modules/", + filepath.Join(homeDir, ".gitconfig"): ` + [core] + excludesFile = /tmp/.gitignore + `, + + filepath.Join(homeDir, ".git/HEAD"): "", + filepath.Join(homeDir, ".devcontainer.json"): "", + filepath.Join(homeDir, "node_modules/y/.devcontainer.json"): "", + }, + expected: []codersdk.WorkspaceAgentDevcontainer{ + { + WorkspaceFolder: homeDir, + ConfigPath: filepath.Join(homeDir, ".devcontainer.json"), + Status: codersdk.WorkspaceAgentDevcontainerStatusStopped, + }, + }, + }, } initFS := func(t *testing.T, files map[string]string) afero.Fs { @@ -3454,7 +3480,7 @@ func TestDevcontainerDiscovery(t *testing.T) { err := json.NewDecoder(rec.Body).Decode(&got) require.NoError(t, err) - return len(got.Devcontainers) == len(tt.expected) + return len(got.Devcontainers) >= len(tt.expected) }, testutil.WaitShort, testutil.IntervalFast, "dev containers never found") // Now projects have been discovered, we'll allow the updater loop diff --git a/agent/agentcontainers/ignore/dir.go b/agent/agentcontainers/ignore/dir.go index 093d7fa5cd893..298a2b8cf6ca9 100644 --- a/agent/agentcontainers/ignore/dir.go +++ b/agent/agentcontainers/ignore/dir.go @@ -1,17 +1,21 @@ package ignore import ( + "bytes" "errors" "io/fs" "os" "path/filepath" "strings" + "github.com/go-git/go-git/v5/plumbing/format/config" "github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/spf13/afero" + "golang.org/x/xerrors" ) const ( + gitconfigFile = ".gitconfig" gitignoreFile = ".gitignore" gitInfoExcludeFile = ".git/info/exclude" ) @@ -78,3 +82,33 @@ func ReadPatterns(fileSystem afero.Fs, path string) ([]gitignore.Pattern, error) return ps, nil } + +func loadPatterns(fileSystem afero.Fs, path string) ([]gitignore.Pattern, error) { + data, err := afero.ReadFile(fileSystem, path) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, err + } + + decoder := config.NewDecoder(bytes.NewBuffer(data)) + + conf := config.New() + if err := decoder.Decode(conf); err != nil { + return nil, xerrors.Errorf("decode config: %w", err) + } + + excludes := conf.Section("core").Options.Get("excludesfile") + if excludes == "" { + return nil, nil + } + + return readIgnoreFile(fileSystem, "", excludes) +} + +func LoadGlobalPatterns(fileSystem afero.Fs) ([]gitignore.Pattern, error) { + home, err := os.UserHomeDir() + if err != nil { + return nil, err + } + + return loadPatterns(fileSystem, filepath.Join(home, gitconfigFile)) +} From e344d814fd78b2f1d5afa0ae2c9707696af58cb9 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 24 Jul 2025 09:11:46 +0000 Subject: [PATCH 10/13] chore: oops, forgot my error handling --- agent/agentcontainers/api.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/agent/agentcontainers/api.go b/agent/agentcontainers/api.go index 1d216ed1ce4e1..617c3d8a68f72 100644 --- a/agent/agentcontainers/api.go +++ b/agent/agentcontainers/api.go @@ -472,6 +472,9 @@ func (api *API) discoverDevcontainerProjects() error { func (api *API) discoverDevcontainersInProject(projectPath string) error { globalPatterns, err := ignore.LoadGlobalPatterns(api.fs) + if err != nil { + return xerrors.Errorf("read global git ignore patterns: %w", err) + } patterns, err := ignore.ReadPatterns(api.fs, projectPath) if err != nil { From 27a735e75b44f1e755991215004d6b6cb8ecac55 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 24 Jul 2025 09:36:18 +0000 Subject: [PATCH 11/13] test: ensure we ignore nonsense dev container names --- agent/agentcontainers/api_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/agent/agentcontainers/api_test.go b/agent/agentcontainers/api_test.go index 3f852411e4457..5714027960a7b 100644 --- a/agent/agentcontainers/api_test.go +++ b/agent/agentcontainers/api_test.go @@ -3428,6 +3428,34 @@ func TestDevcontainerDiscovery(t *testing.T) { }, }, }, + { + name: "IgnoreNonsenseDevcontainerNames", + agentDir: "/home/coder", + fs: map[string]string{ + "/home/coder/.git/HEAD": "", + + "/home/coder/.devcontainer/devcontainer.json.bak": "", + "/home/coder/.devcontainer/devcontainer.json.old": "", + "/home/coder/.devcontainer/devcontainer.json~": "", + "/home/coder/.devcontainer/notdevcontainer.json": "", + "/home/coder/.devcontainer/devcontainer.json.swp": "", + + "/home/coder/foo/.devcontainer.json.bak": "", + "/home/coder/foo/.devcontainer.json.old": "", + "/home/coder/foo/.devcontainer.json~": "", + "/home/coder/foo/.notdevcontainer.json": "", + "/home/coder/foo/.devcontainer.json.swp": "", + + "/home/coder/bar/.devcontainer.json": "", + }, + expected: []codersdk.WorkspaceAgentDevcontainer{ + { + WorkspaceFolder: "/home/coder/bar", + ConfigPath: "/home/coder/bar/.devcontainer.json", + Status: codersdk.WorkspaceAgentDevcontainerStatusStopped, + }, + }, + }, } initFS := func(t *testing.T, files map[string]string) afero.Fs { From 7d8a7966153a3d5432e2050f1897b759092c5f83 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 24 Jul 2025 09:42:12 +0000 Subject: [PATCH 12/13] chore: add some error logging --- agent/agentcontainers/api.go | 19 +++++++++++++++---- agent/agentcontainers/ignore/dir.go | 13 +++++++++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/agent/agentcontainers/api.go b/agent/agentcontainers/api.go index 617c3d8a68f72..4f9287713fcfc 100644 --- a/agent/agentcontainers/api.go +++ b/agent/agentcontainers/api.go @@ -471,12 +471,16 @@ func (api *API) discoverDevcontainerProjects() error { } func (api *API) discoverDevcontainersInProject(projectPath string) error { + logger := api.logger. + Named("project-discovery"). + With(slog.F("project_path", projectPath)) + globalPatterns, err := ignore.LoadGlobalPatterns(api.fs) if err != nil { return xerrors.Errorf("read global git ignore patterns: %w", err) } - patterns, err := ignore.ReadPatterns(api.fs, projectPath) + patterns, err := ignore.ReadPatterns(api.ctx, logger, api.fs, projectPath) if err != nil { return xerrors.Errorf("read git ignore patterns: %w", err) } @@ -488,7 +492,14 @@ func (api *API) discoverDevcontainersInProject(projectPath string) error { "/.devcontainer.json", } - return afero.Walk(api.fs, projectPath, func(path string, info fs.FileInfo, _ error) error { + return afero.Walk(api.fs, projectPath, func(path string, info fs.FileInfo, err error) error { + if err != nil { + logger.Error(api.ctx, "encountered error while walking for dev container projects", + slog.F("path", path), + slog.Error(err)) + return nil + } + pathParts := ignore.FilePathToParts(path) // We know that a directory entry cannot be a `devcontainer.json` file, so we @@ -513,11 +524,11 @@ func (api *API) discoverDevcontainersInProject(projectPath string) error { workspaceFolder := strings.TrimSuffix(path, relativeConfigPath) - api.logger.Debug(api.ctx, "discovered dev container project", slog.F("workspace_folder", workspaceFolder)) + logger.Debug(api.ctx, "discovered dev container project", slog.F("workspace_folder", workspaceFolder)) api.mu.Lock() if _, found := api.knownDevcontainers[workspaceFolder]; !found { - api.logger.Debug(api.ctx, "adding dev container project", slog.F("workspace_folder", workspaceFolder)) + logger.Debug(api.ctx, "adding dev container project", slog.F("workspace_folder", workspaceFolder)) dc := codersdk.WorkspaceAgentDevcontainer{ ID: uuid.New(), diff --git a/agent/agentcontainers/ignore/dir.go b/agent/agentcontainers/ignore/dir.go index 298a2b8cf6ca9..1bbbb34fe702c 100644 --- a/agent/agentcontainers/ignore/dir.go +++ b/agent/agentcontainers/ignore/dir.go @@ -2,12 +2,14 @@ package ignore import ( "bytes" + "context" "errors" "io/fs" "os" "path/filepath" "strings" + "cdr.dev/slog" "github.com/go-git/go-git/v5/plumbing/format/config" "github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/spf13/afero" @@ -53,7 +55,7 @@ func readIgnoreFile(fileSystem afero.Fs, path, ignore string) ([]gitignore.Patte return ps, nil } -func ReadPatterns(fileSystem afero.Fs, path string) ([]gitignore.Pattern, error) { +func ReadPatterns(ctx context.Context, logger slog.Logger, fileSystem afero.Fs, path string) ([]gitignore.Pattern, error) { var ps []gitignore.Pattern subPs, err := readIgnoreFile(fileSystem, path, gitInfoExcludeFile) @@ -63,7 +65,14 @@ func ReadPatterns(fileSystem afero.Fs, path string) ([]gitignore.Pattern, error) ps = append(ps, subPs...) - if err := afero.Walk(fileSystem, path, func(path string, info fs.FileInfo, _ error) error { + if err := afero.Walk(fileSystem, path, func(path string, info fs.FileInfo, err error) error { + if err != nil { + logger.Error(ctx, "encountered error while walking for git ignore files", + slog.F("path", path), + slog.Error(err)) + return nil + } + if !info.IsDir() { return nil } From cd3be6cfb152b8d4a991d21886b3bed465ee29a7 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 24 Jul 2025 09:50:55 +0000 Subject: [PATCH 13/13] chore: appease the linter --- agent/agentcontainers/ignore/dir.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agent/agentcontainers/ignore/dir.go b/agent/agentcontainers/ignore/dir.go index 1bbbb34fe702c..d97e2ef2235a3 100644 --- a/agent/agentcontainers/ignore/dir.go +++ b/agent/agentcontainers/ignore/dir.go @@ -9,11 +9,12 @@ import ( "path/filepath" "strings" - "cdr.dev/slog" "github.com/go-git/go-git/v5/plumbing/format/config" "github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/spf13/afero" "golang.org/x/xerrors" + + "cdr.dev/slog" ) const (








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/coder/coder/pull/19016.patch

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy