mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
267 lines
7.4 KiB
Go
267 lines
7.4 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package migrations
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
git_module "github.com/hanzoai/git/modules/git"
|
|
"github.com/hanzoai/git/modules/log"
|
|
base "github.com/hanzoai/git/modules/migration"
|
|
"github.com/hanzoai/git/modules/structs"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
|
"github.com/aws/aws-sdk-go-v2/service/codecommit"
|
|
"github.com/aws/aws-sdk-go-v2/service/codecommit/types"
|
|
)
|
|
|
|
var (
|
|
_ base.Downloader = &CodeCommitDownloader{}
|
|
_ base.DownloaderFactory = &CodeCommitDownloaderFactory{}
|
|
)
|
|
|
|
func init() {
|
|
RegisterDownloaderFactory(&CodeCommitDownloaderFactory{})
|
|
}
|
|
|
|
// CodeCommitDownloaderFactory defines a codecommit downloader factory
|
|
type CodeCommitDownloaderFactory struct{}
|
|
|
|
// New returns a Downloader related to this factory according MigrateOptions
|
|
func (c *CodeCommitDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
|
|
u, err := url.Parse(opts.CloneAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hostElems := strings.Split(u.Host, ".")
|
|
if len(hostElems) != 4 {
|
|
return nil, errors.New("cannot get the region from clone URL")
|
|
}
|
|
region := hostElems[1]
|
|
|
|
pathElems := strings.Split(u.Path, "/")
|
|
if len(pathElems) == 0 {
|
|
return nil, errors.New("cannot get the repo name from clone URL")
|
|
}
|
|
repoName := pathElems[len(pathElems)-1]
|
|
|
|
baseURL := u.Scheme + "://" + u.Host
|
|
|
|
return NewCodeCommitDownloader(ctx, repoName, baseURL, opts.AWSAccessKeyID, opts.AWSSecretAccessKey, region), nil
|
|
}
|
|
|
|
// GitServiceType returns the type of git service
|
|
func (c *CodeCommitDownloaderFactory) GitServiceType() structs.GitServiceType {
|
|
return structs.CodeCommitService
|
|
}
|
|
|
|
func NewCodeCommitDownloader(_ context.Context, repoName, baseURL, accessKeyID, secretAccessKey, region string) *CodeCommitDownloader {
|
|
downloader := CodeCommitDownloader{
|
|
repoName: repoName,
|
|
baseURL: baseURL,
|
|
codeCommitClient: codecommit.New(codecommit.Options{
|
|
Credentials: credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, ""),
|
|
Region: region,
|
|
}),
|
|
}
|
|
|
|
return &downloader
|
|
}
|
|
|
|
// CodeCommitDownloader implements a downloader for AWS CodeCommit
|
|
type CodeCommitDownloader struct {
|
|
base.NullDownloader
|
|
codeCommitClient *codecommit.Client
|
|
repoName string
|
|
baseURL string
|
|
allPullRequestIDs []string
|
|
}
|
|
|
|
// GetRepoInfo returns a repository information
|
|
func (c *CodeCommitDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) {
|
|
output, err := c.codeCommitClient.GetRepository(ctx, &codecommit.GetRepositoryInput{
|
|
RepositoryName: new(c.repoName),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
repoMeta := output.RepositoryMetadata
|
|
|
|
repo := &base.Repository{
|
|
Name: *repoMeta.RepositoryName,
|
|
Owner: *repoMeta.AccountId,
|
|
IsPrivate: true, // CodeCommit repos are always private
|
|
CloneURL: *repoMeta.CloneUrlHttp,
|
|
}
|
|
if repoMeta.DefaultBranch != nil {
|
|
repo.DefaultBranch = *repoMeta.DefaultBranch
|
|
}
|
|
if repoMeta.RepositoryDescription != nil {
|
|
repo.DefaultBranch = *repoMeta.RepositoryDescription
|
|
}
|
|
return repo, nil
|
|
}
|
|
|
|
// GetComments returns comments of an issue or PR
|
|
func (c *CodeCommitDownloader) GetComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) {
|
|
var (
|
|
nextToken *string
|
|
comments []*base.Comment
|
|
)
|
|
|
|
for {
|
|
resp, err := c.codeCommitClient.GetCommentsForPullRequest(ctx, &codecommit.GetCommentsForPullRequestInput{
|
|
NextToken: nextToken,
|
|
PullRequestId: new(strconv.FormatInt(commentable.GetForeignIndex(), 10)),
|
|
})
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
for _, prComment := range resp.CommentsForPullRequestData {
|
|
for _, ccComment := range prComment.Comments {
|
|
comment := &base.Comment{
|
|
IssueIndex: commentable.GetForeignIndex(),
|
|
PosterName: c.getUsernameFromARN(*ccComment.AuthorArn),
|
|
Content: *ccComment.Content,
|
|
Created: *ccComment.CreationDate,
|
|
Updated: *ccComment.LastModifiedDate,
|
|
}
|
|
comments = append(comments, comment)
|
|
}
|
|
}
|
|
|
|
nextToken = resp.NextToken
|
|
if nextToken == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
return comments, true, nil
|
|
}
|
|
|
|
// GetPullRequests returns pull requests according page and perPage
|
|
func (c *CodeCommitDownloader) GetPullRequests(ctx context.Context, page, perPage int) ([]*base.PullRequest, bool, error) {
|
|
allPullRequestIDs, err := c.getAllPullRequestIDs(ctx)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
startIndex := (page - 1) * perPage
|
|
endIndex := min(page*perPage, len(allPullRequestIDs))
|
|
batch := allPullRequestIDs[startIndex:endIndex]
|
|
|
|
prs := make([]*base.PullRequest, 0, len(batch))
|
|
for _, id := range batch {
|
|
output, err := c.codeCommitClient.GetPullRequest(ctx, &codecommit.GetPullRequestInput{
|
|
PullRequestId: new(id),
|
|
})
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
orig := output.PullRequest
|
|
number, err := strconv.ParseInt(*orig.PullRequestId, 10, 64)
|
|
if err != nil {
|
|
log.Error("CodeCommit pull request id is not a number: %s", *orig.PullRequestId)
|
|
continue
|
|
}
|
|
if len(orig.PullRequestTargets) == 0 {
|
|
log.Error("CodeCommit pull request does not contain targets", *orig.PullRequestId)
|
|
continue
|
|
}
|
|
target := orig.PullRequestTargets[0]
|
|
description := ""
|
|
if orig.Description != nil {
|
|
description = *orig.Description
|
|
}
|
|
pr := &base.PullRequest{
|
|
Number: number,
|
|
Title: *orig.Title,
|
|
PosterName: c.getUsernameFromARN(*orig.AuthorArn),
|
|
Content: description,
|
|
State: "open",
|
|
Created: *orig.CreationDate,
|
|
Updated: *orig.LastActivityDate,
|
|
Merged: target.MergeMetadata.IsMerged,
|
|
Head: base.PullRequestBranch{
|
|
Ref: strings.TrimPrefix(*target.SourceReference, git_module.BranchPrefix),
|
|
SHA: *target.SourceCommit,
|
|
RepoName: c.repoName,
|
|
},
|
|
Base: base.PullRequestBranch{
|
|
Ref: strings.TrimPrefix(*target.DestinationReference, git_module.BranchPrefix),
|
|
SHA: *target.DestinationCommit,
|
|
RepoName: c.repoName,
|
|
},
|
|
ForeignIndex: number,
|
|
}
|
|
|
|
if orig.PullRequestStatus == types.PullRequestStatusEnumClosed {
|
|
pr.State = "closed"
|
|
pr.Closed = orig.LastActivityDate
|
|
}
|
|
if pr.Merged {
|
|
pr.MergeCommitSHA = *target.MergeMetadata.MergeCommitId
|
|
pr.MergedTime = orig.LastActivityDate
|
|
}
|
|
|
|
_ = CheckAndEnsureSafePR(pr, c.baseURL, c)
|
|
prs = append(prs, pr)
|
|
}
|
|
|
|
return prs, len(prs) < perPage, nil
|
|
}
|
|
|
|
// FormatCloneURL add authentication into remote URLs
|
|
func (c *CodeCommitDownloader) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) {
|
|
u, err := url.Parse(remoteAddr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
|
|
return u.String(), nil
|
|
}
|
|
|
|
func (c *CodeCommitDownloader) getAllPullRequestIDs(ctx context.Context) ([]string, error) {
|
|
if len(c.allPullRequestIDs) > 0 {
|
|
return c.allPullRequestIDs, nil
|
|
}
|
|
|
|
var (
|
|
nextToken *string
|
|
prIDs []string
|
|
)
|
|
|
|
for {
|
|
output, err := c.codeCommitClient.ListPullRequests(ctx, &codecommit.ListPullRequestsInput{
|
|
RepositoryName: new(c.repoName),
|
|
NextToken: nextToken,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
prIDs = append(prIDs, output.PullRequestIds...)
|
|
nextToken = output.NextToken
|
|
if nextToken == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
c.allPullRequestIDs = prIDs
|
|
return c.allPullRequestIDs, nil
|
|
}
|
|
|
|
func (c *CodeCommitDownloader) getUsernameFromARN(arn string) string {
|
|
parts := strings.Split(arn, "/")
|
|
if len(parts) > 0 {
|
|
return parts[len(parts)-1]
|
|
}
|
|
return ""
|
|
}
|