paths.go 2.9 KB

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