diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2021-11-16 23:25:33 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-16 23:25:33 +0800 |
commit | 7e1ae380975df0afab3fdc04c7a926181e5daba9 (patch) | |
tree | a6fa4eb2d15b88fc4ff953d748ee3937ff10446b /modules/migrations/gitlab.go | |
parent | 48ccd325a1b81a58ac6d1d5d94fc4e90974599ea (diff) | |
download | gitea-7e1ae380975df0afab3fdc04c7a926181e5daba9.tar.gz gitea-7e1ae380975df0afab3fdc04c7a926181e5daba9.zip |
Move migrations into services and base into modules/migration (#17663)
* Move migrtions into services and base into modules/migration
* Fix imports
* Fix lint
Diffstat (limited to 'modules/migrations/gitlab.go')
-rw-r--r-- | modules/migrations/gitlab.go | 678 |
1 files changed, 0 insertions, 678 deletions
diff --git a/modules/migrations/gitlab.go b/modules/migrations/gitlab.go deleted file mode 100644 index 91ba073d18..0000000000 --- a/modules/migrations/gitlab.go +++ /dev/null @@ -1,678 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package migrations - -import ( - "context" - "crypto/tls" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "path" - "strings" - "time" - - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/migrations/base" - "code.gitea.io/gitea/modules/proxy" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/structs" - - "github.com/xanzy/go-gitlab" -) - -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.AuthUsername, opts.AuthPassword, opts.AuthToken) -} - -// GitServiceType returns the type of git service -func (f *GitlabDownloaderFactory) GitServiceType() structs.GitServiceType { - return structs.GitlabService -} - -// 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 - ctx context.Context - client *gitlab.Client - repoID int - repoName string - issueCount int64 - 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, username, password, token string) (*GitlabDownloader, error) { - gitlabClient, err := gitlab.NewClient(token, gitlab.WithBaseURL(baseURL), gitlab.WithHTTPClient(&http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Migrations.SkipTLSVerify}, - Proxy: proxy.Proxy(), - }, - })) - // Only use basic auth if token is blank and password is NOT - // Basic auth will fail with empty strings, but empty token will allow anonymous public API usage - if token == "" && password != "" { - gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL)) - } - - 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 == 401 { - 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{ - ctx: ctx, - client: gitlabClient, - repoID: gr.ID, - repoName: gr.Name, - maxPerPage: 100, - }, nil -} - -// SetContext set context -func (g *GitlabDownloader) SetContext(ctx context.Context) { - g.ctx = ctx -} - -// GetRepoInfo returns a repository information -func (g *GitlabDownloader) GetRepoInfo() (*base.Repository, error) { - gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(g.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() ([]string, error) { - gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(g.ctx)) - if err != nil { - return nil, err - } - return gr.TagList, err -} - -// GetMilestones returns milestones -func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) { - var perPage = g.maxPerPage - var state = "all" - var 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: i, - PerPage: perPage, - }}, nil, gitlab.WithContext(g.ctx)) - if err != nil { - return nil, err - } - - for _, m := range ms { - var desc string - if m.Description != "" { - desc = m.Description - } - var 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() ([]*base.Label, error) { - var perPage = g.maxPerPage - var labels = make([]*base.Label, 0, perPage) - for i := 1; ; i++ { - ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ListOptions: gitlab.ListOptions{ - Page: i, - PerPage: perPage, - }}, nil, gitlab.WithContext(g.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(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: int64(rel.Author.ID), - PublisherName: rel.Author.Username, - } - - httpClient := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Migrations.SkipTLSVerify}, - Proxy: proxy.Proxy(), - }, - } - - for k, asset := range rel.Assets.Links { - r.Assets = append(r.Assets, &base.ReleaseAsset{ - ID: int64(asset.ID), - Name: asset.Name, - ContentType: &rel.Assets.Sources[k].Format, - Size: &zero, - DownloadCount: &zero, - DownloadFunc: func() (io.ReadCloser, error) { - link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, asset.ID, gitlab.WithContext(g.ctx)) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", link.URL, nil) - if err != nil { - return nil, err - } - req = req.WithContext(g.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() ([]*base.Release, error) { - var perPage = g.maxPerPage - var releases = make([]*base.Release, 0, perPage) - for i := 1; ; i++ { - ls, _, err := g.client.Releases.ListReleases(g.repoID, &gitlab.ListReleasesOptions{ - Page: i, - PerPage: perPage, - }, nil, gitlab.WithContext(g.ctx)) - if err != nil { - return nil, err - } - - for _, release := range ls { - releases = append(releases, g.convertGitlabRelease(release)) - } - if len(ls) < perPage { - break - } - } - return releases, nil -} - -type gitlabIssueContext struct { - foreignID int64 - localID int64 - IsMergeRequest bool -} - -func (c gitlabIssueContext) LocalID() int64 { - return c.localID -} - -func (c gitlabIssueContext) ForeignID() int64 { - return c.foreignID -} - -// 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(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: perPage, - Page: page, - }, - } - - var allIssues = make([]*base.Issue, 0, perPage) - - issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil, gitlab.WithContext(g.ctx)) - if err != nil { - return nil, false, fmt.Errorf("error while listing issues: %v", err) - } - for _, issue := range issues { - - var 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 []*base.Reaction - var awardPage = 1 - for { - awards, _, err := g.client.AwardEmoji.ListIssueAwardEmoji(g.repoID, issue.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx)) - if err != nil { - return nil, false, fmt.Errorf("error while listing issue awards: %v", err) - } - - for i := range awards { - reactions = append(reactions, g.awardToReaction(awards[i])) - } - - if len(awards) < perPage { - break - } - - awardPage++ - } - - allIssues = append(allIssues, &base.Issue{ - Title: issue.Title, - Number: int64(issue.IID), - PosterID: int64(issue.Author.ID), - PosterName: issue.Author.Username, - Content: issue.Description, - Milestone: milestone, - State: issue.State, - Created: *issue.CreatedAt, - Labels: labels, - Reactions: reactions, - Closed: issue.ClosedAt, - IsLocked: issue.DiscussionLocked, - Updated: *issue.UpdatedAt, - Context: gitlabIssueContext{ - foreignID: int64(issue.IID), - localID: int64(issue.IID), - IsMergeRequest: false, - }, - }) - - // increment issueCount, to be used in GetPullRequests() - g.issueCount++ - } - - return allIssues, len(issues) < perPage, nil -} - -// GetComments returns comments according issueNumber -// TODO: figure out how to transfer comment reactions -func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Comment, bool, error) { - context, ok := opts.Context.(gitlabIssueContext) - if !ok { - return nil, false, fmt.Errorf("unexpected context: %+v", opts.Context) - } - - var allComments = make([]*base.Comment, 0, g.maxPerPage) - - var page = 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, int(context.ForeignID()), &gitlab.ListIssueDiscussionsOptions{ - Page: page, - PerPage: g.maxPerPage, - }, nil, gitlab.WithContext(g.ctx)) - } else { - comments, resp, err = g.client.Discussions.ListMergeRequestDiscussions(g.repoID, int(context.ForeignID()), &gitlab.ListMergeRequestDiscussionsOptions{ - Page: page, - PerPage: g.maxPerPage, - }, nil, gitlab.WithContext(g.ctx)) - } - - if err != nil { - return nil, false, fmt.Errorf("error while listing comments: %v %v", g.repoID, err) - } - for _, comment := range comments { - // Flatten comment threads - if !comment.IndividualNote { - for _, note := range comment.Notes { - allComments = append(allComments, &base.Comment{ - IssueIndex: context.LocalID(), - PosterID: int64(note.Author.ID), - PosterName: note.Author.Username, - PosterEmail: note.Author.Email, - Content: note.Body, - Created: *note.CreatedAt, - }) - } - } else { - c := comment.Notes[0] - allComments = append(allComments, &base.Comment{ - IssueIndex: context.LocalID(), - PosterID: int64(c.Author.ID), - PosterName: c.Author.Username, - PosterEmail: c.Author.Email, - Content: c.Body, - Created: *c.CreatedAt, - }) - } - - } - if resp.NextPage == 0 { - break - } - page = resp.NextPage - } - return allComments, true, nil -} - -// GetPullRequests returns pull requests according page and perPage -func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { - if perPage > g.maxPerPage { - perPage = g.maxPerPage - } - - opt := &gitlab.ListProjectMergeRequestsOptions{ - ListOptions: gitlab.ListOptions{ - PerPage: perPage, - Page: page, - }, - } - - var allPRs = make([]*base.PullRequest, 0, perPage) - - prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(g.ctx)) - if err != nil { - return nil, false, fmt.Errorf("error while listing merge requests: %v", err) - } - for _, pr := range prs { - - var 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" - } - - var mergeTime = pr.MergedAt - if merged && pr.MergedAt == nil { - mergeTime = pr.UpdatedAt - } - - var closeTime = pr.ClosedAt - if merged && pr.ClosedAt == nil { - closeTime = pr.UpdatedAt - } - - var locked bool - if pr.State == "locked" { - locked = true - } - - var milestone string - if pr.Milestone != nil { - milestone = pr.Milestone.Title - } - - var reactions []*base.Reaction - var awardPage = 1 - for { - awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx)) - if err != nil { - return nil, false, fmt.Errorf("error while listing merge requests awards: %v", err) - } - - for i := range awards { - reactions = append(reactions, g.awardToReaction(awards[i])) - } - - if len(awards) < perPage { - break - } - - awardPage++ - } - - // Add the PR ID to the Issue Count because PR and Issues share ID space in Gitea - newPRNumber := g.issueCount + int64(pr.IID) - - allPRs = append(allPRs, &base.PullRequest{ - Title: pr.Title, - Number: newPRNumber, - PosterName: pr.Author.Username, - PosterID: int64(pr.Author.ID), - Content: pr.Description, - Milestone: milestone, - State: pr.State, - Created: *pr.CreatedAt, - Closed: closeTime, - Labels: labels, - Merged: merged, - MergeCommitSHA: pr.MergeCommitSHA, - MergedTime: mergeTime, - IsLocked: locked, - Reactions: 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", - Context: gitlabIssueContext{ - foreignID: int64(pr.IID), - localID: newPRNumber, - IsMergeRequest: true, - }, - }) - } - - return allPRs, len(prs) < perPage, nil -} - -// GetReviews returns pull requests review -func (g *GitlabDownloader) GetReviews(context base.IssueContext) ([]*base.Review, error) { - approvals, resp, err := g.client.MergeRequestApprovals.GetConfiguration(g.repoID, int(context.ForeignID()), gitlab.WithContext(g.ctx)) - if err != nil { - if resp != nil && resp.StatusCode == 404 { - log.Error(fmt.Sprintf("GitlabDownloader: while migrating a error occurred: '%s'", err.Error())) - return []*base.Review{}, nil - } - return nil, err - } - - var reviews = make([]*base.Review, 0, len(approvals.ApprovedBy)) - for _, user := range approvals.ApprovedBy { - reviews = append(reviews, &base.Review{ - IssueIndex: context.LocalID(), - ReviewerID: int64(user.User.ID), - ReviewerName: user.User.Username, - CreatedAt: *approvals.UpdatedAt, - // All we get are approvals - State: base.ReviewStateApproved, - }) - } - - return reviews, nil -} - -func (g *GitlabDownloader) awardToReaction(award *gitlab.AwardEmoji) *base.Reaction { - return &base.Reaction{ - UserID: int64(award.User.ID), - UserName: award.User.Username, - Content: award.Name, - } -} |