github.com/androidjp/reviewdog@v0.0.0-20220826111045-b4abb51cf85a/service/serviceutil/serviceutil.go (about) 1 package serviceutil 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 ) 9 10 // GitRelWorkdir returns git relative workdir of current directory. 11 // 12 // It should return the same output as `git rev-parse --show-prefix`. 13 // It does not execute `git` command to avoid needless git binary dependency. 14 // 15 // An example problem due to `git` command dependency: https://github.com/androidjp/reviewdog/issues/1158 16 func GitRelWorkdir() (string, error) { 17 cwd, err := os.Getwd() 18 if err != nil { 19 return "", err 20 } 21 root, err := findGitRoot(cwd) 22 if err != nil { 23 return "", err 24 } 25 if !strings.HasPrefix(cwd, root) { 26 return "", fmt.Errorf("cannot get GitRelWorkdir: cwd=%q, root=%q", cwd, root) 27 } 28 const separator = string(filepath.Separator) 29 path := strings.Trim(strings.TrimPrefix(cwd, root), separator) 30 if path != "" { 31 path += separator 32 } 33 return path, nil 34 } 35 36 func findGitRoot(path string) (string, error) { 37 gitPath, err := findDotGitPath(path) 38 if err != nil { 39 return "", err 40 } 41 return filepath.Dir(gitPath), nil 42 } 43 44 func findDotGitPath(path string) (string, error) { 45 // normalize the path 46 path, err := filepath.Abs(path) 47 if err != nil { 48 return "", err 49 } 50 51 for { 52 fi, err := os.Stat(filepath.Join(path, ".git")) 53 if err == nil { 54 if !fi.IsDir() { 55 return "", fmt.Errorf(".git exist but is not a directory") 56 } 57 return filepath.Join(path, ".git"), nil 58 } 59 if !os.IsNotExist(err) { 60 // unknown error 61 return "", err 62 } 63 64 // detect bare repo 65 ok, err := isGitDir(path) 66 if err != nil { 67 return "", err 68 } 69 if ok { 70 return path, nil 71 } 72 73 parent := filepath.Dir(path) 74 if parent == path { 75 return "", fmt.Errorf(".git not found") 76 } 77 path = parent 78 } 79 } 80 81 // ref: https://github.com/git/git/blob/3bab5d56259722843359702bc27111475437ad2a/setup.c#L328-L338 82 func isGitDir(path string) (bool, error) { 83 markers := []string{"HEAD", "objects", "refs"} 84 for _, marker := range markers { 85 _, err := os.Stat(filepath.Join(path, marker)) 86 if err == nil { 87 continue 88 } 89 if !os.IsNotExist(err) { 90 // unknown error 91 return false, err 92 } 93 return false, nil 94 } 95 return true, nil 96 }