250 lines
7.3 KiB
Go
250 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"slices"
|
|
"strings"
|
|
|
|
"src.opensuse.org/autogits/common"
|
|
"src.opensuse.org/autogits/common/gitea-generated/models"
|
|
)
|
|
|
|
type PRInfo struct {
|
|
pr *models.PullRequest
|
|
}
|
|
|
|
type PRSet struct {
|
|
prs []PRInfo
|
|
config *common.AutogitConfig
|
|
}
|
|
|
|
func readPRData(gitea common.GiteaPRFetcher, org, repo string, num int64, currentSet []PRInfo) ([]PRInfo, error) {
|
|
for _, p := range currentSet {
|
|
if num == p.pr.Index && repo == p.pr.Base.Repo.Name && org == p.pr.Base.Repo.Owner.UserName {
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
pr, err := gitea.GetPullRequest(org, repo, num)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, refPRs := common.ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(pr.Body)))
|
|
|
|
retSet := []PRInfo{PRInfo{pr: pr}}
|
|
|
|
for _, prdata := range refPRs {
|
|
data, err := readPRData(gitea, prdata.Org, prdata.Repo, prdata.Num, slices.Concat(currentSet, retSet))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
retSet = slices.Concat(retSet, data)
|
|
}
|
|
|
|
return retSet, nil
|
|
}
|
|
|
|
func FetchPRSet(gitea common.GiteaPRFetcher, org, repo string, num int64, config *common.AutogitConfig) (*PRSet, error) {
|
|
prs, err := readPRData(gitea, org, repo, num, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &PRSet{prs: prs, config: config}, nil
|
|
}
|
|
|
|
func (rs *PRSet) IsPrjGitPR(pr *models.PullRequest) bool {
|
|
return pr.Base.Repo.Name == rs.config.GitProjectName && pr.Base.Repo.Owner.UserName == rs.config.Organization
|
|
}
|
|
|
|
func (rs *PRSet) GetPrjGitPR() (*models.PullRequest, error) {
|
|
var ret *models.PullRequest
|
|
|
|
for _, prinfo := range rs.prs {
|
|
if rs.IsPrjGitPR(prinfo.pr) {
|
|
if ret == nil {
|
|
ret = prinfo.pr
|
|
} else {
|
|
return nil, errors.New("Multiple PrjGit PRs in one review set")
|
|
}
|
|
}
|
|
}
|
|
|
|
if ret != nil {
|
|
return ret, nil
|
|
}
|
|
|
|
return nil, errors.New("No PrjGit PR found")
|
|
}
|
|
|
|
func (rs *PRSet) IsConsistent() bool {
|
|
prjpr, err := rs.GetPrjGitPR()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
_, prjpr_set := common.ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(prjpr.Body)))
|
|
if len(prjpr_set) != len(rs.prs)-1 { // 1 to many mapping
|
|
return false
|
|
}
|
|
|
|
for _, prinfo := range rs.prs {
|
|
if prjpr == prinfo.pr {
|
|
continue
|
|
}
|
|
_, prs := common.ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(prinfo.pr.Body)))
|
|
if len(prs) != 1 || prs[0].Repo != prjpr.Base.Repo.Name || prs[0].Org != prjpr.Base.Repo.Owner.UserName || prs[0].Num != prjpr.Index {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (rs *PRSet) IsReviewed(gitea common.GiteaReviewFetcher) bool {
|
|
common_reviwers := rs.config.Reviewers
|
|
is_reviewed := false
|
|
for _, pr := range rs.prs {
|
|
r, err := FetchGiteaReviews(gitea, common_reviwers, pr.pr.Base.Repo.Owner.UserName, pr.pr.Base.Repo.Name, pr.pr.Index)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
is_reviewed = r.IsReviewed()
|
|
if !is_reviewed {
|
|
return false
|
|
}
|
|
}
|
|
return is_reviewed
|
|
}
|
|
|
|
func (rs *PRSet) Merge() error {
|
|
prjgit, err := rs.GetPrjGitPR()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gh := common.GitHandlerGeneratorImpl{}
|
|
git, err := gh.CreateGitHandler(GitAuthor, GitEmail, prjgit.Base.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
git.GitExecOrPanic("", "clone", "--depth", "1", prjgit.Base.Repo.SSHURL, common.DefaultGitPrj)
|
|
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", "origin", prjgit.Base.Sha, prjgit.Head.Sha)
|
|
|
|
// if other changes merged, check if we have conflicts
|
|
rev := strings.TrimSpace(git.GitExecWithOutputOrPanic(common.DefaultGitPrj, "merge-base", "HEAD", prjgit.Base.Sha, prjgit.Head.Sha))
|
|
if rev != prjgit.Base.Sha {
|
|
return fmt.Errorf("Base.Sha (%s) not yet merged into project-git. Aborting merge.", prjgit.Base.Sha)
|
|
}
|
|
/*
|
|
rev := git.GitExecWithOutputOrPanic(common.DefaultGitPrj, "rev-list", "-1", "HEAD")
|
|
if rev != prjgit.Base.Sha {
|
|
panic("FIXME")
|
|
}
|
|
*/
|
|
msg := "haha"
|
|
|
|
err = git.GitExec(common.DefaultGitPrj, "merge", "--no-ff", "-m", msg, prjgit.Head.Sha)
|
|
if err != nil {
|
|
status, statusErr := git.GitStatus(common.DefaultGitPrj)
|
|
if statusErr != nil {
|
|
return fmt.Errorf("Failed to merge: %w . Status also failed: %w", err, statusErr)
|
|
}
|
|
|
|
// we can only resolve conflicts with .gitmodules
|
|
for _, s := range status {
|
|
if s.Status == common.GitStatus_Unmerged {
|
|
if s.Path != ".gitmodules" {
|
|
return err
|
|
}
|
|
|
|
submodules, err := git.GitSubmoduleList(common.DefaultGitPrj, "MERGE_HEAD")
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to fetch submodules during merge resolution: %w", err)
|
|
}
|
|
s1, err := git.GitExecWithOutput(common.DefaultGitPrj, "cat-file", "blob", s.States[0])
|
|
if err != nil {
|
|
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
|
}
|
|
s2, err := git.GitExecWithOutput(common.DefaultGitPrj, "cat-file", "blob", s.States[1])
|
|
if err != nil {
|
|
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
|
}
|
|
s3, err := git.GitExecWithOutput(common.DefaultGitPrj, "cat-file", "blob", s.States[2])
|
|
if err != nil {
|
|
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
|
}
|
|
|
|
subs1, err := ParseSubmodulesFile(strings.NewReader(s1))
|
|
if err != nil {
|
|
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
|
}
|
|
subs2, err := ParseSubmodulesFile(strings.NewReader(s2))
|
|
if err != nil {
|
|
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
|
}
|
|
subs3, err := ParseSubmodulesFile(strings.NewReader(s3))
|
|
if err != nil {
|
|
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
|
}
|
|
|
|
// merge from subs3 (target), subs1 (orig), subs2 (2-nd base that is missing from target base)
|
|
// this will update submodules
|
|
mergedSubs := slices.Concat(subs1, subs2, subs3)
|
|
|
|
var filteredSubs []Submodule = make([]Submodule, 0, max(len(subs1), len(subs2), len(subs3)))
|
|
nextSub:
|
|
for subName := range submodules {
|
|
|
|
for i := range mergedSubs {
|
|
if path.Base(mergedSubs[i].Path) == subName {
|
|
filteredSubs = append(filteredSubs, mergedSubs[i])
|
|
continue nextSub
|
|
}
|
|
}
|
|
return fmt.Errorf("Cannot find submodule for path: %s", subName)
|
|
}
|
|
|
|
out, err := os.Create(path.Join(git.GetPath(), common.DefaultGitPrj, ".gitmodules"))
|
|
if err != nil {
|
|
return fmt.Errorf("Can't open .gitmodules for writing: %w", err)
|
|
}
|
|
if err = WriteSubmodules(filteredSubs, out); err != nil {
|
|
return fmt.Errorf("Can't write .gitmodules: %w", err)
|
|
}
|
|
if out.Close(); err != nil {
|
|
return fmt.Errorf("Can't close .gitmodules: %w", err)
|
|
}
|
|
|
|
os.CopyFS("/tmp/test", os.DirFS(git.GetPath()))
|
|
|
|
git.GitExecOrPanic(common.DefaultGitPrj, "add", ".gitmodules")
|
|
git.GitExecOrPanic(common.DefaultGitPrj, "-c", "core.editor=true", "merge", "--continue")
|
|
}
|
|
}
|
|
}
|
|
|
|
// FF all non-prj git
|
|
for _, prinfo := range rs.prs {
|
|
if rs.IsPrjGitPR(prinfo.pr) {
|
|
continue
|
|
}
|
|
git.GitExecOrPanic("", "clone", prinfo.pr.Base.Repo.SSHURL, prinfo.pr.Base.Name)
|
|
git.GitExecOrPanic(prinfo.pr.Base.Name, "fetch", "origin", prinfo.pr.Head.Sha)
|
|
git.GitExecOrPanic(prinfo.pr.Base.Name, "merge", "--ff", prinfo.pr.Head.Sha)
|
|
}
|
|
|
|
// push changes
|
|
git.GitExecOrPanic(common.DefaultGitPrj, "push", "origin")
|
|
for _, prinfo := range rs.prs {
|
|
if rs.IsPrjGitPR(prinfo.pr) {
|
|
continue
|
|
}
|
|
git.GitExecOrPanic(prinfo.pr.Base.Name, "push", "origin")
|
|
}
|
|
|
|
return nil
|
|
}
|