summaryrefslogtreecommitdiffstats
path: root/models/migrations/v1_14
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-10-11 10:27:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-10-11 10:27:00 +0000
commit65aa53fc52ff15efe54df4147564828d535837f8 (patch)
tree31c51dad04fdcca80e6d3043c8bd49d2f1a51f83 /models/migrations/v1_14
parentInitial commit. (diff)
downloadforgejo-65aa53fc52ff15efe54df4147564828d535837f8.tar.xz
forgejo-65aa53fc52ff15efe54df4147564828d535837f8.zip
Adding upstream version 8.0.3.HEADupstream/8.0.3upstreamdebian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'models/migrations/v1_14')
-rw-r--r--models/migrations/v1_14/main_test.go14
-rw-r--r--models/migrations/v1_14/v155.go21
-rw-r--r--models/migrations/v1_14/v156.go177
-rw-r--r--models/migrations/v1_14/v157.go66
-rw-r--r--models/migrations/v1_14/v158.go101
-rw-r--r--models/migrations/v1_14/v159.go38
-rw-r--r--models/migrations/v1_14/v160.go16
-rw-r--r--models/migrations/v1_14/v161.go73
-rw-r--r--models/migrations/v1_14/v162.go62
-rw-r--r--models/migrations/v1_14/v163.go35
-rw-r--r--models/migrations/v1_14/v164.go37
-rw-r--r--models/migrations/v1_14/v165.go57
-rw-r--r--models/migrations/v1_14/v166.go112
-rw-r--r--models/migrations/v1_14/v167.go23
-rw-r--r--models/migrations/v1_14/v168.go10
-rw-r--r--models/migrations/v1_14/v169.go13
-rw-r--r--models/migrations/v1_14/v170.go21
-rw-r--r--models/migrations/v1_14/v171.go21
-rw-r--r--models/migrations/v1_14/v172.go19
-rw-r--r--models/migrations/v1_14/v173.go21
-rw-r--r--models/migrations/v1_14/v174.go34
-rw-r--r--models/migrations/v1_14/v175.go53
-rw-r--r--models/migrations/v1_14/v176.go76
-rw-r--r--models/migrations/v1_14/v176_test.go128
-rw-r--r--models/migrations/v1_14/v177.go42
-rw-r--r--models/migrations/v1_14/v177_test.go89
26 files changed, 1359 insertions, 0 deletions
diff --git a/models/migrations/v1_14/main_test.go b/models/migrations/v1_14/main_test.go
new file mode 100644
index 00000000..7a091b9b
--- /dev/null
+++ b/models/migrations/v1_14/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_14 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+)
+
+func TestMain(m *testing.M) {
+ base.MainTest(m)
+}
diff --git a/models/migrations/v1_14/v155.go b/models/migrations/v1_14/v155.go
new file mode 100644
index 00000000..e814f599
--- /dev/null
+++ b/models/migrations/v1_14/v155.go
@@ -0,0 +1,21 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.Sync(new(PullRequest)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_14/v156.go b/models/migrations/v1_14/v156.go
new file mode 100644
index 00000000..2cf4954a
--- /dev/null
+++ b/models/migrations/v1_14/v156.go
@@ -0,0 +1,177 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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
+}
diff --git a/models/migrations/v1_14/v157.go b/models/migrations/v1_14/v157.go
new file mode 100644
index 00000000..7187278d
--- /dev/null
+++ b/models/migrations/v1_14/v157.go
@@ -0,0 +1,66 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_14 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func FixRepoTopics(x *xorm.Engine) error {
+ type Topic struct { //nolint:unused
+ ID int64 `xorm:"pk autoincr"`
+ Name string `xorm:"UNIQUE VARCHAR(25)"`
+ RepoCount int
+ }
+
+ type RepoTopic struct { //nolint:unused
+ 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
+}
diff --git a/models/migrations/v1_14/v158.go b/models/migrations/v1_14/v158.go
new file mode 100644
index 00000000..2d688b17
--- /dev/null
+++ b/models/migrations/v1_14/v158.go
@@ -0,0 +1,101 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.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
+ }
+
+ 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)
+ 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
+}
diff --git a/models/migrations/v1_14/v159.go b/models/migrations/v1_14/v159.go
new file mode 100644
index 00000000..149ae0f6
--- /dev/null
+++ b/models/migrations/v1_14/v159.go
@@ -0,0 +1,38 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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()
+}
diff --git a/models/migrations/v1_14/v160.go b/models/migrations/v1_14/v160.go
new file mode 100644
index 00000000..4dea91b5
--- /dev/null
+++ b/models/migrations/v1_14/v160.go
@@ -0,0 +1,16 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.Sync(new(ProtectedBranch))
+}
diff --git a/models/migrations/v1_14/v161.go b/models/migrations/v1_14/v161.go
new file mode 100644
index 00000000..ac7e821a
--- /dev/null
+++ b/models/migrations/v1_14/v161.go
@@ -0,0 +1,73 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.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()
+}
diff --git a/models/migrations/v1_14/v162.go b/models/migrations/v1_14/v162.go
new file mode 100644
index 00000000..2e4e0b8e
--- /dev/null
+++ b/models/migrations/v1_14/v162.go
@@ -0,0 +1,62 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.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()
+}
diff --git a/models/migrations/v1_14/v163.go b/models/migrations/v1_14/v163.go
new file mode 100644
index 00000000..0cd8ba68
--- /dev/null
+++ b/models/migrations/v1_14/v163.go
@@ -0,0 +1,35 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.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()
+}
diff --git a/models/migrations/v1_14/v164.go b/models/migrations/v1_14/v164.go
new file mode 100644
index 00000000..54f69514
--- /dev/null
+++ b/models/migrations/v1_14/v164.go
@@ -0,0 +1,37 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.Sync(new(OAuth2Grant)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_14/v165.go b/models/migrations/v1_14/v165.go
new file mode 100644
index 00000000..5b1a7792
--- /dev/null
+++ b/models/migrations/v1_14/v165.go
@@ -0,0 +1,57 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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 { //nolint:unused
+ 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
+ }
+
+ if _, err := x.Exec("UPDATE hook_task SET typ = TRIM(typ)"); err != nil {
+ return err
+ }
+
+ type Webhook struct { //nolint:unused
+ 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
+ }
+
+ _, err := x.Exec("UPDATE webhook SET type = TRIM(type)")
+ return err
+}
diff --git a/models/migrations/v1_14/v166.go b/models/migrations/v1_14/v166.go
new file mode 100644
index 00000000..e5731582
--- /dev/null
+++ b/models/migrations/v1_14/v166.go
@@ -0,0 +1,112 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_14 //nolint
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+
+ "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 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
+}
diff --git a/models/migrations/v1_14/v167.go b/models/migrations/v1_14/v167.go
new file mode 100644
index 00000000..9d416f6a
--- /dev/null
+++ b/models/migrations/v1_14/v167.go
@@ -0,0 +1,23 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.Sync(new(UserRedirect)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_14/v168.go b/models/migrations/v1_14/v168.go
new file mode 100644
index 00000000..a30a8859
--- /dev/null
+++ b/models/migrations/v1_14/v168.go
@@ -0,0 +1,10 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_14 //nolint
+
+import "xorm.io/xorm"
+
+func RecreateUserTableToFixDefaultValues(_ *xorm.Engine) error {
+ return nil
+}
diff --git a/models/migrations/v1_14/v169.go b/models/migrations/v1_14/v169.go
new file mode 100644
index 00000000..5b81bb58
--- /dev/null
+++ b/models/migrations/v1_14/v169.go
@@ -0,0 +1,13 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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
+}
diff --git a/models/migrations/v1_14/v170.go b/models/migrations/v1_14/v170.go
new file mode 100644
index 00000000..7b6498a3
--- /dev/null
+++ b/models/migrations/v1_14/v170.go
@@ -0,0 +1,21 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.Sync(new(Review)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_14/v171.go b/models/migrations/v1_14/v171.go
new file mode 100644
index 00000000..51a35a02
--- /dev/null
+++ b/models/migrations/v1_14/v171.go
@@ -0,0 +1,21 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.Sync(new(ProjectBoard)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_14/v172.go b/models/migrations/v1_14/v172.go
new file mode 100644
index 00000000..0f9bef90
--- /dev/null
+++ b/models/migrations/v1_14/v172.go
@@ -0,0 +1,19 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.Sync(new(Session))
+}
diff --git a/models/migrations/v1_14/v173.go b/models/migrations/v1_14/v173.go
new file mode 100644
index 00000000..2d9eee91
--- /dev/null
+++ b/models/migrations/v1_14/v173.go
@@ -0,0 +1,21 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_14 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddTimeIDCommentColumn(x *xorm.Engine) error {
+ type Comment struct {
+ TimeID int64
+ }
+
+ if err := x.Sync(new(Comment)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_14/v174.go b/models/migrations/v1_14/v174.go
new file mode 100644
index 00000000..c839e15d
--- /dev/null
+++ b/models/migrations/v1_14/v174.go
@@ -0,0 +1,34 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.Sync(new(RepoTransfer)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_14/v175.go b/models/migrations/v1_14/v175.go
new file mode 100644
index 00000000..70d72b26
--- /dev/null
+++ b/models/migrations/v1_14/v175.go
@@ -0,0 +1,53 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.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()
+}
diff --git a/models/migrations/v1_14/v176.go b/models/migrations/v1_14/v176.go
new file mode 100644
index 00000000..1ed49f75
--- /dev/null
+++ b/models/migrations/v1_14/v176.go
@@ -0,0 +1,76 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.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
+}
diff --git a/models/migrations/v1_14/v176_test.go b/models/migrations/v1_14/v176_test.go
new file mode 100644
index 00000000..ea3e750d
--- /dev/null
+++ b/models/migrations/v1_14/v176_test.go
@@ -0,0 +1,128 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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)
+ }
+ }
+}
diff --git a/models/migrations/v1_14/v177.go b/models/migrations/v1_14/v177.go
new file mode 100644
index 00000000..6e1838f3
--- /dev/null
+++ b/models/migrations/v1_14/v177.go
@@ -0,0 +1,42 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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.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()
+}
diff --git a/models/migrations/v1_14/v177_test.go b/models/migrations/v1_14/v177_test.go
new file mode 100644
index 00000000..30ef0aa8
--- /dev/null
+++ b/models/migrations/v1_14/v177_test.go
@@ -0,0 +1,89 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_14 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+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 {
+ require.NoError(t, err)
+ return
+ }
+ for _, issueLabel := range issueLabels {
+ preMigration[issueLabel.ID] = issueLabel
+ }
+
+ // Run the migration
+ if err := DeleteOrphanedIssueLabels(x); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ // Load the remaining issue-labels
+ issueLabels = issueLabels[:0]
+ if err := x.Find(&issueLabels); err != nil {
+ require.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)
+ }
+}