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