fix(git): use the right git executable for WSL

git executable detection
path conversion when needed
This commit is contained in:
lnu 2021-12-22 17:28:15 +01:00 committed by Jan De Dobbeleer
parent 52d7badbac
commit 77993158e2
6 changed files with 121 additions and 26 deletions

View file

@ -100,12 +100,16 @@ type environmentInfo interface {
doGet(url string, timeout int, requestModifiers ...HTTPRequestModifier) ([]byte, error) doGet(url string, timeout int, requestModifiers ...HTTPRequestModifier) ([]byte, error)
hasParentFilePath(path string) (fileInfo *fileInfo, err error) hasParentFilePath(path string) (fileInfo *fileInfo, err error)
isWsl() bool isWsl() bool
isWsl2() bool
stackCount() int stackCount() int
getTerminalWidth() (int, error) getTerminalWidth() (int, error)
getCachePath() string getCachePath() string
cache() cache cache() cache
close() close()
logs() string logs() string
inWSLSharedDrive() bool
convertToLinuxPath(path string) string
convertToWindowsPath(path string) string
} }
type commandCache struct { type commandCache struct {

View file

@ -5,6 +5,7 @@ package main
import ( import (
"errors" "errors"
"os" "os"
"strings"
"time" "time"
"github.com/shirou/gopsutil/v3/host" "github.com/shirou/gopsutil/v3/host"
@ -33,6 +34,15 @@ func (env *environment) isWsl() bool {
return env.getenv("WSL_DISTRO_NAME") != "" return env.getenv("WSL_DISTRO_NAME") != ""
} }
func (env *environment) isWsl2() bool {
defer env.trace(time.Now(), "isWsl2")
if !env.isWsl() {
return false
}
uname := env.getFileContent("/proc/sys/kernel/osrelease")
return strings.Contains(uname, "WSL2")
}
func (env *environment) getTerminalWidth() (int, error) { func (env *environment) getTerminalWidth() (int, error) {
defer env.trace(time.Now(), "getTerminalWidth") defer env.trace(time.Now(), "getTerminalWidth")
width, err := terminal.Width() width, err := terminal.Width()
@ -63,3 +73,23 @@ func (env *environment) getCachePath() string {
func (env *environment) getWindowsRegistryKeyValue(path string) (*windowsRegistryValue, error) { func (env *environment) getWindowsRegistryKeyValue(path string) (*windowsRegistryValue, error) {
return nil, errors.New("not implemented") return nil, errors.New("not implemented")
} }
func (env *environment) inWSLSharedDrive() bool {
return env.isWsl() && strings.HasPrefix(env.getcwd(), "/mnt/")
}
func (env *environment) convertToWindowsPath(path string) string {
windowsPath, err := env.runCommand("wslpath", "-w", path)
if err == nil {
return windowsPath
}
return path
}
func (env *environment) convertToLinuxPath(path string) string {
linuxPath, err := env.runCommand("wslpath", "-u", path)
if err == nil {
return linuxPath
}
return path
}

View file

@ -75,6 +75,11 @@ func (env *environment) isWsl() bool {
return false return false
} }
func (env *environment) isWsl2() bool {
defer env.trace(time.Now(), "isWsl2")
return false
}
func (env *environment) getTerminalWidth() (int, error) { func (env *environment) getTerminalWidth() (int, error) {
defer env.trace(time.Now(), "getTerminalWidth") defer env.trace(time.Now(), "getTerminalWidth")
handle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0) handle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
@ -243,3 +248,15 @@ func (env *environment) getWindowsRegistryKeyValue(path string) (*windowsRegistr
return nil, errors.New(errorLogMsg) return nil, errors.New(errorLogMsg)
} }
} }
func (env *environment) inWSLSharedDrive() bool {
return false
}
func (env *environment) convertToWindowsPath(path string) string {
return path
}
func (env *environment) convertToLinuxPath(path string) string {
return path
}

View file

@ -45,11 +45,13 @@ type git struct {
WorktreeCount int WorktreeCount int
IsWorkTree bool IsWorkTree bool
gitWorkingFolder string // .git working folder, can be different of root if using worktree gitWorkingFolder string // .git working folder
gitRootFolder string // .git root folder gitRootFolder string // .git root folder
gitWorktreeFolder string // .git real worktree path gitRealFolder string // .git real folder(can be different from current path when in worktrees)
gitCommand string gitCommand string
IsWslSharedPath bool
} }
const ( const (
@ -102,6 +104,10 @@ const (
) )
func (g *git) enabled() bool { func (g *git) enabled() bool {
// when in wsl/wsl2 and in a windows shared folder
// we must use git.exe and convert paths accordingly
// for worktrees, stashes, and path to work
g.IsWslSharedPath = g.env.inWSLSharedDrive()
if !g.env.hasCommand(g.getGitCommand()) { if !g.env.hasCommand(g.getGitCommand()) {
return false return false
} }
@ -116,7 +122,8 @@ func (g *git) enabled() bool {
if gitdir.isDir { if gitdir.isDir {
g.gitWorkingFolder = gitdir.path g.gitWorkingFolder = gitdir.path
g.gitRootFolder = gitdir.path g.gitRootFolder = gitdir.path
g.gitWorktreeFolder = strings.TrimSuffix(gitdir.path, ".git") // convert the worktree file path to a windows one when in wsl 2 shared folder
g.gitRealFolder = strings.TrimSuffix(g.convertToWindowsPath(gitdir.path), ".git")
return true return true
} }
// handle worktree // handle worktree
@ -124,13 +131,15 @@ func (g *git) enabled() bool {
dirPointer := strings.Trim(g.env.getFileContent(gitdir.path), " \r\n") dirPointer := strings.Trim(g.env.getFileContent(gitdir.path), " \r\n")
matches := findNamedRegexMatch(`^gitdir: (?P<dir>.*)$`, dirPointer) matches := findNamedRegexMatch(`^gitdir: (?P<dir>.*)$`, dirPointer)
if matches != nil && matches["dir"] != "" { if matches != nil && matches["dir"] != "" {
g.gitWorkingFolder = matches["dir"] // if we open a worktree file in a shared wsl2 folder, we have to convert it back
// to the mounted path
g.gitWorkingFolder = g.convertToLinuxPath(matches["dir"])
// in worktrees, the path looks like this: gitdir: path/.git/worktrees/branch // in worktrees, the path looks like this: gitdir: path/.git/worktrees/branch
// strips the last .git/worktrees part // strips the last .git/worktrees part
// :ind+5 = index + /.git // :ind+5 = index + /.git
ind := strings.LastIndex(g.gitWorkingFolder, "/.git/worktrees") ind := strings.LastIndex(g.gitWorkingFolder, "/.git/worktrees")
g.gitRootFolder = g.gitWorkingFolder[:ind+5] g.gitRootFolder = g.gitWorkingFolder[:ind+5]
g.gitWorktreeFolder = strings.TrimSuffix(g.env.getFileContent(g.gitWorkingFolder+"/gitdir"), ".git\n") g.gitRealFolder = strings.TrimSuffix(g.env.getFileContent(g.gitWorkingFolder+"/gitdir"), ".git\n")
g.IsWorkTree = true g.IsWorkTree = true
return true return true
} }
@ -278,25 +287,15 @@ func (g *git) getGitCommand() string {
if len(g.gitCommand) > 0 { if len(g.gitCommand) > 0 {
return g.gitCommand return g.gitCommand
} }
inWSL2SharedDrive := func(env environmentInfo) bool {
if !env.isWsl() {
return false
}
if !strings.HasPrefix(env.getcwd(), "/mnt/") {
return false
}
uname, _ := g.env.runCommand("uname", "-r")
return strings.Contains(uname, "WSL2")
}
g.gitCommand = "git" g.gitCommand = "git"
if g.env.getRuntimeGOOS() == windowsPlatform || inWSL2SharedDrive(g.env) { if g.env.getRuntimeGOOS() == windowsPlatform || g.IsWslSharedPath {
g.gitCommand = "git.exe" g.gitCommand = "git.exe"
} }
return g.gitCommand return g.gitCommand
} }
func (g *git) getGitCommandOutput(args ...string) string { func (g *git) getGitCommandOutput(args ...string) string {
args = append([]string{"-C", g.gitWorktreeFolder, "--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"}, args...) args = append([]string{"-C", g.gitRealFolder, "--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"}, args...)
val, _ := g.env.runCommand(g.getGitCommand(), args...) val, _ := g.env.runCommand(g.getGitCommand(), args...)
return val return val
} }
@ -490,3 +489,17 @@ func (g *git) getOriginURL(upstream string) string {
} }
return keyVal return keyVal
} }
func (g *git) convertToWindowsPath(path string) string {
if !g.IsWslSharedPath {
return path
}
return g.env.convertToWindowsPath(path)
}
func (g *git) convertToLinuxPath(path string) string {
if !g.IsWslSharedPath {
return path
}
return g.env.convertToLinuxPath(path)
}

View file

@ -15,6 +15,7 @@ const (
func TestEnabledGitNotFound(t *testing.T) { func TestEnabledGitNotFound(t *testing.T) {
env := new(MockedEnvironment) env := new(MockedEnvironment)
env.On("inWSLSharedDrive", nil).Return(false)
env.On("hasCommand", "git").Return(false) env.On("hasCommand", "git").Return(false)
env.On("getRuntimeGOOS", nil).Return("") env.On("getRuntimeGOOS", nil).Return("")
env.On("isWsl", nil).Return(false) env.On("isWsl", nil).Return(false)
@ -28,6 +29,7 @@ func TestEnabledGitNotFound(t *testing.T) {
func TestEnabledInWorkingDirectory(t *testing.T) { func TestEnabledInWorkingDirectory(t *testing.T) {
env := new(MockedEnvironment) env := new(MockedEnvironment)
env.On("inWSLSharedDrive", nil).Return(false)
env.On("hasCommand", "git").Return(true) env.On("hasCommand", "git").Return(true)
env.On("getRuntimeGOOS", nil).Return("") env.On("getRuntimeGOOS", nil).Return("")
env.On("isWsl", nil).Return(false) env.On("isWsl", nil).Return(false)
@ -48,6 +50,7 @@ func TestEnabledInWorkingDirectory(t *testing.T) {
func TestEnabledInWorkingTree(t *testing.T) { func TestEnabledInWorkingTree(t *testing.T) {
env := new(MockedEnvironment) env := new(MockedEnvironment)
env.On("inWSLSharedDrive", nil).Return(false)
env.On("hasCommand", "git").Return(true) env.On("hasCommand", "git").Return(true)
env.On("getRuntimeGOOS", nil).Return("") env.On("getRuntimeGOOS", nil).Return("")
env.On("isWsl", nil).Return(false) env.On("isWsl", nil).Return(false)
@ -66,7 +69,7 @@ func TestEnabledInWorkingTree(t *testing.T) {
} }
assert.True(t, g.enabled()) assert.True(t, g.enabled())
assert.Equal(t, "/dev/real_folder/.git/worktrees/folder_worktree", g.gitWorkingFolder) assert.Equal(t, "/dev/real_folder/.git/worktrees/folder_worktree", g.gitWorkingFolder)
assert.Equal(t, "/dev/folder_worktree", g.gitWorktreeFolder) assert.Equal(t, "/dev/folder_worktree", g.gitRealFolder)
} }
func TestGetGitOutputForCommand(t *testing.T) { func TestGetGitOutputForCommand(t *testing.T) {
@ -195,6 +198,7 @@ func TestSetGitHEADContextClean(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
env := new(MockedEnvironment) env := new(MockedEnvironment)
env.On("inWSLSharedDrive", nil).Return(false)
env.On("getRuntimeGOOS", nil).Return("unix") env.On("getRuntimeGOOS", nil).Return("unix")
env.On("isWsl", nil).Return(false) env.On("isWsl", nil).Return(false)
env.mockGitCommand("", "describe", "--tags", "--exact-match") env.mockGitCommand("", "describe", "--tags", "--exact-match")
@ -537,17 +541,18 @@ func TestShouldIgnoreRootRepository(t *testing.T) {
func TestGetGitCommand(t *testing.T) { func TestGetGitCommand(t *testing.T) {
cases := []struct { cases := []struct {
Case string Case string
Expected string Expected string
IsWSL bool IsWSL bool
IsWSL1 bool IsWSL1 bool
GOOS string GOOS string
CWD string CWD string
IsWslSharedPath bool
}{ }{
{Case: "On Windows", Expected: "git.exe", GOOS: windowsPlatform}, {Case: "On Windows", Expected: "git.exe", GOOS: windowsPlatform},
{Case: "Non Windows", Expected: "git"}, {Case: "Non Windows", Expected: "git"},
{Case: "Iside WSL2, non shared", IsWSL: true, Expected: "git"}, {Case: "Iside WSL2, non shared", IsWSL: true, Expected: "git"},
{Case: "Iside WSL2, shared", Expected: "git.exe", IsWSL: true, CWD: "/mnt/bill"}, {Case: "Iside WSL2, shared", Expected: "git.exe", IsWSL: true, IsWslSharedPath: true, CWD: "/mnt/bill"},
{Case: "Iside WSL1, shared", Expected: "git", IsWSL: true, IsWSL1: true, CWD: "/mnt/bill"}, {Case: "Iside WSL1, shared", Expected: "git", IsWSL: true, IsWSL1: true, CWD: "/mnt/bill"},
} }
@ -566,6 +571,12 @@ func TestGetGitCommand(t *testing.T) {
env: env, env: env,
}, },
} }
if tc.IsWslSharedPath {
env.On("inWSLSharedDrive", nil).Return(true)
g.IsWslSharedPath = tc.IsWslSharedPath
} else {
env.On("inWSLSharedDrive", nil).Return(false)
}
assert.Equal(t, tc.Expected, g.getGitCommand(), tc.Case) assert.Equal(t, tc.Expected, g.getGitCommand(), tc.Case)
} }
} }

View file

@ -154,6 +154,11 @@ func (env *MockedEnvironment) isWsl() bool {
return args.Bool(0) return args.Bool(0)
} }
func (env *MockedEnvironment) isWsl2() bool {
args := env.Called(nil)
return args.Bool(0)
}
func (env *MockedEnvironment) getTerminalWidth() (int, error) { func (env *MockedEnvironment) getTerminalWidth() (int, error) {
args := env.Called(nil) args := env.Called(nil)
return args.Int(0), args.Error(1) return args.Int(0), args.Error(1)
@ -178,6 +183,21 @@ func (env *MockedEnvironment) logs() string {
return args.String(0) return args.String(0)
} }
func (env *MockedEnvironment) inWSLSharedDrive() bool {
args := env.Called(nil)
return args.Bool(0)
}
func (env *MockedEnvironment) convertToWindowsPath(path string) string {
args := env.Called(nil)
return args.String(0)
}
func (env *MockedEnvironment) convertToLinuxPath(path string) string {
args := env.Called(nil)
return args.String(0)
}
const ( const (
homeBill = "/home/bill" homeBill = "/home/bill"
homeJan = "/usr/home/jan" homeJan = "/usr/home/jan"