| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- /*
- 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 paths
- import (
- "os"
- "path/filepath"
- "strings"
- )
- 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
- }
- }
- // EncompassingPaths returns the minimal set of paths that root all paths
- // from the original collection.
- //
- // For example, ["/foo", "/foo/bar", "/foo", "/baz"] -> ["/foo", "/baz].
- func EncompassingPaths(paths []string) []string {
- result := []string{}
- for _, current := range paths {
- isCovered := false
- hasRemovals := false
- for i, existing := range result {
- if IsChild(existing, current) {
- // The path is already covered, so there's no need to include it
- isCovered = true
- break
- }
- if IsChild(current, existing) {
- // Mark the element empty for removal.
- result[i] = ""
- hasRemovals = true
- }
- }
- if !isCovered {
- result = append(result, current)
- }
- if hasRemovals {
- // Remove all the empties
- newResult := []string{}
- for _, r := range result {
- if r != "" {
- newResult = append(newResult, r)
- }
- }
- result = newResult
- }
- }
- return result
- }
|