diff --git a/CHANGELOG.md b/CHANGELOG.md index 709591fb90..ef909bbd4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### :sparkles: Release Note ### :rocket: Enhancements +- [#1160](https://github.com/reviewdog/reviewdog/pull/1160) Remove needless git command dependency by GitRelWorkdir func - ... ### :bug: Fixes diff --git a/project/run_test.go b/project/run_test.go index e03fb3ebc1..51ba7e1e80 100644 --- a/project/run_test.go +++ b/project/run_test.go @@ -152,7 +152,7 @@ func TestRun(t *testing.T) { conf := &Config{ Runner: map[string]*Runner{ "test": { - Cmd: "not found", + Cmd: "not-found", Errorformat: []string{`%f:%l:%c:%m`}, }, }, @@ -162,7 +162,7 @@ func TestRun(t *testing.T) { } else { t.Log(err) } - want := "sh: 1: not: not found\n" + want := "sh: 1: not-found: not found\n" if got := buf.String(); got != want { t.Errorf("got stderr %q, want %q", got, want) } diff --git a/service/serviceutil/serviceutil.go b/service/serviceutil/serviceutil.go index c046948280..49a53406bb 100644 --- a/service/serviceutil/serviceutil.go +++ b/service/serviceutil/serviceutil.go @@ -2,15 +2,95 @@ package serviceutil import ( "fmt" - "os/exec" + "os" + "path/filepath" "strings" ) // GitRelWorkdir returns git relative workdir of current directory. +// +// It should return the same output as `git rev-parse --show-prefix`. +// It does not execute `git` command to avoid needless git binary dependency. +// +// An example problem due to `git` command dependency: https://github.com/reviewdog/reviewdog/issues/1158 func GitRelWorkdir() (string, error) { - b, err := exec.Command("git", "rev-parse", "--show-prefix").Output() + cwd, err := os.Getwd() if err != nil { - return "", fmt.Errorf("failed to run 'git rev-parse --show-prefix': %w", err) + return "", err } - return strings.Trim(string(b), "\n"), nil + root, err := findGitRoot(cwd) + if err != nil { + return "", err + } + if !strings.HasPrefix(cwd, root) { + return "", fmt.Errorf("cannot get GitRelWorkdir: cwd=%q, root=%q", cwd, root) + } + const separator = string(filepath.Separator) + path := strings.Trim(strings.TrimPrefix(cwd, root), separator) + if path != "" { + path += separator + } + return path, nil +} + +func findGitRoot(path string) (string, error) { + gitPath, err := findDotGitPath(path) + if err != nil { + return "", err + } + return filepath.Dir(gitPath), nil +} + +func findDotGitPath(path string) (string, error) { + // normalize the path + path, err := filepath.Abs(path) + if err != nil { + return "", err + } + + for { + fi, err := os.Stat(filepath.Join(path, ".git")) + if err == nil { + if !fi.IsDir() { + return "", fmt.Errorf(".git exist but is not a directory") + } + return filepath.Join(path, ".git"), nil + } + if !os.IsNotExist(err) { + // unknown error + return "", err + } + + // detect bare repo + ok, err := isGitDir(path) + if err != nil { + return "", err + } + if ok { + return path, nil + } + + parent := filepath.Dir(path) + if parent == path { + return "", fmt.Errorf(".git not found") + } + path = parent + } +} + +// ref: https://github.com/git/git/blob/3bab5d56259722843359702bc27111475437ad2a/setup.c#L328-L338 +func isGitDir(path string) (bool, error) { + markers := []string{"HEAD", "objects", "refs"} + for _, marker := range markers { + _, err := os.Stat(filepath.Join(path, marker)) + if err == nil { + continue + } + if !os.IsNotExist(err) { + // unknown error + return false, err + } + return false, nil + } + return true, nil }