Files
wehub-resource-sync 5357c39144
Fuzzer / Run Fuzzer (push) Has been cancelled
Race tests / Go race tests (ubuntu-22.04) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:01:40 +08:00

352 lines
12 KiB
Go

// Copyright 2019 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"context"
"strings"
"github.com/fatih/color"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/libraries/utils/config"
"github.com/dolthub/dolt/go/libraries/utils/set"
)
const (
globalParamName = "global"
localParamName = "local"
addOperationStr = "add"
setOperationStr = "set"
listOperationStr = "list"
getOperationStr = "get"
unsetOperationStr = "unset"
)
var cfgDocs = cli.CommandDocumentationContent{
ShortDesc: `Get and set repository or global options`,
LongDesc: `You can query/set/replace/unset options with this command.
When reading, the values are read from the global and repository local configuration files, and options {{.LessThan}}--global{{.GreaterThan}}, and {{.LessThan}}--local{{.GreaterThan}} can be used to tell the command to read from only that location.
When writing, the new value is written to the repository local configuration file by default, and options {{.LessThan}}--global{{.GreaterThan}}, can be used to tell the command to write to that location (you can say {{.LessThan}}--local{{.GreaterThan}} but that is the default).
Valid configuration variables:
- core.editor - lets you edit 'commit' or 'tag' messages by launching the set editor.
- creds.add_url - sets the endpoint used to authenticate a client for 'dolt login'.
- doltlab.insecure - boolean flag used to authenticate a client against DoltLab.
- init.defaultbranch - allows overriding the default branch name e.g. when initializing a new repository.
- metrics.disabled - boolean flag disables sending metrics when true.
- user.creds - sets user keypairs for authenticating with doltremoteapi.
- user.email - sets name used in the author and committer field of commit objects.
- user.name - sets email used in the author and committer field of commit objects.
- remotes.default_host - sets default host for authenticating with doltremoteapi.
- remotes.default_port - sets default port for authenticating with doltremoteapi.
- remotesapi.credential_helper - sets a global credential helper for remotesapi requests. This option is ignored in repository local config.
- push.autoSetupRemote - if set to "true" assume --set-upstream on default push when no upstream tracking exists for the current branch.
Credential helpers use the Bazel credential helper protocol. Dolt invokes the configured executable with the {{.EmphasisLeft}}get{{.EmphasisRight}} argument and sends the canonical remotesapi origin on stdin:
{"uri":"https://example.com:443"}
The helper returns additive HTTP headers and, optionally, an RFC 3339 expiration time:
{"headers":{"Proxy-Authorization":["Bearer TOKEN"]},"expires":"2026-06-24T20:00:00Z"}
Dolt caches headers until shortly before their expiration. Responses without an expiration are used once. The helper is responsible for returning credentials only for origins it trusts. It cannot replace Dolt's own Authorization header or transport-controlled headers.
`,
Synopsis: []string{
`[--global|--local] --list`,
`[--global|--local] --add {{.LessThan}}name{{.GreaterThan}} {{.LessThan}}value{{.GreaterThan}}`,
`[--global|--local] --set {{.LessThan}}name{{.GreaterThan}} {{.LessThan}}value{{.GreaterThan}}`,
`[--global|--local] --get {{.LessThan}}name{{.GreaterThan}}`,
`[--global|--local] --unset {{.LessThan}}name{{.GreaterThan}}...`,
},
}
type ConfigCmd struct{}
// Name returns the name of the Dolt cli command. This is what is used on the command line to invoke the command
func (cmd ConfigCmd) Name() string {
return "config"
}
// Description returns a description of the command
func (cmd ConfigCmd) Description() string {
return "Dolt configuration."
}
func (cmd ConfigCmd) RequiresRepo() bool {
return false
}
func (cmd ConfigCmd) Docs() *cli.CommandDocumentation {
ap := cmd.ArgParser()
return cli.NewCommandDocumentation(cfgDocs, ap)
}
func (cmd ConfigCmd) ArgParser() *argparser.ArgParser {
ap := argparser.NewArgParserWithVariableArgs(cmd.Name())
ap.SupportsFlag(globalParamName, "", "Use global config.")
ap.SupportsFlag(localParamName, "", "Use repository local config.")
ap.SupportsFlag(addOperationStr, "", "Set the value of one or more config parameters")
ap.SupportsFlag(setOperationStr, "", "Set the value of one or more config parameters")
ap.SupportsFlag(listOperationStr, "", "List the values of all config parameters.")
ap.SupportsFlag(getOperationStr, "", "Get the value of one or more config parameters.")
ap.SupportsFlag(unsetOperationStr, "", "Unset the value of one or more config parameters.")
return ap
}
// Exec is used by the config command to allow users to view / edit their global and repository local configurations.
// Exec executes the command
func (cmd ConfigCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int {
ap := cmd.ArgParser()
help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, cfgDocs, ap))
apr := cli.ParseArgsOrDie(ap, args, help)
cfgTypes := apr.FlagsEqualTo([]string{globalParamName, localParamName}, true)
ops := apr.FlagsEqualTo([]string{addOperationStr, setOperationStr, listOperationStr, getOperationStr, unsetOperationStr}, true)
if cfgTypes.Size() > 1 {
cli.PrintErrln(color.RedString("Specifying both -local and -global is not valid. Exactly one may be set"))
usage()
} else {
switch ops.Size() {
case 1:
return processConfigCommand(dEnv, cfgTypes, ops.AsSlice()[0], apr.Args, usage)
default:
cli.PrintErrln(color.RedString("Exactly one of the -add, -set, -get, -unset, -list flags must be set."))
usage()
}
}
return 1
}
func processConfigCommand(dEnv *env.DoltEnv, setCfgTypes *set.StrSet, opName string, args []string, usage cli.UsagePrinter) int {
switch opName {
case getOperationStr:
return getOperation(dEnv, setCfgTypes, args, func(k string, v *string) {
cli.Println(*v)
})
case addOperationStr, setOperationStr:
return addOperation(dEnv, setCfgTypes, args, usage)
case unsetOperationStr:
return unsetOperation(dEnv, setCfgTypes, args, usage)
case listOperationStr:
return listOperation(dEnv, setCfgTypes, args, usage, func(k string, v string) {
cli.Println(k, "=", v)
})
}
panic("New operation added but not implemented.")
}
// Gets the config value for the key requested in the args, running the printFn given with the key and fetched value as
// arguments. If the key is not found, or if there is an error retrieving it, returns 1. Otherwise returns 0.
func getOperation(dEnv *env.DoltEnv, setCfgTypes *set.StrSet, args []string, printFn func(string, *string)) int {
if len(args) != 1 {
// matches git behavior... kinda dumb
return 1
}
var cfg config.ReadableConfig
switch setCfgTypes.Size() {
case 0:
cfg = dEnv.Config
case 1:
configElement := newCfgElement(setCfgTypes.AsSlice()[0])
var ok bool
cfg, ok = dEnv.Config.GetConfig(configElement)
if !ok {
cli.Println(color.RedString("No config found for %s", configElement.String()))
return 1
}
default:
// should be impossible due to earlier checks
cli.Println(color.RedString("Cannot get more than one config scope at once"))
return 1
}
val, err := cfg.GetString(args[0])
if err != nil {
if err != config.ErrConfigParamNotFound {
cli.PrintErrln(color.RedString("Unexpected error: %s", err.Error()))
}
// Not found prints no error but returns status 1
return 1
}
printFn(args[0], &val)
return 0
}
func addOperation(dEnv *env.DoltEnv, setCfgTypes *set.StrSet, args []string, usage cli.UsagePrinter) int {
if len(args) == 0 || len(args)%2 != 0 {
cli.Println("error: wrong number of arguments")
usage()
return 1
}
updates := make(map[string]string)
for i := 0; i < len(args); i += 2 {
option := strings.ToLower(args[i])
value := args[i+1]
if _, ok := config.ConfigOptions[option]; !ok && !strings.HasPrefix(option, env.SqlServerGlobalsPrefix) {
cli.Println("error: invalid config option, use dolt config --help to check valid configuration variables")
return 1
}
updates[option] = value
}
var cfgType string
switch setCfgTypes.Size() {
case 0:
cfgType = localParamName
case 1:
cfgType = setCfgTypes.AsSlice()[0]
default:
cli.Println("error: cannot add to multiple configs simultaneously")
return 1
}
cfg, ok := dEnv.Config.GetConfig(newCfgElement(cfgType))
if !ok {
switch cfgType {
case globalParamName:
panic("Should not have been able to get this far without a global config.")
case localParamName:
configDir, err := dEnv.FS.Abs(".")
if err != nil {
cli.PrintErrln(color.RedString("Unable to resolve current path to create repo local config file"))
return 1
}
err = dEnv.Config.CreateLocalConfig(configDir, updates)
if err != nil {
cli.PrintErrln(color.RedString("Unable to create repo local config file"))
return 1
}
return 0
default:
cli.Println("error: unknown config flag")
return 1
}
}
err := cfg.SetStrings(updates)
if err != nil {
cli.PrintErrln(color.RedString("Failed to update config."))
return 1
}
cli.Println(color.CyanString("Config successfully updated."))
return 0
}
func unsetOperation(dEnv *env.DoltEnv, setCfgTypes *set.StrSet, args []string, usage cli.UsagePrinter) int {
if len(args) == 0 {
cli.Println("error: wrong number of arguments")
usage()
return 1
}
for i, a := range args {
args[i] = strings.ToLower(a)
}
var cfgType string
switch setCfgTypes.Size() {
case 0:
cfgType = localParamName
case 1:
cfgType = setCfgTypes.AsSlice()[0]
default:
cli.Println("error: cannot unset from multiple configs simultaneously")
return 1
}
if cfg, ok := dEnv.Config.GetConfig(newCfgElement(cfgType)); !ok {
cli.PrintErrln(color.RedString("Unable to read config."))
return 1
} else {
err := cfg.Unset(args)
if err != nil && err != config.ErrConfigParamNotFound {
cli.PrintErrln(color.RedString("Error unsetting the keys %v. Error: %s", args, err.Error()))
return 1
}
cli.Println(color.CyanString("Config successfully updated."))
return 0
}
}
func listOperation(dEnv *env.DoltEnv, setCfgTypes *set.StrSet, args []string, usage cli.UsagePrinter, printFn func(string, string)) int {
if len(args) != 0 {
cli.Println("error: wrong number of arguments")
usage()
return 1
}
cfgTypesSl := setCfgTypes.AsSlice()
for _, cfgType := range cfgTypesSl {
if _, ok := dEnv.Config.GetConfig(newCfgElement(cfgType)); !ok {
cli.PrintErrln(color.RedString("Unable to read config."))
return 1
}
}
if setCfgTypes.Size() == 0 {
cfgTypesSl = []string{localParamName, globalParamName}
}
for _, cfgType := range cfgTypesSl {
if cfg, ok := dEnv.Config.GetConfig(newCfgElement(cfgType)); ok {
cfg.Iter(func(name, val string) bool {
printFn(name, val)
return false
})
}
}
return 0
}
func newCfgElement(configFlag string) env.ConfigScope {
switch configFlag {
case localParamName:
return env.LocalConfig
case globalParamName:
return env.GlobalConfig
default:
return env.LocalConfig
}
}