2021-01-21 22:33:58 +03:00
// 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"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"code.gitea.io/gitea/modules/log"
2021-11-16 18:25:33 +03:00
base "code.gitea.io/gitea/modules/migration"
2021-08-18 16:10:39 +03:00
"code.gitea.io/gitea/modules/proxy"
2021-01-21 22:33:58 +03:00
"code.gitea.io/gitea/modules/structs"
"github.com/gogs/go-gogs-client"
)
var (
_ base . Downloader = & GogsDownloader { }
_ base . DownloaderFactory = & GogsDownloaderFactory { }
)
func init ( ) {
RegisterDownloaderFactory ( & GogsDownloaderFactory { } )
}
// GogsDownloaderFactory defines a gogs downloader factory
type GogsDownloaderFactory struct {
}
// New returns a Downloader related to this factory according MigrateOptions
func ( f * GogsDownloaderFactory ) 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 . TrimSuffix ( u . Path , ".git" )
repoNameSpace = strings . Trim ( repoNameSpace , "/" )
fields := strings . Split ( repoNameSpace , "/" )
if len ( fields ) < 2 {
return nil , fmt . Errorf ( "invalid path: %s" , repoNameSpace )
}
log . Trace ( "Create gogs downloader. BaseURL: %s RepoOwner: %s RepoName: %s" , baseURL , fields [ 0 ] , fields [ 1 ] )
return NewGogsDownloader ( ctx , baseURL , opts . AuthUsername , opts . AuthPassword , opts . AuthToken , fields [ 0 ] , fields [ 1 ] ) , nil
}
// GitServiceType returns the type of git service
func ( f * GogsDownloaderFactory ) GitServiceType ( ) structs . GitServiceType {
return structs . GogsService
}
2021-07-08 14:38:13 +03:00
// GogsDownloader implements a Downloader interface to get repository information
2021-01-21 22:33:58 +03:00
// from gogs via API
type GogsDownloader struct {
base . NullDownloader
ctx context . Context
client * gogs . Client
baseURL string
repoOwner string
repoName string
userName string
password string
openIssuesFinished bool
openIssuesPages int
transport http . RoundTripper
}
// SetContext set context
func ( g * GogsDownloader ) SetContext ( ctx context . Context ) {
g . ctx = ctx
}
// NewGogsDownloader creates a gogs Downloader via gogs API
func NewGogsDownloader ( ctx context . Context , baseURL , userName , password , token , repoOwner , repoName string ) * GogsDownloader {
var downloader = GogsDownloader {
ctx : ctx ,
baseURL : baseURL ,
userName : userName ,
password : password ,
repoOwner : repoOwner ,
repoName : repoName ,
}
var client * gogs . Client
if len ( token ) != 0 {
client = gogs . NewClient ( baseURL , token )
downloader . userName = token
} else {
2021-11-20 12:34:05 +03:00
var transport = NewMigrationHTTPTransport ( )
transport . Proxy = func ( req * http . Request ) ( * url . URL , error ) {
req . SetBasicAuth ( userName , password )
return proxy . Proxy ( ) ( req )
2021-01-21 22:33:58 +03:00
}
2021-11-20 12:34:05 +03:00
downloader . transport = transport
2021-01-21 22:33:58 +03:00
client = gogs . NewClient ( baseURL , "" )
client . SetHTTPClient ( & http . Client {
Transport : & downloader ,
} )
}
downloader . client = client
return & downloader
}
// RoundTrip wraps the provided request within this downloader's context and passes it to our internal http.Transport.
// This implements http.RoundTripper and makes the gogs client requests cancellable even though it is not cancellable itself
func ( g * GogsDownloader ) RoundTrip ( req * http . Request ) ( * http . Response , error ) {
return g . transport . RoundTrip ( req . WithContext ( g . ctx ) )
}
// GetRepoInfo returns a repository information
func ( g * GogsDownloader ) GetRepoInfo ( ) ( * base . Repository , error ) {
gr , err := g . client . GetRepo ( g . repoOwner , g . repoName )
if err != nil {
return nil , err
}
// convert gogs repo to stand Repo
return & base . Repository {
Owner : g . repoOwner ,
Name : g . repoName ,
IsPrivate : gr . Private ,
Description : gr . Description ,
CloneURL : gr . CloneURL ,
OriginalURL : gr . HTMLURL ,
DefaultBranch : gr . DefaultBranch ,
} , nil
}
// GetMilestones returns milestones
func ( g * GogsDownloader ) GetMilestones ( ) ( [ ] * base . Milestone , error ) {
var perPage = 100
var milestones = make ( [ ] * base . Milestone , 0 , perPage )
ms , err := g . client . ListRepoMilestones ( g . repoOwner , g . repoName )
if err != nil {
return nil , err
}
for _ , m := range ms {
milestones = append ( milestones , & base . Milestone {
Title : m . Title ,
Description : m . Description ,
Deadline : m . Deadline ,
State : string ( m . State ) ,
Closed : m . Closed ,
} )
}
return milestones , nil
}
// GetLabels returns labels
func ( g * GogsDownloader ) GetLabels ( ) ( [ ] * base . Label , error ) {
var perPage = 100
var labels = make ( [ ] * base . Label , 0 , perPage )
ls , err := g . client . ListRepoLabels ( g . repoOwner , g . repoName )
if err != nil {
return nil , err
}
for _ , label := range ls {
labels = append ( labels , convertGogsLabel ( label ) )
}
return labels , nil
}
// GetIssues returns issues according start and limit, perPage is not supported
func ( g * GogsDownloader ) GetIssues ( page , _ int ) ( [ ] * base . Issue , bool , error ) {
var state string
if g . openIssuesFinished {
state = string ( gogs . STATE_CLOSED )
page -= g . openIssuesPages
} else {
state = string ( gogs . STATE_OPEN )
g . openIssuesPages = page
}
issues , isEnd , err := g . getIssues ( page , state )
if err != nil {
return nil , false , err
}
if isEnd {
if g . openIssuesFinished {
return issues , true , nil
}
g . openIssuesFinished = true
}
return issues , false , nil
}
func ( g * GogsDownloader ) getIssues ( page int , state string ) ( [ ] * base . Issue , bool , error ) {
var allIssues = make ( [ ] * base . Issue , 0 , 10 )
issues , err := g . client . ListRepoIssues ( g . repoOwner , g . repoName , gogs . ListIssueOption {
Page : page ,
State : state ,
} )
if err != nil {
return nil , false , fmt . Errorf ( "error while listing repos: %v" , err )
}
for _ , issue := range issues {
if issue . PullRequest != nil {
continue
}
allIssues = append ( allIssues , convertGogsIssue ( issue ) )
}
return allIssues , len ( issues ) == 0 , nil
}
// GetComments returns comments according issueNumber
2021-06-30 10:23:49 +03:00
func ( g * GogsDownloader ) GetComments ( opts base . GetCommentOptions ) ( [ ] * base . Comment , bool , error ) {
2021-01-21 22:33:58 +03:00
var allComments = make ( [ ] * base . Comment , 0 , 100 )
2021-08-22 01:47:45 +03:00
comments , err := g . client . ListIssueComments ( g . repoOwner , g . repoName , opts . Context . ForeignID ( ) )
2021-01-21 22:33:58 +03:00
if err != nil {
2021-06-30 10:23:49 +03:00
return nil , false , fmt . Errorf ( "error while listing repos: %v" , err )
2021-01-21 22:33:58 +03:00
}
for _ , comment := range comments {
if len ( comment . Body ) == 0 || comment . Poster == nil {
continue
}
allComments = append ( allComments , & base . Comment {
2021-08-22 01:47:45 +03:00
IssueIndex : opts . Context . LocalID ( ) ,
2021-01-21 22:33:58 +03:00
PosterID : comment . Poster . ID ,
PosterName : comment . Poster . Login ,
PosterEmail : comment . Poster . Email ,
Content : comment . Body ,
Created : comment . Created ,
Updated : comment . Updated ,
} )
}
2021-06-30 10:23:49 +03:00
return allComments , true , nil
2021-01-21 22:33:58 +03:00
}
// GetTopics return repository topics
func ( g * GogsDownloader ) GetTopics ( ) ( [ ] string , error ) {
return [ ] string { } , nil
}
2022-01-10 12:32:37 +03:00
// FormatCloneURL add authentication into remote URLs
2021-01-21 22:33:58 +03:00
func ( g * GogsDownloader ) FormatCloneURL ( opts MigrateOptions , remoteAddr string ) ( string , error ) {
if len ( opts . AuthToken ) > 0 || len ( opts . AuthUsername ) > 0 {
u , err := url . Parse ( remoteAddr )
if err != nil {
return "" , err
}
if len ( opts . AuthToken ) != 0 {
u . User = url . UserPassword ( opts . AuthToken , "" )
} else {
u . User = url . UserPassword ( opts . AuthUsername , opts . AuthPassword )
}
return u . String ( ) , nil
}
return remoteAddr , nil
}
func convertGogsIssue ( issue * gogs . Issue ) * base . Issue {
var milestone string
if issue . Milestone != nil {
milestone = issue . Milestone . Title
}
var labels = make ( [ ] * base . Label , 0 , len ( issue . Labels ) )
for _ , l := range issue . Labels {
labels = append ( labels , convertGogsLabel ( l ) )
}
var closed * time . Time
if issue . State == gogs . STATE_CLOSED {
// gogs client haven't provide closed, so we use updated instead
closed = & issue . Updated
}
return & base . Issue {
Title : issue . Title ,
Number : issue . Index ,
2021-08-18 03:47:18 +03:00
PosterID : issue . Poster . ID ,
2021-01-21 22:33:58 +03:00
PosterName : issue . Poster . Login ,
PosterEmail : issue . Poster . Email ,
Content : issue . Body ,
Milestone : milestone ,
State : string ( issue . State ) ,
Created : issue . Created ,
2021-08-18 03:47:18 +03:00
Updated : issue . Updated ,
2021-01-21 22:33:58 +03:00
Labels : labels ,
Closed : closed ,
2021-08-22 01:47:45 +03:00
Context : base . BasicIssueContext ( issue . Index ) ,
2021-01-21 22:33:58 +03:00
}
}
func convertGogsLabel ( label * gogs . Label ) * base . Label {
return & base . Label {
Name : label . Name ,
Color : label . Color ,
}
}