gitea
Зеркало из https://github.com/go-gitea/gitea
269 строк · 7.4 Кб
1// Copyright 2024 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package migrations
5
6import (
7"context"
8"fmt"
9"net/url"
10"strconv"
11"strings"
12
13git_module "code.gitea.io/gitea/modules/git"
14"code.gitea.io/gitea/modules/log"
15base "code.gitea.io/gitea/modules/migration"
16"code.gitea.io/gitea/modules/structs"
17
18"github.com/aws/aws-sdk-go-v2/credentials"
19"github.com/aws/aws-sdk-go-v2/service/codecommit"
20"github.com/aws/aws-sdk-go-v2/service/codecommit/types"
21"github.com/aws/aws-sdk-go/aws"
22)
23
24var (
25_ base.Downloader = &CodeCommitDownloader{}
26_ base.DownloaderFactory = &CodeCommitDownloaderFactory{}
27)
28
29func init() {
30RegisterDownloaderFactory(&CodeCommitDownloaderFactory{})
31}
32
33// CodeCommitDownloaderFactory defines a codecommit downloader factory
34type CodeCommitDownloaderFactory struct{}
35
36// New returns a Downloader related to this factory according MigrateOptions
37func (c *CodeCommitDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
38u, err := url.Parse(opts.CloneAddr)
39if err != nil {
40return nil, err
41}
42
43hostElems := strings.Split(u.Host, ".")
44if len(hostElems) != 4 {
45return nil, fmt.Errorf("cannot get the region from clone URL")
46}
47region := hostElems[1]
48
49pathElems := strings.Split(u.Path, "/")
50if len(pathElems) == 0 {
51return nil, fmt.Errorf("cannot get the repo name from clone URL")
52}
53repoName := pathElems[len(pathElems)-1]
54
55baseURL := u.Scheme + "://" + u.Host
56
57return NewCodeCommitDownloader(ctx, repoName, baseURL, opts.AWSAccessKeyID, opts.AWSSecretAccessKey, region), nil
58}
59
60// GitServiceType returns the type of git service
61func (c *CodeCommitDownloaderFactory) GitServiceType() structs.GitServiceType {
62return structs.CodeCommitService
63}
64
65func NewCodeCommitDownloader(ctx context.Context, repoName, baseURL, accessKeyID, secretAccessKey, region string) *CodeCommitDownloader {
66downloader := CodeCommitDownloader{
67ctx: ctx,
68repoName: repoName,
69baseURL: baseURL,
70codeCommitClient: codecommit.New(codecommit.Options{
71Credentials: credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, ""),
72Region: region,
73}),
74}
75
76return &downloader
77}
78
79// CodeCommitDownloader implements a downloader for AWS CodeCommit
80type CodeCommitDownloader struct {
81base.NullDownloader
82ctx context.Context
83codeCommitClient *codecommit.Client
84repoName string
85baseURL string
86allPullRequestIDs []string
87}
88
89// SetContext set context
90func (c *CodeCommitDownloader) SetContext(ctx context.Context) {
91c.ctx = ctx
92}
93
94// GetRepoInfo returns a repository information
95func (c *CodeCommitDownloader) GetRepoInfo() (*base.Repository, error) {
96output, err := c.codeCommitClient.GetRepository(c.ctx, &codecommit.GetRepositoryInput{
97RepositoryName: aws.String(c.repoName),
98})
99if err != nil {
100return nil, err
101}
102repoMeta := output.RepositoryMetadata
103
104repo := &base.Repository{
105Name: *repoMeta.RepositoryName,
106Owner: *repoMeta.AccountId,
107IsPrivate: true, // CodeCommit repos are always private
108CloneURL: *repoMeta.CloneUrlHttp,
109}
110if repoMeta.DefaultBranch != nil {
111repo.DefaultBranch = *repoMeta.DefaultBranch
112}
113if repoMeta.RepositoryDescription != nil {
114repo.DefaultBranch = *repoMeta.RepositoryDescription
115}
116return repo, nil
117}
118
119// GetComments returns comments of an issue or PR
120func (c *CodeCommitDownloader) GetComments(commentable base.Commentable) ([]*base.Comment, bool, error) {
121var (
122nextToken *string
123comments []*base.Comment
124)
125
126for {
127resp, err := c.codeCommitClient.GetCommentsForPullRequest(c.ctx, &codecommit.GetCommentsForPullRequestInput{
128NextToken: nextToken,
129PullRequestId: aws.String(strconv.FormatInt(commentable.GetForeignIndex(), 10)),
130})
131if err != nil {
132return nil, false, err
133}
134
135for _, prComment := range resp.CommentsForPullRequestData {
136for _, ccComment := range prComment.Comments {
137comment := &base.Comment{
138IssueIndex: commentable.GetForeignIndex(),
139PosterName: c.getUsernameFromARN(*ccComment.AuthorArn),
140Content: *ccComment.Content,
141Created: *ccComment.CreationDate,
142Updated: *ccComment.LastModifiedDate,
143}
144comments = append(comments, comment)
145}
146}
147
148nextToken = resp.NextToken
149if nextToken == nil {
150break
151}
152}
153
154return comments, true, nil
155}
156
157// GetPullRequests returns pull requests according page and perPage
158func (c *CodeCommitDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) {
159allPullRequestIDs, err := c.getAllPullRequestIDs()
160if err != nil {
161return nil, false, err
162}
163
164startIndex := (page - 1) * perPage
165endIndex := page * perPage
166if endIndex > len(allPullRequestIDs) {
167endIndex = len(allPullRequestIDs)
168}
169batch := allPullRequestIDs[startIndex:endIndex]
170
171prs := make([]*base.PullRequest, 0, len(batch))
172for _, id := range batch {
173output, err := c.codeCommitClient.GetPullRequest(c.ctx, &codecommit.GetPullRequestInput{
174PullRequestId: aws.String(id),
175})
176if err != nil {
177return nil, false, err
178}
179orig := output.PullRequest
180number, err := strconv.ParseInt(*orig.PullRequestId, 10, 64)
181if err != nil {
182log.Error("CodeCommit pull request id is not a number: %s", *orig.PullRequestId)
183continue
184}
185if len(orig.PullRequestTargets) == 0 {
186log.Error("CodeCommit pull request does not contain targets", *orig.PullRequestId)
187continue
188}
189target := orig.PullRequestTargets[0]
190pr := &base.PullRequest{
191Number: number,
192Title: *orig.Title,
193PosterName: c.getUsernameFromARN(*orig.AuthorArn),
194Content: *orig.Description,
195State: "open",
196Created: *orig.CreationDate,
197Updated: *orig.LastActivityDate,
198Merged: target.MergeMetadata.IsMerged,
199Head: base.PullRequestBranch{
200Ref: strings.TrimPrefix(*target.SourceReference, git_module.BranchPrefix),
201SHA: *target.SourceCommit,
202RepoName: c.repoName,
203},
204Base: base.PullRequestBranch{
205Ref: strings.TrimPrefix(*target.DestinationReference, git_module.BranchPrefix),
206SHA: *target.DestinationCommit,
207RepoName: c.repoName,
208},
209ForeignIndex: number,
210}
211
212if orig.PullRequestStatus == types.PullRequestStatusEnumClosed {
213pr.State = "closed"
214pr.Closed = orig.LastActivityDate
215}
216
217_ = CheckAndEnsureSafePR(pr, c.baseURL, c)
218prs = append(prs, pr)
219}
220
221return prs, len(prs) < perPage, nil
222}
223
224// FormatCloneURL add authentication into remote URLs
225func (c *CodeCommitDownloader) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) {
226u, err := url.Parse(remoteAddr)
227if err != nil {
228return "", err
229}
230u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
231return u.String(), nil
232}
233
234func (c *CodeCommitDownloader) getAllPullRequestIDs() ([]string, error) {
235if len(c.allPullRequestIDs) > 0 {
236return c.allPullRequestIDs, nil
237}
238
239var (
240nextToken *string
241prIDs []string
242)
243
244for {
245output, err := c.codeCommitClient.ListPullRequests(c.ctx, &codecommit.ListPullRequestsInput{
246RepositoryName: aws.String(c.repoName),
247NextToken: nextToken,
248})
249if err != nil {
250return nil, err
251}
252prIDs = append(prIDs, output.PullRequestIds...)
253nextToken = output.NextToken
254if nextToken == nil {
255break
256}
257}
258
259c.allPullRequestIDs = prIDs
260return c.allPullRequestIDs, nil
261}
262
263func (c *CodeCommitDownloader) getUsernameFromARN(arn string) string {
264parts := strings.Split(arn, "/")
265if len(parts) > 0 {
266return parts[len(parts)-1]
267}
268return ""
269}
270