Repository: cli/cli
Pull request #8246 (2023-10-24 16:05:43)

Fix git protocol and refactor Config interface
Fixes https://github.com/cli/cli/issues/8250

This PR refactors the `Config` interface to now include direct accessor functions for the default configuration keys. This allows callers to not have to know about key names and hides that implementation detail from them. Outside of the `config` command and tests there should be no places aware of the configuration keys. As part of this work the `AuthConfig.GitProtocol` function got moved to now live on `Config`. I also added in more constants in the `config` package for the configuration keys so that we can stop writing them out without the compilers help. This PR is not as long as it looks as most of these changes are just updating call points. Lastly, there was a stale prompter mock that is included in the PR but is unrelated to the changes above. 

samcoe: @andyfeller Apologies for not expanding my PR body further. It was the end of my day and was just trying to get this open for review. 

For more context I opened up https://github.com/cli/cli/issues/8250. 



Patch:
diff --git a/go.mod b/go.mod
index 91bfac2ad..e7840350b 100644
--- a/go.mod
+++ b/go.mod
@@ -40,8 +40,8 @@ require (
 	golang.org/x/sync v0.1.0
 	golang.org/x/term v0.13.0
 	golang.org/x/text v0.13.0
-	google.golang.org/grpc v1.53.0
-	google.golang.org/protobuf v1.28.1
+	google.golang.org/grpc v1.56.3
+	google.golang.org/protobuf v1.30.0
 	gopkg.in/h2non/gock.v1 v1.1.2
 	gopkg.in/yaml.v3 v3.0.1
 )
@@ -59,7 +59,7 @@ require (
 	github.com/fatih/color v1.7.0 // indirect
 	github.com/gdamore/encoding v1.0.0 // indirect
 	github.com/godbus/dbus/v5 v5.1.0 // indirect
-	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/gorilla/css v1.0.0 // indirect
 	github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
 	github.com/hashicorp/errwrap v1.0.0 // indirect
@@ -84,6 +84,6 @@ require (
 	github.com/yuin/goldmark-emoji v1.0.1 // indirect
 	golang.org/x/net v0.17.0 // indirect
 	golang.org/x/sys v0.13.0 // indirect
-	google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
+	google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
 	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
 )
diff --git a/go.sum b/go.sum
index d77e0ef20..00aca879a 100644
--- a/go.sum
+++ b/go.sum
@@ -57,8 +57,8 @@ github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIk
 github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
 github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -186,8 +186,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
 golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
-golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
-golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
+golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
+golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -232,14 +232,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
-google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
-google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
-google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
+google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
+google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
-google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/config/config.go b/internal/config/config.go
index 32475a5d8..4e48c8a83 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -10,22 +10,33 @@ import (
 )
 
 const (
-	aliases    = "aliases"
-	hosts      = "hosts"
-	oauthToken = "oauth_token"
+	aliasesKey        = "aliases"
+	browserKey        = "browser"
+	editorKey         = "editor"
+	gitProtocolKey    = "git_protocol"
+	hostsKey          = "hosts"
+	httpUnixSocketKey = "http_unix_socket"
+	oauthTokenKey     = "oauth_token"
+	pagerKey          = "pager"
+	promptKey         = "prompt"
 )
 
 // This interface describes interacting with some persistent configuration for gh.
 //
 //go:generate moq -rm -out config_mock.go . Config
 type Config interface {
-	Get(string, string) (string, error)
 	GetOrDefault(string, string) (string, error)
 	Set(string, string, string)
 	Write() error
 
 	Aliases() *AliasConfig
 	Authentication() *AuthConfig
+	Browser(string) string
+	Editor(string) string
+	GitProtocol(string) string
+	HTTPUnixSocket(string) string
+	Pager(string) string
+	Prompt(string) string
 }
 
 func NewConfig() (Config, error) {
@@ -43,7 +54,7 @@ type cfg struct {
 
 func (c *cfg) Get(hostname, key string) (string, error) {
 	if hostname != "" {
-		val, err := c.cfg.Get([]string{hosts, hostname, key})
+		val, err := c.cfg.Get([]string{hostsKey, hostname, key})
 		if err == nil {
 			return val, err
 		}
@@ -70,7 +81,7 @@ func (c *cfg) Set(hostname, key, value string) {
 		c.cfg.Set([]string{key}, value)
 		return
 	}
-	c.cfg.Set([]string{hosts, hostname, key}, value)
+	c.cfg.Set([]string{hostsKey, hostname, key}, value)
 }
 
 func (c *cfg) Write() error {
@@ -85,6 +96,36 @@ func (c *cfg) Authentication() *AuthConfig {
 	return &AuthConfig{cfg: c.cfg}
 }
 
+func (c *cfg) Browser(hostname string) string {
+	val, _ := c.GetOrDefault(hostname, browserKey)
+	return val
+}
+
+func (c *cfg) Editor(hostname string) string {
+	val, _ := c.GetOrDefault(hostname, editorKey)
+	return val
+}
+
+func (c *cfg) GitProtocol(hostname string) string {
+	val, _ := c.GetOrDefault(hostname, gitProtocolKey)
+	return val
+}
+
+func (c *cfg) HTTPUnixSocket(hostname string) string {
+	val, _ := c.GetOrDefault(hostname, httpUnixSocketKey)
+	return val
+}
+
+func (c *cfg) Pager(hostname string) string {
+	val, _ := c.GetOrDefault(hostname, pagerKey)
+	return val
+}
+
+func (c *cfg) Prompt(hostname string) string {
+	val, _ := c.GetOrDefault(hostname, promptKey)
+	return val
+}
+
 func defaultFor(key string) (string, bool) {
 	for _, co := range ConfigOptions() {
 		if co.Key == key {
@@ -159,25 +200,7 @@ func (c *AuthConfig) TokenFromKeyring(hostname string) (string, error) {
 
 // User will retrieve the username for the logged in user at the given hostname.
 func (c *AuthConfig) User(hostname string) (string, error) {
-	return c.cfg.Get([]string{hosts, hostname, "user"})
-}
-
-// GitProtocol will retrieve the git protocol for the logged in user at the given hostname.
-// If none is set it will return the default value.
-func (c *AuthConfig) GitProtocol(hostname string) string {
-	key := "git_protocol"
-	if val, err := c.cfg.Get([]string{hosts, hostname, key}); err == nil {
-		return val
-	}
-
-	if val, ok := defaultFor(key); ok {
-		return val
-	}
-
-	// This should not happen, as we know there is a default value for this key.
-	// Perhaps it says something about our current default abstraction being not quite right?
-	// The defaults are also currently used to provide information about the config options.
-	return ""
+	return c.cfg.Get([]string{hostsKey, hostname, "user"})
 }
 
 func (c *AuthConfig) Hosts() []string {
@@ -218,19 +241,19 @@ func (c *AuthConfig) Login(hostname, username, token, gitProtocol string, secure
 	if secureStorage {
 		if setErr = keyring.Set(keyringServiceName(hostname), "", token); setErr == nil {
 			// Clean up the previous oauth_token from the config file.
-			_ = c.cfg.Remove([]string{hosts, hostname, oauthToken})
+			_ = c.cfg.Remove([]string{hostsKey, hostname, oauthTokenKey})
 		}
 	}
 	insecureStorageUsed := false
 	if !secureStorage || setErr != nil {
-		c.cfg.Set([]string{hosts, hostname, oauthToken}, token)
+		c.cfg.Set([]string{hostsKey, hostname, oauthTokenKey}, token)
 		insecureStorageUsed = true
 	}
 
-	c.cfg.Set([]string{hosts, hostname, "user"}, username)
+	c.cfg.Set([]string{hostsKey, hostname, "user"}, username)
 
 	if gitProtocol != "" {
-		c.cfg.Set([]string{hosts, hostname, "git_protocol"}, gitProtocol)
+		c.cfg.Set([]string{hostsKey, hostname, gitProtocolKey}, gitProtocol)
 	}
 	return insecureStorageUsed, ghConfig.Write(c.cfg)
 }
@@ -238,7 +261,7 @@ func (c *AuthConfig) Login(hostname, username, token, gitProtocol string, secure
 // Logout will remove user, git protocol, and auth token for the given hostname.
 // It will remove the auth token from the encrypted storage if it exists there.
 func (c *AuthConfig) Logout(hostname string) error {
-	_ = c.cfg.Remove([]string{hosts, hostname})
+	_ = c.cfg.Remove([]string{hostsKey, hostname})
 	_ = keyring.Delete(keyringServiceName(hostname), "")
 	return ghConfig.Write(c.cfg)
 }
@@ -252,25 +275,25 @@ type AliasConfig struct {
 }
 
 func (a *AliasConfig) Get(alias string) (string, error) {
-	return a.cfg.Get([]string{aliases, alias})
+	return a.cfg.Get([]string{aliasesKey, alias})
 }
 
 func (a *AliasConfig) Add(alias, expansion string) {
-	a.cfg.Set([]string{aliases, alias}, expansion)
+	a.cfg.Set([]string{aliasesKey, alias}, expansion)
 }
 
 func (a *AliasConfig) Delete(alias string) error {
-	return a.cfg.Remove([]string{aliases, alias})
+	return a.cfg.Remove([]string{aliasesKey, alias})
 }
 
 func (a *AliasConfig) All() map[string]string {
 	out := map[string]string{}
-	keys, err := a.cfg.Keys([]string{aliases})
+	keys, err := a.cfg.Keys([]string{aliasesKey})
 	if err != nil {
 		return out
 	}
 	for _, key := range keys {
-		val, _ := a.cfg.Get([]string{aliases, key})
+		val, _ := a.cfg.Get([]string{aliasesKey, key})
 		out[key] = val
 	}
 	return out
@@ -308,34 +331,34 @@ type ConfigOption struct {
 func ConfigOptions() []ConfigOption {
 	return []ConfigOption{
 		{
-			Key:           "git_protocol",
+			Key:           gitProtocolKey,
 			Description:   "the protocol to use for git clone and push operations",
 			DefaultValue:  "https",
 			AllowedValues: []string{"https", "ssh"},
 		},
 		{
-			Key:          "editor",
+			Key:          editorKey,
 			Description:  "the text editor program to use for authoring text",
 			DefaultValue: "",
 		},
 		{
-			Key:           "prompt",
+			Key:           promptKey,
 			Description:   "toggle interactive prompting in the terminal",
 			DefaultValue:  "enabled",
 			AllowedValues: []string{"enabled", "disabled"},
 		},
 		{
-			Key:          "pager",
+			Key:          pagerKey,
 			Description:  "the terminal pager program to send standard output to",
 			DefaultValue: "",
 		},
 		{
-			Key:          "http_unix_socket",
+			Key:          httpUnixSocketKey,
 			Description:  "the path to a Unix socket through which to make an HTTP connection",
 			DefaultValue: "",
 		},
 		{
-			Key:          "browser",
+			Key:          browserKey,
 			Description:  "the web browser to use for opening URLs",
 			DefaultValue: "",
 		},
diff --git a/internal/config/config_mock.go b/internal/config/config_mock.go
index 6fd76dc55..aff4cb9d4 100644
--- a/internal/config/config_mock.go
+++ b/internal/config/config_mock.go
@@ -23,12 +23,27 @@ var _ Config = &ConfigMock{}
 //			AuthenticationFunc: func() *AuthConfig {
 //				panic("mock out the Authentication method")
 //			},
-//			GetFunc: func(s1 string, s2 string) (string, error) {
-//				panic("mock out the Get method")
+//			BrowserFunc: func(s string) string {
+//				panic("mock out the Browser method")
+//			},
+//			EditorFunc: func(s string) string {
+//				panic("mock out the Editor method")
 //			},
 //			GetOrDefaultFunc: func(s1 string, s2 string) (string, error) {
 //				panic("mock out the GetOrDefault method")
 //			},
+//			GitProtocolFunc: func(s string) string {
+//				panic("mock out the GitProtocol method")
+//			},
+//			HTTPUnixSocketFunc: func(s string) string {
+//				panic("mock out the HTTPUnixSocket method")
+//			},
+//			PagerFunc: func(s string) string {
+//				panic("mock out the Pager method")
+//			},
+//			PromptFunc: func(s string) string {
+//				panic("mock out the Prompt method")
+//			},
 //			SetFunc: func(s1 string, s2 string, s3 string)  {
 //				panic("mock out the Set method")
 //			},
@@ -48,12 +63,27 @@ type ConfigMock struct {
 	// AuthenticationFunc mocks the Authentication method.
 	AuthenticationFunc func() *AuthConfig
 
-	// GetFunc mocks the Get method.
-	GetFunc func(s1 string, s2 string) (string, error)
+	// BrowserFunc mocks the Browser method.
+	BrowserFunc func(s string) string
+
+	// EditorFunc mocks the Editor method.
+	EditorFunc func(s string) string
 
 	// GetOrDefaultFunc mocks the GetOrDefault method.
 	GetOrDefaultFunc func(s1 string, s2 string) (string, error)
 
+	// GitProtocolFunc mocks the GitProtocol method.
+	GitProtocolFunc func(s string) string
+
+	// HTTPUnixSocketFunc mocks the HTTPUnixSocket method.
+	HTTPUnixSocketFunc func(s string) string
+
+	// PagerFunc mocks the Pager method.
+	PagerFunc func(s string) string
+
+	// PromptFunc mocks the Prompt method.
+	PromptFunc func(s string) string
+
 	// SetFunc mocks the Set method.
 	SetFunc func(s1 string, s2 string, s3 string)
 
@@ -68,12 +98,15 @@ type ConfigMock struct {
 		// Authentication holds details about calls to the Authentication method.
 		Authentication []struct {
 		}
-		// Get holds details about calls to the Get method.
-		Get []struct {
-			// S1 is the s1 argument value.
-			S1 string
-			// S2 is the s2 argument value.
-			S2 string
+		// Browser holds details about calls to the Browser method.
+		Browser []struct {
+			// S is the s argument value.
+			S string
+		}
+		// Editor holds details about calls to the Editor method.
+		Editor []struct {
+			// S is the s argument value.
+			S string
 		}
 		// GetOrDefault holds details about calls to the GetOrDefault method.
 		GetOrDefault []struct {
@@ -82,6 +115,26 @@ type ConfigMock struct {
 			// S2 is the s2 argument value.
 			S2 string
 		}
+		// GitProtocol holds details about calls to the GitProtocol method.
+		GitProtocol []struct {
+			// S is the s argument value.
+			S string
+		}
+		// HTTPUnixSocket holds details about calls to the HTTPUnixSocket method.
+		HTTPUnixSocket []struct {
+			// S is the s argument value.
+			S string
+		}
+		// Pager holds details about calls to the Pager method.
+		Pager []struct {
+			// S is the s argument value.
+			S string
+		}
+		// Prompt holds details about calls to the Prompt method.
+		Prompt []struct {
+			// S is the s argument value.
+			S string
+		}
 		// Set holds details about calls to the Set method.
 		Set []struct {
 			// S1 is the s1 argument value.
@@ -97,8 +150,13 @@ type ConfigMock struct {
 	}
 	lockAliases        sync.RWMutex
 	lockAuthentication sync.RWMutex
-	lockGet            sync.RWMutex
+	lockBrowser        sync.RWMutex
+	lockEditor         sync.RWMutex
 	lockGetOrDefault   sync.RWMutex
+	lockGitProtocol    sync.RWMutex
+	lockHTTPUnixSocket sync.RWMutex
+	lockPager          sync.RWMutex
+	lockPrompt         sync.RWMutex
 	lockSet            sync.RWMutex
 	lockWrite          sync.RWMutex
 }
@@ -157,39 +215,67 @@ func (mock *ConfigMock) AuthenticationCalls() []struct {
 	return calls
 }
 
-// Get calls GetFunc.
-func (mock *ConfigMock) Get(s1 string, s2 string) (string, error) {
-	if mock.GetFunc == nil {
-		panic("ConfigMock.GetFunc: method is nil but Config.Get was just called")
+// Browser calls BrowserFunc.
+func (mock *ConfigMock) Browser(s string) string {
+	if mock.BrowserFunc == nil {
+		panic("ConfigMock.BrowserFunc: method is nil but Config.Browser was just called")
 	}
 	callInfo := struct {
-		S1 string
-		S2 string
+		S string
 	}{
-		S1: s1,
-		S2: s2,
+		S: s,
 	}
-	mock.lockGet.Lock()
-	mock.calls.Get = append(mock.calls.Get, callInfo)
-	mock.lockGet.Unlock()
-	return mock.GetFunc(s1, s2)
+	mock.lockBrowser.Lock()
+	mock.calls.Browser = append(mock.calls.Browser, callInfo)
+	mock.lockBrowser.Unlock()
+	return mock.BrowserFunc(s)
 }
 
-// GetCalls gets all the calls that were made to Get.
+// BrowserCalls gets all the calls that were made to Browser.
 // Check the length with:
 //
-//	len(mockedConfig.GetCalls())
-func (mock *ConfigMock) GetCalls() []struct {
-	S1 string
-	S2 string
+//	len(mockedConfig.BrowserCalls())
+func (mock *ConfigMock) BrowserCalls() []struct {
+	S string
 } {
 	var calls []struct {
-		S1 string
-		S2 string
+		S string
+	}
+	mock.lockBrowser.RLock()
+	calls = mock.calls.Browser
+	mock.lockBrowser.RUnlock()
+	return calls
+}
+
+// Editor calls EditorFunc.
+func (mock *ConfigMock) Editor(s string) string {
+	if mock.EditorFunc == nil {
+		panic("ConfigMock.EditorFunc: method is nil but Config.Editor was just called")
+	}
+	callInfo := struct {
+		S string
+	}{
+		S: s,
+	}
+	mock.lockEditor.Lock()
+	mock.calls.Editor = append(mock.calls.Editor, callInfo)
+	mock.lockEditor.Unlock()
+	return mock.EditorFunc(s)
+}
+
+// EditorCalls gets all the calls that were made to Editor.
+// Check the length with:
+//
+//	len(mockedConfig.EditorCalls())
+func (mock *ConfigMock) EditorCalls() []struct {
+	S string
+} {
+	var calls []struct {
+		S string
 	}
-	mock.lockGet.RLock()
-	calls = mock.calls.Get
-	mock.lockGet.RUnlock()
+	mock.lockEditor.RLock()
+	calls = mock.calls.Editor
+	mock.lockEditor.RUnlock()
 	return calls
 }
 
@@ -229,6 +315,134 @@ func (mock *ConfigMock) GetOrDefaultCalls() []struct {
 	return calls
 }
 
+// GitProtocol calls GitProtocolFunc.
+func (mock *ConfigMock) GitProtocol(s string) string {
+	if mock.GitProtocolFunc == nil {
+		panic("ConfigMock.GitProtocolFunc: method is nil but Config.GitProtocol was just called")
+	}
+	callInfo := struct {
+		S string
+	}{
+		S: s,
+	}
+	mock.lockGitProtocol.Lock()
+	mock.calls.GitProtocol = append(mock.calls.GitProtocol, callInfo)
+	mock.lockGitProtocol.Unlock()
+	return mock.GitProtocolFunc(s)
+}
+
+// GitProtocolCalls gets all the calls that were made to GitProtocol.
+// Check the length with:
+//
+//	len(mockedConfig.GitProtocolCalls())
+func (mock *ConfigMock) GitProtocolCalls() []struct {
+	S string
+} {
+	var calls []struct {
+		S string
+	}
+	mock.lockGitProtocol.RLock()
+	calls = mock.calls.GitProtocol
+	mock.lockGitProtocol.RUnlock()
+	return calls
+}
+
+// HTTPUnixSocket calls HTTPUnixSocketFunc.
+func (mock *ConfigMock) HTTPUnixSocket(s string) string {
+	if mock.HTTPUnixSocketFunc == nil {
+		panic("ConfigMock.HTTPUnixSocketFunc: method is nil but Config.HTTPUnixSocket was just called")
+	}
+	callInfo := struct {
+		S string
+	}{
+		S: s,
+	}
+	mock.lockHTTPUnixSocket.Lock()
+	mock.calls.HTTPUnixSocket = append(mock.calls.HTTPUnixSocket, callInfo)
+	mock.lockHTTPUnixSocket.Unlock()
+	return mock.HTTPUnixSocketFunc(s)
+}
+
+// HTTPUnixSocketCalls gets all the calls that were made to HTTPUnixSocket.
+// Check the length with:
+//
+//	len(mockedConfig.HTTPUnixSocketCalls())
+func (mock *ConfigMock) HTTPUnixSocketCalls() []struct {
+	S string
+} {
+	var calls []struct {
+		S string
+	}
+	mock.lockHTTPUnixSocket.RLock()
+	calls = mock.calls.HTTPUnixSocket
+	mock.lockHTTPUnixSocket.RUnlock()
+	return calls
+}
+
+// Pager calls PagerFunc.
+func (mock *ConfigMock) Pager(s string) string {
+	if mock.PagerFunc == nil {
+		panic("ConfigMock.PagerFunc: method is nil but Config.Pager was just called")
+	}
+	callInfo := struct {
+		S string
+	}{
+		S: s,
+	}
+	mock.lockPager.Lock()
+	mock.calls.Pager = append(mock.calls.Pager, callInfo)
+	mock.lockPager.Unlock()
+	return mock.PagerFunc(s)
+}
+
+// PagerCalls gets all the calls that were made to Pager.
+// Check the length with:
+//
+//	len(mockedConfig.PagerCalls())
+func (mock *ConfigMock) PagerCalls() []struct {
+	S string
+} {
+	var calls []struct {
+		S string
+	}
+	mock.lockPager.RLock()
+	calls = mock.calls.Pager
+	mock.lockPager.RUnlock()
+	return calls
+}
+
+// Prompt calls PromptFunc.
+func (mock *ConfigMock) Prompt(s string) string {
+	if mock.PromptFunc == nil {
+		panic("ConfigMock.PromptFunc: method is nil but Config.Prompt was just called")
+	}
+	callInfo := struct {
+		S string
+	}{
+		S: s,
+	}
+	mock.lockPrompt.Lock()
+	mock.calls.Prompt = append(mock.calls.Prompt, callInfo)
+	mock.lockPrompt.Unlock()
+	return mock.PromptFunc(s)
+}
+
+// PromptCalls gets all the calls that were made to Prompt.
+// Check the length with:
+//
+//	len(mockedConfig.PromptCalls())
+func (mock *ConfigMock) PromptCalls() []struct {
+	S string
+} {
+	var calls []struct {
+		S string
+	}
+	mock.lockPrompt.RLock()
+	calls = mock.calls.Prompt
+	mock.lockPrompt.RUnlock()
+	return calls
+}
+
 // Set calls SetFunc.
 func (mock *ConfigMock) Set(s1 string, s2 string, s3 string) {
 	if mock.SetFunc == nil {
diff --git a/internal/config/stub.go b/internal/config/stub.go
index 871cf1a16..143c9b870 100644
--- a/internal/config/stub.go
+++ b/internal/config/stub.go
@@ -17,9 +17,6 @@ func NewFromString(cfgStr string) *ConfigMock {
 	c := ghConfig.ReadFromString(cfgStr)
 	cfg := cfg{c}
 	mock := &ConfigMock{}
-	mock.GetFunc = func(host, key string) (string, error) {
-		return cfg.Get(host, key)
-	}
 	mock.GetOrDefaultFunc = func(host, key string) (string, error) {
 		return cfg.GetOrDefault(host, key)
 	}
@@ -39,15 +36,39 @@ func NewFromString(cfgStr string) *ConfigMock {
 				return "github.com", "default"
 			},
 			hostsOverride: func() []string {
-				keys, _ := c.Keys([]string{"hosts"})
+				keys, _ := c.Keys([]string{hostsKey})
 				return keys
 			},
 			tokenOverride: func(hostname string) (string, string) {
-				token, _ := c.Get([]string{hosts, hostname, oauthToken})
-				return token, "oauth_token"
+				token, _ := c.Get([]string{hostsKey, hostname, oauthTokenKey})
+				return token, oauthTokenKey
 			},
 		}
 	}
+	mock.BrowserFunc = func(hostname string) string {
+		val, _ := cfg.GetOrDefault(hostname, browserKey)
+		return val
+	}
+	mock.EditorFunc = func(hostname string) string {
+		val, _ := cfg.GetOrDefault(hostname, editorKey)
+		return val
+	}
+	mock.GitProtocolFunc = func(hostname string) string {
+		val, _ := cfg.GetOrDefault(hostname, gitProtocolKey)
+		return val
+	}
+	mock.HTTPUnixSocketFunc = func(hostname string) string {
+		val, _ := cfg.GetOrDefault(hostname, httpUnixSocketKey)
+		return val
+	}
+	mock.PagerFunc = func(hostname string) string {
+		val, _ := cfg.GetOrDefault(hostname, pagerKey)
+		return val
+	}
+	mock.PromptFunc = func(hostname string) string {
+		val, _ := cfg.GetOrDefault(hostname, promptKey)
+		return val
+	}
 	return mock
 }
 
diff --git a/internal/prompter/prompter_mock.go b/internal/prompter/prompter_mock.go
index 9a59a7bd1..b817a491f 100644
--- a/internal/prompter/prompter_mock.go
+++ b/internal/prompter/prompter_mock.go
@@ -35,7 +35,7 @@ var _ Prompter = &PrompterMock{}
 //			MarkdownEditorFunc: func(s1 string, s2 string, b bool) (string, error) {
 //				panic("mock out the MarkdownEditor method")
 //			},
-//			MultiSelectFunc: func(s string, strings1 []string, strings2 []string) ([]int, error) {
+//			MultiSelectFunc: func(prompt string, defaults []string, options []string) ([]int, error) {
 //				panic("mock out the MultiSelect method")
 //			},
 //			PasswordFunc: func(s string) (string, error) {
@@ -70,7 +70,7 @@ type PrompterMock struct {
 	MarkdownEditorFunc func(s1 string, s2 string, b bool) (string, error)
 
 	// MultiSelectFunc mocks the MultiSelect method.
-	MultiSelectFunc func(s string, strings1 []string, strings2 []string) ([]int, error)
+	MultiSelectFunc func(prompt string, defaults []string, options []string) ([]int, error)
 
 	// PasswordFunc mocks the Password method.
 	PasswordFunc func(s string) (string, error)
@@ -116,12 +116,12 @@ type PrompterMock struct {
 		}
 		// MultiSelect holds details about calls to the MultiSelect method.
 		MultiSelect []struct {
-			// S is the s argument value.
-			S string
-			// Strings1 is the strings1 argument value.
-			Strings1 []string
-			// Strings2 is the strings2 argument value.
-			Strings2 []string
+			// Prompt is the prompt argument value.
+			Prompt string
+			// Defaults is the defaults argument value.
+			Defaults []string
+			// Options is the options argument value.
+			Options []string
 		}
 		// Password holds details about calls to the Password method.
 		Password []struct {
@@ -348,23 +348,23 @@ func (mock *PrompterMock) MarkdownEditorCalls() []struct {
 }
 
 // MultiSelect calls MultiSelectFunc.
-func (mock *PrompterMock) MultiSelect(s string, strings1 []string, strings2 []string) ([]int, error) {
+func (mock *PrompterMock) MultiSelect(prompt string, defaults []string, options []string) ([]int, error) {
 	if mock.MultiSelectFunc == nil {
 		panic("PrompterMock.MultiSelectFunc: method is nil but Prompter.MultiSelect was just called")
 	}
 	callInfo := struct {
-		S        string
-		Strings1 []string
-		Strings2 []string
+		Prompt   string
+		Defaults []string
+		Options  []string
 	}{
-		S:        s,
-		Strings1: strings1,
-		Strings2: strings2,
+		Prompt:   prompt,
+		Defaults: defaults,
+		Options:  options,
 	}
 	mock.lockMultiSelect.Lock()
 	mock.calls.MultiSelect = append(mock.calls.MultiSelect, callInfo)
 	mock.lockMultiSelect.Unlock()
-	return mock.MultiSelectFunc(s, strings1, strings2)
+	return mock.MultiSelectFunc(prompt, defaults, options)
 }
 
 // MultiSelectCalls gets all the calls that were made to MultiSelect.
@@ -372,14 +372,14 @@ func (mock *PrompterMock) MultiSelect(s string, strings1 []string, strings2 []st
 //
 //	len(mockedPrompter.MultiSelectCalls())
 func (mock *PrompterMock) MultiSelectCalls() []struct {
-	S        string
-	Strings1 []string
-	Strings2 []string
+	Prompt   string
+	Defaults []string
+	Options  []string
 } {
 	var calls []struct {
-		S        string
-		Strings1 []string
-		Strings2 []string
+		Prompt   string
+		Defaults []string
+		Options  []string
 	}
 	mock.lockMultiSelect.RLock()
 	calls = mock.calls.MultiSelect
diff --git a/pkg/cmd/auth/refresh/refresh.go b/pkg/cmd/auth/refresh/refresh.go
index 30b5a3ff0..870871284 100644
--- a/pkg/cmd/auth/refresh/refresh.go
+++ b/pkg/cmd/auth/refresh/refresh.go
@@ -178,7 +178,7 @@ func refreshRun(opts *RefreshOptions) error {
 		Prompter:   opts.Prompter,
 		GitClient:  opts.GitClient,
 	}
-	gitProtocol := authCfg.GitProtocol(hostname)
+	gitProtocol := cfg.GitProtocol(hostname)
 	if opts.Interactive && gitProtocol == "https" {
 		if err := credentialFlow.Prompt(hostname); err != nil {
 			return err
diff --git a/pkg/cmd/auth/status/status.go b/pkg/cmd/auth/status/status.go
index bde74306a..a064aabc2 100644
--- a/pkg/cmd/auth/status/status.go
+++ b/pkg/cmd/auth/status/status.go
@@ -139,7 +139,7 @@ func statusRun(opts *StatusOptions) error {
 			}
 
 			addMsg("%s Logged in to %s as %s (%s)", cs.SuccessIcon(), hostname, cs.Bold(username), tokenSource)
-			proto := authCfg.GitProtocol(hostname)
+			proto := cfg.GitProtocol(hostname)
 			if proto != "" {
 				addMsg("%s Git operations for %s configured to use %s protocol.",
 					cs.SuccessIcon(), hostname, cs.Bold(proto))
diff --git a/pkg/cmd/extension/manager.go b/pkg/cmd/extension/manager.go
index b19175880..a8d0db844 100644
--- a/pkg/cmd/extension/manager.go
+++ b/pkg/cmd/extension/manager.go
@@ -346,7 +346,7 @@ func writeManifest(dir, name string, data []byte) (writeErr error) {
 }
 
 func (m *Manager) installGit(repo ghrepo.Interface, target string) error {
-	protocol, _ := m.config.GetOrDefault(repo.RepoHost(), "git_protocol")
+	protocol := m.config.GitProtocol(repo.RepoHost())
 	cloneURL := ghrepo.FormatRemoteURL(repo, protocol)
 
 	var commitSHA string
diff --git a/pkg/cmd/factory/default.go b/pkg/cmd/factory/default.go
index a24dc20fb..dc5b37138 100644
--- a/pkg/cmd/factory/default.go
+++ b/pkg/cmd/factory/default.go
@@ -184,7 +184,7 @@ func ioStreams(f *cmdutil.Factory) *iostreams.IOStreams {
 
 	if _, ghPromptDisabled := os.LookupEnv("GH_PROMPT_DISABLED"); ghPromptDisabled {
 		io.SetNeverPrompt(true)
-	} else if prompt, _ := cfg.GetOrDefault("", "prompt"); prompt == "disabled" {
+	} else if prompt := cfg.Prompt(""); prompt == "disabled" {
 		io.SetNeverPrompt(true)
 	}
 
@@ -194,7 +194,7 @@ func ioStreams(f *cmdutil.Factory) *iostreams.IOStreams {
 	// 3. PAGER
 	if ghPager, ghPagerExists := os.LookupEnv("GH_PAGER"); ghPagerExists {
 		io.SetPager(ghPager)
-	} else if pager, _ := cfg.Get("", "pager"); pager != "" {
+	} else if pager := cfg.Pager(""); pager != "" {
 		io.SetPager(pager)
 	}
 
diff --git a/pkg/cmd/gist/clone/clone.go b/pkg/cmd/gist/clone/clone.go
index cd477a745..d83560f40 100644
--- a/pkg/cmd/gist/clone/clone.go
+++ b/pkg/cmd/gist/clone/clone.go
@@ -80,7 +80,7 @@ func cloneRun(opts *CloneOptions) error {
 			return err
 		}
 		hostname, _ := cfg.Authentication().DefaultHost()
-		protocol := cfg.Authentication().GitProtocol(hostname)
+		protocol := cfg.GitProtocol(hostname)
 		gistURL = formatRemoteURL(hostname, gistURL, protocol)
 	}
 
diff --git a/pkg/cmd/pr/checkout/checkout.go b/pkg/cmd/pr/checkout/checkout.go
index b91fb1765..389d554be 100644
--- a/pkg/cmd/pr/checkout/checkout.go
+++ b/pkg/cmd/pr/checkout/checkout.go
@@ -84,7 +84,7 @@ func checkoutRun(opts *CheckoutOptions) error {
 	if err != nil {
 		return err
 	}
-	protocol := cfg.Authentication().GitProtocol(baseRepo.RepoHost())
+	protocol := cfg.GitProtocol(baseRepo.RepoHost())
 
 	remotes, err := opts.Remotes()
 	if err != nil {
diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go
index 81b2c05f3..890171860 100644
--- a/pkg/cmd/pr/create/create.go
+++ b/pkg/cmd/pr/create/create.go
@@ -761,7 +761,7 @@ func handlePush(opts CreateOptions, ctx CreateContext) error {
 			return err
 		}
 
-		cloneProtocol := cfg.Authentication().GitProtocol(headRepo.RepoHost())
+		cloneProtocol := cfg.GitProtocol(headRepo.RepoHost())
 		headRepoURL := ghrepo.FormatRemoteURL(headRepo, cloneProtocol)
 		gitClient := ctx.GitClient
 		origin, _ := remotes.FindByName("origin")
diff --git a/pkg/cmd/repo/clone/clone.go b/pkg/cmd/repo/clone/clone.go
index 298ca30de..b6def2edd 100644
--- a/pkg/cmd/repo/clone/clone.go
+++ b/pkg/cmd/repo/clone/clone.go
@@ -129,7 +129,7 @@ func cloneRun(opts *CloneOptions) error {
 			return err
 		}
 
-		protocol = cfg.Authentication().GitProtocol(repo.RepoHost())
+		protocol = cfg.GitProtocol(repo.RepoHost())
 	}
 
 	wantsWiki := strings.HasSuffix(repo.RepoName(), ".wiki")
@@ -163,7 +163,7 @@ func cloneRun(opts *CloneOptions) error {
 
 	// If the repo is a fork, add the parent as an upstream remote and set the parent as the default repo.
 	if canonicalRepo.Parent != nil {
-		protocol := cfg.Authentication().GitProtocol(canonicalRepo.Parent.RepoHost())
+		protocol := cfg.GitProtocol(canonicalRepo.Parent.RepoHost())
 		upstreamURL := ghrepo.FormatRemoteURL(canonicalRepo.Parent, protocol)
 
 		upstreamName := opts.UpstreamName
diff --git a/pkg/cmd/repo/create/create.go b/pkg/cmd/repo/create/create.go
index c96aeb3f9..a2e7b82c8 100644
--- a/pkg/cmd/repo/create/create.go
+++ b/pkg/cmd/repo/create/create.go
@@ -395,7 +395,7 @@ func createFromScratch(opts *CreateOptions) error {
 	}
 
 	if opts.Clone {
-		protocol := cfg.Authentication().GitProtocol(repo.RepoHost())
+		protocol := cfg.GitProtocol(repo.RepoHost())
 		remoteURL := ghrepo.FormatRemoteURL(repo, protocol)
 
 		if !opts.AddReadme && opts.LicenseTemplate == "" && opts.GitIgnoreTemplate == "" && opts.Template == "" {
@@ -492,7 +492,7 @@ func createFromTemplate(opts *CreateOptions) error {
 	}
 
 	if opts.Clone {
-		protocol := cfg.Authentication().GitProtocol(repo.RepoHost())
+		protocol := cfg.GitProtocol(repo.RepoHost())
 		remoteURL := ghrepo.FormatRemoteURL(repo, protocol)
 
 		if err := cloneWithRetry(opts, remoteURL, templateRepoMainBranch); err != nil {
@@ -614,7 +614,7 @@ func createFromLocal(opts *CreateOptions) error {
 		fmt.Fprintln(stdout, repo.URL)
 	}
 
-	protocol := cfg.Authentication().GitProtocol(repo.RepoHost())
+	protocol := cfg.GitProtocol(repo.RepoHost())
 	remoteURL := ghrepo.FormatRemoteURL(repo, protocol)
 
 	if opts.Interactive {
diff --git a/pkg/cmd/repo/fork/fork.go b/pkg/cmd/repo/fork/fork.go
index e31a6a3c9..73fa906a6 100644
--- a/pkg/cmd/repo/fork/fork.go
+++ b/pkg/cmd/repo/fork/fork.go
@@ -243,7 +243,7 @@ func forkRun(opts *ForkOptions) error {
 	if err != nil {
 		return err
 	}
-	protocol, _ := cfg.Get(repoToFork.RepoHost(), "git_protocol")
+	protocol := cfg.GitProtocol(repoToFork.RepoHost())
 
 	gitClient := opts.GitClient
 	ctx := context.Background()
diff --git a/pkg/cmd/repo/rename/rename.go b/pkg/cmd/repo/rename/rename.go
index 8dbc1326d..d15f2efdf 100644
--- a/pkg/cmd/repo/rename/rename.go
+++ b/pkg/cmd/repo/rename/rename.go
@@ -149,7 +149,7 @@ func updateRemote(repo ghrepo.Interface, renamed ghrepo.Interface, opts *RenameO
 		return nil, err
 	}
 
-	protocol := cfg.Authentication().GitProtocol(repo.RepoHost())
+	protocol := cfg.GitProtocol(repo.RepoHost())
 
 	remotes, err := opts.Remotes()
 	if err != nil {
diff --git a/pkg/cmdutil/legacy.go b/pkg/cmdutil/legacy.go
index 19400f1cc..1f247d4e6 100644
--- a/pkg/cmdutil/legacy.go
+++ b/pkg/cmdutil/legacy.go
@@ -16,7 +16,7 @@ func DetermineEditor(cf func() (config.Config, error)) (string, error) {
 		if err != nil {
 			return "", fmt.Errorf("could not read config: %w", err)
 		}
-		editorCommand, _ = cfg.Get("", "editor")
+		editorCommand = cfg.Editor("")
 	}
 
 	return editorCommand, nil


Test patch:
diff --git a/internal/config/auth_config_test.go b/internal/config/auth_config_test.go
index 6d7e73c5c..afa47fbfd 100644
--- a/internal/config/auth_config_test.go
+++ b/internal/config/auth_config_test.go
@@ -158,17 +158,6 @@ func TestUserNotLoggedIn(t *testing.T) {
 	require.ErrorAs(t, err, &keyNotFoundError)
 }
 
-func TestGitProtocolNotLoggedInDefaults(t *testing.T) {
-	// Given we have not logged in
-	authCfg := newTestAuthConfig(t)
-
-	// When we get the git protocol
-	gitProtocol := authCfg.GitProtocol("github.com")
-
-	// Then it returns the default
-	require.Equal(t, "https", gitProtocol)
-}
-
 func TestHostsIncludesEnvVar(t *testing.T) {
 	// Given the GH_HOST env var is set
 	authCfg := newTestAuthConfig(t)
@@ -217,7 +206,7 @@ func TestDefaultHostLoggedInToOnlyOneHost(t *testing.T) {
 
 	// Then the returned host is that logged in host and the source is the hosts config
 	require.Equal(t, "ghe.io", defaultHost)
-	require.Equal(t, "hosts", source)
+	require.Equal(t, hostsKey, source)
 }
 
 func TestLoginSecureStorageUsesKeyring(t *testing.T) {
@@ -241,14 +230,14 @@ func TestLoginSecureStorageRemovesOldInsecureConfigToken(t *testing.T) {
 	// Given a usable keyring and an oauth token in the config
 	keyring.MockInit()
 	authCfg := newTestAuthConfig(t)
-	authCfg.cfg.Set([]string{hosts, "github.com", oauthToken}, "old-token")
+	authCfg.cfg.Set([]string{hostsKey, "github.com", oauthTokenKey}, "old-token")
 
 	// When we login with secure storage
 	_, err := authCfg.Login("github.com", "test-user", "test-token", "", true)
 
 	// Then it returns success, having also removed the old token from the config
 	require.NoError(t, err)
-	requireNoKey(t, authCfg.cfg, []string{hosts, "github.com", oauthToken})
+	requireNoKey(t, authCfg.cfg, []string{hostsKey, "github.com", oauthTokenKey})
 }
 
 func TestLoginSecureStorageWithErrorFallsbackAndReports(t *testing.T) {
@@ -263,7 +252,7 @@ func TestLoginSecureStorageWithErrorFallsbackAndReports(t *testing.T) {
 	require.NoError(t, err)
 
 	require.True(t, insecureStorageUsed, "expected to use insecure storage")
-	requireKeyWithValue(t, authCfg.cfg, []string{hosts, "github.com", oauthToken}, "test-token")
+	requireKeyWithValue(t, authCfg.cfg, []string{hostsKey, "github.com", oauthTokenKey}, "test-token")
 }
 
 func TestLoginInsecureStorage(t *testing.T) {
@@ -277,7 +266,7 @@ func TestLoginInsecureStorage(t *testing.T) {
 	require.NoError(t, err)
 
 	require.True(t, insecureStorageUsed, "expected to use insecure storage")
-	requireKeyWithValue(t, authCfg.cfg, []string{hosts, "github.com", oauthToken}, "test-token")
+	requireKeyWithValue(t, authCfg.cfg, []string{hostsKey, "github.com", oauthTokenKey}, "test-token")
 }
 
 func TestLoginSetsUserForProvidedHost(t *testing.T) {
@@ -302,10 +291,11 @@ func TestLoginSetsGitProtocolForProvidedHost(t *testing.T) {
 	require.NoError(t, err)
 
 	// When we get the git protocol
-	gitProtocol := authCfg.GitProtocol("github.com")
+	protocol, err := authCfg.cfg.Get([]string{hostsKey, "github.com", gitProtocolKey})
+	require.NoError(t, err)
 
 	// Then it returns the git protocol we provided on login
-	require.Equal(t, "ssh", gitProtocol)
+	require.Equal(t, "ssh", protocol)
 }
 
 func TestLoginAddsHostIfNotAlreadyAdded(t *testing.T) {
@@ -334,7 +324,7 @@ func TestLogoutRemovesHostAndKeyringToken(t *testing.T) {
 	// Then we return success, and the host and token are removed from the config and keyring
 	require.NoError(t, err)
 
-	requireNoKey(t, authCfg.cfg, []string{hosts, "github.com"})
+	requireNoKey(t, authCfg.cfg, []string{hostsKey, "github.com"})
 	_, err = keyring.Get(keyringServiceName("github.com"), "")
 	require.ErrorIs(t, err, keyring.ErrNotFound)
 }
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 349a0db0b..6acc32245 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -22,13 +22,13 @@ func TestNewConfigProvidesFallback(t *testing.T) {
 	}
 	_, err := NewConfig()
 	require.NoError(t, err)
-	requireKeyWithValue(t, spiedCfg, []string{"git_protocol"}, "https")
-	requireKeyWithValue(t, spiedCfg, []string{"editor"}, "")
-	requireKeyWithValue(t, spiedCfg, []string{"prompt"}, "enabled")
-	requireKeyWithValue(t, spiedCfg, []string{"pager"}, "")
-	requireKeyWithValue(t, spiedCfg, []string{"aliases", "co"}, "pr checkout")
-	requireKeyWithValue(t, spiedCfg, []string{"http_unix_socket"}, "")
-	requireKeyWithValue(t, spiedCfg, []string{"browser"}, "")
+	requireKeyWithValue(t, spiedCfg, []string{gitProtocolKey}, "https")
+	requireKeyWithValue(t, spiedCfg, []string{editorKey}, "")
+	requireKeyWithValue(t, spiedCfg, []string{promptKey}, "enabled")
+	requireKeyWithValue(t, spiedCfg, []string{pagerKey}, "")
+	requireKeyWithValue(t, spiedCfg, []string{aliasesKey, "co"}, "pr checkout")
+	requireKeyWithValue(t, spiedCfg, []string{httpUnixSocketKey}, "")
+	requireKeyWithValue(t, spiedCfg, []string{browserKey}, "")
 }
 
 func TestGetNonExistentKey(t *testing.T) {
@@ -101,12 +101,12 @@ func TestGetOrDefaultApplicationDefaults(t *testing.T) {
 		key             string
 		expectedDefault string
 	}{
-		{"git_protocol", "https"},
-		{"editor", ""},
-		{"prompt", "enabled"},
-		{"pager", ""},
-		{"http_unix_socket", ""},
-		{"browser", ""},
+		{gitProtocolKey, "https"},
+		{editorKey, ""},
+		{promptKey, "enabled"},
+		{pagerKey, ""},
+		{httpUnixSocketKey, ""},
+		{browserKey, ""},
 	}
 
 	for _, tt := range tests {
@@ -127,10 +127,10 @@ func TestGetOrDefaultApplicationDefaults(t *testing.T) {
 func TestGetOrDefaultExistingKey(t *testing.T) {
 	// Given have a top level config entry
 	cfg := newTestConfig()
-	cfg.Set("", "git_protocol", "ssh")
+	cfg.Set("", gitProtocolKey, "ssh")
 
 	// When we get that key
-	val, err := cfg.GetOrDefault("", "git_protocol")
+	val, err := cfg.GetOrDefault("", gitProtocolKey)
 
 	// Then it returns successfully with the correct value, and doesn't fall back
 	// to the default
@@ -153,12 +153,12 @@ func TestGetOrDefaultNotFoundAndNoDefault(t *testing.T) {
 
 func TestFallbackConfig(t *testing.T) {
 	cfg := fallbackConfig()
-	requireKeyWithValue(t, cfg, []string{"git_protocol"}, "https")
-	requireKeyWithValue(t, cfg, []string{"editor"}, "")
-	requireKeyWithValue(t, cfg, []string{"prompt"}, "enabled")
-	requireKeyWithValue(t, cfg, []string{"pager"}, "")
-	requireKeyWithValue(t, cfg, []string{"aliases", "co"}, "pr checkout")
-	requireKeyWithValue(t, cfg, []string{"http_unix_socket"}, "")
-	requireKeyWithValue(t, cfg, []string{"browser"}, "")
+	requireKeyWithValue(t, cfg, []string{gitProtocolKey}, "https")
+	requireKeyWithValue(t, cfg, []string{editorKey}, "")
+	requireKeyWithValue(t, cfg, []string{promptKey}, "enabled")
+	requireKeyWithValue(t, cfg, []string{pagerKey}, "")
+	requireKeyWithValue(t, cfg, []string{aliasesKey, "co"}, "pr checkout")
+	requireKeyWithValue(t, cfg, []string{httpUnixSocketKey}, "")
+	requireKeyWithValue(t, cfg, []string{browserKey}, "")
 	requireNoKey(t, cfg, []string{"unknown"})
 }