standardise how we read from the config

This commit is contained in:
Jesse Duffield 2020-11-25 08:52:00 +11:00
parent 7513bfb13a
commit 999e170f1d
8 changed files with 240 additions and 234 deletions

View file

@ -43,10 +43,13 @@ func (c *GitCommand) colorArg() string {
} }
func (c *GitCommand) GetConfigValue(key string) string { func (c *GitCommand) GetConfigValue(key string) string {
output, err := c.OSCommand.RunCommandWithOutput("git config --get %s", key) value, _ := c.getLocalGitConfig(key)
if err != nil { // we get an error if the key doesn't exist which we don't care about
// looks like this returns an error if there is no matching value which we're okay with
return "" if value != "" {
return value
} }
return strings.TrimSpace(output)
value, _ = c.getGlobalGitConfig(key)
return value
} }

View file

@ -2,6 +2,7 @@ package commands
import ( import (
"fmt" "fmt"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -9,6 +10,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str"
) )
// CatFile obtains the content of a file // CatFile obtains the content of a file
@ -262,3 +264,28 @@ func (c *GitCommand) ResetAndClean() error {
return c.RemoveUntrackedFiles() return c.RemoveUntrackedFiles()
} }
// EditFile opens a file in a subprocess using whatever editor is available,
// falling back to core.editor, VISUAL, EDITOR, then vi
func (c *GitCommand) EditFile(filename string) (*exec.Cmd, error) {
editor, _ := c.getGlobalGitConfig("core.editor")
if editor == "" {
editor = c.OSCommand.Getenv("VISUAL")
}
if editor == "" {
editor = c.OSCommand.Getenv("EDITOR")
}
if editor == "" {
if err := c.OSCommand.RunCommand("which vi"); err == nil {
editor = "vi"
}
}
if editor == "" {
return nil, errors.New("No editor defined in $VISUAL, $EDITOR, or git config")
}
splitCmd := str.ToArgv(fmt.Sprintf("%s %s", editor, c.OSCommand.Quote(filename)))
return c.OSCommand.PrepareSubProcess(splitCmd[0], splitCmd[1:]...), nil
}

View file

@ -2147,3 +2147,154 @@ func TestFindDotGitDir(t *testing.T) {
}) })
} }
} }
// TestEditFile is a function.
func TestEditFile(t *testing.T) {
type scenario struct {
filename string
command func(string, ...string) *exec.Cmd
getenv func(string) string
getGlobalGitConfig func(string) (string, error)
test func(*exec.Cmd, error)
}
scenarios := []scenario{
{
"test",
func(name string, arg ...string) *exec.Cmd {
return exec.Command("exit", "1")
},
func(env string) string {
return ""
},
func(cf string) (string, error) {
return "", nil
},
func(cmd *exec.Cmd, err error) {
assert.EqualError(t, err, "No editor defined in $VISUAL, $EDITOR, or git config")
},
},
{
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("exit", "1")
}
assert.EqualValues(t, "nano", name)
return nil
},
func(env string) string {
return ""
},
func(cf string) (string, error) {
return "nano", nil
},
func(cmd *exec.Cmd, err error) {
assert.NoError(t, err)
},
},
{
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("exit", "1")
}
assert.EqualValues(t, "nano", name)
return nil
},
func(env string) string {
if env == "VISUAL" {
return "nano"
}
return ""
},
func(cf string) (string, error) {
return "", nil
},
func(cmd *exec.Cmd, err error) {
assert.NoError(t, err)
},
},
{
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("exit", "1")
}
assert.EqualValues(t, "emacs", name)
return nil
},
func(env string) string {
if env == "EDITOR" {
return "emacs"
}
return ""
},
func(cf string) (string, error) {
return "", nil
},
func(cmd *exec.Cmd, err error) {
assert.NoError(t, err)
},
},
{
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("echo")
}
assert.EqualValues(t, "vi", name)
return nil
},
func(env string) string {
return ""
},
func(cf string) (string, error) {
return "", nil
},
func(cmd *exec.Cmd, err error) {
assert.NoError(t, err)
},
},
{
"file/with space",
func(name string, args ...string) *exec.Cmd {
if name == "which" {
return exec.Command("echo")
}
assert.EqualValues(t, "vi", name)
assert.EqualValues(t, "file/with space", args[0])
return nil
},
func(env string) string {
return ""
},
func(cf string) (string, error) {
return "", nil
},
func(cmd *exec.Cmd, err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
gitCmd := NewDummyGitCommand()
gitCmd.OSCommand.Command = s.command
gitCmd.OSCommand.Getenv = s.getenv
gitCmd.getGlobalGitConfig = s.getGlobalGitConfig
s.test(gitCmd.EditFile(s.filename))
}
}

View file

@ -19,7 +19,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str" "github.com/mgutz/str"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
gitconfig "github.com/tcnksm/go-gitconfig"
) )
// Platform stores the os state // Platform stores the os state
@ -41,7 +40,6 @@ type OSCommand struct {
Config config.AppConfigurer Config config.AppConfigurer
Command func(string, ...string) *exec.Cmd Command func(string, ...string) *exec.Cmd
BeforeExecuteCmd func(*exec.Cmd) BeforeExecuteCmd func(*exec.Cmd)
GetGlobalGitConfig func(string) (string, error)
Getenv func(string) string Getenv func(string) string
} }
@ -53,7 +51,6 @@ func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand {
Config: config, Config: config,
Command: exec.Command, Command: exec.Command,
BeforeExecuteCmd: func(*exec.Cmd) {}, BeforeExecuteCmd: func(*exec.Cmd) {},
GetGlobalGitConfig: gitconfig.Global,
Getenv: os.Getenv, Getenv: os.Getenv,
} }
} }
@ -235,31 +232,6 @@ func (c *OSCommand) OpenLink(link string) error {
return err return err
} }
// EditFile opens a file in a subprocess using whatever editor is available,
// falling back to core.editor, VISUAL, EDITOR, then vi
func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) {
editor, _ := c.GetGlobalGitConfig("core.editor")
if editor == "" {
editor = c.Getenv("VISUAL")
}
if editor == "" {
editor = c.Getenv("EDITOR")
}
if editor == "" {
if err := c.RunCommand("which vi"); err == nil {
editor = "vi"
}
}
if editor == "" {
return nil, errors.New("No editor defined in $VISUAL, $EDITOR, or git config")
}
splitCmd := str.ToArgv(fmt.Sprintf("%s %s", editor, c.Quote(filename)))
return c.PrepareSubProcess(splitCmd[0], splitCmd[1:]...), nil
}
// PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it // PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it
// TODO: see if this needs to exist, given that ExecutableFromString does the same things // TODO: see if this needs to exist, given that ExecutableFromString does the same things
func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *exec.Cmd { func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *exec.Cmd {

View file

@ -109,158 +109,6 @@ func TestOSCommandOpenFile(t *testing.T) {
} }
} }
// TestOSCommandEditFile is a function.
func TestOSCommandEditFile(t *testing.T) {
type scenario struct {
filename string
command func(string, ...string) *exec.Cmd
getenv func(string) string
getGlobalGitConfig func(string) (string, error)
test func(*exec.Cmd, error)
}
scenarios := []scenario{
{
"test",
func(name string, arg ...string) *exec.Cmd {
return exec.Command("exit", "1")
},
func(env string) string {
return ""
},
func(cf string) (string, error) {
return "", nil
},
func(cmd *exec.Cmd, err error) {
assert.EqualError(t, err, "No editor defined in $VISUAL, $EDITOR, or git config")
},
},
{
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("exit", "1")
}
assert.EqualValues(t, "nano", name)
return nil
},
func(env string) string {
return ""
},
func(cf string) (string, error) {
return "nano", nil
},
func(cmd *exec.Cmd, err error) {
assert.NoError(t, err)
},
},
{
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("exit", "1")
}
assert.EqualValues(t, "nano", name)
return nil
},
func(env string) string {
if env == "VISUAL" {
return "nano"
}
return ""
},
func(cf string) (string, error) {
return "", nil
},
func(cmd *exec.Cmd, err error) {
assert.NoError(t, err)
},
},
{
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("exit", "1")
}
assert.EqualValues(t, "emacs", name)
return nil
},
func(env string) string {
if env == "EDITOR" {
return "emacs"
}
return ""
},
func(cf string) (string, error) {
return "", nil
},
func(cmd *exec.Cmd, err error) {
assert.NoError(t, err)
},
},
{
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("echo")
}
assert.EqualValues(t, "vi", name)
return nil
},
func(env string) string {
return ""
},
func(cf string) (string, error) {
return "", nil
},
func(cmd *exec.Cmd, err error) {
assert.NoError(t, err)
},
},
{
"file/with space",
func(name string, args ...string) *exec.Cmd {
if name == "which" {
return exec.Command("echo")
}
assert.EqualValues(t, "vi", name)
assert.EqualValues(t, "file/with space", args[0])
return nil
},
func(env string) string {
return ""
},
func(cf string) (string, error) {
return "", nil
},
func(cmd *exec.Cmd, err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
OSCmd := NewDummyOSCommand()
OSCmd.Command = s.command
OSCmd.GetGlobalGitConfig = s.getGlobalGitConfig
OSCmd.Getenv = s.getenv
s.test(OSCmd.EditFile(s.filename))
}
}
// TestOSCommandQuote is a function. // TestOSCommandQuote is a function.
func TestOSCommandQuote(t *testing.T) { func TestOSCommandQuote(t *testing.T) {
osCommand := NewDummyOSCommand() osCommand := NewDummyOSCommand()

View file

@ -48,17 +48,19 @@ func TestCreatePullRequest(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
branch *models.Branch branch *models.Branch
remoteUrl string
command func(string, ...string) *exec.Cmd command func(string, ...string) *exec.Cmd
test func(err error) test func(err error)
} }
scenarios := []scenario{ scenarios := []scenario{
{ {
"Opens a link to new pull request on bitbucket", testName: "Opens a link to new pull request on bitbucket",
&models.Branch{ branch: &models.Branch{
Name: "feature/profile-page", Name: "feature/profile-page",
}, },
func(cmd string, args ...string) *exec.Cmd { remoteUrl: "git@bitbucket.org:johndoe/social_network.git",
command: func(cmd string, args ...string) *exec.Cmd {
// Handle git remote url call // Handle git remote url call
if strings.HasPrefix(cmd, "git") { if strings.HasPrefix(cmd, "git") {
return exec.Command("echo", "git@bitbucket.org:johndoe/social_network.git") return exec.Command("echo", "git@bitbucket.org:johndoe/social_network.git")
@ -68,16 +70,17 @@ func TestCreatePullRequest(t *testing.T) {
assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1"}) assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1"})
return exec.Command("echo") return exec.Command("echo")
}, },
func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{ {
"Opens a link to new pull request on bitbucket with http remote url", testName: "Opens a link to new pull request on bitbucket with http remote url",
&models.Branch{ branch: &models.Branch{
Name: "feature/events", Name: "feature/events",
}, },
func(cmd string, args ...string) *exec.Cmd { remoteUrl: "https://my_username@bitbucket.org/johndoe/social_network.git",
command: func(cmd string, args ...string) *exec.Cmd {
// Handle git remote url call // Handle git remote url call
if strings.HasPrefix(cmd, "git") { if strings.HasPrefix(cmd, "git") {
return exec.Command("echo", "https://my_username@bitbucket.org/johndoe/social_network.git") return exec.Command("echo", "https://my_username@bitbucket.org/johndoe/social_network.git")
@ -87,16 +90,17 @@ func TestCreatePullRequest(t *testing.T) {
assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events&t=1"}) assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events&t=1"})
return exec.Command("echo") return exec.Command("echo")
}, },
func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{ {
"Opens a link to new pull request on github", testName: "Opens a link to new pull request on github",
&models.Branch{ branch: &models.Branch{
Name: "feature/sum-operation", Name: "feature/sum-operation",
}, },
func(cmd string, args ...string) *exec.Cmd { remoteUrl: "git@github.com:peter/calculator.git",
command: func(cmd string, args ...string) *exec.Cmd {
// Handle git remote url call // Handle git remote url call
if strings.HasPrefix(cmd, "git") { if strings.HasPrefix(cmd, "git") {
return exec.Command("echo", "git@github.com:peter/calculator.git") return exec.Command("echo", "git@github.com:peter/calculator.git")
@ -106,16 +110,17 @@ func TestCreatePullRequest(t *testing.T) {
assert.Equal(t, args, []string{"https://github.com/peter/calculator/compare/feature/sum-operation?expand=1"}) assert.Equal(t, args, []string{"https://github.com/peter/calculator/compare/feature/sum-operation?expand=1"})
return exec.Command("echo") return exec.Command("echo")
}, },
func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{ {
"Opens a link to new pull request on gitlab", testName: "Opens a link to new pull request on gitlab",
&models.Branch{ branch: &models.Branch{
Name: "feature/ui", Name: "feature/ui",
}, },
func(cmd string, args ...string) *exec.Cmd { remoteUrl: "git@gitlab.com:peter/calculator.git",
command: func(cmd string, args ...string) *exec.Cmd {
// Handle git remote url call // Handle git remote url call
if strings.HasPrefix(cmd, "git") { if strings.HasPrefix(cmd, "git") {
return exec.Command("echo", "git@gitlab.com:peter/calculator.git") return exec.Command("echo", "git@gitlab.com:peter/calculator.git")
@ -125,19 +130,20 @@ func TestCreatePullRequest(t *testing.T) {
assert.Equal(t, args, []string{"https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"}) assert.Equal(t, args, []string{"https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"})
return exec.Command("echo") return exec.Command("echo")
}, },
func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{ {
"Throws an error if git service is unsupported", testName: "Throws an error if git service is unsupported",
&models.Branch{ branch: &models.Branch{
Name: "feature/divide-operation", Name: "feature/divide-operation",
}, },
func(cmd string, args ...string) *exec.Cmd { remoteUrl: "git@something.com:peter/calculator.git",
return exec.Command("echo", "git@something.com:peter/calculator.git") command: func(cmd string, args ...string) *exec.Cmd {
return exec.Command("echo")
}, },
func(err error) { test: func(err error) {
assert.Error(t, err) assert.Error(t, err)
}, },
}, },
@ -155,6 +161,14 @@ func TestCreatePullRequest(t *testing.T) {
"invalid.work.com": "noservice:invalid.work.com", "invalid.work.com": "noservice:invalid.work.com",
"noservice.work.com": "noservice.work.com", "noservice.work.com": "noservice.work.com",
} }
gitCommand.getLocalGitConfig = func(path string) (string, error) {
assert.Equal(t, path, "remote.origin.url")
return s.remoteUrl, nil
}
gitCommand.getGlobalGitConfig = func(path string) (string, error) {
assert.Equal(t, path, "remote.origin.url")
return "", nil
}
dummyPullRequest := NewPullRequest(gitCommand) dummyPullRequest := NewPullRequest(gitCommand)
s.test(dummyPullRequest.Create(s.branch)) s.test(dummyPullRequest.Create(s.branch))
}) })

View file

@ -13,10 +13,7 @@ func (c *GitCommand) usingGpg() bool {
return false return false
} }
gpgsign, _ := c.getLocalGitConfig("commit.gpgsign") gpgsign := c.GetConfigValue("commit.gpgsign")
if gpgsign == "" {
gpgsign, _ = c.getGlobalGitConfig("commit.gpgsign")
}
value := strings.ToLower(gpgsign) value := strings.ToLower(gpgsign)
return value == "true" || value == "1" || value == "yes" || value == "on" return value == "true" || value == "1" || value == "yes" || value == "on"
@ -25,14 +22,8 @@ func (c *GitCommand) usingGpg() bool {
// Push pushes to a branch // Push pushes to a branch
func (c *GitCommand) Push(branchName string, force bool, upstream string, args string, promptUserForCredential func(string) string) error { func (c *GitCommand) Push(branchName string, force bool, upstream string, args string, promptUserForCredential func(string) string) error {
followTagsFlag := "--follow-tags" followTagsFlag := "--follow-tags"
followsign, _ := c.getLocalGitConfig("push.followTags") if c.GetConfigValue("push.followTags") == "false" {
if followsign == "false" {
followTagsFlag = "" followTagsFlag = ""
} else if followsign == "" {
followsign, _ = c.getGlobalGitConfig("push.followTags")
if followsign == "false" {
followTagsFlag = ""
}
} }
forceFlag := "" forceFlag := ""

View file

@ -417,7 +417,7 @@ func (gui *Gui) PrepareSubProcess(command string) {
} }
func (gui *Gui) editFile(filename string) error { func (gui *Gui) editFile(filename string) error {
_, err := gui.runSyncOrAsyncCommand(gui.OSCommand.EditFile(filename)) _, err := gui.runSyncOrAsyncCommand(gui.GitCommand.EditFile(filename))
return err return err
} }