From 4056729e8e932c0d69d9794bca2287e3330f4a0f Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Wed, 14 Sep 2022 14:50:08 +0200 Subject: [PATCH] feat(wsl): allow using native executable on shared drive relates to #2097 --- src/segments/fossil.go | 5 ++- src/segments/fossil_test.go | 1 + src/segments/git.go | 8 ++--- src/segments/git_test.go | 59 ++++++-------------------------- src/segments/posh_git_test.go | 1 + src/segments/scm.go | 31 ++++++++++++++--- src/segments/scm_test.go | 38 ++++++++++++++++++++ src/segments/svn.go | 7 ++-- src/segments/svn_test.go | 5 +-- website/docs/segments/fossil.mdx | 5 +++ website/docs/segments/git.mdx | 2 ++ website/docs/segments/svn.mdx | 2 ++ 12 files changed, 95 insertions(+), 69 deletions(-) diff --git a/src/segments/fossil.go b/src/segments/fossil.go index a026672e..b094808b 100644 --- a/src/segments/fossil.go +++ b/src/segments/fossil.go @@ -38,12 +38,11 @@ func (f *Fossil) Template() string { } func (f *Fossil) Enabled() bool { - command := f.getCommand(FOSSILCOMMAND) - if !f.env.HasCommand(command) { + if !f.hasCommand(FOSSILCOMMAND) { return false } // run fossil command - output, err := f.env.RunCommand(command, "status") + output, err := f.env.RunCommand(f.command, "status") if err != nil { return false } diff --git a/src/segments/fossil_test.go b/src/segments/fossil_test.go index a9879252..47d6f222 100644 --- a/src/segments/fossil_test.go +++ b/src/segments/fossil_test.go @@ -54,6 +54,7 @@ func TestFossilStatus(t *testing.T) { env := new(mock.MockedEnvironment) env.On("GOOS").Return("unix") env.On("IsWsl").Return(false) + env.On("InWSLSharedDrive").Return(false) env.On("HasCommand", FOSSILCOMMAND).Return(tc.HasCommand) env.On("RunCommand", FOSSILCOMMAND, []string{"status"}).Return(strings.ReplaceAll(tc.Output, "\t", ""), tc.OutputError) f := &Fossil{ diff --git a/src/segments/git.go b/src/segments/git.go index abf673e7..13d8fc53 100644 --- a/src/segments/git.go +++ b/src/segments/git.go @@ -164,11 +164,7 @@ func (g *Git) Kraken() string { } func (g *Git) shouldDisplay() bool { - // when in a WSL 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.getCommand(GITCOMMAND)) { + if !g.hasCommand(GITCOMMAND) { return false } @@ -376,7 +372,7 @@ func (g *Git) setGitStatus() { func (g *Git) getGitCommandOutput(args ...string) string { args = append([]string{"-C", g.realDir, "--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"}, args...) - val, err := g.env.RunCommand(g.getCommand(GITCOMMAND), args...) + val, err := g.env.RunCommand(g.command, args...) if err != nil { return "" } diff --git a/src/segments/git_test.go b/src/segments/git_test.go index 0fcdb504..1a96bedc 100644 --- a/src/segments/git_test.go +++ b/src/segments/git_test.go @@ -154,8 +154,9 @@ func TestGetGitOutputForCommand(t *testing.T) { env.On("GOOS").Return("unix") g := &Git{ scm: scm{ - env: env, - props: properties.Map{}, + env: env, + props: properties.Map{}, + command: GITCOMMAND, }, } got := g.getGitCommandOutput(commandArgs...) @@ -308,6 +309,7 @@ func TestSetGitHEADContextClean(t *testing.T) { TagIcon: "tag ", RevertIcon: "revert ", }, + command: GITCOMMAND, }, ShortHash: "1234567", Ref: tc.Ref, @@ -346,6 +348,7 @@ func TestSetPrettyHEADName(t *testing.T) { CommitIcon: "commit ", TagIcon: "tag ", }, + command: GITCOMMAND, }, ShortHash: tc.ShortHash, } @@ -473,8 +476,9 @@ func TestSetGitStatus(t *testing.T) { env.MockGitCommand("", strings.ReplaceAll(tc.Output, "\t", ""), "status", "-unormal", "--branch", "--porcelain=2") g := &Git{ scm: scm{ - env: env, - props: properties.Map{}, + env: env, + props: properties.Map{}, + command: GITCOMMAND, }, } if tc.ExpectedWorking == nil { @@ -546,8 +550,9 @@ func TestGitUpstream(t *testing.T) { } g := &Git{ scm: scm{ - env: env, - props: props, + env: env, + props: props, + command: GITCOMMAND, }, Upstream: "origin/main", } @@ -595,48 +600,6 @@ func TestGetBranchStatus(t *testing.T) { } } -func TestGetGitCommand(t *testing.T) { - cases := []struct { - Case string - Expected string - IsWSL bool - IsWSL1 bool - GOOS string - CWD string - IsWslSharedPath bool - }{ - {Case: "On Windows", Expected: "git.exe", GOOS: environment.WINDOWS}, - {Case: "Non Windows", Expected: "git"}, - {Case: "Iside WSL2, non shared", IsWSL: true, Expected: "git"}, - {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"}, - } - - for _, tc := range cases { - env := new(mock.MockedEnvironment) - env.On("IsWsl").Return(tc.IsWSL) - env.On("GOOS").Return(tc.GOOS) - env.On("Pwd").Return(tc.CWD) - wslUname := "5.10.60.1-microsoft-standard-WSL2" - if tc.IsWSL1 { - wslUname = "4.4.0-19041-Microsoft" - } - env.On("RunCommand", "uname", []string{"-r"}).Return(wslUname, nil) - g := &Git{ - scm: scm{ - env: env, - }, - } - if tc.IsWslSharedPath { - env.On("InWSLSharedDrive").Return(true) - g.IsWslSharedPath = tc.IsWslSharedPath - } else { - env.On("InWSLSharedDrive").Return(false) - } - assert.Equal(t, tc.Expected, g.getCommand(GITCOMMAND), tc.Case) - } -} - func TestGitTemplateString(t *testing.T) { cases := []struct { Case string diff --git a/src/segments/posh_git_test.go b/src/segments/posh_git_test.go index fee78e89..16a5d76e 100644 --- a/src/segments/posh_git_test.go +++ b/src/segments/posh_git_test.go @@ -193,6 +193,7 @@ func TestPoshGitSegment(t *testing.T) { props: &properties.Map{ FetchUpstreamIcon: tc.FetchUpstreamIcon, }, + command: GITCOMMAND, }, } if len(tc.Template) == 0 { diff --git a/src/segments/scm.go b/src/segments/scm.go index 8bc8963d..0853e054 100644 --- a/src/segments/scm.go +++ b/src/segments/scm.go @@ -7,6 +7,11 @@ import ( "strings" ) +const ( + // Fallback to native command + NativeFallback properties.Property = "native_fallback" +) + // ScmStatus represents part of the status of a repository type ScmStatus struct { Unmerged int @@ -45,6 +50,7 @@ type scm struct { env environment.Environment IsWslSharedPath bool + CommandMissing bool Dir string // actual repo root directory workingDir string @@ -107,13 +113,28 @@ func (s *scm) convertToLinuxPath(path string) string { return s.env.ConvertToLinuxPath(path) } -func (s *scm) getCommand(command string) string { +func (s *scm) hasCommand(command string) bool { if len(s.command) > 0 { - return s.command + return true } - s.command = command + // when in a WSL shared folder, we must use command.exe and convert paths accordingly + // for worktrees, stashes, and path to work + s.IsWslSharedPath = s.env.InWSLSharedDrive() if s.env.GOOS() == environment.WINDOWS || s.IsWslSharedPath { - s.command += ".exe" + command += ".exe" } - return s.command + if s.env.HasCommand(command) { + s.command = command + return true + } + s.CommandMissing = true + // only use the native fallback when set by the user + if s.IsWslSharedPath && s.props.GetBool(NativeFallback, false) { + command = strings.TrimSuffix(command, ".exe") + if s.env.HasCommand(command) { + s.command = command + return true + } + } + return false } diff --git a/src/segments/scm_test.go b/src/segments/scm_test.go index 9b6f711d..b0d89868 100644 --- a/src/segments/scm_test.go +++ b/src/segments/scm_test.go @@ -1,6 +1,8 @@ package segments import ( + "oh-my-posh/environment" + "oh-my-posh/mock" "oh-my-posh/properties" "testing" @@ -154,3 +156,39 @@ func TestTruncateBranchWithSymbol(t *testing.T) { assert.Equal(t, tc.Expected, p.truncateBranch(tc.Branch), tc.Case) } } + +func TestHasCommand(t *testing.T) { + cases := []struct { + Case string + ExpectedCommand string + Command string + GOOS string + IsWslSharedPath bool + NativeFallback bool + }{ + {Case: "On Windows", ExpectedCommand: "git.exe", GOOS: environment.WINDOWS}, + {Case: "Cache", ExpectedCommand: "git.exe", Command: "git.exe"}, + {Case: "Non Windows", ExpectedCommand: "git"}, + {Case: "Iside WSL2, non shared", ExpectedCommand: "git"}, + {Case: "Iside WSL2, shared", ExpectedCommand: "git.exe", IsWslSharedPath: true}, + {Case: "Iside WSL2, shared fallback", ExpectedCommand: "git", IsWslSharedPath: true, NativeFallback: true}, + } + + for _, tc := range cases { + env := new(mock.MockedEnvironment) + env.On("GOOS").Return(tc.GOOS) + env.On("InWSLSharedDrive").Return(tc.IsWslSharedPath) + env.On("HasCommand", "git").Return(true) + env.On("HasCommand", "git.exe").Return(!tc.NativeFallback) + s := &scm{ + env: env, + props: properties.Map{ + NativeFallback: tc.NativeFallback, + }, + command: tc.Command, + } + + _ = s.hasCommand(GITCOMMAND) + assert.Equal(t, tc.ExpectedCommand, s.command, tc.Case) + } +} diff --git a/src/segments/svn.go b/src/segments/svn.go index ce730cc4..4d6fd71f 100644 --- a/src/segments/svn.go +++ b/src/segments/svn.go @@ -62,10 +62,7 @@ func (s *Svn) Enabled() bool { } func (s *Svn) shouldDisplay() bool { - // when in a WSL shared folder, we must use git.exe and convert paths accordingly - // for worktrees, stashes, and path to work - s.IsWslSharedPath = s.env.InWSLSharedDrive() - if !s.env.HasCommand(s.getCommand(SVNCOMMAND)) { + if !s.hasCommand(SVNCOMMAND) { return false } Svndir, err := s.env.HasParentFilePath(".svn") @@ -120,7 +117,7 @@ func (s *Svn) setSvnStatus() { func (s *Svn) getSvnCommandOutput(command string, args ...string) string { args = append([]string{command, s.realDir}, args...) - val, err := s.env.RunCommand(s.getCommand(SVNCOMMAND), args...) + val, err := s.env.RunCommand(s.command, args...) if err != nil { return "" } diff --git a/src/segments/svn_test.go b/src/segments/svn_test.go index 6ce67135..ccbea01f 100644 --- a/src/segments/svn_test.go +++ b/src/segments/svn_test.go @@ -234,8 +234,9 @@ R Moved.File`, s := &Svn{ scm: scm{ - env: env, - props: properties.Map{}, + env: env, + props: properties.Map{}, + command: SVNCOMMAND, }, } s.setSvnStatus() diff --git a/website/docs/segments/fossil.mdx b/website/docs/segments/fossil.mdx index 52116c8e..3a85dcbb 100644 --- a/website/docs/segments/fossil.mdx +++ b/website/docs/segments/fossil.mdx @@ -28,6 +28,11 @@ Local changes can also be displayed which uses the following syntax: } ``` +## Properties + +- native_fallback: `boolean` - when set to `true` and `fossil.exe` is not available when inside a WSL2 shared Windows drive, we will fallback to the native fossil +executable to fetch data. Not all information can be displayed in this case. Defaults to `false`. + ## Template ([info][templates]) :::note default template diff --git a/website/docs/segments/git.mdx b/website/docs/segments/git.mdx index 0eedbb90..7e5a05a8 100644 --- a/website/docs/segments/git.mdx +++ b/website/docs/segments/git.mdx @@ -66,6 +66,8 @@ You can set the following properties to `true` to enable fetching additional inf - ignore_submodules: `map[string]string` - map of repo's where to change the [--ignore-submodules][submodules] flag (`none`, `untracked`, `dirty` or `all`). For example `"ignore_submodules": { "/Users/me/repos/repo1": "all" }`. If you want to override for all repo's, use `*` to set the mode instead of the repo path. +- native_fallback: `boolean` - when set to `true` and `git.exe` is not available when inside a WSL2 shared Windows drive, we will fallback to the native git +executable to fetch data. Not all information can be displayed in this case. Defaults to `false`. ### Icons diff --git a/website/docs/segments/svn.mdx b/website/docs/segments/svn.mdx index 5111b156..e614fc43 100644 --- a/website/docs/segments/svn.mdx +++ b/website/docs/segments/svn.mdx @@ -40,6 +40,8 @@ As doing multiple [subversion][svn] calls can slow down the prompt experience, w You can set the following properties to `true` to enable fetching additional information (and populate the template). - fetch_status: `boolean` - fetch the local changes - defaults to `false` +- native_fallback: `boolean` - when set to `true` and `svn.exe` is not available when inside a WSL2 shared Windows drive, we will fallback to the native svn +executable to fetch data. Not all information can be displayed in this case. Defaults to `false`. ## Template ([info][templates])