mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-08 22:13:26 +09:00
refactor: prepare to decouple the "model migration" package and "models" package (#38533)
Migrations should never use model structs directly, because the model structs can be different in different releases. e.g. if one migration uses "User" model, it works in the early releases, then one day, when the User model changes, the migration breaks because it will use the new (incorrect) User model, it should only use the old User model. The same to "modules/structs". --------- Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: delvh <dev.lh@web.de>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
migrationtest.MainTest(m)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddChangedProtectedFilesPullRequestColumn(x base.EngineMigration) error {
|
||||
type PullRequest struct {
|
||||
ChangedProtectedFiles []string `xorm:"TEXT JSON"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(PullRequest)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/log"
|
||||
)
|
||||
|
||||
func FixPublisherIDforTagReleases(ctx context.Context, x base.EngineMigration) error {
|
||||
type Release struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
Sha1 string
|
||||
TagName string
|
||||
PublisherID int64
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
ID int64
|
||||
OwnerID int64
|
||||
OwnerName string
|
||||
Name string
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
Name string
|
||||
Email string
|
||||
}
|
||||
|
||||
const batchSize = 100
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
var (
|
||||
repo *Repository
|
||||
gitRepo *git.Repository
|
||||
user *User
|
||||
)
|
||||
defer func() {
|
||||
if gitRepo != nil {
|
||||
gitRepo.Close()
|
||||
}
|
||||
}()
|
||||
for start := 0; ; start += batchSize {
|
||||
releases := make([]*Release, 0, batchSize)
|
||||
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sess.Limit(batchSize, start).
|
||||
Where("publisher_id = 0 OR publisher_id is null").
|
||||
Asc("repo_id", "id").Where("is_tag=?", true).
|
||||
Find(&releases); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(releases) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, release := range releases {
|
||||
if repo == nil || repo.ID != release.RepoID {
|
||||
if gitRepo != nil {
|
||||
gitRepo.Close()
|
||||
gitRepo = nil
|
||||
}
|
||||
repo = new(Repository)
|
||||
has, err := sess.ID(release.RepoID).Get(repo)
|
||||
if err != nil {
|
||||
log.Error("Error whilst loading repository[%d] for release[%d] with tag name %s. Error: %v", release.RepoID, release.ID, release.TagName, err)
|
||||
return err
|
||||
} else if !has {
|
||||
log.Warn("Release[%d] is orphaned and refers to non-existing repository %d", release.ID, release.RepoID)
|
||||
log.Warn("This release should be deleted")
|
||||
continue
|
||||
}
|
||||
|
||||
if repo.OwnerName == "" {
|
||||
// v120.go migration may not have been run correctly - we'll just replicate it here
|
||||
// because this appears to be a common-ish problem.
|
||||
if _, err := sess.Exec("UPDATE repository SET owner_name = (SELECT name FROM `user` WHERE `user`.id = repository.owner_id)"); err != nil {
|
||||
log.Error("Error whilst updating repository[%d] owner name", repo.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sess.ID(release.RepoID).Get(repo); err != nil {
|
||||
log.Error("Error whilst loading repository[%d] for release[%d] with tag name %s. Error: %v", release.RepoID, release.ID, release.TagName, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
gitRepo, err = git.OpenRepository(repo_model.CodeRepoByName(repo.OwnerName, repo.Name))
|
||||
if err != nil {
|
||||
log.Error("Error whilst opening git repo for [%d]%s/%s. Error: %v", repo.ID, repo.OwnerName, repo.Name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
commit, err := gitRepo.GetTagCommit(ctx, release.TagName)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
log.Warn("Unable to find commit %s for Tag: %s in [%d]%s/%s. Cannot update publisher ID.", err.(git.ErrNotExist).ID, release.TagName, repo.ID, repo.OwnerName, repo.Name)
|
||||
continue
|
||||
}
|
||||
log.Error("Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v", release.TagName, repo.ID, repo.OwnerName, repo.Name, err)
|
||||
return fmt.Errorf("GetTagCommit: %w", err)
|
||||
}
|
||||
|
||||
if commit.Author.Email == "" {
|
||||
log.Warn("Tag: %s in Repo[%d]%s/%s does not have a tagger.", release.TagName, repo.ID, repo.OwnerName, repo.Name)
|
||||
commit, err = gitRepo.GetCommit(ctx, commit.ID.String())
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
log.Warn("Unable to find commit %s for Tag: %s in [%d]%s/%s. Cannot update publisher ID.", err.(git.ErrNotExist).ID, release.TagName, repo.ID, repo.OwnerName, repo.Name)
|
||||
continue
|
||||
}
|
||||
log.Error("Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v", release.TagName, repo.ID, repo.OwnerName, repo.Name, err)
|
||||
return fmt.Errorf("GetCommit: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if commit.Author.Email == "" {
|
||||
log.Warn("Tag: %s in Repo[%d]%s/%s does not have a Tagger and its underlying commit does not have an Author either!", release.TagName, repo.ID, repo.OwnerName, repo.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if user == nil || !strings.EqualFold(user.Email, commit.Author.Email) {
|
||||
user = new(User)
|
||||
_, err = sess.Where("email=?", commit.Author.Email).Get(user)
|
||||
if err != nil {
|
||||
log.Error("Error whilst getting commit author by email: %s for Tag: %s in [%d]%s/%s. Error: %v", commit.Author.Email, release.TagName, repo.ID, repo.OwnerName, repo.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
user.Email = commit.Author.Email
|
||||
}
|
||||
|
||||
if user.ID <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
release.PublisherID = user.ID
|
||||
if _, err := sess.ID(release.ID).Cols("publisher_id").Update(release); err != nil {
|
||||
log.Error("Error whilst updating publisher[%d] for release[%d] with tag name %s. Error: %v", release.PublisherID, release.ID, release.TagName, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if gitRepo != nil {
|
||||
gitRepo.Close()
|
||||
}
|
||||
|
||||
if err := sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func FixRepoTopics(x base.EngineMigration) error {
|
||||
type Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Topics []string `xorm:"TEXT JSON"`
|
||||
}
|
||||
|
||||
const batchSize = 100
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
repos := make([]*Repository, 0, batchSize)
|
||||
topics := make([]string, 0, batchSize)
|
||||
for start := 0; ; start += batchSize {
|
||||
repos = repos[:0]
|
||||
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sess.Limit(batchSize, start).Find(&repos); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(repos) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, repo := range repos {
|
||||
topics = topics[:0]
|
||||
if err := sess.Select("name").Table("topic").
|
||||
Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id").
|
||||
Where("repo_topic.repo_id = ?", repo.ID).Desc("topic.repo_count").Find(&topics); err != nil {
|
||||
return err
|
||||
}
|
||||
repo.Topics = topics
|
||||
if _, err := sess.ID(repo.ID).Cols("topics").Update(repo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
func UpdateCodeCommentReplies(x base.EngineMigration) error {
|
||||
type Comment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CommitSHA string `xorm:"VARCHAR(40)"`
|
||||
Patch string `xorm:"TEXT patch"`
|
||||
Invalidated bool
|
||||
|
||||
// Not extracted but used in the below query
|
||||
Type int `xorm:"INDEX"`
|
||||
Line int64 // - previous line / + proposed line
|
||||
TreePath string
|
||||
ReviewID int64 `xorm:"index"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Comment)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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`
|
||||
|
||||
var (
|
||||
sqlCmd string
|
||||
start = 0
|
||||
batchSize = 100
|
||||
sess = x.NewSession()
|
||||
)
|
||||
defer sess.Close()
|
||||
for {
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if setting.Database.Type.IsMSSQL() {
|
||||
if _, err := sess.Exec(sqlSelect + " INTO #temp_comments" + sqlTail); err != nil {
|
||||
log.Error("unable to create temporary table")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
comments := make([]*Comment, 0, batchSize)
|
||||
|
||||
switch {
|
||||
case setting.Database.Type.IsMySQL():
|
||||
sqlCmd = sqlSelect + sqlTail + " LIMIT " + strconv.Itoa(batchSize) + ", " + strconv.Itoa(start)
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
fallthrough
|
||||
case setting.Database.Type.IsSQLite3():
|
||||
sqlCmd = sqlSelect + sqlTail + " LIMIT " + strconv.Itoa(batchSize) + " OFFSET " + strconv.Itoa(start)
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
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 errors.New("Unsupported database type")
|
||||
}
|
||||
|
||||
if err := sess.SQL(sqlCmd).Find(&comments); err != nil {
|
||||
log.Error("failed to select: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, comment := range comments {
|
||||
if _, err := sess.Table("comment").ID(comment.ID).Cols("commit_sha", "patch", "invalidated").Update(comment); err != nil {
|
||||
log.Error("failed to update comment[%d]: %v %v", comment.ID, comment, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
start += len(comments)
|
||||
|
||||
if err := sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(comments) < batchSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/timeutil"
|
||||
)
|
||||
|
||||
func UpdateReactionConstraint(x base.EngineMigration) error {
|
||||
// Reaction represents a reactions on issues and comments.
|
||||
type Reaction struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type string `xorm:"INDEX UNIQUE(s) NOT NULL"`
|
||||
IssueID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
|
||||
CommentID int64 `xorm:"INDEX UNIQUE(s)"`
|
||||
UserID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
|
||||
OriginalAuthorID int64 `xorm:"INDEX UNIQUE(s) NOT NULL DEFAULT(0)"`
|
||||
OriginalAuthor string `xorm:"INDEX UNIQUE(s)"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := base.RecreateTable(sess, &Reaction{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddBlockOnOfficialReviewRequests(x base.EngineMigration) error {
|
||||
type ProtectedBranch struct {
|
||||
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync(new(ProtectedBranch))
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func ConvertTaskTypeToString(x base.EngineMigration) error {
|
||||
const (
|
||||
GOGS int = iota + 1
|
||||
SLACK
|
||||
GITEA
|
||||
DISCORD
|
||||
DINGTALK
|
||||
TELEGRAM
|
||||
MSTEAMS
|
||||
FEISHU
|
||||
MATRIX
|
||||
WECHATWORK
|
||||
)
|
||||
|
||||
hookTaskTypes := map[int]string{
|
||||
GITEA: "gitea",
|
||||
GOGS: "gogs",
|
||||
SLACK: "slack",
|
||||
DISCORD: "discord",
|
||||
DINGTALK: "dingtalk",
|
||||
TELEGRAM: "telegram",
|
||||
MSTEAMS: "msteams",
|
||||
FEISHU: "feishu",
|
||||
MATRIX: "matrix",
|
||||
WECHATWORK: "wechatwork",
|
||||
}
|
||||
|
||||
type HookTask struct {
|
||||
Typ string `xorm:"VARCHAR(16) index"`
|
||||
}
|
||||
if err := x.Sync(new(HookTask)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// to keep the migration could be rerun
|
||||
exist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "hook_task", "type")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, s := range hookTaskTypes {
|
||||
if _, err := x.Exec("UPDATE hook_task set typ = ? where `type`=?", s, i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := base.DropTableColumns(sess, "hook_task", "type"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func ConvertWebhookTaskTypeToString(x base.EngineMigration) error {
|
||||
const (
|
||||
GOGS int = iota + 1
|
||||
SLACK
|
||||
GITEA
|
||||
DISCORD
|
||||
DINGTALK
|
||||
TELEGRAM
|
||||
MSTEAMS
|
||||
FEISHU
|
||||
MATRIX
|
||||
WECHATWORK
|
||||
)
|
||||
|
||||
hookTaskTypes := map[int]string{
|
||||
GITEA: "gitea",
|
||||
GOGS: "gogs",
|
||||
SLACK: "slack",
|
||||
DISCORD: "discord",
|
||||
DINGTALK: "dingtalk",
|
||||
TELEGRAM: "telegram",
|
||||
MSTEAMS: "msteams",
|
||||
FEISHU: "feishu",
|
||||
MATRIX: "matrix",
|
||||
WECHATWORK: "wechatwork",
|
||||
}
|
||||
|
||||
type Webhook struct {
|
||||
Type string `xorm:"char(16) index"`
|
||||
}
|
||||
if err := x.Sync(new(Webhook)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, s := range hookTaskTypes {
|
||||
if _, err := x.Exec("UPDATE webhook set type = ? where hook_task_type=?", s, i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := base.DropTableColumns(sess, "webhook", "hook_task_type"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func ConvertTopicNameFrom25To50(x base.EngineMigration) error {
|
||||
type Topic struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Name string `xorm:"UNIQUE VARCHAR(50)"`
|
||||
RepoCount int
|
||||
CreatedUnix int64 `xorm:"INDEX created"`
|
||||
UpdatedUnix int64 `xorm:"INDEX updated"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Topic)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := base.RecreateTable(sess, new(Topic)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
// OAuth2Grant here is a snapshot of models.OAuth2Grant for this version
|
||||
// of the database, as it does not appear to have been added as a part
|
||||
// of a previous migration.
|
||||
type OAuth2Grant struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"INDEX unique(user_application)"`
|
||||
ApplicationID int64 `xorm:"INDEX unique(user_application)"`
|
||||
Counter int64 `xorm:"NOT NULL DEFAULT 1"`
|
||||
Scope string `xorm:"TEXT"`
|
||||
Nonce string `xorm:"TEXT"`
|
||||
CreatedUnix int64 `xorm:"created"`
|
||||
UpdatedUnix int64 `xorm:"updated"`
|
||||
}
|
||||
|
||||
// TableName sets the database table name to be the correct one, as the
|
||||
// autogenerated table name for this struct is "o_auth2_grant".
|
||||
func (grant *OAuth2Grant) TableName() string {
|
||||
return "oauth2_grant"
|
||||
}
|
||||
|
||||
func AddScopeAndNonceColumnsToOAuth2Grant(x base.EngineMigration) error {
|
||||
if err := x.Sync(new(OAuth2Grant)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func ConvertHookTaskTypeToVarcharAndTrim(x base.EngineMigration) error {
|
||||
dbType := x.Dialect().URI().DBType
|
||||
if dbType == schemas.SQLITE { // For SQLITE, varchar or char will always be represented as TEXT
|
||||
return nil
|
||||
}
|
||||
|
||||
// HookTask: Typ string `xorm:"VARCHAR(16) index"`
|
||||
if err := base.ModifyColumn(x, "hook_task", &schemas.Column{
|
||||
Name: "typ",
|
||||
SQLType: schemas.SQLType{
|
||||
Name: "VARCHAR",
|
||||
},
|
||||
Length: 16,
|
||||
Nullable: true, // To keep compatible as nullable
|
||||
DefaultIsEmpty: true,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var hookTaskTrimSQL string
|
||||
if dbType == schemas.MSSQL {
|
||||
hookTaskTrimSQL = "UPDATE hook_task SET typ = RTRIM(LTRIM(typ))"
|
||||
} else {
|
||||
hookTaskTrimSQL = "UPDATE hook_task SET typ = TRIM(typ)"
|
||||
}
|
||||
if _, err := x.Exec(hookTaskTrimSQL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Webhook: Type string `xorm:"VARCHAR(16) index"`
|
||||
if err := base.ModifyColumn(x, "webhook", &schemas.Column{
|
||||
Name: "type",
|
||||
SQLType: schemas.SQLType{
|
||||
Name: "VARCHAR",
|
||||
},
|
||||
Length: 16,
|
||||
Nullable: true, // To keep compatible as nullable
|
||||
DefaultIsEmpty: true,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var webhookTrimSQL string
|
||||
if dbType == schemas.MSSQL {
|
||||
webhookTrimSQL = "UPDATE webhook SET type = RTRIM(LTRIM(type))"
|
||||
} else {
|
||||
webhookTrimSQL = "UPDATE webhook SET type = TRIM(type)"
|
||||
}
|
||||
_, err := x.Exec(webhookTrimSQL)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func RecalculateUserEmptyPWD(x base.EngineMigration) (err error) {
|
||||
const (
|
||||
algoBcrypt = "bcrypt"
|
||||
algoScrypt = "scrypt"
|
||||
algoArgon2 = "argon2"
|
||||
algoPbkdf2 = "pbkdf2"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Passwd string `xorm:"NOT NULL"`
|
||||
PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"`
|
||||
MustChangePassword bool `xorm:"NOT NULL DEFAULT false"`
|
||||
LoginType int
|
||||
LoginName string
|
||||
Type int
|
||||
Salt string `xorm:"VARCHAR(10)"`
|
||||
}
|
||||
|
||||
// hashPassword hash password based on algo and salt
|
||||
// state 461406070c
|
||||
hashPassword := func(passwd, salt, algo string) string {
|
||||
var tempPasswd []byte
|
||||
|
||||
switch algo {
|
||||
case algoBcrypt:
|
||||
tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost)
|
||||
return string(tempPasswd)
|
||||
case algoScrypt:
|
||||
tempPasswd, _ = scrypt.Key([]byte(passwd), []byte(salt), 65536, 16, 2, 50)
|
||||
case algoArgon2:
|
||||
tempPasswd = argon2.IDKey([]byte(passwd), []byte(salt), 2, 65536, 8, 50)
|
||||
case algoPbkdf2:
|
||||
fallthrough
|
||||
default:
|
||||
tempPasswd = pbkdf2.Key([]byte(passwd), []byte(salt), 10000, 50, sha256.New)
|
||||
}
|
||||
|
||||
return hex.EncodeToString(tempPasswd)
|
||||
}
|
||||
|
||||
// ValidatePassword checks if given password matches the one belongs to the user.
|
||||
// state 461406070c, changed since it's not necessary to be time constant
|
||||
ValidatePassword := func(u *User, passwd string) bool {
|
||||
tempHash := hashPassword(passwd, u.Salt, u.PasswdHashAlgo)
|
||||
|
||||
if u.PasswdHashAlgo != algoBcrypt && u.Passwd == tempHash {
|
||||
return true
|
||||
}
|
||||
if u.PasswdHashAlgo == algoBcrypt && bcrypt.CompareHashAndPassword([]byte(u.Passwd), []byte(passwd)) == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
const batchSize = 100
|
||||
|
||||
for start := 0; ; start += batchSize {
|
||||
users := make([]*User, 0, batchSize)
|
||||
if err = sess.Limit(batchSize, start).Where(builder.Neq{"passwd": ""}, 0).Find(&users); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
if ValidatePassword(user, "") {
|
||||
user.Passwd = ""
|
||||
user.Salt = ""
|
||||
user.PasswdHashAlgo = ""
|
||||
if _, err = sess.ID(user.ID).Cols("passwd", "salt", "passwd_hash_algo").Update(user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// delete salt and algo where password is empty
|
||||
_, err = sess.Where(builder.Eq{"passwd": ""}.And(builder.Neq{"salt": ""}.Or(builder.Neq{"passwd_hash_algo": ""}))).
|
||||
Cols("salt", "passwd_hash_algo").Update(&User{})
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddUserRedirect(x base.EngineMigration) (err error) {
|
||||
type UserRedirect struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
RedirectUserID int64
|
||||
}
|
||||
|
||||
if err := x.Sync(new(UserRedirect)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func RecreateUserTableToFixDefaultValues(_ base.EngineMigration) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func CommentTypeDeleteBranchUseOldRef(x base.EngineMigration) error {
|
||||
_, err := x.Exec("UPDATE comment SET old_ref = commit_sha, commit_sha = '' WHERE type = 11")
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddDismissedReviewColumn(x base.EngineMigration) error {
|
||||
type Review struct {
|
||||
Dismissed bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Review)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddSortingColToProjectBoard(x base.EngineMigration) error {
|
||||
type ProjectBoard struct {
|
||||
Sorting int8 `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(ProjectBoard)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/timeutil"
|
||||
)
|
||||
|
||||
func AddSessionTable(x base.EngineMigration) error {
|
||||
type Session struct {
|
||||
Key string `xorm:"pk CHAR(16)"`
|
||||
Data []byte `xorm:"BLOB"`
|
||||
Expiry timeutil.TimeStamp
|
||||
}
|
||||
return x.Sync(new(Session))
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddTimeIDCommentColumn(x base.EngineMigration) error {
|
||||
type Comment struct {
|
||||
TimeID int64
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Comment)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddRepoTransfer(x base.EngineMigration) error {
|
||||
type RepoTransfer struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
DoerID int64
|
||||
RecipientID int64
|
||||
RepoID int64
|
||||
TeamIDs []int64
|
||||
CreatedUnix int64 `xorm:"INDEX NOT NULL created"`
|
||||
UpdatedUnix int64 `xorm:"INDEX NOT NULL updated"`
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sess.Sync(new(RepoTransfer)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
func FixPostgresIDSequences(x base.EngineMigration) error {
|
||||
if !setting.Database.Type.IsPostgreSQL() {
|
||||
return nil
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sequences []string
|
||||
schema := sess.Engine().Dialect().URI().Schema
|
||||
|
||||
sess.Engine().SetSchema("")
|
||||
if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__%_id_seq%' AND sequence_catalog = ?", setting.Database.Name).Find(&sequences); err != nil {
|
||||
log.Error("Unable to find sequences: %v", err)
|
||||
return err
|
||||
}
|
||||
sess.Engine().SetSchema(schema)
|
||||
|
||||
sequenceRegexp := regexp.MustCompile(`tmp_recreate__(\w+)_id_seq.*`)
|
||||
|
||||
for _, sequence := range sequences {
|
||||
tableName := sequenceRegexp.FindStringSubmatch(sequence)[1]
|
||||
newSequenceName := tableName + "_id_seq"
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE `%s` RENAME TO `%s`", sequence, newSequenceName)); err != nil {
|
||||
log.Error("Unable to rename %s to %s. Error: %v", sequence, newSequenceName, err)
|
||||
return err
|
||||
}
|
||||
if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM `%s`), 1), false)", newSequenceName, tableName)); err != nil {
|
||||
log.Error("Unable to reset sequence %s for %s. Error: %v", newSequenceName, tableName, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
// RemoveInvalidLabels looks through the database to look for comments and issue_labels
|
||||
// that refer to labels do not belong to the repository or organization that repository
|
||||
// that the issue is in
|
||||
func RemoveInvalidLabels(x base.EngineMigration) error {
|
||||
type Comment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type int `xorm:"INDEX"`
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
LabelID int64
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
|
||||
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64 `xorm:"UNIQUE(s) index"`
|
||||
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
type IssueLabel struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IssueID int64 `xorm:"UNIQUE(s)"`
|
||||
LabelID int64 `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Comment), new(Issue), new(Repository), new(Label), new(IssueLabel)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := x.Exec(`DELETE FROM issue_label WHERE issue_label.id IN (
|
||||
SELECT il_too.id FROM (
|
||||
SELECT il_too_too.id
|
||||
FROM issue_label AS il_too_too
|
||||
INNER JOIN label ON il_too_too.label_id = label.id
|
||||
INNER JOIN issue on issue.id = il_too_too.issue_id
|
||||
INNER JOIN repository on repository.id = issue.repo_id
|
||||
WHERE
|
||||
(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
|
||||
) AS il_too )`); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := x.Exec(`DELETE FROM comment WHERE comment.id IN (
|
||||
SELECT il_too.id FROM (
|
||||
SELECT com.id
|
||||
FROM comment AS com
|
||||
INNER JOIN label ON com.label_id = label.id
|
||||
INNER JOIN issue on issue.id = com.issue_id
|
||||
INNER JOIN repository on repository.id = issue.repo_id
|
||||
WHERE
|
||||
com.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))
|
||||
) AS il_too)`, 7); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_RemoveInvalidLabels(t *testing.T) {
|
||||
// Models used by the migration
|
||||
type Comment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type int `xorm:"INDEX"`
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
LabelID int64
|
||||
ShouldRemain bool // <- Flag for testing the migration
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
|
||||
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64 `xorm:"UNIQUE(s) index"`
|
||||
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
type IssueLabel struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IssueID int64 `xorm:"UNIQUE(s)"`
|
||||
LabelID int64 `xorm:"UNIQUE(s)"`
|
||||
ShouldRemain bool // <- Flag for testing the migration
|
||||
}
|
||||
|
||||
// load and prepare the test database
|
||||
x, deferable := migrationtest.PrepareTestEnv(t, 0, new(Comment), new(Issue), new(Repository), new(IssueLabel), new(Label))
|
||||
if x == nil || t.Failed() {
|
||||
defer deferable()
|
||||
return
|
||||
}
|
||||
defer deferable()
|
||||
|
||||
var issueLabels []*IssueLabel
|
||||
ilPreMigration := map[int64]*IssueLabel{}
|
||||
ilPostMigration := map[int64]*IssueLabel{}
|
||||
|
||||
var comments []*Comment
|
||||
comPreMigration := map[int64]*Comment{}
|
||||
comPostMigration := map[int64]*Comment{}
|
||||
|
||||
// Get pre migration values
|
||||
if err := x.Find(&issueLabels); err != nil {
|
||||
t.Errorf("Unable to find issueLabels: %v", err)
|
||||
return
|
||||
}
|
||||
for _, issueLabel := range issueLabels {
|
||||
ilPreMigration[issueLabel.ID] = issueLabel
|
||||
}
|
||||
if err := x.Find(&comments); err != nil {
|
||||
t.Errorf("Unable to find comments: %v", err)
|
||||
return
|
||||
}
|
||||
for _, comment := range comments {
|
||||
comPreMigration[comment.ID] = comment
|
||||
}
|
||||
|
||||
// Run the migration
|
||||
if err := RemoveInvalidLabels(x); err != nil {
|
||||
t.Errorf("unable to RemoveInvalidLabels: %v", err)
|
||||
}
|
||||
|
||||
// Get the post migration values
|
||||
issueLabels = issueLabels[:0]
|
||||
if err := x.Find(&issueLabels); err != nil {
|
||||
t.Errorf("Unable to find issueLabels: %v", err)
|
||||
return
|
||||
}
|
||||
for _, issueLabel := range issueLabels {
|
||||
ilPostMigration[issueLabel.ID] = issueLabel
|
||||
}
|
||||
comments = comments[:0]
|
||||
if err := x.Find(&comments); err != nil {
|
||||
t.Errorf("Unable to find comments: %v", err)
|
||||
return
|
||||
}
|
||||
for _, comment := range comments {
|
||||
comPostMigration[comment.ID] = comment
|
||||
}
|
||||
|
||||
// Finally test results of the migration
|
||||
for id, comment := range comPreMigration {
|
||||
post, ok := comPostMigration[id]
|
||||
if ok {
|
||||
if !comment.ShouldRemain {
|
||||
t.Errorf("Comment[%d] remained but should have been deleted", id)
|
||||
}
|
||||
assert.Equal(t, comment, post)
|
||||
} else if comment.ShouldRemain {
|
||||
t.Errorf("Comment[%d] was deleted but should have remained", id)
|
||||
}
|
||||
}
|
||||
|
||||
for id, il := range ilPreMigration {
|
||||
post, ok := ilPostMigration[id]
|
||||
if ok {
|
||||
if !il.ShouldRemain {
|
||||
t.Errorf("IssueLabel[%d] remained but should have been deleted", id)
|
||||
}
|
||||
assert.Equal(t, il, post)
|
||||
} else if il.ShouldRemain {
|
||||
t.Errorf("IssueLabel[%d] was deleted but should have remained", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
// DeleteOrphanedIssueLabels looks through the database for issue_labels where the label no longer exists and deletes them.
|
||||
func DeleteOrphanedIssueLabels(x base.EngineMigration) error {
|
||||
type IssueLabel struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IssueID int64 `xorm:"UNIQUE(s)"`
|
||||
LabelID int64 `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sess.Sync(new(IssueLabel)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
|
||||
if _, err := sess.Exec(`DELETE FROM issue_label WHERE issue_label.id IN (
|
||||
SELECT ill.id FROM (
|
||||
SELECT il.id
|
||||
FROM issue_label AS il
|
||||
LEFT JOIN label ON il.label_id = label.id
|
||||
WHERE
|
||||
label.id IS NULL
|
||||
) AS ill)`); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_14
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_DeleteOrphanedIssueLabels(t *testing.T) {
|
||||
// Create the models used in the migration
|
||||
type IssueLabel struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IssueID int64 `xorm:"UNIQUE(s)"`
|
||||
LabelID int64 `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
Description string
|
||||
Color string `xorm:"VARCHAR(7)"`
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
|
||||
// Prepare and load the testing database
|
||||
x, deferable := migrationtest.PrepareTestEnv(t, 0, new(IssueLabel), new(Label))
|
||||
if x == nil || t.Failed() {
|
||||
defer deferable()
|
||||
return
|
||||
}
|
||||
defer deferable()
|
||||
|
||||
var issueLabels []*IssueLabel
|
||||
preMigration := map[int64]*IssueLabel{}
|
||||
postMigration := map[int64]*IssueLabel{}
|
||||
|
||||
// Load issue labels that exist in the database pre-migration
|
||||
if err := x.Find(&issueLabels); err != nil {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
}
|
||||
for _, issueLabel := range issueLabels {
|
||||
preMigration[issueLabel.ID] = issueLabel
|
||||
}
|
||||
|
||||
// Run the migration
|
||||
if err := DeleteOrphanedIssueLabels(x); err != nil {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Load the remaining issue-labels
|
||||
issueLabels = issueLabels[:0]
|
||||
if err := x.Find(&issueLabels); err != nil {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
}
|
||||
for _, issueLabel := range issueLabels {
|
||||
postMigration[issueLabel.ID] = issueLabel
|
||||
}
|
||||
|
||||
// Now test what is left
|
||||
if _, ok := postMigration[2]; ok {
|
||||
t.Errorf("Orphaned Label[2] survived the migration")
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := postMigration[5]; ok {
|
||||
t.Errorf("Orphaned Label[5] survived the migration")
|
||||
return
|
||||
}
|
||||
|
||||
for id, post := range postMigration {
|
||||
pre := preMigration[id]
|
||||
assert.Equal(t, pre, post, "migration changed issueLabel %d", id)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user