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"
29 //go:generate go run ../docs/man/mangen.go
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)
39 tqManifest = make(map[string]*tq.Manifest)
40 apiClient *lfsapi.Client
47 // getTransferManifest builds a tq.Manifest from the global os and git
49 func getTransferManifest() *tq.Manifest {
50 return getTransferManifestOperationRemote("", "")
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 {
62 k := fmt.Sprintf("%s.%s", operation, remote)
63 if tqManifest[k] == nil {
64 tqManifest[k] = tq.NewManifestClientOperationRemote(c, operation, remote)
70 func getAPIClient() *lfsapi.Client {
75 c, err := lfsapi.NewClient(cfg.Os, cfg.Git)
84 func closeAPIClient() error {
90 return apiClient.Close()
93 func newLockClient(remote string) *locking.Client {
94 storageConfig := config.Config.StorageConfig()
95 lockClient, err := locking.NewClient(remote, getAPIClient())
97 err = lockClient.SetupFileCache(storageConfig.LfsStorageDir)
101 Exit("Unable to create lock system: %v", err.Error())
105 lockClient.LocalWorkingDir = config.LocalWorkingDir
106 lockClient.LocalGitDir = config.LocalGitDir
107 lockClient.SetLockableFilesReadOnly = cfg.SetLockableFilesReadOnly()
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...)
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...)
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...)
130 func buildFilepathFilter(config *config.Configuration, includeArg, excludeArg *string) *filepathfilter.Filter {
131 inc, exc := determineIncludeExcludePaths(config, includeArg, excludeArg)
132 return filepathfilter.New(inc, exc)
135 func downloadTransfer(p *lfs.WrappedPointer) (name, path, oid string, size int64) {
136 path, _ = lfs.LocalMediaPath(p.Oid)
138 return p.Name, path, p.Oid, p.Size
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{}) {
145 fmt.Fprintln(ErrorWriter, format)
148 fmt.Fprintf(ErrorWriter, format+"\n", args...)
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{}) {
155 fmt.Fprintln(OutputWriter, format)
158 fmt.Fprintf(OutputWriter, format+"\n", args...)
161 // Exit prints a formatted message and exits.
162 func Exit(format string, args ...interface{}) {
163 Error(format, args...)
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)
173 // FullError prints either a full stack trace for fatal errors, or just the
175 func FullError(err error) {
176 errorWith(err, LoggedError, Error)
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)
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{}) {
194 log.Printf(format, args...)
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
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{}) {
205 Error(format, args...)
207 file := handlePanic(err)
210 fmt.Fprintf(os.Stderr, "\nErrors logged to %s\nUse `git lfs logs last` to view the log.\n", file)
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...)
222 if err := lfs.ClearTempObjects(); err != nil {
223 fmt.Fprintf(os.Stderr, "Error clearing old temp files: %s\n", err)
227 func PipeMediaCommand(name string, args ...string) error {
228 return PipeCommand("bin/"+name, args...)
231 func PipeCommand(name string, args ...string) error {
232 cmd := exec.Command(name, args...)
234 cmd.Stderr = os.Stderr
235 cmd.Stdout = os.Stdout
239 func requireStdin(msg string) {
242 stat, err := os.Stdin.Stat()
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)
255 func requireInRepo() {
257 Print("Not in a git repository.")
262 func handlePanic(err error) string {
270 func logPanic(loggedError error) string {
272 fmtWriter io.Writer = os.Stderr
273 lineEnding string = "\n"
277 name := now.Format("20060102T150405.999999999")
278 full := filepath.Join(config.LocalLogDir, name+".log")
280 if err := os.MkdirAll(config.LocalLogDir, 0755); err != nil {
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 {
287 fmt.Fprintf(fmtWriter, "Unable to log panic to %s\n\n", filename)
288 logPanicToWriter(fmtWriter, err, lineEnding)
292 lineEnding = gitLineEnding(cfg.Git)
296 logPanicToWriter(fmtWriter, loggedError, lineEnding)
301 func ipAddresses() []string {
302 ips := make([]string, 0, 1)
303 ifaces, err := net.Interfaces()
305 ips = append(ips, "Error getting network interface: "+err.Error())
308 for _, i := range ifaces {
309 if i.Flags&net.FlagUp == 0 {
310 continue // interface down
312 if i.Flags&net.FlagLoopback != 0 {
313 continue // loopback interface
315 addrs, _ := i.Addrs()
316 l := make([]string, 0, 1)
318 ips = append(ips, "Error getting IP address: "+err.Error())
321 for _, addr := range addrs {
323 switch v := addr.(type) {
329 if ip == nil || ip.IsLoopback() {
332 l = append(l, ip.String())
335 ips = append(ips, strings.Join(l, " "))
341 func logPanicToWriter(w io.Writer, loggedError error, le string) {
343 gitV, err := git.Config.Version()
345 gitV = "Error getting git version: " + err.Error()
348 fmt.Fprint(w, config.VersionDesc+le)
349 fmt.Fprint(w, gitV+le)
351 // log the command that was run
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:], " "))
359 // log the error message and stack trace
360 w.Write(ErrorBuffer.Bytes())
363 fmt.Fprintf(w, "%+v"+le, loggedError)
365 for key, val := range errors.Context(err) {
366 fmt.Fprintf(w, "%s=%v"+le, key, val)
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)
372 fmt.Fprint(w, le+"ENV:"+le)
374 // log the environment
375 for _, env := range lfs.Environ(cfg, getTransferManifest()) {
376 fmt.Fprint(w, env+le)
379 fmt.Fprint(w, le+"Client IP addresses:"+le)
381 for _, ip := range ipAddresses() {
386 func determineIncludeExcludePaths(config *config.Configuration, includeArg, excludeArg *string) (include, exclude []string) {
387 if includeArg == nil {
388 include = config.FetchIncludePaths()
390 include = tools.CleanPaths(*includeArg, ",")
392 if excludeArg == nil {
393 exclude = config.FetchExcludePaths()
395 exclude = tools.CleanPaths(*excludeArg, ",")
400 func buildProgressMeter(dryRun bool) *progress.ProgressMeter {
401 return progress.NewMeter(
402 progress.WithOSEnv(cfg.Os),
403 progress.DryRun(dryRun),
407 func requireGitVersion() {
408 minimumGit := "1.8.2"
410 if !git.Config.IsGitVersionAtLeast(minimumGit) {
411 gitver, err := git.Config.Version()
413 Exit("Error getting git version: %s", err)
415 Exit("git version >= %s is required for Git LFS, your version: %s", minimumGit, gitver)
420 log.SetOutput(ErrorWriter)