83 lines
2.1 KiB
Go
83 lines
2.1 KiB
Go
package sync
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
|
|
git "github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
|
)
|
|
|
|
// SyncRepo ensures the repository at workdir matches the remote url. It
|
|
// performs a shallow clone when the directory does not exist, otherwise a
|
|
// fetch and reset. The returned string is the current HEAD commit hash.
|
|
func SyncRepo(ctx context.Context, url, workdir string) (string, error) {
|
|
attempt := func() (string, error) {
|
|
if _, err := os.Stat(workdir); os.IsNotExist(err) {
|
|
// shallow clone
|
|
_, err := git.PlainCloneContext(ctx, workdir, false, &git.CloneOptions{
|
|
URL: url,
|
|
Depth: 1,
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
} else {
|
|
r, err := git.PlainOpen(workdir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// fetch
|
|
if err := r.FetchContext(ctx, &git.FetchOptions{Depth: 1}); err != nil && err != git.NoErrAlreadyUpToDate {
|
|
return "", err
|
|
}
|
|
// reset to origin/HEAD
|
|
head, err := r.ResolveRevision(plumbing.Revision("origin/HEAD"))
|
|
if err != nil {
|
|
// fallback to master/main
|
|
head, err = r.ResolveRevision(plumbing.Revision("origin/main"))
|
|
if err != nil {
|
|
head, err = r.ResolveRevision(plumbing.Revision("origin/master"))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
}
|
|
w, err := r.Worktree()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := w.Reset(&git.ResetOptions{Mode: git.HardReset, Commit: *head}); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
r, err := git.PlainOpen(workdir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
ref, err := r.Head()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return ref.Hash().String(), nil
|
|
}
|
|
|
|
if hash, err := attempt(); err == nil {
|
|
return hash, nil
|
|
}
|
|
_ = os.RemoveAll(workdir)
|
|
return attempt()
|
|
}
|
|
|
|
// WithAuth returns CloneOptions with basic auth if username/token provided in URL.
|
|
// This is a helper for future extension; currently unused.
|
|
func WithAuth(url, token string) *git.CloneOptions {
|
|
opts := &git.CloneOptions{URL: url, Depth: 1}
|
|
if token != "" {
|
|
opts.Auth = &http.BasicAuth{Username: "token", Password: token}
|
|
}
|
|
return opts
|
|
}
|