github.com/git-lfs/git-lfs@v2.5.2+incompatible/commands/commands.go (about)

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