summaryrefslogtreecommitdiffstats
path: root/models/migrations/v1_22
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-12-12 23:57:56 +0100
commite68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch)
tree97775d6c13b0f416af55314eb6a89ef792474615 /models/migrations/v1_22
parentInitial commit. (diff)
downloadforgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz
forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r--models/migrations/v1_22/main_test.go14
-rw-r--r--models/migrations/v1_22/v280.go29
-rw-r--r--models/migrations/v1_22/v281.go21
-rw-r--r--models/migrations/v1_22/v282.go16
-rw-r--r--models/migrations/v1_22/v283.go38
-rw-r--r--models/migrations/v1_22/v283_test.go28
-rw-r--r--models/migrations/v1_22/v284.go18
-rw-r--r--models/migrations/v1_22/v285.go22
-rw-r--r--models/migrations/v1_22/v286.go75
-rw-r--r--models/migrations/v1_22/v286_test.go119
-rw-r--r--models/migrations/v1_22/v287.go46
-rw-r--r--models/migrations/v1_22/v288.go26
-rw-r--r--models/migrations/v1_22/v289.go21
-rw-r--r--models/migrations/v1_22/v290.go46
-rw-r--r--models/migrations/v1_22/v290_test.go59
-rw-r--r--models/migrations/v1_22/v291.go18
-rw-r--r--models/migrations/v1_22/v292.go9
-rw-r--r--models/migrations/v1_22/v293.go108
-rw-r--r--models/migrations/v1_22/v293_test.go45
-rw-r--r--models/migrations/v1_22/v294.go44
-rw-r--r--models/migrations/v1_22/v294_test.go53
-rw-r--r--models/migrations/v1_22/v295.go18
-rw-r--r--models/migrations/v1_22/v296.go16
-rw-r--r--models/migrations/v1_22/v298.go10
24 files changed, 899 insertions, 0 deletions
diff --git a/models/migrations/v1_22/main_test.go b/models/migrations/v1_22/main_test.go
new file mode 100644
index 0000000..2005789
--- /dev/null
+++ b/models/migrations/v1_22/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "testing"
+
+ migration_tests "code.gitea.io/gitea/models/migrations/test"
+)
+
+func TestMain(m *testing.M) {
+ migration_tests.MainTest(m)
+}
diff --git a/models/migrations/v1_22/v280.go b/models/migrations/v1_22/v280.go
new file mode 100644
index 0000000..a8ee4a3
--- /dev/null
+++ b/models/migrations/v1_22/v280.go
@@ -0,0 +1,29 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func RenameUserThemes(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if _, err := sess.Exec("UPDATE `user` SET `theme` = 'gitea-light' WHERE `theme` = 'gitea'"); err != nil {
+ return err
+ }
+ if _, err := sess.Exec("UPDATE `user` SET `theme` = 'gitea-dark' WHERE `theme` = 'arc-green'"); err != nil {
+ return err
+ }
+ if _, err := sess.Exec("UPDATE `user` SET `theme` = 'gitea-auto' WHERE `theme` = 'auto'"); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_22/v281.go b/models/migrations/v1_22/v281.go
new file mode 100644
index 0000000..fc1866a
--- /dev/null
+++ b/models/migrations/v1_22/v281.go
@@ -0,0 +1,21 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func CreateAuthTokenTable(x *xorm.Engine) error {
+ type AuthToken struct {
+ ID string `xorm:"pk"`
+ TokenHash string
+ UserID int64 `xorm:"INDEX"`
+ ExpiresUnix timeutil.TimeStamp `xorm:"INDEX"`
+ }
+
+ return x.Sync(new(AuthToken))
+}
diff --git a/models/migrations/v1_22/v282.go b/models/migrations/v1_22/v282.go
new file mode 100644
index 0000000..baad9e0
--- /dev/null
+++ b/models/migrations/v1_22/v282.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddIndexToPullAutoMergeDoerID(x *xorm.Engine) error {
+ type PullAutoMerge struct {
+ DoerID int64 `xorm:"INDEX NOT NULL"`
+ }
+
+ return x.Sync(&PullAutoMerge{})
+}
diff --git a/models/migrations/v1_22/v283.go b/models/migrations/v1_22/v283.go
new file mode 100644
index 0000000..86946d1
--- /dev/null
+++ b/models/migrations/v1_22/v283.go
@@ -0,0 +1,38 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddCombinedIndexToIssueUser(x *xorm.Engine) error {
+ type OldIssueUser struct {
+ IssueID int64
+ UID int64
+ Cnt int64
+ }
+
+ var duplicatedIssueUsers []OldIssueUser
+ if err := x.SQL("select * from (select issue_id, uid, count(1) as cnt from issue_user group by issue_id, uid) a where a.cnt > 1").
+ Find(&duplicatedIssueUsers); err != nil {
+ return err
+ }
+ for _, issueUser := range duplicatedIssueUsers {
+ var ids []int64
+ if err := x.SQL("SELECT id FROM issue_user WHERE issue_id = ? and uid = ? limit ?", issueUser.IssueID, issueUser.UID, issueUser.Cnt-1).Find(&ids); err != nil {
+ return err
+ }
+ if _, err := x.Table("issue_user").In("id", ids).Delete(); err != nil {
+ return err
+ }
+ }
+
+ type IssueUser struct {
+ UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID.
+ IssueID int64 `xorm:"INDEX unique(uid_to_issue)"`
+ }
+
+ return x.Sync(&IssueUser{})
+}
diff --git a/models/migrations/v1_22/v283_test.go b/models/migrations/v1_22/v283_test.go
new file mode 100644
index 0000000..5f6c04a
--- /dev/null
+++ b/models/migrations/v1_22/v283_test.go
@@ -0,0 +1,28 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "testing"
+
+ migration_tests "code.gitea.io/gitea/models/migrations/test"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_AddCombinedIndexToIssueUser(t *testing.T) {
+ type IssueUser struct { // old struct
+ ID int64 `xorm:"pk autoincr"`
+ UID int64 `xorm:"INDEX"` // User ID.
+ IssueID int64 `xorm:"INDEX"`
+ IsRead bool
+ IsMentioned bool
+ }
+
+ // Prepare and load the testing database
+ x, deferable := migration_tests.PrepareTestEnv(t, 0, new(IssueUser))
+ defer deferable()
+
+ require.NoError(t, AddCombinedIndexToIssueUser(x))
+}
diff --git a/models/migrations/v1_22/v284.go b/models/migrations/v1_22/v284.go
new file mode 100644
index 0000000..2b95078
--- /dev/null
+++ b/models/migrations/v1_22/v284.go
@@ -0,0 +1,18 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+import (
+ "xorm.io/xorm"
+)
+
+func AddIgnoreStaleApprovalsColumnToProtectedBranchTable(x *xorm.Engine) error {
+ type ProtectedBranch struct {
+ IgnoreStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
+ }
+ _, err := x.SyncWithOptions(xorm.SyncOptions{
+ IgnoreIndices: true,
+ IgnoreConstrains: true,
+ }, new(ProtectedBranch))
+ return err
+}
diff --git a/models/migrations/v1_22/v285.go b/models/migrations/v1_22/v285.go
new file mode 100644
index 0000000..a55cc17
--- /dev/null
+++ b/models/migrations/v1_22/v285.go
@@ -0,0 +1,22 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "time"
+
+ "xorm.io/xorm"
+)
+
+func AddPreviousDurationToActionRun(x *xorm.Engine) error {
+ type ActionRun struct {
+ PreviousDuration time.Duration
+ }
+
+ _, err := x.SyncWithOptions(xorm.SyncOptions{
+ IgnoreIndices: true,
+ IgnoreConstrains: true,
+ }, &ActionRun{})
+ return err
+}
diff --git a/models/migrations/v1_22/v286.go b/models/migrations/v1_22/v286.go
new file mode 100644
index 0000000..97ff649
--- /dev/null
+++ b/models/migrations/v1_22/v286.go
@@ -0,0 +1,75 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+package v1_22 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func expandHashReferencesToSha256(x *xorm.Engine) error {
+ alteredTables := [][2]string{
+ {"commit_status", "context_hash"},
+ {"comment", "commit_sha"},
+ {"pull_request", "merge_base"},
+ {"pull_request", "merged_commit_id"},
+ {"review", "commit_id"},
+ {"review_state", "commit_sha"},
+ {"repo_archiver", "commit_id"},
+ {"release", "sha1"},
+ {"repo_indexer_status", "commit_sha"},
+ }
+
+ db := x.NewSession()
+ defer db.Close()
+
+ if err := db.Begin(); err != nil {
+ return err
+ }
+
+ if !setting.Database.Type.IsSQLite3() {
+ for _, alts := range alteredTables {
+ var err error
+ if setting.Database.Type.IsMySQL() {
+ _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1]))
+ } else {
+ _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1]))
+ }
+ if err != nil {
+ return fmt.Errorf("alter column '%s' of table '%s' failed: %w", alts[1], alts[0], err)
+ }
+ }
+ }
+ log.Debug("Updated database tables to hold SHA256 git hash references")
+
+ return db.Commit()
+}
+
+func addObjectFormatNameToRepository(x *xorm.Engine) error {
+ type Repository struct {
+ ObjectFormatName string `xorm:"VARCHAR(6) NOT NULL DEFAULT 'sha1'"`
+ }
+
+ if _, err := x.SyncWithOptions(xorm.SyncOptions{
+ IgnoreIndices: true,
+ IgnoreConstrains: true,
+ }, new(Repository)); err != nil {
+ return err
+ }
+
+ // Here to catch weird edge-cases where column constraints above are
+ // not applied by the DB backend
+ _, err := x.Exec("UPDATE `repository` set `object_format_name` = 'sha1' WHERE `object_format_name` = '' or `object_format_name` IS NULL")
+ return err
+}
+
+func AdjustDBForSha256(x *xorm.Engine) error {
+ if err := expandHashReferencesToSha256(x); err != nil {
+ return err
+ }
+ return addObjectFormatNameToRepository(x)
+}
diff --git a/models/migrations/v1_22/v286_test.go b/models/migrations/v1_22/v286_test.go
new file mode 100644
index 0000000..76b00e5
--- /dev/null
+++ b/models/migrations/v1_22/v286_test.go
@@ -0,0 +1,119 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "testing"
+
+ migration_tests "code.gitea.io/gitea/models/migrations/test"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "xorm.io/xorm"
+)
+
+func PrepareOldRepository(t *testing.T) (*xorm.Engine, func()) {
+ type Repository struct { // old struct
+ ID int64 `xorm:"pk autoincr"`
+ }
+
+ type CommitStatus struct {
+ ID int64
+ ContextHash string `xorm:"char(40) index"`
+ }
+
+ type RepoArchiver struct {
+ ID int64
+ RepoID int64 `xorm:"index unique(s)"`
+ Type int `xorm:"unique(s)"`
+ CommitID string `xorm:"VARCHAR(40) unique(s)"`
+ }
+
+ type ReviewState struct {
+ ID int64
+ UserID int64 `xorm:"NOT NULL UNIQUE(pull_commit_user)"`
+ PullID int64 `xorm:"NOT NULL INDEX UNIQUE(pull_commit_user) DEFAULT 0"`
+ CommitSHA string `xorm:"NOT NULL VARCHAR(40) UNIQUE(pull_commit_user)"`
+ }
+
+ type Comment struct {
+ ID int64
+ CommitSHA string
+ }
+
+ type PullRequest struct {
+ ID int64
+ CommitSHA string
+ MergeBase string
+ MergedCommitID string
+ }
+
+ type Release struct {
+ ID int64
+ Sha1 string
+ }
+
+ type RepoIndexerStatus struct {
+ ID int64
+ CommitSHA string
+ }
+
+ type Review struct {
+ ID int64
+ CommitID string
+ }
+
+ // Prepare and load the testing database
+ return migration_tests.PrepareTestEnv(t, 0,
+ new(Repository),
+ new(CommitStatus),
+ new(RepoArchiver),
+ new(ReviewState),
+ new(Review),
+ new(Comment),
+ new(PullRequest),
+ new(Release),
+ new(RepoIndexerStatus),
+ )
+}
+
+func Test_RepositoryFormat(t *testing.T) {
+ x, deferable := PrepareOldRepository(t)
+ defer deferable()
+
+ require.NoError(t, AdjustDBForSha256(x))
+
+ type Repository struct {
+ ID int64 `xorm:"pk autoincr"`
+ ObjectFormatName string `xorg:"not null default('sha1')"`
+ }
+
+ repo := new(Repository)
+
+ // check we have some records to migrate
+ count, err := x.Count(new(Repository))
+ require.NoError(t, err)
+ assert.EqualValues(t, 4, count)
+
+ repo.ObjectFormatName = "sha256"
+ _, err = x.Insert(repo)
+ require.NoError(t, err)
+ id := repo.ID
+
+ count, err = x.Count(new(Repository))
+ require.NoError(t, err)
+ assert.EqualValues(t, 5, count)
+
+ repo = new(Repository)
+ ok, err := x.ID(2).Get(repo)
+ require.NoError(t, err)
+ assert.True(t, ok)
+ assert.EqualValues(t, "sha1", repo.ObjectFormatName)
+
+ repo = new(Repository)
+ ok, err = x.ID(id).Get(repo)
+ require.NoError(t, err)
+ assert.True(t, ok)
+ assert.EqualValues(t, "sha256", repo.ObjectFormatName)
+}
diff --git a/models/migrations/v1_22/v287.go b/models/migrations/v1_22/v287.go
new file mode 100644
index 0000000..c8b1593
--- /dev/null
+++ b/models/migrations/v1_22/v287.go
@@ -0,0 +1,46 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+type BadgeUnique struct {
+ ID int64 `xorm:"pk autoincr"`
+ Slug string `xorm:"UNIQUE"`
+}
+
+func (BadgeUnique) TableName() string {
+ return "badge"
+}
+
+func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error {
+ type Badge struct {
+ Slug string
+ }
+
+ err := x.Sync(new(Badge))
+ if err != nil {
+ return err
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ _, err = sess.Exec("UPDATE `badge` SET `slug` = `id` Where `slug` IS NULL")
+ if err != nil {
+ return err
+ }
+
+ err = sess.Sync(new(BadgeUnique))
+ if err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_22/v288.go b/models/migrations/v1_22/v288.go
new file mode 100644
index 0000000..7c93bfc
--- /dev/null
+++ b/models/migrations/v1_22/v288.go
@@ -0,0 +1,26 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+type Blocking struct {
+ ID int64 `xorm:"pk autoincr"`
+ BlockerID int64 `xorm:"UNIQUE(block)"`
+ BlockeeID int64 `xorm:"UNIQUE(block)"`
+ Note string
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+}
+
+func (*Blocking) TableName() string {
+ return "user_blocking"
+}
+
+func AddUserBlockingTable(x *xorm.Engine) error {
+ return x.Sync(&Blocking{})
+}
diff --git a/models/migrations/v1_22/v289.go b/models/migrations/v1_22/v289.go
new file mode 100644
index 0000000..b9941aa
--- /dev/null
+++ b/models/migrations/v1_22/v289.go
@@ -0,0 +1,21 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import "xorm.io/xorm"
+
+func AddDefaultWikiBranch(x *xorm.Engine) error {
+ type Repository struct {
+ ID int64
+ DefaultWikiBranch string
+ }
+ if _, err := x.SyncWithOptions(xorm.SyncOptions{
+ IgnoreIndices: true,
+ IgnoreConstrains: true,
+ }, &Repository{}); err != nil {
+ return err
+ }
+ _, err := x.Exec("UPDATE `repository` SET default_wiki_branch = 'master' WHERE (default_wiki_branch IS NULL) OR (default_wiki_branch = '')")
+ return err
+}
diff --git a/models/migrations/v1_22/v290.go b/models/migrations/v1_22/v290.go
new file mode 100644
index 0000000..e3c58b0
--- /dev/null
+++ b/models/migrations/v1_22/v290.go
@@ -0,0 +1,46 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+ webhook_module "code.gitea.io/gitea/modules/webhook"
+
+ "xorm.io/xorm"
+)
+
+// HookTask represents a hook task.
+// exact copy of models/webhook/hooktask.go when this migration was created
+// - xorm:"-" fields deleted
+type HookTask struct {
+ ID int64 `xorm:"pk autoincr"`
+ HookID int64 `xorm:"index"`
+ UUID string `xorm:"unique"`
+ PayloadContent string `xorm:"LONGTEXT"`
+ EventType webhook_module.HookEventType
+ IsDelivered bool
+ Delivered timeutil.TimeStampNano
+
+ // History info.
+ IsSucceed bool
+ RequestContent string `xorm:"LONGTEXT"`
+ ResponseContent string `xorm:"LONGTEXT"`
+
+ // Version number to allow for smooth version upgrades:
+ // - Version 1: PayloadContent contains the JSON as send to the URL
+ // - Version 2: PayloadContent contains the original event
+ PayloadVersion int `xorm:"DEFAULT 1"`
+}
+
+func AddPayloadVersionToHookTaskTable(x *xorm.Engine) error {
+ // create missing column
+ if _, err := x.SyncWithOptions(xorm.SyncOptions{
+ IgnoreIndices: true,
+ IgnoreConstrains: true,
+ }, new(HookTask)); err != nil {
+ return err
+ }
+ _, err := x.Exec("UPDATE hook_task SET payload_version = 1 WHERE payload_version IS NULL")
+ return err
+}
diff --git a/models/migrations/v1_22/v290_test.go b/models/migrations/v1_22/v290_test.go
new file mode 100644
index 0000000..ced200f
--- /dev/null
+++ b/models/migrations/v1_22/v290_test.go
@@ -0,0 +1,59 @@
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "strconv"
+ "testing"
+
+ migration_tests "code.gitea.io/gitea/models/migrations/test"
+ "code.gitea.io/gitea/modules/timeutil"
+ webhook_module "code.gitea.io/gitea/modules/webhook"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_AddPayloadVersionToHookTaskTable(t *testing.T) {
+ type HookTaskMigrated HookTask
+
+ // HookTask represents a hook task, as of before the migration
+ type HookTask struct {
+ ID int64 `xorm:"pk autoincr"`
+ HookID int64 `xorm:"index"`
+ UUID string `xorm:"unique"`
+ PayloadContent string `xorm:"LONGTEXT"`
+ EventType webhook_module.HookEventType
+ IsDelivered bool
+ Delivered timeutil.TimeStampNano
+
+ // History info.
+ IsSucceed bool
+ RequestContent string `xorm:"LONGTEXT"`
+ ResponseContent string `xorm:"LONGTEXT"`
+ }
+
+ // Prepare and load the testing database
+ x, deferable := migration_tests.PrepareTestEnv(t, 0, new(HookTask), new(HookTaskMigrated))
+ defer deferable()
+ if x == nil || t.Failed() {
+ return
+ }
+
+ require.NoError(t, AddPayloadVersionToHookTaskTable(x))
+
+ expected := []HookTaskMigrated{}
+ require.NoError(t, x.Table("hook_task_migrated").Asc("id").Find(&expected))
+ assert.Len(t, expected, 2)
+
+ got := []HookTaskMigrated{}
+ require.NoError(t, x.Table("hook_task").Asc("id").Find(&got))
+
+ for i, expected := range expected {
+ expected, got := expected, got[i]
+ t.Run(strconv.FormatInt(expected.ID, 10), func(t *testing.T) {
+ assert.Equal(t, expected.PayloadVersion, got.PayloadVersion)
+ })
+ }
+}
diff --git a/models/migrations/v1_22/v291.go b/models/migrations/v1_22/v291.go
new file mode 100644
index 0000000..74726fa
--- /dev/null
+++ b/models/migrations/v1_22/v291.go
@@ -0,0 +1,18 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import "xorm.io/xorm"
+
+func AddCommentIDIndexofAttachment(x *xorm.Engine) error {
+ type Attachment struct {
+ CommentID int64 `xorm:"INDEX"`
+ }
+
+ _, err := x.SyncWithOptions(xorm.SyncOptions{
+ IgnoreDropIndices: true,
+ IgnoreConstrains: true,
+ }, &Attachment{})
+ return err
+}
diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go
new file mode 100644
index 0000000..beca556
--- /dev/null
+++ b/models/migrations/v1_22/v292.go
@@ -0,0 +1,9 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+// NOTE: noop the original migration has bug which some projects will be skip, so
+// these projects will have no default board.
+// So that this migration will be skipped and go to v293.go
+// This file is a placeholder so that readers can know what happened
diff --git a/models/migrations/v1_22/v293.go b/models/migrations/v1_22/v293.go
new file mode 100644
index 0000000..53cc719
--- /dev/null
+++ b/models/migrations/v1_22/v293.go
@@ -0,0 +1,108 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+// CheckProjectColumnsConsistency ensures there is exactly one default board per project present
+func CheckProjectColumnsConsistency(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+
+ limit := setting.Database.IterateBufferSize
+ if limit <= 0 {
+ limit = 50
+ }
+
+ type Project struct {
+ ID int64
+ CreatorID int64
+ BoardID int64
+ }
+
+ type ProjectBoard struct {
+ ID int64 `xorm:"pk autoincr"`
+ Title string
+ Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
+ Sorting int8 `xorm:"NOT NULL DEFAULT 0"`
+ Color string `xorm:"VARCHAR(7)"`
+
+ ProjectID int64 `xorm:"INDEX NOT NULL"`
+ CreatorID int64 `xorm:"NOT NULL"`
+
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ for {
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ // all these projects without defaults will be fixed in the same loop, so
+ // we just need to always get projects without defaults until no such project
+ var projects []*Project
+ if err := sess.Select("project.id as id, project.creator_id, project_board.id as board_id").
+ Join("LEFT", "project_board", "project_board.project_id = project.id AND project_board.`default`=?", true).
+ Where("project_board.id is NULL OR project_board.id = 0").
+ Limit(limit).
+ Find(&projects); err != nil {
+ return err
+ }
+
+ for _, p := range projects {
+ if _, err := sess.Insert(ProjectBoard{
+ ProjectID: p.ID,
+ Default: true,
+ Title: "Uncategorized",
+ CreatorID: p.CreatorID,
+ }); err != nil {
+ return err
+ }
+ }
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+
+ if len(projects) == 0 {
+ break
+ }
+ }
+ sess.Close()
+
+ return removeDuplicatedBoardDefault(x)
+}
+
+func removeDuplicatedBoardDefault(x *xorm.Engine) error {
+ type ProjectInfo struct {
+ ProjectID int64
+ DefaultNum int
+ }
+ var projects []ProjectInfo
+ if err := x.Select("project_id, count(*) AS default_num").
+ Table("project_board").
+ Where("`default` = ?", true).
+ GroupBy("project_id").
+ Having("count(*) > 1").
+ Find(&projects); err != nil {
+ return err
+ }
+
+ for _, project := range projects {
+ if _, err := x.Where("project_id=?", project.ProjectID).
+ Table("project_board").
+ Limit(project.DefaultNum - 1).
+ Update(map[string]bool{
+ "`default`": false,
+ }); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/models/migrations/v1_22/v293_test.go b/models/migrations/v1_22/v293_test.go
new file mode 100644
index 0000000..85bb464
--- /dev/null
+++ b/models/migrations/v1_22/v293_test.go
@@ -0,0 +1,45 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ migration_tests "code.gitea.io/gitea/models/migrations/test"
+ "code.gitea.io/gitea/models/project"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_CheckProjectColumnsConsistency(t *testing.T) {
+ // Prepare and load the testing database
+ x, deferable := migration_tests.PrepareTestEnv(t, 0, new(project.Project), new(project.Column))
+ defer deferable()
+ if x == nil || t.Failed() {
+ return
+ }
+
+ require.NoError(t, CheckProjectColumnsConsistency(x))
+
+ // check if default column was added
+ var defaultColumn project.Column
+ has, err := x.Where("project_id=? AND `default` = ?", 1, true).Get(&defaultColumn)
+ require.NoError(t, err)
+ assert.True(t, has)
+ assert.Equal(t, int64(1), defaultColumn.ProjectID)
+ assert.True(t, defaultColumn.Default)
+
+ // check if multiple defaults, previous were removed and last will be kept
+ expectDefaultColumn, err := project.GetColumn(db.DefaultContext, 2)
+ require.NoError(t, err)
+ assert.Equal(t, int64(2), expectDefaultColumn.ProjectID)
+ assert.False(t, expectDefaultColumn.Default)
+
+ expectNonDefaultColumn, err := project.GetColumn(db.DefaultContext, 3)
+ require.NoError(t, err)
+ assert.Equal(t, int64(2), expectNonDefaultColumn.ProjectID)
+ assert.True(t, expectNonDefaultColumn.Default)
+}
diff --git a/models/migrations/v1_22/v294.go b/models/migrations/v1_22/v294.go
new file mode 100644
index 0000000..314b451
--- /dev/null
+++ b/models/migrations/v1_22/v294.go
@@ -0,0 +1,44 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+// AddUniqueIndexForProjectIssue adds unique indexes for project issue table
+func AddUniqueIndexForProjectIssue(x *xorm.Engine) error {
+ // remove possible duplicated records in table project_issue
+ type result struct {
+ IssueID int64
+ ProjectID int64
+ Cnt int
+ }
+ var results []result
+ if err := x.Select("issue_id, project_id, count(*) as cnt").
+ Table("project_issue").
+ GroupBy("issue_id, project_id").
+ Having("count(*) > 1").
+ Find(&results); err != nil {
+ return err
+ }
+ for _, r := range results {
+ var ids []int64
+ if err := x.SQL("SELECT id FROM project_issue WHERE issue_id = ? and project_id = ? limit ?", r.IssueID, r.ProjectID, r.Cnt-1).Find(&ids); err != nil {
+ return err
+ }
+ if _, err := x.Table("project_issue").In("id", ids).Delete(); err != nil {
+ return err
+ }
+ }
+
+ // add unique index for project_issue table
+ type ProjectIssue struct { //revive:disable-line:exported
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"INDEX unique(s)"`
+ ProjectID int64 `xorm:"INDEX unique(s)"`
+ }
+
+ return x.Sync(new(ProjectIssue))
+}
diff --git a/models/migrations/v1_22/v294_test.go b/models/migrations/v1_22/v294_test.go
new file mode 100644
index 0000000..c465d53
--- /dev/null
+++ b/models/migrations/v1_22/v294_test.go
@@ -0,0 +1,53 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "slices"
+ "testing"
+
+ migration_tests "code.gitea.io/gitea/models/migrations/test"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "xorm.io/xorm/schemas"
+)
+
+func Test_AddUniqueIndexForProjectIssue(t *testing.T) {
+ type ProjectIssue struct { //revive:disable-line:exported
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"INDEX"`
+ ProjectID int64 `xorm:"INDEX"`
+ }
+
+ // Prepare and load the testing database
+ x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ProjectIssue))
+ defer deferable()
+ if x == nil || t.Failed() {
+ return
+ }
+
+ cnt, err := x.Table("project_issue").Where("project_id=1 AND issue_id=1").Count()
+ require.NoError(t, err)
+ assert.EqualValues(t, 2, cnt)
+
+ require.NoError(t, AddUniqueIndexForProjectIssue(x))
+
+ cnt, err = x.Table("project_issue").Where("project_id=1 AND issue_id=1").Count()
+ require.NoError(t, err)
+ assert.EqualValues(t, 1, cnt)
+
+ tables, err := x.DBMetas()
+ require.NoError(t, err)
+ assert.Len(t, tables, 1)
+ found := false
+ for _, index := range tables[0].Indexes {
+ if index.Type == schemas.UniqueType {
+ found = true
+ slices.Equal(index.Cols, []string{"project_id", "issue_id"})
+ break
+ }
+ }
+ assert.True(t, found)
+}
diff --git a/models/migrations/v1_22/v295.go b/models/migrations/v1_22/v295.go
new file mode 100644
index 0000000..17bdadb
--- /dev/null
+++ b/models/migrations/v1_22/v295.go
@@ -0,0 +1,18 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import "xorm.io/xorm"
+
+func AddCommitStatusSummary(x *xorm.Engine) error {
+ type CommitStatusSummary struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"`
+ SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"`
+ State string `xorm:"VARCHAR(7) NOT NULL"`
+ }
+ // there is no migrations because if there is no data on this table, it will fall back to get data
+ // from commit status
+ return x.Sync2(new(CommitStatusSummary))
+}
diff --git a/models/migrations/v1_22/v296.go b/models/migrations/v1_22/v296.go
new file mode 100644
index 0000000..1ecacab
--- /dev/null
+++ b/models/migrations/v1_22/v296.go
@@ -0,0 +1,16 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import "xorm.io/xorm"
+
+func AddCommitStatusSummary2(x *xorm.Engine) error {
+ type CommitStatusSummary struct {
+ ID int64 `xorm:"pk autoincr"`
+ TargetURL string `xorm:"TEXT"`
+ }
+ // there is no migrations because if there is no data on this table, it will fall back to get data
+ // from commit status
+ return x.Sync(new(CommitStatusSummary))
+}
diff --git a/models/migrations/v1_22/v298.go b/models/migrations/v1_22/v298.go
new file mode 100644
index 0000000..b9f3b95
--- /dev/null
+++ b/models/migrations/v1_22/v298.go
@@ -0,0 +1,10 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import "xorm.io/xorm"
+
+func DropWronglyCreatedTable(x *xorm.Engine) error {
+ return x.DropTables("o_auth2_application")
+}