Adam Majer
b7ec9a9ffb
Handle default branch name in push and branch create handlers Don't panic in this case in case the project has multiple configs
489 lines
13 KiB
Go
489 lines
13 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/filepath"
|
|
"slices"
|
|
"strings"
|
|
"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"
|
|
)
|
|
|
|
const PrPattern = "PR: %s/%s#%d"
|
|
|
|
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 GiteaTransport struct {
|
|
transport *transport.Runtime
|
|
client *apiclient.GiteaAPI
|
|
}
|
|
|
|
func AllocateGiteaTransport(host string) *GiteaTransport {
|
|
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(
|
|
repository.NewRepoGetPullRequestParams().
|
|
WithDefaults().
|
|
WithOwner(org).
|
|
WithRepo(project).
|
|
WithIndex(num),
|
|
gitea.transport.DefaultAuthentication,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
limit := int64(1000)
|
|
reviews, err := gitea.client.Repository.RepoListPullReviews(
|
|
repository.NewRepoListPullReviewsParams().
|
|
WithDefaults().
|
|
WithOwner(org).
|
|
WithRepo(project).
|
|
WithIndex(num).
|
|
WithLimit(&limit),
|
|
gitea.transport.DefaultAuthentication,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return pr.Payload, reviews.Payload, 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 *GitHandler, 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.GitPath, 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.GitPath, 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) CreatePullRequest(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) {
|
|
prOptions := models.CreatePullRequestOption{
|
|
Base: repo.DefaultBranch,
|
|
Head: srcId,
|
|
Title: title,
|
|
Body: body,
|
|
}
|
|
|
|
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) IsReviewed(pr *models.PullRequest) (bool, error) {
|
|
// TODO: get review from project git
|
|
reviewers := pr.RequestedReviewers
|
|
var page, limit int64
|
|
var reviews []*models.PullReview
|
|
page = 0
|
|
limit = 20
|
|
for {
|
|
res, err := gitea.client.Repository.RepoListPullReviews(
|
|
repository.NewRepoListPullReviewsParams().
|
|
WithOwner(pr.Base.Repo.Owner.UserName).
|
|
WithRepo(pr.Base.Repo.Name).
|
|
WithPage(&page).
|
|
WithLimit(&limit),
|
|
gitea.transport.DefaultAuthentication)
|
|
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if res.IsSuccess() {
|
|
r := res.Payload
|
|
|
|
if reviews == nil {
|
|
reviews = r
|
|
} else {
|
|
reviews = append(reviews, r...)
|
|
}
|
|
|
|
if len(r) < int(limit) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
slices.Reverse(reviews)
|
|
|
|
for _, review := range reviews {
|
|
if review.Stale || review.Dismissed {
|
|
continue
|
|
}
|
|
|
|
next_review:
|
|
for i, reviewer := range reviewers {
|
|
if review.User.UserName == reviewer.UserName {
|
|
switch review.State {
|
|
case ReviewStateApproved:
|
|
reviewers = slices.Delete(reviewers, i, i)
|
|
break next_review
|
|
case ReviewStateRequestChanges:
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return len(reviewers) == 0, 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) GetAssociatedPrjGitPR(pr *PullRequestWebhookEvent) (*models.PullRequest, error) {
|
|
var page, maxSize int64
|
|
page = 1
|
|
maxSize = 10000
|
|
state := "open"
|
|
prs, err := gitea.client.Repository.RepoListPullRequests(
|
|
repository.
|
|
NewRepoListPullRequestsParams().
|
|
WithDefaults().
|
|
WithOwner(pr.Repository.Owner.Username).
|
|
WithRepo(DefaultGitPrj).
|
|
WithState(&state).
|
|
WithLimit(&maxSize).
|
|
WithPage(&page),
|
|
gitea.transport.DefaultAuthentication)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot fetch PR list for %s / %s : %w", pr.Repository.Owner.Username, pr.Repository.Name, err)
|
|
}
|
|
|
|
prLine := fmt.Sprintf(PrPattern, pr.Repository.Owner.Username, pr.Repository.Name, pr.Number)
|
|
// h.StdLogger.Printf("attemping to match line: '%s'\n", prLine)
|
|
|
|
// payload_processing:
|
|
for _, pr := range prs.Payload {
|
|
lines := strings.Split(pr.Body, "\n")
|
|
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) == prLine {
|
|
return pr, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (gitea *GiteaTransport) GetRepositoryFileContent(repo *models.Repository, 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(repo.Owner.UserName).
|
|
WithRepo(repo.Name).
|
|
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, pr.Head.Sha, path)
|
|
}
|
|
|
|
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
|
|
}
|
|
|