mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-06 03:52:36 +02:00
Merge branch 'main' into ssh-mirroring
This commit is contained in:
632 files changed
+19973
-21115
No files matched your search
@@ -7,7 +7,7 @@ package migrations
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/google/go-github/v71/github"
|
||||
"github.com/google/go-github/v74/github"
|
||||
)
|
||||
|
||||
// ErrRepoNotCreated returns the error that repository not created
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestGiteaUploadRepo(t *testing.T) {
|
||||
uploader = NewGiteaLocalUploader(graceful.GetManager().HammerContext(), user, user.Name, repoName)
|
||||
)
|
||||
|
||||
err := migrateRepository(db.DefaultContext, user, downloader, uploader, base.MigrateOptions{
|
||||
err := migrateRepository(t.Context(), user, downloader, uploader, base.MigrateOptions{
|
||||
CloneAddr: "https://github.com/go-xorm/builder",
|
||||
RepoName: repoName,
|
||||
AuthUsername: "",
|
||||
@@ -63,17 +63,17 @@ func TestGiteaUploadRepo(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, Name: repoName})
|
||||
assert.True(t, repo.HasWiki())
|
||||
assert.True(t, repo_service.HasWiki(ctx, repo))
|
||||
assert.Equal(t, repo_model.RepositoryReady, repo.Status)
|
||||
|
||||
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
milestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
|
||||
RepoID: repo.ID,
|
||||
IsClosed: optional.Some(false),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, milestones, 1)
|
||||
|
||||
milestones, err = db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
milestones, err = db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
|
||||
RepoID: repo.ID,
|
||||
IsClosed: optional.Some(true),
|
||||
})
|
||||
@@ -84,7 +84,7 @@ func TestGiteaUploadRepo(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, labels, 12)
|
||||
|
||||
releases, err := db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
|
||||
releases, err := db.Find[repo_model.Release](t.Context(), repo_model.FindReleasesOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
PageSize: 10,
|
||||
Page: 0,
|
||||
@@ -95,7 +95,7 @@ func TestGiteaUploadRepo(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, releases, 8)
|
||||
|
||||
releases, err = db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
|
||||
releases, err = db.Find[repo_model.Release](t.Context(), repo_model.FindReleasesOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
PageSize: 10,
|
||||
Page: 0,
|
||||
@@ -106,23 +106,23 @@ func TestGiteaUploadRepo(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, releases, 1)
|
||||
|
||||
issues, err := issues_model.Issues(db.DefaultContext, &issues_model.IssuesOptions{
|
||||
issues, err := issues_model.Issues(t.Context(), &issues_model.IssuesOptions{
|
||||
RepoIDs: []int64{repo.ID},
|
||||
IsPull: optional.Some(false),
|
||||
SortType: "oldest",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, issues, 15)
|
||||
assert.NoError(t, issues[0].LoadDiscussComments(db.DefaultContext))
|
||||
assert.NoError(t, issues[0].LoadDiscussComments(t.Context()))
|
||||
assert.Empty(t, issues[0].Comments)
|
||||
|
||||
pulls, _, err := issues_model.PullRequests(db.DefaultContext, repo.ID, &issues_model.PullRequestsOptions{
|
||||
pulls, _, err := issues_model.PullRequests(t.Context(), repo.ID, &issues_model.PullRequestsOptions{
|
||||
SortType: "oldest",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pulls, 30)
|
||||
assert.NoError(t, pulls[0].LoadIssue(db.DefaultContext))
|
||||
assert.NoError(t, pulls[0].Issue.LoadDiscussComments(db.DefaultContext))
|
||||
assert.NoError(t, pulls[0].LoadIssue(t.Context()))
|
||||
assert.NoError(t, pulls[0].Issue.LoadDiscussComments(t.Context()))
|
||||
assert.Len(t, pulls[0].Issue.Comments, 2)
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) {
|
||||
LoginSourceID: 0,
|
||||
Provider: structs.GiteaService.Name(),
|
||||
}
|
||||
err = user_model.LinkExternalToUser(db.DefaultContext, linkedUser, externalLoginUser)
|
||||
err = user_model.LinkExternalToUser(t.Context(), linkedUser, externalLoginUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
//
|
||||
@@ -236,22 +236,23 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
|
||||
//
|
||||
fromRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
baseRef := "master"
|
||||
assert.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false, fromRepo.ObjectFormatName))
|
||||
err := git.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(git.DefaultContext, &git.RunOpts{Dir: fromRepo.RepoPath()})
|
||||
// this is very different from the real situation. It should be a bare repository for all the Gitea managed repositories
|
||||
assert.NoError(t, git.InitRepository(t.Context(), fromRepo.RepoPath(), false, fromRepo.ObjectFormatName))
|
||||
err := git.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(t.Context(), &git.RunOpts{Dir: fromRepo.RepoPath()})
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("# Testing Repository\n\nOriginally created in: "+fromRepo.RepoPath()), 0o644))
|
||||
assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true))
|
||||
assert.NoError(t, git.AddChanges(t.Context(), fromRepo.RepoPath(), true))
|
||||
signature := git.Signature{
|
||||
Email: "test@example.com",
|
||||
Name: "test",
|
||||
When: time.Now(),
|
||||
}
|
||||
assert.NoError(t, git.CommitChanges(fromRepo.RepoPath(), git.CommitChangesOptions{
|
||||
assert.NoError(t, git.CommitChanges(t.Context(), fromRepo.RepoPath(), git.CommitChangesOptions{
|
||||
Committer: &signature,
|
||||
Author: &signature,
|
||||
Message: "Initial Commit",
|
||||
}))
|
||||
fromGitRepo, err := gitrepo.OpenRepository(git.DefaultContext, fromRepo)
|
||||
fromGitRepo, err := gitrepo.OpenRepository(t.Context(), fromRepo)
|
||||
assert.NoError(t, err)
|
||||
defer fromGitRepo.Close()
|
||||
baseSHA, err := fromGitRepo.GetBranchCommitID(baseRef)
|
||||
@@ -261,12 +262,12 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
|
||||
// fromRepo branch1
|
||||
//
|
||||
headRef := "branch1"
|
||||
_, _, err = git.NewCommand("checkout", "-b").AddDynamicArguments(headRef).RunStdString(git.DefaultContext, &git.RunOpts{Dir: fromRepo.RepoPath()})
|
||||
_, _, err = git.NewCommand("checkout", "-b").AddDynamicArguments(headRef).RunStdString(t.Context(), &git.RunOpts{Dir: fromRepo.RepoPath()})
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("SOMETHING"), 0o644))
|
||||
assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true))
|
||||
assert.NoError(t, git.AddChanges(t.Context(), fromRepo.RepoPath(), true))
|
||||
signature.When = time.Now()
|
||||
assert.NoError(t, git.CommitChanges(fromRepo.RepoPath(), git.CommitChangesOptions{
|
||||
assert.NoError(t, git.CommitChanges(t.Context(), fromRepo.RepoPath(), git.CommitChangesOptions{
|
||||
Committer: &signature,
|
||||
Author: &signature,
|
||||
Message: "Pull request",
|
||||
@@ -282,19 +283,19 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
|
||||
//
|
||||
forkHeadRef := "branch2"
|
||||
forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8})
|
||||
assert.NoError(t, git.CloneWithArgs(git.DefaultContext, nil, fromRepo.RepoPath(), forkRepo.RepoPath(), git.CloneRepoOptions{
|
||||
assert.NoError(t, git.Clone(t.Context(), fromRepo.RepoPath(), forkRepo.RepoPath(), git.CloneRepoOptions{
|
||||
Branch: headRef,
|
||||
}))
|
||||
_, _, err = git.NewCommand("checkout", "-b").AddDynamicArguments(forkHeadRef).RunStdString(git.DefaultContext, &git.RunOpts{Dir: forkRepo.RepoPath()})
|
||||
_, _, err = git.NewCommand("checkout", "-b").AddDynamicArguments(forkHeadRef).RunStdString(t.Context(), &git.RunOpts{Dir: forkRepo.RepoPath()})
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte("# branch2 "+forkRepo.RepoPath()), 0o644))
|
||||
assert.NoError(t, git.AddChanges(forkRepo.RepoPath(), true))
|
||||
assert.NoError(t, git.CommitChanges(forkRepo.RepoPath(), git.CommitChangesOptions{
|
||||
assert.NoError(t, git.AddChanges(t.Context(), forkRepo.RepoPath(), true))
|
||||
assert.NoError(t, git.CommitChanges(t.Context(), forkRepo.RepoPath(), git.CommitChangesOptions{
|
||||
Committer: &signature,
|
||||
Author: &signature,
|
||||
Message: "branch2 commit",
|
||||
}))
|
||||
forkGitRepo, err := gitrepo.OpenRepository(git.DefaultContext, forkRepo)
|
||||
forkGitRepo, err := gitrepo.OpenRepository(t.Context(), forkRepo)
|
||||
assert.NoError(t, err)
|
||||
defer forkGitRepo.Close()
|
||||
forkHeadSHA, err := forkGitRepo.GetBranchCommitID(forkHeadRef)
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/proxy"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/google/go-github/v71/github"
|
||||
"github.com/google/go-github/v74/github"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
@@ -354,7 +354,8 @@ func (g *GithubDownloaderV3) convertGithubRelease(ctx context.Context, rel *gith
|
||||
|
||||
// Prevent open redirect
|
||||
if !hasBaseURL(redirectURL, g.baseURL) &&
|
||||
!hasBaseURL(redirectURL, "https://objects.githubusercontent.com/") {
|
||||
!hasBaseURL(redirectURL, "https://objects.githubusercontent.com/") &&
|
||||
!hasBaseURL(redirectURL, "https://release-assets.githubusercontent.com/") {
|
||||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.GetID(), g, redirectURL)
|
||||
|
||||
return io.NopCloser(strings.NewReader(redirectURL)), nil
|
||||
|
||||
@@ -50,7 +50,7 @@ func (f *GitlabDownloaderFactory) New(ctx context.Context, opts base.MigrateOpti
|
||||
|
||||
log.Trace("Create gitlab downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace)
|
||||
|
||||
return NewGitlabDownloader(ctx, baseURL, repoNameSpace, opts.AuthUsername, opts.AuthPassword, opts.AuthToken)
|
||||
return NewGitlabDownloader(ctx, baseURL, repoNameSpace, opts.AuthToken)
|
||||
}
|
||||
|
||||
// GitServiceType returns the type of git service
|
||||
@@ -93,14 +93,8 @@ type GitlabDownloader struct {
|
||||
//
|
||||
// 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(ctx context.Context, baseURL, repoPath, username, password, token string) (*GitlabDownloader, error) {
|
||||
func NewGitlabDownloader(ctx context.Context, baseURL, repoPath, token string) (*GitlabDownloader, error) {
|
||||
gitlabClient, err := gitlab.NewClient(token, gitlab.WithBaseURL(baseURL), gitlab.WithHTTPClient(NewMigrationHTTPClient()))
|
||||
// Only use basic auth if token is blank and password is NOT
|
||||
// Basic auth will fail with empty strings, but empty token will allow anonymous public API usage
|
||||
if token == "" && password != "" {
|
||||
gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL), gitlab.WithHTTPClient(NewMigrationHTTPClient()))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Trace("Error logging into gitlab: %v", err)
|
||||
return nil, err
|
||||
@@ -206,7 +200,7 @@ func (g *GitlabDownloader) GetTopics(ctx context.Context) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gr.TagList, err
|
||||
return gr.Topics, err
|
||||
}
|
||||
|
||||
// GetMilestones returns milestones
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
|
||||
t.Skipf("Can't access test repo, skipping %s", t.Name())
|
||||
}
|
||||
ctx := t.Context()
|
||||
downloader, err := NewGitlabDownloader(ctx, "https://gitlab.com", "gitea/test_repo", "", "", gitlabPersonalAccessToken)
|
||||
downloader, err := NewGitlabDownloader(ctx, "https://gitlab.com", "gitea/test_repo", gitlabPersonalAccessToken)
|
||||
if err != nil {
|
||||
t.Fatalf("NewGitlabDownloader is nil: %v", err)
|
||||
}
|
||||
|
||||
+162
-111
@@ -6,6 +6,7 @@ package migrations
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -16,8 +17,12 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
base "code.gitea.io/gitea/modules/migration"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
const OneDevRequiredVersion = "12.0.1"
|
||||
|
||||
var (
|
||||
_ base.Downloader = &OneDevDownloader{}
|
||||
_ base.DownloaderFactory = &OneDevDownloaderFactory{}
|
||||
@@ -37,23 +42,14 @@ func (f *OneDevDownloaderFactory) New(ctx context.Context, opts base.MigrateOpti
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var repoName string
|
||||
|
||||
fields := strings.Split(strings.Trim(u.Path, "/"), "/")
|
||||
if len(fields) == 2 && fields[0] == "projects" {
|
||||
repoName = fields[1]
|
||||
} else if len(fields) == 1 {
|
||||
repoName = fields[0]
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid path: %s", u.Path)
|
||||
}
|
||||
repoPath := strings.Trim(u.Path, "/")
|
||||
|
||||
u.Path = ""
|
||||
u.Fragment = ""
|
||||
|
||||
log.Trace("Create onedev downloader. BaseURL: %v RepoName: %s", u, repoName)
|
||||
log.Trace("Create onedev downloader. BaseURL: %v RepoPath: %s", u, repoPath)
|
||||
|
||||
return NewOneDevDownloader(ctx, u, opts.AuthUsername, opts.AuthPassword, repoName), nil
|
||||
return NewOneDevDownloader(ctx, u, opts.AuthUsername, opts.AuthPassword, repoPath), nil
|
||||
}
|
||||
|
||||
// GitServiceType returns the type of git service
|
||||
@@ -62,9 +58,9 @@ func (f *OneDevDownloaderFactory) GitServiceType() structs.GitServiceType {
|
||||
}
|
||||
|
||||
type onedevUser struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
ID int64
|
||||
Name string
|
||||
Email string
|
||||
}
|
||||
|
||||
// OneDevDownloader implements a Downloader interface to get repository information
|
||||
@@ -73,7 +69,7 @@ type OneDevDownloader struct {
|
||||
base.NullDownloader
|
||||
client *http.Client
|
||||
baseURL *url.URL
|
||||
repoName string
|
||||
repoPath string
|
||||
repoID int64
|
||||
maxIssueIndex int64
|
||||
userMap map[int64]*onedevUser
|
||||
@@ -81,10 +77,10 @@ type OneDevDownloader struct {
|
||||
}
|
||||
|
||||
// NewOneDevDownloader creates a new downloader
|
||||
func NewOneDevDownloader(_ context.Context, baseURL *url.URL, username, password, repoName string) *OneDevDownloader {
|
||||
func NewOneDevDownloader(_ context.Context, baseURL *url.URL, username, password, repoPath string) *OneDevDownloader {
|
||||
downloader := &OneDevDownloader{
|
||||
baseURL: baseURL,
|
||||
repoName: repoName,
|
||||
repoPath: repoPath,
|
||||
client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: func(req *http.Request) (*url.URL, error) {
|
||||
@@ -104,14 +100,14 @@ func NewOneDevDownloader(_ context.Context, baseURL *url.URL, username, password
|
||||
|
||||
// String implements Stringer
|
||||
func (d *OneDevDownloader) String() string {
|
||||
return fmt.Sprintf("migration from oneDev server %s [%d]/%s", d.baseURL, d.repoID, d.repoName)
|
||||
return fmt.Sprintf("migration from oneDev server %s [%d]/%s", d.baseURL, d.repoID, d.repoPath)
|
||||
}
|
||||
|
||||
func (d *OneDevDownloader) LogString() string {
|
||||
if d == nil {
|
||||
return "<OneDevDownloader nil>"
|
||||
}
|
||||
return fmt.Sprintf("<OneDevDownloader %s [%d]/%s>", d.baseURL, d.repoID, d.repoName)
|
||||
return fmt.Sprintf("<OneDevDownloader %s [%d]/%s>", d.baseURL, d.repoID, d.repoPath)
|
||||
}
|
||||
|
||||
func (d *OneDevDownloader) callAPI(ctx context.Context, endpoint string, parameter map[string]string, result any) error {
|
||||
@@ -139,23 +135,54 @@ func (d *OneDevDownloader) callAPI(ctx context.Context, endpoint string, paramet
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// special case to read OneDev server version, which is not valid JSON
|
||||
if presult, ok := result.(**version.Version); ok {
|
||||
bytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vers, err := version.NewVersion(string(bytes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*presult = vers
|
||||
return nil
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
return decoder.Decode(&result)
|
||||
}
|
||||
|
||||
// GetRepoInfo returns repository information
|
||||
func (d *OneDevDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, error) {
|
||||
// check OneDev server version
|
||||
var serverVersion *version.Version
|
||||
err := d.callAPI(
|
||||
ctx,
|
||||
"/~api/version/server",
|
||||
nil,
|
||||
&serverVersion,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get OneDev server version; OneDev %s or newer required", OneDevRequiredVersion)
|
||||
}
|
||||
requiredVersion, _ := version.NewVersion(OneDevRequiredVersion)
|
||||
if serverVersion.LessThan(requiredVersion) {
|
||||
return nil, fmt.Errorf("OneDev %s or newer required; currently running OneDev %s", OneDevRequiredVersion, serverVersion)
|
||||
}
|
||||
|
||||
info := make([]struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
}, 0, 1)
|
||||
|
||||
err := d.callAPI(
|
||||
err = d.callAPI(
|
||||
ctx,
|
||||
"/api/projects",
|
||||
"/~api/projects",
|
||||
map[string]string{
|
||||
"query": `"Name" is "` + d.repoName + `"`,
|
||||
"query": `"Path" is "` + d.repoPath + `"`,
|
||||
"offset": "0",
|
||||
"count": "1",
|
||||
},
|
||||
@@ -165,16 +192,12 @@ func (d *OneDevDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, e
|
||||
return nil, err
|
||||
}
|
||||
if len(info) != 1 {
|
||||
return nil, fmt.Errorf("Project %s not found", d.repoName)
|
||||
return nil, fmt.Errorf("Project %s not found", d.repoPath)
|
||||
}
|
||||
|
||||
d.repoID = info[0].ID
|
||||
|
||||
cloneURL, err := d.baseURL.Parse(info[0].Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
originalURL, err := d.baseURL.Parse("/projects/" + info[0].Name)
|
||||
cloneURL, err := d.baseURL.Parse(info[0].Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -183,25 +206,25 @@ func (d *OneDevDownloader) GetRepoInfo(ctx context.Context) (*base.Repository, e
|
||||
Name: info[0].Name,
|
||||
Description: info[0].Description,
|
||||
CloneURL: cloneURL.String(),
|
||||
OriginalURL: originalURL.String(),
|
||||
OriginalURL: cloneURL.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetMilestones returns milestones
|
||||
func (d *OneDevDownloader) GetMilestones(ctx context.Context) ([]*base.Milestone, error) {
|
||||
rawMilestones := make([]struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
DueDate *time.Time `json:"dueDate"`
|
||||
Closed bool `json:"closed"`
|
||||
}, 0, 100)
|
||||
|
||||
endpoint := fmt.Sprintf("/api/projects/%d/milestones", d.repoID)
|
||||
endpoint := fmt.Sprintf("/~api/projects/%d/iterations", d.repoID)
|
||||
|
||||
milestones := make([]*base.Milestone, 0, 100)
|
||||
offset := 0
|
||||
for {
|
||||
rawMilestones := make([]struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
DueDay int64 `json:"dueDay"`
|
||||
Closed bool `json:"closed"`
|
||||
}, 0, 100)
|
||||
|
||||
err := d.callAPI(
|
||||
ctx,
|
||||
endpoint,
|
||||
@@ -221,16 +244,26 @@ func (d *OneDevDownloader) GetMilestones(ctx context.Context) ([]*base.Milestone
|
||||
|
||||
for _, milestone := range rawMilestones {
|
||||
d.milestoneMap[milestone.ID] = milestone.Name
|
||||
closed := milestone.DueDate
|
||||
if !milestone.Closed {
|
||||
closed = nil
|
||||
|
||||
var dueDate *time.Time
|
||||
if milestone.DueDay != 0 {
|
||||
d := time.Unix(milestone.DueDay*24*60*60, 0)
|
||||
dueDate = &d
|
||||
}
|
||||
|
||||
var closedDate *time.Time
|
||||
state := "open"
|
||||
if milestone.Closed {
|
||||
closedDate = dueDate
|
||||
state = "closed"
|
||||
}
|
||||
|
||||
milestones = append(milestones, &base.Milestone{
|
||||
Title: milestone.Name,
|
||||
Description: milestone.Description,
|
||||
Deadline: milestone.DueDate,
|
||||
Closed: closed,
|
||||
Deadline: dueDate,
|
||||
Closed: closedDate,
|
||||
State: state,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -273,6 +306,10 @@ type onedevIssueContext struct {
|
||||
|
||||
// GetIssues returns issues
|
||||
func (d *OneDevDownloader) GetIssues(ctx context.Context, page, perPage int) ([]*base.Issue, bool, error) {
|
||||
type Field struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
rawIssues := make([]struct {
|
||||
ID int64 `json:"id"`
|
||||
Number int64 `json:"number"`
|
||||
@@ -281,15 +318,17 @@ func (d *OneDevDownloader) GetIssues(ctx context.Context, page, perPage int) ([]
|
||||
Description string `json:"description"`
|
||||
SubmitterID int64 `json:"submitterId"`
|
||||
SubmitDate time.Time `json:"submitDate"`
|
||||
Fields []Field `json:"fields"`
|
||||
}, 0, perPage)
|
||||
|
||||
err := d.callAPI(
|
||||
ctx,
|
||||
"/api/issues",
|
||||
"/~api/issues",
|
||||
map[string]string{
|
||||
"query": `"Project" is "` + d.repoName + `"`,
|
||||
"offset": strconv.Itoa((page - 1) * perPage),
|
||||
"count": strconv.Itoa(perPage),
|
||||
"query": `"Project" is "` + d.repoPath + `"`,
|
||||
"offset": strconv.Itoa((page - 1) * perPage),
|
||||
"count": strconv.Itoa(perPage),
|
||||
"withFields": "true",
|
||||
},
|
||||
&rawIssues,
|
||||
)
|
||||
@@ -299,22 +338,8 @@ func (d *OneDevDownloader) GetIssues(ctx context.Context, page, perPage int) ([]
|
||||
|
||||
issues := make([]*base.Issue, 0, len(rawIssues))
|
||||
for _, issue := range rawIssues {
|
||||
fields := make([]struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}, 0, 10)
|
||||
err := d.callAPI(
|
||||
ctx,
|
||||
fmt.Sprintf("/api/issues/%d/fields", issue.ID),
|
||||
nil,
|
||||
&fields,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
var label *base.Label
|
||||
for _, field := range fields {
|
||||
for _, field := range issue.Fields {
|
||||
if field.Name == "Type" {
|
||||
label = &base.Label{Name: field.Value}
|
||||
break
|
||||
@@ -327,7 +352,7 @@ func (d *OneDevDownloader) GetIssues(ctx context.Context, page, perPage int) ([]
|
||||
}, 0, 10)
|
||||
err = d.callAPI(
|
||||
ctx,
|
||||
fmt.Sprintf("/api/issues/%d/milestones", issue.ID),
|
||||
fmt.Sprintf("/~api/issues/%d/iterations", issue.ID),
|
||||
nil,
|
||||
&milestones,
|
||||
)
|
||||
@@ -383,9 +408,9 @@ func (d *OneDevDownloader) GetComments(ctx context.Context, commentable base.Com
|
||||
|
||||
var endpoint string
|
||||
if context.IsPullRequest {
|
||||
endpoint = fmt.Sprintf("/api/pull-requests/%d/comments", commentable.GetForeignIndex())
|
||||
endpoint = fmt.Sprintf("/~api/pulls/%d/comments", commentable.GetForeignIndex())
|
||||
} else {
|
||||
endpoint = fmt.Sprintf("/api/issues/%d/comments", commentable.GetForeignIndex())
|
||||
endpoint = fmt.Sprintf("/~api/issues/%d/comments", commentable.GetForeignIndex())
|
||||
}
|
||||
|
||||
err := d.callAPI(
|
||||
@@ -405,9 +430,9 @@ func (d *OneDevDownloader) GetComments(ctx context.Context, commentable base.Com
|
||||
}, 0, 100)
|
||||
|
||||
if context.IsPullRequest {
|
||||
endpoint = fmt.Sprintf("/api/pull-requests/%d/changes", commentable.GetForeignIndex())
|
||||
endpoint = fmt.Sprintf("/~api/pulls/%d/changes", commentable.GetForeignIndex())
|
||||
} else {
|
||||
endpoint = fmt.Sprintf("/api/issues/%d/changes", commentable.GetForeignIndex())
|
||||
endpoint = fmt.Sprintf("/~api/issues/%d/changes", commentable.GetForeignIndex())
|
||||
}
|
||||
|
||||
err = d.callAPI(
|
||||
@@ -468,26 +493,24 @@ func (d *OneDevDownloader) GetComments(ctx context.Context, commentable base.Com
|
||||
// GetPullRequests returns pull requests
|
||||
func (d *OneDevDownloader) GetPullRequests(ctx context.Context, page, perPage int) ([]*base.PullRequest, bool, error) {
|
||||
rawPullRequests := make([]struct {
|
||||
ID int64 `json:"id"`
|
||||
Number int64 `json:"number"`
|
||||
Title string `json:"title"`
|
||||
SubmitterID int64 `json:"submitterId"`
|
||||
SubmitDate time.Time `json:"submitDate"`
|
||||
Description string `json:"description"`
|
||||
TargetBranch string `json:"targetBranch"`
|
||||
SourceBranch string `json:"sourceBranch"`
|
||||
BaseCommitHash string `json:"baseCommitHash"`
|
||||
CloseInfo *struct {
|
||||
Date *time.Time `json:"date"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
ID int64 `json:"id"`
|
||||
Number int64 `json:"number"`
|
||||
Title string `json:"title"`
|
||||
SubmitterID int64 `json:"submitterId"`
|
||||
SubmitDate time.Time `json:"submitDate"`
|
||||
Description string `json:"description"`
|
||||
TargetBranch string `json:"targetBranch"`
|
||||
SourceBranch string `json:"sourceBranch"`
|
||||
BaseCommitHash string `json:"baseCommitHash"`
|
||||
CloseDate *time.Time `json:"closeDate"`
|
||||
Status string `json:"status"` // Possible values: OPEN, MERGED, DISCARDED
|
||||
}, 0, perPage)
|
||||
|
||||
err := d.callAPI(
|
||||
ctx,
|
||||
"/api/pull-requests",
|
||||
"/~api/pulls",
|
||||
map[string]string{
|
||||
"query": `"Target Project" is "` + d.repoName + `"`,
|
||||
"query": `"Target Project" is "` + d.repoPath + `"`,
|
||||
"offset": strconv.Itoa((page - 1) * perPage),
|
||||
"count": strconv.Itoa(perPage),
|
||||
},
|
||||
@@ -507,7 +530,7 @@ func (d *OneDevDownloader) GetPullRequests(ctx context.Context, page, perPage in
|
||||
}
|
||||
err := d.callAPI(
|
||||
ctx,
|
||||
fmt.Sprintf("/api/pull-requests/%d/merge-preview", pr.ID),
|
||||
fmt.Sprintf("/~api/pulls/%d/merge-preview", pr.ID),
|
||||
nil,
|
||||
&mergePreview,
|
||||
)
|
||||
@@ -519,12 +542,12 @@ func (d *OneDevDownloader) GetPullRequests(ctx context.Context, page, perPage in
|
||||
merged := false
|
||||
var closeTime *time.Time
|
||||
var mergedTime *time.Time
|
||||
if pr.CloseInfo != nil {
|
||||
if pr.Status != "OPEN" {
|
||||
state = "closed"
|
||||
closeTime = pr.CloseInfo.Date
|
||||
if pr.CloseInfo.Status == "MERGED" { // "DISCARDED"
|
||||
closeTime = pr.CloseDate
|
||||
if pr.Status == "MERGED" { // "DISCARDED"
|
||||
merged = true
|
||||
mergedTime = pr.CloseInfo.Date
|
||||
mergedTime = pr.CloseDate
|
||||
}
|
||||
}
|
||||
poster := d.tryGetUser(ctx, pr.SubmitterID)
|
||||
@@ -545,12 +568,12 @@ func (d *OneDevDownloader) GetPullRequests(ctx context.Context, page, perPage in
|
||||
Head: base.PullRequestBranch{
|
||||
Ref: pr.SourceBranch,
|
||||
SHA: mergePreview.HeadCommitHash,
|
||||
RepoName: d.repoName,
|
||||
RepoName: d.repoPath,
|
||||
},
|
||||
Base: base.PullRequestBranch{
|
||||
Ref: pr.TargetBranch,
|
||||
SHA: mergePreview.TargetHeadCommitHash,
|
||||
RepoName: d.repoName,
|
||||
RepoName: d.repoPath,
|
||||
},
|
||||
ForeignIndex: pr.ID,
|
||||
Context: onedevIssueContext{IsPullRequest: true},
|
||||
@@ -566,18 +589,14 @@ func (d *OneDevDownloader) GetPullRequests(ctx context.Context, page, perPage in
|
||||
// GetReviews returns pull requests reviews
|
||||
func (d *OneDevDownloader) GetReviews(ctx context.Context, reviewable base.Reviewable) ([]*base.Review, error) {
|
||||
rawReviews := make([]struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"userId"`
|
||||
Result *struct {
|
||||
Commit string `json:"commit"`
|
||||
Approved bool `json:"approved"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"userId"`
|
||||
Status string `json:"status"` // Possible values: PENDING, APPROVED, REQUESTED_FOR_CHANGES, EXCLUDED
|
||||
}, 0, 100)
|
||||
|
||||
err := d.callAPI(
|
||||
ctx,
|
||||
fmt.Sprintf("/api/pull-requests/%d/reviews", reviewable.GetForeignIndex()),
|
||||
fmt.Sprintf("/~api/pulls/%d/reviews", reviewable.GetForeignIndex()),
|
||||
nil,
|
||||
&rawReviews,
|
||||
)
|
||||
@@ -589,14 +608,11 @@ func (d *OneDevDownloader) GetReviews(ctx context.Context, reviewable base.Revie
|
||||
for _, review := range rawReviews {
|
||||
state := base.ReviewStatePending
|
||||
content := ""
|
||||
if review.Result != nil {
|
||||
if len(review.Result.Comment) > 0 {
|
||||
state = base.ReviewStateCommented
|
||||
content = review.Result.Comment
|
||||
}
|
||||
if review.Result.Approved {
|
||||
state = base.ReviewStateApproved
|
||||
}
|
||||
switch review.Status {
|
||||
case "APPROVED":
|
||||
state = base.ReviewStateApproved
|
||||
case "REQUESTED_FOR_CHANGES":
|
||||
state = base.ReviewStateChangesRequested
|
||||
}
|
||||
|
||||
poster := d.tryGetUser(ctx, review.UserID)
|
||||
@@ -620,17 +636,52 @@ func (d *OneDevDownloader) GetTopics(_ context.Context) ([]string, error) {
|
||||
func (d *OneDevDownloader) tryGetUser(ctx context.Context, userID int64) *onedevUser {
|
||||
user, ok := d.userMap[userID]
|
||||
if !ok {
|
||||
// get user name
|
||||
type RawUser struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
var rawUser RawUser
|
||||
err := d.callAPI(
|
||||
ctx,
|
||||
fmt.Sprintf("/api/users/%d", userID),
|
||||
fmt.Sprintf("/~api/users/%d", userID),
|
||||
nil,
|
||||
&user,
|
||||
&rawUser,
|
||||
)
|
||||
if err != nil {
|
||||
user = &onedevUser{
|
||||
Name: fmt.Sprintf("User %d", userID),
|
||||
var userName string
|
||||
if err == nil {
|
||||
userName = rawUser.Name
|
||||
} else {
|
||||
userName = fmt.Sprintf("User %d", userID)
|
||||
}
|
||||
|
||||
// get (primary) user Email address
|
||||
rawEmailAddresses := make([]struct {
|
||||
Value string `json:"value"`
|
||||
Primary bool `json:"primary"`
|
||||
}, 0, 10)
|
||||
err = d.callAPI(
|
||||
ctx,
|
||||
fmt.Sprintf("/~api/users/%d/email-addresses", userID),
|
||||
nil,
|
||||
&rawEmailAddresses,
|
||||
)
|
||||
var userEmail string
|
||||
if err == nil {
|
||||
for _, email := range rawEmailAddresses {
|
||||
if userEmail == "" || email.Primary {
|
||||
userEmail = email.Value
|
||||
}
|
||||
if email.Primary {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user = &onedevUser{
|
||||
ID: userID,
|
||||
Name: userName,
|
||||
Email: userEmail,
|
||||
}
|
||||
d.userMap[userID] = user
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user