repo_description: Define and run multi-container applications with Docker
repo_topics: docker docker-compose go golang orchestration
code: /*
     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
  }
  