2025-01-05 07:35:12 +01:00

183 lines
4.8 KiB
Go

package roast
import (
"context"
"errors"
"fmt"
"git-roast/internal/config"
"git-roast/internal/log"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/go-git/go-git/v5"
)
type Roast struct {
repo *git.Repository
dir string
}
func getRepoOrigin() (string, error) {
repo, err := git.PlainOpen(".")
if err != nil {
return "", errors.Join(errors.New("failed to open repository"), err)
}
origin, err := repo.Remote("origin")
if err != nil {
return "", errors.Join(errors.New("failed to open remote origin"), err)
}
originConfig := origin.Config()
if originConfig == nil {
return "", errors.New("failed to read origin config")
}
if len(originConfig.URLs) == 0 {
return "", errors.New("no urls in remote origin")
}
return originConfig.URLs[0], nil
}
func Init() error {
repoOriginUrl, err := getRepoOrigin()
if err != nil {
return err
}
id := url.PathEscape(repoOriginUrl)
p := filepath.Join(config.ContentPath, id)
initSuccessful := false
defer func() {
if !initSuccessful {
_ = os.RemoveAll(p)
}
}()
if err := os.RemoveAll(p); err != nil && !os.IsNotExist(err) {
return err
}
var roastRepoOriginUrl string
if s, hasSuffix := strings.CutSuffix(repoOriginUrl, ".git"); hasSuffix {
roastRepoOriginUrl = s + ".roast.git"
} else {
roastRepoOriginUrl = s + ".roast"
}
{
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd := exec.CommandContext(
ctx,
"git",
"clone",
roastRepoOriginUrl,
p,
)
if out, err := cmd.CombinedOutput(); err != nil {
log.Printf("failed to clone %s\n%s\n%s\n", roastRepoOriginUrl, out, err.Error())
} else {
log.Debugf("cloned %s\n", roastRepoOriginUrl)
repo, err := git.PlainOpen(p)
if err != nil {
return errors.Join(errors.New("failed to open roast repo"), err)
}
if commitIter, err := repo.CommitObjects(); err != nil {
return errors.Join(errors.New("failed to get commits for roast repo"), err)
} else if _, err := commitIter.Next(); err != nil {
// repo has no commits
// git commit --allow-empty -m init
cmd = exec.Command("git", "commit", "--allow-empty", "-m", "init")
cmd.Dir = p
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to add initial commit to roast repo\n%s\n", out), err)
}
// git push -u origin main
cmd = exec.Command("git", "push", "-u", "origin", "main")
cmd.Dir = p
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to push roast repo to %s\n%s\n", roastRepoOriginUrl, out), err)
}
log.Debugf("roast repository init pushed to %s\n", roastRepoOriginUrl)
}
initSuccessful = true
return nil
}
}
{
// git init
cmd := exec.Command("git", "init", p)
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to init %s\n%s\n", roastRepoOriginUrl, out), err)
}
log.Debugf("init %s successful\n", roastRepoOriginUrl)
// git remote add origin $roastRepoOriginUrl
cmd = exec.Command("git", "remote", "add", "origin", roastRepoOriginUrl)
cmd.Dir = p
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to add remote %s to roast repo\n%s\n", roastRepoOriginUrl, out), err)
}
// git commit --allow-empty -m init
cmd = exec.Command("git", "commit", "--allow-empty", "-m", "init")
cmd.Dir = p
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to add initial commit to roast repo\n%s\n", out), err)
}
// git push -u origin main
cmd = exec.Command("git", "push", "-u", "origin", "main")
cmd.Dir = p
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Join(fmt.Errorf("failed to push roast repo to %s\n%s\n", roastRepoOriginUrl, out), err)
}
log.Debugf("new roast repository pushed to %s\n", roastRepoOriginUrl)
}
initSuccessful = true
return nil
}
func Open() (*Roast, error) {
repoOriginUrl, err := getRepoOrigin()
if err != nil {
return nil, err
}
id := url.PathEscape(repoOriginUrl)
p := filepath.Join(config.ContentPath, id)
if stat, err := os.Stat(p); err != nil {
if os.IsNotExist(err) {
return nil, errors.New("roast repo not initialized, use `git roast init`")
} else {
return nil, errors.Join(errors.New("failed to check if roast dir for repo exists"), err)
}
} else if !stat.IsDir() {
return nil, errors.Join(errors.New("path for roast dir for repo exists but is not a directory"), err)
}
roastRepo, err := git.PlainOpen(p)
if err != nil {
return nil, errors.Join(fmt.Errorf("failed to open roast repo %s", p), err)
}
pull := exec.Command("git", "pull", "--force")
pull.Dir = p
if err := pull.Run(); err != nil {
return nil, errors.Join(fmt.Errorf("failed to pull roast repo %s", p), err)
}
return &Roast{
repo: roastRepo,
dir: p,
}, nil
}