github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/ospath/ospath.go (about)

     1  package ospath
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  )
     9  
    10  // filepath.Abs for testing
    11  func MustAbs(path string) string {
    12  	result, err := filepath.Abs(path)
    13  	if err != nil {
    14  		panic(fmt.Errorf("Abs(%s): %v", path, err))
    15  	}
    16  	return result
    17  }
    18  
    19  // Given absolute paths `dir` and `file`, returns
    20  // the relative path of `file` relative to `dir`.
    21  //
    22  // Returns true if successful. If `file` is not under `dir`, returns false.
    23  //
    24  // TODO(nick): Should we have an assertion that dir and file are absolute? This is a common
    25  // mistake when using this API. It won't work correctly with relative paths.
    26  func Child(dir string, file string) (string, bool) {
    27  	if dir == "" {
    28  		return "", false
    29  	}
    30  
    31  	dir = filepath.Clean(dir)
    32  	current := filepath.Clean(file)
    33  	child := "."
    34  	for {
    35  		if strings.EqualFold(dir, current) {
    36  			// If the two paths are exactly equal, then they must be the same.
    37  			if dir == current {
    38  				return child, true
    39  			}
    40  
    41  			// If the two paths are equal under case-folding, but not exactly equal,
    42  			// then the only way to check if they're truly "equal" is to check
    43  			// to see if we're on a case-insensitive file system.
    44  			//
    45  			// This is a notoriously tricky problem. See how dep solves it here:
    46  			// https://github.com/golang/dep/blob/v0.5.4/internal/fs/fs.go#L33
    47  			//
    48  			// because you can mount case-sensitive filesystems onto case-insensitive
    49  			// file-systems, and vice versa :scream:
    50  			//
    51  			// We want to do as much of this check as possible with strings-only
    52  			// (to avoid a file system read and error handling), so we only
    53  			// do this check if we have no other choice.
    54  			dirInfo, err := os.Stat(dir)
    55  			if err != nil {
    56  				return "", false
    57  			}
    58  
    59  			currentInfo, err := os.Stat(current)
    60  			if err != nil {
    61  				return "", false
    62  			}
    63  
    64  			if !os.SameFile(dirInfo, currentInfo) {
    65  				return "", false
    66  			}
    67  			return child, true
    68  		}
    69  
    70  		if len(current) <= len(dir) || current == "." {
    71  			return "", false
    72  		}
    73  
    74  		cDir := filepath.Dir(current)
    75  		cBase := filepath.Base(current)
    76  		child = filepath.Join(cBase, child)
    77  		current = cDir
    78  	}
    79  }
    80  
    81  // IsChild returns true if the given file is a child of the given directory
    82  func IsChild(dir string, file string) bool {
    83  	_, ret := Child(dir, file)
    84  	return ret
    85  }
    86  
    87  // IsChildOfOne returns true if the given file is a child of (at least) one of
    88  // the given directories.
    89  func IsChildOfOne(dirs []string, file string) bool {
    90  	for _, dir := range dirs {
    91  		if IsChild(dir, file) {
    92  			return true
    93  		}
    94  	}
    95  	return false
    96  }
    97  
    98  func RealChild(dir string, file string) (string, bool, error) {
    99  	realDir, err := RealAbs(dir)
   100  	if err != nil {
   101  		return "", false, err
   102  	}
   103  	realFile, err := RealAbs(file)
   104  	if err != nil {
   105  		return "", false, err
   106  	}
   107  
   108  	rel, isChild := Child(realDir, realFile)
   109  	return rel, isChild, nil
   110  }
   111  
   112  // Returns the absolute version of this path, resolving all symlinks.
   113  func RealAbs(path string) (string, error) {
   114  	// Make the path absolute first, so that we find any symlink parents.
   115  	absPath, err := filepath.Abs(path)
   116  	if err != nil {
   117  		return "", err
   118  	}
   119  
   120  	// Resolve the symlinks.
   121  	realPath, err := filepath.EvalSymlinks(absPath)
   122  	if err != nil {
   123  		return "", err
   124  	}
   125  
   126  	// Double-check we're still absolute.
   127  	return filepath.Abs(realPath)
   128  }
   129  
   130  // Like os.Getwd, but with all symlinks resolved.
   131  func Realwd() (string, error) {
   132  	path, err := os.Getwd()
   133  	if err != nil {
   134  		return "", err
   135  	}
   136  	return RealAbs(path)
   137  }
   138  
   139  func IsRegularFile(path string) bool {
   140  	f, err := os.Stat(path)
   141  	if err != nil {
   142  		return false
   143  	}
   144  
   145  	return f.Mode().IsRegular()
   146  }
   147  
   148  func IsDir(path string) bool {
   149  	f, err := os.Stat(path)
   150  	if err != nil {
   151  		return false
   152  	}
   153  
   154  	return f.Mode().IsDir()
   155  }
   156  
   157  func IsBrokenSymlink(path string) (bool, error) {
   158  	// Stat resolves symlinks, lstat does not.
   159  	// So if Stat reports IsNotExist, but Lstat does not,
   160  	// then we have a broken symlink.
   161  	_, err := os.Stat(path)
   162  	if err == nil {
   163  		return false, nil
   164  	}
   165  
   166  	if !os.IsNotExist(err) {
   167  		return false, err
   168  	}
   169  
   170  	_, err = os.Lstat(path)
   171  	if err != nil {
   172  		if os.IsNotExist(err) {
   173  			return false, nil
   174  		}
   175  		return false, err
   176  	}
   177  	return true, nil
   178  }
   179  
   180  // TryAsCwdChildren converts the given absolute paths to children of the CWD,
   181  // if possible (otherwise, leaves them as absolute paths).
   182  func TryAsCwdChildren(absPaths []string) []string {
   183  	wd, err := os.Getwd()
   184  	if err != nil {
   185  		// This is just a util for printing right now, so don't actually throw an
   186  		// error, just return back all the absolute paths
   187  		return absPaths[:]
   188  	}
   189  
   190  	res := make([]string, len(absPaths))
   191  	for i, abs := range absPaths {
   192  		rel, isChild := Child(wd, abs)
   193  		if isChild {
   194  			res[i] = rel
   195  		} else {
   196  			res[i] = abs
   197  		}
   198  	}
   199  	return res
   200  }