github.com/git-lfs/git-lfs@v2.5.2+incompatible/lfs/util.go (about) 1 package lfs 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "runtime" 10 "strings" 11 12 "github.com/git-lfs/git-lfs/config" 13 "github.com/git-lfs/git-lfs/tools" 14 ) 15 16 type Platform int 17 18 const ( 19 PlatformWindows = Platform(iota) 20 PlatformLinux = Platform(iota) 21 PlatformOSX = Platform(iota) 22 PlatformOther = Platform(iota) // most likely a *nix variant e.g. freebsd 23 PlatformUndetermined = Platform(iota) 24 ) 25 26 var currentPlatform = PlatformUndetermined 27 28 func join(parts ...string) string { 29 return strings.Join(parts, "/") 30 } 31 32 func (f *GitFilter) CopyCallbackFile(event, filename string, index, totalFiles int) (tools.CopyCallback, *os.File, error) { 33 logPath, _ := f.cfg.Os.Get("GIT_LFS_PROGRESS") 34 if len(logPath) == 0 || len(filename) == 0 || len(event) == 0 { 35 return nil, nil, nil 36 } 37 38 if !filepath.IsAbs(logPath) { 39 return nil, nil, fmt.Errorf("GIT_LFS_PROGRESS must be an absolute path") 40 } 41 42 cbDir := filepath.Dir(logPath) 43 if err := os.MkdirAll(cbDir, 0755); err != nil { 44 return nil, nil, wrapProgressError(err, event, logPath) 45 } 46 47 file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) 48 if err != nil { 49 return nil, file, wrapProgressError(err, event, logPath) 50 } 51 52 var prevWritten int64 53 54 cb := tools.CopyCallback(func(total int64, written int64, current int) error { 55 if written != prevWritten { 56 _, err := file.Write([]byte(fmt.Sprintf("%s %d/%d %d/%d %s\n", event, index, totalFiles, written, total, filename))) 57 file.Sync() 58 prevWritten = written 59 return wrapProgressError(err, event, logPath) 60 } 61 62 return nil 63 }) 64 65 return cb, file, nil 66 } 67 68 func wrapProgressError(err error, event, filename string) error { 69 if err != nil { 70 return fmt.Errorf("Error writing Git LFS %s progress to %s: %s", event, filename, err.Error()) 71 } 72 73 return nil 74 } 75 76 var localDirSet = tools.NewStringSetFromSlice([]string{".", "./", ".\\"}) 77 78 func GetPlatform() Platform { 79 if currentPlatform == PlatformUndetermined { 80 switch runtime.GOOS { 81 case "windows": 82 currentPlatform = PlatformWindows 83 case "linux": 84 currentPlatform = PlatformLinux 85 case "darwin": 86 currentPlatform = PlatformOSX 87 default: 88 currentPlatform = PlatformOther 89 } 90 } 91 return currentPlatform 92 } 93 94 type PathConverter interface { 95 Convert(string) string 96 } 97 98 // Convert filenames expressed relative to the root of the repo relative to the 99 // current working dir. Useful when needing to calling git with results from a rooted command, 100 // but the user is in a subdir of their repo 101 // Pass in a channel which you will fill with relative files & receive a channel which will get results 102 func NewRepoToCurrentPathConverter(cfg *config.Configuration) (PathConverter, error) { 103 r, c, p, err := pathConverterArgs(cfg) 104 if err != nil { 105 return nil, err 106 } 107 108 return &repoToCurrentPathConverter{ 109 repoDir: r, 110 currDir: c, 111 passthrough: p, 112 }, nil 113 } 114 115 type repoToCurrentPathConverter struct { 116 repoDir string 117 currDir string 118 passthrough bool 119 } 120 121 func (p *repoToCurrentPathConverter) Convert(filename string) string { 122 if p.passthrough { 123 return filename 124 } 125 126 abs := join(p.repoDir, filename) 127 rel, err := filepath.Rel(p.currDir, abs) 128 if err != nil { 129 // Use absolute file instead 130 return abs 131 } 132 return filepath.ToSlash(rel) 133 } 134 135 // Convert filenames expressed relative to the current directory to be 136 // relative to the repo root. Useful when calling git with arguments that requires them 137 // to be rooted but the user is in a subdir of their repo & expects to use relative args 138 // Pass in a channel which you will fill with relative files & receive a channel which will get results 139 func NewCurrentToRepoPathConverter(cfg *config.Configuration) (PathConverter, error) { 140 r, c, p, err := pathConverterArgs(cfg) 141 if err != nil { 142 return nil, err 143 } 144 145 return ¤tToRepoPathConverter{ 146 repoDir: r, 147 currDir: c, 148 passthrough: p, 149 }, nil 150 } 151 152 type currentToRepoPathConverter struct { 153 repoDir string 154 currDir string 155 passthrough bool 156 } 157 158 func (p *currentToRepoPathConverter) Convert(filename string) string { 159 if p.passthrough { 160 return filename 161 } 162 163 var abs string 164 if filepath.IsAbs(filename) { 165 abs = tools.ResolveSymlinks(filename) 166 } else { 167 abs = join(p.currDir, filename) 168 } 169 reltoroot, err := filepath.Rel(p.repoDir, abs) 170 if err != nil { 171 // Can't do this, use absolute as best fallback 172 return abs 173 } 174 return filepath.ToSlash(reltoroot) 175 } 176 177 func pathConverterArgs(cfg *config.Configuration) (string, string, bool, error) { 178 currDir, err := os.Getwd() 179 if err != nil { 180 return "", "", false, fmt.Errorf("Unable to get working dir: %v", err) 181 } 182 currDir = tools.ResolveSymlinks(currDir) 183 return cfg.LocalWorkingDir(), currDir, cfg.LocalWorkingDir() == currDir, nil 184 } 185 186 // Are we running on Windows? Need to handle some extra path shenanigans 187 func IsWindows() bool { 188 return GetPlatform() == PlatformWindows 189 } 190 191 func CopyFileContents(cfg *config.Configuration, src string, dst string) error { 192 tmp, err := ioutil.TempFile(cfg.TempDir(), filepath.Base(dst)) 193 if err != nil { 194 return err 195 } 196 defer func() { 197 tmp.Close() 198 os.Remove(tmp.Name()) 199 }() 200 in, err := os.Open(src) 201 if err != nil { 202 return err 203 } 204 defer in.Close() 205 _, err = io.Copy(tmp, in) 206 if err != nil { 207 return err 208 } 209 err = tmp.Close() 210 if err != nil { 211 return err 212 } 213 return os.Rename(tmp.Name(), dst) 214 } 215 216 func LinkOrCopy(cfg *config.Configuration, src string, dst string) error { 217 if src == dst { 218 return nil 219 } 220 err := os.Link(src, dst) 221 if err == nil { 222 return err 223 } 224 return CopyFileContents(cfg, src, dst) 225 }