mangalaman93/giggle

View on GitHub
svc/repo.go

Summary

Maintainability
A
0 mins
Test Coverage
package svc

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/config"
    "github.com/mangalaman93/giggle/conf"
)

func performSync(ctx context.Context, cf *conf.Config) error {
    log.Println("[INFO] periodic sync begin")
    defer log.Println("[INFO] periodic sync end")

    for _, sc := range cf.Sync {
        log.Printf("[INFO] syncing %v\n", sc.Name)
        if err := syncRepo(ctx, sc, cf.Auth); err != nil {
            log.Printf("[WARN] error syncing %v :: %v\n", sc.Name, err)
            continue
        }

        log.Printf("[INFO] synced %v\n", sc.Name)
    }

    return nil
}

func syncRepo(ctx context.Context, sc conf.SyncConfig,
    authMap map[string]*conf.AuthMethod) error {

    repoFolder := conf.GetSyncTarget(sc.Name)
    fromAuth := authMap[sc.From.AuthToUse]
    fromRepo, err := openRepo(ctx, sc.From, fromAuth, repoFolder)
    if err != nil {
        return err
    }

    fromRemote, err := fromRepo.Remote(sc.From.Name)
    if err != nil {
        return err
    }

    if err := fetch(ctx, fromRemote, fromAuth); err != nil {
        return err
    }

    var errRet error
    for _, to := range sc.ToList {
        toAuth := authMap[to.AuthToUse]
        toRemote, err := createRemote(fromRepo, to.Name, to.URLToRepo)
        if err != nil {
            log.Printf("[WARN] error creating remote in: %v :: %v\n", sc.From.Name, err)
            errRet = err
            continue
        }

        if err := push(ctx, fromRemote, toRemote, toAuth); err != nil {
            log.Printf("[WARN] error syncing to repo: %v :: %v\n", to.Name, err)
            errRet = err
            continue
        }
    }

    return errRet
}

// openRepo opens the git repo and returns an instance to it.
// If the repository doesn't exist, it would clone the repo first.
func openRepo(ctx context.Context, cr conf.Repo, auth *conf.AuthMethod, folder string) (
    *git.Repository, error) {

    if _, errExist := os.Stat(folder); os.IsNotExist(errExist) {
        repo, err := git.PlainCloneContext(ctx, folder, false, &git.CloneOptions{
            URL:        cr.URLToRepo,
            RemoteName: cr.Name,
            Auth:       auth.GetAuth(),
        })
        if err != nil {
            return nil, fmt.Errorf("error cloning the repo [%v] :: %w", cr.Name, err)
        }

        return repo, nil
    }

    repo, err := git.PlainOpen(folder)
    if err != nil {
        return nil, fmt.Errorf("error opening the repo [%v] :: %w", cr.Name, err)
    }

    return repo, nil
}

// createRemote creates a remote in a repository. It would first try to find whether
// the given remote exists. If it does, it will ensure the URL is correctly updated.
// In case it doesn't, it will create a brand new remote in the repository.
func createRemote(repo *git.Repository, name, url string) (*git.Remote, error) {
    remote, err := repo.Remote(name)
    if err == nil {
        remoteURLs := remote.Config().URLs
        if len(remoteURLs) != 1 || remoteURLs[0] != url {
            remote.Config().URLs = []string{url}
        }

        return remote, nil
    } else if err != nil && err != git.ErrRemoteNotFound {
        return nil, fmt.Errorf("error finding remote [%v] :: %w", name, err)
    }

    c := &config.RemoteConfig{
        Name: name,
        URLs: []string{url},
    }
    if remote, err = repo.CreateRemote(c); err != nil {
        return nil, fmt.Errorf("error creating remote [%v] :: %w", name, err)
    }

    return remote, nil
}

// fetch fetches from provided remote.
func fetch(ctx context.Context, from *git.Remote, am *conf.AuthMethod) error {
    err := from.FetchContext(ctx, &git.FetchOptions{Auth: am.GetAuth()})
    if err != nil && err != git.NoErrAlreadyUpToDate {
        return fmt.Errorf("error fetching from [%v] :: %w", from.Config().Name, err)
    }

    return nil
}

// push pushes from the `from` remote to the `to` remote.
// `auth` is authentication for `to` auth.
func push(ctx context.Context, from, to *git.Remote, am *conf.AuthMethod) error {
    o := &git.PushOptions{
        RemoteName: to.Config().Name,
        Auth:       am.GetAuth(),
        RefSpecs: []config.RefSpec{
            config.RefSpec(fmt.Sprintf("refs/remotes/%v/master:refs/heads/master", from.Config().Name)),
        },
    }
    if err := to.PushContext(ctx, o); err != nil && err != git.NoErrAlreadyUpToDate {
        return fmt.Errorf("error pushing [%v] :: %w", o.RefSpecs, err)
    }

    return nil
}