初始提交: Gitea 项目代码
This commit is contained in:
@@ -0,0 +1,779 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/models/user"
|
||||
"gitea.dev/modules/container"
|
||||
"gitea.dev/modules/log"
|
||||
base "gitea.dev/modules/migration"
|
||||
"gitea.dev/modules/structs"
|
||||
|
||||
gitlab "gitlab.com/gitlab-org/api/client-go/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
_ base.Downloader = &GitlabDownloader{}
|
||||
_ base.DownloaderFactory = &GitlabDownloaderFactory{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDownloaderFactory(&GitlabDownloaderFactory{})
|
||||
}
|
||||
|
||||
// GitlabDownloaderFactory defines a gitlab downloader factory
|
||||
type GitlabDownloaderFactory struct{}
|
||||
|
||||
// New returns a Downloader related to this factory according MigrateOptions
|
||||
func (f *GitlabDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
|
||||
u, err := url.Parse(opts.CloneAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseURL := u.Scheme + "://" + u.Host
|
||||
repoNameSpace := strings.TrimPrefix(u.Path, "/")
|
||||
repoNameSpace = strings.TrimSuffix(repoNameSpace, ".git")
|
||||
|
||||
log.Trace("Create gitlab downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace)
|
||||
|
||||
return NewGitlabDownloader(ctx, baseURL, repoNameSpace, opts.AuthToken)
|
||||
}
|
||||
|
||||
// GitServiceType returns the type of git service
|
||||
func (f *GitlabDownloaderFactory) GitServiceType() structs.GitServiceType {
|
||||
return structs.GitlabService
|
||||
}
|
||||
|
||||
type gitlabIIDResolver struct {
|
||||
maxIssueIID int64
|
||||
frozen bool
|
||||
}
|
||||
|
||||
func (r *gitlabIIDResolver) recordIssueIID(issueIID int64) {
|
||||
if r.frozen {
|
||||
panic("cannot record issue IID after pull request IID generation has started")
|
||||
}
|
||||
r.maxIssueIID = max(r.maxIssueIID, issueIID)
|
||||
}
|
||||
|
||||
func (r *gitlabIIDResolver) generatePullRequestNumber(mrIID int64) int64 {
|
||||
r.frozen = true
|
||||
return r.maxIssueIID + mrIID
|
||||
}
|
||||
|
||||
// GitlabDownloader implements a Downloader interface to get repository information
|
||||
// from gitlab via go-gitlab
|
||||
// - issueCount is incremented in GetIssues() to ensure PR and Issue numbers do not overlap,
|
||||
// because Gitlab has individual Issue and Pull Request numbers.
|
||||
type GitlabDownloader struct {
|
||||
base.NullDownloader
|
||||
client *gitlab.Client
|
||||
baseURL string
|
||||
repoID int64
|
||||
repoName string
|
||||
iidResolver gitlabIIDResolver
|
||||
maxPerPage int
|
||||
}
|
||||
|
||||
// NewGitlabDownloader creates a gitlab Downloader via gitlab API
|
||||
//
|
||||
// Use either a username/password, personal token entered into the username field, or anonymous/public access
|
||||
// Note: Public access only allows very basic access
|
||||
func NewGitlabDownloader(ctx context.Context, baseURL, repoPath, token string) (*GitlabDownloader, error) {
|
||||
gitlabClient, err := gitlab.NewClient(token, gitlab.WithBaseURL(baseURL), gitlab.WithHTTPClient(NewMigrationHTTPClient()))
|
||||
if err != nil {
|
||||
log.Trace("Error logging into gitlab: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// split namespace and subdirectory
|
||||
pathParts := strings.Split(strings.Trim(repoPath, "/"), "/")
|
||||
var resp *gitlab.Response
|
||||
u, _ := url.Parse(baseURL)
|
||||
for len(pathParts) >= 2 {
|
||||
_, resp, err = gitlabClient.Version.GetVersion()
|
||||
if err == nil || resp != nil && resp.StatusCode == http.StatusUnauthorized {
|
||||
err = nil // if no authentication given, this still should work
|
||||
break
|
||||
}
|
||||
|
||||
u.Path = path.Join(u.Path, pathParts[0])
|
||||
baseURL = u.String()
|
||||
pathParts = pathParts[1:]
|
||||
_ = gitlab.WithBaseURL(baseURL)(gitlabClient)
|
||||
repoPath = strings.Join(pathParts, "/")
|
||||
}
|
||||
if err != nil {
|
||||
log.Trace("Error could not get gitlab version: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Trace("gitlab downloader: use BaseURL: '%s' and RepoPath: '%s'", baseURL, repoPath)
|
||||
|
||||
// Grab and store project/repo ID here, due to issues using the URL escaped path
|
||||
gr, _, err := gitlabClient.Projects.GetProject(repoPath, nil, nil, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
log.Trace("Error retrieving project: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gr == nil {
|
||||
log.Trace("Error getting project, project is nil")
|
||||
return nil, errors.New("Error getting project, project is nil")
|
||||
}
|
||||
|
||||
return &GitlabDownloader{
|
||||
client: gitlabClient,
|
||||
baseURL: baseURL,
|
||||
repoID: gr.ID,
|
||||
repoName: gr.Name,
|
||||
maxPerPage: 100,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String implements Stringer
|
||||
func (g *GitlabDownloader) String() string {
|
||||
return fmt.Sprintf("migration from gitlab server %s [%d]/%s", g.baseURL, g.repoID, g.repoName)
|
||||
}
|
||||
|
||||
func (g *GitlabDownloader) LogString() string {
|
||||
if g == nil {
|
||||
return "<GitlabDownloader nil>"
|
||||
}
|
||||
return fmt.Sprintf("<GitlabDownloader %s [%d]/%s>", g.baseURL, g.repoID, g.repoName)
|
||||
}
|
||||
|
||||
// GetRepoInfo returns a repository information
|
||||
func (g *GitlabDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) {
|
||||
gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var private bool
|
||||
switch gr.Visibility {
|
||||
case gitlab.InternalVisibility:
|
||||
private = true
|
||||
case gitlab.PrivateVisibility:
|
||||
private = true
|
||||
}
|
||||
|
||||
var owner string
|
||||
if gr.Owner == nil {
|
||||
log.Trace("gr.Owner is nil, trying to get owner from Namespace")
|
||||
if gr.Namespace != nil && gr.Namespace.Kind == "user" {
|
||||
owner = gr.Namespace.Path
|
||||
}
|
||||
} else {
|
||||
owner = gr.Owner.Username
|
||||
}
|
||||
|
||||
// convert gitlab repo to stand Repo
|
||||
return &base.Repository{
|
||||
Owner: owner,
|
||||
Name: gr.Name,
|
||||
IsPrivate: private,
|
||||
Description: gr.Description,
|
||||
OriginalURL: gr.WebURL,
|
||||
CloneURL: gr.HTTPURLToRepo,
|
||||
DefaultBranch: gr.DefaultBranch,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetTopics return gitlab topics
|
||||
func (g *GitlabDownloader) GetTopics(ctx context.Context) ([]string, error) {
|
||||
gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gr.Topics, err
|
||||
}
|
||||
|
||||
// GetMilestones returns milestones
|
||||
func (g *GitlabDownloader) GetMilestones(ctx context.Context) ([]*base.Milestone, error) {
|
||||
perPage := g.maxPerPage
|
||||
state := "all"
|
||||
milestones := make([]*base.Milestone, 0, perPage)
|
||||
for i := 1; ; i++ {
|
||||
ms, _, err := g.client.Milestones.ListMilestones(g.repoID, &gitlab.ListMilestonesOptions{
|
||||
State: &state,
|
||||
ListOptions: gitlab.ListOptions{
|
||||
Page: int64(i),
|
||||
PerPage: int64(perPage),
|
||||
},
|
||||
}, nil, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, m := range ms {
|
||||
var desc string
|
||||
if m.Description != "" {
|
||||
desc = m.Description
|
||||
}
|
||||
state := "open"
|
||||
var closedAt *time.Time
|
||||
if m.State != "" {
|
||||
state = m.State
|
||||
if state == "closed" {
|
||||
closedAt = m.UpdatedAt
|
||||
}
|
||||
}
|
||||
|
||||
var deadline *time.Time
|
||||
if m.DueDate != nil {
|
||||
deadlineParsed, err := time.Parse("2006-01-02", m.DueDate.String())
|
||||
if err != nil {
|
||||
log.Trace("Error parsing Milestone DueDate time")
|
||||
deadline = nil
|
||||
} else {
|
||||
deadline = &deadlineParsed
|
||||
}
|
||||
}
|
||||
|
||||
milestones = append(milestones, &base.Milestone{
|
||||
Title: m.Title,
|
||||
Description: desc,
|
||||
Deadline: deadline,
|
||||
State: state,
|
||||
Created: *m.CreatedAt,
|
||||
Updated: m.UpdatedAt,
|
||||
Closed: closedAt,
|
||||
})
|
||||
}
|
||||
if len(ms) < perPage {
|
||||
break
|
||||
}
|
||||
}
|
||||
return milestones, nil
|
||||
}
|
||||
|
||||
func (g *GitlabDownloader) normalizeColor(val string) string {
|
||||
val = strings.TrimLeft(val, "#")
|
||||
val = strings.ToLower(val)
|
||||
if len(val) == 3 {
|
||||
c := []rune(val)
|
||||
val = fmt.Sprintf("%c%c%c%c%c%c", c[0], c[0], c[1], c[1], c[2], c[2])
|
||||
}
|
||||
if len(val) != 6 {
|
||||
return ""
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// GetLabels returns labels
|
||||
func (g *GitlabDownloader) GetLabels(ctx context.Context) ([]*base.Label, error) {
|
||||
perPage := g.maxPerPage
|
||||
labels := make([]*base.Label, 0, perPage)
|
||||
for i := 1; ; i++ {
|
||||
ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ListOptions: gitlab.ListOptions{
|
||||
Page: int64(i),
|
||||
PerPage: int64(perPage),
|
||||
}}, nil, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, label := range ls {
|
||||
baseLabel := &base.Label{
|
||||
Name: label.Name,
|
||||
Color: g.normalizeColor(label.Color),
|
||||
Description: label.Description,
|
||||
}
|
||||
labels = append(labels, baseLabel)
|
||||
}
|
||||
if len(ls) < perPage {
|
||||
break
|
||||
}
|
||||
}
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func (g *GitlabDownloader) convertGitlabRelease(ctx context.Context, rel *gitlab.Release) *base.Release {
|
||||
var zero int
|
||||
r := &base.Release{
|
||||
TagName: rel.TagName,
|
||||
TargetCommitish: rel.Commit.ID,
|
||||
Name: rel.Name,
|
||||
Body: rel.Description,
|
||||
Created: *rel.CreatedAt,
|
||||
PublisherID: rel.Author.ID,
|
||||
PublisherName: rel.Author.Username,
|
||||
}
|
||||
|
||||
httpClient := NewMigrationHTTPClient()
|
||||
|
||||
for _, asset := range rel.Assets.Links {
|
||||
assetID := asset.ID // Don't optimize this, for closure we need a local variable
|
||||
r.Assets = append(r.Assets, &base.ReleaseAsset{
|
||||
ID: asset.ID,
|
||||
Name: asset.Name,
|
||||
Size: &zero,
|
||||
DownloadCount: &zero,
|
||||
DownloadFunc: func() (io.ReadCloser, error) {
|
||||
link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, assetID, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !hasBaseURL(link.URL, g.baseURL) {
|
||||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", assetID, g, link.URL)
|
||||
return io.NopCloser(strings.NewReader(link.URL)), nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, link.URL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// resp.Body is closed by the uploader
|
||||
return resp.Body, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// GetReleases returns releases
|
||||
func (g *GitlabDownloader) GetReleases(ctx context.Context) ([]*base.Release, error) {
|
||||
perPage := g.maxPerPage
|
||||
releases := make([]*base.Release, 0, perPage)
|
||||
for i := 1; ; i++ {
|
||||
ls, _, err := g.client.Releases.ListReleases(g.repoID, &gitlab.ListReleasesOptions{
|
||||
ListOptions: gitlab.ListOptions{
|
||||
Page: int64(i),
|
||||
PerPage: int64(perPage),
|
||||
},
|
||||
}, nil, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, release := range ls {
|
||||
releases = append(releases, g.convertGitlabRelease(ctx, release))
|
||||
}
|
||||
if len(ls) < perPage {
|
||||
break
|
||||
}
|
||||
}
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
type gitlabIssueContext struct {
|
||||
IsMergeRequest bool
|
||||
}
|
||||
|
||||
// GetIssues returns issues according start and limit
|
||||
//
|
||||
// Note: issue label description and colors are not supported by the go-gitlab library at this time
|
||||
func (g *GitlabDownloader) GetIssues(ctx context.Context, page, perPage int) ([]*base.Issue, bool, error) {
|
||||
state := "all"
|
||||
sort := "asc"
|
||||
|
||||
if perPage > g.maxPerPage {
|
||||
perPage = g.maxPerPage
|
||||
}
|
||||
|
||||
opt := &gitlab.ListProjectIssuesOptions{
|
||||
State: &state,
|
||||
Sort: &sort,
|
||||
ListOptions: gitlab.ListOptions{
|
||||
PerPage: int64(perPage),
|
||||
Page: int64(page),
|
||||
},
|
||||
}
|
||||
|
||||
allIssues := make([]*base.Issue, 0, perPage)
|
||||
|
||||
issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error while listing issues: %w", err)
|
||||
}
|
||||
for _, issue := range issues {
|
||||
labels := make([]*base.Label, 0, len(issue.Labels))
|
||||
for _, l := range issue.Labels {
|
||||
labels = append(labels, &base.Label{
|
||||
Name: l,
|
||||
})
|
||||
}
|
||||
|
||||
var milestone string
|
||||
if issue.Milestone != nil {
|
||||
milestone = issue.Milestone.Title
|
||||
}
|
||||
|
||||
var reactions []*gitlab.AwardEmoji
|
||||
awardPage := 1
|
||||
for {
|
||||
awards, _, err := g.client.AwardEmoji.ListIssueAwardEmoji(g.repoID, issue.IID, &gitlab.ListAwardEmojiOptions{ListOptions: gitlab.ListOptions{Page: int64(awardPage), PerPage: int64(perPage)}}, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error while listing issue awards: %w", err)
|
||||
}
|
||||
|
||||
reactions = append(reactions, awards...)
|
||||
|
||||
if len(awards) < perPage {
|
||||
break
|
||||
}
|
||||
|
||||
awardPage++
|
||||
}
|
||||
|
||||
allIssues = append(allIssues, &base.Issue{
|
||||
Title: issue.Title,
|
||||
Number: issue.IID,
|
||||
PosterID: issue.Author.ID,
|
||||
PosterName: issue.Author.Username,
|
||||
Content: issue.Description,
|
||||
Milestone: milestone,
|
||||
State: issue.State,
|
||||
Created: *issue.CreatedAt,
|
||||
Labels: labels,
|
||||
Reactions: g.awardsToReactions(reactions),
|
||||
Closed: issue.ClosedAt,
|
||||
IsLocked: issue.DiscussionLocked,
|
||||
Updated: *issue.UpdatedAt,
|
||||
ForeignIndex: issue.IID,
|
||||
Context: gitlabIssueContext{IsMergeRequest: false},
|
||||
})
|
||||
|
||||
// record the issue IID, to be used in GetPullRequests()
|
||||
g.iidResolver.recordIssueIID(issue.IID)
|
||||
}
|
||||
|
||||
return allIssues, len(issues) < perPage, nil
|
||||
}
|
||||
|
||||
// GetComments returns comments according issueNumber
|
||||
// TODO: figure out how to transfer comment reactions
|
||||
func (g *GitlabDownloader) GetComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) {
|
||||
context, ok := commentable.GetContext().(gitlabIssueContext)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("unexpected context: %+v", commentable.GetContext())
|
||||
}
|
||||
|
||||
allComments := make([]*base.Comment, 0, g.maxPerPage)
|
||||
|
||||
var page int64 = 1
|
||||
|
||||
for {
|
||||
var comments []*gitlab.Discussion
|
||||
var resp *gitlab.Response
|
||||
var err error
|
||||
if !context.IsMergeRequest {
|
||||
comments, resp, err = g.client.Discussions.ListIssueDiscussions(g.repoID, commentable.GetForeignIndex(), &gitlab.ListIssueDiscussionsOptions{
|
||||
ListOptions: gitlab.ListOptions{
|
||||
Page: page,
|
||||
PerPage: int64(g.maxPerPage),
|
||||
},
|
||||
}, nil, gitlab.WithContext(ctx))
|
||||
} else {
|
||||
comments, resp, err = g.client.Discussions.ListMergeRequestDiscussions(g.repoID, commentable.GetForeignIndex(), &gitlab.ListMergeRequestDiscussionsOptions{
|
||||
ListOptions: gitlab.ListOptions{
|
||||
Page: page,
|
||||
PerPage: int64(g.maxPerPage),
|
||||
},
|
||||
}, nil, gitlab.WithContext(ctx))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error while listing comments: %v %w", g.repoID, err)
|
||||
}
|
||||
for _, comment := range comments {
|
||||
for _, note := range comment.Notes {
|
||||
allComments = append(allComments, g.convertNoteToComment(commentable.GetLocalIndex(), note))
|
||||
}
|
||||
}
|
||||
if resp.NextPage == 0 {
|
||||
break
|
||||
}
|
||||
page = resp.NextPage
|
||||
}
|
||||
|
||||
page = 1
|
||||
for {
|
||||
var stateEvents []*gitlab.StateEvent
|
||||
var resp *gitlab.Response
|
||||
var err error
|
||||
if context.IsMergeRequest {
|
||||
stateEvents, resp, err = g.client.ResourceStateEvents.ListMergeStateEvents(g.repoID, commentable.GetForeignIndex(), &gitlab.ListStateEventsOptions{
|
||||
ListOptions: gitlab.ListOptions{
|
||||
Page: page,
|
||||
PerPage: int64(g.maxPerPage),
|
||||
},
|
||||
}, nil, gitlab.WithContext(ctx))
|
||||
} else {
|
||||
stateEvents, resp, err = g.client.ResourceStateEvents.ListIssueStateEvents(g.repoID, commentable.GetForeignIndex(), &gitlab.ListStateEventsOptions{
|
||||
ListOptions: gitlab.ListOptions{
|
||||
Page: page,
|
||||
PerPage: int64(g.maxPerPage),
|
||||
},
|
||||
}, nil, gitlab.WithContext(ctx))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error while listing state events: %v %w", g.repoID, err)
|
||||
}
|
||||
|
||||
for _, stateEvent := range stateEvents {
|
||||
posterUserID, posterUsername := user.GhostUserID, user.GhostUserName
|
||||
if stateEvent.User != nil {
|
||||
posterUserID, posterUsername = stateEvent.User.ID, stateEvent.User.Username
|
||||
}
|
||||
comment := &base.Comment{
|
||||
IssueIndex: commentable.GetLocalIndex(),
|
||||
Index: stateEvent.ID,
|
||||
PosterID: posterUserID,
|
||||
PosterName: posterUsername,
|
||||
Content: "",
|
||||
Created: *stateEvent.CreatedAt,
|
||||
}
|
||||
switch stateEvent.State {
|
||||
case gitlab.ClosedEventType:
|
||||
comment.CommentType = issues_model.CommentTypeClose.String()
|
||||
case gitlab.MergedEventType:
|
||||
comment.CommentType = issues_model.CommentTypeMergePull.String()
|
||||
case gitlab.ReopenedEventType:
|
||||
comment.CommentType = issues_model.CommentTypeReopen.String()
|
||||
default:
|
||||
// Ignore other event types
|
||||
continue
|
||||
}
|
||||
allComments = append(allComments, comment)
|
||||
}
|
||||
|
||||
if resp.NextPage == 0 {
|
||||
break
|
||||
}
|
||||
page = resp.NextPage
|
||||
}
|
||||
|
||||
return allComments, true, nil
|
||||
}
|
||||
|
||||
var targetBranchChangeRegexp = regexp.MustCompile("^changed target branch from `(.*?)` to `(.*?)`$")
|
||||
|
||||
func (g *GitlabDownloader) convertNoteToComment(localIndex int64, note *gitlab.Note) *base.Comment {
|
||||
comment := &base.Comment{
|
||||
IssueIndex: localIndex,
|
||||
Index: note.ID,
|
||||
PosterID: note.Author.ID,
|
||||
PosterName: note.Author.Username,
|
||||
PosterEmail: note.Author.Email,
|
||||
Content: note.Body,
|
||||
Created: *note.CreatedAt,
|
||||
Meta: map[string]any{},
|
||||
}
|
||||
|
||||
// Try to find the underlying event of system notes.
|
||||
if note.System {
|
||||
if match := targetBranchChangeRegexp.FindStringSubmatch(note.Body); match != nil {
|
||||
comment.CommentType = issues_model.CommentTypeChangeTargetBranch.String()
|
||||
comment.Meta["OldRef"] = match[1]
|
||||
comment.Meta["NewRef"] = match[2]
|
||||
} else if strings.HasPrefix(note.Body, "enabled an automatic merge") {
|
||||
comment.CommentType = issues_model.CommentTypePRScheduledToAutoMerge.String()
|
||||
} else if note.Body == "canceled the automatic merge" {
|
||||
comment.CommentType = issues_model.CommentTypePRUnScheduledToAutoMerge.String()
|
||||
}
|
||||
}
|
||||
|
||||
return comment
|
||||
}
|
||||
|
||||
// GetPullRequests returns pull requests according page and perPage
|
||||
func (g *GitlabDownloader) GetPullRequests(ctx context.Context, page, perPage int) ([]*base.PullRequest, bool, error) {
|
||||
if perPage > g.maxPerPage {
|
||||
perPage = g.maxPerPage
|
||||
}
|
||||
|
||||
view := "simple"
|
||||
opt := &gitlab.ListProjectMergeRequestsOptions{
|
||||
ListOptions: gitlab.ListOptions{
|
||||
PerPage: int64(perPage),
|
||||
Page: int64(page),
|
||||
},
|
||||
View: &view,
|
||||
}
|
||||
|
||||
allPRs := make([]*base.PullRequest, 0, perPage)
|
||||
|
||||
prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error while listing merge requests: %w", err)
|
||||
}
|
||||
for _, simplePR := range prs {
|
||||
// Load merge request again by itself, as not all fields are populated in the ListProjectMergeRequests endpoint.
|
||||
// See https://gitlab.com/gitlab-org/gitlab/-/issues/29620
|
||||
pr, _, err := g.client.MergeRequests.GetMergeRequest(g.repoID, simplePR.IID, nil)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error while loading merge request: %w", err)
|
||||
}
|
||||
|
||||
labels := make([]*base.Label, 0, len(pr.Labels))
|
||||
for _, l := range pr.Labels {
|
||||
labels = append(labels, &base.Label{
|
||||
Name: l,
|
||||
})
|
||||
}
|
||||
|
||||
var merged bool
|
||||
if pr.State == "merged" {
|
||||
merged = true
|
||||
pr.State = "closed"
|
||||
}
|
||||
|
||||
mergeTime := pr.MergedAt
|
||||
if merged && pr.MergedAt == nil {
|
||||
mergeTime = pr.UpdatedAt
|
||||
}
|
||||
|
||||
closeTime := pr.ClosedAt
|
||||
if merged && pr.ClosedAt == nil {
|
||||
closeTime = pr.UpdatedAt
|
||||
}
|
||||
|
||||
mergeCommitSHA := pr.MergeCommitSHA
|
||||
if mergeCommitSHA == "" {
|
||||
mergeCommitSHA = pr.SquashCommitSHA
|
||||
}
|
||||
|
||||
var locked bool
|
||||
if pr.State == "locked" {
|
||||
locked = true
|
||||
}
|
||||
|
||||
var milestone string
|
||||
if pr.Milestone != nil {
|
||||
milestone = pr.Milestone.Title
|
||||
}
|
||||
|
||||
var reactions []*gitlab.AwardEmoji
|
||||
awardPage := 1
|
||||
for {
|
||||
awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{ListOptions: gitlab.ListOptions{Page: int64(awardPage), PerPage: int64(perPage)}}, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error while listing merge requests awards: %w", err)
|
||||
}
|
||||
|
||||
reactions = append(reactions, awards...)
|
||||
|
||||
if len(awards) < perPage {
|
||||
break
|
||||
}
|
||||
|
||||
awardPage++
|
||||
}
|
||||
|
||||
// Generate new PR Numbers by the known Issue Numbers, because they share the same number space in Gitea, but they are independent in Gitlab
|
||||
newPRNumber := g.iidResolver.generatePullRequestNumber(pr.IID)
|
||||
|
||||
allPRs = append(allPRs, &base.PullRequest{
|
||||
Title: pr.Title,
|
||||
Number: newPRNumber,
|
||||
PosterName: pr.Author.Username,
|
||||
PosterID: pr.Author.ID,
|
||||
Content: pr.Description,
|
||||
Milestone: milestone,
|
||||
State: pr.State,
|
||||
Created: *pr.CreatedAt,
|
||||
Closed: closeTime,
|
||||
Labels: labels,
|
||||
Merged: merged,
|
||||
MergeCommitSHA: mergeCommitSHA,
|
||||
MergedTime: mergeTime,
|
||||
IsLocked: locked,
|
||||
Reactions: g.awardsToReactions(reactions),
|
||||
Head: base.PullRequestBranch{
|
||||
Ref: pr.SourceBranch,
|
||||
SHA: pr.SHA,
|
||||
RepoName: g.repoName,
|
||||
OwnerName: pr.Author.Username,
|
||||
CloneURL: pr.WebURL,
|
||||
},
|
||||
Base: base.PullRequestBranch{
|
||||
Ref: pr.TargetBranch,
|
||||
SHA: pr.DiffRefs.BaseSha,
|
||||
RepoName: g.repoName,
|
||||
OwnerName: pr.Author.Username,
|
||||
},
|
||||
PatchURL: pr.WebURL + ".patch",
|
||||
ForeignIndex: pr.IID,
|
||||
Context: gitlabIssueContext{IsMergeRequest: true},
|
||||
IsDraft: pr.Draft,
|
||||
})
|
||||
|
||||
// SECURITY: Ensure that the PR is safe
|
||||
_ = CheckAndEnsureSafePR(allPRs[len(allPRs)-1], g.baseURL, g)
|
||||
}
|
||||
|
||||
return allPRs, len(prs) < perPage, nil
|
||||
}
|
||||
|
||||
// GetReviews returns pull requests review
|
||||
func (g *GitlabDownloader) GetReviews(ctx context.Context, reviewable base.Reviewable) ([]*base.Review, error) {
|
||||
approvals, resp, err := g.client.MergeRequestApprovals.GetConfiguration(g.repoID, reviewable.GetForeignIndex(), gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
log.Error(fmt.Sprintf("GitlabDownloader: while migrating a error occurred: '%s'", err.Error()))
|
||||
return []*base.Review{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var createdAt time.Time
|
||||
if approvals.CreatedAt != nil {
|
||||
createdAt = *approvals.CreatedAt
|
||||
} else if approvals.UpdatedAt != nil {
|
||||
createdAt = *approvals.UpdatedAt
|
||||
} else {
|
||||
createdAt = time.Now()
|
||||
}
|
||||
|
||||
reviews := make([]*base.Review, 0, len(approvals.ApprovedBy))
|
||||
for _, user := range approvals.ApprovedBy {
|
||||
reviews = append(reviews, &base.Review{
|
||||
IssueIndex: reviewable.GetLocalIndex(),
|
||||
ReviewerID: user.User.ID,
|
||||
ReviewerName: user.User.Username,
|
||||
CreatedAt: createdAt,
|
||||
// All we get are approvals
|
||||
State: base.ReviewStateApproved,
|
||||
})
|
||||
}
|
||||
|
||||
return reviews, nil
|
||||
}
|
||||
|
||||
func (g *GitlabDownloader) awardsToReactions(awards []*gitlab.AwardEmoji) []*base.Reaction {
|
||||
result := make([]*base.Reaction, 0, len(awards))
|
||||
uniqCheck := make(container.Set[string])
|
||||
for _, award := range awards {
|
||||
uid := fmt.Sprintf("%s%d", award.Name, award.User.ID)
|
||||
if uniqCheck.Add(uid) {
|
||||
result = append(result, &base.Reaction{
|
||||
UserID: award.User.ID,
|
||||
UserName: award.User.Username,
|
||||
Content: award.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user