summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorJordan <eatsleepgame@gmail.com>2020-04-19 10:44:11 -0500
committerGitHub <noreply@github.com>2020-04-19 23:44:11 +0800
commit5c092eb0ef9347e88db59618902781e099880196 (patch)
treebd0d54eaea67bf916c2a23e678a253bbb47e3dc7 /modules
parent41f05588edf8026e43e799dd56114ac001bd7589 (diff)
downloadgitea-5c092eb0ef9347e88db59618902781e099880196.tar.gz
gitea-5c092eb0ef9347e88db59618902781e099880196.zip
Add support for migrating from Gitlab (#9084)
* First stab at a Gitlab migrations interface. * Modify JS to show migration for Gitlab * Properly strip out #gitlab tag from repo name * Working Gitlab migrations! Still need to figure out how to hide tokens/etc from showing up in opts.CloneAddr * Try #2 at trying to hide credentials. CloneAddr was being used as OriginalURL. Now passing OriginalURL through from the form and saving it. * Add go-gitlab dependency * Vendor go-gitlab * Use gitlab.BasicAuthClient Correct CloneURL. This should be functioning! Previous commits fixed "Migrated from" from including the migration credentials. * Replaced repoPath with repoID globally. RepoID is grabbed in NewGitlabDownloader * Logging touchup * Properly set private repo status. Properly set milestone deadline time. Consistently use Gitlab username for 'Name'. * Add go-gitlab vendor cache * Fix PR migrations: - Count of issues is kept to set a non-conflicting PR.ID - Bool is used to tell whether to fetch Issue or PR comments * Ensure merged PRs are closed and set with the proper time * Remove copyright and some commented code * Rip out '#gitlab' based self-hosted Gitlab support * Hide given credentials for migrated repos. CloneAddr was being saved as OriginalURL. Now passing OriginalURL through from the form and saving it in it's place * Use asset.URL directly, no point in parsing. Opened PRs should fall through to false. * Fix importing Milestones. Allow importing using Personal Tokens or anonymous access. * Fix Gitlab Milestone migration if DueDate isn't set * Empty Milestone due dates properly return nil, not zero time * Add GITLAB_READ_TOKEN to drone unit-test step * Add working gitlab_test.go. A Personal Access Token, given in env variable GITLAB_READ_TOKEN is required to run the test. * Fix linting issues * Add modified JS files * Remove pre-build JS files * Only merged PRs are marged as merged/closed * Test topics * Skip test if gitlab is inaccessible * Grab personal token from username, not password. Matches Github migration implementation * Add SetContext() to GitlabDownloader. * Checking Updated field in Issues. * Actually fetch Issue Updated time from Gitlab * Add Gitlab migration GetReviews() stub * Fix Patch and Clone URLs * check Updated too * fix mod * make vendor with go1.14 Co-authored-by: techknowlogick <techknowlogick@gitea.io> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Diffstat (limited to 'modules')
-rw-r--r--modules/migrations/gitlab.go539
-rw-r--r--modules/migrations/gitlab_test.go246
-rw-r--r--modules/structs/repo.go1
3 files changed, 786 insertions, 0 deletions
diff --git a/modules/migrations/gitlab.go b/modules/migrations/gitlab.go
new file mode 100644
index 0000000000..843e90c177
--- /dev/null
+++ b/modules/migrations/gitlab.go
@@ -0,0 +1,539 @@
+// 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"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/migrations/base"
+ "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 {
+}
+
+// Match returns ture if the migration remote URL matched this downloader factory
+func (f *GitlabDownloaderFactory) Match(opts base.MigrateOptions) (bool, error) {
+ var matched bool
+
+ u, err := url.Parse(opts.CloneAddr)
+ if err != nil {
+ return false, err
+ }
+ if strings.EqualFold(u.Host, "gitlab.com") && opts.AuthUsername != "" {
+ matched = true
+ }
+
+ return matched, nil
+}
+
+// New returns a Downloader related to this factory according MigrateOptions
+func (f *GitlabDownloaderFactory) New(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, "/")
+
+ log.Trace("Create gitlab downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace)
+
+ return NewGitlabDownloader(baseURL, repoNameSpace, opts.AuthUsername, opts.AuthPassword), nil
+}
+
+// GitServiceType returns the type of git service
+func (f *GitlabDownloaderFactory) GitServiceType() structs.GitServiceType {
+ return structs.GitlabService
+}
+
+// GitlabDownloader implements a Downloader interface to get repository informations
+// 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.
+// - issueSeen, working alongside issueCount, is checked in GetComments() to see whether we
+// need to fetch the Issue or PR comments, as Gitlab stores them separately.
+type GitlabDownloader struct {
+ ctx context.Context
+ client *gitlab.Client
+ repoID int
+ repoName string
+ issueCount int64
+ fetchPRcomments bool
+}
+
+// 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(baseURL, repoPath, username, password string) *GitlabDownloader {
+ var client *http.Client
+ var gitlabClient *gitlab.Client
+ var err error
+ if username != "" {
+ if password == "" {
+ gitlabClient = gitlab.NewClient(client, username)
+ } else {
+ gitlabClient, err = gitlab.NewBasicAuthClient(client, baseURL, username, password)
+
+ }
+ }
+
+ if err != nil {
+ log.Trace("Error logging into gitlab: %v", err)
+ return nil
+ }
+
+ // Grab and store project/repo ID here, due to issues using the URL escaped path
+ gr, _, err := gitlabClient.Projects.GetProject(repoPath, nil, nil)
+ if err != nil {
+ log.Trace("Error retrieving project: %v", err)
+ return nil
+ }
+
+ if gr == nil {
+ log.Trace("Error getting project, project is nil")
+ return nil
+ }
+
+ return &GitlabDownloader{
+ ctx: context.Background(),
+ client: gitlabClient,
+ repoID: gr.ID,
+ repoName: gr.Name,
+ }
+}
+
+// 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) {
+ if g == nil {
+ return nil, errors.New("error: GitlabDownloader is nil")
+ }
+
+ gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil)
+ 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,
+ }, nil
+}
+
+// GetTopics return gitlab topics
+func (g *GitlabDownloader) GetTopics() ([]string, error) {
+ if g == nil {
+ return nil, errors.New("error: GitlabDownloader is nil")
+ }
+
+ gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ return gr.TagList, err
+}
+
+// GetMilestones returns milestones
+func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) {
+ if g == nil {
+ return nil, errors.New("error: GitlabDownloader is nil")
+ }
+ var perPage = 100
+ 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)
+ 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
+}
+
+// GetLabels returns labels
+func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) {
+ if g == nil {
+ return nil, errors.New("error: GitlabDownloader is nil")
+ }
+ var perPage = 100
+ var labels = make([]*base.Label, 0, perPage)
+ for i := 1; ; i++ {
+ ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{
+ Page: i,
+ PerPage: perPage,
+ }, nil)
+ if err != nil {
+ return nil, err
+ }
+ for _, label := range ls {
+ baseLabel := &base.Label{
+ Name: label.Name,
+ Color: strings.TrimLeft(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 {
+
+ 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,
+ }
+
+ for k, asset := range rel.Assets.Links {
+ r.Assets = append(r.Assets, base.ReleaseAsset{
+ URL: asset.URL,
+ Name: asset.Name,
+ ContentType: &rel.Assets.Sources[k].Format,
+ })
+ }
+ return r
+}
+
+// GetReleases returns releases
+func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) {
+ var perPage = 100
+ 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)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, release := range ls {
+ releases = append(releases, g.convertGitlabRelease(release))
+ }
+ if len(ls) < perPage {
+ break
+ }
+ }
+ return releases, nil
+}
+
+// GetIssues returns issues according start and limit
+// Note: issue label description and colors are not supported by the go-gitlab library at this time
+// TODO: figure out how to transfer issue reactions
+func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
+ state := "all"
+ sort := "asc"
+
+ 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)
+ 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
+ }
+
+ 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,
+ Closed: issue.ClosedAt,
+ IsLocked: issue.DiscussionLocked,
+ Updated: *issue.UpdatedAt,
+ })
+
+ // increment issueCount, to be used in GetPullRequests()
+ g.issueCount++
+ }
+
+ return allIssues, len(issues) < perPage, nil
+}
+
+// GetComments returns comments according issueNumber
+func (g *GitlabDownloader) GetComments(issueNumber int64) ([]*base.Comment, error) {
+ var allComments = make([]*base.Comment, 0, 100)
+
+ var page = 1
+ var realIssueNumber int64
+
+ for {
+ var comments []*gitlab.Discussion
+ var resp *gitlab.Response
+ var err error
+ // fetchPRcomments decides whether to fetch Issue or PR comments
+ if !g.fetchPRcomments {
+ realIssueNumber = issueNumber
+ comments, resp, err = g.client.Discussions.ListIssueDiscussions(g.repoID, int(realIssueNumber), &gitlab.ListIssueDiscussionsOptions{
+ Page: page,
+ PerPage: 100,
+ }, nil)
+ } else {
+ // If this is a PR, we need to figure out the Gitlab/original PR ID to be passed below
+ realIssueNumber = issueNumber - g.issueCount
+ comments, resp, err = g.client.Discussions.ListMergeRequestDiscussions(g.repoID, int(realIssueNumber), &gitlab.ListMergeRequestDiscussionsOptions{
+ Page: page,
+ PerPage: 100,
+ }, nil)
+ }
+
+ if err != nil {
+ return nil, 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: realIssueNumber,
+ 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: realIssueNumber,
+ 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, nil
+}
+
+// GetPullRequests returns pull requests according page and perPage
+func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, error) {
+
+ opt := &gitlab.ListProjectMergeRequestsOptions{
+ ListOptions: gitlab.ListOptions{
+ PerPage: perPage,
+ Page: page,
+ },
+ }
+
+ // Set fetchPRcomments to true here, so PR comments are fetched instead of Issue comments
+ g.fetchPRcomments = true
+
+ var allPRs = make([]*base.PullRequest, 0, perPage)
+
+ prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil)
+ if err != nil {
+ return nil, 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
+ }
+
+ // 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: int64(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,
+ 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",
+ })
+ }
+
+ return allPRs, nil
+}
+
+// GetReviews returns pull requests review
+func (g *GitlabDownloader) GetReviews(pullRequestNumber int64) ([]*base.Review, error) {
+
+ return nil, nil
+}
diff --git a/modules/migrations/gitlab_test.go b/modules/migrations/gitlab_test.go
new file mode 100644
index 0000000000..8525d7e9ce
--- /dev/null
+++ b/modules/migrations/gitlab_test.go
@@ -0,0 +1,246 @@
+// 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 (
+ "net/http"
+ "os"
+ "testing"
+ "time"
+
+ "code.gitea.io/gitea/modules/migrations/base"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGitlabDownloadRepo(t *testing.T) {
+ // Skip tests if Gitlab token is not found
+ gitlabPersonalAccessToken := os.Getenv("GITLAB_READ_TOKEN")
+ if gitlabPersonalAccessToken == "" {
+ t.Skip("skipped test because GITLAB_READ_TOKEN was not in the environment")
+ }
+
+ resp, err := http.Get("https://gitlab.com/gitea/test_repo")
+ if err != nil || resp.StatusCode != 200 {
+ t.Skipf("Can't access test repo, skipping %s", t.Name())
+ }
+
+ downloader := NewGitlabDownloader("https://gitlab.com", "gitea/test_repo", gitlabPersonalAccessToken, "")
+ if downloader == nil {
+ t.Fatal("NewGitlabDownloader is nil")
+ }
+ repo, err := downloader.GetRepoInfo()
+ assert.NoError(t, err)
+ // Repo Owner is blank in Gitlab Group repos
+ assert.EqualValues(t, &base.Repository{
+ Name: "test_repo",
+ Owner: "",
+ Description: "Test repository for testing migration from gitlab to gitea",
+ CloneURL: "https://gitlab.com/gitea/test_repo.git",
+ OriginalURL: "https://gitlab.com/gitea/test_repo",
+ }, repo)
+
+ topics, err := downloader.GetTopics()
+ assert.NoError(t, err)
+ assert.True(t, len(topics) == 2)
+ assert.EqualValues(t, []string{"migration", "test"}, topics)
+
+ milestones, err := downloader.GetMilestones()
+ assert.NoError(t, err)
+ assert.True(t, len(milestones) >= 2)
+
+ for _, milestone := range milestones {
+ switch milestone.Title {
+ case "1.0":
+ assertMilestoneEqual(t, "", "1.0",
+ "",
+ "2019-11-28 08:42:30.301 +0000 UTC",
+ "2019-11-28 15:57:52.401 +0000 UTC",
+ "",
+ "closed", milestone)
+ case "1.1.0":
+ assertMilestoneEqual(t, "", "1.1.0",
+ "",
+ "2019-11-28 08:42:44.575 +0000 UTC",
+ "2019-11-28 08:42:44.575 +0000 UTC",
+ "",
+ "active", milestone)
+ }
+ }
+
+ labels, err := downloader.GetLabels()
+ assert.NoError(t, err)
+ assert.True(t, len(labels) >= 9)
+ for _, l := range labels {
+ switch l.Name {
+ case "bug":
+ assertLabelEqual(t, "bug", "d9534f", "", l)
+ case "documentation":
+ assertLabelEqual(t, "documentation", "f0ad4e", "", l)
+ case "confirmed":
+ assertLabelEqual(t, "confirmed", "d9534f", "", l)
+ case "enhancement":
+ assertLabelEqual(t, "enhancement", "5cb85c", "", l)
+ case "critical":
+ assertLabelEqual(t, "critical", "d9534f", "", l)
+ case "discussion":
+ assertLabelEqual(t, "discussion", "428bca", "", l)
+ case "suggestion":
+ assertLabelEqual(t, "suggestion", "428bca", "", l)
+ case "support":
+ assertLabelEqual(t, "support", "f0ad4e", "", l)
+ case "duplicate":
+ assertLabelEqual(t, "duplicate", "7F8C8D", "", l)
+ }
+ }
+
+ releases, err := downloader.GetReleases()
+ assert.NoError(t, err)
+ assert.EqualValues(t, []*base.Release{
+ {
+ TagName: "v0.9.99",
+ TargetCommitish: "0720a3ec57c1f843568298117b874319e7deee75",
+ Name: "First Release",
+ Body: "A test release",
+ Created: time.Date(2019, 11, 28, 9, 9, 48, 840000000, time.UTC),
+ PublisherID: 1241334,
+ PublisherName: "lafriks",
+ },
+ }, releases[len(releases)-1:])
+
+ // downloader.GetIssues()
+ issues, isEnd, err := downloader.GetIssues(1, 2)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 2, len(issues))
+ assert.False(t, isEnd)
+
+ var (
+ closed1 = time.Date(2019, 11, 28, 8, 46, 23, 275000000, time.UTC)
+ closed2 = time.Date(2019, 11, 28, 8, 45, 44, 959000000, time.UTC)
+ )
+ assert.EqualValues(t, []*base.Issue{
+ {
+ Number: 1,
+ Title: "Please add an animated gif icon to the merge button",
+ Content: "I just want the merge button to hurt my eyes a little. :stuck_out_tongue_closed_eyes:",
+ Milestone: "1.0.0",
+ PosterID: 1241334,
+ PosterName: "lafriks",
+ State: "closed",
+ Created: time.Date(2019, 11, 28, 8, 43, 35, 459000000, time.UTC),
+ Updated: time.Date(2019, 11, 28, 8, 46, 23, 275000000, time.UTC),
+ Labels: []*base.Label{
+ {
+ Name: "bug",
+ },
+ {
+ Name: "discussion",
+ },
+ },
+ Reactions: nil,
+ Closed: &closed1,
+ },
+ {
+ Number: 2,
+ Title: "Test issue",
+ Content: "This is test issue 2, do not touch!",
+ Milestone: "1.1.0",
+ PosterID: 1241334,
+ PosterName: "lafriks",
+ State: "closed",
+ Created: time.Date(2019, 11, 28, 8, 44, 46, 277000000, time.UTC),
+ Updated: time.Date(2019, 11, 28, 8, 45, 44, 987000000, time.UTC),
+ Labels: []*base.Label{
+ {
+ Name: "duplicate",
+ },
+ },
+ Reactions: nil,
+ Closed: &closed2,
+ },
+ }, issues)
+
+ // downloader.GetComments()
+ comments, err := downloader.GetComments(2)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 4, len(comments))
+ assert.EqualValues(t, []*base.Comment{
+ {
+ IssueIndex: 2,
+ PosterID: 1241334,
+ PosterName: "lafriks",
+ Created: time.Date(2019, 11, 28, 8, 44, 52, 501000000, time.UTC),
+ Updated: time.Date(2019, 11, 28, 8, 44, 52, 501000000, time.UTC),
+ Content: "This is a comment",
+ Reactions: nil,
+ },
+ {
+ IssueIndex: 2,
+ PosterID: 1241334,
+ PosterName: "lafriks",
+ Created: time.Date(2019, 11, 28, 8, 45, 2, 329000000, time.UTC),
+ Content: "changed milestone to %2",
+ Reactions: nil,
+ },
+ {
+ IssueIndex: 2,
+ PosterID: 1241334,
+ PosterName: "lafriks",
+ Created: time.Date(2019, 11, 28, 8, 45, 45, 7000000, time.UTC),
+ Content: "closed",
+ Reactions: nil,
+ },
+ {
+ IssueIndex: 2,
+ PosterID: 1241334,
+ PosterName: "lafriks",
+ Created: time.Date(2019, 11, 28, 8, 45, 53, 501000000, time.UTC),
+ Content: "A second comment",
+ Reactions: nil,
+ },
+ }, comments[:4])
+
+ // downloader.GetPullRequests()
+ prs, err := downloader.GetPullRequests(1, 1)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 1, len(prs))
+
+ assert.EqualValues(t, []*base.PullRequest{
+ {
+ Number: 4,
+ Title: "Test branch",
+ Content: "do not merge this PR",
+ Milestone: "1.0.0",
+ PosterID: 1241334,
+ PosterName: "lafriks",
+ State: "opened",
+ Created: time.Date(2019, 11, 28, 15, 56, 54, 104000000, time.UTC),
+ Updated: time.Date(2019, 11, 28, 15, 56, 54, 104000000, time.UTC),
+ Labels: []*base.Label{
+ {
+ Name: "bug",
+ },
+ },
+ PatchURL: "https://gitlab.com/gitea/test_repo/-/merge_requests/2.patch",
+ Head: base.PullRequestBranch{
+ Ref: "feat/test",
+ CloneURL: "https://gitlab.com/gitea/test_repo/-/merge_requests/2",
+ SHA: "9f733b96b98a4175276edf6a2e1231489c3bdd23",
+ RepoName: "test_repo",
+ OwnerName: "lafriks",
+ },
+ Base: base.PullRequestBranch{
+ Ref: "master",
+ SHA: "",
+ OwnerName: "lafriks",
+ RepoName: "test_repo",
+ },
+ Closed: nil,
+ Merged: false,
+ MergedTime: nil,
+ MergeCommitSHA: "",
+ },
+ }, prs)
+}
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index cabb5e12f9..70de9b7469 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -203,6 +203,7 @@ var (
// TODO: add to this list after new git service added
SupportedFullGitService = []GitServiceType{
GithubService,
+ GitlabService,
}
)