Skip to content

Commit 2263c5a

Browse files
committed
Migrate reviews when migrating repository from github
1 parent 8ae3e2d commit 2263c5a

File tree

9 files changed

+319
-9
lines changed

9 files changed

+319
-9
lines changed

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ var migrations = []Migration{
304304
NewMigration("Add original informations for reactions", addReactionOriginals),
305305
// v124 -> v125
306306
NewMigration("Add columns to user and repository", addUserRepoMissingColumns),
307+
// v125 -> v126
308+
NewMigration("Add some columns on review for migration", addReviewMigrateInfo),
307309
}
308310

309311
// Migrate database to current version

models/migrations/v125.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"fmt"
9+
10+
"xorm.io/xorm"
11+
)
12+
13+
func addReviewMigrateInfo(x *xorm.Engine) error {
14+
type Review struct {
15+
OriginalAuthor string
16+
OriginalAuthorID int64
17+
}
18+
19+
if err := x.Sync2(new(Review)); err != nil {
20+
return fmt.Errorf("Sync2: %v", err)
21+
}
22+
return nil
23+
}

models/review.go

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@ func (rt ReviewType) Icon() string {
4545

4646
// Review represents collection of code comments giving feedback for a PR
4747
type Review struct {
48-
ID int64 `xorm:"pk autoincr"`
49-
Type ReviewType
50-
Reviewer *User `xorm:"-"`
51-
ReviewerID int64 `xorm:"index"`
52-
Issue *Issue `xorm:"-"`
53-
IssueID int64 `xorm:"index"`
54-
Content string `xorm:"TEXT"`
48+
ID int64 `xorm:"pk autoincr"`
49+
Type ReviewType
50+
Reviewer *User `xorm:"-"`
51+
ReviewerID int64 `xorm:"index"`
52+
OriginalAuthor string
53+
OriginalAuthorID int64
54+
Issue *Issue `xorm:"-"`
55+
IssueID int64 `xorm:"index"`
56+
Content string `xorm:"TEXT"`
5557
// Official is a review made by an assigned approver (counts towards approval)
5658
Official bool `xorm:"NOT NULL DEFAULT false"`
5759
CommitID string `xorm:"VARCHAR(40)"`
@@ -62,6 +64,8 @@ type Review struct {
6264

6365
// CodeComments are the initial code comments of the review
6466
CodeComments CodeComments `xorm:"-"`
67+
68+
Comments []*Comment `xorm:"-"`
6569
}
6670

6771
func (r *Review) loadCodeComments(e Engine) (err error) {
@@ -398,3 +402,36 @@ func MarkReviewsAsNotStale(issueID int64, commitID string) (err error) {
398402

399403
return
400404
}
405+
406+
// InsertReviews inserts review and review comments
407+
func InsertReviews(reviews []*Review) error {
408+
sess := x.NewSession()
409+
defer sess.Close()
410+
411+
if err := sess.Begin(); err != nil {
412+
return err
413+
}
414+
415+
for _, review := range reviews {
416+
if _, err := sess.NoAutoTime().Insert(review); err != nil {
417+
return err
418+
}
419+
420+
for _, c := range review.Comments {
421+
c.ReviewID = review.ID
422+
}
423+
}
424+
425+
if err := sess.Commit(); err != nil {
426+
return err
427+
}
428+
sess.Close()
429+
430+
for _, review := range reviews {
431+
if _, err := x.NoAutoTime().Insert(review.Comments); err != nil {
432+
return err
433+
}
434+
}
435+
436+
return nil
437+
}

modules/migrations/base/downloader.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type Downloader interface {
2323
GetIssues(page, perPage int) ([]*Issue, bool, error)
2424
GetComments(issueNumber int64) ([]*Comment, error)
2525
GetPullRequests(page, perPage int) ([]*PullRequest, error)
26+
GetReviews(pullRequestNumber int64) ([]*Review, error)
2627
}
2728

2829
// DownloaderFactory defines an interface to match a downloader implementation and create a downloader

modules/migrations/base/review.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package base
6+
7+
import "time"
8+
9+
// Review is a standard review information
10+
type Review struct {
11+
ID int64
12+
IssueIndex int64
13+
ReviewerID int64
14+
ReviewerName string
15+
Official bool
16+
CommitID string
17+
Content string
18+
CreatedAt time.Time
19+
State string // PENDING, APPROVE, REQUEST_CHANGES, or COMMENT
20+
Comments []*ReviewComment
21+
}
22+
23+
// ReviewComment represents a review comment
24+
type ReviewComment struct {
25+
ID int64
26+
InReplyTo int64
27+
Content string
28+
TreePath string
29+
Position int
30+
CommitID string
31+
PosterID int64
32+
Reactions *Reactions
33+
CreatedAt time.Time
34+
UpdatedAt time.Time
35+
}

modules/migrations/base/uploader.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type Uploader interface {
1717
CreateIssues(issues ...*Issue) error
1818
CreateComments(comments ...*Comment) error
1919
CreatePullRequests(prs ...*PullRequest) error
20+
CreateReviews(reviews ...*Review) error
2021
Rollback() error
2122
Close()
2223
}

modules/migrations/gitea.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package migrations
77

88
import (
9+
"bytes"
910
"context"
1011
"fmt"
1112
"io"
@@ -27,6 +28,7 @@ import (
2728
"code.gitea.io/gitea/modules/setting"
2829
"code.gitea.io/gitea/modules/structs"
2930
"code.gitea.io/gitea/modules/timeutil"
31+
"code.gitea.io/gitea/services/gitdiff"
3032

3133
gouuid "github.com/satori/go.uuid"
3234
)
@@ -706,6 +708,115 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
706708
return &pullRequest, nil
707709
}
708710

711+
func convertReviewState(state string) models.ReviewType {
712+
switch state {
713+
case "PENDING":
714+
return models.ReviewTypePending
715+
case "APPROVE":
716+
return models.ReviewTypeApprove
717+
case "REQUEST_CHANGES":
718+
return models.ReviewTypeReject
719+
case "COMMENT":
720+
return models.ReviewTypeComment
721+
default:
722+
return models.ReviewTypePending
723+
}
724+
}
725+
726+
// CreateReviews create pull request reviews
727+
func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
728+
var cms = make([]*models.Review, 0, len(reviews))
729+
for _, review := range reviews {
730+
var issueID int64
731+
if issueIDStr, ok := g.issues.Load(review.IssueIndex); !ok {
732+
issue, err := models.GetIssueByIndex(g.repo.ID, review.IssueIndex)
733+
if err != nil {
734+
return err
735+
}
736+
issueID = issue.ID
737+
g.issues.Store(review.IssueIndex, issueID)
738+
} else {
739+
issueID = issueIDStr.(int64)
740+
}
741+
742+
userid, ok := g.userMap[review.ReviewerID]
743+
tp := g.gitServiceType.Name()
744+
if !ok && tp != "" {
745+
var err error
746+
userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", review.ReviewerID))
747+
if err != nil {
748+
log.Error("GetUserIDByExternalUserID: %v", err)
749+
}
750+
if userid > 0 {
751+
g.userMap[review.ReviewerID] = userid
752+
}
753+
}
754+
755+
var cm = models.Review{
756+
Type: convertReviewState(review.State),
757+
IssueID: issueID,
758+
Content: review.Content,
759+
Official: review.Official,
760+
CreatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
761+
UpdatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
762+
}
763+
764+
if userid > 0 {
765+
cm.ReviewerID = userid
766+
} else {
767+
cm.ReviewerID = g.doer.ID
768+
cm.OriginalAuthor = review.ReviewerName
769+
cm.OriginalAuthorID = review.ReviewerID
770+
}
771+
772+
// TODO: cache pr
773+
pr, err := models.GetPullRequestByID(issueID)
774+
if err != nil {
775+
return err
776+
}
777+
778+
for _, comment := range review.Comments {
779+
headCommitID, err := g.gitRepo.GetRefCommitID(pr.GetGitRefName())
780+
if err != nil {
781+
return fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err)
782+
}
783+
patchBuf := new(bytes.Buffer)
784+
if err := gitdiff.GetRawDiffForFile(g.gitRepo.Path, pr.MergeBase, headCommitID, gitdiff.RawDiffNormal, comment.TreePath, patchBuf); err != nil {
785+
return fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", err, g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath)
786+
}
787+
line := int64(comment.Position)
788+
patch := gitdiff.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
789+
790+
var c = models.Comment{
791+
Type: models.CommentTypeCode,
792+
PosterID: comment.PosterID,
793+
IssueID: issueID,
794+
Content: comment.Content,
795+
Line: line,
796+
TreePath: comment.TreePath,
797+
CommitSHA: comment.CommitID,
798+
Patch: patch,
799+
}
800+
801+
if userid > 0 {
802+
c.PosterID = userid
803+
} else {
804+
c.PosterID = g.doer.ID
805+
c.OriginalAuthor = review.ReviewerName
806+
c.OriginalAuthorID = review.ReviewerID
807+
}
808+
809+
cm.Comments = append(cm.Comments, &c)
810+
}
811+
812+
cms = append(cms, &cm)
813+
814+
// TODO: Reactions
815+
}
816+
817+
return models.InsertReviews(cms)
818+
}
819+
709820
// Rollback when migrating failed, this will rollback all the changes.
710821
func (g *GiteaLocalUploader) Rollback() error {
711822
if g.repo != nil && g.repo.ID > 0 {

modules/migrations/github.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,3 +614,77 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
614614

615615
return allPRs, nil
616616
}
617+
618+
func convertGithubReview(r *github.PullRequestReview) *base.Review {
619+
return &base.Review{
620+
ID: r.GetID(),
621+
ReviewerID: r.GetUser().GetID(),
622+
ReviewerName: r.GetUser().GetLogin(),
623+
CommitID: r.GetCommitID(),
624+
Content: r.GetBody(),
625+
CreatedAt: r.GetSubmittedAt(),
626+
State: r.GetState(),
627+
}
628+
}
629+
630+
func convertGithubReviewComments(cs []*github.PullRequestComment) []*base.ReviewComment {
631+
var rcs = make([]*base.ReviewComment, 0, len(cs))
632+
for _, c := range cs {
633+
rcs = append(rcs, &base.ReviewComment{
634+
ID: c.GetID(),
635+
InReplyTo: c.GetInReplyTo(),
636+
Content: c.GetBody(),
637+
TreePath: c.GetPath(),
638+
Position: c.GetPosition(),
639+
CommitID: c.GetCommitID(),
640+
PosterID: c.GetUser().GetID(),
641+
Reactions: convertGithubReactions(c.Reactions),
642+
CreatedAt: c.GetCreatedAt(),
643+
UpdatedAt: c.GetUpdatedAt(),
644+
})
645+
}
646+
return rcs
647+
}
648+
649+
// GetReviews returns pull requests review
650+
func (g *GithubDownloaderV3) GetReviews(pullRequestNumber int64) ([]*base.Review, error) {
651+
var allReviews = make([]*base.Review, 0, 100)
652+
opt := &github.ListOptions{
653+
PerPage: 100,
654+
}
655+
for {
656+
g.sleep()
657+
reviews, resp, err := g.client.PullRequests.ListReviews(g.ctx, g.repoOwner, g.repoName, int(pullRequestNumber), opt)
658+
if err != nil {
659+
return nil, fmt.Errorf("error while listing repos: %v", err)
660+
}
661+
g.rate = &resp.Rate
662+
for _, review := range reviews {
663+
r := convertGithubReview(review)
664+
r.IssueIndex = pullRequestNumber
665+
// retrieve all review comments
666+
opt2 := &github.ListOptions{
667+
PerPage: 100,
668+
}
669+
for {
670+
g.sleep()
671+
reviewComments, resp, err := g.client.PullRequests.ListReviewComments(g.ctx, g.repoOwner, g.repoName, int(pullRequestNumber), review.GetID(), opt2)
672+
if err != nil {
673+
return nil, fmt.Errorf("error while listing repos: %v", err)
674+
}
675+
g.rate = &resp.Rate
676+
r.Comments = append(r.Comments, convertGithubReviewComments(reviewComments)...)
677+
if resp.NextPage == 0 {
678+
break
679+
}
680+
opt2.Page = resp.NextPage
681+
}
682+
allReviews = append(allReviews, r)
683+
}
684+
if resp.NextPage == 0 {
685+
break
686+
}
687+
opt.Page = resp.NextPage
688+
}
689+
return allReviews, nil
690+
}

0 commit comments

Comments
 (0)