350 lines
10 KiB
Go
350 lines
10 KiB
Go
//go:build !gitgo
|
|
// +build !gitgo
|
|
|
|
package sync
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"strings"
|
|
|
|
"srs.epita.fr/fic-server/libfic"
|
|
)
|
|
|
|
// GitImporter implements an Importer, where files to imports are located
|
|
// inside a local directory from your filesystem, backed by git (binary).
|
|
type GitImporter struct {
|
|
li LocalImporter
|
|
Remote string
|
|
Branch string
|
|
}
|
|
|
|
func NewGitImporter(li LocalImporter, remote string, branch string) GitImporter {
|
|
if len(branch) == 0 {
|
|
branch = "master"
|
|
}
|
|
|
|
return GitImporter{
|
|
li: li,
|
|
Remote: remote,
|
|
Branch: branch,
|
|
}
|
|
}
|
|
|
|
func (i GitImporter) Id() *string {
|
|
cmdshow := exec.Command("git", "-C", i.li.Base, "show")
|
|
var outshow bytes.Buffer
|
|
cmdshow.Stdout = &outshow
|
|
err := cmdshow.Run()
|
|
|
|
var commit string
|
|
if err != nil {
|
|
commit = fmt.Sprintf("error (%s)", err.Error())
|
|
} else {
|
|
commit, err = outshow.ReadString('\n')
|
|
if err == nil {
|
|
commit = strings.TrimPrefix(commit, "commit ")
|
|
} else {
|
|
commit = fmt.Sprintf("error (%s)", err.Error())
|
|
}
|
|
}
|
|
|
|
return &commit
|
|
}
|
|
|
|
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 {
|
|
args := []string{"clone", "--recursive", "--depth", "1"}
|
|
if i.Branch != "" {
|
|
args = append(args, "-b", i.Branch)
|
|
}
|
|
args = append(args, "--shallow-submodules", i.Remote, i.li.Base)
|
|
|
|
log.Println("Please wait while creating the local git repository...")
|
|
cmdclone := exec.Command("git", args...)
|
|
stdout, err := cmdclone.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
log.Println("Local git repository successfully cloned")
|
|
} else if _, err := os.Stat(path.Join(i.li.Base, ".git")); errors.Is(err, os.ErrNotExist) {
|
|
log.Println("[ERR] ", i.li.Base, " is not a valid git repository and it cannot be initialized because it's not empty.")
|
|
return nil
|
|
}
|
|
|
|
// Check if the .git directory exists, change the origin remote to our
|
|
cmdremote := exec.Command("git", "-C", i.li.Base, "remote", "set-url", "origin", i.Remote)
|
|
stdout, err := cmdremote.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (i GitImporter) Sync() error {
|
|
oneGitPull.Lock()
|
|
defer oneGitPull.Unlock()
|
|
|
|
log.Println("Synchronizing local git repository...")
|
|
cmdfetch := exec.Command("git", "-C", i.li.Base, "fetch", "origin")
|
|
stdout, err := cmdfetch.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Git repository fetch failed: %s\n%s", err, stdout)
|
|
return fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
|
|
cmdclean := exec.Command("git", "-C", i.li.Base, "clean", "-xfde", "*_MERGED")
|
|
stdout, err = cmdclean.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Local git repository clean failed: %s\n%s", err, stdout)
|
|
return fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
|
|
if _, err := os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
|
|
// We have submodules, clean it
|
|
cmdsubclean := exec.Command("git", "-C", i.li.Base, "submodule", "foreach", "--recursive", "git", "clean", "-xfde", "*_MERGED")
|
|
stdout, err = cmdsubclean.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Local git repository submodules clean failed: %s\n%s", err, stdout)
|
|
return fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
|
|
// Start by a light hard reset (without submodules, in order to init new ones)
|
|
cmdreset := exec.Command("git", "-C", i.li.Base, "reset", "--hard", "origin/"+i.Branch)
|
|
stdout, err = cmdreset.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Local git repository reset failed: %s\n%s", err, stdout)
|
|
return fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
|
|
cmdsubinit := exec.Command("git", "-C", i.li.Base, "submodule", "init")
|
|
stdout, err = cmdsubinit.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Local git repository submodule init failed: %s\n%s", err, stdout)
|
|
return fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
|
|
cmdsubupdate := exec.Command("git", "-C", i.li.Base, "submodule", "update")
|
|
stdout, err = cmdsubupdate.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Local git repository submodule update failed: %s\n%s", err, stdout)
|
|
}
|
|
}
|
|
|
|
cmdreset := exec.Command("git", "-C", i.li.Base, "reset", "--hard", "--recurse-submodule", "origin/"+i.Branch)
|
|
stdout, err = cmdreset.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Local git repository reset failed: %s\n%s", err, stdout)
|
|
return fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
|
|
if _, err := os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
|
|
// Treat submodules
|
|
cmdsublfs := exec.Command("git", "-C", i.li.Base, "submodule", "foreach", "--recursive", "git", "lfs", "pull")
|
|
stdout, err = cmdsublfs.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Local LFS synchronization failed: %s\n%s", err, stdout)
|
|
return fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
} else {
|
|
cmdlfs := exec.Command("git", "-C", i.li.Base, "lfs", "pull")
|
|
stdout, err = cmdlfs.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Local LFS synchronization failed: %s\n%s", err, stdout)
|
|
return fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
}
|
|
|
|
log.Println("Local git repository synchronized successfully")
|
|
return nil
|
|
}
|
|
|
|
func (i GitImporter) GetThemeLink(th *fic.Theme) (u *url.URL, err error) {
|
|
prefix := ""
|
|
|
|
if _, err = os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
|
|
thdir := path.Join(i.li.Base, th.Path)
|
|
cmdremote := exec.Command("git", "-C", thdir, "remote", "get-url", "origin")
|
|
var stdout []byte
|
|
stdout, err = cmdremote.CombinedOutput()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
u, err = getForgeBaseLink(string(bytes.TrimSpace(stdout)))
|
|
|
|
// Search .git directory
|
|
for {
|
|
if _, err = os.Stat(path.Join(thdir, ".git")); !errors.Is(err, os.ErrNotExist) {
|
|
break
|
|
}
|
|
|
|
thdir, _ = path.Split(thdir)
|
|
}
|
|
prefix = strings.TrimPrefix(thdir, i.li.Base)
|
|
} else {
|
|
u, err = getForgeBaseLink(i.Remote)
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
u.Path = path.Join(u.Path, "-", "tree", i.Branch, strings.TrimPrefix(th.Path, prefix))
|
|
|
|
return
|
|
}
|
|
|
|
func (i GitImporter) GetExerciceLink(e *fic.Exercice) (u *url.URL, err error) {
|
|
prefix := ""
|
|
|
|
if _, err = os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
|
|
exdir := path.Join(i.li.Base, e.Path)
|
|
cmdremote := exec.Command("git", "-C", exdir, "remote", "get-url", "origin")
|
|
var stdout []byte
|
|
stdout, err = cmdremote.CombinedOutput()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
u, err = getForgeBaseLink(string(bytes.TrimSpace(stdout)))
|
|
|
|
// Search .git directory
|
|
for {
|
|
if _, err = os.Stat(path.Join(exdir, ".git")); !errors.Is(err, os.ErrNotExist) {
|
|
break
|
|
}
|
|
|
|
exdir, _ = path.Split(exdir)
|
|
}
|
|
prefix = strings.TrimPrefix(exdir, i.li.Base)
|
|
} else {
|
|
u, err = getForgeBaseLink(i.Remote)
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
u.Path = path.Join(u.Path, "-", "tree", i.Branch, strings.TrimPrefix(e.Path, prefix))
|
|
|
|
return
|
|
}
|
|
|
|
func (i GitImporter) GetSubmodules() ([]GitSubmoduleStatus, error) {
|
|
oneGitPull.Lock()
|
|
defer oneGitPull.Unlock()
|
|
|
|
cmdsubmodule := exec.Command("git", "-C", i.li.Base, "submodule", "status")
|
|
stdout, err := cmdsubmodule.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Git repository submodule failed: %s\n%s", err, stdout)
|
|
return nil, fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
|
|
var ret []GitSubmoduleStatus
|
|
for _, line := range strings.Split(string(stdout), "\n") {
|
|
flds := strings.Fields(line)
|
|
if len(flds) == 3 {
|
|
ret = append(ret, GitSubmoduleStatus{
|
|
Hash: flds[0],
|
|
Path: flds[1],
|
|
Branch: strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(flds[2], "("), "refs/"), "remotes/"), "heads/"), "origin/"), ")"),
|
|
})
|
|
}
|
|
}
|
|
|
|
return ret, err
|
|
}
|
|
|
|
func (i GitImporter) GetSubmodule(repopath string) (*GitSubmoduleStatus, error) {
|
|
oneGitPull.Lock()
|
|
defer oneGitPull.Unlock()
|
|
|
|
if repopath == "" {
|
|
cmdsubmodule := exec.Command("git", "-C", i.li.Base, "show", "-q", "--oneline")
|
|
stdout, err := cmdsubmodule.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Git repository show failed: %s\n%s", err, stdout)
|
|
return nil, fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
|
|
flds := strings.SplitN(string(stdout), " ", 2)
|
|
return &GitSubmoduleStatus{
|
|
Hash: flds[0],
|
|
Text: strings.TrimSpace(flds[1]),
|
|
Path: "",
|
|
Branch: i.Branch,
|
|
}, nil
|
|
} else {
|
|
cmdsubmodule := exec.Command("git", "-C", i.li.Base, "submodule", "status", repopath)
|
|
stdout, err := cmdsubmodule.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Git repository submodule failed: %s\n%s", err, stdout)
|
|
return nil, fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
|
|
flds := strings.Fields(strings.TrimSpace(string(stdout)))
|
|
if len(flds) == 3 {
|
|
return &GitSubmoduleStatus{
|
|
Hash: flds[0],
|
|
Path: flds[1],
|
|
Branch: strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(flds[2], "("), "refs/"), "remotes/"), "heads/"), "origin/"), ")"),
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("Unable to parse")
|
|
}
|
|
|
|
func (i GitImporter) IsRepositoryUptodate(repopath string) (*GitSubmoduleStatus, error) {
|
|
oneGitPull.Lock()
|
|
defer oneGitPull.Unlock()
|
|
|
|
cmdsubmodule := exec.Command("git", "-C", path.Join(i.li.Base, repopath), "fetch", "origin", i.Branch)
|
|
stdout, err := cmdsubmodule.CombinedOutput()
|
|
if err != nil {
|
|
log.Printf("Git repository submodule fetch failed: %s\n%s", err, stdout)
|
|
return nil, fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
|
|
cmdsubmodule = exec.Command("git", "-C", path.Join(i.li.Base, repopath), "show", "-q", "--oneline", "origin/"+i.Branch)
|
|
stdout, err = cmdsubmodule.CombinedOutput()
|
|
if err != nil {
|
|
cmdconfig := exec.Command("git", "-C", path.Join(i.li.Base, repopath), "config", "remote.origin.fetch")
|
|
if cfg, err2 := cmdconfig.CombinedOutput(); err2 == nil && !strings.Contains(string(cfg), "+refs/heads/*:refs/remotes/origin/*") {
|
|
cmdsubmodule := exec.Command("git", "-C", path.Join(i.li.Base, repopath), "config", "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*")
|
|
if stdout, err = cmdsubmodule.CombinedOutput(); err != nil {
|
|
log.Printf("Git repository submodule config failed: %s\n%s", err, stdout)
|
|
return nil, fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
} else {
|
|
log.Printf("Git repository submodule status failed: %s\n%s", err, stdout)
|
|
return nil, fmt.Errorf("%w:\n%s", err, stdout)
|
|
}
|
|
}
|
|
|
|
flds := strings.SplitN(string(stdout), " ", 2)
|
|
return &GitSubmoduleStatus{
|
|
Hash: flds[0],
|
|
Text: strings.TrimSpace(flds[1]),
|
|
Path: repopath,
|
|
Branch: i.Branch,
|
|
}, nil
|
|
}
|