diff --git a/cmd/all.go b/cmd/all.go index 3454c14..2aab4ce 100644 --- a/cmd/all.go +++ b/cmd/all.go @@ -6,6 +6,7 @@ func AddSubcommands(baseCmd *cobra.Command) { baseCmd.AddCommand( addRemoteCmd, applyCmd, + newConfigCmd(), newCloneCmd(), editCmd, fetchCmd, diff --git a/cmd/config.go b/cmd/config.go new file mode 100644 index 0000000..e58ad39 --- /dev/null +++ b/cmd/config.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "strings" +) + +func newConfigCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "config key[=value]", + Short: "set or get a config value", + RunE: Config, + } + + // Set flags + cmd.Flags().Bool("global", false, "set/get from global config") + + return cmd +} + +func Config(cmd *cobra.Command, args []string) error { + // Three modes: + // - config key - get key + // - config key value - set key to value + // - config key=value - set key to value + + if len(args) > 1 { + return setConfig(args[0], args[1]) + } + if strings.Contains(args[0], "=") { + split := strings.SplitN(args[0], "=", 0) + return setConfig(split[0], split[1]) + } + return getConfig(args[0]) +} + +func getConfig(key string) error {} + +func setConfig(key, value string) error {} diff --git a/cmd/edit.go b/cmd/edit.go index 775e3b5..c0c15c5 100644 --- a/cmd/edit.go +++ b/cmd/edit.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "github.com/barrettj12/jit/common" + "github.com/barrettj12/jit/common/config" "github.com/barrettj12/jit/common/types" "github.com/spf13/cobra" "strings" @@ -16,6 +17,14 @@ var editCmd = &cobra.Command{ // Edit opens the given branch in the default editor. func Edit(cmd *cobra.Command, args []string) error { + editor, err := config.Editor() + if err != nil { + fmt.Printf("error getting default editor: %v\n", err) + } else { + fmt.Printf("default editor is %q\n", editor) + } + return nil + branch, err := common.ReqArg(args, 0, "Which branch would you like to edit?") if err != nil { return err diff --git a/common/config/bindings.go b/common/config/bindings.go new file mode 100644 index 0000000..b3f32bf --- /dev/null +++ b/common/config/bindings.go @@ -0,0 +1,17 @@ +package config + +import ( + "errors" +) + +// Editor returns the default editor. +func Editor() (string, error) { + editor, err := readRepoConfig[string]("editor") + if err != nil { + editor, err = readGlobalConfig[string]("editor") + if err != nil { + return "", errors.New("default editor not set") + } + } + return editor, nil +} diff --git a/common/config/config.go b/common/config/config.go new file mode 100644 index 0000000..fc48eaa --- /dev/null +++ b/common/config/config.go @@ -0,0 +1,65 @@ +package config + +import ( + "fmt" + "github.com/barrettj12/jit/common" + "gopkg.in/yaml.v3" + "os" + "path/filepath" +) + +// Read a config value from the repo-specific config file at +// +// /.jit/config.yaml +// +// The provided type parameter defines the type that the value will be +// unmarshalled to. +func readRepoConfig[T any](key string) (t T, err error) { + repoBasePath, err := getRepoBasePath() + if err != nil { + return t, fmt.Errorf("getting repo base path: %w", err) + } + repoConfigFilePath := filepath.Join(repoBasePath.Path(), ".jit/config.yaml") + return readConfigFile[T](repoConfigFilePath, key) +} + +// Read a config value from the global Jit config file at +// +// $HOME/.jit/config.yaml +// +// The provided type parameter defines the type that the value will be +// unmarshalled to. +func readGlobalConfig[T any](key string) (t T, err error) { + userHomeDir, err := getUserHomeDir() + if err != nil { + return t, fmt.Errorf("getting user home dir: %w", err) + } + globalConfigFilePath := filepath.Join(userHomeDir, ".jit/config.yaml") + return readConfigFile[T](globalConfigFilePath, key) +} + +// Read a key from a Jit config file. The format of the config file is a +// uni-level mapping of keys to values. The provided type parameter defines the +// type that the value will be unmarshalled to. +func readConfigFile[T any](filePath, key string) (t T, err error) { + file, err := os.Open(filePath) + if err != nil { + return t, fmt.Errorf("reading repo config: %w", err) + } + + var config map[string]T + // We will get an error for values that don't match type T, but we should + // ignore this, because we only care about the value corresponding to the + // given key. + err = yaml.NewDecoder(file).Decode(&config) + if _, ok := config[key]; !ok { + return t, fmt.Errorf("key %q not defined in config file at %s", key, filePath) + } + return config[key], nil +} + +func addKeyToConfigFile(filePath, key string, value any) {} + +// These methods can be replaced for testing. +var getRepoBasePath = common.RepoBasePath +var getUserHomeDir = os.UserHomeDir diff --git a/common/config/config_test.go b/common/config/config_test.go new file mode 100644 index 0000000..d66693d --- /dev/null +++ b/common/config/config_test.go @@ -0,0 +1,32 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + "testing" +) + +// TODO: maybe better to do this in each test to isolate the tests +func TestMain(m *testing.M) { + // Mock out config files + tmpDir, err := os.MkdirTemp("", "") + if err != nil { + panic(fmt.Sprintf("failed to create testing dir: %v", err)) + } + defer func() { + err := os.RemoveAll(tmpDir) + if err != nil { + fmt.Printf("error removing temp dir %q: %v", tmpDir, err) + } + }() + + getRepoConfigFilePath = func() (string, error) { + return filepath.Join(tmpDir, "repoconfig"), nil + } + getGlobalConfigFilePath = func() (string, error) { + return filepath.Join(tmpDir, "globalconfig"), nil + } + + m.Run() +} diff --git a/common/config/doc.go b/common/config/doc.go new file mode 100644 index 0000000..c0457e9 --- /dev/null +++ b/common/config/doc.go @@ -0,0 +1,8 @@ +// Package config contains utilities for interacting with Jit config. +// Jit can be configured in several ways: +// - environment variables +// - global Jit config in $HOME/.jit/config +// - repo-specific Jit config in $REPO/.jit/config +// +// Jit config is distinct from Git config. +package config diff --git a/go.mod b/go.mod index d248f2b..8025f42 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module github.com/barrettj12/jit go 1.22 -require github.com/spf13/cobra v1.8.1 +require ( + github.com/spf13/cobra v1.8.1 + gopkg.in/yaml.v3 v3.0.1 +) require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index 912390a..a01295b 100644 --- a/go.sum +++ b/go.sum @@ -6,5 +6,7 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=