git-lfs/commands/run.go
brian m. carlson f4f8fae03d
commands: canonicalize certain Git environment variables
When we use certain Git environment variables to find the Git directory
and the working tree, we want those values to work regardless of what
directory we're in.  Currently we don't change the working directory,
but we soon will in order to mimic Git's behavior.  In order to make
relative values of these environment variables work correctly once we
change directories, let's turn them into absolute canonical paths when
we set up the repository.

We save the old environment variables into a variable and pass this
variable to the helper for the git lfs env command, which wants to print
the original values, not our modified ones.  Note that we can't make the
canonicalization conditional on the subcommand being run because the
configuration structure has been initialized by the time we get to
argument parsing and the configuration code invokes Git upon
initialization.

Because the Cygwin path canonicalization invokes a subprocess on Windows
and the subprocess code caches the environment, we must also reset the
cache after adjusting these environment variables so that future
subprocess invocations, like those for Git, work as expected with the
updated environment.
2020-10-14 16:49:23 +00:00

164 lines
3.9 KiB
Go

package commands
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/tools"
"github.com/spf13/cobra"
)
var (
commandFuncs []func() *cobra.Command
commandMu sync.Mutex
rootVersion bool
)
// NewCommand creates a new 'git-lfs' sub command, given a command name and
// command run function.
//
// Each command will initialize the local storage ('.git/lfs') directory when
// run, unless the PreRun hook is set to nil.
func NewCommand(name string, runFn func(*cobra.Command, []string)) *cobra.Command {
return &cobra.Command{Use: name, Run: runFn, PreRun: setupHTTPLogger}
}
// RegisterCommand creates a direct 'git-lfs' subcommand, given a command name,
// a command run function, and an optional callback during the command
// initialization process.
//
// The 'git-lfs' command initialization is deferred until the `commands.Run()`
// function is called. The fn callback is passed the output from NewCommand,
// and gives the caller the flexibility to customize the command by adding
// flags, tweaking command hooks, etc.
func RegisterCommand(name string, runFn func(cmd *cobra.Command, args []string), fn func(cmd *cobra.Command)) {
commandMu.Lock()
commandFuncs = append(commandFuncs, func() *cobra.Command {
cmd := NewCommand(name, runFn)
if fn != nil {
fn(cmd)
}
return cmd
})
commandMu.Unlock()
}
// Run initializes the 'git-lfs' command and runs it with the given stdin and
// command line args.
//
// It returns an exit code.
func Run() int {
log.SetOutput(ErrorWriter)
root := NewCommand("git-lfs", gitlfsCommand)
root.PreRun = nil
// Set up help/usage funcs based on manpage text
helpcmd := &cobra.Command{
Use: "help [command]",
Short: "Help about any command",
Long: `Help provides help for any command in the application.
Simply type ` + root.Name() + ` help [path to command] for full details.`,
Run: func(c *cobra.Command, args []string) {
cmd, _, e := c.Root().Find(args)
// In the case of "git lfs help config", pretend the
// last arg was "help" so our command lookup succeeds,
// since cmd will be ignored in helpCommand().
if e != nil && args[0] == "config" {
cmd, _, e = c.Root().Find([]string{"help"})
}
if cmd == nil || e != nil {
c.Printf("Unknown help topic %#q\n", args)
c.Root().Usage()
} else {
c.HelpFunc()(cmd, args)
}
},
}
root.SetHelpCommand(helpcmd)
root.SetHelpTemplate("{{.UsageString}}")
root.SetHelpFunc(helpCommand)
root.SetUsageFunc(usageCommand)
root.Flags().BoolVarP(&rootVersion, "version", "v", false, "")
canonicalizeEnvironment()
cfg = config.New()
for _, f := range commandFuncs {
if cmd := f(); cmd != nil {
root.AddCommand(cmd)
}
}
err := root.Execute()
closeAPIClient()
if err != nil {
return 127
}
return 0
}
func gitlfsCommand(cmd *cobra.Command, args []string) {
versionCommand(cmd, args)
if !rootVersion {
cmd.Usage()
}
}
func helpCommand(cmd *cobra.Command, args []string) {
if len(args) == 0 {
printHelp("git-lfs")
} else {
printHelp(args[0])
}
}
func usageCommand(cmd *cobra.Command) error {
printHelp(cmd.Name())
return nil
}
func printHelp(commandName string) {
if commandName == "--help" {
commandName = "git-lfs"
}
if txt, ok := ManPages[commandName]; ok {
fmt.Fprintf(os.Stdout, "%s\n", strings.TrimSpace(txt))
} else {
fmt.Fprintf(os.Stdout, "Sorry, no usage text found for %q\n", commandName)
}
}
func setupHTTPLogger(cmd *cobra.Command, args []string) {
if len(os.Getenv("GIT_LOG_STATS")) < 1 {
return
}
logBase := filepath.Join(cfg.LocalLogDir(), "http")
if err := tools.MkdirAll(logBase, cfg); err != nil {
fmt.Fprintf(os.Stderr, "Error logging http stats: %s\n", err)
return
}
logFile := fmt.Sprintf("http-%d.log", time.Now().Unix())
file, err := os.Create(filepath.Join(logBase, logFile))
if err != nil {
fmt.Fprintf(os.Stderr, "Error logging http stats: %s\n", err)
} else {
getAPIClient().LogHTTPStats(file)
}
}