mirror of https://github.com/docker/compose.git
adjust code and dependencies
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
7d6ee74e62
commit
25576289c8
|
@ -35,6 +35,7 @@ COPY --from=xx / /
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
docker \
|
docker \
|
||||||
file \
|
file \
|
||||||
|
findutils \
|
||||||
git \
|
git \
|
||||||
make \
|
make \
|
||||||
protoc \
|
protoc \
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -33,6 +33,7 @@ require (
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/theupdateframework/notary v0.7.0
|
github.com/theupdateframework/notary v0.7.0
|
||||||
|
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375
|
||||||
go.opentelemetry.io/otel v1.12.0
|
go.opentelemetry.io/otel v1.12.0
|
||||||
golang.org/x/sync v0.1.0
|
golang.org/x/sync v0.1.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
@ -92,7 +93,7 @@ require (
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||||
github.com/moby/locker v1.0.1 // indirect
|
github.com/moby/locker v1.0.1 // indirect
|
||||||
github.com/moby/patternmatcher v0.5.0 // indirect
|
github.com/moby/patternmatcher v0.5.0
|
||||||
github.com/moby/spdystream v0.2.0 // indirect
|
github.com/moby/spdystream v0.2.0 // indirect
|
||||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||||
github.com/moby/sys/signal v0.7.0 // indirect
|
github.com/moby/sys/signal v0.7.0 // indirect
|
||||||
|
@ -152,6 +153,8 @@ require (
|
||||||
|
|
||||||
require go.uber.org/goleak v1.1.12
|
require go.uber.org/goleak v1.1.12
|
||||||
|
|
||||||
|
require github.com/fsnotify/fsevents v0.1.1
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
// Override for e2e tests
|
// Override for e2e tests
|
||||||
github.com/cucumber/godog => github.com/laurazard/godog v0.0.0-20220922095256-4c4b17abdae7
|
github.com/cucumber/godog => github.com/laurazard/godog v0.0.0-20220922095256-4c4b17abdae7
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -205,6 +205,8 @@ github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||||
|
github.com/fsnotify/fsevents v0.1.1 h1:/125uxJvvoSDDBPen6yUZbil8J9ydKZnnl3TWWmvnkw=
|
||||||
|
github.com/fsnotify/fsevents v0.1.1/go.mod h1:+d+hS27T6k5J8CRaPLKFgwKYcpS7GwW3Ule9+SC2ZRc=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
|
@ -649,6 +651,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
|
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
|
||||||
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
|
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
|
||||||
|
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8QU9nHY3xJSZR2kX9bgpZekRKGkLTmEXA=
|
||||||
|
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5 h1:NJ1nZs4j4XcBJKIY5sAwTGp9w5b78Zxr3+r0zXRuKnA=
|
github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5 h1:NJ1nZs4j4XcBJKIY5sAwTGp9w5b78Zxr3+r0zXRuKnA=
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5/go.mod h1:F83XRhNblQsKQH9hcKEE45GAOkL9590mtw9KsD0Q4fE=
|
github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5/go.mod h1:F83XRhNblQsKQH9hcKEE45GAOkL9590mtw9KsD0Q4fE=
|
||||||
|
@ -904,6 +908,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/frontend/dockerfile/dockerignore"
|
||||||
|
"github.com/moby/patternmatcher"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dockerPathMatcher struct {
|
||||||
|
repoRoot string
|
||||||
|
matcher *patternmatcher.PatternMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i dockerPathMatcher) Matches(f string) (bool, error) {
|
||||||
|
if !filepath.IsAbs(f) {
|
||||||
|
f = filepath.Join(i.repoRoot, f)
|
||||||
|
}
|
||||||
|
return i.matcher.MatchesOrParentMatches(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i dockerPathMatcher) MatchesEntireDir(f string) (bool, error) {
|
||||||
|
matches, err := i.Matches(f)
|
||||||
|
if !matches || err != nil {
|
||||||
|
return matches, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We match the dir, but we might exclude files underneath it.
|
||||||
|
if i.matcher.Exclusions() {
|
||||||
|
for _, pattern := range i.matcher.Patterns() {
|
||||||
|
if !pattern.Exclusion() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if IsChild(f, pattern.String()) {
|
||||||
|
// Found an exclusion match -- we don't match this whole dir
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDockerIgnoreTester(repoRoot string) (*dockerPathMatcher, error) {
|
||||||
|
absRoot, err := filepath.Abs(repoRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
patterns, err := readDockerignorePatterns(absRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewDockerPatternMatcher(absRoot, patterns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make all the patterns use absolute paths.
|
||||||
|
func absPatterns(absRoot string, patterns []string) []string {
|
||||||
|
absPatterns := make([]string, 0, len(patterns))
|
||||||
|
for _, p := range patterns {
|
||||||
|
// The pattern parsing here is loosely adapted from fileutils' NewPatternMatcher
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p = filepath.Clean(p)
|
||||||
|
|
||||||
|
pPath := p
|
||||||
|
isExclusion := false
|
||||||
|
if p[0] == '!' {
|
||||||
|
pPath = p[1:]
|
||||||
|
isExclusion = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filepath.IsAbs(pPath) {
|
||||||
|
pPath = filepath.Join(absRoot, pPath)
|
||||||
|
}
|
||||||
|
absPattern := pPath
|
||||||
|
if isExclusion {
|
||||||
|
absPattern = fmt.Sprintf("!%s", pPath)
|
||||||
|
}
|
||||||
|
absPatterns = append(absPatterns, absPattern)
|
||||||
|
}
|
||||||
|
return absPatterns
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDockerPatternMatcher(repoRoot string, patterns []string) (*dockerPathMatcher, error) {
|
||||||
|
absRoot, err := filepath.Abs(repoRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pm, err := patternmatcher.New(absPatterns(absRoot, patterns))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dockerPathMatcher{
|
||||||
|
repoRoot: absRoot,
|
||||||
|
matcher: pm,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readDockerignorePatterns(repoRoot string) ([]string, error) {
|
||||||
|
var excludes []string
|
||||||
|
|
||||||
|
f, err := os.Open(filepath.Join(repoRoot, ".dockerignore"))
|
||||||
|
switch {
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
return excludes, nil
|
||||||
|
case err != nil:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
|
return dockerignore.ReadAll(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DockerIgnoreTesterFromContents(repoRoot string, contents string) (*dockerPathMatcher, error) {
|
||||||
|
patterns, err := dockerignore.ReadAll(strings.NewReader(contents))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewDockerPatternMatcher(repoRoot, patterns)
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
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
|
package watch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -8,8 +24,6 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tilt-dev/tilt/pkg/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -68,8 +82,8 @@ func (EmptyMatcher) MatchesEntireDir(f string) (bool, error) { return false, nil
|
||||||
|
|
||||||
var _ PathMatcher = EmptyMatcher{}
|
var _ PathMatcher = EmptyMatcher{}
|
||||||
|
|
||||||
func NewWatcher(paths []string, ignore PathMatcher, l logger.Logger) (Notify, error) {
|
func NewWatcher(paths []string, ignore PathMatcher) (Notify, error) {
|
||||||
return newWatcher(paths, ignore, l)
|
return newWatcher(paths, ignore)
|
||||||
}
|
}
|
||||||
|
|
||||||
const WindowsBufferSizeEnvVar = "TILT_WATCH_WINDOWS_BUFFER_SIZE"
|
const WindowsBufferSizeEnvVar = "TILT_WATCH_WINDOWS_BUFFER_SIZE"
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
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
|
package watch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -13,10 +29,6 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/tilt-dev/tilt/internal/dockerignore"
|
|
||||||
"github.com/tilt-dev/tilt/internal/testutils/tempdir"
|
|
||||||
"github.com/tilt-dev/tilt/pkg/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Each implementation of the notify interface should have the same basic
|
// Each implementation of the notify interface should have the same basic
|
||||||
|
@ -24,15 +36,18 @@ import (
|
||||||
|
|
||||||
func TestWindowsBufferSize(t *testing.T) {
|
func TestWindowsBufferSize(t *testing.T) {
|
||||||
orig := os.Getenv(WindowsBufferSizeEnvVar)
|
orig := os.Getenv(WindowsBufferSizeEnvVar)
|
||||||
defer os.Setenv(WindowsBufferSizeEnvVar, orig)
|
defer os.Setenv(WindowsBufferSizeEnvVar, orig) //nolint:errcheck
|
||||||
|
|
||||||
os.Setenv(WindowsBufferSizeEnvVar, "")
|
err := os.Setenv(WindowsBufferSizeEnvVar, "")
|
||||||
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, defaultBufferSize, DesiredWindowsBufferSize())
|
assert.Equal(t, defaultBufferSize, DesiredWindowsBufferSize())
|
||||||
|
|
||||||
os.Setenv(WindowsBufferSizeEnvVar, "a")
|
err = os.Setenv(WindowsBufferSizeEnvVar, "a")
|
||||||
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, defaultBufferSize, DesiredWindowsBufferSize())
|
assert.Equal(t, defaultBufferSize, DesiredWindowsBufferSize())
|
||||||
|
|
||||||
os.Setenv(WindowsBufferSizeEnvVar, "10")
|
err = os.Setenv(WindowsBufferSizeEnvVar, "10")
|
||||||
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 10, DesiredWindowsBufferSize())
|
assert.Equal(t, 10, DesiredWindowsBufferSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +86,7 @@ func TestEventOrdering(t *testing.T) {
|
||||||
for i, dir := range dirs {
|
for i, dir := range dirs {
|
||||||
base := fmt.Sprintf("%d.txt", i)
|
base := fmt.Sprintf("%d.txt", i)
|
||||||
p := filepath.Join(dir, base)
|
p := filepath.Join(dir, base)
|
||||||
err := os.WriteFile(p, []byte(base), os.FileMode(0777))
|
err := os.WriteFile(p, []byte(base), os.FileMode(0o777))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -174,7 +189,7 @@ func TestNewDirectoriesAreRecursivelyWatched(t *testing.T) {
|
||||||
|
|
||||||
// change something inside sub directory
|
// change something inside sub directory
|
||||||
changeFilePath := filepath.Join(subPath, "change")
|
changeFilePath := filepath.Join(subPath, "change")
|
||||||
file, err := os.OpenFile(changeFilePath, os.O_RDONLY|os.O_CREATE, 0666)
|
file, err := os.OpenFile(changeFilePath, os.O_RDONLY|os.O_CREATE, 0o666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -236,7 +251,7 @@ func TestRemoveAndAddBack(t *testing.T) {
|
||||||
path := filepath.Join(f.paths[0], "change")
|
path := filepath.Join(f.paths[0], "change")
|
||||||
|
|
||||||
d1 := []byte("hello\ngo\n")
|
d1 := []byte("hello\ngo\n")
|
||||||
err := os.WriteFile(path, d1, 0644)
|
err := os.WriteFile(path, d1, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -251,7 +266,7 @@ func TestRemoveAndAddBack(t *testing.T) {
|
||||||
f.assertEvents(path)
|
f.assertEvents(path)
|
||||||
f.events = nil
|
f.events = nil
|
||||||
|
|
||||||
err = os.WriteFile(path, d1, 0644)
|
err = os.WriteFile(path, d1, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -272,7 +287,7 @@ func TestSingleFile(t *testing.T) {
|
||||||
f.fsync()
|
f.fsync()
|
||||||
|
|
||||||
d2 := []byte("hello\nworld\n")
|
d2 := []byte("hello\nworld\n")
|
||||||
err := os.WriteFile(path, d2, 0644)
|
err := os.WriteFile(path, d2, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -302,7 +317,7 @@ func TestWriteGoodLink(t *testing.T) {
|
||||||
f := newNotifyFixture(t)
|
f := newNotifyFixture(t)
|
||||||
|
|
||||||
goodFile := filepath.Join(f.paths[0], "goodFile")
|
goodFile := filepath.Join(f.paths[0], "goodFile")
|
||||||
err := os.WriteFile(goodFile, []byte("hello"), 0644)
|
err := os.WriteFile(goodFile, []byte("hello"), 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -387,7 +402,7 @@ func TestWatchNonexistentFileInNonexistentDirectoryCreatedSimultaneously(t *test
|
||||||
f := newNotifyFixture(t)
|
f := newNotifyFixture(t)
|
||||||
|
|
||||||
root := f.JoinPath("root")
|
root := f.JoinPath("root")
|
||||||
err := os.Mkdir(root, 0777)
|
err := os.Mkdir(root, 0o777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -404,7 +419,7 @@ func TestWatchNonexistentDirectory(t *testing.T) {
|
||||||
f := newNotifyFixture(t)
|
f := newNotifyFixture(t)
|
||||||
|
|
||||||
root := f.JoinPath("root")
|
root := f.JoinPath("root")
|
||||||
err := os.Mkdir(root, 0777)
|
err := os.Mkdir(root, 0o777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -415,12 +430,12 @@ func TestWatchNonexistentDirectory(t *testing.T) {
|
||||||
f.fsync()
|
f.fsync()
|
||||||
f.events = nil
|
f.events = nil
|
||||||
|
|
||||||
err = os.Mkdir(parent, 0777)
|
err = os.Mkdir(parent, 0o777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// for directories that were the root of an Add, we don't report creation, cf. watcher_darwin.go
|
// for directories that were the root of an Add, we don't report creation, cf. watcher_fsevent.go
|
||||||
f.assertEvents()
|
f.assertEvents()
|
||||||
|
|
||||||
f.events = nil
|
f.events = nil
|
||||||
|
@ -433,7 +448,7 @@ func TestWatchNonexistentFileInNonexistentDirectory(t *testing.T) {
|
||||||
f := newNotifyFixture(t)
|
f := newNotifyFixture(t)
|
||||||
|
|
||||||
root := f.JoinPath("root")
|
root := f.JoinPath("root")
|
||||||
err := os.Mkdir(root, 0777)
|
err := os.Mkdir(root, 0o777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -443,7 +458,7 @@ func TestWatchNonexistentFileInNonexistentDirectory(t *testing.T) {
|
||||||
f.watch(file)
|
f.watch(file)
|
||||||
f.assertEvents()
|
f.assertEvents()
|
||||||
|
|
||||||
err = os.Mkdir(parent, 0777)
|
err = os.Mkdir(parent, 0o777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -474,7 +489,7 @@ func TestWatchCountInnerFileWithIgnore(t *testing.T) {
|
||||||
f := newNotifyFixture(t)
|
f := newNotifyFixture(t)
|
||||||
|
|
||||||
root := f.paths[0]
|
root := f.paths[0]
|
||||||
ignore, _ := dockerignore.NewDockerPatternMatcher(root, []string{
|
ignore, _ := NewDockerPatternMatcher(root, []string{
|
||||||
"a",
|
"a",
|
||||||
"!a/b",
|
"!a/b",
|
||||||
})
|
})
|
||||||
|
@ -497,7 +512,7 @@ func TestIgnoreCreatedDir(t *testing.T) {
|
||||||
f := newNotifyFixture(t)
|
f := newNotifyFixture(t)
|
||||||
|
|
||||||
root := f.paths[0]
|
root := f.paths[0]
|
||||||
ignore, _ := dockerignore.NewDockerPatternMatcher(root, []string{"a/b"})
|
ignore, _ := NewDockerPatternMatcher(root, []string{"a/b"})
|
||||||
f.setIgnore(ignore)
|
f.setIgnore(ignore)
|
||||||
|
|
||||||
a := f.JoinPath(root, "a")
|
a := f.JoinPath(root, "a")
|
||||||
|
@ -517,7 +532,7 @@ func TestIgnoreCreatedDirWithExclusions(t *testing.T) {
|
||||||
f := newNotifyFixture(t)
|
f := newNotifyFixture(t)
|
||||||
|
|
||||||
root := f.paths[0]
|
root := f.paths[0]
|
||||||
ignore, _ := dockerignore.NewDockerPatternMatcher(root,
|
ignore, _ := NewDockerPatternMatcher(root,
|
||||||
[]string{
|
[]string{
|
||||||
"a/b",
|
"a/b",
|
||||||
"c",
|
"c",
|
||||||
|
@ -542,7 +557,7 @@ func TestIgnoreInitialDir(t *testing.T) {
|
||||||
f := newNotifyFixture(t)
|
f := newNotifyFixture(t)
|
||||||
|
|
||||||
root := f.TempDir("root")
|
root := f.TempDir("root")
|
||||||
ignore, _ := dockerignore.NewDockerPatternMatcher(root, []string{"a/b"})
|
ignore, _ := NewDockerPatternMatcher(root, []string{"a/b"})
|
||||||
f.setIgnore(ignore)
|
f.setIgnore(ignore)
|
||||||
|
|
||||||
a := f.JoinPath(root, "a")
|
a := f.JoinPath(root, "a")
|
||||||
|
@ -568,7 +583,7 @@ type notifyFixture struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel func()
|
cancel func()
|
||||||
out *bytes.Buffer
|
out *bytes.Buffer
|
||||||
*tempdir.TempDirFixture
|
*TempDirFixture
|
||||||
notify Notify
|
notify Notify
|
||||||
ignore PathMatcher
|
ignore PathMatcher
|
||||||
paths []string
|
paths []string
|
||||||
|
@ -581,7 +596,7 @@ func newNotifyFixture(t *testing.T) *notifyFixture {
|
||||||
nf := ¬ifyFixture{
|
nf := ¬ifyFixture{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
TempDirFixture: tempdir.NewTempDirFixture(t),
|
TempDirFixture: NewTempDirFixture(t),
|
||||||
paths: []string{},
|
paths: []string{},
|
||||||
ignore: EmptyMatcher{},
|
ignore: EmptyMatcher{},
|
||||||
out: out,
|
out: out,
|
||||||
|
@ -609,7 +624,7 @@ func (f *notifyFixture) rebuildWatcher() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new watcher
|
// create a new watcher
|
||||||
notify, err := NewWatcher(f.paths, f.ignore, logger.NewTestLogger(f.out))
|
notify, err := NewWatcher(f.paths, f.ignore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.T().Fatal(err)
|
f.T().Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -674,7 +689,7 @@ func (f *notifyFixture) fsyncWithRetryCount(retryCount int) {
|
||||||
syncPathBase := fmt.Sprintf("sync-%d.txt", time.Now().UnixNano())
|
syncPathBase := fmt.Sprintf("sync-%d.txt", time.Now().UnixNano())
|
||||||
syncPath := filepath.Join(f.paths[0], syncPathBase)
|
syncPath := filepath.Join(f.paths[0], syncPathBase)
|
||||||
anySyncPath := filepath.Join(f.paths[0], "sync-")
|
anySyncPath := filepath.Join(f.paths[0], "sync-")
|
||||||
timeout := time.After(250 * time.Millisecond)
|
timeout := time.After(250 * time.Second)
|
||||||
|
|
||||||
f.WriteFile(syncPath, time.Now().String())
|
f.WriteFile(syncPath, time.Now().String())
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,28 @@
|
||||||
|
/*
|
||||||
|
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
|
package watch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/tilt-dev/tilt/internal/ospath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func greatestExistingAncestor(path string) (string, error) {
|
func greatestExistingAncestor(path string) (string, error) {
|
||||||
|
@ -37,13 +52,13 @@ func dedupePathsForRecursiveWatcher(paths []string) []string {
|
||||||
hasRemovals := false
|
hasRemovals := false
|
||||||
|
|
||||||
for i, existing := range result {
|
for i, existing := range result {
|
||||||
if ospath.IsChild(existing, current) {
|
if IsChild(existing, current) {
|
||||||
// The path is already covered, so there's no need to include it
|
// The path is already covered, so there's no need to include it
|
||||||
isCovered = true
|
isCovered = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if ospath.IsChild(current, existing) {
|
if IsChild(current, existing) {
|
||||||
// Mark the element empty fo removal.
|
// Mark the element empty fo removal.
|
||||||
result[i] = ""
|
result[i] = ""
|
||||||
hasRemovals = true
|
hasRemovals = true
|
||||||
|
@ -67,3 +82,58 @@ func dedupePathsForRecursiveWatcher(paths []string) []string {
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsChild(dir string, file string) bool {
|
||||||
|
if dir == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
dir = filepath.Clean(dir)
|
||||||
|
current := filepath.Clean(file)
|
||||||
|
child := "."
|
||||||
|
for {
|
||||||
|
if strings.EqualFold(dir, current) {
|
||||||
|
// If the two paths are exactly equal, then they must be the same.
|
||||||
|
if dir == current {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the two paths are equal under case-folding, but not exactly equal,
|
||||||
|
// then the only way to check if they're truly "equal" is to check
|
||||||
|
// to see if we're on a case-insensitive file system.
|
||||||
|
//
|
||||||
|
// This is a notoriously tricky problem. See how dep solves it here:
|
||||||
|
// https://github.com/golang/dep/blob/v0.5.4/internal/fs/fs.go#L33
|
||||||
|
//
|
||||||
|
// because you can mount case-sensitive filesystems onto case-insensitive
|
||||||
|
// file-systems, and vice versa :scream:
|
||||||
|
//
|
||||||
|
// We want to do as much of this check as possible with strings-only
|
||||||
|
// (to avoid a file system read and error handling), so we only
|
||||||
|
// do this check if we have no other choice.
|
||||||
|
dirInfo, err := os.Stat(dir)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
currentInfo, err := os.Stat(current)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !os.SameFile(dirInfo, currentInfo) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(current) <= len(dir) || current == "." {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
cDir := filepath.Dir(current)
|
||||||
|
cBase := filepath.Base(current)
|
||||||
|
child = filepath.Join(cBase, child)
|
||||||
|
current = cDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
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
|
package watch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -5,12 +21,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/tilt-dev/tilt/internal/testutils/tempdir"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGreatestExistingAncestor(t *testing.T) {
|
func TestGreatestExistingAncestor(t *testing.T) {
|
||||||
f := tempdir.NewTempDirFixture(t)
|
f := NewTempDirFixture(t)
|
||||||
|
|
||||||
p, err := greatestExistingAncestor(f.Path())
|
p, err := greatestExistingAncestor(f.Path())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
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
|
package watch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -50,7 +66,7 @@ func (d *TempDir) NewDir(prefix string) (*TempDir, error) {
|
||||||
|
|
||||||
func (d *TempDir) NewDeterministicDir(name string) (*TempDir, error) {
|
func (d *TempDir) NewDeterministicDir(name string) (*TempDir, error) {
|
||||||
d2 := filepath.Join(d.dir, name)
|
d2 := filepath.Join(d.dir, name)
|
||||||
err := os.Mkdir(d2, 0700)
|
err := os.Mkdir(d2, 0o700)
|
||||||
if os.IsExist(err) {
|
if os.IsExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TempDirFixture struct {
|
||||||
|
t testing.TB
|
||||||
|
dir *TempDir
|
||||||
|
oldDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// everything not listed in this character class will get replaced by _, so that it's a safe filename
|
||||||
|
var sanitizeForFilenameRe = regexp.MustCompile("[^a-zA-Z0-9.]")
|
||||||
|
|
||||||
|
func SanitizeFileName(name string) string {
|
||||||
|
return sanitizeForFilenameRe.ReplaceAllString(name, "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTempDirFixture(t testing.TB) *TempDirFixture {
|
||||||
|
dir, err := NewDir(SanitizeFileName(t.Name()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error making temp dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := &TempDirFixture{
|
||||||
|
t: t,
|
||||||
|
dir: dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(ret.tearDown)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) T() testing.TB {
|
||||||
|
return f.t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) Path() string {
|
||||||
|
return f.dir.Path()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) Chdir() {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.oldDir = cwd
|
||||||
|
|
||||||
|
err = os.Chdir(f.Path())
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) JoinPath(path ...string) string {
|
||||||
|
p := []string{}
|
||||||
|
isAbs := len(path) > 0 && filepath.IsAbs(path[0])
|
||||||
|
if isAbs {
|
||||||
|
if !strings.HasPrefix(path[0], f.Path()) {
|
||||||
|
f.t.Fatalf("Path outside fixture tempdir are forbidden: %s", path[0])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p = append(p, f.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
p = append(p, path...)
|
||||||
|
return filepath.Join(p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) JoinPaths(paths []string) []string {
|
||||||
|
joined := make([]string, len(paths))
|
||||||
|
for i, p := range paths {
|
||||||
|
joined[i] = f.JoinPath(p)
|
||||||
|
}
|
||||||
|
return joined
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the full path to the file written.
|
||||||
|
func (f *TempDirFixture) WriteFile(path string, contents string) string {
|
||||||
|
fullPath := f.JoinPath(path)
|
||||||
|
base := filepath.Dir(fullPath)
|
||||||
|
err := os.MkdirAll(base, os.FileMode(0o777))
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(fullPath, []byte(contents), os.FileMode(0o777))
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
return fullPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the full path to the file written.
|
||||||
|
func (f *TempDirFixture) CopyFile(originalPath, newPath string) {
|
||||||
|
contents, err := os.ReadFile(originalPath)
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.WriteFile(newPath, string(contents))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the file.
|
||||||
|
func (f *TempDirFixture) ReadFile(path string) string {
|
||||||
|
fullPath := f.JoinPath(path)
|
||||||
|
contents, err := os.ReadFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
return string(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) WriteSymlink(linkContents, destPath string) {
|
||||||
|
fullDestPath := f.JoinPath(destPath)
|
||||||
|
err := os.MkdirAll(filepath.Dir(fullDestPath), os.FileMode(0o777))
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = os.Symlink(linkContents, fullDestPath)
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) MkdirAll(path string) {
|
||||||
|
fullPath := f.JoinPath(path)
|
||||||
|
err := os.MkdirAll(fullPath, os.FileMode(0o777))
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) TouchFiles(paths []string) {
|
||||||
|
for _, p := range paths {
|
||||||
|
f.WriteFile(p, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) Rm(pathInRepo string) {
|
||||||
|
fullPath := f.JoinPath(pathInRepo)
|
||||||
|
err := os.RemoveAll(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) NewFile(prefix string) (*os.File, error) {
|
||||||
|
return os.CreateTemp(f.dir.Path(), prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) TempDir(prefix string) string {
|
||||||
|
name, err := os.MkdirTemp(f.dir.Path(), prefix)
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TempDirFixture) tearDown() {
|
||||||
|
if f.oldDir != "" {
|
||||||
|
err := os.Chdir(f.oldDir)
|
||||||
|
if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := f.dir.TearDown()
|
||||||
|
if err != nil && runtime.GOOS == "windows" &&
|
||||||
|
(strings.Contains(err.Error(), "The process cannot access the file") ||
|
||||||
|
strings.Contains(err.Error(), "Access is denied")) {
|
||||||
|
// NOTE(nick): I'm not convinced that this is a real problem.
|
||||||
|
// I think it might just be clean up of file notification I/O.
|
||||||
|
} else if err != nil {
|
||||||
|
f.t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,36 @@
|
||||||
|
//go:build darwin
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
/*
|
||||||
|
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
|
package watch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsevents"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/tilt-dev/tilt/pkg/logger"
|
|
||||||
|
|
||||||
"github.com/tilt-dev/fsevents"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A file watcher optimized for Darwin.
|
// A file watcher optimized for Darwin.
|
||||||
// Uses FSEvents to avoid the terrible perf characteristics of kqueue.
|
// Uses FSEvents to avoid the terrible perf characteristics of kqueue. Requires CGO
|
||||||
type darwinNotify struct {
|
type fseventNotify struct {
|
||||||
stream *fsevents.EventStream
|
stream *fsevents.EventStream
|
||||||
events chan FileEvent
|
events chan FileEvent
|
||||||
errors chan error
|
errors chan error
|
||||||
|
@ -21,11 +38,10 @@ type darwinNotify struct {
|
||||||
|
|
||||||
pathsWereWatching map[string]interface{}
|
pathsWereWatching map[string]interface{}
|
||||||
ignore PathMatcher
|
ignore PathMatcher
|
||||||
logger logger.Logger
|
|
||||||
sawAnyHistoryDone bool
|
sawAnyHistoryDone bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *darwinNotify) loop() {
|
func (d *fseventNotify) loop() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-d.stop:
|
case <-d.stop:
|
||||||
|
@ -58,7 +74,7 @@ func (d *darwinNotify) loop() {
|
||||||
|
|
||||||
ignore, err := d.ignore.Matches(e.Path)
|
ignore, err := d.ignore.Matches(e.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.Infof("Error matching path %q: %v", e.Path, err)
|
logrus.Infof("Error matching path %q: %v", e.Path, err)
|
||||||
} else if ignore {
|
} else if ignore {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -70,7 +86,7 @@ func (d *darwinNotify) loop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a path to be watched. Should only be called during initialization.
|
// Add a path to be watched. Should only be called during initialization.
|
||||||
func (d *darwinNotify) initAdd(name string) {
|
func (d *fseventNotify) initAdd(name string) {
|
||||||
d.stream.Paths = append(d.stream.Paths, name)
|
d.stream.Paths = append(d.stream.Paths, name)
|
||||||
|
|
||||||
if d.pathsWereWatching == nil {
|
if d.pathsWereWatching == nil {
|
||||||
|
@ -79,7 +95,7 @@ func (d *darwinNotify) initAdd(name string) {
|
||||||
d.pathsWereWatching[name] = struct{}{}
|
d.pathsWereWatching[name] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *darwinNotify) Start() error {
|
func (d *fseventNotify) Start() error {
|
||||||
if len(d.stream.Paths) == 0 {
|
if len(d.stream.Paths) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -93,7 +109,7 @@ func (d *darwinNotify) Start() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *darwinNotify) Close() error {
|
func (d *fseventNotify) Close() error {
|
||||||
numberOfWatches.Add(int64(-len(d.stream.Paths)))
|
numberOfWatches.Add(int64(-len(d.stream.Paths)))
|
||||||
|
|
||||||
d.stream.Stop()
|
d.stream.Stop()
|
||||||
|
@ -103,18 +119,17 @@ func (d *darwinNotify) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *darwinNotify) Events() chan FileEvent {
|
func (d *fseventNotify) Events() chan FileEvent {
|
||||||
return d.events
|
return d.events
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *darwinNotify) Errors() chan error {
|
func (d *fseventNotify) Errors() chan error {
|
||||||
return d.errors
|
return d.errors
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWatcher(paths []string, ignore PathMatcher, l logger.Logger) (*darwinNotify, error) {
|
func newFSEventWatcher(paths []string, ignore PathMatcher) (*fseventNotify, error) {
|
||||||
dw := &darwinNotify{
|
dw := &fseventNotify{
|
||||||
ignore: ignore,
|
ignore: ignore,
|
||||||
logger: l,
|
|
||||||
stream: &fsevents.EventStream{
|
stream: &fsevents.EventStream{
|
||||||
Latency: 1 * time.Millisecond,
|
Latency: 1 * time.Millisecond,
|
||||||
Flags: fsevents.FileEvents,
|
Flags: fsevents.FileEvents,
|
||||||
|
@ -139,4 +154,4 @@ func newWatcher(paths []string, ignore PathMatcher, l logger.Logger) (*darwinNot
|
||||||
return dw, nil
|
return dw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Notify = &darwinNotify{}
|
var _ Notify = &fseventNotify{}
|
|
@ -1,5 +1,18 @@
|
||||||
//go:build !darwin
|
/*
|
||||||
// +build !darwin
|
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
|
package watch
|
||||||
|
|
||||||
|
@ -12,10 +25,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/tilt-dev/fsnotify"
|
"github.com/tilt-dev/fsnotify"
|
||||||
"github.com/tilt-dev/tilt/internal/ospath"
|
|
||||||
"github.com/tilt-dev/tilt/pkg/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A naive file watcher that uses the plain fsnotify API.
|
// A naive file watcher that uses the plain fsnotify API.
|
||||||
|
@ -33,7 +45,6 @@ type naiveNotify struct {
|
||||||
notifyList map[string]bool
|
notifyList map[string]bool
|
||||||
|
|
||||||
ignore PathMatcher
|
ignore PathMatcher
|
||||||
log logger.Logger
|
|
||||||
|
|
||||||
isWatcherRecursive bool
|
isWatcherRecursive bool
|
||||||
watcher *fsnotify.Watcher
|
watcher *fsnotify.Watcher
|
||||||
|
@ -71,7 +82,9 @@ func (d *naiveNotify) Start() error {
|
||||||
// we should have caught that above, let's just skip it.
|
// we should have caught that above, let's just skip it.
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
continue
|
continue
|
||||||
} else if fi.IsDir() {
|
}
|
||||||
|
|
||||||
|
if fi.IsDir() {
|
||||||
err = d.watchRecursively(name)
|
err = d.watchRecursively(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "notify.Add(%q)", name)
|
return errors.Wrapf(err, "notify.Add(%q)", name)
|
||||||
|
@ -141,7 +154,7 @@ func (d *naiveNotify) Errors() chan error {
|
||||||
return d.errors
|
return d.errors
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *naiveNotify) loop() {
|
func (d *naiveNotify) loop() { //nolint:gocyclo
|
||||||
defer close(d.wrappedEvents)
|
defer close(d.wrappedEvents)
|
||||||
for e := range d.events {
|
for e := range d.events {
|
||||||
// The Windows fsnotify event stream sometimes gets events with empty names
|
// The Windows fsnotify event stream sometimes gets events with empty names
|
||||||
|
@ -202,13 +215,13 @@ func (d *naiveNotify) loop() {
|
||||||
if shouldWatch {
|
if shouldWatch {
|
||||||
err := d.add(path)
|
err := d.add(path)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
d.log.Infof("Error watching path %s: %s", e.Name, err)
|
logrus.Infof("Error watching path %s: %s", e.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
d.log.Infof("Error walking directory %s: %s", e.Name, err)
|
logrus.Infof("Error walking directory %s: %s", e.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,7 +229,7 @@ func (d *naiveNotify) loop() {
|
||||||
func (d *naiveNotify) shouldNotify(path string) bool {
|
func (d *naiveNotify) shouldNotify(path string) bool {
|
||||||
ignore, err := d.ignore.Matches(path)
|
ignore, err := d.ignore.Matches(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.log.Infof("Error matching path %q: %v", path, err)
|
logrus.Infof("Error matching path %q: %v", path, err)
|
||||||
} else if ignore {
|
} else if ignore {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -225,14 +238,11 @@ func (d *naiveNotify) shouldNotify(path string) bool {
|
||||||
// We generally don't care when directories change at the root of an ADD
|
// We generally don't care when directories change at the root of an ADD
|
||||||
stat, err := os.Lstat(path)
|
stat, err := os.Lstat(path)
|
||||||
isDir := err == nil && stat.IsDir()
|
isDir := err == nil && stat.IsDir()
|
||||||
if isDir {
|
return !isDir
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for root := range d.notifyList {
|
for root := range d.notifyList {
|
||||||
if ospath.IsChild(root, path) {
|
if IsChild(root, path) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,7 +277,7 @@ func (d *naiveNotify) shouldSkipDir(path string) (bool, error) {
|
||||||
// - A parent of a directory that's in our notify list
|
// - A parent of a directory that's in our notify list
|
||||||
// (i.e., to cover the "path doesn't exist" case).
|
// (i.e., to cover the "path doesn't exist" case).
|
||||||
for root := range d.notifyList {
|
for root := range d.notifyList {
|
||||||
if ospath.IsChild(root, path) || ospath.IsChild(path, root) {
|
if IsChild(root, path) || IsChild(path, root) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -284,7 +294,7 @@ func (d *naiveNotify) add(path string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWatcher(paths []string, ignore PathMatcher, l logger.Logger) (*naiveNotify, error) {
|
func newWatcher(paths []string, ignore PathMatcher) (*naiveNotify, error) {
|
||||||
if ignore == nil {
|
if ignore == nil {
|
||||||
return nil, fmt.Errorf("newWatcher: ignore is nil")
|
return nil, fmt.Errorf("newWatcher: ignore is nil")
|
||||||
}
|
}
|
||||||
|
@ -319,7 +329,6 @@ func newWatcher(paths []string, ignore PathMatcher, l logger.Logger) (*naiveNoti
|
||||||
wmw := &naiveNotify{
|
wmw := &naiveNotify{
|
||||||
notifyList: notifyList,
|
notifyList: notifyList,
|
||||||
ignore: ignore,
|
ignore: ignore,
|
||||||
log: l,
|
|
||||||
watcher: fsw,
|
watcher: fsw,
|
||||||
events: fsw.Events,
|
events: fsw.Events,
|
||||||
wrappedEvents: wrappedEvents,
|
wrappedEvents: wrappedEvents,
|
||||||
|
|
|
@ -1,5 +1,18 @@
|
||||||
//go:build !darwin
|
/*
|
||||||
// +build !darwin
|
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
|
package watch
|
||||||
|
|
||||||
|
@ -39,7 +52,7 @@ func TestDontWatchEachFile(t *testing.T) {
|
||||||
f.WriteFile(f.JoinPath(watched, "initial.txt"), "initial data")
|
f.WriteFile(f.JoinPath(watched, "initial.txt"), "initial data")
|
||||||
|
|
||||||
initialDir := f.JoinPath(watched, "initial_dir")
|
initialDir := f.JoinPath(watched, "initial_dir")
|
||||||
if err := os.Mkdir(initialDir, 0777); err != nil {
|
if err := os.Mkdir(initialDir, 0o777); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,13 +69,13 @@ func TestDontWatchEachFile(t *testing.T) {
|
||||||
|
|
||||||
// inplace
|
// inplace
|
||||||
inplace := f.JoinPath(watched, "inplace")
|
inplace := f.JoinPath(watched, "inplace")
|
||||||
if err := os.Mkdir(inplace, 0777); err != nil {
|
if err := os.Mkdir(inplace, 0o777); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
f.WriteFile(f.JoinPath(inplace, "inplace.txt"), "inplace data")
|
f.WriteFile(f.JoinPath(inplace, "inplace.txt"), "inplace data")
|
||||||
|
|
||||||
inplaceDir := f.JoinPath(inplace, "inplace_dir")
|
inplaceDir := f.JoinPath(inplace, "inplace_dir")
|
||||||
if err := os.Mkdir(inplaceDir, 0777); err != nil {
|
if err := os.Mkdir(inplaceDir, 0o777); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +94,7 @@ func TestDontWatchEachFile(t *testing.T) {
|
||||||
f.WriteFile(f.JoinPath(staged, "staged.txt"), "staged data")
|
f.WriteFile(f.JoinPath(staged, "staged.txt"), "staged data")
|
||||||
|
|
||||||
stagedDir := f.JoinPath(staged, "staged_dir")
|
stagedDir := f.JoinPath(staged, "staged_dir")
|
||||||
if err := os.Mkdir(stagedDir, 0777); err != nil {
|
if err := os.Mkdir(stagedDir, 0o777); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,10 +122,10 @@ func TestDontWatchEachFile(t *testing.T) {
|
||||||
func inotifyNodes() (int, error) {
|
func inotifyNodes() (int, error) {
|
||||||
pid := os.Getpid()
|
pid := os.Getpid()
|
||||||
|
|
||||||
output, err := exec.Command("bash", "-c", fmt.Sprintf(
|
output, err := exec.Command("/bin/sh", "-c", fmt.Sprintf(
|
||||||
"find /proc/%d/fd -lname anon_inode:inotify -printf '%%hinfo/%%f\n' | xargs cat | grep -c '^inotify'", pid)).Output()
|
"find /proc/%d/fd -lname anon_inode:inotify -printf '%%hinfo/%%f\n' | xargs cat | grep -c '^inotify'", pid)).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("error running command to determine number of watched files: %v", err)
|
return 0, fmt.Errorf("error running command to determine number of watched files: %v\n %s", err, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := strconv.Atoi(strings.TrimSpace(string(output)))
|
n, err := strconv.Atoi(strings.TrimSpace(string(output)))
|
||||||
|
|
|
@ -1,6 +1,22 @@
|
||||||
//go:build !windows
|
//go:build !windows
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
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
|
package watch
|
||||||
|
|
||||||
import "github.com/tilt-dev/fsnotify"
|
import "github.com/tilt-dev/fsnotify"
|
||||||
|
|
|
@ -1,6 +1,22 @@
|
||||||
//go:build windows
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
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
|
package watch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
Loading…
Reference in New Issue