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.ABRA_DIR, "apps", 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(recipeName string) (bool, error) { recipeDir := path.Join(config.ABRA_DIR, "apps", recipeName) repo, err := git.PlainOpen(recipeDir) 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 for %s repository: %s", recipeName, status.String()) } else { logrus.Debugf("discovered clean git status for %s repository", recipeName) } return status.IsClean(), nil } // GetExcludesFiles reads the exlude files from a global git ignore 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 git ignore 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 ignore 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 }