Migrate reviews when migrating repository from github (#9463)
* fix typo * Migrate reviews when migrating repository from github * fix lint * Added test and migration when external user login * fix test * fix commented state * Some improvements * fix bug when get pull request and ref original author on code comments * Fix migrated line; Added comment for review * Don't load all pull requests attributes * Fix typo * wrong change copy head * fix tests * fix reactions * Fix test * fix fmt * fix review comment reactions
This commit is contained in:
parent
bfdfa9a8b3
commit
f6067a8465
|
@ -181,5 +181,8 @@ func UpdateMigrationsByType(tp structs.GitServiceType, externalUserID string, us
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return UpdateReactionsMigrationsByType(tp, externalUserID, userID)
|
if err := UpdateReactionsMigrationsByType(tp, externalUserID, userID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return UpdateReviewsMigrationsByType(tp, externalUserID, userID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,12 @@
|
||||||
|
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import "xorm.io/xorm"
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
// InsertMilestones creates milestones of repository.
|
// InsertMilestones creates milestones of repository.
|
||||||
func InsertMilestones(ms ...*Milestone) (err error) {
|
func InsertMilestones(ms ...*Milestone) (err error) {
|
||||||
|
@ -202,3 +207,23 @@ func InsertReleases(rels ...*Release) error {
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateReviewsMigrationsByType updates reviews' migrations information via given git service type and original id and poster id
|
||||||
|
func UpdateReviewsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error {
|
||||||
|
_, err := x.Table("review").
|
||||||
|
Where(builder.In("issue_id",
|
||||||
|
builder.Select("issue.id").
|
||||||
|
From("issue").
|
||||||
|
InnerJoin("repository", "issue.repo_id = repository.id").
|
||||||
|
Where(builder.Eq{
|
||||||
|
"repository.original_service_type": tp,
|
||||||
|
}),
|
||||||
|
)).
|
||||||
|
And("review.original_author_id = ?", originalAuthorID).
|
||||||
|
Update(map[string]interface{}{
|
||||||
|
"poster_id": posterID,
|
||||||
|
"original_author": "",
|
||||||
|
"original_author_id": 0,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -304,6 +304,8 @@ var migrations = []Migration{
|
||||||
NewMigration("Add original informations for reactions", addReactionOriginals),
|
NewMigration("Add original informations for reactions", addReactionOriginals),
|
||||||
// v124 -> v125
|
// v124 -> v125
|
||||||
NewMigration("Add columns to user and repository", addUserRepoMissingColumns),
|
NewMigration("Add columns to user and repository", addUserRepoMissingColumns),
|
||||||
|
// v125 -> v126
|
||||||
|
NewMigration("Add some columns on review for migration", addReviewMigrateInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate database to current version
|
// Migrate database to current version
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright 2020 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 (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addReviewMigrateInfo(x *xorm.Engine) error {
|
||||||
|
type Review struct {
|
||||||
|
OriginalAuthor string
|
||||||
|
OriginalAuthorID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := x.Sync2(new(Review)); err != nil {
|
||||||
|
return fmt.Errorf("Sync2: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -655,6 +655,19 @@ func GetPullRequestByID(id int64) (*PullRequest, error) {
|
||||||
return getPullRequestByID(x, id)
|
return getPullRequestByID(x, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPullRequestByIssueIDWithNoAttributes returns pull request with no attributes loaded by given issue ID.
|
||||||
|
func GetPullRequestByIssueIDWithNoAttributes(issueID int64) (*PullRequest, error) {
|
||||||
|
var pr PullRequest
|
||||||
|
has, err := x.Where("issue_id = ?", issueID).Get(&pr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""}
|
||||||
|
}
|
||||||
|
return &pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getPullRequestByIssueID(e Engine, issueID int64) (*PullRequest, error) {
|
func getPullRequestByIssueID(e Engine, issueID int64) (*PullRequest, error) {
|
||||||
pr := &PullRequest{
|
pr := &PullRequest{
|
||||||
IssueID: issueID,
|
IssueID: issueID,
|
||||||
|
|
|
@ -45,13 +45,15 @@ func (rt ReviewType) Icon() string {
|
||||||
|
|
||||||
// Review represents collection of code comments giving feedback for a PR
|
// Review represents collection of code comments giving feedback for a PR
|
||||||
type Review struct {
|
type Review struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
Type ReviewType
|
Type ReviewType
|
||||||
Reviewer *User `xorm:"-"`
|
Reviewer *User `xorm:"-"`
|
||||||
ReviewerID int64 `xorm:"index"`
|
ReviewerID int64 `xorm:"index"`
|
||||||
Issue *Issue `xorm:"-"`
|
OriginalAuthor string
|
||||||
IssueID int64 `xorm:"index"`
|
OriginalAuthorID int64
|
||||||
Content string `xorm:"TEXT"`
|
Issue *Issue `xorm:"-"`
|
||||||
|
IssueID int64 `xorm:"index"`
|
||||||
|
Content string `xorm:"TEXT"`
|
||||||
// Official is a review made by an assigned approver (counts towards approval)
|
// Official is a review made by an assigned approver (counts towards approval)
|
||||||
Official bool `xorm:"NOT NULL DEFAULT false"`
|
Official bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
CommitID string `xorm:"VARCHAR(40)"`
|
CommitID string `xorm:"VARCHAR(40)"`
|
||||||
|
@ -62,6 +64,8 @@ type Review struct {
|
||||||
|
|
||||||
// CodeComments are the initial code comments of the review
|
// CodeComments are the initial code comments of the review
|
||||||
CodeComments CodeComments `xorm:"-"`
|
CodeComments CodeComments `xorm:"-"`
|
||||||
|
|
||||||
|
Comments []*Comment `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Review) loadCodeComments(e Engine) (err error) {
|
func (r *Review) loadCodeComments(e Engine) (err error) {
|
||||||
|
@ -398,3 +402,43 @@ func MarkReviewsAsNotStale(issueID int64, commitID string) (err error) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InsertReviews inserts review and review comments
|
||||||
|
func InsertReviews(reviews []*Review) error {
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, review := range reviews {
|
||||||
|
if _, err := sess.NoAutoTime().Insert(review); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := sess.NoAutoTime().Insert(&Comment{
|
||||||
|
Type: CommentTypeReview,
|
||||||
|
Content: review.Content,
|
||||||
|
PosterID: review.ReviewerID,
|
||||||
|
OriginalAuthor: review.OriginalAuthor,
|
||||||
|
OriginalAuthorID: review.OriginalAuthorID,
|
||||||
|
IssueID: review.IssueID,
|
||||||
|
ReviewID: review.ID,
|
||||||
|
CreatedUnix: review.CreatedUnix,
|
||||||
|
UpdatedUnix: review.UpdatedUnix,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range review.Comments {
|
||||||
|
c.ReviewID = review.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := sess.NoAutoTime().Insert(review.Comments); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ type Downloader interface {
|
||||||
GetIssues(page, perPage int) ([]*Issue, bool, error)
|
GetIssues(page, perPage int) ([]*Issue, bool, error)
|
||||||
GetComments(issueNumber int64) ([]*Comment, error)
|
GetComments(issueNumber int64) ([]*Comment, error)
|
||||||
GetPullRequests(page, perPage int) ([]*PullRequest, error)
|
GetPullRequests(page, perPage int) ([]*PullRequest, error)
|
||||||
|
GetReviews(pullRequestNumber int64) ([]*Review, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloaderFactory defines an interface to match a downloader implementation and create a downloader
|
// DownloaderFactory defines an interface to match a downloader implementation and create a downloader
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
// 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 base
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// enumerate all review states
|
||||||
|
const (
|
||||||
|
ReviewStatePending = "PENDING"
|
||||||
|
ReviewStateApproved = "APPROVED"
|
||||||
|
ReviewStateChangesRequested = "CHANGES_REQUESTED"
|
||||||
|
ReviewStateCommented = "COMMENTED"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Review is a standard review information
|
||||||
|
type Review struct {
|
||||||
|
ID int64
|
||||||
|
IssueIndex int64
|
||||||
|
ReviewerID int64
|
||||||
|
ReviewerName string
|
||||||
|
Official bool
|
||||||
|
CommitID string
|
||||||
|
Content string
|
||||||
|
CreatedAt time.Time
|
||||||
|
State string // PENDING, APPROVED, REQUEST_CHANGES, or COMMENT
|
||||||
|
Comments []*ReviewComment
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReviewComment represents a review comment
|
||||||
|
type ReviewComment struct {
|
||||||
|
ID int64
|
||||||
|
InReplyTo int64
|
||||||
|
Content string
|
||||||
|
TreePath string
|
||||||
|
DiffHunk string
|
||||||
|
Position int
|
||||||
|
CommitID string
|
||||||
|
PosterID int64
|
||||||
|
Reactions []*Reaction
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ type Uploader interface {
|
||||||
CreateIssues(issues ...*Issue) error
|
CreateIssues(issues ...*Issue) error
|
||||||
CreateComments(comments ...*Comment) error
|
CreateComments(comments ...*Comment) error
|
||||||
CreatePullRequests(prs ...*PullRequest) error
|
CreatePullRequests(prs ...*PullRequest) error
|
||||||
|
CreateReviews(reviews ...*Review) error
|
||||||
Rollback() error
|
Rollback() error
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,3 +78,8 @@ func (g *PlainGitDownloader) GetComments(issueNumber int64) ([]*base.Comment, er
|
||||||
func (g *PlainGitDownloader) GetPullRequests(start, limit int) ([]*base.PullRequest, error) {
|
func (g *PlainGitDownloader) GetPullRequests(start, limit int) ([]*base.PullRequest, error) {
|
||||||
return nil, ErrNotSupported
|
return nil, ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetReviews returns reviews according issue number
|
||||||
|
func (g *PlainGitDownloader) GetReviews(issueNumber int64) ([]*base.Review, error) {
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -27,6 +28,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
|
|
||||||
gouuid "github.com/satori/go.uuid"
|
gouuid "github.com/satori/go.uuid"
|
||||||
)
|
)
|
||||||
|
@ -48,6 +50,7 @@ type GiteaLocalUploader struct {
|
||||||
gitRepo *git.Repository
|
gitRepo *git.Repository
|
||||||
prHeadCache map[string]struct{}
|
prHeadCache map[string]struct{}
|
||||||
userMap map[int64]int64 // external user id mapping to user id
|
userMap map[int64]int64 // external user id mapping to user id
|
||||||
|
prCache map[int64]*models.PullRequest
|
||||||
gitServiceType structs.GitServiceType
|
gitServiceType structs.GitServiceType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +63,7 @@ func NewGiteaLocalUploader(ctx context.Context, doer *models.User, repoOwner, re
|
||||||
repoName: repoName,
|
repoName: repoName,
|
||||||
prHeadCache: make(map[string]struct{}),
|
prHeadCache: make(map[string]struct{}),
|
||||||
userMap: make(map[int64]int64),
|
userMap: make(map[int64]int64),
|
||||||
|
prCache: make(map[int64]*models.PullRequest),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -706,6 +710,122 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
|
||||||
return &pullRequest, nil
|
return &pullRequest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertReviewState(state string) models.ReviewType {
|
||||||
|
switch state {
|
||||||
|
case base.ReviewStatePending:
|
||||||
|
return models.ReviewTypePending
|
||||||
|
case base.ReviewStateApproved:
|
||||||
|
return models.ReviewTypeApprove
|
||||||
|
case base.ReviewStateChangesRequested:
|
||||||
|
return models.ReviewTypeReject
|
||||||
|
case base.ReviewStateCommented:
|
||||||
|
return models.ReviewTypeComment
|
||||||
|
default:
|
||||||
|
return models.ReviewTypePending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateReviews create pull request reviews
|
||||||
|
func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
|
||||||
|
var cms = make([]*models.Review, 0, len(reviews))
|
||||||
|
for _, review := range reviews {
|
||||||
|
var issueID int64
|
||||||
|
if issueIDStr, ok := g.issues.Load(review.IssueIndex); !ok {
|
||||||
|
issue, err := models.GetIssueByIndex(g.repo.ID, review.IssueIndex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
issueID = issue.ID
|
||||||
|
g.issues.Store(review.IssueIndex, issueID)
|
||||||
|
} else {
|
||||||
|
issueID = issueIDStr.(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
userid, ok := g.userMap[review.ReviewerID]
|
||||||
|
tp := g.gitServiceType.Name()
|
||||||
|
if !ok && tp != "" {
|
||||||
|
var err error
|
||||||
|
userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", review.ReviewerID))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetUserIDByExternalUserID: %v", err)
|
||||||
|
}
|
||||||
|
if userid > 0 {
|
||||||
|
g.userMap[review.ReviewerID] = userid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cm = models.Review{
|
||||||
|
Type: convertReviewState(review.State),
|
||||||
|
IssueID: issueID,
|
||||||
|
Content: review.Content,
|
||||||
|
Official: review.Official,
|
||||||
|
CreatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
|
||||||
|
UpdatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if userid > 0 {
|
||||||
|
cm.ReviewerID = userid
|
||||||
|
} else {
|
||||||
|
cm.ReviewerID = g.doer.ID
|
||||||
|
cm.OriginalAuthor = review.ReviewerName
|
||||||
|
cm.OriginalAuthorID = review.ReviewerID
|
||||||
|
}
|
||||||
|
|
||||||
|
// get pr
|
||||||
|
pr, ok := g.prCache[issueID]
|
||||||
|
if !ok {
|
||||||
|
var err error
|
||||||
|
pr, err = models.GetPullRequestByIssueIDWithNoAttributes(issueID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
g.prCache[issueID] = pr
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, comment := range review.Comments {
|
||||||
|
headCommitID, err := g.gitRepo.GetRefCommitID(pr.GetGitRefName())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err)
|
||||||
|
}
|
||||||
|
patchBuf := new(bytes.Buffer)
|
||||||
|
if err := gitdiff.GetRawDiffForFile(g.gitRepo.Path, pr.MergeBase, headCommitID, gitdiff.RawDiffNormal, comment.TreePath, patchBuf); err != nil {
|
||||||
|
return fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, line, _ := gitdiff.ParseDiffHunkString(comment.DiffHunk)
|
||||||
|
|
||||||
|
patch := gitdiff.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
|
||||||
|
|
||||||
|
var c = models.Comment{
|
||||||
|
Type: models.CommentTypeCode,
|
||||||
|
PosterID: comment.PosterID,
|
||||||
|
IssueID: issueID,
|
||||||
|
Content: comment.Content,
|
||||||
|
Line: int64(line + comment.Position - 1),
|
||||||
|
TreePath: comment.TreePath,
|
||||||
|
CommitSHA: comment.CommitID,
|
||||||
|
Patch: patch,
|
||||||
|
CreatedUnix: timeutil.TimeStamp(comment.CreatedAt.Unix()),
|
||||||
|
UpdatedUnix: timeutil.TimeStamp(comment.UpdatedAt.Unix()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if userid > 0 {
|
||||||
|
c.PosterID = userid
|
||||||
|
} else {
|
||||||
|
c.PosterID = g.doer.ID
|
||||||
|
c.OriginalAuthor = review.ReviewerName
|
||||||
|
c.OriginalAuthorID = review.ReviewerID
|
||||||
|
}
|
||||||
|
|
||||||
|
cm.Comments = append(cm.Comments, &c)
|
||||||
|
}
|
||||||
|
|
||||||
|
cms = append(cms, &cm)
|
||||||
|
}
|
||||||
|
|
||||||
|
return models.InsertReviews(cms)
|
||||||
|
}
|
||||||
|
|
||||||
// Rollback when migrating failed, this will rollback all the changes.
|
// Rollback when migrating failed, this will rollback all the changes.
|
||||||
func (g *GiteaLocalUploader) Rollback() error {
|
func (g *GiteaLocalUploader) Rollback() error {
|
||||||
if g.repo != nil && g.repo.ID > 0 {
|
if g.repo != nil && g.repo.ID > 0 {
|
||||||
|
|
|
@ -418,10 +418,14 @@ func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool,
|
||||||
|
|
||||||
// GetComments returns comments according issueNumber
|
// GetComments returns comments according issueNumber
|
||||||
func (g *GithubDownloaderV3) GetComments(issueNumber int64) ([]*base.Comment, error) {
|
func (g *GithubDownloaderV3) GetComments(issueNumber int64) ([]*base.Comment, error) {
|
||||||
var allComments = make([]*base.Comment, 0, 100)
|
var (
|
||||||
|
allComments = make([]*base.Comment, 0, 100)
|
||||||
|
created = "created"
|
||||||
|
asc = "asc"
|
||||||
|
)
|
||||||
opt := &github.IssueListCommentsOptions{
|
opt := &github.IssueListCommentsOptions{
|
||||||
Sort: "created",
|
Sort: created,
|
||||||
Direction: "asc",
|
Direction: asc,
|
||||||
ListOptions: github.ListOptions{
|
ListOptions: github.ListOptions{
|
||||||
PerPage: 100,
|
PerPage: 100,
|
||||||
},
|
},
|
||||||
|
@ -614,3 +618,107 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
|
||||||
|
|
||||||
return allPRs, nil
|
return allPRs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertGithubReview(r *github.PullRequestReview) *base.Review {
|
||||||
|
return &base.Review{
|
||||||
|
ID: r.GetID(),
|
||||||
|
ReviewerID: r.GetUser().GetID(),
|
||||||
|
ReviewerName: r.GetUser().GetLogin(),
|
||||||
|
CommitID: r.GetCommitID(),
|
||||||
|
Content: r.GetBody(),
|
||||||
|
CreatedAt: r.GetSubmittedAt(),
|
||||||
|
State: r.GetState(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GithubDownloaderV3) convertGithubReviewComments(cs []*github.PullRequestComment) ([]*base.ReviewComment, error) {
|
||||||
|
var rcs = make([]*base.ReviewComment, 0, len(cs))
|
||||||
|
for _, c := range cs {
|
||||||
|
// get reactions
|
||||||
|
var reactions []*base.Reaction
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
g.sleep()
|
||||||
|
res, resp, err := g.client.Reactions.ListPullRequestCommentReactions(g.ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListOptions{
|
||||||
|
Page: i,
|
||||||
|
PerPage: 100,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
g.rate = &resp.Rate
|
||||||
|
if len(res) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, reaction := range res {
|
||||||
|
reactions = append(reactions, &base.Reaction{
|
||||||
|
UserID: reaction.User.GetID(),
|
||||||
|
UserName: reaction.User.GetLogin(),
|
||||||
|
Content: reaction.GetContent(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rcs = append(rcs, &base.ReviewComment{
|
||||||
|
ID: c.GetID(),
|
||||||
|
InReplyTo: c.GetInReplyTo(),
|
||||||
|
Content: c.GetBody(),
|
||||||
|
TreePath: c.GetPath(),
|
||||||
|
DiffHunk: c.GetDiffHunk(),
|
||||||
|
Position: c.GetPosition(),
|
||||||
|
CommitID: c.GetCommitID(),
|
||||||
|
PosterID: c.GetUser().GetID(),
|
||||||
|
Reactions: reactions,
|
||||||
|
CreatedAt: c.GetCreatedAt(),
|
||||||
|
UpdatedAt: c.GetUpdatedAt(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return rcs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReviews returns pull requests review
|
||||||
|
func (g *GithubDownloaderV3) GetReviews(pullRequestNumber int64) ([]*base.Review, error) {
|
||||||
|
var allReviews = make([]*base.Review, 0, 100)
|
||||||
|
opt := &github.ListOptions{
|
||||||
|
PerPage: 100,
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
g.sleep()
|
||||||
|
reviews, resp, err := g.client.PullRequests.ListReviews(g.ctx, g.repoOwner, g.repoName, int(pullRequestNumber), opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error while listing repos: %v", err)
|
||||||
|
}
|
||||||
|
g.rate = &resp.Rate
|
||||||
|
for _, review := range reviews {
|
||||||
|
r := convertGithubReview(review)
|
||||||
|
r.IssueIndex = pullRequestNumber
|
||||||
|
// retrieve all review comments
|
||||||
|
opt2 := &github.ListOptions{
|
||||||
|
PerPage: 100,
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
g.sleep()
|
||||||
|
reviewComments, resp, err := g.client.PullRequests.ListReviewComments(g.ctx, g.repoOwner, g.repoName, int(pullRequestNumber), review.GetID(), opt2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error while listing repos: %v", err)
|
||||||
|
}
|
||||||
|
g.rate = &resp.Rate
|
||||||
|
|
||||||
|
cs, err := g.convertGithubReviewComments(reviewComments)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.Comments = append(r.Comments, cs...)
|
||||||
|
if resp.NextPage == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
opt2.Page = resp.NextPage
|
||||||
|
}
|
||||||
|
allReviews = append(allReviews, r)
|
||||||
|
}
|
||||||
|
if resp.NextPage == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
opt.Page = resp.NextPage
|
||||||
|
}
|
||||||
|
return allReviews, nil
|
||||||
|
}
|
||||||
|
|
|
@ -361,4 +361,95 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, prs)
|
}, prs)
|
||||||
|
|
||||||
|
reviews, err := downloader.GetReviews(3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, []*base.Review{
|
||||||
|
{
|
||||||
|
ID: 315859956,
|
||||||
|
IssueIndex: 3,
|
||||||
|
ReviewerID: 42128690,
|
||||||
|
ReviewerName: "jolheiser",
|
||||||
|
CommitID: "076160cf0b039f13e5eff19619932d181269414b",
|
||||||
|
CreatedAt: time.Date(2019, 11, 12, 21, 35, 24, 0, time.UTC),
|
||||||
|
State: base.ReviewStateApproved,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 315860062,
|
||||||
|
IssueIndex: 3,
|
||||||
|
ReviewerID: 1824502,
|
||||||
|
ReviewerName: "zeripath",
|
||||||
|
CommitID: "076160cf0b039f13e5eff19619932d181269414b",
|
||||||
|
CreatedAt: time.Date(2019, 11, 12, 21, 35, 36, 0, time.UTC),
|
||||||
|
State: base.ReviewStateApproved,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 315861440,
|
||||||
|
IssueIndex: 3,
|
||||||
|
ReviewerID: 165205,
|
||||||
|
ReviewerName: "lafriks",
|
||||||
|
CommitID: "076160cf0b039f13e5eff19619932d181269414b",
|
||||||
|
CreatedAt: time.Date(2019, 11, 12, 21, 38, 00, 0, time.UTC),
|
||||||
|
State: base.ReviewStateApproved,
|
||||||
|
},
|
||||||
|
}, reviews)
|
||||||
|
|
||||||
|
reviews, err = downloader.GetReviews(4)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, []*base.Review{
|
||||||
|
{
|
||||||
|
ID: 338338740,
|
||||||
|
IssueIndex: 4,
|
||||||
|
ReviewerID: 81045,
|
||||||
|
ReviewerName: "lunny",
|
||||||
|
CommitID: "2be9101c543658591222acbee3eb799edfc3853d",
|
||||||
|
CreatedAt: time.Date(2020, 01, 04, 05, 33, 18, 0, time.UTC),
|
||||||
|
State: base.ReviewStateApproved,
|
||||||
|
Comments: []*base.ReviewComment{
|
||||||
|
{
|
||||||
|
ID: 363017488,
|
||||||
|
Content: "This is a good pull request.",
|
||||||
|
TreePath: "README.md",
|
||||||
|
DiffHunk: "@@ -1,2 +1,4 @@\n # test_repo\n Test repository for testing migration from github to gitea\n+",
|
||||||
|
Position: 3,
|
||||||
|
CommitID: "2be9101c543658591222acbee3eb799edfc3853d",
|
||||||
|
PosterID: 81045,
|
||||||
|
CreatedAt: time.Date(2020, 01, 04, 05, 33, 06, 0, time.UTC),
|
||||||
|
UpdatedAt: time.Date(2020, 01, 04, 05, 33, 18, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 338339651,
|
||||||
|
IssueIndex: 4,
|
||||||
|
ReviewerID: 81045,
|
||||||
|
ReviewerName: "lunny",
|
||||||
|
CommitID: "2be9101c543658591222acbee3eb799edfc3853d",
|
||||||
|
CreatedAt: time.Date(2020, 01, 04, 06, 07, 06, 0, time.UTC),
|
||||||
|
State: base.ReviewStateChangesRequested,
|
||||||
|
Content: "Don't add more reviews",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 338349019,
|
||||||
|
IssueIndex: 4,
|
||||||
|
ReviewerID: 81045,
|
||||||
|
ReviewerName: "lunny",
|
||||||
|
CommitID: "2be9101c543658591222acbee3eb799edfc3853d",
|
||||||
|
CreatedAt: time.Date(2020, 01, 04, 11, 21, 41, 0, time.UTC),
|
||||||
|
State: base.ReviewStateCommented,
|
||||||
|
Comments: []*base.ReviewComment{
|
||||||
|
{
|
||||||
|
ID: 363029944,
|
||||||
|
Content: "test a single comment.",
|
||||||
|
TreePath: "LICENSE",
|
||||||
|
DiffHunk: "@@ -19,3 +19,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n+",
|
||||||
|
Position: 4,
|
||||||
|
CommitID: "2be9101c543658591222acbee3eb799edfc3853d",
|
||||||
|
PosterID: 81045,
|
||||||
|
CreatedAt: time.Date(2020, 01, 04, 11, 21, 41, 0, time.UTC),
|
||||||
|
UpdatedAt: time.Date(2020, 01, 04, 11, 21, 41, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, reviews)
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,10 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var commentBatchSize = uploader.MaxBatchInsertSize("comment")
|
var (
|
||||||
|
commentBatchSize = uploader.MaxBatchInsertSize("comment")
|
||||||
|
reviewBatchSize = uploader.MaxBatchInsertSize("review")
|
||||||
|
)
|
||||||
|
|
||||||
if opts.Issues {
|
if opts.Issues {
|
||||||
log.Trace("migrating issues and comments")
|
log.Trace("migrating issues and comments")
|
||||||
|
@ -248,6 +251,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// plain comments
|
||||||
var allComments = make([]*base.Comment, 0, commentBatchSize)
|
var allComments = make([]*base.Comment, 0, commentBatchSize)
|
||||||
for _, pr := range prs {
|
for _, pr := range prs {
|
||||||
comments, err := downloader.GetComments(pr.Number)
|
comments, err := downloader.GetComments(pr.Number)
|
||||||
|
@ -270,6 +274,29 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// migrate reviews
|
||||||
|
var allReviews = make([]*base.Review, 0, reviewBatchSize)
|
||||||
|
for _, pr := range prs {
|
||||||
|
reviews, err := downloader.GetReviews(pr.Number)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
allReviews = append(allReviews, reviews...)
|
||||||
|
|
||||||
|
if len(allReviews) >= reviewBatchSize {
|
||||||
|
if err := uploader.CreateReviews(allReviews[:reviewBatchSize]...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
allReviews = allReviews[reviewBatchSize:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(allReviews) > 0 {
|
||||||
|
if err := uploader.CreateReviews(allReviews...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(prs) < prBatchSize {
|
if len(prs) < prBatchSize {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,14 +149,9 @@ func (d *DiffLine) GetExpandDirection() DiffLineExpandDirection {
|
||||||
return DiffLineExpandSingle
|
return DiffLineExpandSingle
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDiffLineSectionInfo(curFile *DiffFile, line string, lastLeftIdx, lastRightIdx int) *DiffLineSectionInfo {
|
// ParseDiffHunkString parse the diffhunk content and return
|
||||||
var (
|
func ParseDiffHunkString(diffhunk string) (leftLine, leftHunk, rightLine, righHunk int) {
|
||||||
leftLine int
|
ss := strings.Split(diffhunk, "@@")
|
||||||
leftHunk int
|
|
||||||
rightLine int
|
|
||||||
righHunk int
|
|
||||||
)
|
|
||||||
ss := strings.Split(line, "@@")
|
|
||||||
ranges := strings.Split(ss[1][1:], " ")
|
ranges := strings.Split(ss[1][1:], " ")
|
||||||
leftRange := strings.Split(ranges[0], ",")
|
leftRange := strings.Split(ranges[0], ",")
|
||||||
leftLine, _ = com.StrTo(leftRange[0][1:]).Int()
|
leftLine, _ = com.StrTo(leftRange[0][1:]).Int()
|
||||||
|
@ -170,12 +165,18 @@ func getDiffLineSectionInfo(curFile *DiffFile, line string, lastLeftIdx, lastRig
|
||||||
righHunk, _ = com.StrTo(rightRange[1]).Int()
|
righHunk, _ = com.StrTo(rightRange[1]).Int()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warn("Parse line number failed: %v", line)
|
log.Warn("Parse line number failed: %v", diffhunk)
|
||||||
rightLine = leftLine
|
rightLine = leftLine
|
||||||
righHunk = leftHunk
|
righHunk = leftHunk
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDiffLineSectionInfo(treePath, line string, lastLeftIdx, lastRightIdx int) *DiffLineSectionInfo {
|
||||||
|
leftLine, leftHunk, rightLine, righHunk := ParseDiffHunkString(line)
|
||||||
|
|
||||||
return &DiffLineSectionInfo{
|
return &DiffLineSectionInfo{
|
||||||
Path: curFile.Name,
|
Path: treePath,
|
||||||
LastLeftIdx: lastLeftIdx,
|
LastLeftIdx: lastLeftIdx,
|
||||||
LastRightIdx: lastRightIdx,
|
LastRightIdx: lastRightIdx,
|
||||||
LeftIdx: leftLine,
|
LeftIdx: leftLine,
|
||||||
|
@ -651,7 +652,7 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
|
||||||
case line[0] == '@':
|
case line[0] == '@':
|
||||||
curSection = &DiffSection{}
|
curSection = &DiffSection{}
|
||||||
curFile.Sections = append(curFile.Sections, curSection)
|
curFile.Sections = append(curFile.Sections, curSection)
|
||||||
lineSectionInfo := getDiffLineSectionInfo(curFile, line, leftLine-1, rightLine-1)
|
lineSectionInfo := getDiffLineSectionInfo(curFile.Name, line, leftLine-1, rightLine-1)
|
||||||
diffLine := &DiffLine{
|
diffLine := &DiffLine{
|
||||||
Type: DiffLineSection,
|
Type: DiffLineSection,
|
||||||
Content: line,
|
Content: line,
|
||||||
|
|
|
@ -209,3 +209,11 @@ func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseDiffHunkString(t *testing.T) {
|
||||||
|
leftLine, leftHunk, rightLine, rightHunk := ParseDiffHunkString("@@ -19,3 +19,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER")
|
||||||
|
assert.EqualValues(t, 19, leftLine)
|
||||||
|
assert.EqualValues(t, 3, leftHunk)
|
||||||
|
assert.EqualValues(t, 19, rightLine)
|
||||||
|
assert.EqualValues(t, 5, rightHunk)
|
||||||
|
}
|
||||||
|
|
|
@ -2,12 +2,22 @@
|
||||||
|
|
||||||
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.root.Lang }}
|
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.root.Lang }}
|
||||||
<div class="comment" id="{{.HashTag}}">
|
<div class="comment" id="{{.HashTag}}">
|
||||||
<a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
|
{{if .OriginalAuthor }}
|
||||||
<img src="{{.Poster.RelAvatarLink}}">
|
<span class="avatar"><img src="/img/avatar_default.png"></span>
|
||||||
</a>
|
{{else}}
|
||||||
|
<a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
|
||||||
|
<img src="{{.Poster.RelAvatarLink}}">
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="ui top attached header">
|
<div class="ui top attached header">
|
||||||
<span class="text grey"><a {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>{{.Poster.GetDisplayName}}</a> {{$.root.i18n.Tr "repo.issues.commented_at" .HashTag $createdStr | Safe}}</span>
|
<span class="text grey">
|
||||||
|
{{if .OriginalAuthor }}
|
||||||
|
<span class="text black"><i class="fa {{MigrationIcon $.root.Repository.GetOriginalURLHostname}}" aria-hidden="true"></i> {{ .OriginalAuthor }}</span><span class="text grey"> {{$.root.i18n.Tr "repo.issues.commented_at" .HashTag $createdStr | Safe}}</span> <span class="text migrate">{{if $.root.Repository.OriginalURL}} ({{$.root.i18n.Tr "repo.migrated_from" $.root.Repository.OriginalURL $.root.Repository.GetOriginalURLHostname | Safe }}){{end}}</span>
|
||||||
|
{{else}}
|
||||||
|
<a {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>{{.Poster.GetDisplayName}}</a> {{$.root.i18n.Tr "repo.issues.commented_at" .HashTag $createdStr | Safe}}
|
||||||
|
{{end}}
|
||||||
|
</span>
|
||||||
<div class="ui right actions">
|
<div class="ui right actions">
|
||||||
{{if and .Review}}
|
{{if and .Review}}
|
||||||
{{if eq .Review.Type 0}}
|
{{if eq .Review.Type 0}}
|
||||||
|
|
|
@ -319,10 +319,19 @@
|
||||||
{{else if eq .Type 22}}
|
{{else if eq .Type 22}}
|
||||||
<div class="event" id="{{.HashTag}}">
|
<div class="event" id="{{.HashTag}}">
|
||||||
<span class="octicon octicon-{{.Review.Type.Icon}} issue-symbol"></span>
|
<span class="octicon octicon-{{.Review.Type.Icon}} issue-symbol"></span>
|
||||||
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
|
{{if .OriginalAuthor }}
|
||||||
<img src="{{.Poster.RelAvatarLink}}">
|
{{else}}
|
||||||
</a>
|
<a class="avatar"{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}>
|
||||||
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
|
<img src="{{.Poster.RelAvatarLink}}">
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
<span class="text grey">
|
||||||
|
{{if .OriginalAuthor }}
|
||||||
|
<span class="text black"><i class="fa {{MigrationIcon $.Repository.GetOriginalURLHostname}}" aria-hidden="true"></i> {{ .OriginalAuthor }}</span><span class="text grey"> {{if $.Repository.OriginalURL}}</span><span class="text migrate">({{$.i18n.Tr "repo.migrated_from" $.Repository.OriginalURL $.Repository.GetOriginalURLHostname | Safe }}){{end}}</span>
|
||||||
|
{{else}}
|
||||||
|
<a{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}>{{.Poster.GetDisplayName}}</a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{if eq .Review.Type 1}}
|
{{if eq .Review.Type 1}}
|
||||||
{{$.i18n.Tr "repo.issues.review.approve" $createdStr | Safe}}
|
{{$.i18n.Tr "repo.issues.review.approve" $createdStr | Safe}}
|
||||||
{{else if eq .Review.Type 2}}
|
{{else if eq .Review.Type 2}}
|
||||||
|
|
Loading…
Reference in New Issue