mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-12-31 13:57:26 -08:00
fix(git): better cross-platform path resolution and tests
The simple filepath.IsAbs() is not enough on Windows, where the relative path may start with a separator and should then be taken relative to the volume name. So, introduce resolveGitPath() helper function that will do the right thing on both Windows and other systems. As a bonus, it returns paths converted to slashes for ease of use with the rest of the git segment: - Git for Windows uses only slashes. - Slashes do work as a path separator on Windows anyway. - Some tests in git segment still use (and rightfully so) slashes. Add tests for resolveGitPath() on both types of systems and fix TestEnableInWorktree using a system-dependent testing constant as a root path.
This commit is contained in:
parent
3895210ab3
commit
a2533c8f5b
|
@ -196,18 +196,14 @@ func (g *Git) hasWorktree(gitdir *environment.FileInfo) bool {
|
||||||
// we need the parent folder to detect where the real .git folder is
|
// we need the parent folder to detect where the real .git folder is
|
||||||
ind = strings.LastIndex(g.workingFolder, "/.git/modules")
|
ind = strings.LastIndex(g.workingFolder, "/.git/modules")
|
||||||
if ind > -1 {
|
if ind > -1 {
|
||||||
if !filepath.IsAbs(g.workingFolder) {
|
g.rootFolder = resolveGitPath(gitdir.ParentFolder, g.workingFolder)
|
||||||
g.rootFolder = filepath.Join(gitdir.ParentFolder, g.workingFolder)
|
|
||||||
} else {
|
|
||||||
g.rootFolder = g.workingFolder
|
|
||||||
}
|
|
||||||
// this might be both a worktree and a submodule, where the path would look like
|
// this might be both a worktree and a submodule, where the path would look like
|
||||||
// this: path/.git/modules/module/path/worktrees/location. We cannot distinguish
|
// this: path/.git/modules/module/path/worktrees/location. We cannot distinguish
|
||||||
// between worktree and a module path containing the word 'worktree,' however.
|
// between worktree and a module path containing the word 'worktree,' however.
|
||||||
ind = strings.LastIndex(g.rootFolder, "/worktrees/")
|
ind = strings.LastIndex(g.rootFolder, "/worktrees/")
|
||||||
if ind > -1 && g.env.HasFilesInDir(g.rootFolder, "gitdir") {
|
if ind > -1 && g.env.HasFilesInDir(g.rootFolder, "gitdir") {
|
||||||
gitDir := filepath.Join(g.rootFolder, "gitdir")
|
gitDir := filepath.Join(g.rootFolder, "gitdir")
|
||||||
realGitFolder := filepath.Clean(g.env.FileContent(gitDir))
|
realGitFolder := g.env.FileContent(gitDir)
|
||||||
g.realFolder = strings.TrimSuffix(realGitFolder, ".git\n")
|
g.realFolder = strings.TrimSuffix(realGitFolder, ".git\n")
|
||||||
g.rootFolder = g.rootFolder[:ind]
|
g.rootFolder = g.rootFolder[:ind]
|
||||||
g.workingFolder = g.rootFolder
|
g.workingFolder = g.rootFolder
|
||||||
|
|
|
@ -57,6 +57,11 @@ func TestEnabledInWorkingDirectory(t *testing.T) {
|
||||||
assert.Equal(t, fileInfo.Path, g.workingFolder)
|
assert.Equal(t, fileInfo.Path, g.workingFolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolveEmptyGitPath(t *testing.T) {
|
||||||
|
base := "base"
|
||||||
|
assert.Equal(t, base, resolveGitPath(base, ""))
|
||||||
|
}
|
||||||
|
|
||||||
func TestEnabledInWorktree(t *testing.T) {
|
func TestEnabledInWorktree(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Case string
|
Case string
|
||||||
|
@ -67,63 +72,59 @@ func TestEnabledInWorktree(t *testing.T) {
|
||||||
ExpectedRealFolder string
|
ExpectedRealFolder string
|
||||||
ExpectedWorkingFolder string
|
ExpectedWorkingFolder string
|
||||||
ExpectedRootFolder string
|
ExpectedRootFolder string
|
||||||
WindowsPaths bool
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Case: "worktree",
|
Case: "worktree",
|
||||||
ExpectedEnabled: true,
|
ExpectedEnabled: true,
|
||||||
WorkingFolder: "/dev/.git/worktrees/folder_worktree",
|
WorkingFolder: TestRootPath + "dev/.git/worktrees/folder_worktree",
|
||||||
WorkingFolderAddon: "gitdir",
|
WorkingFolderAddon: "gitdir",
|
||||||
WorkingFolderContent: "/dev/worktree.git\n",
|
WorkingFolderContent: TestRootPath + "dev/worktree.git\n",
|
||||||
ExpectedWorkingFolder: "/dev/.git/worktrees/folder_worktree",
|
ExpectedWorkingFolder: TestRootPath + "dev/.git/worktrees/folder_worktree",
|
||||||
ExpectedRealFolder: "/dev/worktree",
|
ExpectedRealFolder: TestRootPath + "dev/worktree",
|
||||||
ExpectedRootFolder: "/dev/.git",
|
ExpectedRootFolder: TestRootPath + "dev/.git",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "submodule",
|
Case: "submodule",
|
||||||
ExpectedEnabled: true,
|
ExpectedEnabled: true,
|
||||||
WorkingFolder: "./.git/modules/submodule",
|
WorkingFolder: "./.git/modules/submodule",
|
||||||
ExpectedWorkingFolder: "/dev/.git/modules/submodule",
|
ExpectedWorkingFolder: TestRootPath + "dev/.git/modules/submodule",
|
||||||
ExpectedRealFolder: "/dev/.git/modules/submodule",
|
ExpectedRealFolder: TestRootPath + "dev/.git/modules/submodule",
|
||||||
ExpectedRootFolder: "/dev/.git/modules/submodule",
|
ExpectedRootFolder: TestRootPath + "dev/.git/modules/submodule",
|
||||||
WindowsPaths: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "submodule with root working folder",
|
Case: "submodule with root working folder",
|
||||||
ExpectedEnabled: true,
|
ExpectedEnabled: true,
|
||||||
WorkingFolder: "/repo/.git/modules/submodule",
|
WorkingFolder: TestRootPath + "repo/.git/modules/submodule",
|
||||||
ExpectedWorkingFolder: "/repo/.git/modules/submodule",
|
ExpectedWorkingFolder: TestRootPath + "repo/.git/modules/submodule",
|
||||||
ExpectedRealFolder: "/repo/.git/modules/submodule",
|
ExpectedRealFolder: TestRootPath + "repo/.git/modules/submodule",
|
||||||
ExpectedRootFolder: "/repo/.git/modules/submodule",
|
ExpectedRootFolder: TestRootPath + "repo/.git/modules/submodule",
|
||||||
WindowsPaths: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "submodule with worktrees",
|
Case: "submodule with worktrees",
|
||||||
ExpectedEnabled: true,
|
ExpectedEnabled: true,
|
||||||
WorkingFolder: "/dev/.git/modules/module/path/worktrees/location",
|
WorkingFolder: TestRootPath + "dev/.git/modules/module/path/worktrees/location",
|
||||||
WorkingFolderAddon: "gitdir",
|
WorkingFolderAddon: "gitdir",
|
||||||
WorkingFolderContent: "/dev/worktree.git\n",
|
WorkingFolderContent: TestRootPath + "dev/worktree.git\n",
|
||||||
ExpectedWorkingFolder: "/dev/.git/modules/module/path",
|
ExpectedWorkingFolder: TestRootPath + "dev/.git/modules/module/path",
|
||||||
ExpectedRealFolder: "/dev/worktree",
|
ExpectedRealFolder: TestRootPath + "dev/worktree",
|
||||||
ExpectedRootFolder: "/dev/.git/modules/module/path",
|
ExpectedRootFolder: TestRootPath + "dev/.git/modules/module/path",
|
||||||
WindowsPaths: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "separate git dir",
|
Case: "separate git dir",
|
||||||
ExpectedEnabled: true,
|
ExpectedEnabled: true,
|
||||||
WorkingFolder: "/dev/separate/.git/posh",
|
WorkingFolder: TestRootPath + "dev/separate/.git/posh",
|
||||||
ExpectedWorkingFolder: "/dev/",
|
ExpectedWorkingFolder: TestRootPath + "dev/",
|
||||||
ExpectedRealFolder: "/dev/",
|
ExpectedRealFolder: TestRootPath + "dev/",
|
||||||
ExpectedRootFolder: "/dev/separate/.git/posh",
|
ExpectedRootFolder: TestRootPath + "dev/separate/.git/posh",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fileInfo := &environment.FileInfo{
|
fileInfo := &environment.FileInfo{
|
||||||
Path: "/dev/.git",
|
Path: TestRootPath + "dev/.git",
|
||||||
ParentFolder: "/dev",
|
ParentFolder: TestRootPath + "dev",
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
env := new(mock.MockedEnvironment)
|
env := new(mock.MockedEnvironment)
|
||||||
env.On("FileContent", "/dev/.git").Return(fmt.Sprintf("gitdir: %s", tc.WorkingFolder))
|
env.On("FileContent", TestRootPath+"dev/.git").Return(fmt.Sprintf("gitdir: %s", tc.WorkingFolder))
|
||||||
env.On("FileContent", filepath.Join(tc.WorkingFolder, tc.WorkingFolderAddon)).Return(tc.WorkingFolderContent)
|
env.On("FileContent", filepath.Join(tc.WorkingFolder, tc.WorkingFolderAddon)).Return(tc.WorkingFolderContent)
|
||||||
env.On("HasFilesInDir", tc.WorkingFolder, tc.WorkingFolderAddon).Return(true)
|
env.On("HasFilesInDir", tc.WorkingFolder, tc.WorkingFolderAddon).Return(true)
|
||||||
env.On("HasFilesInDir", tc.WorkingFolder, "HEAD").Return(true)
|
env.On("HasFilesInDir", tc.WorkingFolder, "HEAD").Return(true)
|
||||||
|
@ -135,11 +136,6 @@ func TestEnabledInWorktree(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Equal(t, tc.ExpectedEnabled, g.hasWorktree(fileInfo), tc.Case)
|
assert.Equal(t, tc.ExpectedEnabled, g.hasWorktree(fileInfo), tc.Case)
|
||||||
if tc.WindowsPaths {
|
|
||||||
tc.ExpectedRealFolder = filepath.Clean(tc.ExpectedRealFolder)
|
|
||||||
tc.ExpectedRootFolder = filepath.Clean(tc.ExpectedRootFolder)
|
|
||||||
tc.ExpectedWorkingFolder = filepath.Clean(tc.ExpectedWorkingFolder)
|
|
||||||
}
|
|
||||||
assert.Equal(t, tc.ExpectedWorkingFolder, g.workingFolder, tc.Case)
|
assert.Equal(t, tc.ExpectedWorkingFolder, g.workingFolder, tc.Case)
|
||||||
assert.Equal(t, tc.ExpectedRealFolder, g.realFolder, tc.Case)
|
assert.Equal(t, tc.ExpectedRealFolder, g.realFolder, tc.Case)
|
||||||
assert.Equal(t, tc.ExpectedRootFolder, g.rootFolder, tc.Case)
|
assert.Equal(t, tc.ExpectedRootFolder, g.rootFolder, tc.Case)
|
||||||
|
|
13
src/segments/git_unix.go
Normal file
13
src/segments/git_unix.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package segments
|
||||||
|
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
// resolveGitPath resolves path relative to base.
|
||||||
|
func resolveGitPath(base, path string) string {
|
||||||
|
if filepath.IsAbs(path) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return filepath.Join(base, path)
|
||||||
|
}
|
36
src/segments/git_unix_test.go
Normal file
36
src/segments/git_unix_test.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package segments
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TestRootPath = "/"
|
||||||
|
|
||||||
|
func TestResolveGitPath(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Case string
|
||||||
|
Base string
|
||||||
|
Path string
|
||||||
|
Expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Case: "relative path",
|
||||||
|
Base: "dir/",
|
||||||
|
Path: "sub",
|
||||||
|
Expected: "dir/sub",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "absolute path",
|
||||||
|
Base: "/base",
|
||||||
|
Path: "/absolute/path",
|
||||||
|
Expected: "/absolute/path",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
assert.Equal(t, tc.Expected, resolveGitPath(tc.Base, tc.Path), tc.Case)
|
||||||
|
}
|
||||||
|
}
|
22
src/segments/git_windows.go
Normal file
22
src/segments/git_windows.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package segments
|
||||||
|
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
// resolveGitPath resolves path relative to base.
|
||||||
|
func resolveGitPath(base, path string) string {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
if filepath.IsAbs(path) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
// Note that git on Windows uses slashes exclusively. And it's okay
|
||||||
|
// because Windows actually accepts both directory separators. More
|
||||||
|
// importantly, however, parts of the git segment depend on those
|
||||||
|
// slashes.
|
||||||
|
if path[0] == '/' {
|
||||||
|
// path is a disk-relative path.
|
||||||
|
return filepath.VolumeName(base) + path
|
||||||
|
}
|
||||||
|
return filepath.ToSlash(filepath.Join(base, path))
|
||||||
|
}
|
42
src/segments/git_windows_test.go
Normal file
42
src/segments/git_windows_test.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package segments
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TestRootPath = "C:/"
|
||||||
|
|
||||||
|
func TestResolveGitPath(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Case string
|
||||||
|
Base string
|
||||||
|
Path string
|
||||||
|
Expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Case: "relative path",
|
||||||
|
Base: "dir\\",
|
||||||
|
Path: "sub",
|
||||||
|
Expected: "dir/sub",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "absolute path",
|
||||||
|
Base: "C:\\base",
|
||||||
|
Path: "C:/absolute/path",
|
||||||
|
Expected: "C:/absolute/path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "disk-relative path",
|
||||||
|
Base: "C:\\base",
|
||||||
|
Path: "/absolute/path",
|
||||||
|
Expected: "C:/absolute/path",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
assert.Equal(t, tc.Expected, resolveGitPath(tc.Base, tc.Path), tc.Case)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue