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 }