mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 15:46:26 -04:00
lazygit builds a shell command by interpolating Quote'd arguments into a template and running the result via `cmd /c`. Several things were wrong on Windows: - Quote emitted bash-style `\"…\"` quoting, which cmd.exe doesn't understand. Making it usable at all previously required a fragile round-trip through str.ToArgv and re-escaping. - The assembled command line was handed to `cmd /c` without `/s`, so cmd's default rules stripped the wrong quotes once the line contained more than two of them (e.g. a quoted editor path at a location with spaces, plus a quoted filename that also contains spaces). - Shell metacharacters were escaped with `^` (`&` → `^&`, etc.), which neutralised command chaining, pipes, redirection and `%VAR%` expansion in custom commands. Quote now emits the standard Windows convention directly, and NewShell hands cmd.exe the fully-assembled line verbatim via SysProcAttr.CmdLine, wrapped as `cmd /s /c "<command>"`. The /s flag strips exactly the outer quote pair we add, leaving each argument's own quoting intact. With the `^` escaping gone, metacharacters in a custom command reach cmd as the author intended; this also removes the spurious `^` reported in #3092. Fixes #5560 Fixes #2427 Fixes #4147 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
76 lines
1.7 KiB
Go
76 lines
1.7 KiB
Go
package oscommands
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/go-errors/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// handling this in a separate file because str.ToArgv has different behaviour if we're on windows
|
|
|
|
func TestOSCommandOpenFileWindows(t *testing.T) {
|
|
type scenario struct {
|
|
filename string
|
|
runner *FakeCmdObjRunner
|
|
test func(error)
|
|
}
|
|
|
|
scenarios := []scenario{
|
|
{
|
|
filename: "test",
|
|
runner: NewFakeRunner(t).
|
|
ExpectArgs([]string{"cmd", "/s", "/c", `start "" "test"`}, "", errors.New("error")),
|
|
test: func(err error) {
|
|
assert.Error(t, err)
|
|
},
|
|
},
|
|
{
|
|
filename: "test",
|
|
runner: NewFakeRunner(t).
|
|
ExpectArgs([]string{"cmd", "/s", "/c", `start "" "test"`}, "", nil),
|
|
test: func(err error) {
|
|
assert.NoError(t, err)
|
|
},
|
|
},
|
|
{
|
|
filename: "filename with spaces",
|
|
runner: NewFakeRunner(t).
|
|
ExpectArgs([]string{"cmd", "/s", "/c", `start "" "filename with spaces"`}, "", nil),
|
|
test: func(err error) {
|
|
assert.NoError(t, err)
|
|
},
|
|
},
|
|
{
|
|
filename: "let's_test_with_single_quote",
|
|
runner: NewFakeRunner(t).
|
|
ExpectArgs([]string{"cmd", "/s", "/c", `start "" "let's_test_with_single_quote"`}, "", nil),
|
|
test: func(err error) {
|
|
assert.NoError(t, err)
|
|
},
|
|
},
|
|
{
|
|
filename: "$USER.txt",
|
|
runner: NewFakeRunner(t).
|
|
ExpectArgs([]string{"cmd", "/s", "/c", `start "" "$USER.txt"`}, "", nil),
|
|
test: func(err error) {
|
|
assert.NoError(t, err)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
oSCmd := NewDummyOSCommandWithRunner(s.runner)
|
|
platform := &Platform{
|
|
OS: "windows",
|
|
Shell: "cmd",
|
|
ShellArg: "/c",
|
|
}
|
|
oSCmd.Platform = platform
|
|
oSCmd.Cmd.platform = platform
|
|
oSCmd.UserConfig().OS.Open = `start "" {{filename}}`
|
|
|
|
s.test(oSCmd.OpenFile(s.filename))
|
|
}
|
|
}
|