forked from toolshed/abra
		
	
		
			
				
	
	
		
			175 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			175 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package git
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"os/user"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 
 | |
| 	"coopcloud.tech/abra/pkg/log"
 | |
| 	"github.com/go-git/go-git/v5"
 | |
| 	gitConfigPkg "github.com/go-git/go-git/v5/config"
 | |
| 	"github.com/go-git/go-git/v5/plumbing/format/gitignore"
 | |
| )
 | |
| 
 | |
| // IsClean checks if a repo has unstaged changes
 | |
| func IsClean(repoPath string) (bool, error) {
 | |
| 	repo, err := git.PlainOpen(repoPath)
 | |
| 	if err != nil {
 | |
| 		if errors.Is(err, git.ErrRepositoryNotExists) {
 | |
| 			return false, git.ErrRepositoryNotExists
 | |
| 		}
 | |
| 
 | |
| 		return false, fmt.Errorf("unable to open %s: %s", repoPath, err)
 | |
| 	}
 | |
| 
 | |
| 	worktree, err := repo.Worktree()
 | |
| 	if err != nil {
 | |
| 		return false, fmt.Errorf("unable to open worktree of %s: %s", repoPath, err)
 | |
| 	}
 | |
| 
 | |
| 	patterns, err := GetExcludesFiles()
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 
 | |
| 	if len(patterns) > 0 {
 | |
| 		worktree.Excludes = append(patterns, worktree.Excludes...)
 | |
| 	}
 | |
| 
 | |
| 	status, err := worktree.Status()
 | |
| 	if err != nil {
 | |
| 		return false, fmt.Errorf("unable to query status of %s: %s", repoPath, err)
 | |
| 	}
 | |
| 
 | |
| 	if status.String() != "" {
 | |
| 		noNewline := strings.TrimSuffix(status.String(), "\n")
 | |
| 		log.Debugf("git status: %s: %s", repoPath, noNewline)
 | |
| 	} else {
 | |
| 		log.Debugf("git status: %s: clean", repoPath)
 | |
| 	}
 | |
| 
 | |
| 	return status.IsClean(), nil
 | |
| }
 | |
| 
 | |
| // GetExcludesFiles reads the exlude files from a global gitignore
 | |
| func GetExcludesFiles() ([]gitignore.Pattern, error) {
 | |
| 	var err error
 | |
| 	var patterns []gitignore.Pattern
 | |
| 
 | |
| 	cfg, err := parseGitConfig()
 | |
| 	if err != nil {
 | |
| 		return patterns, err
 | |
| 	}
 | |
| 
 | |
| 	excludesfile := getExcludesFile(cfg)
 | |
| 	patterns, err = parseExcludesFile(excludesfile)
 | |
| 	if err != nil {
 | |
| 		return patterns, err
 | |
| 	}
 | |
| 
 | |
| 	return patterns, nil
 | |
| }
 | |
| 
 | |
| func parseGitConfig() (*gitConfigPkg.Config, error) {
 | |
| 	cfg := gitConfigPkg.NewConfig()
 | |
| 
 | |
| 	usr, err := user.Current()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	globalGitConfig := filepath.Join(usr.HomeDir, ".gitconfig")
 | |
| 	if _, err := os.Stat(globalGitConfig); err != nil {
 | |
| 		if os.IsNotExist(err) {
 | |
| 			log.Debugf("no %s exists, not reading any global gitignore config", globalGitConfig)
 | |
| 			return cfg, nil
 | |
| 		}
 | |
| 		return cfg, err
 | |
| 	}
 | |
| 
 | |
| 	b, err := ioutil.ReadFile(globalGitConfig)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if err := cfg.Unmarshal(b); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return cfg, err
 | |
| }
 | |
| 
 | |
| func getExcludesFile(cfg *gitConfigPkg.Config) string {
 | |
| 	for _, sec := range cfg.Raw.Sections {
 | |
| 		if sec.Name == "core" {
 | |
| 			for _, opt := range sec.Options {
 | |
| 				if opt.Key == "excludesfile" {
 | |
| 					return opt.Value
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return "~/.gitignore"
 | |
| }
 | |
| 
 | |
| func parseExcludesFile(excludesfile string) ([]gitignore.Pattern, error) {
 | |
| 	var ps []gitignore.Pattern
 | |
| 
 | |
| 	excludesfile, err := expandTilde(excludesfile)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if _, err := os.Stat(excludesfile); err != nil {
 | |
| 		if os.IsNotExist(err) {
 | |
| 			log.Debugf("no %s exists, skipping reading gitignore paths", excludesfile)
 | |
| 			return ps, nil
 | |
| 		}
 | |
| 		return ps, err
 | |
| 	}
 | |
| 
 | |
| 	data, err := ioutil.ReadFile(excludesfile)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	var pathsRaw []string
 | |
| 	for _, s := range strings.Split(string(data), "\n") {
 | |
| 		if !strings.HasPrefix(s, "#") && len(strings.TrimSpace(s)) > 0 {
 | |
| 			pathsRaw = append(pathsRaw, s)
 | |
| 			ps = append(ps, gitignore.ParsePattern(s, nil))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	log.Debugf("read global ignore paths: %s", strings.Join(pathsRaw, " "))
 | |
| 
 | |
| 	return ps, nil
 | |
| }
 | |
| 
 | |
| func expandTilde(path string) (string, error) {
 | |
| 	if !strings.HasPrefix(path, "~") {
 | |
| 		return path, nil
 | |
| 	}
 | |
| 
 | |
| 	var paths []string
 | |
| 	u, err := user.Current()
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	for _, p := range strings.Split(path, string(filepath.Separator)) {
 | |
| 		if p == "~" {
 | |
| 			paths = append(paths, u.HomeDir)
 | |
| 		} else {
 | |
| 			paths = append(paths, p)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return filepath.Join(paths...), nil
 | |
| }
 |