dockerignore.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /*
  2. Copyright 2020 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package watch
  14. import (
  15. "fmt"
  16. "os"
  17. "path/filepath"
  18. "strings"
  19. "github.com/docker/compose/v2/internal/paths"
  20. "github.com/moby/patternmatcher"
  21. "github.com/moby/patternmatcher/ignorefile"
  22. )
  23. type dockerPathMatcher struct {
  24. repoRoot string
  25. matcher *patternmatcher.PatternMatcher
  26. }
  27. func (i dockerPathMatcher) Matches(f string) (bool, error) {
  28. if !filepath.IsAbs(f) {
  29. f = filepath.Join(i.repoRoot, f)
  30. }
  31. return i.matcher.MatchesOrParentMatches(f)
  32. }
  33. func (i dockerPathMatcher) MatchesEntireDir(f string) (bool, error) {
  34. matches, err := i.Matches(f)
  35. if !matches || err != nil {
  36. return matches, err
  37. }
  38. // We match the dir, but we might exclude files underneath it.
  39. if i.matcher.Exclusions() {
  40. for _, pattern := range i.matcher.Patterns() {
  41. if !pattern.Exclusion() {
  42. continue
  43. }
  44. if paths.IsChild(f, pattern.String()) {
  45. // Found an exclusion match -- we don't match this whole dir
  46. return false, nil
  47. }
  48. }
  49. return true, nil
  50. }
  51. return true, nil
  52. }
  53. func LoadDockerIgnore(repoRoot string) (*dockerPathMatcher, error) {
  54. absRoot, err := filepath.Abs(repoRoot)
  55. if err != nil {
  56. return nil, err
  57. }
  58. patterns, err := readDockerignorePatterns(absRoot)
  59. if err != nil {
  60. return nil, err
  61. }
  62. return NewDockerPatternMatcher(absRoot, patterns)
  63. }
  64. // Make all the patterns use absolute paths.
  65. func absPatterns(absRoot string, patterns []string) []string {
  66. absPatterns := make([]string, 0, len(patterns))
  67. for _, p := range patterns {
  68. // The pattern parsing here is loosely adapted from fileutils' NewPatternMatcher
  69. p = strings.TrimSpace(p)
  70. if p == "" {
  71. continue
  72. }
  73. p = filepath.Clean(p)
  74. pPath := p
  75. isExclusion := false
  76. if p[0] == '!' {
  77. pPath = p[1:]
  78. isExclusion = true
  79. }
  80. if !filepath.IsAbs(pPath) {
  81. pPath = filepath.Join(absRoot, pPath)
  82. }
  83. absPattern := pPath
  84. if isExclusion {
  85. absPattern = fmt.Sprintf("!%s", pPath)
  86. }
  87. absPatterns = append(absPatterns, absPattern)
  88. }
  89. return absPatterns
  90. }
  91. func NewDockerPatternMatcher(repoRoot string, patterns []string) (*dockerPathMatcher, error) {
  92. absRoot, err := filepath.Abs(repoRoot)
  93. if err != nil {
  94. return nil, err
  95. }
  96. pm, err := patternmatcher.New(absPatterns(absRoot, patterns))
  97. if err != nil {
  98. return nil, err
  99. }
  100. return &dockerPathMatcher{
  101. repoRoot: absRoot,
  102. matcher: pm,
  103. }, nil
  104. }
  105. func readDockerignorePatterns(repoRoot string) ([]string, error) {
  106. var excludes []string
  107. f, err := os.Open(filepath.Join(repoRoot, ".dockerignore"))
  108. switch {
  109. case os.IsNotExist(err):
  110. return excludes, nil
  111. case err != nil:
  112. return nil, err
  113. }
  114. defer func() { _ = f.Close() }()
  115. patterns, err := ignorefile.ReadAll(f)
  116. if err != nil {
  117. return nil, fmt.Errorf("error reading .dockerignore: %w", err)
  118. }
  119. return patterns, nil
  120. }
  121. func DockerIgnoreTesterFromContents(repoRoot string, contents string) (*dockerPathMatcher, error) {
  122. patterns, err := ignorefile.ReadAll(strings.NewReader(contents))
  123. if err != nil {
  124. return nil, fmt.Errorf("error reading .dockerignore: %w", err)
  125. }
  126. return NewDockerPatternMatcher(repoRoot, patterns)
  127. }