1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

Split migrations folder (#21549)

There are too many files in `models/migrations` folder so that I split
them into sub folders.
This commit is contained in:
Lunny Xiao
2022-11-02 16:54:36 +08:00
committed by GitHub
parent 4827f42f56
commit e72acd5e5b
190 changed files with 1711 additions and 1481 deletions

View File

@@ -0,0 +1,15 @@
// Copyright 2021 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 v1_14 //nolint
import (
"testing"
"code.gitea.io/gitea/models/migrations/base"
)
func TestMain(m *testing.M) {
base.MainTest(m)
}

View File

@@ -0,0 +1,22 @@
// 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 v1_14 //nolint
import (
"fmt"
"xorm.io/xorm"
)
func AddChangedProtectedFilesPullRequestColumn(x *xorm.Engine) error {
type PullRequest struct {
ChangedProtectedFiles []string `xorm:"TEXT JSON"`
}
if err := x.Sync2(new(PullRequest)); err != nil {
return fmt.Errorf("Sync2: %w", err)
}
return nil
}

View File

@@ -0,0 +1,178 @@
// 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 v1_14 //nolint
import (
"fmt"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm"
)
// Copy paste from models/repo.go because we cannot import models package
func repoPath(userName, repoName string) string {
return filepath.Join(userPath(userName), strings.ToLower(repoName)+".git")
}
func userPath(userName string) string {
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
}
func FixPublisherIDforTagReleases(x *xorm.Engine) 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(git.DefaultContext, repoPath(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(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(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
}

View File

@@ -0,0 +1,67 @@
// 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 v1_14 //nolint
import (
"xorm.io/xorm"
)
func FixRepoTopics(x *xorm.Engine) error {
type Topic struct {
ID int64 `xorm:"pk autoincr"`
Name string `xorm:"UNIQUE VARCHAR(25)"`
RepoCount int
}
type RepoTopic struct {
RepoID int64 `xorm:"pk"`
TopicID int64 `xorm:"pk"`
}
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
}

View File

@@ -0,0 +1,112 @@
// 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 v1_14 //nolint
import (
"fmt"
"strconv"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm"
)
func UpdateCodeCommentReplies(x *xorm.Engine) 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.Sync2(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.UseMSSQL {
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.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 fmt.Errorf("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
}

View File

@@ -0,0 +1,39 @@
// 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 v1_14 //nolint
import (
"code.gitea.io/gitea/models/migrations/base"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func UpdateReactionConstraint(x *xorm.Engine) 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()
}

View File

@@ -0,0 +1,17 @@
// 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 v1_14 //nolint
import (
"xorm.io/xorm"
)
func AddBlockOnOfficialReviewRequests(x *xorm.Engine) error {
type ProtectedBranch struct {
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync2(new(ProtectedBranch))
}

View File

@@ -0,0 +1,74 @@
// 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 v1_14 //nolint
import (
"context"
"code.gitea.io/gitea/models/migrations/base"
"xorm.io/xorm"
)
func ConvertTaskTypeToString(x *xorm.Engine) 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.Sync2(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()
}

View File

@@ -0,0 +1,63 @@
// 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 v1_14 //nolint
import (
"code.gitea.io/gitea/models/migrations/base"
"xorm.io/xorm"
)
func ConvertWebhookTaskTypeToString(x *xorm.Engine) 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.Sync2(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()
}

View File

@@ -0,0 +1,36 @@
// 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 v1_14 //nolint
import (
"code.gitea.io/gitea/models/migrations/base"
"xorm.io/xorm"
)
func ConvertTopicNameFrom25To50(x *xorm.Engine) 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.Sync2(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()
}

View File

@@ -0,0 +1,38 @@
// 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 v1_14 //nolint
import (
"fmt"
"xorm.io/xorm"
)
// 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 *xorm.Engine) error {
if err := x.Sync2(new(OAuth2Grant)); err != nil {
return fmt.Errorf("Sync2: %w", err)
}
return nil
}

View File

@@ -0,0 +1,70 @@
// 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 v1_14 //nolint
import (
"code.gitea.io/gitea/models/migrations/base"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
func ConvertHookTaskTypeToVarcharAndTrim(x *xorm.Engine) error {
dbType := x.Dialect().URI().DBType
if dbType == schemas.SQLITE { // For SQLITE, varchar or char will always be represented as TEXT
return nil
}
type HookTask struct {
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
}
type Webhook struct {
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
}

View File

@@ -0,0 +1,113 @@
// Copyright 2021 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 v1_14 //nolint
import (
"crypto/sha256"
"fmt"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
"xorm.io/builder"
"xorm.io/xorm"
)
func RecalculateUserEmptyPWD(x *xorm.Engine) (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 fmt.Sprintf("%x", 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
}
if len(users) == 0 {
break
}
if err = sess.Begin(); err != nil {
return
}
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
}
}
// 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
}

View File

@@ -0,0 +1,24 @@
// Copyright 2021 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 v1_14 //nolint
import (
"fmt"
"xorm.io/xorm"
)
func AddUserRedirect(x *xorm.Engine) (err error) {
type UserRedirect struct {
ID int64 `xorm:"pk autoincr"`
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
RedirectUserID int64
}
if err := x.Sync2(new(UserRedirect)); err != nil {
return fmt.Errorf("Sync2: %w", err)
}
return nil
}

View File

@@ -0,0 +1,11 @@
// Copyright 2021 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 v1_14 //nolint
import "xorm.io/xorm"
func RecreateUserTableToFixDefaultValues(_ *xorm.Engine) error {
return nil
}

View File

@@ -0,0 +1,14 @@
// Copyright 2021 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 v1_14 //nolint
import (
"xorm.io/xorm"
)
func CommentTypeDeleteBranchUseOldRef(x *xorm.Engine) error {
_, err := x.Exec("UPDATE comment SET old_ref = commit_sha, commit_sha = '' WHERE type = 11")
return err
}

View File

@@ -0,0 +1,22 @@
// Copyright 2021 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 v1_14 //nolint
import (
"fmt"
"xorm.io/xorm"
)
func AddDismissedReviewColumn(x *xorm.Engine) error {
type Review struct {
Dismissed bool `xorm:"NOT NULL DEFAULT false"`
}
if err := x.Sync2(new(Review)); err != nil {
return fmt.Errorf("Sync2: %w", err)
}
return nil
}

View File

@@ -0,0 +1,22 @@
// Copyright 2021 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 v1_14 //nolint
import (
"fmt"
"xorm.io/xorm"
)
func AddSortingColToProjectBoard(x *xorm.Engine) error {
type ProjectBoard struct {
Sorting int8 `xorm:"NOT NULL DEFAULT 0"`
}
if err := x.Sync2(new(ProjectBoard)); err != nil {
return fmt.Errorf("Sync2: %w", err)
}
return nil
}

View File

@@ -0,0 +1,20 @@
// 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 v1_14 //nolint
import (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func AddSessionTable(x *xorm.Engine) error {
type Session struct {
Key string `xorm:"pk CHAR(16)"`
Data []byte `xorm:"BLOB"`
Expiry timeutil.TimeStamp
}
return x.Sync2(new(Session))
}

View File

@@ -0,0 +1,22 @@
// Copyright 2021 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 v1_14 //nolint
import (
"fmt"
"xorm.io/xorm"
)
func AddTimeIDCommentColumn(x *xorm.Engine) error {
type Comment struct {
TimeID int64
}
if err := x.Sync2(new(Comment)); err != nil {
return fmt.Errorf("Sync2: %w", err)
}
return nil
}

View File

@@ -0,0 +1,35 @@
// Copyright 2021 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 v1_14 //nolint
import (
"fmt"
"xorm.io/xorm"
)
func AddRepoTransfer(x *xorm.Engine) 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.Sync2(new(RepoTransfer)); err != nil {
return fmt.Errorf("Sync2: %w", err)
}
return sess.Commit()
}

View File

@@ -0,0 +1,54 @@
// Copyright 2021 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 v1_14 //nolint
import (
"fmt"
"regexp"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm"
)
func FixPostgresIDSequences(x *xorm.Engine) error {
if !setting.Database.UsePostgreSQL {
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()
}

View File

@@ -0,0 +1,77 @@
// Copyright 2021 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 v1_14 //nolint
import (
"xorm.io/xorm"
)
// 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 *xorm.Engine) 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.Sync2(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
}

View File

@@ -0,0 +1,129 @@
// Copyright 2021 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 v1_14 //nolint
import (
"testing"
"code.gitea.io/gitea/models/migrations/base"
"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 := base.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)
}
}
}

View File

@@ -0,0 +1,43 @@
// Copyright 2021 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 v1_14 //nolint
import (
"fmt"
"xorm.io/xorm"
)
// DeleteOrphanedIssueLabels looks through the database for issue_labels where the label no longer exists and deletes them.
func DeleteOrphanedIssueLabels(x *xorm.Engine) 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.Sync2(new(IssueLabel)); err != nil {
return fmt.Errorf("Sync2: %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()
}

View File

@@ -0,0 +1,89 @@
// Copyright 2021 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 v1_14 //nolint
import (
"testing"
"code.gitea.io/gitea/models/migrations/base"
"code.gitea.io/gitea/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 := base.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)
}
}