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