package git import ( "fmt" "os" "path/filepath" "strings" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/sirupsen/logrus" ) // Clone runs a git clone which accounts for different default branches. func Clone(dir, url string) error { if _, err := os.Stat(dir); os.IsNotExist(err) { logrus.Debugf("'%s' does not exist, attempting to git clone from '%s'", dir, url) _, err := git.PlainClone(dir, false, &git.CloneOptions{URL: url, Tags: git.AllTags}) if err != nil { logrus.Debugf("cloning '%s' default branch failed, attempting from main branch", url) _, err := git.PlainClone(dir, false, &git.CloneOptions{ URL: url, Tags: git.AllTags, ReferenceName: plumbing.ReferenceName("refs/heads/main"), }) if err != nil { if strings.Contains(err.Error(), "authentication required") { name := filepath.Base(dir) return fmt.Errorf("unable to clone %s, does %s exist?", name, url) } return err } } logrus.Debugf("'%s' has been git cloned successfully", dir) } else { logrus.Debugf("'%s' already exists, doing nothing", dir) } return nil } // EnsureUpToDate ensures that a git repo on disk has the latest changes (git-fetch). func EnsureUpToDate(dir string) error { repo, err := git.PlainOpen(dir) if err != nil { return err } recipeName := filepath.Base(dir) isClean, err := IsClean(recipeName) if err != nil { return err } if !isClean { return fmt.Errorf("'%s' has locally unstaged changes", recipeName) } branch := "master" if _, err := repo.Branch("master"); err != nil { if _, err := repo.Branch("main"); err != nil { logrus.Debugf("failed to select branch in '%s'", dir) return err } branch = "main" } logrus.Debugf("choosing '%s' as main git branch in '%s'", branch, dir) worktree, err := repo.Worktree() if err != nil { return err } refName := fmt.Sprintf("refs/heads/%s", branch) checkOutOpts := &git.CheckoutOptions{ Create: false, Force: true, Branch: plumbing.ReferenceName(refName), } if err := worktree.Checkout(checkOutOpts); err != nil { logrus.Debugf("failed to check out '%s' in '%s'", refName, dir) return err } logrus.Debugf("successfully checked out '%s' in '%s'", branch, dir) remote, err := repo.Remote("origin") if err != nil { return err } fetchOpts := &git.FetchOptions{ RemoteName: "origin", RefSpecs: []config.RefSpec{"refs/heads/*:refs/remotes/origin/*"}, Force: true, } if err := remote.Fetch(fetchOpts); err != nil { if !strings.Contains(err.Error(), "already up-to-date") { return err } } logrus.Debugf("successfully fetched all changes in '%s'", dir) return nil }