pkgdash/internal/source/gitea/gitea.go
2024-02-14 23:03:30 +03:00

615 lines
20 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package gitea
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"strings"
"text/template"
"time"
"git.unistack.org/unistack-org/pkgdash/internal/configcli"
"git.unistack.org/unistack-org/pkgdash/internal/modules"
"github.com/go-git/go-git/v5"
gitconfig "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
httpauth "github.com/go-git/go-git/v5/plumbing/transport/http"
"go.unistack.org/micro/v4/logger"
)
type Gitea struct {
Token string
}
func NewGitea(t string) *Gitea {
return &Gitea{
Token: t,
}
}
type giteaPull struct {
URL string `json:"url"`
Title string `json:"title"`
Base struct {
Ref string `json:"ref"`
} `json:"base"`
ID int64 `json:"id"`
}
func (g *Gitea) RequestOpen(ctx context.Context, cfg *configcli.Config, branch string, path string, mod modules.Update) error {
logger.Debugf(ctx, "RequestOpen start, mod title: %s", path)
if cfg.Source == nil {
cfg.Source = &configcli.Source{
TypeGit: "gitea",
Token: os.Getenv("GITHUB_TOKEN"),
APIURL: os.Getenv("GITHUB_API_URL"),
Repository: os.Getenv("GITHUB_REPOSITORY"),
}
}
var buf []byte
var err error
// создания шаблона названия для пулл реквеста
tplTitle, err := template.New("pull_request_title").Parse(cfg.PullRequestTitle)
if err != nil {
logger.Fatalf(ctx, "failed to parse template: %v", err)
}
wTitle := bytes.NewBuffer(nil)
// создания шаблона тела для пулл реквеста
tplBody, err := template.New("pull_request_body").Parse(cfg.PullRequestBody)
if err != nil {
logger.Fatalf(ctx, "failed to parse template: %v", err)
}
wBody := bytes.NewBuffer(nil)
// открытие гит репозитория с опцией обхода репозитория для нахождения .git
repo, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{DetectDotGit: true})
if err != nil {
logger.Fatalf(ctx, "failed to open repo: %v", err)
}
//извлекаем ссылки с объектами из удаленного объекта??
if err = repo.FetchContext(ctx, &git.FetchOptions{
Auth: &httpauth.BasicAuth{Username: cfg.Source.Token, Password: cfg.Source.Token},
Force: true,
}); err != nil && err != git.NoErrAlreadyUpToDate {
logger.Fatalf(ctx, "failed to fetch repo: %v", err)
}
var headRef *plumbing.Reference // вроде ссылка на гит
refIter, err := repo.Branches() //получение веток
if err != nil {
logger.Fatalf(ctx, "failed to get branches: %v", err)
}
for {
ref, err := refIter.Next()
if err != nil {
break
}
if ref.Name().String() == branch {
headRef = ref
break
}
} //перебираем получение ветки и когда находим нужную выходим из цикла записав ветку в headRef
refIter.Close()
if headRef == nil {
logger.Fatalf(ctx, "failed to get repo branch head")
} // Не получили нужную ветку
logger.Infof(ctx, "repo head %s", headRef)
wtree, err := repo.Worktree()
if err != nil {
logger.Fatalf(ctx, "failed to get worktree: %v", err)
}
var pulls []*giteaPull
req, err := http.NewRequestWithContext(ctx, http.MethodGet, cfg.Source.APIURL+"/repos/"+cfg.Source.Repository+"/pulls?state=open&token="+cfg.Source.Token, nil)
if err != nil {
return err
} //вроде запроса к репозиторию
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
rsp, err := http.DefaultClient.Do(req) // выполнение запроса
if err != nil {
return err
}
buf, _ = io.ReadAll(rsp.Body)
if rsp.StatusCode != http.StatusOK {
return fmt.Errorf("unknown error: %s", buf)
}
if err = json.Unmarshal(buf, &pulls); err != nil {
logger.Fatalf(ctx, "failed to decode response %s err: %v", buf, err)
} // записываем ответ от гита по пулл реквестам, видимо существующим
// перебираем наши модификации и если они уже есть в гите удаляем их из mods
for _, pull := range pulls {
if strings.Contains(pull.Title, path) && pull.Base.Ref == branch {
logger.Infof(ctx, "skip %s as pr already exists %s", path, pull.URL)
return nil
}
}
wTitle.Reset()
wBody.Reset()
logger.Infof(ctx, "update %s from %s to %s", path, mod.Module.Version, mod.Version)
logger.Infof(ctx, "reset worktree")
if err = wtree.Reset(&git.ResetOptions{Mode: git.HardReset}); err != nil {
logger.Fatalf(ctx, "failed to reset repo branch: %v", err)
} //вроде меняем ветку
if err = wtree.PullContext(ctx, &git.PullOptions{
Auth: &httpauth.BasicAuth{Username: cfg.Source.Token, Password: cfg.Source.Token},
Depth: 1,
// RemoteURL :
Force: true,
RemoteName: "origin",
}); err != nil && err != git.NoErrAlreadyUpToDate {
logger.Fatalf(ctx, "failed to pull repo: %v", err)
}
logger.Infof(ctx, "checkout ref %s", headRef)
if err = wtree.Checkout(&git.CheckoutOptions{
Hash: headRef.Hash(),
Branch: plumbing.NewBranchReferenceName(fmt.Sprintf("pkgdash-1/go_modules/%s-%s", path, mod.Version)),
Create: true,
Force: true,
}); err != nil {
logger.Fatalf(ctx, "failed to checkout tree: %v", err)
} //вроде как переходим на другую ветку
epath, err := exec.LookPath("go")
if errors.Is(err, exec.ErrDot) {
err = nil
}
if err != nil {
logger.Fatalf(ctx, "failed to find go command: %v", err)
} // ищем go файл
var cmd *exec.Cmd
var out []byte
cmd = exec.CommandContext(ctx, epath, "mod", "edit", fmt.Sprintf("-require=%s@%s", path, mod.Version))
if out, err = cmd.CombinedOutput(); err != nil {
logger.Fatalf(ctx, "failed to run go mod edit: %s err: %v", out, err)
} // пытаемся выполнить команду go mod edit с новой версией модуля
cmd = exec.CommandContext(ctx, epath, "mod", "tidy")
if out, err = cmd.CombinedOutput(); err != nil {
logger.Fatalf(ctx, "failed to run go mod tidy: %s err: %v", out, err)
} // пытаемся выполнить команду go mod tidy пытаемся подтянуть новую версию модуля
logger.Infof(ctx, "worktree add go.mod")
if _, err = wtree.Add("go.mod"); err != nil {
logger.Fatalf(ctx, "failed to add file: %v", err)
}
logger.Infof(ctx, "worktree add go.sum")
if _, err = wtree.Add("go.sum"); err != nil {
logger.Fatalf(ctx, "failed to add file: %v", err)
}
logger.Infof(ctx, "worktree commit")
_, err = wtree.Commit(wTitle.String(), &git.CommitOptions{
Parents: []plumbing.Hash{headRef.Hash()},
Author: &object.Signature{
Name: "gitea-actions",
Email: "info@unistack.org",
When: time.Now(),
},
}) // хотим за коммитить изменения
if err != nil {
logger.Fatalf(ctx, "failed to commit: %v", err)
}
// newref := plumbing.NewHashReference(plumbing.ReferenceName(fmt.Sprintf("refs/heads/pkgdash-1/go_modules/%s-%s", path, mod.Version)), headRef.Hash())
/*
if err = repo.Storer.SetReference(newref); err != nil {
logger.Fatalf(ctx, "failed to create repo branch: %v", err)
}
*/
refspec := gitconfig.RefSpec(fmt.Sprintf("+refs/heads/pkgdash-1/go_modules/%s-%s:refs/heads/pkgdash-1/go_modules/%s-%s", path, mod.Version, path, mod.Version))
logger.Infof(ctx, "try to push refspec %s", refspec)
if err = repo.PushContext(ctx, &git.PushOptions{
RefSpecs: []gitconfig.RefSpec{refspec},
Auth: &httpauth.BasicAuth{Username: cfg.Source.Token, Password: cfg.Source.Token},
Force: true,
}); err != nil {
logger.Fatalf(ctx, "failed to push repo branch: %v", err)
} // пытаемся за пушить изменения
data := map[string]string{
"Name": path,
"VersionOld": mod.Module.Version,
"VersionNew": mod.Version,
}
if err = tplTitle.Execute(wTitle, data); err != nil {
logger.Fatalf(ctx, "failed to execute template: %v", err)
}
if err = tplBody.Execute(wBody, data); err != nil {
logger.Fatalf(ctx, "failed to execute template: %v", err)
}
body := map[string]string{
"base": branch,
"body": wBody.String(),
"head": fmt.Sprintf("pkgdash-1/go_modules/%s-%s", path, mod.Version),
"title": wTitle.String(),
}
logger.Infof(ctx, "raw body: %#+v", body)
buf, err = json.Marshal(body)
if err != nil {
return err
}
logger.Infof(ctx, "marshal body: %s", buf)
req, err = http.NewRequestWithContext(ctx, http.MethodPost, cfg.Source.APIURL+"/repos/"+cfg.Source.Repository+"/pulls?token="+cfg.Source.Token, bytes.NewReader(buf))
if err != nil {
return err
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
rsp, err = http.DefaultClient.Do(req)
if err != nil {
return err
} //Вроде создаем новый реквест на создание пулл реквеста
if rsp.StatusCode != http.StatusCreated {
buf, _ = io.ReadAll(rsp.Body)
return fmt.Errorf("unknown error: %s", buf)
}
return nil
}
func (g *Gitea) RequestClose(ctx context.Context, cfg *configcli.Config, branch string, path string) error {
logger.Debugf(ctx, "RequestOpen start, mod title: %s", path)
if cfg.Source == nil {
cfg.Source = &configcli.Source{
TypeGit: "gitea",
Token: os.Getenv("GITHUB_TOKEN"),
APIURL: os.Getenv("GITHUB_API_URL"),
Repository: os.Getenv("GITHUB_REPOSITORY"),
}
}
var buf []byte
var err error
repo, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{DetectDotGit: true})
if err != nil {
logger.Fatalf(ctx, "failed to open repo: %v", err)
}
//извлекаем ссылки с объектами из удаленного объекта??
if err = repo.FetchContext(ctx, &git.FetchOptions{
Auth: &httpauth.BasicAuth{Username: cfg.Source.Token, Password: cfg.Source.Token},
Force: true,
}); err != nil && err != git.NoErrAlreadyUpToDate {
logger.Fatalf(ctx, "failed to fetch repo: %v", err)
}
var headRef *plumbing.Reference // вроде ссылка на гит
refIter, err := repo.Branches() //получение веток
if err != nil {
logger.Fatalf(ctx, "failed to get branches: %v", err)
}
for {
ref, err := refIter.Next()
if err != nil {
break
}
if ref.Name().String() == branch {
headRef = ref
break
}
} //перебираем получение ветки и когда находим нужную выходим из цикла записав ветку в headRef
refIter.Close()
if headRef == nil {
logger.Fatalf(ctx, "failed to get repo branch head")
} // Не получили нужную ветку
logger.Infof(ctx, "repo head %s", headRef)
var pulls []*giteaPull
req, err := http.NewRequestWithContext(ctx, http.MethodGet, cfg.Source.APIURL+"/repos/"+cfg.Source.Repository+"/pulls?state=open&token="+cfg.Source.Token, nil)
if err != nil {
return err
} //вроде запроса к репозиторию
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
rsp, err := http.DefaultClient.Do(req) // выполнение запроса
if err != nil {
return err
}
buf, _ = io.ReadAll(rsp.Body)
if rsp.StatusCode != http.StatusOK {
return fmt.Errorf("unknown error: %s", buf)
}
if err = json.Unmarshal(buf, &pulls); err != nil {
logger.Fatalf(ctx, "failed to decode response %s err: %v", buf, err)
} // записываем ответ от гита по пулл реквестам, видимо существующим
// перебираем наши модификации и если они уже есть в гите удаляем их из mods
for _, pull := range pulls {
if !strings.Contains(pull.Title, path) && pull.Base.Ref != branch {
logger.Infof(ctx, "skip %s since pr does not exist %s", path, pull.URL)
return nil
}
}
if err = repo.DeleteBranch(path); err != nil {
logger.Errorf(ctx, "failed to delete the branch: %s", path)
return err
}
logger.Infof(ctx, "Delete branch %s successful", path)
return nil
}
func (g *Gitea) RequestUpdate(ctx context.Context, cfg *configcli.Config, branch string, path string, mod modules.Update) error {
logger.Debugf(ctx, "RequestOpen start, mod title: %s", path)
if cfg.Source == nil {
cfg.Source = &configcli.Source{
TypeGit: "gitea",
Token: os.Getenv("GITHUB_TOKEN"),
APIURL: os.Getenv("GITHUB_API_URL"),
Repository: os.Getenv("GITHUB_REPOSITORY"),
}
}
var buf []byte
var err error
// создания шаблона названия для пулл реквеста
tplTitle, err := template.New("pull_request_title").Parse(cfg.PullRequestTitle)
if err != nil {
logger.Fatalf(ctx, "failed to parse template: %v", err)
}
wTitle := bytes.NewBuffer(nil)
// создания шаблона тела для пулл реквеста
tplBody, err := template.New("pull_request_body").Parse(cfg.PullRequestBody)
if err != nil {
logger.Fatalf(ctx, "failed to parse template: %v", err)
}
wBody := bytes.NewBuffer(nil)
// открытие гит репозитория с опцией обхода репозитория для нахождения .git
repo, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{DetectDotGit: true})
if err != nil {
logger.Fatalf(ctx, "failed to open repo: %v", err)
}
//извлекаем ссылки с объектами из удаленного объекта??
if err = repo.FetchContext(ctx, &git.FetchOptions{
Auth: &httpauth.BasicAuth{Username: cfg.Source.Token, Password: cfg.Source.Token},
Force: true,
}); err != nil && err != git.NoErrAlreadyUpToDate {
logger.Fatalf(ctx, "failed to fetch repo: %v", err)
}
var headRef *plumbing.Reference // вроде ссылка на гит
refIter, err := repo.Branches() //получение веток
if err != nil {
logger.Fatalf(ctx, "failed to get branches: %v", err)
}
for {
ref, err := refIter.Next()
if err != nil {
break
}
if ref.Name().String() == branch {
headRef = ref
break
}
} //перебираем получение ветки и когда находим нужную выходим из цикла записав ветку в headRef
refIter.Close()
if headRef == nil {
logger.Fatalf(ctx, "failed to get repo branch head")
} // Не получили нужную ветку
logger.Infof(ctx, "repo head %s", headRef)
wtree, err := repo.Worktree()
if err != nil {
logger.Fatalf(ctx, "failed to get worktree: %v", err)
}
var pulls []*giteaPull
req, err := http.NewRequestWithContext(ctx, http.MethodGet, cfg.Source.APIURL+"/repos/"+cfg.Source.Repository+"/pulls?state=open&token="+cfg.Source.Token, nil)
if err != nil {
return err
} //вроде запроса к репозиторию
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
rsp, err := http.DefaultClient.Do(req) // выполнение запроса
if err != nil {
return err
}
buf, _ = io.ReadAll(rsp.Body)
if rsp.StatusCode != http.StatusOK {
return fmt.Errorf("unknown error: %s", buf)
}
if err = json.Unmarshal(buf, &pulls); err != nil {
logger.Fatalf(ctx, "failed to decode response %s err: %v", buf, err)
} // записываем ответ от гита по пулл реквестам, видимо существующим
// перебираем наши модификации и если они уже есть в гите удаляем их из mods
for _, pull := range pulls {
if !strings.Contains(pull.Title, path) && pull.Base.Ref != branch {
logger.Infof(ctx, "skip %s since pr does not exist %s", path, pull.URL)
return nil
}
}
wTitle.Reset()
wBody.Reset()
logger.Infof(ctx, "update %s from %s to %s", path, mod.Module.Version, mod.Version)
logger.Infof(ctx, "reset worktree")
if err = wtree.Reset(&git.ResetOptions{Mode: git.HardReset}); err != nil {
logger.Fatalf(ctx, "failed to reset repo branch: %v", err)
} //вроде меняем ветку
if err = wtree.PullContext(ctx, &git.PullOptions{
Auth: &httpauth.BasicAuth{Username: cfg.Source.Token, Password: cfg.Source.Token},
Depth: 1,
// RemoteURL :
Force: true,
RemoteName: "origin",
}); err != nil && err != git.NoErrAlreadyUpToDate {
logger.Fatalf(ctx, "failed to pull repo: %v", err)
}
logger.Infof(ctx, "checkout ref %s", headRef)
if err = wtree.Checkout(&git.CheckoutOptions{
Hash: headRef.Hash(),
Branch: headRef.Name(),
Create: false,
Force: true,
}); err != nil {
logger.Fatalf(ctx, "failed to checkout tree: %v", err)
} //вроде как переходим на другую ветку
epath, err := exec.LookPath("go")
if errors.Is(err, exec.ErrDot) {
err = nil
}
if err != nil {
logger.Fatalf(ctx, "failed to find go command: %v", err)
} // ищем go файл
var cmd *exec.Cmd
var out []byte
cmd = exec.CommandContext(ctx, epath, "mod", "edit", fmt.Sprintf("-require=%s@%s", path, mod.Version))
if out, err = cmd.CombinedOutput(); err != nil {
logger.Fatalf(ctx, "failed to run go mod edit: %s err: %v", out, err)
} // пытаемся выполнить команду go mod edit с новой версией модуля
cmd = exec.CommandContext(ctx, epath, "mod", "tidy")
if out, err = cmd.CombinedOutput(); err != nil {
logger.Fatalf(ctx, "failed to run go mod tidy: %s err: %v", out, err)
} // пытаемся выполнить команду go mod tidy пытаемся подтянуть новую версию модуля
logger.Infof(ctx, "worktree add go.mod")
if _, err = wtree.Add("go.mod"); err != nil {
logger.Fatalf(ctx, "failed to add file: %v", err)
}
logger.Infof(ctx, "worktree add go.sum")
if _, err = wtree.Add("go.sum"); err != nil {
logger.Fatalf(ctx, "failed to add file: %v", err)
}
logger.Infof(ctx, "worktree commit")
_, err = wtree.Commit(wTitle.String(), &git.CommitOptions{
Parents: []plumbing.Hash{headRef.Hash()},
Author: &object.Signature{
Name: "gitea-actions",
Email: "info@unistack.org",
When: time.Now(),
},
}) // хотим за коммитить изменения
if err != nil {
logger.Fatalf(ctx, "failed to commit: %v", err)
}
// newref := plumbing.NewHashReference(plumbing.ReferenceName(fmt.Sprintf("refs/heads/pkgdash-1/go_modules/%s-%s", path, mod.Version)), headRef.Hash())
/*
if err = repo.Storer.SetReference(newref); err != nil {
logger.Fatalf(ctx, "failed to create repo branch: %v", err)
}
*/
refspec := gitconfig.RefSpec(fmt.Sprintf("+refs/heads/pkgdash-1/go_modules/%s-%s:refs/heads/pkgdash-1/go_modules/%s-%s", path, mod.Version, path, mod.Version))
logger.Infof(ctx, "try to push refspec %s", refspec)
if err = repo.PushContext(ctx, &git.PushOptions{
RefSpecs: []gitconfig.RefSpec{refspec},
Auth: &httpauth.BasicAuth{Username: cfg.Source.Token, Password: cfg.Source.Token},
Force: true,
}); err != nil {
logger.Fatalf(ctx, "failed to push repo branch: %v", err)
} // пытаемся за пушить изменения
data := map[string]string{
"Name": path,
"VersionOld": mod.Module.Version,
"VersionNew": mod.Version,
}
if err = tplTitle.Execute(wTitle, data); err != nil {
logger.Fatalf(ctx, "failed to execute template: %v", err)
}
if err = tplBody.Execute(wBody, data); err != nil {
logger.Fatalf(ctx, "failed to execute template: %v", err)
}
body := map[string]string{
"base": branch,
"body": wBody.String(),
"head": fmt.Sprintf("pkgdash-1/go_modules/%s-%s", path, mod.Version),
"title": wTitle.String(),
}
logger.Infof(ctx, "raw body: %#+v", body)
buf, err = json.Marshal(body)
if err != nil {
return err
}
logger.Infof(ctx, "marshal body: %s", buf)
req, err = http.NewRequestWithContext(ctx, http.MethodPost, cfg.Source.APIURL+"/repos/"+cfg.Source.Repository+"/pulls?token="+cfg.Source.Token, bytes.NewReader(buf))
if err != nil {
return err
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
rsp, err = http.DefaultClient.Do(req)
if err != nil {
return err
} //Вроде создаем новый реквест на создание пулл реквеста
if rsp.StatusCode != http.StatusCreated {
buf, _ = io.ReadAll(rsp.Body)
return fmt.Errorf("unknown error: %s", buf)
}
return nil
}