Imported Upstream version 2.3.4
[scm/test.git] / lfs / util.go
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 &currentToRepoPathConverter{
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 }