From 1217e2aa424f3bf074fa46c6b30efc3df774ba54 Mon Sep 17 00:00:00 2001 From: "hiroto.toyoda" Date: Mon, 19 Oct 2026 23:38:24 +0900 Subject: [PATCH] fix(watch): ignore .git at any depth inside the watched path Signed-off-by: hiroto.toyoda --- pkg/compose/watch.go | 2 +- pkg/compose/watch_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/pkg/compose/watch.go b/pkg/compose/watch.go index e9b471c87f5..2893965ffcc 100644 --- a/pkg/compose/watch.go +++ b/pkg/compose/watch.go @@ -314,7 +314,7 @@ func getWatchRules(config *types.DevelopConfig, service types.ServiceConfig) ([] // add a hardcoded set of ignores on top of what came from .dockerignore // some of this should likely be configurable (e.g. there could be cases // where you want `.git` to be synced) but this is suitable for now - dotGitIgnore, err := watch.NewDockerPatternMatcher("/", []string{".git/"}) + dotGitIgnore, err := watch.NewDockerPatternMatcher("/", []string{"**/.git"}) if err != nil { return nil, err } diff --git a/pkg/compose/watch_test.go b/pkg/compose/watch_test.go index 48a44987b98..bfce6e51be8 100644 --- a/pkg/compose/watch_test.go +++ b/pkg/compose/watch_test.go @@ -177,6 +177,34 @@ func TestWatch_Sync(t *testing.T) { }) } +// #14362: the .git ignore rule only matched /.git, not .git inside the watched path. +func TestWatchRules_IgnoreGitDirectoryAtAnyDepth(t *testing.T) { + root := t.TempDir() + rules, err := getWatchRules(&types.DevelopConfig{ + Watch: []types.Trigger{{Path: root, Action: "rebuild"}}, + }, types.ServiceConfig{Name: "test"}) + assert.NilError(t, err) + assert.Equal(t, len(rules), 1) + + for _, tt := range []struct { + path string + ignored bool + }{ + {path: ".git", ignored: true}, + {path: ".git/index", ignored: true}, + {path: ".git/objects/ab/cdef", ignored: true}, + {path: "vendor/lib/.git/HEAD", ignored: true}, + {path: "main.go", ignored: false}, + {path: ".gitignore", ignored: false}, + {path: ".github/workflows/ci.yml", ignored: false}, + } { + t.Run(tt.path, func(t *testing.T) { + mapping := rules[0].Matches(watch.NewFileEvent(filepath.Join(root, filepath.FromSlash(tt.path)))) + assert.Equal(t, mapping == nil, tt.ignored) + }) + } +} + // A rebuild running in the background from an earlier batch must not be // raced by a plain restart of the same service triggered by a later batch: // the restart is folded into the scheduler instead — the stale run is