paths.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  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/pkg/errors"
  20. )
  21. func greatestExistingAncestor(path string) (string, error) {
  22. if path == string(filepath.Separator) ||
  23. path == fmt.Sprintf("%s%s", filepath.VolumeName(path), string(filepath.Separator)) {
  24. return "", fmt.Errorf("cannot watch root directory")
  25. }
  26. _, err := os.Stat(path)
  27. if err != nil && !os.IsNotExist(err) {
  28. return "", errors.Wrapf(err, "os.Stat(%q)", path)
  29. }
  30. if os.IsNotExist(err) {
  31. return greatestExistingAncestor(filepath.Dir(path))
  32. }
  33. return path, nil
  34. }
  35. // If we're recursively watching a path, it doesn't
  36. // make sense to watch any of its descendants.
  37. func dedupePathsForRecursiveWatcher(paths []string) []string {
  38. result := []string{}
  39. for _, current := range paths {
  40. isCovered := false
  41. hasRemovals := false
  42. for i, existing := range result {
  43. if IsChild(existing, current) {
  44. // The path is already covered, so there's no need to include it
  45. isCovered = true
  46. break
  47. }
  48. if IsChild(current, existing) {
  49. // Mark the element empty fo removal.
  50. result[i] = ""
  51. hasRemovals = true
  52. }
  53. }
  54. if !isCovered {
  55. result = append(result, current)
  56. }
  57. if hasRemovals {
  58. // Remove all the empties
  59. newResult := []string{}
  60. for _, r := range result {
  61. if r != "" {
  62. newResult = append(newResult, r)
  63. }
  64. }
  65. result = newResult
  66. }
  67. }
  68. return result
  69. }
  70. func IsChild(dir string, file string) bool {
  71. if dir == "" {
  72. return false
  73. }
  74. dir = filepath.Clean(dir)
  75. current := filepath.Clean(file)
  76. child := "."
  77. for {
  78. if strings.EqualFold(dir, current) {
  79. // If the two paths are exactly equal, then they must be the same.
  80. if dir == current {
  81. return true
  82. }
  83. // If the two paths are equal under case-folding, but not exactly equal,
  84. // then the only way to check if they're truly "equal" is to check
  85. // to see if we're on a case-insensitive file system.
  86. //
  87. // This is a notoriously tricky problem. See how dep solves it here:
  88. // https://github.com/golang/dep/blob/v0.5.4/internal/fs/fs.go#L33
  89. //
  90. // because you can mount case-sensitive filesystems onto case-insensitive
  91. // file-systems, and vice versa :scream:
  92. //
  93. // We want to do as much of this check as possible with strings-only
  94. // (to avoid a file system read and error handling), so we only
  95. // do this check if we have no other choice.
  96. dirInfo, err := os.Stat(dir)
  97. if err != nil {
  98. return false
  99. }
  100. currentInfo, err := os.Stat(current)
  101. if err != nil {
  102. return false
  103. }
  104. if !os.SameFile(dirInfo, currentInfo) {
  105. return false
  106. }
  107. return true
  108. }
  109. if len(current) <= len(dir) || current == "." {
  110. return false
  111. }
  112. cDir := filepath.Dir(current)
  113. cBase := filepath.Base(current)
  114. child = filepath.Join(cBase, child)
  115. current = cDir
  116. }
  117. }