1package commands 2 3import ( 4 "fmt" 5 "log" 6 "os" 7 "path/filepath" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/git-lfs/git-lfs/v3/config" 13 "github.com/git-lfs/git-lfs/v3/tools" 14 "github.com/spf13/cobra" 15) 16 17var ( 18 commandFuncs []func() *cobra.Command 19 commandMu sync.Mutex 20 21 rootVersion bool 22) 23 24// NewCommand creates a new 'git-lfs' sub command, given a command name and 25// command run function. 26// 27// Each command will initialize the local storage ('.git/lfs') directory when 28// run, unless the PreRun hook is set to nil. 29func NewCommand(name string, runFn func(*cobra.Command, []string)) *cobra.Command { 30 return &cobra.Command{Use: name, Run: runFn, PreRun: setupHTTPLogger} 31} 32 33// RegisterCommand creates a direct 'git-lfs' subcommand, given a command name, 34// a command run function, and an optional callback during the command 35// initialization process. 36// 37// The 'git-lfs' command initialization is deferred until the `commands.Run()` 38// function is called. The fn callback is passed the output from NewCommand, 39// and gives the caller the flexibility to customize the command by adding 40// flags, tweaking command hooks, etc. 41func RegisterCommand(name string, runFn func(cmd *cobra.Command, args []string), fn func(cmd *cobra.Command)) { 42 commandMu.Lock() 43 commandFuncs = append(commandFuncs, func() *cobra.Command { 44 cmd := NewCommand(name, runFn) 45 if fn != nil { 46 fn(cmd) 47 } 48 return cmd 49 }) 50 commandMu.Unlock() 51} 52 53// Run initializes the 'git-lfs' command and runs it with the given stdin and 54// command line args. 55// 56// It returns an exit code. 57func Run() int { 58 log.SetOutput(ErrorWriter) 59 60 root := NewCommand("git-lfs", gitlfsCommand) 61 root.PreRun = nil 62 63 // Set up help/usage funcs based on manpage text 64 helpcmd := &cobra.Command{ 65 Use: "help [command]", 66 Short: "Help about any command", 67 Long: `Help provides help for any command in the application. 68Simply type ` + root.Name() + ` help [path to command] for full details.`, 69 70 Run: func(c *cobra.Command, args []string) { 71 cmd, _, e := c.Root().Find(args) 72 // In the case of "git lfs help config", pretend the 73 // last arg was "help" so our command lookup succeeds, 74 // since cmd will be ignored in helpCommand(). 75 if e != nil && args[0] == "config" { 76 cmd, _, e = c.Root().Find([]string{"help"}) 77 } 78 if cmd == nil || e != nil { 79 c.Printf("Unknown help topic %#q\n", args) 80 c.Root().Usage() 81 } else { 82 c.HelpFunc()(cmd, args) 83 } 84 }, 85 } 86 87 root.SetHelpCommand(helpcmd) 88 89 root.SetHelpTemplate("{{.UsageString}}") 90 root.SetHelpFunc(helpCommand) 91 root.SetUsageFunc(usageCommand) 92 93 root.Flags().BoolVarP(&rootVersion, "version", "v", false, "") 94 95 canonicalizeEnvironment() 96 97 cfg = config.New() 98 99 for _, f := range commandFuncs { 100 if cmd := f(); cmd != nil { 101 root.AddCommand(cmd) 102 } 103 } 104 105 err := root.Execute() 106 closeAPIClient() 107 108 if err != nil { 109 return 127 110 } 111 return 0 112} 113 114func gitlfsCommand(cmd *cobra.Command, args []string) { 115 versionCommand(cmd, args) 116 if !rootVersion { 117 cmd.Usage() 118 } 119} 120 121func helpCommand(cmd *cobra.Command, args []string) { 122 if len(args) == 0 { 123 printHelp("git-lfs") 124 } else { 125 printHelp(args[0]) 126 } 127} 128 129func usageCommand(cmd *cobra.Command) error { 130 printHelp(cmd.Name()) 131 return nil 132} 133 134func printHelp(commandName string) { 135 if commandName == "--help" { 136 commandName = "git-lfs" 137 } 138 if txt, ok := ManPages[commandName]; ok { 139 fmt.Fprintf(os.Stdout, "%s\n", strings.TrimSpace(txt)) 140 } else { 141 fmt.Fprintf(os.Stdout, "Sorry, no usage text found for %q\n", commandName) 142 } 143} 144 145func setupHTTPLogger(cmd *cobra.Command, args []string) { 146 if len(os.Getenv("GIT_LOG_STATS")) < 1 { 147 return 148 } 149 150 logBase := filepath.Join(cfg.LocalLogDir(), "http") 151 if err := tools.MkdirAll(logBase, cfg); err != nil { 152 fmt.Fprintf(os.Stderr, "Error logging http stats: %s\n", err) 153 return 154 } 155 156 logFile := fmt.Sprintf("http-%d.log", time.Now().Unix()) 157 file, err := os.Create(filepath.Join(logBase, logFile)) 158 if err != nil { 159 fmt.Fprintf(os.Stderr, "Error logging http stats: %s\n", err) 160 } else { 161 getAPIClient().LogHTTPStats(file) 162 } 163} 164