autogits/bots-common/gitea_utils.go

545 lines
15 KiB
Go
Raw Normal View History

2024-07-07 21:08:41 +02:00
package common
2024-09-10 18:24:41 +02:00
/*
* This file is part of Autogits.
*
* Copyright © 2024 SUSE LLC
*
* Autogits is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 2 of the License, or (at your option) any later
* version.
*
* Autogits is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Foobar. If not, see <https://www.gnu.org/licenses/>.
*/
2024-07-07 21:08:41 +02:00
import (
2024-07-10 17:20:23 +02:00
"fmt"
2024-07-07 21:08:41 +02:00
"io"
"os"
"path/filepath"
2024-08-23 15:19:37 +02:00
"slices"
2024-07-10 17:20:23 +02:00
"strings"
2024-07-18 16:43:27 +02:00
"time"
2024-07-07 21:08:41 +02:00
transport "github.com/go-openapi/runtime/client"
2024-07-18 16:43:27 +02:00
"github.com/go-openapi/strfmt"
2024-07-07 21:08:41 +02:00
apiclient "src.opensuse.org/autogits/common/gitea-generated/client"
2024-07-18 16:43:27 +02:00
"src.opensuse.org/autogits/common/gitea-generated/client/notification"
2024-07-07 21:08:41 +02:00
"src.opensuse.org/autogits/common/gitea-generated/client/organization"
"src.opensuse.org/autogits/common/gitea-generated/client/repository"
"src.opensuse.org/autogits/common/gitea-generated/models"
)
2024-11-07 18:25:35 +01:00
//go:generate mockgen -source=gitea_utils.go -destination=mock/gitea_utils.go -typed
2024-07-11 16:45:49 +02:00
const PrPattern = "PR: %s/%s#%d"
2024-07-18 23:36:41 +02:00
const (
// from Gitea
2024-07-22 17:35:48 +02:00
// ReviewStateApproved pr is approved
ReviewStateApproved models.ReviewStateType = "APPROVED"
// ReviewStatePending pr state is pending
ReviewStatePending models.ReviewStateType = "PENDING"
// ReviewStateComment is a comment review
ReviewStateComment models.ReviewStateType = "COMMENT"
// ReviewStateRequestChanges changes for pr are requested
ReviewStateRequestChanges models.ReviewStateType = "REQUEST_CHANGES"
// ReviewStateRequestReview review is requested from user
ReviewStateRequestReview models.ReviewStateType = "REQUEST_REVIEW"
// ReviewStateUnknown state of pr is unknown
ReviewStateUnknown models.ReviewStateType = ""
2024-07-18 23:36:41 +02:00
)
2024-11-03 22:21:57 +01:00
type Gitea interface {
GetPullRequestAndReviews(org, project string, num int64) (*models.PullRequest, []*models.PullReview, error)
GetPullNotifications(since *time.Time) ([]*models.NotificationThread, error)
SetNotificationRead(notificationId int64) error
GetOrganization(orgName string) (*models.Organization, error)
GetOrganizationRepositories(orgName string) ([]*models.Repository, error)
CreateRepositoryIfNotExist(git *GitHandler, org Organization, repoName string) (*models.Repository, error)
CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error)
RequestReviews(pr *models.PullRequest, reviewer string) ([]*models.PullReview, error)
IsReviewed(pr *models.PullRequest) (bool, error)
AddReviewComment(pr *models.PullRequest, state models.ReviewStateType, comment string) (*models.PullReview, error)
GetAssociatedPrjGitPR(pr *PullRequestWebhookEvent) (*models.PullRequest, error)
GetRepositoryFileContent(repo *models.Repository, hash, path string) ([]byte, error)
GetPullRequestFileContent(pr *models.PullRequest, path string) ([]byte, error)
GetRecentPullRequests(org, repo string) ([]*models.PullRequest, error)
GetRecentCommits(org, repo, branch string, commitNo int64) ([]*models.Commit, error)
}
2024-08-30 17:05:57 +02:00
type GiteaTransport struct {
transport *transport.Runtime
client *apiclient.GiteaAPI
}
2024-11-03 22:21:57 +01:00
func AllocateGiteaTransport(host string) Gitea {
2024-08-30 17:05:57 +02:00
var r GiteaTransport
r.transport = transport.New(host, apiclient.DefaultBasePath, [](string){"https"})
r.transport.DefaultAuthentication = transport.BearerToken(giteaToken)
r.client = apiclient.New(r.transport, nil)
return &r
}
func (gitea *GiteaTransport) GetPullRequestAndReviews(org, project string, num int64) (*models.PullRequest, []*models.PullReview, error) {
pr, err := gitea.client.Repository.RepoGetPullRequest(
2024-07-18 23:36:41 +02:00
repository.NewRepoGetPullRequestParams().
WithDefaults().
WithOwner(org).
WithRepo(project).
2024-07-22 17:35:48 +02:00
WithIndex(num),
2024-08-30 17:05:57 +02:00
gitea.transport.DefaultAuthentication,
2024-07-18 23:36:41 +02:00
)
if err != nil {
return nil, nil, err
}
limit := int64(1000)
2024-08-30 17:05:57 +02:00
reviews, err := gitea.client.Repository.RepoListPullReviews(
2024-07-18 23:36:41 +02:00
repository.NewRepoListPullReviewsParams().
WithDefaults().
WithOwner(org).
WithRepo(project).
WithIndex(num).
WithLimit(&limit),
2024-08-30 17:05:57 +02:00
gitea.transport.DefaultAuthentication,
2024-07-18 23:36:41 +02:00
)
if err != nil {
return nil, nil, err
}
return pr.Payload, reviews.Payload, nil
}
2024-08-30 17:05:57 +02:00
func (gitea *GiteaTransport) GetPullNotifications(since *time.Time) ([]*models.NotificationThread, error) {
2024-07-18 16:43:27 +02:00
bigLimit := int64(100000)
params := notification.NewNotifyGetListParams().
2024-07-22 17:35:48 +02:00
WithDefaults().
2024-08-05 17:00:39 +02:00
WithSubjectType([]string{"Pull"}).
WithStatusTypes([]string{"unread"}).
2024-07-22 17:35:48 +02:00
WithLimit(&bigLimit)
2024-07-18 16:43:27 +02:00
if since != nil {
s := strfmt.DateTime(*since)
params.SetSince(&s)
}
2024-07-22 17:35:48 +02:00
2024-08-30 17:05:57 +02:00
list, err := gitea.client.Notification.NotifyGetList(params, gitea.transport.DefaultAuthentication)
2024-07-18 16:43:27 +02:00
if err != nil {
return nil, err
}
return list.Payload, nil
}
2024-08-30 17:05:57 +02:00
func (gitea *GiteaTransport) SetNotificationRead(notificationId int64) error {
_, err := gitea.client.Notification.NotifyReadThread(
2024-07-18 16:43:27 +02:00
notification.NewNotifyReadThreadParams().
WithDefaults().
2024-08-05 17:00:39 +02:00
WithID(fmt.Sprint(notificationId)),
2024-08-30 17:05:57 +02:00
gitea.transport.DefaultAuthentication,
2024-07-18 16:43:27 +02:00
)
if err != nil {
2024-08-30 17:05:57 +02:00
return fmt.Errorf("Error setting notification: %d. Err: %w", notificationId, err)
2024-07-18 16:43:27 +02:00
}
return nil
}
2024-09-02 17:27:23 +02:00
func (gitea *GiteaTransport) GetOrganization(orgName string) (*models.Organization, error) {
org, err := gitea.client.Organization.OrgGet(
organization.NewOrgGetParams().WithOrg(orgName),
gitea.transport.DefaultAuthentication,
)
if err != nil {
return nil, fmt.Errorf("Error fetching org: '%s' data. Err: %w", orgName, err)
}
return org.Payload, nil
}
2024-09-05 15:02:26 +02:00
func (gitea *GiteaTransport) GetOrganizationRepositories(orgName string) ([]*models.Repository, error) {
var page int64
repos := make([]*models.Repository, 0, 100)
page = 1
for {
ret, err := gitea.client.Organization.OrgListRepos(
organization.NewOrgListReposParams().WithOrg(orgName).WithPage(&page),
gitea.transport.DefaultAuthentication,
)
if err != nil {
return nil, fmt.Errorf("Error retrieving repository list for org: '%s'. Err: %w", orgName, err)
}
if len(ret.Payload) == 0 {
break
}
repos = append(repos, ret.Payload...)
2024-09-05 15:28:08 +02:00
page++
2024-09-05 15:02:26 +02:00
}
return repos, nil
}
2024-09-02 17:27:23 +02:00
func (gitea *GiteaTransport) CreateRepositoryIfNotExist(git *GitHandler, org Organization, repoName string) (*models.Repository, error) {
2024-08-30 17:05:57 +02:00
repo, err := gitea.client.Repository.RepoGet(
2024-07-07 21:08:41 +02:00
repository.NewRepoGetParams().WithDefaults().WithOwner(org.Username).WithRepo(repoName),
2024-08-30 17:05:57 +02:00
gitea.transport.DefaultAuthentication)
2024-07-07 21:08:41 +02:00
if err != nil {
switch err.(type) {
case *repository.RepoGetNotFound:
2024-08-30 17:05:57 +02:00
repo, err := gitea.client.Organization.CreateOrgRepo(
2024-07-07 21:08:41 +02:00
organization.NewCreateOrgRepoParams().WithDefaults().WithBody(
&models.CreateRepoOption{
AutoInit: false,
Name: &repoName,
ObjectFormatName: models.CreateRepoOptionObjectFormatNameSha256,
},
).WithOrg(org.Username),
nil,
)
if err != nil {
switch err.(type) {
case *organization.CreateOrgRepoCreated:
2024-08-30 17:05:57 +02:00
// weird, but ok, repo created
2024-07-07 21:08:41 +02:00
default:
2024-08-30 17:05:57 +02:00
return nil, fmt.Errorf("error creating repo '%s' under '%s'. Err: %w", repoName, org.Username, err)
2024-07-07 21:08:41 +02:00
}
}
// initialize repository
2024-08-30 17:05:57 +02:00
if err = os.Mkdir(filepath.Join(git.GitPath, DefaultGitPrj), 0700); err != nil {
2024-08-28 17:20:09 +02:00
return nil, err
2024-07-07 21:08:41 +02:00
}
2024-09-04 14:04:13 +02:00
if err = git.GitExec(DefaultGitPrj, "init", "--object-format="+repo.Payload.ObjectFormatName); err != nil {
return nil, err
}
if err = git.GitExec(DefaultGitPrj, "checkout", "-b", repo.Payload.DefaultBranch); err != nil {
return nil, err
}
2024-08-30 17:05:57 +02:00
readmeFilename := filepath.Join(git.GitPath, DefaultGitPrj, "README.md")
2024-07-07 21:08:41 +02:00
{
file, _ := os.Create(readmeFilename)
defer file.Close()
io.WriteString(file, ReadmeBoilerplate)
}
2024-09-04 14:04:13 +02:00
if err = git.GitExec(DefaultGitPrj, "add", "README.md"); err != nil {
return nil, err
}
if err = git.GitExec(DefaultGitPrj, "commit", "-m", "Automatic devel project creation"); err != nil {
return nil, err
}
if err = git.GitExec(DefaultGitPrj, "remote", "add", "origin", repo.Payload.SSHURL); err != nil {
return nil, err
}
2024-07-07 21:08:41 +02:00
2024-08-28 17:20:09 +02:00
return repo.Payload, nil
2024-07-07 21:08:41 +02:00
default:
2024-08-30 17:05:57 +02:00
return nil, fmt.Errorf("cannot fetch repo data for '%s' / '%s' : %w", org.Username, repoName, err)
2024-07-07 21:08:41 +02:00
}
}
2024-08-28 17:20:09 +02:00
return repo.Payload, nil
2024-07-07 21:08:41 +02:00
}
func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) {
2024-07-10 11:41:34 +02:00
prOptions := models.CreatePullRequestOption{
Base: repo.DefaultBranch,
Head: srcId,
Title: title,
Body: body,
}
2024-07-09 23:22:42 +02:00
if pr, err := gitea.client.Repository.RepoGetPullRequestByBaseHead(
repository.NewRepoGetPullRequestByBaseHeadParams().WithOwner(repo.Owner.UserName).WithRepo(repo.Name).WithBase(repo.DefaultBranch).WithHead(srcId),
gitea.transport.DefaultAuthentication,
); err == nil {
return pr.Payload, nil
}
2024-08-30 17:05:57 +02:00
pr, err := gitea.client.Repository.RepoCreatePullRequest(
2024-07-09 23:22:42 +02:00
repository.
NewRepoCreatePullRequestParams().
WithDefaults().
WithOwner(repo.Owner.UserName).
WithRepo(repo.Name).
WithBody(&prOptions),
2024-08-30 17:05:57 +02:00
gitea.transport.DefaultAuthentication,
2024-07-09 23:22:42 +02:00
)
if err != nil {
2024-08-28 17:20:09 +02:00
return nil, fmt.Errorf("Cannot create pull request. %w", err)
2024-07-09 23:22:42 +02:00
}
2024-08-28 17:20:09 +02:00
return pr.GetPayload(), nil
2024-07-09 23:22:42 +02:00
}
2024-08-30 17:05:57 +02:00
func (gitea *GiteaTransport) RequestReviews(pr *models.PullRequest, reviewer string) ([]*models.PullReview, error) {
2024-07-10 11:41:34 +02:00
reviewOptions := models.PullReviewRequestOptions{
Reviewers: []string{reviewer},
}
2024-07-09 23:22:42 +02:00
2024-08-30 17:05:57 +02:00
review, err := gitea.client.Repository.RepoCreatePullReviewRequests(
2024-07-09 23:22:42 +02:00
repository.
NewRepoCreatePullReviewRequestsParams().
WithOwner(pr.Base.Repo.Owner.UserName).
WithRepo(pr.Base.Repo.Name).
WithIndex(pr.Index).
WithBody(&reviewOptions),
2024-08-30 17:05:57 +02:00
gitea.transport.DefaultAuthentication,
2024-07-09 23:22:42 +02:00
)
if err != nil {
2024-08-28 17:20:09 +02:00
return nil, fmt.Errorf("Cannot create pull request: %w", err)
2024-07-09 23:22:42 +02:00
}
2024-08-28 17:20:09 +02:00
return review.GetPayload(), nil
2024-07-09 23:22:42 +02:00
}
2024-07-10 17:20:23 +02:00
2024-08-30 17:05:57 +02:00
func (gitea *GiteaTransport) IsReviewed(pr *models.PullRequest) (bool, error) {
2024-08-23 15:19:37 +02:00
// TODO: get review from project git
reviewers := pr.RequestedReviewers
2024-10-01 12:18:37 +02:00
var page int64
reviews := make([]*models.PullReview, 0, 10)
2024-08-23 15:19:37 +02:00
for {
2024-10-01 12:18:37 +02:00
page++
2024-08-30 17:05:57 +02:00
res, err := gitea.client.Repository.RepoListPullReviews(
2024-08-23 15:19:37 +02:00
repository.NewRepoListPullReviewsParams().
WithOwner(pr.Base.Repo.Owner.UserName).
WithRepo(pr.Base.Repo.Name).
2024-10-01 12:18:37 +02:00
WithPage(&page),
2024-08-30 17:05:57 +02:00
gitea.transport.DefaultAuthentication)
2024-08-23 15:19:37 +02:00
if err != nil {
return false, err
}
if res.IsSuccess() {
2024-10-01 12:18:37 +02:00
reviews = append(reviews, res.Payload...)
if len(res.Payload) < 10 {
2024-08-23 15:19:37 +02:00
break
}
}
}
slices.Reverse(reviews)
for _, review := range reviews {
if review.Stale || review.Dismissed {
continue
}
2024-08-30 17:05:57 +02:00
next_review:
2024-08-23 15:19:37 +02:00
for i, reviewer := range reviewers {
if review.User.UserName == reviewer.UserName {
2024-08-30 17:05:57 +02:00
switch review.State {
2024-08-23 15:19:37 +02:00
case ReviewStateApproved:
reviewers = slices.Delete(reviewers, i, i)
break next_review
case ReviewStateRequestChanges:
return false, nil
}
}
}
}
return len(reviewers) == 0, nil
}
2024-08-30 17:05:57 +02:00
func (gitea *GiteaTransport) AddReviewComment(pr *models.PullRequest, state models.ReviewStateType, comment string) (*models.PullReview, error) {
c, err := gitea.client.Repository.RepoCreatePullReview(
2024-07-26 16:53:09 +02:00
repository.NewRepoCreatePullReviewParams().
WithDefaults().
WithOwner(pr.Base.Repo.Owner.UserName).
WithRepo(pr.Base.Repo.Name).
WithIndex(pr.Index).
WithBody(&models.CreatePullReviewOptions{
Event: state,
Body: comment,
}),
2024-08-30 17:05:57 +02:00
gitea.transport.DefaultAuthentication,
2024-07-26 16:53:09 +02:00
)
/*
c, err := client.Repository.RepoSubmitPullReview(
repository.NewRepoSubmitPullReviewParams().
WithDefaults().
WithOwner(pr.Base.Repo.Owner.UserName).
WithRepo(pr.Base.Repo.Name).
WithIndex(pr.Index).
WithID(review.ID).
WithBody(&models.SubmitPullReviewOptions{
Event: state,
Body: comment,
}),
transport.DefaultAuthentication,
)
*/
/* c, err := client.Issue.IssueCreateComment(
issue.NewIssueCreateCommentParams().
WithDefaults().
WithOwner(pr.Base.Repo.Owner.UserName).
WithRepo(pr.Base.Repo.Name).
WithIndex(pr.Index).
WithBody(&models.CreateIssueCommentOption{
Body: &comment,
}),
transport.DefaultAuthentication)
*/
if err != nil {
return nil, err
}
return c.Payload, nil
}
2024-08-30 17:05:57 +02:00
func (gitea *GiteaTransport) GetAssociatedPrjGitPR(pr *PullRequestWebhookEvent) (*models.PullRequest, error) {
var page int64
2024-07-10 17:20:23 +02:00
state := "open"
for {
page++
prs, err := gitea.client.Repository.RepoListPullRequests(
repository.
NewRepoListPullRequestsParams().
WithDefaults().
WithOwner(pr.Repository.Owner.Username).
WithRepo(DefaultGitPrj).
WithState(&state).
WithPage(&page),
gitea.transport.DefaultAuthentication)
2024-07-10 17:20:23 +02:00
if err != nil {
return nil, fmt.Errorf("cannot fetch PR list for %s / %s : %w", pr.Repository.Owner.Username, pr.Repository.Name, err)
}
2024-07-10 17:20:23 +02:00
prLine := fmt.Sprintf(PrPattern, pr.Repository.Owner.Username, pr.Repository.Name, pr.Number)
// h.StdLogger.Printf("attemping to match line: '%s'\n", prLine)
2024-07-11 16:45:49 +02:00
// payload_processing:
for _, pr := range prs.Payload {
lines := strings.Split(pr.Body, "\n")
2024-07-10 17:20:23 +02:00
for _, line := range lines {
if strings.TrimSpace(line) == prLine {
return pr, nil
}
2024-07-10 17:20:23 +02:00
}
}
if len(prs.Payload) < 10 {
break
}
2024-07-10 17:20:23 +02:00
}
2024-08-28 17:20:09 +02:00
return nil, nil
2024-07-10 17:20:23 +02:00
}
2024-07-22 17:35:48 +02:00
2024-08-30 17:05:57 +02:00
func (gitea *GiteaTransport) GetRepositoryFileContent(repo *models.Repository, hash, path string) ([]byte, error) {
2024-07-26 16:53:09 +02:00
var retData []byte
dataOut := writeFunc(func(data []byte) (int, error) {
if len(data) == 0 {
return 0, nil
}
retData = data
return len(data), nil
})
2024-08-30 17:05:57 +02:00
_, err := gitea.client.Repository.RepoGetRawFile(
2024-07-22 17:35:48 +02:00
repository.NewRepoGetRawFileParams().
WithOwner(repo.Owner.UserName).
WithRepo(repo.Name).
WithFilepath(path).
2024-07-26 16:53:09 +02:00
WithRef(&hash),
2024-08-30 17:05:57 +02:00
gitea.transport.DefaultAuthentication,
2024-07-26 16:53:09 +02:00
dataOut,
repository.WithContentTypeApplicationOctetStream,
2024-07-22 17:35:48 +02:00
)
if err != nil {
return nil, err
}
2024-07-26 16:53:09 +02:00
return retData, nil
2024-07-22 17:35:48 +02:00
}
2024-08-30 17:05:57 +02:00
func (gitea *GiteaTransport) GetPullRequestFileContent(pr *models.PullRequest, path string) ([]byte, error) {
return gitea.GetRepositoryFileContent(pr.Head.Repo, pr.Head.Sha, path)
2024-07-26 16:53:09 +02:00
}
2024-09-02 17:27:23 +02:00
2024-09-30 15:09:45 +02:00
func (gitea *GiteaTransport) GetRecentPullRequests(org, repo string) ([]*models.PullRequest, error) {
prs := make([]*models.PullRequest, 0, 10)
var page int64
page = 1
sort := "recentupdate"
for {
res, err := gitea.client.Repository.RepoListPullRequests(
repository.NewRepoListPullRequestsParams().
WithOwner(org).
WithRepo(repo).
WithPage(&page).
WithSort(&sort),
gitea.transport.DefaultAuthentication)
if err != nil {
return nil, err
}
prs = append(prs, res.Payload...)
n := len(res.Payload)
if n < 10 {
break
}
// if pr is closed for more than a week, assume that we are done too
if time.Since(time.Time(res.Payload[n-1].Updated)) > 7*24*time.Hour {
2024-09-30 15:09:45 +02:00
break
}
page++
}
return prs, nil
}
2024-09-02 17:27:23 +02:00
func (gitea *GiteaTransport) GetRecentCommits(org, repo, branch string, commitNo int64) ([]*models.Commit, error) {
not := false
2024-09-04 14:04:13 +02:00
var page int64
page = 1
2024-09-02 17:27:23 +02:00
commits, err := gitea.client.Repository.RepoGetAllCommits(
repository.NewRepoGetAllCommitsParams().
2024-09-04 14:04:13 +02:00
WithOwner(org).
WithRepo(repo).
WithSha(&branch).
WithPage(&page).
2024-09-02 17:27:23 +02:00
WithStat(&not).
WithFiles(&not).
WithVerification(&not).
WithLimit(&commitNo),
gitea.transport.DefaultAuthentication,
)
if err != nil {
return nil, err
}
return commits.Payload, nil
}