summaryrefslogtreecommitdiffstats
path: root/models/migrations/v1_16
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_16
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_16/main_test.go14
-rw-r--r--models/migrations/v1_16/v189.go111
-rw-r--r--models/migrations/v1_16/v189_test.go83
-rw-r--r--models/migrations/v1_16/v190.go23
-rw-r--r--models/migrations/v1_16/v191.go28
-rw-r--r--models/migrations/v1_16/v192.go19
-rw-r--r--models/migrations/v1_16/v193.go32
-rw-r--r--models/migrations/v1_16/v193_test.go81
-rw-r--r--models/migrations/v1_16/v194.go21
-rw-r--r--models/migrations/v1_16/v195.go46
-rw-r--r--models/migrations/v1_16/v195_test.go64
-rw-r--r--models/migrations/v1_16/v196.go21
-rw-r--r--models/migrations/v1_16/v197.go19
-rw-r--r--models/migrations/v1_16/v198.go32
-rw-r--r--models/migrations/v1_16/v199.go6
-rw-r--r--models/migrations/v1_16/v200.go22
-rw-r--r--models/migrations/v1_16/v201.go14
-rw-r--r--models/migrations/v1_16/v202.go23
-rw-r--r--models/migrations/v1_16/v203.go17
-rw-r--r--models/migrations/v1_16/v204.go14
-rw-r--r--models/migrations/v1_16/v205.go42
-rw-r--r--models/migrations/v1_16/v206.go28
-rw-r--r--models/migrations/v1_16/v207.go14
-rw-r--r--models/migrations/v1_16/v208.go13
-rw-r--r--models/migrations/v1_16/v209.go16
-rw-r--r--models/migrations/v1_16/v210.go177
-rw-r--r--models/migrations/v1_16/v210_test.go88
27 files changed, 1068 insertions, 0 deletions
diff --git a/models/migrations/v1_16/main_test.go b/models/migrations/v1_16/main_test.go
new file mode 100644
index 0000000..4961177
--- /dev/null
+++ b/models/migrations/v1_16/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //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_16/v189.go b/models/migrations/v1_16/v189.go
new file mode 100644
index 0000000..5649645
--- /dev/null
+++ b/models/migrations/v1_16/v189.go
@@ -0,0 +1,111 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "encoding/binary"
+ "fmt"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/json"
+
+ "xorm.io/xorm"
+)
+
+func UnwrapLDAPSourceCfg(x *xorm.Engine) error {
+ jsonUnmarshalHandleDoubleEncode := func(bs []byte, v any) error {
+ err := json.Unmarshal(bs, v)
+ if err != nil {
+ ok := true
+ rs := []byte{}
+ temp := make([]byte, 2)
+ for _, rn := range string(bs) {
+ if rn > 0xffff {
+ ok = false
+ break
+ }
+ binary.LittleEndian.PutUint16(temp, uint16(rn))
+ rs = append(rs, temp...)
+ }
+ if ok {
+ if rs[0] == 0xff && rs[1] == 0xfe {
+ rs = rs[2:]
+ }
+ err = json.Unmarshal(rs, v)
+ }
+ }
+ if err != nil && len(bs) > 2 && bs[0] == 0xff && bs[1] == 0xfe {
+ err = json.Unmarshal(bs[2:], v)
+ }
+ return err
+ }
+
+ // LoginSource represents an external way for authorizing users.
+ type LoginSource struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type int
+ IsActived bool `xorm:"INDEX NOT NULL DEFAULT false"`
+ IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"`
+ Cfg string `xorm:"TEXT"`
+ }
+
+ const ldapType = 2
+ const dldapType = 5
+
+ type WrappedSource struct {
+ Source map[string]any
+ }
+
+ // change lower_email as unique
+ if err := x.Sync(new(LoginSource)); err != nil {
+ return err
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ const batchSize = 100
+ for start := 0; ; start += batchSize {
+ sources := make([]*LoginSource, 0, batchSize)
+ if err := sess.Limit(batchSize, start).Where("`type` = ? OR `type` = ?", ldapType, dldapType).Find(&sources); err != nil {
+ return err
+ }
+ if len(sources) == 0 {
+ break
+ }
+
+ for _, source := range sources {
+ wrapped := &WrappedSource{
+ Source: map[string]any{},
+ }
+ err := jsonUnmarshalHandleDoubleEncode([]byte(source.Cfg), &wrapped)
+ if err != nil {
+ return fmt.Errorf("failed to unmarshal %s: %w", source.Cfg, err)
+ }
+ if len(wrapped.Source) > 0 {
+ bs, err := json.Marshal(wrapped.Source)
+ if err != nil {
+ return err
+ }
+ source.Cfg = string(bs)
+ if _, err := sess.ID(source.ID).Cols("cfg").Update(source); err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ if _, err := x.SetExpr("is_active", "is_actived").Update(&LoginSource{}); err != nil {
+ return fmt.Errorf("SetExpr Update failed: %w", err)
+ }
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ if err := base.DropTableColumns(sess, "login_source", "is_actived"); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_16/v189_test.go b/models/migrations/v1_16/v189_test.go
new file mode 100644
index 0000000..88c6ebd
--- /dev/null
+++ b/models/migrations/v1_16/v189_test.go
@@ -0,0 +1,83 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "testing"
+
+ migration_tests "code.gitea.io/gitea/models/migrations/test"
+ "code.gitea.io/gitea/modules/json"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// LoginSource represents an external way for authorizing users.
+type LoginSourceOriginalV189 struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type int
+ IsActived bool `xorm:"INDEX NOT NULL DEFAULT false"`
+ Cfg string `xorm:"TEXT"`
+ Expected string `xorm:"TEXT"`
+}
+
+func (ls *LoginSourceOriginalV189) TableName() string {
+ return "login_source"
+}
+
+func Test_UnwrapLDAPSourceCfg(t *testing.T) {
+ // Prepare and load the testing database
+ x, deferable := migration_tests.PrepareTestEnv(t, 0, new(LoginSourceOriginalV189))
+ if x == nil || t.Failed() {
+ defer deferable()
+ return
+ }
+ defer deferable()
+
+ // LoginSource represents an external way for authorizing users.
+ type LoginSource struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type int
+ IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"`
+ Cfg string `xorm:"TEXT"`
+ Expected string `xorm:"TEXT"`
+ }
+
+ // Run the migration
+ if err := UnwrapLDAPSourceCfg(x); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ const batchSize = 100
+ for start := 0; ; start += batchSize {
+ sources := make([]*LoginSource, 0, batchSize)
+ if err := x.Table("login_source").Limit(batchSize, start).Find(&sources); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ if len(sources) == 0 {
+ break
+ }
+
+ for _, source := range sources {
+ converted := map[string]any{}
+ expected := map[string]any{}
+
+ if err := json.Unmarshal([]byte(source.Cfg), &converted); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ if err := json.Unmarshal([]byte(source.Expected), &expected); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ assert.EqualValues(t, expected, converted, "UnwrapLDAPSourceCfg failed for %d", source.ID)
+ assert.EqualValues(t, source.ID%2 == 0, source.IsActive, "UnwrapLDAPSourceCfg failed for %d", source.ID)
+ }
+ }
+}
diff --git a/models/migrations/v1_16/v190.go b/models/migrations/v1_16/v190.go
new file mode 100644
index 0000000..5953802
--- /dev/null
+++ b/models/migrations/v1_16/v190.go
@@ -0,0 +1,23 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddAgitFlowPullRequest(x *xorm.Engine) error {
+ type PullRequestFlow int
+
+ type PullRequest struct {
+ Flow PullRequestFlow `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ if err := x.Sync(new(PullRequest)); err != nil {
+ return fmt.Errorf("sync2: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_16/v191.go b/models/migrations/v1_16/v191.go
new file mode 100644
index 0000000..c618783
--- /dev/null
+++ b/models/migrations/v1_16/v191.go
@@ -0,0 +1,28 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func AlterIssueAndCommentTextFieldsToLongText(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if setting.Database.Type.IsMySQL() {
+ if _, err := sess.Exec("ALTER TABLE `issue` CHANGE `content` `content` LONGTEXT"); err != nil {
+ return err
+ }
+ if _, err := sess.Exec("ALTER TABLE `comment` CHANGE `content` `content` LONGTEXT, CHANGE `patch` `patch` LONGTEXT"); err != nil {
+ return err
+ }
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_16/v192.go b/models/migrations/v1_16/v192.go
new file mode 100644
index 0000000..2d5d158
--- /dev/null
+++ b/models/migrations/v1_16/v192.go
@@ -0,0 +1,19 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "xorm.io/xorm"
+)
+
+func RecreateIssueResourceIndexTable(x *xorm.Engine) error {
+ type IssueIndex struct {
+ GroupID int64 `xorm:"pk"`
+ MaxIndex int64 `xorm:"index"`
+ }
+
+ return base.RecreateTables(new(IssueIndex))(x)
+}
diff --git a/models/migrations/v1_16/v193.go b/models/migrations/v1_16/v193.go
new file mode 100644
index 0000000..8d3ce7a
--- /dev/null
+++ b/models/migrations/v1_16/v193.go
@@ -0,0 +1,32 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddRepoIDForAttachment(x *xorm.Engine) error {
+ type Attachment struct {
+ ID int64 `xorm:"pk autoincr"`
+ UUID string `xorm:"uuid UNIQUE"`
+ RepoID int64 `xorm:"INDEX"` // this should not be zero
+ IssueID int64 `xorm:"INDEX"` // maybe zero when creating
+ ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
+ UploaderID int64 `xorm:"INDEX DEFAULT 0"`
+ }
+ if err := x.Sync(new(Attachment)); err != nil {
+ return err
+ }
+
+ if _, err := x.Exec("UPDATE `attachment` set repo_id = (SELECT repo_id FROM `issue` WHERE `issue`.id = `attachment`.issue_id) WHERE `attachment`.issue_id > 0"); err != nil {
+ return err
+ }
+
+ if _, err := x.Exec("UPDATE `attachment` set repo_id = (SELECT repo_id FROM `release` WHERE `release`.id = `attachment`.release_id) WHERE `attachment`.release_id > 0"); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/models/migrations/v1_16/v193_test.go b/models/migrations/v1_16/v193_test.go
new file mode 100644
index 0000000..0da6708
--- /dev/null
+++ b/models/migrations/v1_16/v193_test.go
@@ -0,0 +1,81 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "testing"
+
+ migration_tests "code.gitea.io/gitea/models/migrations/test"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_AddRepoIDForAttachment(t *testing.T) {
+ type Attachment struct {
+ ID int64 `xorm:"pk autoincr"`
+ UUID string `xorm:"uuid UNIQUE"`
+ IssueID int64 `xorm:"INDEX"` // maybe zero when creating
+ ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
+ UploaderID int64 `xorm:"INDEX DEFAULT 0"`
+ }
+
+ type Issue struct {
+ ID int64
+ RepoID int64
+ }
+
+ type Release struct {
+ ID int64
+ RepoID int64
+ }
+
+ // Prepare and load the testing database
+ x, deferrable := migration_tests.PrepareTestEnv(t, 0, new(Attachment), new(Issue), new(Release))
+ defer deferrable()
+ if x == nil || t.Failed() {
+ return
+ }
+
+ // Run the migration
+ if err := AddRepoIDForAttachment(x); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ type NewAttachment struct {
+ ID int64 `xorm:"pk autoincr"`
+ UUID string `xorm:"uuid UNIQUE"`
+ RepoID int64 `xorm:"INDEX"` // this should not be zero
+ IssueID int64 `xorm:"INDEX"` // maybe zero when creating
+ ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
+ UploaderID int64 `xorm:"INDEX DEFAULT 0"`
+ }
+
+ var issueAttachments []*NewAttachment
+ err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments)
+ require.NoError(t, err)
+ for _, attach := range issueAttachments {
+ assert.Positive(t, attach.RepoID)
+ assert.Positive(t, attach.IssueID)
+ var issue Issue
+ has, err := x.ID(attach.IssueID).Get(&issue)
+ require.NoError(t, err)
+ assert.True(t, has)
+ assert.EqualValues(t, attach.RepoID, issue.RepoID)
+ }
+
+ var releaseAttachments []*NewAttachment
+ err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments)
+ require.NoError(t, err)
+ for _, attach := range releaseAttachments {
+ assert.Positive(t, attach.RepoID)
+ assert.Positive(t, attach.ReleaseID)
+ var release Release
+ has, err := x.ID(attach.ReleaseID).Get(&release)
+ require.NoError(t, err)
+ assert.True(t, has)
+ assert.EqualValues(t, attach.RepoID, release.RepoID)
+ }
+}
diff --git a/models/migrations/v1_16/v194.go b/models/migrations/v1_16/v194.go
new file mode 100644
index 0000000..6aa13c5
--- /dev/null
+++ b/models/migrations/v1_16/v194.go
@@ -0,0 +1,21 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddBranchProtectionUnprotectedFilesColumn(x *xorm.Engine) error {
+ type ProtectedBranch struct {
+ UnprotectedFilePatterns string `xorm:"TEXT"`
+ }
+
+ if err := x.Sync(new(ProtectedBranch)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_16/v195.go b/models/migrations/v1_16/v195.go
new file mode 100644
index 0000000..6d7e941
--- /dev/null
+++ b/models/migrations/v1_16/v195.go
@@ -0,0 +1,46 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddTableCommitStatusIndex(x *xorm.Engine) error {
+ // CommitStatusIndex represents a table for commit status index
+ type CommitStatusIndex struct {
+ ID int64
+ RepoID int64 `xorm:"unique(repo_sha)"`
+ SHA string `xorm:"unique(repo_sha)"`
+ MaxIndex int64 `xorm:"index"`
+ }
+
+ if err := x.Sync(new(CommitStatusIndex)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ // Remove data we're goint to rebuild
+ if _, err := sess.Table("commit_status_index").Where("1=1").Delete(&CommitStatusIndex{}); err != nil {
+ return err
+ }
+
+ // Create current data for all repositories with issues and PRs
+ if _, err := sess.Exec("INSERT INTO commit_status_index (repo_id, sha, max_index) " +
+ "SELECT max_data.repo_id, max_data.sha, max_data.max_index " +
+ "FROM ( SELECT commit_status.repo_id AS repo_id, commit_status.sha AS sha, max(commit_status.`index`) AS max_index " +
+ "FROM commit_status GROUP BY commit_status.repo_id, commit_status.sha) AS max_data"); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_16/v195_test.go b/models/migrations/v1_16/v195_test.go
new file mode 100644
index 0000000..9a62fc9
--- /dev/null
+++ b/models/migrations/v1_16/v195_test.go
@@ -0,0 +1,64 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "testing"
+
+ migration_tests "code.gitea.io/gitea/models/migrations/test"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_AddTableCommitStatusIndex(t *testing.T) {
+ // Create the models used in the migration
+ type CommitStatus struct {
+ ID int64 `xorm:"pk autoincr"`
+ Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
+ RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
+ SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
+ }
+
+ // Prepare and load the testing database
+ x, deferable := migration_tests.PrepareTestEnv(t, 0, new(CommitStatus))
+ if x == nil || t.Failed() {
+ defer deferable()
+ return
+ }
+ defer deferable()
+
+ // Run the migration
+ if err := AddTableCommitStatusIndex(x); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ type CommitStatusIndex struct {
+ ID int64
+ RepoID int64 `xorm:"unique(repo_sha)"`
+ SHA string `xorm:"unique(repo_sha)"`
+ MaxIndex int64 `xorm:"index"`
+ }
+
+ start := 0
+ const batchSize = 1000
+ for {
+ indexes := make([]CommitStatusIndex, 0, batchSize)
+ err := x.Table("commit_status_index").Limit(batchSize, start).Find(&indexes)
+ require.NoError(t, err)
+
+ for _, idx := range indexes {
+ var maxIndex int
+ has, err := x.SQL("SELECT max(`index`) FROM commit_status WHERE repo_id = ? AND sha = ?", idx.RepoID, idx.SHA).Get(&maxIndex)
+ require.NoError(t, err)
+ assert.True(t, has)
+ assert.EqualValues(t, maxIndex, idx.MaxIndex)
+ }
+ if len(indexes) < batchSize {
+ break
+ }
+ start += len(indexes)
+ }
+}
diff --git a/models/migrations/v1_16/v196.go b/models/migrations/v1_16/v196.go
new file mode 100644
index 0000000..7cbafc6
--- /dev/null
+++ b/models/migrations/v1_16/v196.go
@@ -0,0 +1,21 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddColorColToProjectBoard(x *xorm.Engine) error {
+ type ProjectBoard struct {
+ Color string `xorm:"VARCHAR(7)"`
+ }
+
+ if err := x.Sync(new(ProjectBoard)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_16/v197.go b/models/migrations/v1_16/v197.go
new file mode 100644
index 0000000..97888b2
--- /dev/null
+++ b/models/migrations/v1_16/v197.go
@@ -0,0 +1,19 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddRenamedBranchTable(x *xorm.Engine) error {
+ type RenamedBranch struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX NOT NULL"`
+ From string
+ To string
+ CreatedUnix int64 `xorm:"created"`
+ }
+ return x.Sync(new(RenamedBranch))
+}
diff --git a/models/migrations/v1_16/v198.go b/models/migrations/v1_16/v198.go
new file mode 100644
index 0000000..115bb31
--- /dev/null
+++ b/models/migrations/v1_16/v198.go
@@ -0,0 +1,32 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddTableIssueContentHistory(x *xorm.Engine) error {
+ type IssueContentHistory struct {
+ ID int64 `xorm:"pk autoincr"`
+ PosterID int64
+ IssueID int64 `xorm:"INDEX"`
+ CommentID int64 `xorm:"INDEX"`
+ EditedUnix timeutil.TimeStamp `xorm:"INDEX"`
+ ContentText string `xorm:"LONGTEXT"`
+ IsFirstCreated bool
+ IsDeleted bool
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Sync(new(IssueContentHistory)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_16/v199.go b/models/migrations/v1_16/v199.go
new file mode 100644
index 0000000..6adcf89
--- /dev/null
+++ b/models/migrations/v1_16/v199.go
@@ -0,0 +1,6 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+// We used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now.
diff --git a/models/migrations/v1_16/v200.go b/models/migrations/v1_16/v200.go
new file mode 100644
index 0000000..c08c20e
--- /dev/null
+++ b/models/migrations/v1_16/v200.go
@@ -0,0 +1,22 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddTableAppState(x *xorm.Engine) error {
+ type AppState struct {
+ ID string `xorm:"pk varchar(200)"`
+ Revision int64
+ Content string `xorm:"LONGTEXT"`
+ }
+ if err := x.Sync(new(AppState)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_16/v201.go b/models/migrations/v1_16/v201.go
new file mode 100644
index 0000000..35e0c9f
--- /dev/null
+++ b/models/migrations/v1_16/v201.go
@@ -0,0 +1,14 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func DropTableRemoteVersion(x *xorm.Engine) error {
+ // drop the orphaned table introduced in `v199`, now the update checker also uses AppState, do not need this table
+ _ = x.DropTables("remote_version")
+ return nil
+}
diff --git a/models/migrations/v1_16/v202.go b/models/migrations/v1_16/v202.go
new file mode 100644
index 0000000..6ba3615
--- /dev/null
+++ b/models/migrations/v1_16/v202.go
@@ -0,0 +1,23 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func CreateUserSettingsTable(x *xorm.Engine) error {
+ type UserSetting struct {
+ ID int64 `xorm:"pk autoincr"`
+ UserID int64 `xorm:"index unique(key_userid)"` // to load all of someone's settings
+ SettingKey string `xorm:"varchar(255) index unique(key_userid)"` // ensure key is always lowercase
+ SettingValue string `xorm:"text"`
+ }
+ if err := x.Sync(new(UserSetting)); err != nil {
+ return fmt.Errorf("sync2: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_16/v203.go b/models/migrations/v1_16/v203.go
new file mode 100644
index 0000000..e8e6b52
--- /dev/null
+++ b/models/migrations/v1_16/v203.go
@@ -0,0 +1,17 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddProjectIssueSorting(x *xorm.Engine) error {
+ // ProjectIssue saves relation from issue to a project
+ type ProjectIssue struct {
+ Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ return x.Sync(new(ProjectIssue))
+}
diff --git a/models/migrations/v1_16/v204.go b/models/migrations/v1_16/v204.go
new file mode 100644
index 0000000..ece03e1
--- /dev/null
+++ b/models/migrations/v1_16/v204.go
@@ -0,0 +1,14 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import "xorm.io/xorm"
+
+func AddSSHKeyIsVerified(x *xorm.Engine) error {
+ type PublicKey struct {
+ Verified bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(PublicKey))
+}
diff --git a/models/migrations/v1_16/v205.go b/models/migrations/v1_16/v205.go
new file mode 100644
index 0000000..d6c5770
--- /dev/null
+++ b/models/migrations/v1_16/v205.go
@@ -0,0 +1,42 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+func MigrateUserPasswordSalt(x *xorm.Engine) error {
+ dbType := x.Dialect().URI().DBType
+ // For SQLITE, the max length doesn't matter.
+ if dbType == schemas.SQLITE {
+ return nil
+ }
+
+ if err := base.ModifyColumn(x, "user", &schemas.Column{
+ Name: "rands",
+ SQLType: schemas.SQLType{
+ Name: "VARCHAR",
+ },
+ Length: 32,
+ // MySQL will like us again.
+ Nullable: true,
+ DefaultIsEmpty: true,
+ }); err != nil {
+ return err
+ }
+
+ return base.ModifyColumn(x, "user", &schemas.Column{
+ Name: "salt",
+ SQLType: schemas.SQLType{
+ Name: "VARCHAR",
+ },
+ Length: 32,
+ Nullable: true,
+ DefaultIsEmpty: true,
+ })
+}
diff --git a/models/migrations/v1_16/v206.go b/models/migrations/v1_16/v206.go
new file mode 100644
index 0000000..581a7d7
--- /dev/null
+++ b/models/migrations/v1_16/v206.go
@@ -0,0 +1,28 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddAuthorizeColForTeamUnit(x *xorm.Engine) error {
+ type TeamUnit struct {
+ ID int64 `xorm:"pk autoincr"`
+ OrgID int64 `xorm:"INDEX"`
+ TeamID int64 `xorm:"UNIQUE(s)"`
+ Type int `xorm:"UNIQUE(s)"`
+ AccessMode int
+ }
+
+ if err := x.Sync(new(TeamUnit)); err != nil {
+ return fmt.Errorf("sync2: %w", err)
+ }
+
+ // migrate old permission
+ _, err := x.Exec("UPDATE team_unit SET access_mode = (SELECT authorize FROM team WHERE team.id = team_unit.team_id)")
+ return err
+}
diff --git a/models/migrations/v1_16/v207.go b/models/migrations/v1_16/v207.go
new file mode 100644
index 0000000..91208f0
--- /dev/null
+++ b/models/migrations/v1_16/v207.go
@@ -0,0 +1,14 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddWebAuthnCred(x *xorm.Engine) error {
+ // NO-OP Don't migrate here - let v210 do this.
+
+ return nil
+}
diff --git a/models/migrations/v1_16/v208.go b/models/migrations/v1_16/v208.go
new file mode 100644
index 0000000..1a11ef0
--- /dev/null
+++ b/models/migrations/v1_16/v208.go
@@ -0,0 +1,13 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func UseBase32HexForCredIDInWebAuthnCredential(x *xorm.Engine) error {
+ // noop
+ return nil
+}
diff --git a/models/migrations/v1_16/v209.go b/models/migrations/v1_16/v209.go
new file mode 100644
index 0000000..be3100e
--- /dev/null
+++ b/models/migrations/v1_16/v209.go
@@ -0,0 +1,16 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func IncreaseCredentialIDTo410(x *xorm.Engine) error {
+ // no-op
+ // v208 was completely wrong
+ // So now we have to no-op again.
+
+ return nil
+}
diff --git a/models/migrations/v1_16/v210.go b/models/migrations/v1_16/v210.go
new file mode 100644
index 0000000..db45b11
--- /dev/null
+++ b/models/migrations/v1_16/v210.go
@@ -0,0 +1,177 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "crypto/ecdh"
+ "encoding/base32"
+ "errors"
+ "fmt"
+ "strings"
+
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+func parseU2FRegistration(raw []byte) (pubKey *ecdh.PublicKey, keyHandle []byte, err error) {
+ if len(raw) < 69 {
+ return nil, nil, errors.New("data is too short")
+ }
+ if raw[0] != 0x05 {
+ return nil, nil, errors.New("invalid reserved byte")
+ }
+ raw = raw[1:]
+
+ pubKey, err = ecdh.P256().NewPublicKey(raw[:65])
+ if err != nil {
+ return nil, nil, err
+ }
+ raw = raw[65:]
+
+ khLen := int(raw[0])
+ if len(raw) < khLen {
+ return nil, nil, errors.New("invalid key handle")
+ }
+ raw = raw[1:]
+ keyHandle = raw[:khLen]
+
+ return pubKey, keyHandle, nil
+}
+
+// v208 migration was completely broken
+func RemigrateU2FCredentials(x *xorm.Engine) error {
+ // Create webauthnCredential table
+ type webauthnCredential struct {
+ ID int64 `xorm:"pk autoincr"`
+ Name string
+ LowerName string `xorm:"unique(s)"`
+ UserID int64 `xorm:"INDEX unique(s)"`
+ CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
+ PublicKey []byte
+ AttestationType string
+ AAGUID []byte
+ SignCount uint32 `xorm:"BIGINT"`
+ CloneWarning bool
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+ if err := x.Sync(&webauthnCredential{}); err != nil {
+ return err
+ }
+
+ switch x.Dialect().URI().DBType {
+ case schemas.MYSQL:
+ _, err := x.Exec("ALTER TABLE webauthn_credential MODIFY COLUMN credential_id VARCHAR(410)")
+ if err != nil {
+ return err
+ }
+ case schemas.POSTGRES:
+ _, err := x.Exec("ALTER TABLE webauthn_credential ALTER COLUMN credential_id TYPE VARCHAR(410)")
+ if err != nil {
+ return err
+ }
+ default:
+ // SQLite doesn't support ALTER COLUMN, and it already makes String _TEXT_ by default so no migration needed
+ // nor is there any need to re-migrate
+ }
+
+ exist, err := x.IsTableExist("u2f_registration")
+ if err != nil {
+ return err
+ }
+ if !exist {
+ return nil
+ }
+
+ // Now migrate the old u2f registrations to the new format
+ type u2fRegistration struct {
+ ID int64 `xorm:"pk autoincr"`
+ Name string
+ UserID int64 `xorm:"INDEX"`
+ Raw []byte
+ Counter uint32 `xorm:"BIGINT"`
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ var start int
+ regs := make([]*u2fRegistration, 0, 50)
+ for {
+ err := x.OrderBy("id").Limit(50, start).Find(&regs)
+ if err != nil {
+ return err
+ }
+
+ err = func() error {
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return fmt.Errorf("unable to allow start session. Error: %w", err)
+ }
+ for _, reg := range regs {
+ pubKey, keyHandle, err := parseU2FRegistration(reg.Raw)
+ if err != nil {
+ continue
+ }
+ remigrated := &webauthnCredential{
+ ID: reg.ID,
+ Name: reg.Name,
+ LowerName: strings.ToLower(reg.Name),
+ UserID: reg.UserID,
+ CredentialID: base32.HexEncoding.EncodeToString(keyHandle),
+ PublicKey: pubKey.Bytes(),
+ AttestationType: "fido-u2f",
+ AAGUID: []byte{},
+ SignCount: reg.Counter,
+ UpdatedUnix: reg.UpdatedUnix,
+ CreatedUnix: reg.CreatedUnix,
+ }
+
+ has, err := sess.ID(reg.ID).Get(new(webauthnCredential))
+ if err != nil {
+ return fmt.Errorf("unable to get webauthn_credential[%d]. Error: %w", reg.ID, err)
+ }
+ if !has {
+ has, err := sess.Where("`lower_name`=?", remigrated.LowerName).And("`user_id`=?", remigrated.UserID).Exist(new(webauthnCredential))
+ if err != nil {
+ return fmt.Errorf("unable to check webauthn_credential[lower_name: %s, user_id: %d]. Error: %w", remigrated.LowerName, remigrated.UserID, err)
+ }
+ if !has {
+ _, err = sess.Insert(remigrated)
+ if err != nil {
+ return fmt.Errorf("unable to (re)insert webauthn_credential[%d]. Error: %w", reg.ID, err)
+ }
+
+ continue
+ }
+ }
+
+ _, err = sess.ID(remigrated.ID).AllCols().Update(remigrated)
+ if err != nil {
+ return fmt.Errorf("unable to update webauthn_credential[%d]. Error: %w", reg.ID, err)
+ }
+ }
+ return sess.Commit()
+ }()
+ if err != nil {
+ return err
+ }
+
+ if len(regs) < 50 {
+ break
+ }
+ start += 50
+ regs = regs[:0]
+ }
+
+ if x.Dialect().URI().DBType == schemas.POSTGRES {
+ if _, err := x.Exec("SELECT setval('webauthn_credential_id_seq', COALESCE((SELECT MAX(id)+1 FROM `webauthn_credential`), 1), false)"); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/models/migrations/v1_16/v210_test.go b/models/migrations/v1_16/v210_test.go
new file mode 100644
index 0000000..7321350
--- /dev/null
+++ b/models/migrations/v1_16/v210_test.go
@@ -0,0 +1,88 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_16 //nolint
+
+import (
+ "encoding/hex"
+ "testing"
+
+ migration_tests "code.gitea.io/gitea/models/migrations/test"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "xorm.io/xorm/schemas"
+)
+
+func TestParseU2FRegistration(t *testing.T) {
+ // test vectors from https://github.com/tstranex/u2f/blob/d21a03e0b1d9fc1df59ff54e7a513655c1748b0c/register_test.go#L15
+
+ const testRegRespHex = "0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871"
+
+ regResp, err := hex.DecodeString(testRegRespHex)
+ require.NoError(t, err)
+ pubKey, keyHandle, err := parseU2FRegistration(regResp)
+ require.NoError(t, err)
+ assert.Equal(t, "04b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9", hex.EncodeToString(pubKey.Bytes()))
+ assert.Equal(t, "2a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c25", hex.EncodeToString(keyHandle))
+}
+
+func Test_RemigrateU2FCredentials(t *testing.T) {
+ // Create webauthnCredential table
+ type WebauthnCredential struct {
+ ID int64 `xorm:"pk autoincr"`
+ Name string
+ LowerName string `xorm:"unique(s)"`
+ UserID int64 `xorm:"INDEX unique(s)"`
+ CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
+ PublicKey []byte
+ AttestationType string
+ SignCount uint32 `xorm:"BIGINT"`
+ CloneWarning bool
+ }
+
+ // Now migrate the old u2f registrations to the new format
+ type U2fRegistration struct {
+ ID int64 `xorm:"pk autoincr"`
+ Name string
+ UserID int64 `xorm:"INDEX"`
+ Raw []byte
+ Counter uint32 `xorm:"BIGINT"`
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ type ExpectedWebauthnCredential struct {
+ ID int64 `xorm:"pk autoincr"`
+ CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
+ }
+
+ // Prepare and load the testing database
+ x, deferable := migration_tests.PrepareTestEnv(t, 0, new(WebauthnCredential), new(U2fRegistration), new(ExpectedWebauthnCredential))
+ if x == nil || t.Failed() {
+ defer deferable()
+ return
+ }
+ defer deferable()
+
+ if x.Dialect().URI().DBType == schemas.SQLITE {
+ return
+ }
+
+ // Run the migration
+ if err := RemigrateU2FCredentials(x); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ expected := []ExpectedWebauthnCredential{}
+ err := x.Table("expected_webauthn_credential").Asc("id").Find(&expected)
+ require.NoError(t, err)
+
+ got := []ExpectedWebauthnCredential{}
+ err = x.Table("webauthn_credential").Select("id, credential_id").Asc("id").Find(&got)
+ require.NoError(t, err)
+
+ assert.EqualValues(t, expected, got)
+}