server/admin/sync/importer_git.go

215 lines
4.7 KiB
Go

//go:build gitgo
// +build gitgo
package sync
import (
"io/ioutil"
"log"
"os"
"path"
"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/go-git/go-git/v5/plumbing/transport/ssh"
)
// GitImporter implements an Importer, where files to imports are located
// inside a local directory from your filesystem, backed by git.
type GitImporter struct {
li LocalImporter
Remote string
Branch string
Auth ssh.AuthMethod
}
func NewGitImporter(li LocalImporter, remote string, branch string) GitImporter {
var auth ssh.AuthMethod
// If there is no ssh-agent setup, try to use a default ssh key
if _, exists := os.LookupEnv("SSH_AUTH_SOCK"); !exists {
if home, exists := os.LookupEnv("HOME"); exists {
for _, d := range []string{"id_fic", "id_ed25519", "id_rsa"} {
pemBytes, err := ioutil.ReadFile(path.Join(home, ".ssh", d))
if err == nil {
log.Println("[GitImporter] Using", path.Join(home, ".ssh", d), "as ssh key to sync repository")
auth, err = ssh.NewPublicKeys("git", pemBytes, "")
if ccfg, err := auth.ClientConfig(); err != nil {
if hkc, err := ssh.NewKnownHostsCallback(); err != nil {
ccfg.HostKeyCallback = hkc
}
}
break
}
}
}
}
return GitImporter{
li: li,
Remote: remote,
Branch: branch,
Auth: auth,
}
}
func (i GitImporter) Id() *string {
var gitinfo string
r, err := git.PlainOpen(i.li.Base)
if err == nil {
ref, err := r.Head()
if err == nil {
gitinfo = ref.Hash().String()
}
}
return &gitinfo
}
func (i GitImporter) Init() error {
// Check if the directory exists, create it if needed
if err := i.li.Init(); err != nil {
return err
}
// If the directory is empty, clone it
if n, err := countFileInDir(i.li.Base); err != nil {
return err
} else if n == 0 {
_, err = git.PlainClone(i.li.Base, false, &git.CloneOptions{
URL: i.Remote,
ReferenceName: plumbing.ReferenceName(i.Branch),
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
Auth: i.Auth,
})
if err != nil {
return err
}
}
// Check if the .git directory exists, change the origin remote to our
r, err := git.PlainOpen(i.li.Base)
if err != nil {
return err
}
r.DeleteRemote("origin")
_, err = r.CreateRemote(&config.RemoteConfig{
Name: "origin",
URLs: []string{i.Remote},
})
if err != nil {
return err
}
return nil
}
func (i GitImporter) Sync() error {
oneGitPull.Lock()
defer oneGitPull.Unlock()
r, err := git.PlainOpen(i.li.Base)
if err != nil {
return err
}
w, err := r.Worktree()
if err != nil {
return err
}
// Perform a git pull --rebase origin/master
err = w.Pull(&git.PullOptions{
RemoteName: "origin",
ReferenceName: plumbing.ReferenceName(i.Branch),
Depth: 1,
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
Force: true,
Auth: i.Auth,
})
return err
}
func (i GitImporter) GetSubmodules() ([]GitSubmoduleStatus, error) {
oneGitPull.Lock()
defer oneGitPull.Unlock()
r, err := git.PlainOpen(i.li.Base)
if err != nil {
return nil, err
}
w, err := r.Worktree()
if err != nil {
return nil, err
}
modules, err := w.Submodules()
var ret []GitSubmoduleStatus
for _, mod := range modules {
st, err := mod.Status()
if err == nil {
ret = append(ret, GitSubmoduleStatus{
Hash: st.Expected.String(),
Path: st.Path,
Branch: strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(st.Branch.String(), "("), "refs/"), "heads/"), ")"),
})
}
}
return ret, err
}
func (i GitImporter) GetSubmodule(repopath string) (*GitSubmoduleStatus, error) {
oneGitPull.Lock()
defer oneGitPull.Unlock()
r, err := git.PlainOpen(path.Join(i.li.Base, repopath))
if err != nil {
return nil, err
}
st, err := r.Head()
if err != nil {
return nil, err
}
return &GitSubmoduleStatus{
Hash: st.Hash().String(),
Path: repopath,
Branch: st.Name().Short(),
}, nil
}
func (i GitImporter) IsRepositoryUptodate(repopath string) (*GitSubmoduleStatus, error) {
oneGitPull.Lock()
defer oneGitPull.Unlock()
r, err := git.PlainOpen(path.Join(i.li.Base, repopath))
if err != nil {
return nil, err
}
// Perform a git pull --rebase origin/master
err = r.Fetch(&git.FetchOptions{
RemoteName: "origin",
RefSpecs: []config.RefSpec{config.RefSpec("+refs/heads/" + i.Branch + ":refs/remotes/origin/" + i.Branch)},
Auth: i.Auth,
})
st, err := r.Reference(plumbing.ReferenceName("origin/"+i.Branch), true)
if err != nil {
return nil, err
}
return &GitSubmoduleStatus{
Hash: st.Hash().String(),
Path: repopath,
Branch: st.Name().Short(),
}, nil
}