paths.go 3.3 KB

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