package main import ( "bufio" "encoding/json" "fmt" "slices" "strings" "src.opensuse.org/autogits/common" "src.opensuse.org/autogits/common/gitea-generated/models" ) //go:generate mockgen -source=maintainership.go -destination=mock/maintainership.go -typed const ProjectKey = "" type MaintainershipMap map[string][]string func parseMaintainershipData(data []byte) (MaintainershipMap, error) { maintainers := make(MaintainershipMap) if err := json.Unmarshal(data, &maintainers); err != nil { return nil, err } return maintainers, nil } func ProjectMaintainershipData(gitea common.GiteaMaintainershipInterface, org, prjGit, branch string) (MaintainershipMap, error) { data, err := gitea.FetchMaintainershipFile(org, prjGit, branch) if err != nil || data == nil { return nil, err } return parseMaintainershipData(data) } func MaintainerListForProject(maintainers MaintainershipMap) []string { m, found := maintainers[ProjectKey] if !found { return nil } return m } func MaintainerListForPackage(maintainers MaintainershipMap, pkg string) []string { pkgMaintainers := maintainers[pkg] prjMaintainers := maintainers[ProjectKey] prjMaintainer: for _, prjm := range prjMaintainers { for i := range pkgMaintainers { if pkgMaintainers[i] == prjm { continue prjMaintainer } } pkgMaintainers = append(pkgMaintainers, prjm) } return pkgMaintainers } type PRReviewInfo struct { pr *models.PullRequest reviews []*models.PullReview } func fetchPRandReviews(gitea GiteaPRInterface, org, repo string, prNum int64) (PRReviewInfo, error) { pr, reviews, err := gitea.GetPullRequestAndReviews(org, repo, prNum) if err != nil { return PRReviewInfo{}, err } return PRReviewInfo{ pr: pr, reviews: reviews, }, nil } func isMaintainerApprovedPR(pr PRReviewInfo, maintainers MaintainershipMap) bool { m := append(MaintainerListForPackage(maintainers, pr.pr.Base.Name), MaintainerListForProject(maintainers)...) for _, review := range pr.reviews { if review.Stale { continue } if slices.Contains(m, review.User.UserName) { if review.State == common.ReviewStateApproved { return true } return false } } return true } func IsPrjGitPRApproved(gitea common.GiteaMaintainershipInterface, giteapr GiteaPRInterface, config common.AutogitConfig, prjGitPRNumber int64) (bool, error) { prjPR, _ := fetchPRandReviews(giteapr, config.Organization, config.GitProjectName, prjGitPRNumber) data, _ := gitea.FetchMaintainershipFile(config.Organization, config.GitProjectName, config.Branch) maintainers, _ := parseMaintainershipData(data) _, prjAssociatedPRs := common.ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(prjPR.pr.Body))) for _, PR := range prjAssociatedPRs { prInfo, _ := fetchPRandReviews(giteapr, PR.Org, PR.Repo, PR.Num) _, associatedPRs := common.ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(prInfo.pr.Body))) if len(associatedPRs) != 1 { return false, fmt.Errorf("Associated PR doesn't link only to the prjgit PR: %s/%s#%d", associatedPRs[0].Org, associatedPRs[0].Repo, associatedPRs[0].Num) } if associatedPRs[0].Org != config.Organization || associatedPRs[0].Repo != config.GitProjectName || associatedPRs[0].Num != prjGitPRNumber { return false, fmt.Errorf("Associated PR (%s/%s#%d) not linking back to prj PR (%s/%s#%d)", associatedPRs[0].Org, associatedPRs[0].Repo, associatedPRs[0].Num, config.Organization, config.GitProjectName, prjGitPRNumber) } if !isMaintainerApprovedPR(prInfo, maintainers) { return false, nil } } requiredReviews := slices.Clone(config.Reviewers) for _, r := range prjPR.reviews { if !r.Stale && r.State == common.ReviewStateApproved && slices.Contains(requiredReviews, r.User.UserName) { idx := slices.Index(requiredReviews, r.User.UserName) requiredReviews = slices.Delete(requiredReviews, idx, idx+1) } } return len(requiredReviews) == 0, nil }