package git import ( "io/ioutil" "os" "os/user" "path" "path/filepath" "strings" "coopcloud.tech/abra/pkg/config" "github.com/go-git/go-git/v5" gitConfigPkg "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/sirupsen/logrus" ) // GetRecipeHead retrieves latest HEAD metadata. func GetRecipeHead(recipeName string) (*plumbing.Reference, error) { recipeDir := path.Join(config.RECIPES_DIR, recipeName) repo, err := git.PlainOpen(recipeDir) if err != nil { return nil, err } head, err := repo.Head() if err != nil { return nil, err } return head, nil } // IsClean checks if a repo has unstaged changes func IsClean(repoPath string) (bool, error) { repo, err := git.PlainOpen(repoPath) if err != nil { return false, err } worktree, err := repo.Worktree() if err != nil { return false, 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, err } if status.String() != "" { logrus.Debugf("discovered git status in %s: %s", repoPath, status.String()) } else { logrus.Debugf("discovered clean git status in %s", 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) { logrus.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) { logrus.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)) } } logrus.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 }