Merge pull request #10311 from milas/fw-ephemeral

watch: ignore ephemeral files & minor output tweaks
This commit is contained in:
Milas Bowman 2023-02-27 09:26:18 -05:00 committed by GitHub
commit d4f156cc7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 122 additions and 11 deletions

View File

@ -22,14 +22,15 @@ import (
"time"
"github.com/compose-spec/compose-go/types"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/utils"
"github.com/docker/compose/v2/pkg/watch"
"github.com/jonboulle/clockwork"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/utils"
"github.com/docker/compose/v2/pkg/watch"
)
type DevelopmentConfig struct {
@ -82,11 +83,24 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
}
bc := service.Build.Context
ignore, err := watch.LoadDockerIgnore(bc)
dockerIgnores, err := watch.LoadDockerIgnore(bc)
if err != nil {
return err
}
// 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/"})
if err != nil {
return err
}
ignore := watch.NewCompositeMatcher(
dockerIgnores,
watch.EphemeralPathMatcher,
dotGitIgnore,
)
watcher, err := watch.NewWatcher([]string{bc}, ignore)
if err != nil {
return err
@ -109,7 +123,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
path := event.Path()
for _, trigger := range config.Watch {
logrus.Debugf("change deteced on %s - comparing with %s", path, trigger.Path)
logrus.Debugf("change detected on %s - comparing with %s", path, trigger.Path)
if watch.IsChild(trigger.Path, path) {
fmt.Fprintf(s.stderr(), "change detected on %s\n", path)
@ -126,7 +140,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
Destination: fmt.Sprintf("%s:%s", name, dest),
}
case WatchActionRebuild:
logrus.Debugf("modified file %s require image to be rebuilt", path)
logrus.Debugf("modified file %s requires image to be rebuilt", path)
needRebuild <- name
default:
return fmt.Errorf("watch action %q is not supported", trigger)
@ -176,7 +190,7 @@ func (s *composeService) makeRebuildFn(ctx context.Context, project *types.Proje
Services: services,
})
if err != nil {
fmt.Fprintf(s.stderr(), "Build failed")
fmt.Fprintf(s.stderr(), "Build failed\n")
}
for i, service := range project.Services {
if id, ok := imageIds[service.Name]; ok {
@ -196,7 +210,7 @@ func (s *composeService) makeRebuildFn(ctx context.Context, project *types.Proje
},
})
if err != nil {
fmt.Fprintf(s.stderr(), "Application failed to start after update")
fmt.Fprintf(s.stderr(), "Application failed to start after update\n")
}
}
}
@ -212,7 +226,7 @@ func (s *composeService) makeSyncFn(ctx context.Context, project *types.Project,
if err != nil {
return err
}
fmt.Fprintf(s.stderr(), "%s updated\n", opt.Source)
fmt.Fprintf(s.stderr(), "%s updated\n", opt.Destination)
}
}
}

60
pkg/watch/ephemeral.go Normal file
View File

@ -0,0 +1,60 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package watch
// EphemeralPathMatcher filters out spurious changes that we don't want to
// rebuild on, like IDE temp/lock files.
//
// This isn't an ideal solution. In an ideal world, the user would put
// everything to ignore in their tiltignore/dockerignore files. This is a
// stop-gap so they don't have a terrible experience if those files aren't
// there or aren't in the right places.
//
// https://app.clubhouse.io/windmill/story/691/filter-out-ephemeral-file-changes
var EphemeralPathMatcher = initEphemeralPathMatcher()
func initEphemeralPathMatcher() PathMatcher {
golandPatterns := []string{"**/*___jb_old___", "**/*___jb_tmp___", "**/.idea/**"}
emacsPatterns := []string{"**/.#*", "**/#*#"}
// if .swp is taken (presumably because multiple vims are running in that dir),
// vim will go with .swo, .swn, etc, and then even .svz, .svy!
// https://github.com/vim/vim/blob/ea781459b9617aa47335061fcc78403495260315/src/memline.c#L5076
// ignoring .sw? seems dangerous, since things like .swf or .swi exist, but ignoring the first few
// seems safe and should catch most cases
vimPatterns := []string{"**/4913", "**/*~", "**/.*.swp", "**/.*.swx", "**/.*.swo", "**/.*.swn"}
// kate (the default text editor for KDE) uses a file similar to Vim's .swp
// files, but it doesn't have the "incrememnting" character problem mentioned
// above
katePatterns := []string{"**/.*.kate-swp"}
// go stdlib creates tmpfiles to determine umask for setting permissions
// during file creation; they are then immediately deleted
// https://github.com/golang/go/blob/0b5218cf4e3e5c17344ea113af346e8e0836f6c4/src/cmd/go/internal/work/exec.go#L1764
goPatterns := []string{"**/*-go-tmp-umask"}
var allPatterns []string
allPatterns = append(allPatterns, golandPatterns...)
allPatterns = append(allPatterns, emacsPatterns...)
allPatterns = append(allPatterns, vimPatterns...)
allPatterns = append(allPatterns, katePatterns...)
allPatterns = append(allPatterns, goPatterns...)
matcher, err := NewDockerPatternMatcher("/", allPatterns)
if err != nil {
panic(err)
}
return matcher
}

View File

@ -106,3 +106,39 @@ func DesiredWindowsBufferSize() int {
func IsWindowsShortReadError(err error) bool {
return runtime.GOOS == "windows" && !errors.Is(err, fsnotify.ErrEventOverflow)
}
type CompositePathMatcher struct {
Matchers []PathMatcher
}
func NewCompositeMatcher(matchers ...PathMatcher) PathMatcher {
if len(matchers) == 0 {
return EmptyMatcher{}
}
return CompositePathMatcher{Matchers: matchers}
}
func (c CompositePathMatcher) Matches(f string) (bool, error) {
for _, t := range c.Matchers {
ret, err := t.Matches(f)
if err != nil {
return false, err
}
if ret {
return true, nil
}
}
return false, nil
}
func (c CompositePathMatcher) MatchesEntireDir(f string) (bool, error) {
for _, t := range c.Matchers {
matches, err := t.MatchesEntireDir(f)
if matches || err != nil {
return matches, err
}
}
return false, nil
}
var _ PathMatcher = CompositePathMatcher{}

View File

@ -20,7 +20,6 @@
package watch
import (
"fmt"
"path/filepath"
"time"
@ -53,7 +52,6 @@ func (d *fseventNotify) loop() {
}
for _, e := range events {
fmt.Println(e)
e.Path = filepath.Join("/", e.Path)
_, isPathWereWatching := d.pathsWereWatching[e.Path]
@ -67,6 +65,7 @@ func (d *fseventNotify) loop() {
if err != nil {
logrus.Infof("Error matching path %q: %v", e.Path, err)
} else if ignore {
logrus.Tracef("Ignoring event for path: %v", e.Path)
continue
}

View File

@ -129,6 +129,7 @@ func (d *naiveNotify) watchRecursively(dir string) error {
}
if shouldSkipDir {
logrus.Debugf("Ignoring directory and its contents (recursively): %s", path)
return filepath.SkipDir
}
@ -234,6 +235,7 @@ func (d *naiveNotify) shouldNotify(path string) bool {
if err != nil {
logrus.Infof("Error matching path %q: %v", path, err)
} else if ignore {
logrus.Tracef("Ignoring event for path: %v", path)
return false
}