feat(git): follow symlinks for git dir

This commit is contained in:
Jan De Dobbeleer 2024-07-09 07:46:03 +02:00 committed by Jan De Dobbeleer
parent 4d3fe566e8
commit 3d0c85b211
31 changed files with 69 additions and 52 deletions

View file

@ -152,8 +152,8 @@ func (env *Environment) HTTPRequest(url string, _ io.Reader, _ int, _ ...http.Re
return args.Get(0).([]byte), args.Error(1)
}
func (env *Environment) HasParentFilePath(path string) (*runtime.FileInfo, error) {
args := env.Called(path)
func (env *Environment) HasParentFilePath(parent string, followSymlinks bool) (*runtime.FileInfo, error) {
args := env.Called(parent, followSymlinks)
return args.Get(0).(*runtime.FileInfo), args.Error(1)
}

View file

@ -154,7 +154,7 @@ type Environment interface {
HasFiles(pattern string) bool
HasFilesInDir(dir, pattern string) bool
HasFolder(folder string) bool
HasParentFilePath(path string) (fileInfo *FileInfo, err error)
HasParentFilePath(path string, followSymlinks bool) (fileInfo *FileInfo, err error)
HasFileInParentDirs(pattern string, depth uint) bool
ResolveSymlink(path string) (string, error)
DirMatchesOneOf(dir string, regexes []string) bool
@ -664,26 +664,36 @@ func (term *Terminal) HTTPRequest(targetURL string, body io.Reader, timeout int,
return responseBody, nil
}
func (term *Terminal) HasParentFilePath(path string) (*FileInfo, error) {
defer term.Trace(time.Now(), path)
currentFolder := term.Pwd()
func (term *Terminal) HasParentFilePath(parent string, followSymlinks bool) (*FileInfo, error) {
defer term.Trace(time.Now(), parent)
path := term.Pwd()
if followSymlinks {
if actual, err := term.ResolveSymlink(path); err == nil {
path = actual
}
}
for {
fileSystem := os.DirFS(currentFolder)
info, err := fs.Stat(fileSystem, path)
fileSystem := os.DirFS(path)
info, err := fs.Stat(fileSystem, parent)
if err == nil {
return &FileInfo{
ParentFolder: currentFolder,
Path: filepath.Join(currentFolder, path),
ParentFolder: path,
Path: filepath.Join(path, parent),
IsDir: info.IsDir(),
}, nil
}
if !os.IsNotExist(err) {
return nil, err
}
if dir := filepath.Dir(currentFolder); dir != currentFolder {
currentFolder = dir
if dir := filepath.Dir(path); dir != path {
path = dir
continue
}
term.Error(err)
return nil, errors.New("no match at root level")
}

View file

@ -35,7 +35,7 @@ func (t *Azd) Enabled() bool {
folders := t.props.GetStringArray(LanguageFolders, []string{".azure"})
for _, folder := range folders {
if file, err := t.env.HasParentFilePath(folder); err == nil {
if file, err := t.env.HasParentFilePath(folder, false); err == nil {
parentFilePath = file.ParentFolder
break
}

View file

@ -44,7 +44,7 @@ func TestAzdSegment(t *testing.T) {
ParentFolder: "test",
IsDir: true,
}
env.On("HasParentFilePath", ".azure").Return(fileInfo, nil)
env.On("HasParentFilePath", ".azure", false).Return(fileInfo, nil)
dirEntries := []fs.DirEntry{
&MockDirEntry{
name: "config.json",
@ -58,7 +58,7 @@ func TestAzdSegment(t *testing.T) {
env.On("FileContent", filepath.Join("test", ".azure", "config.json")).Return(`{"version": 1, "defaultEnvironment": "TestEnvironment"}`, nil)
} else {
env.On("HasParentFilePath", ".azure").Return(&runtime.FileInfo{}, errors.New("no such file or directory"))
env.On("HasParentFilePath", ".azure", false).Return(&runtime.FileInfo{}, errors.New("no such file or directory"))
}
azd := Azd{

View file

@ -41,7 +41,7 @@ func (c *CfTarget) Enabled() bool {
return c.setCFTargetStatus()
}
manifest, err := c.env.HasParentFilePath("manifest.yml")
manifest, err := c.env.HasParentFilePath("manifest.yml", false)
if err != nil || manifest.IsDir {
return false
}

View file

@ -73,7 +73,7 @@ func TestCFTargetSegment(t *testing.T) {
if tc.FileInfo == nil {
err = errors.New("no such file or directory")
}
env.On("HasParentFilePath", "manifest.yml").Return(tc.FileInfo, err)
env.On("HasParentFilePath", "manifest.yml", false).Return(tc.FileInfo, err)
cfTarget := &CfTarget{}
props := properties.Map{

View file

@ -279,11 +279,12 @@ func (g *Git) shouldDisplay() bool {
return false
}
gitdir, err := g.env.HasParentFilePath(".git")
gitdir, err := g.env.HasParentFilePath(".git", true)
if err != nil {
if !g.props.GetBool(FetchBareInfo, false) {
return false
}
g.realDir = g.env.Pwd()
bare := g.getGitCommandOutput("rev-parse", "--is-bare-repository")
if bare == trueStr {
@ -291,6 +292,7 @@ func (g *Git) shouldDisplay() bool {
g.workingDir = g.realDir
return true
}
return false
}

View file

@ -51,7 +51,7 @@ func TestEnabledInWorkingDirectory(t *testing.T) {
env.On("FileContent", "/dir/hello/HEAD").Return("")
env.MockGitCommand(fileInfo.Path, "", "describe", "--tags", "--exact-match")
env.On("IsWsl").Return(false)
env.On("HasParentFilePath", ".git").Return(fileInfo, nil)
env.On("HasParentFilePath", ".git", true).Return(fileInfo, nil)
env.On("PathSeparator").Return("/")
env.On("Home").Return(poshHome)
env.On("Getenv", poshGitEnv).Return("")
@ -192,7 +192,7 @@ func TestEnabledInBareRepo(t *testing.T) {
env.On("InWSLSharedDrive").Return(false)
env.On("GOOS").Return("")
env.On("HasCommand", "git").Return(true)
env.On("HasParentFilePath", ".git").Return(&runtime.FileInfo{}, errors.New("nope"))
env.On("HasParentFilePath", ".git", true).Return(&runtime.FileInfo{}, errors.New("nope"))
env.MockGitCommand(pwd, tc.IsBare, "rev-parse", "--is-bare-repository")
env.On("Pwd").Return(pwd)
env.On("FileContent", "/home/user/bare.git/HEAD").Return(tc.HEAD)

View file

@ -43,7 +43,7 @@ func (g *Golang) getVersion() (string, error) {
if !g.props.GetBool(ParseModFile, false) {
return "", nil
}
gomod, err := g.language.env.HasParentFilePath("go.mod")
gomod, err := g.language.env.HasParentFilePath("go.mod", false)
if err != nil {
return "", nil
}

View file

@ -51,7 +51,7 @@ func TestGolang(t *testing.T) {
if !tc.HasModFileInParentDir {
err = errors.New("no match")
}
env.On("HasParentFilePath", "go.mod").Return(fileInfo, err)
env.On("HasParentFilePath", "go.mod", false).Return(fileInfo, err)
var content string
if tc.InvalidModfile {
content = "invalid go.mod file"

View file

@ -46,7 +46,7 @@ func (h *Haskell) Init(props properties.Properties, env runtime.Environment) {
h.language.commands = []*cmd{stackGhcCmd}
h.StackGhc = true
case "package":
_, err := h.language.env.HasParentFilePath("stack.yaml")
_, err := h.language.env.HasParentFilePath("stack.yaml", false)
if err == nil {
h.language.commands = []*cmd{stackGhcCmd}
h.StackGhc = true

View file

@ -74,9 +74,9 @@ func TestHaskell(t *testing.T) {
if tc.InStackPackage {
var err error
env.On("HasParentFilePath", "stack.yaml").Return(fileInfo, err)
env.On("HasParentFilePath", "stack.yaml", false).Return(fileInfo, err)
} else {
env.On("HasParentFilePath", "stack.yaml").Return(fileInfo, errors.New("no match"))
env.On("HasParentFilePath", "stack.yaml", false).Return(fileInfo, errors.New("no match"))
}
props[StackGhcMode] = tc.StackGhcMode

View file

@ -21,7 +21,7 @@ func (h *Helm) Enabled() bool {
inChart := false
files := []string{"Chart.yml", "Chart.yaml", "helmfile.yaml", "helmfile.yml"}
for _, file := range files {
if _, err := h.env.HasParentFilePath(file); err == nil {
if _, err := h.env.HasParentFilePath(file, false); err == nil {
inChart = true
break
}

View file

@ -86,8 +86,8 @@ func TestHelmSegment(t *testing.T) {
env.On("HasCommand", "helm").Return(tc.HelmExists)
env.On("RunCommand", "helm", []string{"version", "--short", "--template={{.Version}}"}).Return("v3.12.3", nil)
env.On("HasParentFilePath", tc.ChartFile).Return(&runtime.FileInfo{}, nil)
env.On("HasParentFilePath", testify_mock.Anything).Return(&runtime.FileInfo{}, errors.New("no such file or directory"))
env.On("HasParentFilePath", tc.ChartFile, false).Return(&runtime.FileInfo{}, nil)
env.On("HasParentFilePath", testify_mock.Anything, false).Return(&runtime.FileInfo{}, errors.New("no such file or directory"))
props := properties.Map{
DisplayMode: tc.DisplayMode,

View file

@ -174,11 +174,12 @@ func (l *language) hasLanguageFiles() bool {
func (l *language) hasProjectFiles() bool {
for _, extension := range l.projectFiles {
if configPath, err := l.env.HasParentFilePath(extension); err == nil {
if configPath, err := l.env.HasParentFilePath(extension, false); err == nil {
l.projectRoot = configPath
return true
}
}
return false
}

View file

@ -67,7 +67,7 @@ func (hg *Mercurial) shouldDisplay() bool {
return false
}
hgdir, err := hg.env.HasParentFilePath(".hg")
hgdir, err := hg.env.HasParentFilePath(".hg", false)
if err != nil {
return false
}

View file

@ -37,7 +37,7 @@ func TestMercurialEnabledInWorkingDirectory(t *testing.T) {
env.On("HasCommand", "hg").Return(true)
env.On("GOOS").Return("")
env.On("IsWsl").Return(false)
env.On("HasParentFilePath", ".hg").Return(fileInfo, nil)
env.On("HasParentFilePath", ".hg", false).Return(fileInfo, nil)
env.On("PathSeparator").Return("/")
env.On("Home").Return(poshHome)
env.On("Getenv", poshGitEnv).Return("")
@ -151,7 +151,7 @@ A Added.File
env.On("HasCommand", "hg").Return(true)
env.On("GOOS").Return("")
env.On("IsWsl").Return(false)
env.On("HasParentFilePath", ".hg").Return(fileInfo, nil)
env.On("HasParentFilePath", ".hg", false).Return(fileInfo, nil)
env.On("PathSeparator").Return("/")
env.On("Home").Return(poshHome)
env.On("Getenv", poshGitEnv).Return("")

View file

@ -839,7 +839,7 @@ func (pt *Path) makeFolderFormatMap() map[string]string {
folderFormatMap := make(map[string]string)
if gitDirFormat := pt.props.GetString(GitDirFormat, ""); len(gitDirFormat) != 0 {
dir, err := pt.env.HasParentFilePath(".git")
dir, err := pt.env.HasParentFilePath(".git", false)
if err == nil && dir.IsDir {
folderFormatMap[dir.ParentFolder] = gitDirFormat
}

View file

@ -1609,7 +1609,7 @@ func TestSplitPath(t *testing.T) {
for _, tc := range cases {
env := new(mock.Environment)
env.On("Home").Return("/a/b")
env.On("HasParentFilePath", ".git").Return(tc.GitDir, nil)
env.On("HasParentFilePath", ".git", false).Return(tc.GitDir, nil)
env.On("GOOS").Return(tc.GOOS)
path := &Path{

View file

@ -51,13 +51,16 @@ func (p *Plastic) Enabled() bool {
if !p.env.HasCommand("cm") {
return false
}
wkdir, err := p.env.HasParentFilePath(".plastic")
wkdir, err := p.env.HasParentFilePath(".plastic", false)
if err != nil {
return false
}
if p.shouldIgnoreRootRepository(wkdir.ParentFolder) {
return false
}
if !wkdir.IsDir {
return false
}

View file

@ -35,7 +35,7 @@ func TestPlasticEnabledInWorkspaceDirectory(t *testing.T) {
ParentFolder: "/dir",
IsDir: true,
}
env.On("HasParentFilePath", ".plastic").Return(fileInfo, nil)
env.On("HasParentFilePath", ".plastic", false).Return(fileInfo, nil)
p := &Plastic{
scm: scm{
env: env,

View file

@ -140,7 +140,7 @@ func TestPythonPythonInContext(t *testing.T) {
env.On("Getenv", "CONDA_ENV_PATH").Return("")
env.On("Getenv", "CONDA_DEFAULT_ENV").Return("")
env.On("Getenv", "PYENV_VERSION").Return("")
env.On("HasParentFilePath", ".python-version").Return(&runtime.FileInfo{}, errors.New("no match at root level"))
env.On("HasParentFilePath", ".python-version", false).Return(&runtime.FileInfo{}, errors.New("no match at root level"))
python := &Python{}
python.Init(properties.Map{}, env)
python.loadContext()
@ -188,7 +188,7 @@ func TestPythonVirtualEnvIgnoreDefaultVenvNames(t *testing.T) {
env.On("Getenv", "CONDA_ENV_PATH").Return("")
env.On("Getenv", "CONDA_DEFAULT_ENV").Return("")
env.On("Getenv", "PYENV_VERSION").Return("")
env.On("HasParentFilePath", ".python-version").Return(&runtime.FileInfo{}, errors.New("no match at root level"))
env.On("HasParentFilePath", ".python-version", false).Return(&runtime.FileInfo{}, errors.New("no match at root level"))
props[FolderNameFallback] = tc.FolderNameFallback

View file

@ -65,7 +65,7 @@ func TestQuasar(t *testing.T) {
env.On("HasFilesInDir", "/usr/home/project", "package-lock.json").Return(tc.HasPackageLockFile)
fileInfo := &runtime.FileInfo{ParentFolder: "/usr/home/project", IsDir: true}
env.On("HasParentFilePath", "quasar.config").Return(fileInfo, nil)
env.On("HasParentFilePath", "quasar.config", false).Return(fileInfo, nil)
env.On("FileContent", filepath.Join(fileInfo.ParentFolder, "package-lock.json")).Return(packageLockFile)
props[FetchDependencies] = tc.FetchDependencies

View file

@ -76,7 +76,7 @@ func (sl *Sapling) shouldDisplay() bool {
return false
}
slDir, err := sl.env.HasParentFilePath(".sl")
slDir, err := sl.env.HasParentFilePath(".sl", false)
if err != nil {
return false
}
@ -91,6 +91,7 @@ func (sl *Sapling) shouldDisplay() bool {
sl.realDir = strings.TrimSuffix(sl.convertToWindowsPath(slDir.Path), "/.sl")
sl.RepoName = runtime.Base(sl.env, sl.convertToLinuxPath(sl.realDir))
sl.setDir(slDir.Path)
return true
}

View file

@ -158,9 +158,9 @@ func TestShouldDisplay(t *testing.T) {
env.On("Home").Return("/usr/home/sapling")
env.On("DirMatchesOneOf", fileInfo.ParentFolder, []string{"/sapling/repo"}).Return(tc.Excluded)
if tc.InRepo {
env.On("HasParentFilePath", ".sl").Return(fileInfo, nil)
env.On("HasParentFilePath", ".sl", false).Return(fileInfo, nil)
} else {
env.On("HasParentFilePath", ".sl").Return(&runtime.FileInfo{}, errors.New("error"))
env.On("HasParentFilePath", ".sl", false).Return(&runtime.FileInfo{}, errors.New("error"))
}
sl := &Sapling{
scm: scm{

View file

@ -65,7 +65,7 @@ func (s *Svn) shouldDisplay() bool {
return false
}
Svndir, err := s.env.HasParentFilePath(".svn")
Svndir, err := s.env.HasParentFilePath(".svn", false)
if err != nil {
return false
}

View file

@ -40,7 +40,7 @@ func TestSvnEnabledInWorkingDirectory(t *testing.T) {
env.On("RunCommand", "svn", []string{"info", "/dir/hello", "--show-item", "revision"}).Return("", nil)
env.On("RunCommand", "svn", []string{"info", "/dir/hello", "--show-item", "relative-url"}).Return("", nil)
env.On("IsWsl").Return(false)
env.On("HasParentFilePath", ".svn").Return(fileInfo, nil)
env.On("HasParentFilePath", ".svn", false).Return(fileInfo, nil)
s := &Svn{
scm: scm{
env: env,
@ -237,7 +237,7 @@ R Moved.File`,
env.On("GOOS").Return("")
env.On("FileContent", "/dir/hello/trunk").Return("")
env.MockSvnCommand(fileInfo.Path, "", "info", "--tags", "--exact-match")
env.On("HasParentFilePath", ".svn").Return(fileInfo, nil)
env.On("HasParentFilePath", ".svn", false).Return(fileInfo, nil)
env.On("RunCommand", "svn", []string{"info", "", "--show-item", "revision"}).Return(tc.RefOutput, nil)
env.On("RunCommand", "svn", []string{"info", "", "--show-item", "relative-url"}).Return(tc.BranchOutput, nil)
env.On("RunCommand", "svn", []string{"status", ""}).Return(tc.StatusOutput, nil)

View file

@ -37,7 +37,7 @@ func (u *Umbraco) Enabled() bool {
// Check if we have a folder called Umbraco or umbraco in the current directory or a parent directory
folders := []string{"umbraco", "Umbraco"}
for _, folder := range folders {
if file, err := u.env.HasParentFilePath(folder); err == nil {
if file, err := u.env.HasParentFilePath(folder, false); err == nil {
location = file.ParentFolder
break
}

View file

@ -145,10 +145,10 @@ func TestUmbracoSegment(t *testing.T) {
IsDir: true,
}
env.On("HasParentFilePath", "umbraco").Return(fileInfo, nil)
env.On("HasParentFilePath", "umbraco", false).Return(fileInfo, nil)
} else {
env.On("HasParentFilePath", "Umbraco").Return(&runtime.FileInfo{}, errors.New("no such file or directory"))
env.On("HasParentFilePath", "umbraco").Return(&runtime.FileInfo{}, errors.New("no such file or directory"))
env.On("HasParentFilePath", "Umbraco", false).Return(&runtime.FileInfo{}, errors.New("no such file or directory"))
env.On("HasParentFilePath", "umbraco", false).Return(&runtime.FileInfo{}, errors.New("no such file or directory"))
}
dirEntries := []fs.DirEntry{}

View file

@ -40,7 +40,7 @@ func (u *Unity) Enabled() bool {
}
func (u *Unity) GetUnityVersion() (string, error) {
projectDir, err := u.env.HasParentFilePath("ProjectSettings")
projectDir, err := u.env.HasParentFilePath("ProjectSettings", false)
if err != nil {
u.env.Debug("No ProjectSettings parent folder found")
return "", err

View file

@ -98,7 +98,7 @@ func TestUnitySegment(t *testing.T) {
versionFilePath := filepath.Join(projectDir.Path, "ProjectVersion.txt")
env.On("FileContent", versionFilePath).Return(tc.VersionFileText)
}
env.On("HasParentFilePath", "ProjectSettings").Return(projectDir, err)
env.On("HasParentFilePath", "ProjectSettings", false).Return(projectDir, err)
props := properties.Map{}
unity := &Unity{}
@ -233,7 +233,7 @@ func TestUnitySegmentCSharpWebRequest(t *testing.T) {
versionFilePath := filepath.Join(projectDir.Path, "ProjectVersion.txt")
env.On("FileContent", versionFilePath).Return(tc.VersionFileText)
}
env.On("HasParentFilePath", "ProjectSettings").Return(projectDir, err)
env.On("HasParentFilePath", "ProjectSettings", false).Return(projectDir, err)
cache := &cache_.Cache{}
cache.On("Get", tc.CacheGet.key).Return(tc.CacheGet.val, tc.CacheGet.found)