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  }