Adam Majer
245181ad28
Check if branch exists and if it matches the PR already created. If yes, then nothing needs to be updated.
501 lines
14 KiB
Go
501 lines
14 KiB
Go
package common
|
|
|
|
/*
|
|
* 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/>.
|
|
*/
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"slices"
|
|
"time"
|
|
|
|
transport "github.com/go-openapi/runtime/client"
|
|
"github.com/go-openapi/strfmt"
|
|
apiclient "src.opensuse.org/autogits/common/gitea-generated/client"
|
|
"src.opensuse.org/autogits/common/gitea-generated/client/notification"
|
|
"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"
|
|
)
|
|
|
|
//go:generate mockgen -source=gitea_utils.go -destination=mock/gitea_utils.go -typed
|
|
|
|
// maintainer list file in ProjectGit
|
|
const (
|
|
MaintainershipFile = "_maitnainership.json"
|
|
MaintainershipDir = "maintaineirship"
|
|
)
|
|
|
|
const (
|
|
// from Gitea
|
|
// 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 = ""
|
|
)
|
|
|
|
type GiteaMaintainershipInterface interface {
|
|
FetchMaintainershipFile(org, prjGit, branch string) ([]byte, error)
|
|
FetchMaintainershipDirFile(org, prjGit, branch, pkg string) ([]byte, error)
|
|
}
|
|
|
|
type GiteaPRFetcher interface {
|
|
GetPullRequest(org, project string, num int64) (*models.PullRequest, error)
|
|
}
|
|
|
|
type GiteaReviewFetcher interface {
|
|
GetPullRequestReviews(org, project string, PRnum int64) ([]*models.PullReview, error)
|
|
}
|
|
|
|
type GiteaReviewRequester interface {
|
|
RequestReviews(pr *models.PullRequest, reviewer string) ([]*models.PullReview, error)
|
|
}
|
|
|
|
type GiteaReviewer interface {
|
|
AddReviewComment(pr *models.PullRequest, state models.ReviewStateType, comment string) (*models.PullReview, error)
|
|
}
|
|
|
|
type Gitea interface {
|
|
GiteaReviewRequester
|
|
GiteaReviewer
|
|
GiteaPRFetcher
|
|
|
|
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 Git, org Organization, repoName string) (*models.Repository, error)
|
|
CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error)
|
|
GetRepositoryFileContent(org, repo, 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)
|
|
|
|
GiteaMaintainershipInterface
|
|
}
|
|
|
|
type GiteaTransport struct {
|
|
transport *transport.Runtime
|
|
client *apiclient.GiteaAPI
|
|
}
|
|
|
|
func AllocateGiteaTransport(host string) Gitea {
|
|
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) FetchMaintainershipFile(org, repo, branch string) ([]byte, error) {
|
|
return gitea.GetRepositoryFileContent(org, repo, branch, MaintainershipFile)
|
|
}
|
|
|
|
func (gitea *GiteaTransport) FetchMaintainershipDirFile(org, repo, branch, pkg string) ([]byte, error) {
|
|
return gitea.GetRepositoryFileContent(org, repo, branch, path.Join(MaintainershipDir, pkg))
|
|
}
|
|
|
|
func (gitea *GiteaTransport) GetPullRequest(org, project string, num int64) (*models.PullRequest, error) {
|
|
pr, err := gitea.client.Repository.RepoGetPullRequest(
|
|
repository.NewRepoGetPullRequestParams().
|
|
WithDefaults().
|
|
WithOwner(org).
|
|
WithRepo(project).
|
|
WithIndex(num),
|
|
gitea.transport.DefaultAuthentication,
|
|
)
|
|
|
|
return pr.Payload, err
|
|
}
|
|
|
|
func (gitea *GiteaTransport) GetPullReviews(org, project string, num int64) ([]*models.PullReview, error) {
|
|
limit := int64(20)
|
|
var page int64
|
|
|
|
var allReviews []*models.PullReview
|
|
|
|
for {
|
|
reviews, err := gitea.client.Repository.RepoListPullReviews(
|
|
repository.NewRepoListPullReviewsParams().
|
|
WithDefaults().
|
|
WithOwner(org).
|
|
WithRepo(project).
|
|
WithIndex(num).
|
|
WithPage(&page).
|
|
WithLimit(&limit),
|
|
gitea.transport.DefaultAuthentication,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
allReviews = slices.Concat(allReviews, reviews.Payload)
|
|
if len(reviews.Payload) < int(limit) {
|
|
break
|
|
}
|
|
page++
|
|
}
|
|
|
|
return allReviews, nil
|
|
}
|
|
|
|
func (gitea *GiteaTransport) GetPullNotifications(since *time.Time) ([]*models.NotificationThread, error) {
|
|
bigLimit := int64(100000)
|
|
|
|
params := notification.NewNotifyGetListParams().
|
|
WithDefaults().
|
|
WithSubjectType([]string{"Pull"}).
|
|
WithStatusTypes([]string{"unread"}).
|
|
WithLimit(&bigLimit)
|
|
|
|
if since != nil {
|
|
s := strfmt.DateTime(*since)
|
|
params.SetSince(&s)
|
|
}
|
|
|
|
list, err := gitea.client.Notification.NotifyGetList(params, gitea.transport.DefaultAuthentication)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return list.Payload, nil
|
|
}
|
|
|
|
func (gitea *GiteaTransport) SetNotificationRead(notificationId int64) error {
|
|
_, err := gitea.client.Notification.NotifyReadThread(
|
|
notification.NewNotifyReadThreadParams().
|
|
WithDefaults().
|
|
WithID(fmt.Sprint(notificationId)),
|
|
gitea.transport.DefaultAuthentication,
|
|
)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("Error setting notification: %d. Err: %w", notificationId, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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...)
|
|
page++
|
|
}
|
|
|
|
return repos, nil
|
|
}
|
|
|
|
func (gitea *GiteaTransport) CreateRepositoryIfNotExist(git Git, org Organization, repoName string) (*models.Repository, error) {
|
|
repo, err := gitea.client.Repository.RepoGet(
|
|
repository.NewRepoGetParams().WithDefaults().WithOwner(org.Username).WithRepo(repoName),
|
|
gitea.transport.DefaultAuthentication)
|
|
|
|
if err != nil {
|
|
switch err.(type) {
|
|
case *repository.RepoGetNotFound:
|
|
repo, err := gitea.client.Organization.CreateOrgRepo(
|
|
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:
|
|
// weird, but ok, repo created
|
|
default:
|
|
return nil, fmt.Errorf("error creating repo '%s' under '%s'. Err: %w", repoName, org.Username, err)
|
|
}
|
|
}
|
|
|
|
// initialize repository
|
|
if err = os.Mkdir(filepath.Join(git.GetPath(), DefaultGitPrj), 0700); err != nil {
|
|
return nil, err
|
|
}
|
|
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
|
|
}
|
|
readmeFilename := filepath.Join(git.GetPath(), DefaultGitPrj, "README.md")
|
|
{
|
|
file, _ := os.Create(readmeFilename)
|
|
defer file.Close()
|
|
|
|
io.WriteString(file, ReadmeBoilerplate)
|
|
}
|
|
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
|
|
}
|
|
|
|
return repo.Payload, nil
|
|
default:
|
|
return nil, fmt.Errorf("cannot fetch repo data for '%s' / '%s' : %w", org.Username, repoName, err)
|
|
}
|
|
}
|
|
|
|
return repo.Payload, nil
|
|
}
|
|
|
|
func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) {
|
|
prOptions := models.CreatePullRequestOption{
|
|
Base: repo.DefaultBranch,
|
|
Head: srcId,
|
|
Title: title,
|
|
Body: body,
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
pr, err := gitea.client.Repository.RepoCreatePullRequest(
|
|
repository.
|
|
NewRepoCreatePullRequestParams().
|
|
WithDefaults().
|
|
WithOwner(repo.Owner.UserName).
|
|
WithRepo(repo.Name).
|
|
WithBody(&prOptions),
|
|
gitea.transport.DefaultAuthentication,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Cannot create pull request. %w", err)
|
|
}
|
|
|
|
return pr.GetPayload(), nil
|
|
}
|
|
|
|
func (gitea *GiteaTransport) RequestReviews(pr *models.PullRequest, reviewer string) ([]*models.PullReview, error) {
|
|
reviewOptions := models.PullReviewRequestOptions{
|
|
Reviewers: []string{reviewer},
|
|
}
|
|
|
|
review, err := gitea.client.Repository.RepoCreatePullReviewRequests(
|
|
repository.
|
|
NewRepoCreatePullReviewRequestsParams().
|
|
WithOwner(pr.Base.Repo.Owner.UserName).
|
|
WithRepo(pr.Base.Repo.Name).
|
|
WithIndex(pr.Index).
|
|
WithBody(&reviewOptions),
|
|
gitea.transport.DefaultAuthentication,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Cannot create pull request: %w", err)
|
|
}
|
|
|
|
return review.GetPayload(), nil
|
|
}
|
|
|
|
func (gitea *GiteaTransport) AddReviewComment(pr *models.PullRequest, state models.ReviewStateType, comment string) (*models.PullReview, error) {
|
|
c, err := gitea.client.Repository.RepoCreatePullReview(
|
|
repository.NewRepoCreatePullReviewParams().
|
|
WithDefaults().
|
|
WithOwner(pr.Base.Repo.Owner.UserName).
|
|
WithRepo(pr.Base.Repo.Name).
|
|
WithIndex(pr.Index).
|
|
WithBody(&models.CreatePullReviewOptions{
|
|
Event: state,
|
|
Body: comment,
|
|
}),
|
|
gitea.transport.DefaultAuthentication,
|
|
)
|
|
/*
|
|
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
|
|
}
|
|
|
|
func (gitea *GiteaTransport) GetRepositoryFileContent(org, repo, hash, path string) ([]byte, error) {
|
|
var retData []byte
|
|
|
|
dataOut := writeFunc(func(data []byte) (int, error) {
|
|
if len(data) == 0 {
|
|
return 0, nil
|
|
}
|
|
retData = data
|
|
return len(data), nil
|
|
})
|
|
_, err := gitea.client.Repository.RepoGetRawFile(
|
|
repository.NewRepoGetRawFileParams().
|
|
WithOwner(org).
|
|
WithRepo(repo).
|
|
WithFilepath(path).
|
|
WithRef(&hash),
|
|
gitea.transport.DefaultAuthentication,
|
|
dataOut,
|
|
repository.WithContentTypeApplicationOctetStream,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return retData, nil
|
|
}
|
|
|
|
func (gitea *GiteaTransport) GetPullRequestFileContent(pr *models.PullRequest, path string) ([]byte, error) {
|
|
return gitea.GetRepositoryFileContent(pr.Head.Repo.Owner.UserName, pr.Head.Repo.Name, pr.Head.Sha, path)
|
|
}
|
|
|
|
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 {
|
|
break
|
|
}
|
|
|
|
page++
|
|
}
|
|
|
|
return prs, nil
|
|
}
|
|
|
|
func (gitea *GiteaTransport) GetRecentCommits(org, repo, branch string, commitNo int64) ([]*models.Commit, error) {
|
|
not := false
|
|
var page int64
|
|
page = 1
|
|
commits, err := gitea.client.Repository.RepoGetAllCommits(
|
|
repository.NewRepoGetAllCommitsParams().
|
|
WithOwner(org).
|
|
WithRepo(repo).
|
|
WithSha(&branch).
|
|
WithPage(&page).
|
|
WithStat(¬).
|
|
WithFiles(¬).
|
|
WithVerification(¬).
|
|
WithLimit(&commitNo),
|
|
gitea.transport.DefaultAuthentication,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return commits.Payload, nil
|
|
}
|