705 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			705 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2020 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package migrations
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"code.gitea.io/gitea/modules/log"
 | |
| 	base "code.gitea.io/gitea/modules/migration"
 | |
| 	"code.gitea.io/gitea/modules/structs"
 | |
| 
 | |
| 	gitea_sdk "code.gitea.io/sdk/gitea"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	_ base.Downloader        = &GiteaDownloader{}
 | |
| 	_ base.DownloaderFactory = &GiteaDownloaderFactory{}
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	RegisterDownloaderFactory(&GiteaDownloaderFactory{})
 | |
| }
 | |
| 
 | |
| // GiteaDownloaderFactory defines a gitea downloader factory
 | |
| type GiteaDownloaderFactory struct{}
 | |
| 
 | |
| // New returns a Downloader related to this factory according MigrateOptions
 | |
| func (f *GiteaDownloaderFactory) 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")
 | |
| 
 | |
| 	path := strings.Split(repoNameSpace, "/")
 | |
| 	if len(path) < 2 {
 | |
| 		return nil, fmt.Errorf("invalid path: %s", repoNameSpace)
 | |
| 	}
 | |
| 
 | |
| 	repoPath := strings.Join(path[len(path)-2:], "/")
 | |
| 	if len(path) > 2 {
 | |
| 		subPath := strings.Join(path[:len(path)-2], "/")
 | |
| 		baseURL += "/" + subPath
 | |
| 	}
 | |
| 
 | |
| 	log.Trace("Create gitea downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace)
 | |
| 
 | |
| 	return NewGiteaDownloader(ctx, baseURL, repoPath, opts.AuthUsername, opts.AuthPassword, opts.AuthToken)
 | |
| }
 | |
| 
 | |
| // GitServiceType returns the type of git service
 | |
| func (f *GiteaDownloaderFactory) GitServiceType() structs.GitServiceType {
 | |
| 	return structs.GiteaService
 | |
| }
 | |
| 
 | |
| // GiteaDownloader implements a Downloader interface to get repository information's
 | |
| type GiteaDownloader struct {
 | |
| 	base.NullDownloader
 | |
| 	ctx        context.Context
 | |
| 	client     *gitea_sdk.Client
 | |
| 	baseURL    string
 | |
| 	repoOwner  string
 | |
| 	repoName   string
 | |
| 	pagination bool
 | |
| 	maxPerPage int
 | |
| }
 | |
| 
 | |
| // NewGiteaDownloader creates a gitea Downloader via gitea API
 | |
| //
 | |
| //	Use either a username/password or personal token. token is preferred
 | |
| //	Note: Public access only allows very basic access
 | |
| func NewGiteaDownloader(ctx context.Context, baseURL, repoPath, username, password, token string) (*GiteaDownloader, error) {
 | |
| 	giteaClient, err := gitea_sdk.NewClient(
 | |
| 		baseURL,
 | |
| 		gitea_sdk.SetToken(token),
 | |
| 		gitea_sdk.SetBasicAuth(username, password),
 | |
| 		gitea_sdk.SetContext(ctx),
 | |
| 		gitea_sdk.SetHTTPClient(NewMigrationHTTPClient()),
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		log.Error(fmt.Sprintf("Failed to create NewGiteaDownloader for: %s. Error: %v", baseURL, err))
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	path := strings.Split(repoPath, "/")
 | |
| 
 | |
| 	paginationSupport := true
 | |
| 	if err = giteaClient.CheckServerVersionConstraint(">=1.12"); err != nil {
 | |
| 		paginationSupport = false
 | |
| 	}
 | |
| 
 | |
| 	// set small maxPerPage since we can only guess
 | |
| 	// (default would be 50 but this can differ)
 | |
| 	maxPerPage := 10
 | |
| 	// gitea instances >=1.13 can tell us what maximum they have
 | |
| 	apiConf, _, err := giteaClient.GetGlobalAPISettings()
 | |
| 	if err != nil {
 | |
| 		log.Info("Unable to get global API settings. Ignoring these.")
 | |
| 		log.Debug("giteaClient.GetGlobalAPISettings. Error: %v", err)
 | |
| 	}
 | |
| 	if apiConf != nil {
 | |
| 		maxPerPage = apiConf.MaxResponseItems
 | |
| 	}
 | |
| 
 | |
| 	return &GiteaDownloader{
 | |
| 		ctx:        ctx,
 | |
| 		client:     giteaClient,
 | |
| 		baseURL:    baseURL,
 | |
| 		repoOwner:  path[0],
 | |
| 		repoName:   path[1],
 | |
| 		pagination: paginationSupport,
 | |
| 		maxPerPage: maxPerPage,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // SetContext set context
 | |
| func (g *GiteaDownloader) SetContext(ctx context.Context) {
 | |
| 	g.ctx = ctx
 | |
| }
 | |
| 
 | |
| // String implements Stringer
 | |
| func (g *GiteaDownloader) String() string {
 | |
| 	return fmt.Sprintf("migration from gitea server %s %s/%s", g.baseURL, g.repoOwner, g.repoName)
 | |
| }
 | |
| 
 | |
| // ColorFormat provides a basic color format for a GiteaDownloader
 | |
| func (g *GiteaDownloader) ColorFormat(s fmt.State) {
 | |
| 	if g == nil {
 | |
| 		log.ColorFprintf(s, "<nil: GiteaDownloader>")
 | |
| 		return
 | |
| 	}
 | |
| 	log.ColorFprintf(s, "migration from gitea server %s %s/%s", g.baseURL, g.repoOwner, g.repoName)
 | |
| }
 | |
| 
 | |
| // GetRepoInfo returns a repository information
 | |
| func (g *GiteaDownloader) GetRepoInfo() (*base.Repository, error) {
 | |
| 	if g == nil {
 | |
| 		return nil, errors.New("error: GiteaDownloader is nil")
 | |
| 	}
 | |
| 
 | |
| 	repo, _, err := g.client.GetRepo(g.repoOwner, g.repoName)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &base.Repository{
 | |
| 		Name:          repo.Name,
 | |
| 		Owner:         repo.Owner.UserName,
 | |
| 		IsPrivate:     repo.Private,
 | |
| 		Description:   repo.Description,
 | |
| 		CloneURL:      repo.CloneURL,
 | |
| 		OriginalURL:   repo.HTMLURL,
 | |
| 		DefaultBranch: repo.DefaultBranch,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // GetTopics return gitea topics
 | |
| func (g *GiteaDownloader) GetTopics() ([]string, error) {
 | |
| 	topics, _, err := g.client.ListRepoTopics(g.repoOwner, g.repoName, gitea_sdk.ListRepoTopicsOptions{})
 | |
| 	return topics, err
 | |
| }
 | |
| 
 | |
| // GetMilestones returns milestones
 | |
| func (g *GiteaDownloader) GetMilestones() ([]*base.Milestone, error) {
 | |
| 	milestones := make([]*base.Milestone, 0, g.maxPerPage)
 | |
| 
 | |
| 	for i := 1; ; i++ {
 | |
| 		// make sure gitea can shutdown gracefully
 | |
| 		select {
 | |
| 		case <-g.ctx.Done():
 | |
| 			return nil, nil
 | |
| 		default:
 | |
| 		}
 | |
| 
 | |
| 		ms, _, err := g.client.ListRepoMilestones(g.repoOwner, g.repoName, gitea_sdk.ListMilestoneOption{
 | |
| 			ListOptions: gitea_sdk.ListOptions{
 | |
| 				PageSize: g.maxPerPage,
 | |
| 				Page:     i,
 | |
| 			},
 | |
| 			State: gitea_sdk.StateAll,
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		for i := range ms {
 | |
| 			// old gitea instances dont have this information
 | |
| 			createdAT := time.Time{}
 | |
| 			var updatedAT *time.Time
 | |
| 			if ms[i].Closed != nil {
 | |
| 				createdAT = *ms[i].Closed
 | |
| 				updatedAT = ms[i].Closed
 | |
| 			}
 | |
| 
 | |
| 			// new gitea instances (>=1.13) do
 | |
| 			if !ms[i].Created.IsZero() {
 | |
| 				createdAT = ms[i].Created
 | |
| 			}
 | |
| 			if ms[i].Updated != nil && !ms[i].Updated.IsZero() {
 | |
| 				updatedAT = ms[i].Updated
 | |
| 			}
 | |
| 
 | |
| 			milestones = append(milestones, &base.Milestone{
 | |
| 				Title:       ms[i].Title,
 | |
| 				Description: ms[i].Description,
 | |
| 				Deadline:    ms[i].Deadline,
 | |
| 				Created:     createdAT,
 | |
| 				Updated:     updatedAT,
 | |
| 				Closed:      ms[i].Closed,
 | |
| 				State:       string(ms[i].State),
 | |
| 			})
 | |
| 		}
 | |
| 		if !g.pagination || len(ms) < g.maxPerPage {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return milestones, nil
 | |
| }
 | |
| 
 | |
| func (g *GiteaDownloader) convertGiteaLabel(label *gitea_sdk.Label) *base.Label {
 | |
| 	return &base.Label{
 | |
| 		Name:        label.Name,
 | |
| 		Color:       label.Color,
 | |
| 		Description: label.Description,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // GetLabels returns labels
 | |
| func (g *GiteaDownloader) GetLabels() ([]*base.Label, error) {
 | |
| 	labels := make([]*base.Label, 0, g.maxPerPage)
 | |
| 
 | |
| 	for i := 1; ; i++ {
 | |
| 		// make sure gitea can shutdown gracefully
 | |
| 		select {
 | |
| 		case <-g.ctx.Done():
 | |
| 			return nil, nil
 | |
| 		default:
 | |
| 		}
 | |
| 
 | |
| 		ls, _, err := g.client.ListRepoLabels(g.repoOwner, g.repoName, gitea_sdk.ListLabelsOptions{ListOptions: gitea_sdk.ListOptions{
 | |
| 			PageSize: g.maxPerPage,
 | |
| 			Page:     i,
 | |
| 		}})
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		for i := range ls {
 | |
| 			labels = append(labels, g.convertGiteaLabel(ls[i]))
 | |
| 		}
 | |
| 		if !g.pagination || len(ls) < g.maxPerPage {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return labels, nil
 | |
| }
 | |
| 
 | |
| func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Release {
 | |
| 	r := &base.Release{
 | |
| 		TagName:         rel.TagName,
 | |
| 		TargetCommitish: rel.Target,
 | |
| 		Name:            rel.Title,
 | |
| 		Body:            rel.Note,
 | |
| 		Draft:           rel.IsDraft,
 | |
| 		Prerelease:      rel.IsPrerelease,
 | |
| 		PublisherID:     rel.Publisher.ID,
 | |
| 		PublisherName:   rel.Publisher.UserName,
 | |
| 		PublisherEmail:  rel.Publisher.Email,
 | |
| 		Published:       rel.PublishedAt,
 | |
| 		Created:         rel.CreatedAt,
 | |
| 	}
 | |
| 
 | |
| 	httpClient := NewMigrationHTTPClient()
 | |
| 
 | |
| 	for _, asset := range rel.Attachments {
 | |
| 		size := int(asset.Size)
 | |
| 		dlCount := int(asset.DownloadCount)
 | |
| 		r.Assets = append(r.Assets, &base.ReleaseAsset{
 | |
| 			ID:            asset.ID,
 | |
| 			Name:          asset.Name,
 | |
| 			Size:          &size,
 | |
| 			DownloadCount: &dlCount,
 | |
| 			Created:       asset.Created,
 | |
| 			DownloadURL:   &asset.DownloadURL,
 | |
| 			DownloadFunc: func() (io.ReadCloser, error) {
 | |
| 				asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, rel.ID, asset.ID)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 
 | |
| 				if !hasBaseURL(asset.DownloadURL, g.baseURL) {
 | |
| 					WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.ID, g, asset.DownloadURL)
 | |
| 					return io.NopCloser(strings.NewReader(asset.DownloadURL)), nil
 | |
| 				}
 | |
| 
 | |
| 				// FIXME: for a private download?
 | |
| 				req, err := http.NewRequest("GET", asset.DownloadURL, nil)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				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 *GiteaDownloader) GetReleases() ([]*base.Release, error) {
 | |
| 	releases := make([]*base.Release, 0, g.maxPerPage)
 | |
| 
 | |
| 	for i := 1; ; i++ {
 | |
| 		// make sure gitea can shutdown gracefully
 | |
| 		select {
 | |
| 		case <-g.ctx.Done():
 | |
| 			return nil, nil
 | |
| 		default:
 | |
| 		}
 | |
| 
 | |
| 		rl, _, err := g.client.ListReleases(g.repoOwner, g.repoName, gitea_sdk.ListReleasesOptions{ListOptions: gitea_sdk.ListOptions{
 | |
| 			PageSize: g.maxPerPage,
 | |
| 			Page:     i,
 | |
| 		}})
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		for i := range rl {
 | |
| 			releases = append(releases, g.convertGiteaRelease(rl[i]))
 | |
| 		}
 | |
| 		if !g.pagination || len(rl) < g.maxPerPage {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return releases, nil
 | |
| }
 | |
| 
 | |
| func (g *GiteaDownloader) getIssueReactions(index int64) ([]*base.Reaction, error) {
 | |
| 	var reactions []*base.Reaction
 | |
| 	if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil {
 | |
| 		log.Info("GiteaDownloader: instance to old, skip getIssueReactions")
 | |
| 		return reactions, nil
 | |
| 	}
 | |
| 	rl, _, err := g.client.GetIssueReactions(g.repoOwner, g.repoName, index)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	for _, reaction := range rl {
 | |
| 		reactions = append(reactions, &base.Reaction{
 | |
| 			UserID:   reaction.User.ID,
 | |
| 			UserName: reaction.User.UserName,
 | |
| 			Content:  reaction.Reaction,
 | |
| 		})
 | |
| 	}
 | |
| 	return reactions, nil
 | |
| }
 | |
| 
 | |
| func (g *GiteaDownloader) getCommentReactions(commentID int64) ([]*base.Reaction, error) {
 | |
| 	var reactions []*base.Reaction
 | |
| 	if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil {
 | |
| 		log.Info("GiteaDownloader: instance to old, skip getCommentReactions")
 | |
| 		return reactions, nil
 | |
| 	}
 | |
| 	rl, _, err := g.client.GetIssueCommentReactions(g.repoOwner, g.repoName, commentID)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	for i := range rl {
 | |
| 		reactions = append(reactions, &base.Reaction{
 | |
| 			UserID:   rl[i].User.ID,
 | |
| 			UserName: rl[i].User.UserName,
 | |
| 			Content:  rl[i].Reaction,
 | |
| 		})
 | |
| 	}
 | |
| 	return reactions, nil
 | |
| }
 | |
| 
 | |
| // GetIssues returns issues according start and limit
 | |
| func (g *GiteaDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
 | |
| 	if perPage > g.maxPerPage {
 | |
| 		perPage = g.maxPerPage
 | |
| 	}
 | |
| 	allIssues := make([]*base.Issue, 0, perPage)
 | |
| 
 | |
| 	issues, _, err := g.client.ListRepoIssues(g.repoOwner, g.repoName, gitea_sdk.ListIssueOption{
 | |
| 		ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: perPage},
 | |
| 		State:       gitea_sdk.StateAll,
 | |
| 		Type:        gitea_sdk.IssueTypeIssue,
 | |
| 	})
 | |
| 	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 i := range issue.Labels {
 | |
| 			labels = append(labels, g.convertGiteaLabel(issue.Labels[i]))
 | |
| 		}
 | |
| 
 | |
| 		var milestone string
 | |
| 		if issue.Milestone != nil {
 | |
| 			milestone = issue.Milestone.Title
 | |
| 		}
 | |
| 
 | |
| 		reactions, err := g.getIssueReactions(issue.Index)
 | |
| 		if err != nil {
 | |
| 			WarnAndNotice("Unable to load reactions during migrating issue #%d in %s. Error: %v", issue.Index, g, err)
 | |
| 		}
 | |
| 
 | |
| 		var assignees []string
 | |
| 		for i := range issue.Assignees {
 | |
| 			assignees = append(assignees, issue.Assignees[i].UserName)
 | |
| 		}
 | |
| 
 | |
| 		allIssues = append(allIssues, &base.Issue{
 | |
| 			Title:        issue.Title,
 | |
| 			Number:       issue.Index,
 | |
| 			PosterID:     issue.Poster.ID,
 | |
| 			PosterName:   issue.Poster.UserName,
 | |
| 			PosterEmail:  issue.Poster.Email,
 | |
| 			Content:      issue.Body,
 | |
| 			Milestone:    milestone,
 | |
| 			State:        string(issue.State),
 | |
| 			Created:      issue.Created,
 | |
| 			Updated:      issue.Updated,
 | |
| 			Closed:       issue.Closed,
 | |
| 			Reactions:    reactions,
 | |
| 			Labels:       labels,
 | |
| 			Assignees:    assignees,
 | |
| 			IsLocked:     issue.IsLocked,
 | |
| 			ForeignIndex: issue.Index,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	isEnd := len(issues) < perPage
 | |
| 	if !g.pagination {
 | |
| 		isEnd = len(issues) == 0
 | |
| 	}
 | |
| 	return allIssues, isEnd, nil
 | |
| }
 | |
| 
 | |
| // GetComments returns comments according issueNumber
 | |
| func (g *GiteaDownloader) GetComments(commentable base.Commentable) ([]*base.Comment, bool, error) {
 | |
| 	allComments := make([]*base.Comment, 0, g.maxPerPage)
 | |
| 
 | |
| 	for i := 1; ; i++ {
 | |
| 		// make sure gitea can shutdown gracefully
 | |
| 		select {
 | |
| 		case <-g.ctx.Done():
 | |
| 			return nil, false, nil
 | |
| 		default:
 | |
| 		}
 | |
| 
 | |
| 		comments, _, err := g.client.ListIssueComments(g.repoOwner, g.repoName, commentable.GetForeignIndex(), gitea_sdk.ListIssueCommentOptions{ListOptions: gitea_sdk.ListOptions{
 | |
| 			PageSize: g.maxPerPage,
 | |
| 			Page:     i,
 | |
| 		}})
 | |
| 		if err != nil {
 | |
| 			return nil, false, fmt.Errorf("error while listing comments for issue #%d. Error: %w", commentable.GetForeignIndex(), err)
 | |
| 		}
 | |
| 
 | |
| 		for _, comment := range comments {
 | |
| 			reactions, err := g.getCommentReactions(comment.ID)
 | |
| 			if err != nil {
 | |
| 				WarnAndNotice("Unable to load comment reactions during migrating issue #%d for comment %d in %s. Error: %v", commentable.GetForeignIndex(), comment.ID, g, err)
 | |
| 			}
 | |
| 
 | |
| 			allComments = append(allComments, &base.Comment{
 | |
| 				IssueIndex:  commentable.GetLocalIndex(),
 | |
| 				Index:       comment.ID,
 | |
| 				PosterID:    comment.Poster.ID,
 | |
| 				PosterName:  comment.Poster.UserName,
 | |
| 				PosterEmail: comment.Poster.Email,
 | |
| 				Content:     comment.Body,
 | |
| 				Created:     comment.Created,
 | |
| 				Updated:     comment.Updated,
 | |
| 				Reactions:   reactions,
 | |
| 			})
 | |
| 		}
 | |
| 
 | |
| 		if !g.pagination || len(comments) < g.maxPerPage {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return allComments, true, nil
 | |
| }
 | |
| 
 | |
| // GetPullRequests returns pull requests according page and perPage
 | |
| func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) {
 | |
| 	if perPage > g.maxPerPage {
 | |
| 		perPage = g.maxPerPage
 | |
| 	}
 | |
| 	allPRs := make([]*base.PullRequest, 0, perPage)
 | |
| 
 | |
| 	prs, _, err := g.client.ListRepoPullRequests(g.repoOwner, g.repoName, gitea_sdk.ListPullRequestsOptions{
 | |
| 		ListOptions: gitea_sdk.ListOptions{
 | |
| 			Page:     page,
 | |
| 			PageSize: perPage,
 | |
| 		},
 | |
| 		State: gitea_sdk.StateAll,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, false, fmt.Errorf("error while listing pull requests (page: %d, pagesize: %d). Error: %w", page, perPage, err)
 | |
| 	}
 | |
| 	for _, pr := range prs {
 | |
| 		var milestone string
 | |
| 		if pr.Milestone != nil {
 | |
| 			milestone = pr.Milestone.Title
 | |
| 		}
 | |
| 
 | |
| 		labels := make([]*base.Label, 0, len(pr.Labels))
 | |
| 		for i := range pr.Labels {
 | |
| 			labels = append(labels, g.convertGiteaLabel(pr.Labels[i]))
 | |
| 		}
 | |
| 
 | |
| 		var (
 | |
| 			headUserName string
 | |
| 			headRepoName string
 | |
| 			headCloneURL string
 | |
| 			headRef      string
 | |
| 			headSHA      string
 | |
| 		)
 | |
| 		if pr.Head != nil {
 | |
| 			if pr.Head.Repository != nil {
 | |
| 				headUserName = pr.Head.Repository.Owner.UserName
 | |
| 				headRepoName = pr.Head.Repository.Name
 | |
| 				headCloneURL = pr.Head.Repository.CloneURL
 | |
| 			}
 | |
| 			headSHA = pr.Head.Sha
 | |
| 			headRef = pr.Head.Ref
 | |
| 		}
 | |
| 
 | |
| 		var mergeCommitSHA string
 | |
| 		if pr.MergedCommitID != nil {
 | |
| 			mergeCommitSHA = *pr.MergedCommitID
 | |
| 		}
 | |
| 
 | |
| 		reactions, err := g.getIssueReactions(pr.Index)
 | |
| 		if err != nil {
 | |
| 			WarnAndNotice("Unable to load reactions during migrating pull #%d in %s. Error: %v", pr.Index, g, err)
 | |
| 		}
 | |
| 
 | |
| 		var assignees []string
 | |
| 		for i := range pr.Assignees {
 | |
| 			assignees = append(assignees, pr.Assignees[i].UserName)
 | |
| 		}
 | |
| 
 | |
| 		createdAt := time.Time{}
 | |
| 		if pr.Created != nil {
 | |
| 			createdAt = *pr.Created
 | |
| 		}
 | |
| 		updatedAt := time.Time{}
 | |
| 		if pr.Created != nil {
 | |
| 			updatedAt = *pr.Updated
 | |
| 		}
 | |
| 
 | |
| 		closedAt := pr.Closed
 | |
| 		if pr.Merged != nil && closedAt == nil {
 | |
| 			closedAt = pr.Merged
 | |
| 		}
 | |
| 
 | |
| 		allPRs = append(allPRs, &base.PullRequest{
 | |
| 			Title:          pr.Title,
 | |
| 			Number:         pr.Index,
 | |
| 			PosterID:       pr.Poster.ID,
 | |
| 			PosterName:     pr.Poster.UserName,
 | |
| 			PosterEmail:    pr.Poster.Email,
 | |
| 			Content:        pr.Body,
 | |
| 			State:          string(pr.State),
 | |
| 			Created:        createdAt,
 | |
| 			Updated:        updatedAt,
 | |
| 			Closed:         closedAt,
 | |
| 			Labels:         labels,
 | |
| 			Milestone:      milestone,
 | |
| 			Reactions:      reactions,
 | |
| 			Assignees:      assignees,
 | |
| 			Merged:         pr.HasMerged,
 | |
| 			MergedTime:     pr.Merged,
 | |
| 			MergeCommitSHA: mergeCommitSHA,
 | |
| 			IsLocked:       pr.IsLocked,
 | |
| 			PatchURL:       pr.PatchURL,
 | |
| 			Head: base.PullRequestBranch{
 | |
| 				Ref:       headRef,
 | |
| 				SHA:       headSHA,
 | |
| 				RepoName:  headRepoName,
 | |
| 				OwnerName: headUserName,
 | |
| 				CloneURL:  headCloneURL,
 | |
| 			},
 | |
| 			Base: base.PullRequestBranch{
 | |
| 				Ref:       pr.Base.Ref,
 | |
| 				SHA:       pr.Base.Sha,
 | |
| 				RepoName:  g.repoName,
 | |
| 				OwnerName: g.repoOwner,
 | |
| 			},
 | |
| 			ForeignIndex: pr.Index,
 | |
| 		})
 | |
| 		// SECURITY: Ensure that the PR is safe
 | |
| 		_ = CheckAndEnsureSafePR(allPRs[len(allPRs)-1], g.baseURL, g)
 | |
| 	}
 | |
| 
 | |
| 	isEnd := len(prs) < perPage
 | |
| 	if !g.pagination {
 | |
| 		isEnd = len(prs) == 0
 | |
| 	}
 | |
| 	return allPRs, isEnd, nil
 | |
| }
 | |
| 
 | |
| // GetReviews returns pull requests review
 | |
| func (g *GiteaDownloader) GetReviews(reviewable base.Reviewable) ([]*base.Review, error) {
 | |
| 	if err := g.client.CheckServerVersionConstraint(">=1.12"); err != nil {
 | |
| 		log.Info("GiteaDownloader: instance to old, skip GetReviews")
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 
 | |
| 	allReviews := make([]*base.Review, 0, g.maxPerPage)
 | |
| 
 | |
| 	for i := 1; ; i++ {
 | |
| 		// make sure gitea can shutdown gracefully
 | |
| 		select {
 | |
| 		case <-g.ctx.Done():
 | |
| 			return nil, nil
 | |
| 		default:
 | |
| 		}
 | |
| 
 | |
| 		prl, _, err := g.client.ListPullReviews(g.repoOwner, g.repoName, reviewable.GetForeignIndex(), gitea_sdk.ListPullReviewsOptions{ListOptions: gitea_sdk.ListOptions{
 | |
| 			Page:     i,
 | |
| 			PageSize: g.maxPerPage,
 | |
| 		}})
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		for _, pr := range prl {
 | |
| 			if pr.Reviewer == nil {
 | |
| 				// Presumably this is a team review which we cannot migrate at present but we have to skip this review as otherwise the review will be mapped on to an incorrect user.
 | |
| 				// TODO: handle team reviews
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			rcl, _, err := g.client.ListPullReviewComments(g.repoOwner, g.repoName, reviewable.GetForeignIndex(), pr.ID)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			var reviewComments []*base.ReviewComment
 | |
| 			for i := range rcl {
 | |
| 				line := int(rcl[i].LineNum)
 | |
| 				if rcl[i].OldLineNum > 0 {
 | |
| 					line = int(rcl[i].OldLineNum) * -1
 | |
| 				}
 | |
| 
 | |
| 				reviewComments = append(reviewComments, &base.ReviewComment{
 | |
| 					ID:        rcl[i].ID,
 | |
| 					Content:   rcl[i].Body,
 | |
| 					TreePath:  rcl[i].Path,
 | |
| 					DiffHunk:  rcl[i].DiffHunk,
 | |
| 					Line:      line,
 | |
| 					CommitID:  rcl[i].CommitID,
 | |
| 					PosterID:  rcl[i].Reviewer.ID,
 | |
| 					CreatedAt: rcl[i].Created,
 | |
| 					UpdatedAt: rcl[i].Updated,
 | |
| 				})
 | |
| 			}
 | |
| 
 | |
| 			review := &base.Review{
 | |
| 				ID:           pr.ID,
 | |
| 				IssueIndex:   reviewable.GetLocalIndex(),
 | |
| 				ReviewerID:   pr.Reviewer.ID,
 | |
| 				ReviewerName: pr.Reviewer.UserName,
 | |
| 				Official:     pr.Official,
 | |
| 				CommitID:     pr.CommitID,
 | |
| 				Content:      pr.Body,
 | |
| 				CreatedAt:    pr.Submitted,
 | |
| 				State:        string(pr.State),
 | |
| 				Comments:     reviewComments,
 | |
| 			}
 | |
| 
 | |
| 			allReviews = append(allReviews, review)
 | |
| 		}
 | |
| 
 | |
| 		if len(prl) < g.maxPerPage {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return allReviews, nil
 | |
| }
 |