diff --git a/cmd/doctor.go b/cmd/doctor.go index 2ca2bb5e70b6a..9d1b07876b8e0 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -155,9 +155,31 @@ var checklist = []check{ isDefault: false, f: runDoctorEnablePushOptions, }, + { + title: "Recalculate Code-comment-replies commitID", + name: "recalculate_code_comment_replies", + isDefault: true, + f: runDoctorUpdateCodeCommentReplies, + }, // more checks please append here } +func runDoctorUpdateCodeCommentReplies(ctx *cli.Context) ([]string, error) { + if ctx.Bool("fix") { + _, result, err := models.CountOrFixUpdatableCodeCommentReplies(true) + if err != nil { + return nil, err + } + return result, nil + } + count, _, err := models.CountOrFixUpdatableCodeCommentReplies(false) + if err != nil { + return nil, err + } + result := fmt.Sprintf("%d code comment replies without commitID exist", count) + return []string{result}, nil +} + func runRecreateTable(ctx *cli.Context) error { // Redirect the default golog to here golog.SetFlags(0) diff --git a/models/consistency.go b/models/consistency.go index fbb99ca80c883..4a1d2b02a613c 100644 --- a/models/consistency.go +++ b/models/consistency.go @@ -5,10 +5,14 @@ package models import ( + "fmt" "reflect" + "strconv" "strings" "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" "xorm.io/builder" ) @@ -295,3 +299,92 @@ func FixNullArchivedRepository() (int64, error) { IsArchived: false, }) } + +// CountOrFixUpdatableCodeCommentReplies count or fix CodeCommentReplies missing the CommitSHA +func CountOrFixUpdatableCodeCommentReplies(fix bool) (int64, []string, error) { + + var ( + start = 0 + batchSize = 100 + sqlCmd string + count int64 + result []string + ) + + sqlSelect := `SELECT comment.id as id, first.commit_sha as commit_sha, first.patch as patch, first.invalidated as invalidated` + sqlTail := ` FROM comment INNER JOIN ( + SELECT C.id, C.review_id, C.line, C.tree_path, C.patch, C.commit_sha, C.invalidated + FROM comment AS C + WHERE C.type = 21 + AND C.created_unix = + (SELECT MIN(comment.created_unix) + FROM comment + WHERE comment.review_id = C.review_id + AND comment.type = 21 + AND comment.line = C.line + AND comment.tree_path = C.tree_path) + ) AS first + ON comment.review_id = first.review_id + AND comment.tree_path = first.tree_path AND comment.line = first.line + WHERE comment.type = 21 + AND comment.id != first.id + AND comment.commit_sha != first.commit_sha` + + sess := x.NewSession() + defer sess.Close() + for { + if err := sess.Begin(); err != nil { + return 0, nil, err + } + + if setting.Database.UseMSSQL { + if _, err := sess.Exec(sqlSelect + " INTO #temp_comments" + sqlTail); err != nil { + return 0, nil, fmt.Errorf("unable to create temporary table") + } + } + + var comments = make([]*Comment, 0, batchSize) + + switch { + case setting.Database.UseMySQL: + sqlCmd = sqlSelect + sqlTail + " LIMIT " + strconv.Itoa(batchSize) + ", " + strconv.Itoa(start) + case setting.Database.UsePostgreSQL: + fallthrough + case setting.Database.UseSQLite3: + sqlCmd = sqlSelect + sqlTail + " LIMIT " + strconv.Itoa(batchSize) + " OFFSET " + strconv.Itoa(start) + case setting.Database.UseMSSQL: + sqlCmd = "SELECT TOP " + strconv.Itoa(batchSize) + " * FROM #temp_comments WHERE " + + "(id NOT IN ( SELECT TOP " + strconv.Itoa(start) + " id FROM #temp_comments ORDER BY id )) ORDER BY id" + default: + return 0, nil, fmt.Errorf("Unsupported database type") + } + + if err := sess.SQL(sqlCmd).Find(&comments); err != nil { + return 0, nil, fmt.Errorf("failed to select: %v", err) + } + + if fix { + for _, comment := range comments { + if _, err := sess.Table("comment").ID(comment.ID).Cols("commit_sha", "patch", "invalidated").Update(comment); err != nil { + return 0, nil, fmt.Errorf("failed to update comment[%d]: %v %v", comment.ID, comment, err) + } + result = append(result, fmt.Sprintf("update comment[%d]: %s\n", comment.ID, comment.CommitSHA)) + } + } + + count += int64(len(comments)) + start += len(comments) + + if fix { + if err := sess.Commit(); err != nil { + return count, result, err + } + } + + if len(comments) < batchSize { + break + } + } + + return count, result, nil +}