2025-01-05 06:30:54 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2025-01-05 07:55:11 +01:00
|
|
|
r := &Roast{
|
2025-01-05 06:30:54 +01:00
|
|
|
repo: roastRepo,
|
|
|
|
dir: p,
|
2025-01-05 07:55:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return r, r.Pull()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Roast) Pull() error {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
|
|
defer cancel()
|
|
|
|
log.Debugf("git pull --force\n")
|
|
|
|
pullCmd := exec.CommandContext(ctx, "git", "pull", "--force")
|
|
|
|
pullCmd.Dir = r.dir
|
|
|
|
pullCmd.Stdin = os.Stdin
|
|
|
|
pullCmd.Stdout = os.Stdout
|
|
|
|
pullCmd.Stderr = os.Stderr
|
|
|
|
return pullCmd.Run()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Roast) AddAll() error {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
log.Debugf("git add .\n")
|
|
|
|
addCmd := exec.CommandContext(ctx, "git", "add", ".")
|
|
|
|
addCmd.Dir = r.dir
|
|
|
|
return addCmd.Run()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Roast) Commit(m string) error {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
|
|
defer cancel()
|
|
|
|
log.Debugf("git commit\n")
|
|
|
|
commitCmd := exec.CommandContext(ctx, "git", "commit", "-m", m)
|
|
|
|
commitCmd.Dir = r.dir
|
|
|
|
commitCmd.Stdin = os.Stdin
|
|
|
|
commitCmd.Stdout = os.Stdout
|
|
|
|
commitCmd.Stderr = os.Stderr
|
|
|
|
return commitCmd.Run()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Roast) Push() error {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
|
|
defer cancel()
|
|
|
|
log.Debugf("git push --atomic\n")
|
|
|
|
pushCmd := exec.CommandContext(ctx, "git", "push", "--atomic")
|
|
|
|
pushCmd.Dir = r.dir
|
|
|
|
pushCmd.Stdin = os.Stdin
|
|
|
|
pushCmd.Stdout = os.Stdout
|
|
|
|
pushCmd.Stderr = os.Stderr
|
|
|
|
return pushCmd.Run()
|
2025-01-05 06:30:54 +01:00
|
|
|
}
|