Imported Upstream version 2.3.2
[scm/test.git] / commands / commands.go
1 package commands
2
3 import (
4         "bytes"
5         "fmt"
6         "io"
7         "log"
8         "net"
9         "os"
10         "os/exec"
11         "path/filepath"
12         "strings"
13         "sync"
14         "time"
15
16         "github.com/git-lfs/git-lfs/config"
17         "github.com/git-lfs/git-lfs/errors"
18         "github.com/git-lfs/git-lfs/filepathfilter"
19         "github.com/git-lfs/git-lfs/git"
20         "github.com/git-lfs/git-lfs/lfs"
21         "github.com/git-lfs/git-lfs/lfsapi"
22         "github.com/git-lfs/git-lfs/locking"
23         "github.com/git-lfs/git-lfs/progress"
24         "github.com/git-lfs/git-lfs/tools"
25         "github.com/git-lfs/git-lfs/tq"
26 )
27
28 // Populate man pages
29 //go:generate go run ../docs/man/mangen.go
30
31 var (
32         Debugging    = false
33         ErrorBuffer  = &bytes.Buffer{}
34         ErrorWriter  = io.MultiWriter(os.Stderr, ErrorBuffer)
35         OutputWriter = io.MultiWriter(os.Stdout, ErrorBuffer)
36         ManPages     = make(map[string]string, 20)
37         cfg          = config.Config
38
39         tqManifest = make(map[string]*tq.Manifest)
40         apiClient  *lfsapi.Client
41         global     sync.Mutex
42
43         includeArg string
44         excludeArg string
45 )
46
47 // getTransferManifest builds a tq.Manifest from the global os and git
48 // environments.
49 func getTransferManifest() *tq.Manifest {
50         return getTransferManifestOperationRemote("", "")
51 }
52
53 // getTransferManifestOperationRemote builds a tq.Manifest from the global os
54 // and git environments and operation-specific and remote-specific settings.
55 // Operation must be "download", "upload", or the empty string.
56 func getTransferManifestOperationRemote(operation, remote string) *tq.Manifest {
57         c := getAPIClient()
58
59         global.Lock()
60         defer global.Unlock()
61
62         k := fmt.Sprintf("%s.%s", operation, remote)
63         if tqManifest[k] == nil {
64                 tqManifest[k] = tq.NewManifestClientOperationRemote(c, operation, remote)
65         }
66
67         return tqManifest[k]
68 }
69
70 func getAPIClient() *lfsapi.Client {
71         global.Lock()
72         defer global.Unlock()
73
74         if apiClient == nil {
75                 c, err := lfsapi.NewClient(cfg.Os, cfg.Git)
76                 if err != nil {
77                         ExitWithError(err)
78                 }
79                 apiClient = c
80         }
81         return apiClient
82 }
83
84 func closeAPIClient() error {
85         global.Lock()
86         defer global.Unlock()
87         if apiClient == nil {
88                 return nil
89         }
90         return apiClient.Close()
91 }
92
93 func newLockClient(remote string) *locking.Client {
94         storageConfig := config.Config.StorageConfig()
95         lockClient, err := locking.NewClient(remote, getAPIClient())
96         if err == nil {
97                 err = lockClient.SetupFileCache(storageConfig.LfsStorageDir)
98         }
99
100         if err != nil {
101                 Exit("Unable to create lock system: %v", err.Error())
102         }
103
104         // Configure dirs
105         lockClient.LocalWorkingDir = config.LocalWorkingDir
106         lockClient.LocalGitDir = config.LocalGitDir
107         lockClient.SetLockableFilesReadOnly = cfg.SetLockableFilesReadOnly()
108
109         return lockClient
110 }
111
112 // newDownloadCheckQueue builds a checking queue, checks that objects are there but doesn't download
113 func newDownloadCheckQueue(manifest *tq.Manifest, remote string, options ...tq.Option) *tq.TransferQueue {
114         allOptions := make([]tq.Option, 0, len(options)+1)
115         allOptions = append(allOptions, options...)
116         allOptions = append(allOptions, tq.DryRun(true))
117         return newDownloadQueue(manifest, remote, allOptions...)
118 }
119
120 // newDownloadQueue builds a DownloadQueue, allowing concurrent downloads.
121 func newDownloadQueue(manifest *tq.Manifest, remote string, options ...tq.Option) *tq.TransferQueue {
122         return tq.NewTransferQueue(tq.Download, manifest, remote, options...)
123 }
124
125 // newUploadQueue builds an UploadQueue, allowing `workers` concurrent uploads.
126 func newUploadQueue(manifest *tq.Manifest, remote string, options ...tq.Option) *tq.TransferQueue {
127         return tq.NewTransferQueue(tq.Upload, manifest, remote, options...)
128 }
129
130 func buildFilepathFilter(config *config.Configuration, includeArg, excludeArg *string) *filepathfilter.Filter {
131         inc, exc := determineIncludeExcludePaths(config, includeArg, excludeArg)
132         return filepathfilter.New(inc, exc)
133 }
134
135 func downloadTransfer(p *lfs.WrappedPointer) (name, path, oid string, size int64) {
136         path, _ = lfs.LocalMediaPath(p.Oid)
137
138         return p.Name, path, p.Oid, p.Size
139 }
140
141 // Error prints a formatted message to Stderr.  It also gets printed to the
142 // panic log if one is created for this command.
143 func Error(format string, args ...interface{}) {
144         if len(args) == 0 {
145                 fmt.Fprintln(ErrorWriter, format)
146                 return
147         }
148         fmt.Fprintf(ErrorWriter, format+"\n", args...)
149 }
150
151 // Print prints a formatted message to Stdout.  It also gets printed to the
152 // panic log if one is created for this command.
153 func Print(format string, args ...interface{}) {
154         if len(args) == 0 {
155                 fmt.Fprintln(OutputWriter, format)
156                 return
157         }
158         fmt.Fprintf(OutputWriter, format+"\n", args...)
159 }
160
161 // Exit prints a formatted message and exits.
162 func Exit(format string, args ...interface{}) {
163         Error(format, args...)
164         os.Exit(2)
165 }
166
167 // ExitWithError either panics with a full stack trace for fatal errors, or
168 // simply prints the error message and exits immediately.
169 func ExitWithError(err error) {
170         errorWith(err, Panic, Exit)
171 }
172
173 // FullError prints either a full stack trace for fatal errors, or just the
174 // error message.
175 func FullError(err error) {
176         errorWith(err, LoggedError, Error)
177 }
178
179 func errorWith(err error, fatalErrFn func(error, string, ...interface{}), errFn func(string, ...interface{})) {
180         if Debugging || errors.IsFatalError(err) {
181                 fatalErrFn(err, "%s", err)
182                 return
183         }
184
185         errFn("%s", err)
186 }
187
188 // Debug prints a formatted message if debugging is enabled.  The formatted
189 // message also shows up in the panic log, if created.
190 func Debug(format string, args ...interface{}) {
191         if !Debugging {
192                 return
193         }
194         log.Printf(format, args...)
195 }
196
197 // LoggedError prints the given message formatted with its arguments (if any) to
198 // Stderr. If an empty string is passed as the "format" argument, only the
199 // standard error logging message will be printed, and the error's body will be
200 // omitted.
201 //
202 // It also writes a stack trace for the error to a log file without exiting.
203 func LoggedError(err error, format string, args ...interface{}) {
204         if len(format) > 0 {
205                 Error(format, args...)
206         }
207         file := handlePanic(err)
208
209         if len(file) > 0 {
210                 fmt.Fprintf(os.Stderr, "\nErrors logged to %s\nUse `git lfs logs last` to view the log.\n", file)
211         }
212 }
213
214 // Panic prints a formatted message, and writes a stack trace for the error to
215 // a log file before exiting.
216 func Panic(err error, format string, args ...interface{}) {
217         LoggedError(err, format, args...)
218         os.Exit(2)
219 }
220
221 func Cleanup() {
222         if err := lfs.ClearTempObjects(); err != nil {
223                 fmt.Fprintf(os.Stderr, "Error clearing old temp files: %s\n", err)
224         }
225 }
226
227 func PipeMediaCommand(name string, args ...string) error {
228         return PipeCommand("bin/"+name, args...)
229 }
230
231 func PipeCommand(name string, args ...string) error {
232         cmd := exec.Command(name, args...)
233         cmd.Stdin = os.Stdin
234         cmd.Stderr = os.Stderr
235         cmd.Stdout = os.Stdout
236         return cmd.Run()
237 }
238
239 func requireStdin(msg string) {
240         var out string
241
242         stat, err := os.Stdin.Stat()
243         if err != nil {
244                 out = fmt.Sprintf("Cannot read from STDIN. %s (%s)", msg, err)
245         } else if (stat.Mode() & os.ModeCharDevice) != 0 {
246                 out = fmt.Sprintf("Cannot read from STDIN. %s", msg)
247         }
248
249         if len(out) > 0 {
250                 Error(out)
251                 os.Exit(1)
252         }
253 }
254
255 func requireInRepo() {
256         if !lfs.InRepo() {
257                 Print("Not in a git repository.")
258                 os.Exit(128)
259         }
260 }
261
262 func handlePanic(err error) string {
263         if err == nil {
264                 return ""
265         }
266
267         return logPanic(err)
268 }
269
270 func logPanic(loggedError error) string {
271         var (
272                 fmtWriter  io.Writer = os.Stderr
273                 lineEnding string    = "\n"
274         )
275
276         now := time.Now()
277         name := now.Format("20060102T150405.999999999")
278         full := filepath.Join(config.LocalLogDir, name+".log")
279
280         if err := os.MkdirAll(config.LocalLogDir, 0755); err != nil {
281                 full = ""
282                 fmt.Fprintf(fmtWriter, "Unable to log panic to %s: %s\n\n", config.LocalLogDir, err.Error())
283         } else if file, err := os.Create(full); err != nil {
284                 filename := full
285                 full = ""
286                 defer func() {
287                         fmt.Fprintf(fmtWriter, "Unable to log panic to %s\n\n", filename)
288                         logPanicToWriter(fmtWriter, err, lineEnding)
289                 }()
290         } else {
291                 fmtWriter = file
292                 lineEnding = gitLineEnding(cfg.Git)
293                 defer file.Close()
294         }
295
296         logPanicToWriter(fmtWriter, loggedError, lineEnding)
297
298         return full
299 }
300
301 func ipAddresses() []string {
302         ips := make([]string, 0, 1)
303         ifaces, err := net.Interfaces()
304         if err != nil {
305                 ips = append(ips, "Error getting network interface: "+err.Error())
306                 return ips
307         }
308         for _, i := range ifaces {
309                 if i.Flags&net.FlagUp == 0 {
310                         continue // interface down
311                 }
312                 if i.Flags&net.FlagLoopback != 0 {
313                         continue // loopback interface
314                 }
315                 addrs, _ := i.Addrs()
316                 l := make([]string, 0, 1)
317                 if err != nil {
318                         ips = append(ips, "Error getting IP address: "+err.Error())
319                         continue
320                 }
321                 for _, addr := range addrs {
322                         var ip net.IP
323                         switch v := addr.(type) {
324                         case *net.IPNet:
325                                 ip = v.IP
326                         case *net.IPAddr:
327                                 ip = v.IP
328                         }
329                         if ip == nil || ip.IsLoopback() {
330                                 continue
331                         }
332                         l = append(l, ip.String())
333                 }
334                 if len(l) > 0 {
335                         ips = append(ips, strings.Join(l, " "))
336                 }
337         }
338         return ips
339 }
340
341 func logPanicToWriter(w io.Writer, loggedError error, le string) {
342         // log the version
343         gitV, err := git.Config.Version()
344         if err != nil {
345                 gitV = "Error getting git version: " + err.Error()
346         }
347
348         fmt.Fprint(w, config.VersionDesc+le)
349         fmt.Fprint(w, gitV+le)
350
351         // log the command that was run
352         fmt.Fprint(w, le)
353         fmt.Fprintf(w, "$ %s", filepath.Base(os.Args[0]))
354         if len(os.Args) > 0 {
355                 fmt.Fprintf(w, " %s", strings.Join(os.Args[1:], " "))
356         }
357         fmt.Fprint(w, le)
358
359         // log the error message and stack trace
360         w.Write(ErrorBuffer.Bytes())
361         fmt.Fprint(w, le)
362
363         fmt.Fprintf(w, "%+v"+le, loggedError)
364
365         for key, val := range errors.Context(err) {
366                 fmt.Fprintf(w, "%s=%v"+le, key, val)
367         }
368
369         fmt.Fprint(w, le+"Current time in UTC: "+le)
370         fmt.Fprint(w, time.Now().UTC().Format("2006-01-02 15:04:05")+le)
371
372         fmt.Fprint(w, le+"ENV:"+le)
373
374         // log the environment
375         for _, env := range lfs.Environ(cfg, getTransferManifest()) {
376                 fmt.Fprint(w, env+le)
377         }
378
379         fmt.Fprint(w, le+"Client IP addresses:"+le)
380
381         for _, ip := range ipAddresses() {
382                 fmt.Fprint(w, ip+le)
383         }
384 }
385
386 func determineIncludeExcludePaths(config *config.Configuration, includeArg, excludeArg *string) (include, exclude []string) {
387         if includeArg == nil {
388                 include = config.FetchIncludePaths()
389         } else {
390                 include = tools.CleanPaths(*includeArg, ",")
391         }
392         if excludeArg == nil {
393                 exclude = config.FetchExcludePaths()
394         } else {
395                 exclude = tools.CleanPaths(*excludeArg, ",")
396         }
397         return
398 }
399
400 func buildProgressMeter(dryRun bool) *progress.ProgressMeter {
401         return progress.NewMeter(
402                 progress.WithOSEnv(cfg.Os),
403                 progress.DryRun(dryRun),
404         )
405 }
406
407 func requireGitVersion() {
408         minimumGit := "1.8.2"
409
410         if !git.Config.IsGitVersionAtLeast(minimumGit) {
411                 gitver, err := git.Config.Version()
412                 if err != nil {
413                         Exit("Error getting git version: %s", err)
414                 }
415                 Exit("git version >= %s is required for Git LFS, your version: %s", minimumGit, gitver)
416         }
417 }
418
419 func init() {
420         log.SetOutput(ErrorWriter)
421 }