//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.Fatal(i.li.Base, " is not a valid git repository and it cannot be initialized because it's not empty.") } // 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) } } 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", "main", 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", "main", strings.TrimPrefix(e.Path, prefix)) return }