2014-06-03 15:08:58 +00:00
|
|
|
package commands
|
2013-09-22 23:04:06 +00:00
|
|
|
|
|
|
|
import (
|
2014-06-05 18:19:30 +00:00
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
2013-09-22 23:04:06 +00:00
|
|
|
"os"
|
2013-10-04 14:42:47 +00:00
|
|
|
"os/exec"
|
2013-09-27 14:39:45 +00:00
|
|
|
"path/filepath"
|
2014-06-05 18:19:30 +00:00
|
|
|
"strings"
|
2016-08-10 15:33:25 +00:00
|
|
|
"sync"
|
2014-06-05 18:19:30 +00:00
|
|
|
"time"
|
2015-05-13 19:43:41 +00:00
|
|
|
|
2016-05-26 20:29:21 +00:00
|
|
|
"github.com/github/git-lfs/api"
|
2016-05-13 16:38:06 +00:00
|
|
|
"github.com/github/git-lfs/config"
|
2016-05-16 14:06:02 +00:00
|
|
|
"github.com/github/git-lfs/errutil"
|
2015-07-28 22:43:22 +00:00
|
|
|
"github.com/github/git-lfs/git"
|
2016-07-22 00:07:23 +00:00
|
|
|
"github.com/github/git-lfs/httputil"
|
2015-05-13 19:43:41 +00:00
|
|
|
"github.com/github/git-lfs/lfs"
|
2016-05-31 15:48:09 +00:00
|
|
|
"github.com/github/git-lfs/tools"
|
2016-08-10 15:33:25 +00:00
|
|
|
"github.com/github/git-lfs/transfer"
|
2016-05-23 18:02:27 +00:00
|
|
|
"github.com/spf13/cobra"
|
2013-09-22 23:04:06 +00:00
|
|
|
)
|
|
|
|
|
2015-09-14 11:33:59 +00:00
|
|
|
// Populate man pages
|
|
|
|
//go:generate go run ../docs/man/mangen.go
|
|
|
|
|
2014-06-05 18:19:30 +00:00
|
|
|
var (
|
2016-05-26 20:29:21 +00:00
|
|
|
// API is a package-local instance of the API client for use within
|
|
|
|
// various command implementations.
|
2016-05-27 20:53:14 +00:00
|
|
|
API = api.NewClient(nil)
|
2016-05-26 20:29:21 +00:00
|
|
|
|
2016-08-10 16:08:08 +00:00
|
|
|
Debugging = false
|
|
|
|
ErrorBuffer = &bytes.Buffer{}
|
|
|
|
ErrorWriter = io.MultiWriter(os.Stderr, ErrorBuffer)
|
|
|
|
OutputWriter = io.MultiWriter(os.Stdout, ErrorBuffer)
|
|
|
|
ManPages = make(map[string]string, 20)
|
|
|
|
cfg *config.Configuration
|
|
|
|
subcommandFuncs []func() *cobra.Command
|
|
|
|
subcommandMu sync.Mutex
|
2016-08-10 15:33:25 +00:00
|
|
|
|
|
|
|
includeArg string
|
|
|
|
excludeArg string
|
|
|
|
)
|
|
|
|
|
|
|
|
func Run() {
|
|
|
|
cfg = config.Config
|
|
|
|
|
|
|
|
root := &cobra.Command{
|
2015-09-17 22:08:28 +00:00
|
|
|
Use: "git-lfs",
|
2014-06-26 20:05:23 +00:00
|
|
|
Run: func(cmd *cobra.Command, args []string) {
|
2014-06-26 21:14:11 +00:00
|
|
|
versionCommand(cmd, args)
|
2014-06-26 21:21:13 +00:00
|
|
|
cmd.Usage()
|
2014-06-26 20:05:23 +00:00
|
|
|
},
|
|
|
|
}
|
2016-08-02 16:37:16 +00:00
|
|
|
|
2016-08-10 15:33:25 +00:00
|
|
|
// Set up help/usage funcs based on manpage text
|
|
|
|
root.SetHelpFunc(help)
|
|
|
|
root.SetHelpTemplate("{{.UsageString}}")
|
|
|
|
root.SetUsageFunc(usage)
|
|
|
|
|
|
|
|
for _, f := range subcommandFuncs {
|
|
|
|
if cmd := f(); cmd != nil {
|
|
|
|
root.AddCommand(cmd)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
root.Execute()
|
|
|
|
httputil.LogHttpStats(cfg)
|
|
|
|
}
|
|
|
|
|
|
|
|
func RegisterSubcommand(fn func() *cobra.Command) {
|
|
|
|
subcommandMu.Lock()
|
|
|
|
subcommandFuncs = append(subcommandFuncs, fn)
|
|
|
|
subcommandMu.Unlock()
|
|
|
|
}
|
2014-06-05 18:19:30 +00:00
|
|
|
|
2016-08-10 16:08:08 +00:00
|
|
|
// TransferManifest builds a transfer.Manifest from the commands package global
|
|
|
|
// cfg var.
|
|
|
|
func TransferManifest() *transfer.Manifest {
|
|
|
|
return transfer.ConfigureManifest(transfer.NewManifest(), cfg)
|
|
|
|
}
|
|
|
|
|
2014-06-05 18:19:30 +00:00
|
|
|
// Error prints a formatted message to Stderr. It also gets printed to the
|
|
|
|
// panic log if one is created for this command.
|
|
|
|
func Error(format string, args ...interface{}) {
|
2015-06-29 16:27:33 +00:00
|
|
|
line := format
|
|
|
|
if len(args) > 0 {
|
|
|
|
line = fmt.Sprintf(format, args...)
|
|
|
|
}
|
2014-06-05 18:19:30 +00:00
|
|
|
fmt.Fprintln(ErrorWriter, line)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Print prints a formatted message to Stdout. It also gets printed to the
|
|
|
|
// panic log if one is created for this command.
|
|
|
|
func Print(format string, args ...interface{}) {
|
|
|
|
line := fmt.Sprintf(format, args...)
|
|
|
|
fmt.Fprintln(OutputWriter, line)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exit prints a formatted message and exits.
|
|
|
|
func Exit(format string, args ...interface{}) {
|
|
|
|
Error(format, args...)
|
|
|
|
os.Exit(2)
|
|
|
|
}
|
|
|
|
|
2016-02-23 16:11:52 +00:00
|
|
|
func ExitWithError(err error) {
|
2016-05-16 14:06:02 +00:00
|
|
|
if Debugging || errutil.IsFatalError(err) {
|
2016-02-23 16:11:52 +00:00
|
|
|
Panic(err, err.Error())
|
|
|
|
} else {
|
2016-05-16 14:06:02 +00:00
|
|
|
if inner := errutil.GetInnerError(err); inner != nil {
|
2016-02-23 16:11:52 +00:00
|
|
|
Error(inner.Error())
|
|
|
|
}
|
|
|
|
Exit(err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-05 18:19:30 +00:00
|
|
|
// Debug prints a formatted message if debugging is enabled. The formatted
|
|
|
|
// message also shows up in the panic log, if created.
|
|
|
|
func Debug(format string, args ...interface{}) {
|
|
|
|
if !Debugging {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf(format, args...)
|
|
|
|
}
|
|
|
|
|
2014-08-07 21:33:29 +00:00
|
|
|
// LoggedError prints a formatted message to Stderr and writes a stack trace for
|
|
|
|
// the error to a log file without exiting.
|
|
|
|
func LoggedError(err error, format string, args ...interface{}) {
|
2014-06-05 18:19:30 +00:00
|
|
|
Error(format, args...)
|
|
|
|
file := handlePanic(err)
|
|
|
|
|
|
|
|
if len(file) > 0 {
|
2015-05-05 12:39:05 +00:00
|
|
|
fmt.Fprintf(os.Stderr, "\nErrors logged to %s\nUse `git lfs logs last` to view the log.\n", file)
|
2014-06-05 18:19:30 +00:00
|
|
|
}
|
2014-08-07 21:33:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Panic prints a formatted message, and writes a stack trace for the error to
|
|
|
|
// a log file before exiting.
|
|
|
|
func Panic(err error, format string, args ...interface{}) {
|
|
|
|
LoggedError(err, format, args...)
|
2014-06-05 18:19:30 +00:00
|
|
|
os.Exit(2)
|
|
|
|
}
|
2013-09-23 00:39:26 +00:00
|
|
|
|
2016-07-22 00:07:23 +00:00
|
|
|
func Cleanup() {
|
|
|
|
if err := lfs.ClearTempObjects(); err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Error clearing old temp files: %s\n", err)
|
|
|
|
}
|
2013-09-22 23:04:06 +00:00
|
|
|
}
|
|
|
|
|
2013-10-04 14:44:23 +00:00
|
|
|
func PipeMediaCommand(name string, args ...string) error {
|
|
|
|
return PipeCommand("bin/"+name, args...)
|
|
|
|
}
|
|
|
|
|
2013-10-04 14:42:47 +00:00
|
|
|
func PipeCommand(name string, args ...string) error {
|
|
|
|
cmd := exec.Command(name, args...)
|
|
|
|
cmd.Stdin = os.Stdin
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
return cmd.Run()
|
|
|
|
}
|
|
|
|
|
2015-04-24 20:24:32 +00:00
|
|
|
func requireStdin(msg string) {
|
2016-07-04 23:46:35 +00:00
|
|
|
var out string
|
|
|
|
|
|
|
|
stat, err := os.Stdin.Stat()
|
|
|
|
if err != nil {
|
|
|
|
out = fmt.Sprintf("Cannot read from STDIN. %s (%s)", msg, err)
|
|
|
|
} else if (stat.Mode() & os.ModeCharDevice) != 0 {
|
|
|
|
out = fmt.Sprintf("Cannot read from STDIN. %s", msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(out) > 0 {
|
|
|
|
Error(out)
|
2015-04-24 20:24:32 +00:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-08 16:23:27 +00:00
|
|
|
func requireInRepo() {
|
|
|
|
if !lfs.InRepo() {
|
|
|
|
Print("Not in a git repository.")
|
|
|
|
os.Exit(128)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-05 18:19:30 +00:00
|
|
|
func handlePanic(err error) string {
|
|
|
|
if err == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2015-05-14 17:36:02 +00:00
|
|
|
return logPanic(err)
|
2014-06-05 18:19:30 +00:00
|
|
|
}
|
|
|
|
|
2015-05-14 17:36:02 +00:00
|
|
|
func logPanic(loggedError error) string {
|
2014-08-07 21:00:54 +00:00
|
|
|
var fmtWriter io.Writer = os.Stderr
|
|
|
|
|
2015-05-14 17:36:02 +00:00
|
|
|
now := time.Now()
|
|
|
|
name := now.Format("20060102T150405.999999999")
|
2016-05-13 16:38:06 +00:00
|
|
|
full := filepath.Join(config.LocalLogDir, name+".log")
|
2015-05-14 17:36:02 +00:00
|
|
|
|
2016-05-13 16:38:06 +00:00
|
|
|
if err := os.MkdirAll(config.LocalLogDir, 0755); err != nil {
|
2015-05-14 17:39:03 +00:00
|
|
|
full = ""
|
2016-05-13 16:38:06 +00:00
|
|
|
fmt.Fprintf(fmtWriter, "Unable to log panic to %s: %s\n\n", config.LocalLogDir, err.Error())
|
2015-05-14 17:39:03 +00:00
|
|
|
} else if file, err := os.Create(full); err != nil {
|
2015-05-14 17:36:02 +00:00
|
|
|
filename := full
|
|
|
|
full = ""
|
|
|
|
defer func() {
|
|
|
|
fmt.Fprintf(fmtWriter, "Unable to log panic to %s\n\n", filename)
|
|
|
|
logPanicToWriter(fmtWriter, err)
|
|
|
|
}()
|
|
|
|
} else {
|
|
|
|
fmtWriter = file
|
|
|
|
defer file.Close()
|
2014-06-05 18:19:30 +00:00
|
|
|
}
|
|
|
|
|
2015-05-14 17:34:58 +00:00
|
|
|
logPanicToWriter(fmtWriter, loggedError)
|
|
|
|
|
|
|
|
return full
|
|
|
|
}
|
|
|
|
|
|
|
|
func logPanicToWriter(w io.Writer, loggedError error) {
|
2015-07-28 22:43:22 +00:00
|
|
|
// log the version
|
|
|
|
gitV, err := git.Config.Version()
|
|
|
|
if err != nil {
|
|
|
|
gitV = "Error getting git version: " + err.Error()
|
|
|
|
}
|
|
|
|
|
2016-05-16 11:13:13 +00:00
|
|
|
fmt.Fprintln(w, config.VersionDesc)
|
2015-07-28 22:43:22 +00:00
|
|
|
fmt.Fprintln(w, gitV)
|
|
|
|
|
|
|
|
// log the command that was run
|
|
|
|
fmt.Fprintln(w)
|
|
|
|
fmt.Fprintf(w, "$ %s", filepath.Base(os.Args[0]))
|
2014-06-05 18:19:30 +00:00
|
|
|
if len(os.Args) > 0 {
|
2015-05-14 17:34:58 +00:00
|
|
|
fmt.Fprintf(w, " %s", strings.Join(os.Args[1:], " "))
|
2014-06-05 18:19:30 +00:00
|
|
|
}
|
2015-05-14 17:34:58 +00:00
|
|
|
fmt.Fprintln(w)
|
2014-06-05 18:19:30 +00:00
|
|
|
|
2015-07-28 22:43:22 +00:00
|
|
|
// log the error message and stack trace
|
2015-05-14 17:34:58 +00:00
|
|
|
w.Write(ErrorBuffer.Bytes())
|
|
|
|
fmt.Fprintln(w)
|
2014-06-05 18:19:30 +00:00
|
|
|
|
2015-05-14 17:34:58 +00:00
|
|
|
fmt.Fprintln(w, loggedError.Error())
|
2014-08-07 22:08:47 +00:00
|
|
|
|
2015-08-21 18:31:06 +00:00
|
|
|
if err, ok := loggedError.(ErrorWithStack); ok {
|
|
|
|
fmt.Fprintln(w, err.InnerError())
|
|
|
|
for key, value := range err.Context() {
|
2015-05-14 17:34:58 +00:00
|
|
|
fmt.Fprintf(w, "%s=%s\n", key, value)
|
2014-08-08 17:05:24 +00:00
|
|
|
}
|
2015-08-21 18:31:06 +00:00
|
|
|
w.Write(err.Stack())
|
2014-08-07 22:08:47 +00:00
|
|
|
} else {
|
2016-05-16 14:06:02 +00:00
|
|
|
w.Write(errutil.Stack())
|
2014-08-07 22:08:47 +00:00
|
|
|
}
|
2015-07-28 22:43:22 +00:00
|
|
|
fmt.Fprintln(w, "\nENV:")
|
2014-06-05 18:19:30 +00:00
|
|
|
|
2015-07-28 22:43:22 +00:00
|
|
|
// log the environment
|
2016-08-10 16:08:08 +00:00
|
|
|
for _, env := range lfs.Environ(cfg, TransferManifest()) {
|
2015-05-14 17:20:23 +00:00
|
|
|
fmt.Fprintln(w, env)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-08 16:51:47 +00:00
|
|
|
type ErrorWithStack interface {
|
2014-08-08 17:05:24 +00:00
|
|
|
Context() map[string]string
|
2014-08-08 16:51:47 +00:00
|
|
|
InnerError() string
|
|
|
|
Stack() []byte
|
|
|
|
}
|
|
|
|
|
2016-08-01 22:47:41 +00:00
|
|
|
func determineIncludeExcludePaths(config *config.Configuration, includeArg, excludeArg *string) (include, exclude []string) {
|
|
|
|
if includeArg == nil {
|
|
|
|
include = config.FetchIncludePaths()
|
|
|
|
} else {
|
|
|
|
include = tools.CleanPaths(*includeArg, ",")
|
|
|
|
}
|
|
|
|
if excludeArg == nil {
|
|
|
|
exclude = config.FetchExcludePaths()
|
|
|
|
} else {
|
|
|
|
exclude = tools.CleanPaths(*excludeArg, ",")
|
|
|
|
}
|
|
|
|
return
|
2016-05-31 15:48:09 +00:00
|
|
|
}
|
|
|
|
|
2015-09-15 09:22:33 +00:00
|
|
|
func printHelp(commandName string) {
|
|
|
|
if txt, ok := ManPages[commandName]; ok {
|
2015-09-17 21:56:06 +00:00
|
|
|
fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(txt))
|
2015-09-15 09:22:33 +00:00
|
|
|
} else {
|
|
|
|
fmt.Fprintf(os.Stderr, "Sorry, no usage text found for %q\n", commandName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-14 11:33:59 +00:00
|
|
|
// help is used for 'git-lfs help <command>'
|
|
|
|
func help(cmd *cobra.Command, args []string) {
|
2015-09-15 09:22:33 +00:00
|
|
|
if len(args) == 0 {
|
|
|
|
printHelp("git-lfs")
|
2015-09-14 11:33:59 +00:00
|
|
|
} else {
|
2015-09-15 09:22:33 +00:00
|
|
|
printHelp(args[0])
|
2015-09-14 11:33:59 +00:00
|
|
|
}
|
2015-09-15 09:22:33 +00:00
|
|
|
|
2015-09-14 11:33:59 +00:00
|
|
|
}
|
|
|
|
|
2015-09-17 21:54:33 +00:00
|
|
|
// usage is used for 'git-lfs <command> --help' or when invoked manually
|
2015-09-14 11:33:59 +00:00
|
|
|
func usage(cmd *cobra.Command) error {
|
2015-09-15 09:22:33 +00:00
|
|
|
printHelp(cmd.Name())
|
2015-09-14 11:33:59 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-07-21 16:37:56 +00:00
|
|
|
// isCommandEnabled returns whether the environment variable GITLFS<CMD>ENABLED
|
|
|
|
// is "truthy" according to config.GetenvBool (see
|
|
|
|
// github.com/github/git-lfs/config#Configuration.GetenvBool), returning false
|
|
|
|
// by default if the enviornment variable is not specified.
|
2016-07-21 16:07:06 +00:00
|
|
|
//
|
|
|
|
// This function call should only guard commands that do not yet have stable
|
|
|
|
// APIs or solid server implementations.
|
2016-07-21 16:37:56 +00:00
|
|
|
func isCommandEnabled(cfg *config.Configuration, cmd string) bool {
|
|
|
|
return cfg.GetenvBool(fmt.Sprintf("GITLFS%sENABLED", strings.ToUpper(cmd)), false)
|
2016-07-21 16:07:06 +00:00
|
|
|
}
|
|
|
|
|
2014-06-05 18:19:30 +00:00
|
|
|
func init() {
|
|
|
|
log.SetOutput(ErrorWriter)
|
|
|
|
}
|