summaryrefslogtreecommitdiffstats
path: root/models/migrations
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
parentInitial commit. (diff)
downloadforgejo-debian.tar.xz
forgejo-debian.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')
-rw-r--r--models/migrations/base/db.go540
-rw-r--r--models/migrations/base/db_test.go97
-rw-r--r--models/migrations/base/hash.go16
-rw-r--r--models/migrations/base/main_test.go12
-rw-r--r--models/migrations/base/tests.go173
-rw-r--r--models/migrations/fixtures/Test_AddCombinedIndexToIssueUser/issue_user.yml13
-rw-r--r--models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml2
-rw-r--r--models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml9
-rw-r--r--models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/hook_task.yml8
-rw-r--r--models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/webhook.yml10
-rw-r--r--models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml4
-rw-r--r--models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml16
-rw-r--r--models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml18
-rw-r--r--models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml11
-rw-r--r--models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml3
-rw-r--r--models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml3
-rw-r--r--models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml9
-rw-r--r--models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml23
-rw-r--r--models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml26
-rw-r--r--models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml28
-rw-r--r--models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml43
-rw-r--r--models/migrations/fixtures/Test_RemigrateU2FCredentials/expected_webauthn_credential.yml12
-rw-r--r--models/migrations/fixtures/Test_RemigrateU2FCredentials/u2f_registration.yml21
-rw-r--r--models/migrations/fixtures/Test_RemigrateU2FCredentials/webauthn_credential.yml30
-rw-r--r--models/migrations/fixtures/Test_RemoveInvalidLabels/comment.yml52
-rw-r--r--models/migrations/fixtures/Test_RemoveInvalidLabels/issue.yml21
-rw-r--r--models/migrations/fixtures/Test_RemoveInvalidLabels/issue_label.yml35
-rw-r--r--models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml25
-rw-r--r--models/migrations/fixtures/Test_RemoveInvalidLabels/repository.yml17
-rw-r--r--models/migrations/fixtures/Test_RemoveSSHSignaturesFromReleaseNotes/release.yml22
-rw-r--r--models/migrations/fixtures/Test_RepositoryFormat/comment.yml3
-rw-r--r--models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml3
-rw-r--r--models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml5
-rw-r--r--models/migrations/fixtures/Test_RepositoryFormat/release.yml3
-rw-r--r--models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml3
-rw-r--r--models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml3
-rw-r--r--models/migrations/fixtures/Test_RepositoryFormat/repository.yml11
-rw-r--r--models/migrations/fixtures/Test_RepositoryFormat/review_state.yml5
-rw-r--r--models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml9
-rw-r--r--models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml30
-rw-r--r--models/migrations/fixtures/Test_UnwrapLDAPSourceCfg/login_source.yml48
-rw-r--r--models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml4
-rw-r--r--models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/expected_milestone.yml19
-rw-r--r--models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/issue.yml25
-rw-r--r--models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/milestone.yml19
-rw-r--r--models/migrations/migrations.go715
-rw-r--r--models/migrations/v1_10/v100.go82
-rw-r--r--models/migrations/v1_10/v101.go18
-rw-r--r--models/migrations/v1_10/v88.go65
-rw-r--r--models/migrations/v1_10/v89.go35
-rw-r--r--models/migrations/v1_10/v90.go17
-rw-r--r--models/migrations/v1_10/v91.go25
-rw-r--r--models/migrations/v1_10/v92.go14
-rw-r--r--models/migrations/v1_10/v93.go15
-rw-r--r--models/migrations/v1_10/v94.go23
-rw-r--r--models/migrations/v1_10/v95.go19
-rw-r--r--models/migrations/v1_10/v96.go64
-rw-r--r--models/migrations/v1_10/v97.go14
-rw-r--r--models/migrations/v1_10/v98.go16
-rw-r--r--models/migrations/v1_10/v99.go38
-rw-r--r--models/migrations/v1_11/v102.go22
-rw-r--r--models/migrations/v1_11/v103.go17
-rw-r--r--models/migrations/v1_11/v104.go34
-rw-r--r--models/migrations/v1_11/v105.go23
-rw-r--r--models/migrations/v1_11/v106.go25
-rw-r--r--models/migrations/v1_11/v107.go17
-rw-r--r--models/migrations/v1_11/v108.go17
-rw-r--r--models/migrations/v1_11/v109.go16
-rw-r--r--models/migrations/v1_11/v110.go26
-rw-r--r--models/migrations/v1_11/v111.go437
-rw-r--r--models/migrations/v1_11/v112.go47
-rw-r--r--models/migrations/v1_11/v113.go22
-rw-r--r--models/migrations/v1_11/v114.go50
-rw-r--r--models/migrations/v1_11/v115.go159
-rw-r--r--models/migrations/v1_11/v116.go32
-rw-r--r--models/migrations/v1_12/v117.go16
-rw-r--r--models/migrations/v1_12/v118.go25
-rw-r--r--models/migrations/v1_12/v119.go15
-rw-r--r--models/migrations/v1_12/v120.go19
-rw-r--r--models/migrations/v1_12/v121.go16
-rw-r--r--models/migrations/v1_12/v122.go16
-rw-r--r--models/migrations/v1_12/v123.go17
-rw-r--r--models/migrations/v1_12/v124.go23
-rw-r--r--models/migrations/v1_12/v125.go22
-rw-r--r--models/migrations/v1_12/v126.go24
-rw-r--r--models/migrations/v1_12/v127.go44
-rw-r--r--models/migrations/v1_12/v128.go127
-rw-r--r--models/migrations/v1_12/v129.go16
-rw-r--r--models/migrations/v1_12/v130.go111
-rw-r--r--models/migrations/v1_12/v131.go21
-rw-r--r--models/migrations/v1_12/v132.go21
-rw-r--r--models/migrations/v1_12/v133.go15
-rw-r--r--models/migrations/v1_12/v134.go115
-rw-r--r--models/migrations/v1_12/v135.go21
-rw-r--r--models/migrations/v1_12/v136.go125
-rw-r--r--models/migrations/v1_12/v137.go15
-rw-r--r--models/migrations/v1_12/v138.go21
-rw-r--r--models/migrations/v1_12/v139.go23
-rw-r--r--models/migrations/v1_13/v140.go56
-rw-r--r--models/migrations/v1_13/v141.go21
-rw-r--r--models/migrations/v1_13/v142.go24
-rw-r--r--models/migrations/v1_13/v143.go51
-rw-r--r--models/migrations/v1_13/v144.go25
-rw-r--r--models/migrations/v1_13/v145.go55
-rw-r--r--models/migrations/v1_13/v146.go83
-rw-r--r--models/migrations/v1_13/v147.go153
-rw-r--r--models/migrations/v1_13/v148.go13
-rw-r--r--models/migrations/v1_13/v149.go24
-rw-r--r--models/migrations/v1_13/v150.go39
-rw-r--r--models/migrations/v1_13/v151.go166
-rw-r--r--models/migrations/v1_13/v152.go13
-rw-r--r--models/migrations/v1_13/v153.go24
-rw-r--r--models/migrations/v1_13/v154.go55
-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
-rw-r--r--models/migrations/v1_15/main_test.go14
-rw-r--r--models/migrations/v1_15/v178.go17
-rw-r--r--models/migrations/v1_15/v179.go28
-rw-r--r--models/migrations/v1_15/v180.go121
-rw-r--r--models/migrations/v1_15/v181.go91
-rw-r--r--models/migrations/v1_15/v181_test.go56
-rw-r--r--models/migrations/v1_15/v182.go41
-rw-r--r--models/migrations/v1_15/v182_test.go61
-rw-r--r--models/migrations/v1_15/v183.go38
-rw-r--r--models/migrations/v1_15/v184.go66
-rw-r--r--models/migrations/v1_15/v185.go21
-rw-r--r--models/migrations/v1_15/v186.go25
-rw-r--r--models/migrations/v1_15/v187.go47
-rw-r--r--models/migrations/v1_15/v188.go14
-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
-rw-r--r--models/migrations/v1_17/main_test.go14
-rw-r--r--models/migrations/v1_17/v211.go12
-rw-r--r--models/migrations/v1_17/v212.go93
-rw-r--r--models/migrations/v1_17/v213.go17
-rw-r--r--models/migrations/v1_17/v214.go22
-rw-r--r--models/migrations/v1_17/v215.go24
-rw-r--r--models/migrations/v1_17/v216.go7
-rw-r--r--models/migrations/v1_17/v217.go25
-rw-r--r--models/migrations/v1_17/v218.go52
-rw-r--r--models/migrations/v1_17/v219.go30
-rw-r--r--models/migrations/v1_17/v220.go23
-rw-r--r--models/migrations/v1_17/v221.go74
-rw-r--r--models/migrations/v1_17/v221_test.go63
-rw-r--r--models/migrations/v1_17/v222.go64
-rw-r--r--models/migrations/v1_17/v223.go98
-rw-r--r--models/migrations/v1_18/main_test.go14
-rw-r--r--models/migrations/v1_18/v224.go27
-rw-r--r--models/migrations/v1_18/v225.go28
-rw-r--r--models/migrations/v1_18/v226.go14
-rw-r--r--models/migrations/v1_18/v227.go23
-rw-r--r--models/migrations/v1_18/v228.go25
-rw-r--r--models/migrations/v1_18/v229.go46
-rw-r--r--models/migrations/v1_18/v229_test.go45
-rw-r--r--models/migrations/v1_18/v230.go17
-rw-r--r--models/migrations/v1_18/v230_test.go47
-rw-r--r--models/migrations/v1_19/main_test.go14
-rw-r--r--models/migrations/v1_19/v231.go18
-rw-r--r--models/migrations/v1_19/v232.go25
-rw-r--r--models/migrations/v1_19/v233.go181
-rw-r--r--models/migrations/v1_19/v233_test.go86
-rw-r--r--models/migrations/v1_19/v234.go28
-rw-r--r--models/migrations/v1_19/v235.go16
-rw-r--r--models/migrations/v1_19/v236.go23
-rw-r--r--models/migrations/v1_19/v237.go15
-rw-r--r--models/migrations/v1_19/v238.go27
-rw-r--r--models/migrations/v1_19/v239.go22
-rw-r--r--models/migrations/v1_19/v240.go176
-rw-r--r--models/migrations/v1_19/v241.go17
-rw-r--r--models/migrations/v1_19/v242.go26
-rw-r--r--models/migrations/v1_19/v243.go16
-rw-r--r--models/migrations/v1_20/main_test.go14
-rw-r--r--models/migrations/v1_20/v244.go22
-rw-r--r--models/migrations/v1_20/v245.go69
-rw-r--r--models/migrations/v1_20/v246.go16
-rw-r--r--models/migrations/v1_20/v247.go50
-rw-r--r--models/migrations/v1_20/v248.go14
-rw-r--r--models/migrations/v1_20/v249.go45
-rw-r--r--models/migrations/v1_20/v250.go135
-rw-r--r--models/migrations/v1_20/v251.go47
-rw-r--r--models/migrations/v1_20/v252.go47
-rw-r--r--models/migrations/v1_20/v253.go49
-rw-r--r--models/migrations/v1_20/v254.go18
-rw-r--r--models/migrations/v1_20/v255.go23
-rw-r--r--models/migrations/v1_20/v256.go23
-rw-r--r--models/migrations/v1_20/v257.go33
-rw-r--r--models/migrations/v1_20/v258.go16
-rw-r--r--models/migrations/v1_20/v259.go360
-rw-r--r--models/migrations/v1_20/v259_test.go111
-rw-r--r--models/migrations/v1_21/main_test.go14
-rw-r--r--models/migrations/v1_21/v260.go26
-rw-r--r--models/migrations/v1_21/v261.go24
-rw-r--r--models/migrations/v1_21/v262.go16
-rw-r--r--models/migrations/v1_21/v263.go46
-rw-r--r--models/migrations/v1_21/v264.go93
-rw-r--r--models/migrations/v1_21/v265.go19
-rw-r--r--models/migrations/v1_21/v266.go23
-rw-r--r--models/migrations/v1_21/v267.go23
-rw-r--r--models/migrations/v1_21/v268.go16
-rw-r--r--models/migrations/v1_21/v269.go12
-rw-r--r--models/migrations/v1_21/v270.go26
-rw-r--r--models/migrations/v1_21/v271.go16
-rw-r--r--models/migrations/v1_21/v272.go14
-rw-r--r--models/migrations/v1_21/v273.go45
-rw-r--r--models/migrations/v1_21/v274.go36
-rw-r--r--models/migrations/v1_21/v275.go15
-rw-r--r--models/migrations/v1_21/v276.go156
-rw-r--r--models/migrations/v1_21/v277.go16
-rw-r--r--models/migrations/v1_21/v278.go16
-rw-r--r--models/migrations/v1_21/v279.go16
-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.go14
-rw-r--r--models/migrations/v1_22/v285.go18
-rw-r--r--models/migrations/v1_22/v286.go72
-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.go18
-rw-r--r--models/migrations/v1_22/v290.go39
-rw-r--r--models/migrations/v1_22/v290_test.go59
-rw-r--r--models/migrations/v1_22/v291.go14
-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
-rw-r--r--models/migrations/v1_23/main_test.go14
-rw-r--r--models/migrations/v1_23/v299.go18
-rw-r--r--models/migrations/v1_6/v70.go110
-rw-r--r--models/migrations/v1_6/v71.go79
-rw-r--r--models/migrations/v1_6/v72.go30
-rw-r--r--models/migrations/v1_7/v73.go18
-rw-r--r--models/migrations/v1_7/v74.go15
-rw-r--r--models/migrations/v1_7/v75.go32
-rw-r--r--models/migrations/v1_8/v76.go74
-rw-r--r--models/migrations/v1_8/v77.go16
-rw-r--r--models/migrations/v1_8/v78.go43
-rw-r--r--models/migrations/v1_8/v79.go25
-rw-r--r--models/migrations/v1_8/v80.go16
-rw-r--r--models/migrations/v1_8/v81.go28
-rw-r--r--models/migrations/v1_9/v82.go133
-rw-r--r--models/migrations/v1_9/v83.go27
-rw-r--r--models/migrations/v1_9/v84.go17
-rw-r--r--models/migrations/v1_9/v85.go118
-rw-r--r--models/migrations/v1_9/v86.go16
-rw-r--r--models/migrations/v1_9/v87.go17
303 files changed, 13404 insertions, 0 deletions
diff --git a/models/migrations/base/db.go b/models/migrations/base/db.go
new file mode 100644
index 00000000..e5847933
--- /dev/null
+++ b/models/migrations/base/db.go
@@ -0,0 +1,540 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package base
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+ "fmt"
+ "os"
+ "path"
+ "reflect"
+ "regexp"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+// RecreateTables will recreate the tables for the provided beans using the newly provided bean definition and move all data to that new table
+// WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION
+func RecreateTables(beans ...any) func(*xorm.Engine) error {
+ return func(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ sess = sess.StoreEngine("InnoDB")
+ for _, bean := range beans {
+ log.Info("Recreating Table: %s for Bean: %s", x.TableName(bean), reflect.Indirect(reflect.ValueOf(bean)).Type().Name())
+ if err := RecreateTable(sess, bean); err != nil {
+ return err
+ }
+ }
+ return sess.Commit()
+ }
+}
+
+// RecreateTable will recreate the table using the newly provided bean definition and move all data to that new table
+// WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION
+// WARNING: YOU MUST COMMIT THE SESSION AT THE END
+func RecreateTable(sess *xorm.Session, bean any) error {
+ // TODO: This will not work if there are foreign keys
+
+ tableName := sess.Engine().TableName(bean)
+ tempTableName := fmt.Sprintf("tmp_recreate__%s", tableName)
+
+ // We need to move the old table away and create a new one with the correct columns
+ // We will need to do this in stages to prevent data loss
+ //
+ // First create the temporary table
+ if err := sess.Table(tempTableName).CreateTable(bean); err != nil {
+ log.Error("Unable to create table %s. Error: %v", tempTableName, err)
+ return err
+ }
+
+ if err := sess.Table(tempTableName).CreateUniques(bean); err != nil {
+ log.Error("Unable to create uniques for table %s. Error: %v", tempTableName, err)
+ return err
+ }
+
+ if err := sess.Table(tempTableName).CreateIndexes(bean); err != nil {
+ log.Error("Unable to create indexes for table %s. Error: %v", tempTableName, err)
+ return err
+ }
+
+ // Work out the column names from the bean - these are the columns to select from the old table and install into the new table
+ table, err := sess.Engine().TableInfo(bean)
+ if err != nil {
+ log.Error("Unable to get table info. Error: %v", err)
+
+ return err
+ }
+ newTableColumns := table.Columns()
+ if len(newTableColumns) == 0 {
+ return fmt.Errorf("no columns in new table")
+ }
+ hasID := false
+ for _, column := range newTableColumns {
+ hasID = hasID || (column.IsPrimaryKey && column.IsAutoIncrement)
+ }
+
+ sqlStringBuilder := &strings.Builder{}
+ _, _ = sqlStringBuilder.WriteString("INSERT INTO `")
+ _, _ = sqlStringBuilder.WriteString(tempTableName)
+ _, _ = sqlStringBuilder.WriteString("` (`")
+ _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
+ _, _ = sqlStringBuilder.WriteString("`")
+ for _, column := range newTableColumns[1:] {
+ _, _ = sqlStringBuilder.WriteString(", `")
+ _, _ = sqlStringBuilder.WriteString(column.Name)
+ _, _ = sqlStringBuilder.WriteString("`")
+ }
+ _, _ = sqlStringBuilder.WriteString(")")
+ _, _ = sqlStringBuilder.WriteString(" SELECT ")
+ if newTableColumns[0].Default != "" {
+ _, _ = sqlStringBuilder.WriteString("COALESCE(`")
+ _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
+ _, _ = sqlStringBuilder.WriteString("`, ")
+ _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Default)
+ _, _ = sqlStringBuilder.WriteString(")")
+ } else {
+ _, _ = sqlStringBuilder.WriteString("`")
+ _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
+ _, _ = sqlStringBuilder.WriteString("`")
+ }
+
+ for _, column := range newTableColumns[1:] {
+ if column.Default != "" {
+ _, _ = sqlStringBuilder.WriteString(", COALESCE(`")
+ _, _ = sqlStringBuilder.WriteString(column.Name)
+ _, _ = sqlStringBuilder.WriteString("`, ")
+ _, _ = sqlStringBuilder.WriteString(column.Default)
+ _, _ = sqlStringBuilder.WriteString(")")
+ } else {
+ _, _ = sqlStringBuilder.WriteString(", `")
+ _, _ = sqlStringBuilder.WriteString(column.Name)
+ _, _ = sqlStringBuilder.WriteString("`")
+ }
+ }
+ _, _ = sqlStringBuilder.WriteString(" FROM `")
+ _, _ = sqlStringBuilder.WriteString(tableName)
+ _, _ = sqlStringBuilder.WriteString("`")
+
+ if _, err := sess.Exec(sqlStringBuilder.String()); err != nil {
+ log.Error("Unable to set copy data in to temp table %s. Error: %v", tempTableName, err)
+ return err
+ }
+
+ switch {
+ case setting.Database.Type.IsSQLite3():
+ // SQLite will drop all the constraints on the old table
+ if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
+ log.Error("Unable to drop old table %s. Error: %v", tableName, err)
+ return err
+ }
+
+ if err := sess.Table(tempTableName).DropIndexes(bean); err != nil {
+ log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err)
+ return err
+ }
+
+ if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
+ log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
+ return err
+ }
+
+ if err := sess.Table(tableName).CreateIndexes(bean); err != nil {
+ log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err)
+ return err
+ }
+
+ if err := sess.Table(tableName).CreateUniques(bean); err != nil {
+ log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err)
+ return err
+ }
+
+ case setting.Database.Type.IsMySQL():
+ // MySQL will drop all the constraints on the old table
+ if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
+ log.Error("Unable to drop old table %s. Error: %v", tableName, err)
+ return err
+ }
+
+ if err := sess.Table(tempTableName).DropIndexes(bean); err != nil {
+ log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err)
+ return err
+ }
+
+ // SQLite and MySQL will move all the constraints from the temporary table to the new table
+ if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
+ log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
+ return err
+ }
+
+ if err := sess.Table(tableName).CreateIndexes(bean); err != nil {
+ log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err)
+ return err
+ }
+
+ if err := sess.Table(tableName).CreateUniques(bean); err != nil {
+ log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err)
+ return err
+ }
+ case setting.Database.Type.IsPostgreSQL():
+ var originalSequences []string
+ type sequenceData struct {
+ LastValue int `xorm:"'last_value'"`
+ IsCalled bool `xorm:"'is_called'"`
+ }
+ sequenceMap := map[string]sequenceData{}
+
+ schema := sess.Engine().Dialect().URI().Schema
+ sess.Engine().SetSchema("")
+ if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE ? || '_%' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&originalSequences); err != nil {
+ log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
+ return err
+ }
+ sess.Engine().SetSchema(schema)
+
+ for _, sequence := range originalSequences {
+ sequenceData := sequenceData{}
+ if _, err := sess.Table(sequence).Cols("last_value", "is_called").Get(&sequenceData); err != nil {
+ log.Error("Unable to get last_value and is_called from %s. Error: %v", sequence, err)
+ return err
+ }
+ sequenceMap[sequence] = sequenceData
+ }
+
+ // CASCADE causes postgres to drop all the constraints on the old table
+ if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s` CASCADE", tableName)); err != nil {
+ log.Error("Unable to drop old table %s. Error: %v", tableName, err)
+ return err
+ }
+
+ // CASCADE causes postgres to move all the constraints from the temporary table to the new table
+ if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
+ log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
+ return err
+ }
+
+ var indices []string
+ sess.Engine().SetSchema("")
+ if err := sess.Table("pg_indexes").Cols("indexname").Where("tablename = ? ", tableName).Find(&indices); err != nil {
+ log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
+ return err
+ }
+ sess.Engine().SetSchema(schema)
+
+ for _, index := range indices {
+ newIndexName := strings.Replace(index, "tmp_recreate__", "", 1)
+ if _, err := sess.Exec(fmt.Sprintf("ALTER INDEX `%s` RENAME TO `%s`", index, newIndexName)); err != nil {
+ log.Error("Unable to rename %s to %s. Error: %v", index, newIndexName, err)
+ return err
+ }
+ }
+
+ var sequences []string
+ sess.Engine().SetSchema("")
+ if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__' || ? || '_%' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&sequences); err != nil {
+ log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
+ return err
+ }
+ sess.Engine().SetSchema(schema)
+
+ for _, sequence := range sequences {
+ newSequenceName := strings.Replace(sequence, "tmp_recreate__", "", 1)
+ if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE `%s` RENAME TO `%s`", sequence, newSequenceName)); err != nil {
+ log.Error("Unable to rename %s sequence to %s. Error: %v", sequence, newSequenceName, err)
+ return err
+ }
+ val, ok := sequenceMap[newSequenceName]
+ if newSequenceName == tableName+"_id_seq" {
+ if ok && val.LastValue != 0 {
+ if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', %d, %t)", newSequenceName, val.LastValue, val.IsCalled)); err != nil {
+ log.Error("Unable to reset %s to %d. Error: %v", newSequenceName, val, err)
+ return err
+ }
+ } else {
+ // We're going to try to guess this
+ 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 %s. Error: %v", newSequenceName, err)
+ return err
+ }
+ }
+ } else if ok {
+ if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', %d, %t)", newSequenceName, val.LastValue, val.IsCalled)); err != nil {
+ log.Error("Unable to reset %s to %d. Error: %v", newSequenceName, val, err)
+ return err
+ }
+ }
+ }
+
+ default:
+ log.Fatal("Unrecognized DB")
+ }
+ return nil
+}
+
+// WARNING: YOU MUST COMMIT THE SESSION AT THE END
+func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...string) (err error) {
+ if tableName == "" || len(columnNames) == 0 {
+ return nil
+ }
+ // TODO: This will not work if there are foreign keys
+
+ switch {
+ case setting.Database.Type.IsSQLite3():
+ // First drop the indexes on the columns
+ res, errIndex := sess.Query(fmt.Sprintf("PRAGMA index_list(`%s`)", tableName))
+ if errIndex != nil {
+ return errIndex
+ }
+ for _, row := range res {
+ indexName := row["name"]
+ indexRes, err := sess.Query(fmt.Sprintf("PRAGMA index_info(`%s`)", indexName))
+ if err != nil {
+ return err
+ }
+ if len(indexRes) != 1 {
+ continue
+ }
+ indexColumn := string(indexRes[0]["name"])
+ for _, name := range columnNames {
+ if name == indexColumn {
+ _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s`", indexName))
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ // Here we need to get the columns from the original table
+ sql := fmt.Sprintf("SELECT sql FROM sqlite_master WHERE tbl_name='%s' and type='table'", tableName)
+ res, err := sess.Query(sql)
+ if err != nil {
+ return err
+ }
+ tableSQL := string(res[0]["sql"])
+
+ // Get the string offset for column definitions: `CREATE TABLE ( column-definitions... )`
+ columnDefinitionsIndex := strings.Index(tableSQL, "(")
+ if columnDefinitionsIndex < 0 {
+ return errors.New("couldn't find column definitions")
+ }
+
+ // Separate out the column definitions
+ tableSQL = tableSQL[columnDefinitionsIndex:]
+
+ // Remove the required columnNames
+ for _, name := range columnNames {
+ tableSQL = regexp.MustCompile(regexp.QuoteMeta("`"+name+"`")+"[^`,)]*?[,)]").ReplaceAllString(tableSQL, "")
+ }
+
+ // Ensure the query is ended properly
+ tableSQL = strings.TrimSpace(tableSQL)
+ if tableSQL[len(tableSQL)-1] != ')' {
+ if tableSQL[len(tableSQL)-1] == ',' {
+ tableSQL = tableSQL[:len(tableSQL)-1]
+ }
+ tableSQL += ")"
+ }
+
+ // Find all the columns in the table
+ columns := regexp.MustCompile("`([^`]*)`").FindAllString(tableSQL, -1)
+
+ tableSQL = fmt.Sprintf("CREATE TABLE `new_%s_new` ", tableName) + tableSQL
+ if _, err := sess.Exec(tableSQL); err != nil {
+ return err
+ }
+
+ // Now restore the data
+ columnsSeparated := strings.Join(columns, ",")
+ insertSQL := fmt.Sprintf("INSERT INTO `new_%s_new` (%s) SELECT %s FROM %s", tableName, columnsSeparated, columnsSeparated, tableName)
+ if _, err := sess.Exec(insertSQL); err != nil {
+ return err
+ }
+
+ // Now drop the old table
+ if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
+ return err
+ }
+
+ // Rename the table
+ if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `new_%s_new` RENAME TO `%s`", tableName, tableName)); err != nil {
+ return err
+ }
+
+ case setting.Database.Type.IsPostgreSQL():
+ cols := ""
+ for _, col := range columnNames {
+ if cols != "" {
+ cols += ", "
+ }
+ cols += "DROP COLUMN `" + col + "` CASCADE"
+ }
+ if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
+ return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
+ }
+ case setting.Database.Type.IsMySQL():
+ // Drop indexes on columns first
+ sql := fmt.Sprintf("SHOW INDEX FROM %s WHERE column_name IN ('%s')", tableName, strings.Join(columnNames, "','"))
+ res, err := sess.Query(sql)
+ if err != nil {
+ return err
+ }
+ for _, index := range res {
+ indexName := index["column_name"]
+ if len(indexName) > 0 {
+ _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s` ON `%s`", indexName, tableName))
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ // Now drop the columns
+ cols := ""
+ for _, col := range columnNames {
+ if cols != "" {
+ cols += ", "
+ }
+ cols += "DROP COLUMN `" + col + "`"
+ }
+ if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
+ return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
+ }
+ default:
+ log.Fatal("Unrecognized DB")
+ }
+
+ return nil
+}
+
+// ModifyColumn will modify column's type or other property. SQLITE is not supported
+func ModifyColumn(x *xorm.Engine, tableName string, col *schemas.Column) error {
+ var indexes map[string]*schemas.Index
+ var err error
+
+ defer func() {
+ for _, index := range indexes {
+ _, err = x.Exec(x.Dialect().CreateIndexSQL(tableName, index))
+ if err != nil {
+ log.Error("Create index %s on table %s failed: %v", index.Name, tableName, err)
+ }
+ }
+ }()
+
+ alterSQL := x.Dialect().ModifyColumnSQL(tableName, col)
+ if _, err := x.Exec(alterSQL); err != nil {
+ return err
+ }
+ return nil
+}
+
+func removeAllWithRetry(dir string) error {
+ var err error
+ for i := 0; i < 20; i++ {
+ err = os.RemoveAll(dir)
+ if err == nil {
+ break
+ }
+ time.Sleep(100 * time.Millisecond)
+ }
+ return err
+}
+
+func newXORMEngine() (*xorm.Engine, error) {
+ if err := db.InitEngine(context.Background()); err != nil {
+ return nil, err
+ }
+ x := unittest.GetXORMEngine()
+ return x, nil
+}
+
+func deleteDB() error {
+ switch {
+ case setting.Database.Type.IsSQLite3():
+ if err := util.Remove(setting.Database.Path); err != nil {
+ return err
+ }
+ return os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm)
+
+ case setting.Database.Type.IsMySQL():
+ db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/",
+ setting.Database.User, setting.Database.Passwd, setting.Database.Host))
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil {
+ return err
+ }
+
+ if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)); err != nil {
+ return err
+ }
+ return nil
+ case setting.Database.Type.IsPostgreSQL():
+ db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s",
+ setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode))
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil {
+ return err
+ }
+
+ if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil {
+ return err
+ }
+ db.Close()
+
+ // Check if we need to setup a specific schema
+ if len(setting.Database.Schema) != 0 {
+ db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s",
+ setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode))
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema))
+ if err != nil {
+ return err
+ }
+ defer schrows.Close()
+
+ if !schrows.Next() {
+ // Create and setup a DB schema
+ _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema))
+ if err != nil {
+ return err
+ }
+ }
+
+ // Make the user's default search path the created schema; this will affect new connections
+ _, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema))
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+ }
+
+ return nil
+}
diff --git a/models/migrations/base/db_test.go b/models/migrations/base/db_test.go
new file mode 100644
index 00000000..80bf00b2
--- /dev/null
+++ b/models/migrations/base/db_test.go
@@ -0,0 +1,97 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package base
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm/names"
+)
+
+func Test_DropTableColumns(t *testing.T) {
+ x, deferable := PrepareTestEnv(t, 0)
+ if x == nil || t.Failed() {
+ defer deferable()
+ return
+ }
+ defer deferable()
+
+ type DropTest struct {
+ ID int64 `xorm:"pk autoincr"`
+ FirstColumn string
+ ToDropColumn string `xorm:"unique"`
+ AnotherColumn int64
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ columns := []string{
+ "first_column",
+ "to_drop_column",
+ "another_column",
+ "created_unix",
+ "updated_unix",
+ }
+
+ x.SetMapper(names.GonicMapper{})
+
+ for i := range columns {
+ if err := x.Sync(new(DropTest)); err != nil {
+ t.Errorf("unable to create DropTest table: %v", err)
+ return
+ }
+
+ sess := x.NewSession()
+ if err := sess.Begin(); err != nil {
+ sess.Close()
+ t.Errorf("unable to begin transaction: %v", err)
+ return
+ }
+ if err := DropTableColumns(sess, "drop_test", columns[i:]...); err != nil {
+ sess.Close()
+ t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err)
+ return
+ }
+ if err := sess.Commit(); err != nil {
+ sess.Close()
+ t.Errorf("unable to commit transaction: %v", err)
+ return
+ }
+ sess.Close()
+ if err := x.DropTables(new(DropTest)); err != nil {
+ t.Errorf("unable to drop table: %v", err)
+ return
+ }
+ for j := range columns[i+1:] {
+ if err := x.Sync(new(DropTest)); err != nil {
+ t.Errorf("unable to create DropTest table: %v", err)
+ return
+ }
+ dropcols := append([]string{columns[i]}, columns[j+i+1:]...)
+ sess := x.NewSession()
+ if err := sess.Begin(); err != nil {
+ sess.Close()
+ t.Errorf("unable to begin transaction: %v", err)
+ return
+ }
+ if err := DropTableColumns(sess, "drop_test", dropcols...); err != nil {
+ sess.Close()
+ t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err)
+ return
+ }
+ if err := sess.Commit(); err != nil {
+ sess.Close()
+ t.Errorf("unable to commit transaction: %v", err)
+ return
+ }
+ sess.Close()
+ if err := x.DropTables(new(DropTest)); err != nil {
+ t.Errorf("unable to drop table: %v", err)
+ return
+ }
+ }
+ }
+}
diff --git a/models/migrations/base/hash.go b/models/migrations/base/hash.go
new file mode 100644
index 00000000..00fd1efd
--- /dev/null
+++ b/models/migrations/base/hash.go
@@ -0,0 +1,16 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package base
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+
+ "golang.org/x/crypto/pbkdf2"
+)
+
+func HashToken(token, salt string) string {
+ tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New)
+ return hex.EncodeToString(tempHash)
+}
diff --git a/models/migrations/base/main_test.go b/models/migrations/base/main_test.go
new file mode 100644
index 00000000..c1c78915
--- /dev/null
+++ b/models/migrations/base/main_test.go
@@ -0,0 +1,12 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package base
+
+import (
+ "testing"
+)
+
+func TestMain(m *testing.M) {
+ MainTest(m)
+}
diff --git a/models/migrations/base/tests.go b/models/migrations/base/tests.go
new file mode 100644
index 00000000..7ff5c869
--- /dev/null
+++ b/models/migrations/base/tests.go
@@ -0,0 +1,173 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+//nolint:forbidigo
+package base
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path"
+ "path/filepath"
+ "runtime"
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/testlogger"
+
+ "github.com/stretchr/testify/require"
+ "xorm.io/xorm"
+)
+
+// FIXME: this file shouldn't be in a normal package, it should only be compiled for tests
+
+// PrepareTestEnv prepares the test environment and reset the database. The skip parameter should usually be 0.
+// Provide models to be sync'd with the database - in particular any models you expect fixtures to be loaded from.
+//
+// fixtures in `models/migrations/fixtures/<TestName>` will be loaded automatically
+func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, func()) {
+ t.Helper()
+ ourSkip := 2
+ ourSkip += skip
+ deferFn := testlogger.PrintCurrentTest(t, ourSkip)
+ require.NoError(t, os.RemoveAll(setting.RepoRootPath))
+ require.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath))
+ ownerDirs, err := os.ReadDir(setting.RepoRootPath)
+ if err != nil {
+ require.NoError(t, err, "unable to read the new repo root: %v\n", err)
+ }
+ for _, ownerDir := range ownerDirs {
+ if !ownerDir.Type().IsDir() {
+ continue
+ }
+ repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
+ if err != nil {
+ require.NoError(t, err, "unable to read the new repo root: %v\n", err)
+ }
+ for _, repoDir := range repoDirs {
+ _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755)
+ _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755)
+ _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755)
+ _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755)
+ }
+ }
+
+ if err := deleteDB(); err != nil {
+ t.Errorf("unable to reset database: %v", err)
+ return nil, deferFn
+ }
+
+ x, err := newXORMEngine()
+ require.NoError(t, err)
+ if x != nil {
+ oldDefer := deferFn
+ deferFn = func() {
+ oldDefer()
+ if err := x.Close(); err != nil {
+ t.Errorf("error during close: %v", err)
+ }
+ if err := deleteDB(); err != nil {
+ t.Errorf("unable to reset database: %v", err)
+ }
+ }
+ }
+ if err != nil {
+ return x, deferFn
+ }
+
+ if len(syncModels) > 0 {
+ if err := x.Sync(syncModels...); err != nil {
+ t.Errorf("error during sync: %v", err)
+ return x, deferFn
+ }
+ }
+
+ fixturesDir := filepath.Join(filepath.Dir(setting.AppPath), "models", "migrations", "fixtures", t.Name())
+
+ if _, err := os.Stat(fixturesDir); err == nil {
+ t.Logf("initializing fixtures from: %s", fixturesDir)
+ if err := unittest.InitFixtures(
+ unittest.FixturesOptions{
+ Dir: fixturesDir,
+ }, x); err != nil {
+ t.Errorf("error whilst initializing fixtures from %s: %v", fixturesDir, err)
+ return x, deferFn
+ }
+ if err := unittest.LoadFixtures(x); err != nil {
+ t.Errorf("error whilst loading fixtures from %s: %v", fixturesDir, err)
+ return x, deferFn
+ }
+ } else if !os.IsNotExist(err) {
+ t.Errorf("unexpected error whilst checking for existence of fixtures: %v", err)
+ } else {
+ t.Logf("no fixtures found in: %s", fixturesDir)
+ }
+
+ return x, deferFn
+}
+
+func MainTest(m *testing.M) {
+ log.RegisterEventWriter("test", testlogger.NewTestLoggerWriter)
+
+ giteaRoot := base.SetupGiteaRoot()
+ if giteaRoot == "" {
+ fmt.Println("Environment variable $GITEA_ROOT not set")
+ os.Exit(1)
+ }
+ giteaBinary := "gitea"
+ if runtime.GOOS == "windows" {
+ giteaBinary += ".exe"
+ }
+ setting.AppPath = path.Join(giteaRoot, giteaBinary)
+ if _, err := os.Stat(setting.AppPath); err != nil {
+ fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath)
+ os.Exit(1)
+ }
+
+ giteaConf := os.Getenv("GITEA_CONF")
+ if giteaConf == "" {
+ giteaConf = path.Join(filepath.Dir(setting.AppPath), "tests/sqlite.ini")
+ fmt.Printf("Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
+ }
+
+ if !path.IsAbs(giteaConf) {
+ setting.CustomConf = path.Join(giteaRoot, giteaConf)
+ } else {
+ setting.CustomConf = giteaConf
+ }
+
+ tmpDataPath, err := os.MkdirTemp("", "data")
+ if err != nil {
+ fmt.Printf("Unable to create temporary data path %v\n", err)
+ os.Exit(1)
+ }
+
+ setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
+ setting.AppDataPath = tmpDataPath
+
+ unittest.InitSettings()
+ if err = git.InitFull(context.Background()); err != nil {
+ fmt.Printf("Unable to InitFull: %v\n", err)
+ os.Exit(1)
+ }
+ setting.LoadDBSetting()
+ setting.InitLoggersForTest()
+
+ exitStatus := m.Run()
+
+ if err := testlogger.WriterCloser.Reset(); err != nil && exitStatus == 0 {
+ fmt.Printf("testlogger.WriterCloser.Reset: error ignored: %v\n", err)
+ }
+ if err := removeAllWithRetry(setting.RepoRootPath); err != nil {
+ fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
+ }
+ if err := removeAllWithRetry(tmpDataPath); err != nil {
+ fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
+ }
+ os.Exit(exitStatus)
+}
diff --git a/models/migrations/fixtures/Test_AddCombinedIndexToIssueUser/issue_user.yml b/models/migrations/fixtures/Test_AddCombinedIndexToIssueUser/issue_user.yml
new file mode 100644
index 00000000..b9995ac2
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddCombinedIndexToIssueUser/issue_user.yml
@@ -0,0 +1,13 @@
+-
+ id: 1
+ uid: 1
+ issue_id: 1
+ is_read: true
+ is_mentioned: false
+
+-
+ id: 2
+ uid: 2
+ issue_id: 1
+ is_read: true
+ is_mentioned: false
diff --git a/models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml b/models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml
new file mode 100644
index 00000000..a88c2ef8
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml
@@ -0,0 +1,2 @@
+-
+ id: 1
diff --git a/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml b/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml
new file mode 100644
index 00000000..f6239998
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml
@@ -0,0 +1,9 @@
+# for matrix, the access_token has been moved to "header_authorization"
+-
+ id: 1
+ meta: '{"homeserver_url":"https://matrix.example.com","room_id":"roomID","message_type":1}'
+ header_authorization: "Bearer s3cr3t"
+-
+ id: 2
+ meta: ''
+ header_authorization: ""
diff --git a/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/hook_task.yml b/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/hook_task.yml
new file mode 100644
index 00000000..8f61d6e7
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/hook_task.yml
@@ -0,0 +1,8 @@
+# unsafe payload
+- id: 1
+ hook_id: 1
+ payload_content: '{"homeserver_url":"https://matrix.example.com","room_id":"roomID","access_token":"s3cr3t","message_type":1}'
+# safe payload
+- id: 2
+ hook_id: 2
+ payload_content: '{"homeserver_url":"https://matrix.example.com","room_id":"roomID","message_type":1}'
diff --git a/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/webhook.yml b/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/webhook.yml
new file mode 100644
index 00000000..ec6f9bff
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/webhook.yml
@@ -0,0 +1,10 @@
+# matrix webhook
+- id: 1
+ type: matrix
+ meta: '{"homeserver_url":"https://matrix.example.com","room_id":"roomID","access_token":"s3cr3t","message_type":1}'
+ header_authorization_encrypted: ''
+# gitea webhook
+- id: 2
+ type: gitea
+ meta: ''
+ header_authorization_encrypted: ''
diff --git a/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml b/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml
new file mode 100644
index 00000000..f95d4791
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml
@@ -0,0 +1,4 @@
+-
+ id: 1
+ repo_id: 1
+ index: 1
diff --git a/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml
new file mode 100644
index 00000000..716a2a01
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml
@@ -0,0 +1,16 @@
+- id: 11
+ uuid: uuid11
+ hook_id: 1
+ payload_content: >
+ {"data":"payload"}
+ event_type: create
+ delivered: 1706106005
+
+- id: 101
+ uuid: uuid101
+ hook_id: 1
+ payload_content: >
+ {"data":"payload"}
+ event_type: create
+ delivered: 1706106006
+ is_delivered: true
diff --git a/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml
new file mode 100644
index 00000000..913d927d
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml
@@ -0,0 +1,18 @@
+- id: 11
+ uuid: uuid11
+ hook_id: 1
+ payload_content: >
+ {"data":"payload"}
+ event_type: create
+ delivered: 1706106005
+ payload_version: 1
+
+- id: 101
+ uuid: uuid101
+ hook_id: 1
+ payload_content: >
+ {"data":"payload"}
+ event_type: create
+ delivered: 1706106006
+ is_delivered: true
+ payload_version: 1
diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml
new file mode 100644
index 00000000..056236ba
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml
@@ -0,0 +1,11 @@
+-
+ id: 1
+ uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
+ issue_id: 1
+ release_id: 0
+
+-
+ id: 2
+ uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12
+ issue_id: 0
+ release_id: 1
diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml
new file mode 100644
index 00000000..7f325509
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ repo_id: 1
diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml
new file mode 100644
index 00000000..7f325509
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ repo_id: 1
diff --git a/models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml b/models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml
new file mode 100644
index 00000000..6feaeb39
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml
@@ -0,0 +1,9 @@
+-
+ id: 1
+ project_id: 1
+ issue_id: 1
+
+-
+ id: 2
+ project_id: 1
+ issue_id: 1
diff --git a/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml
new file mode 100644
index 00000000..2450d20b
--- /dev/null
+++ b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml
@@ -0,0 +1,23 @@
+-
+ id: 1
+ title: project without default column
+ owner_id: 2
+ repo_id: 0
+ is_closed: false
+ creator_id: 2
+ board_type: 1
+ type: 2
+ created_unix: 1688973000
+ updated_unix: 1688973000
+
+-
+ id: 2
+ title: project with multiple default columns
+ owner_id: 2
+ repo_id: 0
+ is_closed: false
+ creator_id: 2
+ board_type: 1
+ type: 2
+ created_unix: 1688973000
+ updated_unix: 1688973000
diff --git a/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml
new file mode 100644
index 00000000..2e1b1c7e
--- /dev/null
+++ b/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml
@@ -0,0 +1,26 @@
+-
+ id: 1
+ project_id: 1
+ title: Done
+ creator_id: 2
+ default: false
+ created_unix: 1588117528
+ updated_unix: 1588117528
+
+-
+ id: 2
+ project_id: 2
+ title: Backlog
+ creator_id: 2
+ default: true
+ created_unix: 1588117528
+ updated_unix: 1588117528
+
+-
+ id: 3
+ project_id: 2
+ title: Uncategorized
+ creator_id: 2
+ default: true
+ created_unix: 1588117528
+ updated_unix: 1588117528
diff --git a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml
new file mode 100644
index 00000000..b02cb570
--- /dev/null
+++ b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml
@@ -0,0 +1,28 @@
+# Issue_Label 1 should not be deleted
+-
+ id: 1
+ issue_id: 1
+ label_id: 1
+
+# Issue_label 2 should be deleted
+-
+ id: 2
+ issue_id: 5
+ label_id: 99
+
+# Issue_Label 3 should not be deleted
+-
+ id: 3
+ issue_id: 2
+ label_id: 1
+
+# Issue_Label 4 should not be deleted
+-
+ id: 4
+ issue_id: 2
+ label_id: 4
+
+-
+ id: 5
+ issue_id: 2
+ label_id: 87
diff --git a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml
new file mode 100644
index 00000000..fa9658aa
--- /dev/null
+++ b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml
@@ -0,0 +1,43 @@
+-
+ id: 1
+ repo_id: 1
+ org_id: 0
+ name: label1
+ color: '#abcdef'
+ num_issues: 2
+ num_closed_issues: 0
+
+-
+ id: 2
+ repo_id: 1
+ org_id: 0
+ name: label2
+ color: '#000000'
+ num_issues: 1
+ num_closed_issues: 1
+-
+ id: 3
+ repo_id: 0
+ org_id: 3
+ name: orglabel3
+ color: '#abcdef'
+ num_issues: 0
+ num_closed_issues: 0
+
+-
+ id: 4
+ repo_id: 0
+ org_id: 3
+ name: orglabel4
+ color: '#000000'
+ num_issues: 1
+ num_closed_issues: 0
+
+-
+ id: 5
+ repo_id: 10
+ org_id: 0
+ name: pull-test-label
+ color: '#000000'
+ num_issues: 0
+ num_closed_issues: 0
diff --git a/models/migrations/fixtures/Test_RemigrateU2FCredentials/expected_webauthn_credential.yml b/models/migrations/fixtures/Test_RemigrateU2FCredentials/expected_webauthn_credential.yml
new file mode 100644
index 00000000..0e68a5d0
--- /dev/null
+++ b/models/migrations/fixtures/Test_RemigrateU2FCredentials/expected_webauthn_credential.yml
@@ -0,0 +1,12 @@
+-
+ id: 1
+ credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
+-
+ id: 2
+ credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
+-
+ id: 3
+ credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
+-
+ id: 4
+ credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
diff --git a/models/migrations/fixtures/Test_RemigrateU2FCredentials/u2f_registration.yml b/models/migrations/fixtures/Test_RemigrateU2FCredentials/u2f_registration.yml
new file mode 100644
index 00000000..5a7b70fd
--- /dev/null
+++ b/models/migrations/fixtures/Test_RemigrateU2FCredentials/u2f_registration.yml
@@ -0,0 +1,21 @@
+-
+ id: 1
+ name: "u2fkey-correctly-migrated"
+ user_id: 1
+ raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
+ counter: 0
+- id: 2
+ name: "u2fkey-incorrectly-migrated"
+ user_id: 1
+ raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
+ counter: 0
+- id: 3
+ name: "u2fkey-deleted"
+ user_id: 1
+ raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
+ counter: 0
+- id: 4
+ name: "u2fkey-wrong-user-id"
+ user_id: 2
+ raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
+ counter: 0
diff --git a/models/migrations/fixtures/Test_RemigrateU2FCredentials/webauthn_credential.yml b/models/migrations/fixtures/Test_RemigrateU2FCredentials/webauthn_credential.yml
new file mode 100644
index 00000000..7f9f10f8
--- /dev/null
+++ b/models/migrations/fixtures/Test_RemigrateU2FCredentials/webauthn_credential.yml
@@ -0,0 +1,30 @@
+-
+ id: 1
+ lower_name: "u2fkey-correctly-migrated"
+ name: "u2fkey-correctly-migrated"
+ user_id: 1
+ credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
+ public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
+ attestation_type: 'fido-u2f'
+ sign_count: 1
+ clone_warning: false
+-
+ id: 2
+ lower_name: "u2fkey-incorrectly-migrated"
+ name: "u2fkey-incorrectly-migrated"
+ user_id: 1
+ credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8A"
+ public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
+ attestation_type: 'fido-u2f'
+ sign_count: 1
+ clone_warning: false
+-
+ id: 4
+ lower_name: "u2fkey-wrong-user-id"
+ name: "u2fkey-wrong-user-id"
+ user_id: 1
+ credential_id: "THIS SHOULD CHANGE"
+ public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
+ attestation_type: 'fido-u2f'
+ sign_count: 1
+ clone_warning: false
diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/comment.yml b/models/migrations/fixtures/Test_RemoveInvalidLabels/comment.yml
new file mode 100644
index 00000000..4f44e296
--- /dev/null
+++ b/models/migrations/fixtures/Test_RemoveInvalidLabels/comment.yml
@@ -0,0 +1,52 @@
+# type Comment struct {
+# ID int64 `xorm:"pk autoincr"`
+# Type int `xorm:"INDEX"`
+# IssueID int64 `xorm:"INDEX"`
+# LabelID int64
+# }
+#
+# we are only interested in type 7
+#
+
+-
+ id: 1 # Should remain
+ type: 6
+ issue_id: 1
+ label_id: 0
+ should_remain: true
+-
+ id: 2 # Should remain
+ type: 7
+ issue_id: 1 # repo_id: 1
+ label_id: 1 # repo_id: 1
+ should_remain: true
+-
+ id: 3 # Should remain
+ type: 7
+ issue_id: 2 # repo_id: 2 owner_id: 1
+ label_id: 2 # org_id: 1
+ should_remain: true
+-
+ id: 4 # Should be DELETED
+ type: 7
+ issue_id: 1 # repo_id: 1
+ label_id: 3 # repo_id: 2
+ should_remain: false
+-
+ id: 5 # Should remain
+ type: 7
+ issue_id: 3 # repo_id: 1
+ label_id: 1 # repo_id: 1
+ should_remain: true
+-
+ id: 6 # Should be DELETED
+ type: 7
+ issue_id: 3 # repo_id: 1 owner_id: 2
+ label_id: 2 # org_id: 1
+ should_remain: false
+-
+ id: 7 # Should be DELETED
+ type: 7
+ issue_id: 3 # repo_id: 1 owner_id: 2
+ label_id: 5 # repo_id: 3
+ should_remain: false
diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/issue.yml b/models/migrations/fixtures/Test_RemoveInvalidLabels/issue.yml
new file mode 100644
index 00000000..46ad46c1
--- /dev/null
+++ b/models/migrations/fixtures/Test_RemoveInvalidLabels/issue.yml
@@ -0,0 +1,21 @@
+# 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.
+# }
+-
+ id: 1
+ repo_id: 1
+ index: 1
+-
+ id: 2
+ repo_id: 2
+ index: 1
+-
+ id: 3
+ repo_id: 1
+ index: 2
+-
+ id: 4
+ repo_id: 3
+ index: 1
diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/issue_label.yml b/models/migrations/fixtures/Test_RemoveInvalidLabels/issue_label.yml
new file mode 100644
index 00000000..5f5b8cb1
--- /dev/null
+++ b/models/migrations/fixtures/Test_RemoveInvalidLabels/issue_label.yml
@@ -0,0 +1,35 @@
+# type IssueLabel struct {
+# ID int64 `xorm:"pk autoincr"`
+# IssueID int64 `xorm:"UNIQUE(s)"`
+# LabelID int64 `xorm:"UNIQUE(s)"`
+# }
+-
+ id: 1 # Should remain - matches comment 2
+ issue_id: 1
+ label_id: 1
+ should_remain: true
+-
+ id: 2 # Should remain
+ issue_id: 2
+ label_id: 2
+ should_remain: true
+-
+ id: 3 # Should be deleted
+ issue_id: 1
+ label_id: 3
+ should_remain: false
+-
+ id: 4 # Should remain
+ issue_id: 3
+ label_id: 1
+ should_remain: true
+-
+ id: 5 # Should be deleted
+ issue_id: 3
+ label_id: 2
+ should_remain: false
+-
+ id: 6 # Should be deleted
+ issue_id: 3
+ label_id: 5
+ should_remain: false
diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml b/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml
new file mode 100644
index 00000000..0f5a3eb5
--- /dev/null
+++ b/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml
@@ -0,0 +1,25 @@
+# type Label struct {
+# ID int64 `xorm:"pk autoincr"`
+# RepoID int64 `xorm:"INDEX"`
+# OrgID int64 `xorm:"INDEX"`
+# }
+-
+ id: 1
+ repo_id: 1
+ org_id: 0
+-
+ id: 2
+ repo_id: 0
+ org_id: 1
+-
+ id: 3
+ repo_id: 2
+ org_id: 0
+-
+ id: 4
+ repo_id: 1
+ org_id: 0
+-
+ id: 5
+ repo_id: 3
+ org_id: 0
diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/repository.yml b/models/migrations/fixtures/Test_RemoveInvalidLabels/repository.yml
new file mode 100644
index 00000000..180f11b3
--- /dev/null
+++ b/models/migrations/fixtures/Test_RemoveInvalidLabels/repository.yml
@@ -0,0 +1,17 @@
+# type Repository struct {
+# ID int64 `xorm:"pk autoincr"`
+# OwnerID int64 `xorm:"UNIQUE(s) index"`
+# LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+# }
+-
+ id: 1
+ owner_id: 2
+ lower_name: "repo1"
+-
+ id: 2
+ owner_id: 1
+ lower_name: "repo2"
+-
+ id: 3
+ owner_id: 2
+ lower_name: "repo3"
diff --git a/models/migrations/fixtures/Test_RemoveSSHSignaturesFromReleaseNotes/release.yml b/models/migrations/fixtures/Test_RemoveSSHSignaturesFromReleaseNotes/release.yml
new file mode 100644
index 00000000..caa0b40b
--- /dev/null
+++ b/models/migrations/fixtures/Test_RemoveSSHSignaturesFromReleaseNotes/release.yml
@@ -0,0 +1,22 @@
+# type Release struct {
+# ID int64 `xorm:"pk autoincr"`
+# Note string `xorm:"TEXT"`
+# }
+-
+ id: 1
+ note: |
+ -----BEGIN SSH SIGNATURE-----
+ some signature
+ -----END SSH SIGNATURE-----
+
+-
+ id: 2
+ note: |
+ A message.
+ -----BEGIN SSH SIGNATURE-----
+ some signature
+ -----END SSH SIGNATURE-----
+
+-
+ id: 3
+ note: "no signature present here"
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/comment.yml b/models/migrations/fixtures/Test_RepositoryFormat/comment.yml
new file mode 100644
index 00000000..1197b086
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/comment.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml b/models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml
new file mode 100644
index 00000000..ca0aaec4
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ context_hash: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml b/models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml
new file mode 100644
index 00000000..380cc079
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml
@@ -0,0 +1,5 @@
+-
+ id: 1
+ commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
+ merge_base: 19fe5caf872476db265596eaac1dc35ad1c6422d
+ merged_commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/release.yml b/models/migrations/fixtures/Test_RepositoryFormat/release.yml
new file mode 100644
index 00000000..ffabe4ab
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/release.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ sha1: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml b/models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml
new file mode 100644
index 00000000..f04cb3b3
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml b/models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml
new file mode 100644
index 00000000..1197b086
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml
@@ -0,0 +1,3 @@
+-
+ id: 1
+ commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/repository.yml b/models/migrations/fixtures/Test_RepositoryFormat/repository.yml
new file mode 100644
index 00000000..5a367591
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/repository.yml
@@ -0,0 +1,11 @@
+# type Repository struct {
+# ID int64 `xorm:"pk autoincr"`
+# }
+-
+ id: 1
+-
+ id: 2
+-
+ id: 3
+-
+ id: 10
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/review_state.yml b/models/migrations/fixtures/Test_RepositoryFormat/review_state.yml
new file mode 100644
index 00000000..dd649809
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/review_state.yml
@@ -0,0 +1,5 @@
+-
+ id: 1
+ user_id: 1
+ pull_id: 1
+ commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
diff --git a/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml b/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml
new file mode 100644
index 00000000..55a237a0
--- /dev/null
+++ b/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml
@@ -0,0 +1,9 @@
+-
+ id: 1
+ credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
+-
+ id: 2
+ credential_id: "051CLMMKB62S6M9M2A4H54K7MMCQALFJ36G4TGB2S9A47APLTILU6C6744CEBG4EKCGV357N21BSLH8JD33GQMFAR6DQ70S76P34J6FR="
+-
+ id: 4
+ credential_id: "APU4B1NDTEVTEM60V4T0FRL7SRJMO9KIE2AKFQ8JDGTQ7VHFI41FDEFTDLBVQEAE4ER49QV2GTGVFDNBO31BPOA3OQN6879OT6MTU3G="
diff --git a/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml b/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml
new file mode 100644
index 00000000..ebb73f44
--- /dev/null
+++ b/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml
@@ -0,0 +1,30 @@
+-
+ id: 1
+ lower_name: "u2fkey-correctly-migrated"
+ name: "u2fkey-correctly-migrated"
+ user_id: 1
+ credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
+ public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
+ attestation_type: 'fido-u2f'
+ sign_count: 1
+ clone_warning: false
+-
+ id: 2
+ lower_name: "non-u2f-key"
+ name: "non-u2f-key"
+ user_id: 1
+ credential_id: "051CLMMKB62S6M9M2A4H54K7MMCQALFJ36G4TGB2S9A47APLTILU6C6744CEBG4EKCGV357N21BSLH8JD33GQMFAR6DQ70S76P34J6FR"
+ public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
+ attestation_type: 'none'
+ sign_count: 1
+ clone_warning: false
+-
+ id: 4
+ lower_name: "packed-key"
+ name: "packed-key"
+ user_id: 1
+ credential_id: "APU4B1NDTEVTEM60V4T0FRL7SRJMO9KIE2AKFQ8JDGTQ7VHFI41FDEFTDLBVQEAE4ER49QV2GTGVFDNBO31BPOA3OQN6879OT6MTU3G="
+ public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
+ attestation_type: 'fido-u2f'
+ sign_count: 1
+ clone_warning: false
diff --git a/models/migrations/fixtures/Test_UnwrapLDAPSourceCfg/login_source.yml b/models/migrations/fixtures/Test_UnwrapLDAPSourceCfg/login_source.yml
new file mode 100644
index 00000000..4b72ba14
--- /dev/null
+++ b/models/migrations/fixtures/Test_UnwrapLDAPSourceCfg/login_source.yml
@@ -0,0 +1,48 @@
+# type LoginSource struct {
+# ID int64 `xorm:"pk autoincr"`
+# Type int
+# Cfg []byte `xorm:"TEXT"`
+# Expected []byte `xorm:"TEXT"`
+# }
+-
+ id: 1
+ type: 1
+ is_actived: false
+ cfg: "{\"Source\":{\"A\":\"string\",\"B\":1}}"
+ expected: "{\"Source\":{\"A\":\"string\",\"B\":1}}"
+-
+ id: 2
+ type: 2
+ is_actived: true
+ cfg: "{\"Source\":{\"A\":\"string2\",\"B\":2}}"
+ expected: "{\"A\":\"string2\",\"B\":2}"
+-
+ id: 3
+ type: 3
+ is_actived: false
+ cfg: "{\"Source\":{\"A\":\"string3\",\"B\":3}}"
+ expected: "{\"Source\":{\"A\":\"string3\",\"B\":3}}"
+-
+ id: 4
+ type: 4
+ is_actived: true
+ cfg: "{\"Source\":{\"A\":\"string4\",\"B\":4}}"
+ expected: "{\"Source\":{\"A\":\"string4\",\"B\":4}}"
+-
+ id: 5
+ type: 5
+ is_actived: false
+ cfg: "{\"Source\":{\"A\":\"string5\",\"B\":5}}"
+ expected: "{\"A\":\"string5\",\"B\":5}"
+-
+ id: 6
+ type: 2
+ is_actived: true
+ cfg: "{\"A\":\"string6\",\"B\":6}"
+ expected: "{\"A\":\"string6\",\"B\":6}"
+-
+ id: 7
+ type: 5
+ is_actived: false
+ cfg: "{\"A\":\"string7\",\"B\":7}"
+ expected: "{\"A\":\"string7\",\"B\":7}"
diff --git a/models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml b/models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml
new file mode 100644
index 00000000..70251441
--- /dev/null
+++ b/models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml
@@ -0,0 +1,4 @@
+-
+ id: 1
+ description: the badge
+ image_url: https://gitea.com/myimage.png
diff --git a/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/expected_milestone.yml b/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/expected_milestone.yml
new file mode 100644
index 00000000..9326fa55
--- /dev/null
+++ b/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/expected_milestone.yml
@@ -0,0 +1,19 @@
+# type Milestone struct {
+# ID int64 `xorm:"pk autoincr"`
+# IsClosed bool
+# NumIssues int
+# NumClosedIssues int
+# Completeness int // Percentage(1-100).
+# }
+-
+ id: 1
+ is_closed: false
+ num_issues: 3
+ num_closed_issues: 1
+ completeness: 33
+-
+ id: 2
+ is_closed: true
+ num_issues: 5
+ num_closed_issues: 5
+ completeness: 100
diff --git a/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/issue.yml b/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/issue.yml
new file mode 100644
index 00000000..fdaacd9f
--- /dev/null
+++ b/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/issue.yml
@@ -0,0 +1,25 @@
+# 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.
+# MilestoneID int64 `xorm:"INDEX"`
+# IsClosed bool `xorm:"INDEX"`
+# }
+-
+ id: 1
+ repo_id: 1
+ index: 1
+ milestone_id: 1
+ is_closed: false
+-
+ id: 2
+ repo_id: 1
+ index: 2
+ milestone_id: 1
+ is_closed: true
+-
+ id: 4
+ repo_id: 1
+ index: 3
+ milestone_id: 1
+ is_closed: false
diff --git a/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/milestone.yml b/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/milestone.yml
new file mode 100644
index 00000000..0bcf4cff
--- /dev/null
+++ b/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/milestone.yml
@@ -0,0 +1,19 @@
+# type Milestone struct {
+# ID int64 `xorm:"pk autoincr"`
+# IsClosed bool
+# NumIssues int
+# NumClosedIssues int
+# Completeness int // Percentage(1-100).
+# }
+-
+ id: 1
+ is_closed: false
+ num_issues: 4
+ num_closed_issues: 2
+ completeness: 50
+-
+ id: 2
+ is_closed: true
+ num_issues: 5
+ num_closed_issues: 5
+ completeness: 100
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
new file mode 100644
index 00000000..2e095c05
--- /dev/null
+++ b/models/migrations/migrations.go
@@ -0,0 +1,715 @@
+// Copyright 2015 The Gogs Authors. All rights reserved.
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package migrations
+
+import (
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/forgejo_migrations"
+ "code.gitea.io/gitea/models/migrations/v1_10"
+ "code.gitea.io/gitea/models/migrations/v1_11"
+ "code.gitea.io/gitea/models/migrations/v1_12"
+ "code.gitea.io/gitea/models/migrations/v1_13"
+ "code.gitea.io/gitea/models/migrations/v1_14"
+ "code.gitea.io/gitea/models/migrations/v1_15"
+ "code.gitea.io/gitea/models/migrations/v1_16"
+ "code.gitea.io/gitea/models/migrations/v1_17"
+ "code.gitea.io/gitea/models/migrations/v1_18"
+ "code.gitea.io/gitea/models/migrations/v1_19"
+ "code.gitea.io/gitea/models/migrations/v1_20"
+ "code.gitea.io/gitea/models/migrations/v1_21"
+ "code.gitea.io/gitea/models/migrations/v1_22"
+ "code.gitea.io/gitea/models/migrations/v1_23"
+ "code.gitea.io/gitea/models/migrations/v1_6"
+ "code.gitea.io/gitea/models/migrations/v1_7"
+ "code.gitea.io/gitea/models/migrations/v1_8"
+ "code.gitea.io/gitea/models/migrations/v1_9"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ forgejo_services "code.gitea.io/gitea/services/forgejo"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/names"
+)
+
+const minDBVersion = 70 // Gitea 1.5.3
+
+// Migration describes on migration from lower version to high version
+type Migration interface {
+ Description() string
+ Migrate(*xorm.Engine) error
+}
+
+type migration struct {
+ description string
+ migrate func(*xorm.Engine) error
+}
+
+// NewMigration creates a new migration
+func NewMigration(desc string, fn func(*xorm.Engine) error) Migration {
+ return &migration{desc, fn}
+}
+
+// Description returns the migration's description
+func (m *migration) Description() string {
+ return m.description
+}
+
+// Migrate executes the migration
+func (m *migration) Migrate(x *xorm.Engine) error {
+ return m.migrate(x)
+}
+
+// Version describes the version table. Should have only one row with id==1
+type Version struct {
+ ID int64 `xorm:"pk autoincr"`
+ Version int64
+}
+
+// Use noopMigration when there is a migration that has been no-oped
+var noopMigration = func(_ *xorm.Engine) error { return nil }
+
+// This is a sequence of migrations. Add new migrations to the bottom of the list.
+// If you want to "retire" a migration, remove it from the top of the list and
+// update minDBVersion accordingly
+var migrations = []Migration{
+ // Gitea 1.5.0 ends at v69
+
+ // v70 -> v71
+ NewMigration("add issue_dependencies", v1_6.AddIssueDependencies),
+ // v71 -> v72
+ NewMigration("protect each scratch token", v1_6.AddScratchHash),
+ // v72 -> v73
+ NewMigration("add review", v1_6.AddReview),
+
+ // Gitea 1.6.0 ends at v73
+
+ // v73 -> v74
+ NewMigration("add must_change_password column for users table", v1_7.AddMustChangePassword),
+ // v74 -> v75
+ NewMigration("add approval whitelists to protected branches", v1_7.AddApprovalWhitelistsToProtectedBranches),
+ // v75 -> v76
+ NewMigration("clear nonused data which not deleted when user was deleted", v1_7.ClearNonusedData),
+
+ // Gitea 1.7.0 ends at v76
+
+ // v76 -> v77
+ NewMigration("add pull request rebase with merge commit", v1_8.AddPullRequestRebaseWithMerge),
+ // v77 -> v78
+ NewMigration("add theme to users", v1_8.AddUserDefaultTheme),
+ // v78 -> v79
+ NewMigration("rename repo is_bare to repo is_empty", v1_8.RenameRepoIsBareToIsEmpty),
+ // v79 -> v80
+ NewMigration("add can close issues via commit in any branch", v1_8.AddCanCloseIssuesViaCommitInAnyBranch),
+ // v80 -> v81
+ NewMigration("add is locked to issues", v1_8.AddIsLockedToIssues),
+ // v81 -> v82
+ NewMigration("update U2F counter type", v1_8.ChangeU2FCounterType),
+
+ // Gitea 1.8.0 ends at v82
+
+ // v82 -> v83
+ NewMigration("hot fix for wrong release sha1 on release table", v1_9.FixReleaseSha1OnReleaseTable),
+ // v83 -> v84
+ NewMigration("add uploader id for table attachment", v1_9.AddUploaderIDForAttachment),
+ // v84 -> v85
+ NewMigration("add table to store original imported gpg keys", v1_9.AddGPGKeyImport),
+ // v85 -> v86
+ NewMigration("hash application token", v1_9.HashAppToken),
+ // v86 -> v87
+ NewMigration("add http method to webhook", v1_9.AddHTTPMethodToWebhook),
+ // v87 -> v88
+ NewMigration("add avatar field to repository", v1_9.AddAvatarFieldToRepository),
+
+ // Gitea 1.9.0 ends at v88
+
+ // v88 -> v89
+ NewMigration("add commit status context field to commit_status", v1_10.AddCommitStatusContext),
+ // v89 -> v90
+ NewMigration("add original author/url migration info to issues, comments, and repo ", v1_10.AddOriginalMigrationInfo),
+ // v90 -> v91
+ NewMigration("change length of some repository columns", v1_10.ChangeSomeColumnsLengthOfRepo),
+ // v91 -> v92
+ NewMigration("add index on owner_id of repository and type, review_id of comment", v1_10.AddIndexOnRepositoryAndComment),
+ // v92 -> v93
+ NewMigration("remove orphaned repository index statuses", v1_10.RemoveLingeringIndexStatus),
+ // v93 -> v94
+ NewMigration("add email notification enabled preference to user", v1_10.AddEmailNotificationEnabledToUser),
+ // v94 -> v95
+ NewMigration("add enable_status_check, status_check_contexts to protected_branch", v1_10.AddStatusCheckColumnsForProtectedBranches),
+ // v95 -> v96
+ NewMigration("add table columns for cross referencing issues", v1_10.AddCrossReferenceColumns),
+ // v96 -> v97
+ NewMigration("delete orphaned attachments", v1_10.DeleteOrphanedAttachments),
+ // v97 -> v98
+ NewMigration("add repo_admin_change_team_access to user", v1_10.AddRepoAdminChangeTeamAccessColumnForUser),
+ // v98 -> v99
+ NewMigration("add original author name and id on migrated release", v1_10.AddOriginalAuthorOnMigratedReleases),
+ // v99 -> v100
+ NewMigration("add task table and status column for repository table", v1_10.AddTaskTable),
+ // v100 -> v101
+ NewMigration("update migration repositories' service type", v1_10.UpdateMigrationServiceTypes),
+ // v101 -> v102
+ NewMigration("change length of some external login users columns", v1_10.ChangeSomeColumnsLengthOfExternalLoginUser),
+
+ // Gitea 1.10.0 ends at v102
+
+ // v102 -> v103
+ NewMigration("update migration repositories' service type", v1_11.DropColumnHeadUserNameOnPullRequest),
+ // v103 -> v104
+ NewMigration("Add WhitelistDeployKeys to protected branch", v1_11.AddWhitelistDeployKeysToBranches),
+ // v104 -> v105
+ NewMigration("remove unnecessary columns from label", v1_11.RemoveLabelUneededCols),
+ // v105 -> v106
+ NewMigration("add includes_all_repositories to teams", v1_11.AddTeamIncludesAllRepositories),
+ // v106 -> v107
+ NewMigration("add column `mode` to table watch", v1_11.AddModeColumnToWatch),
+ // v107 -> v108
+ NewMigration("Add template options to repository", v1_11.AddTemplateToRepo),
+ // v108 -> v109
+ NewMigration("Add comment_id on table notification", v1_11.AddCommentIDOnNotification),
+ // v109 -> v110
+ NewMigration("add can_create_org_repo to team", v1_11.AddCanCreateOrgRepoColumnForTeam),
+ // v110 -> v111
+ NewMigration("change review content type to text", v1_11.ChangeReviewContentToText),
+ // v111 -> v112
+ NewMigration("update branch protection for can push and whitelist enable", v1_11.AddBranchProtectionCanPushAndEnableWhitelist),
+ // v112 -> v113
+ NewMigration("remove release attachments which repository deleted", v1_11.RemoveAttachmentMissedRepo),
+ // v113 -> v114
+ NewMigration("new feature: change target branch of pull requests", v1_11.FeatureChangeTargetBranch),
+ // v114 -> v115
+ NewMigration("Remove authentication credentials from stored URL", v1_11.SanitizeOriginalURL),
+ // v115 -> v116
+ NewMigration("add user_id prefix to existing user avatar name", v1_11.RenameExistingUserAvatarName),
+ // v116 -> v117
+ NewMigration("Extend TrackedTimes", v1_11.ExtendTrackedTimes),
+
+ // Gitea 1.11.0 ends at v117
+
+ // v117 -> v118
+ NewMigration("Add block on rejected reviews branch protection", v1_12.AddBlockOnRejectedReviews),
+ // v118 -> v119
+ NewMigration("Add commit id and stale to reviews", v1_12.AddReviewCommitAndStale),
+ // v119 -> v120
+ NewMigration("Fix migrated repositories' git service type", v1_12.FixMigratedRepositoryServiceType),
+ // v120 -> v121
+ NewMigration("Add owner_name on table repository", v1_12.AddOwnerNameOnRepository),
+ // v121 -> v122
+ NewMigration("add is_restricted column for users table", v1_12.AddIsRestricted),
+ // v122 -> v123
+ NewMigration("Add Require Signed Commits to ProtectedBranch", v1_12.AddRequireSignedCommits),
+ // v123 -> v124
+ NewMigration("Add original information for reactions", v1_12.AddReactionOriginals),
+ // v124 -> v125
+ NewMigration("Add columns to user and repository", v1_12.AddUserRepoMissingColumns),
+ // v125 -> v126
+ NewMigration("Add some columns on review for migration", v1_12.AddReviewMigrateInfo),
+ // v126 -> v127
+ NewMigration("Fix topic repository count", v1_12.FixTopicRepositoryCount),
+ // v127 -> v128
+ NewMigration("add repository code language statistics", v1_12.AddLanguageStats),
+ // v128 -> v129
+ NewMigration("fix merge base for pull requests", v1_12.FixMergeBase),
+ // v129 -> v130
+ NewMigration("remove dependencies from deleted repositories", v1_12.PurgeUnusedDependencies),
+ // v130 -> v131
+ NewMigration("Expand webhooks for more granularity", v1_12.ExpandWebhooks),
+ // v131 -> v132
+ NewMigration("Add IsSystemWebhook column to webhooks table", v1_12.AddSystemWebhookColumn),
+ // v132 -> v133
+ NewMigration("Add Branch Protection Protected Files Column", v1_12.AddBranchProtectionProtectedFilesColumn),
+ // v133 -> v134
+ NewMigration("Add EmailHash Table", v1_12.AddEmailHashTable),
+ // v134 -> v135
+ NewMigration("Refix merge base for merged pull requests", v1_12.RefixMergeBase),
+ // v135 -> v136
+ NewMigration("Add OrgID column to Labels table", v1_12.AddOrgIDLabelColumn),
+ // v136 -> v137
+ NewMigration("Add CommitsAhead and CommitsBehind Column to PullRequest Table", v1_12.AddCommitDivergenceToPulls),
+ // v137 -> v138
+ NewMigration("Add Branch Protection Block Outdated Branch", v1_12.AddBlockOnOutdatedBranch),
+ // v138 -> v139
+ NewMigration("Add ResolveDoerID to Comment table", v1_12.AddResolveDoerIDCommentColumn),
+ // v139 -> v140
+ NewMigration("prepend refs/heads/ to issue refs", v1_12.PrependRefsHeadsToIssueRefs),
+
+ // Gitea 1.12.0 ends at v140
+
+ // v140 -> v141
+ NewMigration("Save detected language file size to database instead of percent", v1_13.FixLanguageStatsToSaveSize),
+ // v141 -> v142
+ NewMigration("Add KeepActivityPrivate to User table", v1_13.AddKeepActivityPrivateUserColumn),
+ // v142 -> v143
+ NewMigration("Ensure Repository.IsArchived is not null", v1_13.SetIsArchivedToFalse),
+ // v143 -> v144
+ NewMigration("recalculate Stars number for all user", v1_13.RecalculateStars),
+ // v144 -> v145
+ NewMigration("update Matrix Webhook http method to 'PUT'", v1_13.UpdateMatrixWebhookHTTPMethod),
+ // v145 -> v146
+ NewMigration("Increase Language field to 50 in LanguageStats", v1_13.IncreaseLanguageField),
+ // v146 -> v147
+ NewMigration("Add projects info to repository table", v1_13.AddProjectsInfo),
+ // v147 -> v148
+ NewMigration("create review for 0 review id code comments", v1_13.CreateReviewsForCodeComments),
+ // v148 -> v149
+ NewMigration("remove issue dependency comments who refer to non existing issues", v1_13.PurgeInvalidDependenciesComments),
+ // v149 -> v150
+ NewMigration("Add Created and Updated to Milestone table", v1_13.AddCreatedAndUpdatedToMilestones),
+ // v150 -> v151
+ NewMigration("add primary key to repo_topic", v1_13.AddPrimaryKeyToRepoTopic),
+ // v151 -> v152
+ NewMigration("set default password algorithm to Argon2", v1_13.SetDefaultPasswordToArgon2),
+ // v152 -> v153
+ NewMigration("add TrustModel field to Repository", v1_13.AddTrustModelToRepository),
+ // v153 > v154
+ NewMigration("add Team review request support", v1_13.AddTeamReviewRequestSupport),
+ // v154 > v155
+ NewMigration("add timestamps to Star, Label, Follow, Watch and Collaboration", v1_13.AddTimeStamps),
+
+ // Gitea 1.13.0 ends at v155
+
+ // v155 -> v156
+ NewMigration("add changed_protected_files column for pull_request table", v1_14.AddChangedProtectedFilesPullRequestColumn),
+ // v156 -> v157
+ NewMigration("fix publisher ID for tag releases", v1_14.FixPublisherIDforTagReleases),
+ // v157 -> v158
+ NewMigration("ensure repo topics are up-to-date", v1_14.FixRepoTopics),
+ // v158 -> v159
+ NewMigration("code comment replies should have the commitID of the review they are replying to", v1_14.UpdateCodeCommentReplies),
+ // v159 -> v160
+ NewMigration("update reactions constraint", v1_14.UpdateReactionConstraint),
+ // v160 -> v161
+ NewMigration("Add block on official review requests branch protection", v1_14.AddBlockOnOfficialReviewRequests),
+ // v161 -> v162
+ NewMigration("Convert task type from int to string", v1_14.ConvertTaskTypeToString),
+ // v162 -> v163
+ NewMigration("Convert webhook task type from int to string", v1_14.ConvertWebhookTaskTypeToString),
+ // v163 -> v164
+ NewMigration("Convert topic name from 25 to 50", v1_14.ConvertTopicNameFrom25To50),
+ // v164 -> v165
+ NewMigration("Add scope and nonce columns to oauth2_grant table", v1_14.AddScopeAndNonceColumnsToOAuth2Grant),
+ // v165 -> v166
+ NewMigration("Convert hook task type from char(16) to varchar(16) and trim the column", v1_14.ConvertHookTaskTypeToVarcharAndTrim),
+ // v166 -> v167
+ NewMigration("Where Password is Valid with Empty String delete it", v1_14.RecalculateUserEmptyPWD),
+ // v167 -> v168
+ NewMigration("Add user redirect", v1_14.AddUserRedirect),
+ // v168 -> v169
+ NewMigration("Recreate user table to fix default values", v1_14.RecreateUserTableToFixDefaultValues),
+ // v169 -> v170
+ NewMigration("Update DeleteBranch comments to set the old_ref to the commit_sha", v1_14.CommentTypeDeleteBranchUseOldRef),
+ // v170 -> v171
+ NewMigration("Add Dismissed to Review table", v1_14.AddDismissedReviewColumn),
+ // v171 -> v172
+ NewMigration("Add Sorting to ProjectBoard table", v1_14.AddSortingColToProjectBoard),
+ // v172 -> v173
+ NewMigration("Add sessions table for go-chi/session", v1_14.AddSessionTable),
+ // v173 -> v174
+ NewMigration("Add time_id column to Comment", v1_14.AddTimeIDCommentColumn),
+ // v174 -> v175
+ NewMigration("Create repo transfer table", v1_14.AddRepoTransfer),
+ // v175 -> v176
+ NewMigration("Fix Postgres ID Sequences broken by recreate-table", v1_14.FixPostgresIDSequences),
+ // v176 -> v177
+ NewMigration("Remove invalid labels from comments", v1_14.RemoveInvalidLabels),
+ // v177 -> v178
+ NewMigration("Delete orphaned IssueLabels", v1_14.DeleteOrphanedIssueLabels),
+
+ // Gitea 1.14.0 ends at v178
+
+ // v178 -> v179
+ NewMigration("Add LFS columns to Mirror", v1_15.AddLFSMirrorColumns),
+ // v179 -> v180
+ NewMigration("Convert avatar url to text", v1_15.ConvertAvatarURLToText),
+ // v180 -> v181
+ NewMigration("Delete credentials from past migrations", v1_15.DeleteMigrationCredentials),
+ // v181 -> v182
+ NewMigration("Always save primary email on email address table", v1_15.AddPrimaryEmail2EmailAddress),
+ // v182 -> v183
+ NewMigration("Add issue resource index table", v1_15.AddIssueResourceIndexTable),
+ // v183 -> v184
+ NewMigration("Create PushMirror table", v1_15.CreatePushMirrorTable),
+ // v184 -> v185
+ NewMigration("Rename Task errors to message", v1_15.RenameTaskErrorsToMessage),
+ // v185 -> v186
+ NewMigration("Add new table repo_archiver", v1_15.AddRepoArchiver),
+ // v186 -> v187
+ NewMigration("Create protected tag table", v1_15.CreateProtectedTagTable),
+ // v187 -> v188
+ NewMigration("Drop unneeded webhook related columns", v1_15.DropWebhookColumns),
+ // v188 -> v189
+ NewMigration("Add key is verified to gpg key", v1_15.AddKeyIsVerified),
+
+ // Gitea 1.15.0 ends at v189
+
+ // v189 -> v190
+ NewMigration("Unwrap ldap.Sources", v1_16.UnwrapLDAPSourceCfg),
+ // v190 -> v191
+ NewMigration("Add agit flow pull request support", v1_16.AddAgitFlowPullRequest),
+ // v191 -> v192
+ NewMigration("Alter issue/comment table TEXT fields to LONGTEXT", v1_16.AlterIssueAndCommentTextFieldsToLongText),
+ // v192 -> v193
+ NewMigration("RecreateIssueResourceIndexTable to have a primary key instead of an unique index", v1_16.RecreateIssueResourceIndexTable),
+ // v193 -> v194
+ NewMigration("Add repo id column for attachment table", v1_16.AddRepoIDForAttachment),
+ // v194 -> v195
+ NewMigration("Add Branch Protection Unprotected Files Column", v1_16.AddBranchProtectionUnprotectedFilesColumn),
+ // v195 -> v196
+ NewMigration("Add table commit_status_index", v1_16.AddTableCommitStatusIndex),
+ // v196 -> v197
+ NewMigration("Add Color to ProjectBoard table", v1_16.AddColorColToProjectBoard),
+ // v197 -> v198
+ NewMigration("Add renamed_branch table", v1_16.AddRenamedBranchTable),
+ // v198 -> v199
+ NewMigration("Add issue content history table", v1_16.AddTableIssueContentHistory),
+ // v199 -> v200
+ NewMigration("No-op (remote version is using AppState now)", noopMigration),
+ // v200 -> v201
+ NewMigration("Add table app_state", v1_16.AddTableAppState),
+ // v201 -> v202
+ NewMigration("Drop table remote_version (if exists)", v1_16.DropTableRemoteVersion),
+ // v202 -> v203
+ NewMigration("Create key/value table for user settings", v1_16.CreateUserSettingsTable),
+ // v203 -> v204
+ NewMigration("Add Sorting to ProjectIssue table", v1_16.AddProjectIssueSorting),
+ // v204 -> v205
+ NewMigration("Add key is verified to ssh key", v1_16.AddSSHKeyIsVerified),
+ // v205 -> v206
+ NewMigration("Migrate to higher varchar on user struct", v1_16.MigrateUserPasswordSalt),
+ // v206 -> v207
+ NewMigration("Add authorize column to team_unit table", v1_16.AddAuthorizeColForTeamUnit),
+ // v207 -> v208
+ NewMigration("Add webauthn table and migrate u2f data to webauthn - NO-OPED", v1_16.AddWebAuthnCred),
+ // v208 -> v209
+ NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive - NO-OPED", v1_16.UseBase32HexForCredIDInWebAuthnCredential),
+ // v209 -> v210
+ NewMigration("Increase WebAuthentication CredentialID size to 410 - NO-OPED", v1_16.IncreaseCredentialIDTo410),
+ // v210 -> v211
+ NewMigration("v208 was completely broken - remigrate", v1_16.RemigrateU2FCredentials),
+
+ // Gitea 1.16.2 ends at v211
+
+ // v211 -> v212
+ NewMigration("Create ForeignReference table", v1_17.CreateForeignReferenceTable),
+ // v212 -> v213
+ NewMigration("Add package tables", v1_17.AddPackageTables),
+ // v213 -> v214
+ NewMigration("Add allow edits from maintainers to PullRequest table", v1_17.AddAllowMaintainerEdit),
+ // v214 -> v215
+ NewMigration("Add auto merge table", v1_17.AddAutoMergeTable),
+ // v215 -> v216
+ NewMigration("allow to view files in PRs", v1_17.AddReviewViewedFiles),
+ // v216 -> v217
+ NewMigration("No-op (Improve Action table indices v1)", noopMigration),
+ // v217 -> v218
+ NewMigration("Alter hook_task table TEXT fields to LONGTEXT", v1_17.AlterHookTaskTextFieldsToLongText),
+ // v218 -> v219
+ NewMigration("Improve Action table indices v2", v1_17.ImproveActionTableIndices),
+ // v219 -> v220
+ NewMigration("Add sync_on_commit column to push_mirror table", v1_17.AddSyncOnCommitColForPushMirror),
+ // v220 -> v221
+ NewMigration("Add container repository property", v1_17.AddContainerRepositoryProperty),
+ // v221 -> v222
+ NewMigration("Store WebAuthentication CredentialID as bytes and increase size to at least 1024", v1_17.StoreWebauthnCredentialIDAsBytes),
+ // v222 -> v223
+ NewMigration("Drop old CredentialID column", v1_17.DropOldCredentialIDColumn),
+ // v223 -> v224
+ NewMigration("Rename CredentialIDBytes column to CredentialID", v1_17.RenameCredentialIDBytes),
+
+ // Gitea 1.17.0 ends at v224
+
+ // v224 -> v225
+ NewMigration("Add badges to users", v1_18.CreateUserBadgesTable),
+ // v225 -> v226
+ NewMigration("Alter gpg_key/public_key content TEXT fields to MEDIUMTEXT", v1_18.AlterPublicGPGKeyContentFieldsToMediumText),
+ // v226 -> v227
+ NewMigration("Conan and generic packages do not need to be semantically versioned", v1_18.FixPackageSemverField),
+ // v227 -> v228
+ NewMigration("Create key/value table for system settings", v1_18.CreateSystemSettingsTable),
+ // v228 -> v229
+ NewMigration("Add TeamInvite table", v1_18.AddTeamInviteTable),
+ // v229 -> v230
+ NewMigration("Update counts of all open milestones", v1_18.UpdateOpenMilestoneCounts),
+ // v230 -> v231
+ NewMigration("Add ConfidentialClient column (default true) to OAuth2Application table", v1_18.AddConfidentialClientColumnToOAuth2ApplicationTable),
+
+ // Gitea 1.18.0 ends at v231
+
+ // v231 -> v232
+ NewMigration("Add index for hook_task", v1_19.AddIndexForHookTask),
+ // v232 -> v233
+ NewMigration("Alter package_version.metadata_json to LONGTEXT", v1_19.AlterPackageVersionMetadataToLongText),
+ // v233 -> v234
+ NewMigration("Add header_authorization_encrypted column to webhook table", v1_19.AddHeaderAuthorizationEncryptedColWebhook),
+ // v234 -> v235
+ NewMigration("Add package cleanup rule table", v1_19.CreatePackageCleanupRuleTable),
+ // v235 -> v236
+ NewMigration("Add index for access_token", v1_19.AddIndexForAccessToken),
+ // v236 -> v237
+ NewMigration("Create secrets table", v1_19.CreateSecretsTable),
+ // v237 -> v238
+ NewMigration("Drop ForeignReference table", v1_19.DropForeignReferenceTable),
+ // v238 -> v239
+ NewMigration("Add updated unix to LFSMetaObject", v1_19.AddUpdatedUnixToLFSMetaObject),
+ // v239 -> v240
+ NewMigration("Add scope for access_token", v1_19.AddScopeForAccessTokens),
+ // v240 -> v241
+ NewMigration("Add actions tables", v1_19.AddActionsTables),
+ // v241 -> v242
+ NewMigration("Add card_type column to project table", v1_19.AddCardTypeToProjectTable),
+ // v242 -> v243
+ NewMigration("Alter gpg_key_import content TEXT field to MEDIUMTEXT", v1_19.AlterPublicGPGKeyImportContentFieldToMediumText),
+ // v243 -> v244
+ NewMigration("Add exclusive label", v1_19.AddExclusiveLabel),
+
+ // Gitea 1.19.0 ends at v244
+
+ // v244 -> v245
+ NewMigration("Add NeedApproval to actions tables", v1_20.AddNeedApprovalToActionRun),
+ // v245 -> v246
+ NewMigration("Rename Webhook org_id to owner_id", v1_20.RenameWebhookOrgToOwner),
+ // v246 -> v247
+ NewMigration("Add missed column owner_id for project table", v1_20.AddNewColumnForProject),
+ // v247 -> v248
+ NewMigration("Fix incorrect project type", v1_20.FixIncorrectProjectType),
+ // v248 -> v249
+ NewMigration("Add version column to action_runner table", v1_20.AddVersionToActionRunner),
+ // v249 -> v250
+ NewMigration("Improve Action table indices v3", v1_20.ImproveActionTableIndices),
+ // v250 -> v251
+ NewMigration("Change Container Metadata", v1_20.ChangeContainerMetadataMultiArch),
+ // v251 -> v252
+ NewMigration("Fix incorrect owner team unit access mode", v1_20.FixIncorrectOwnerTeamUnitAccessMode),
+ // v252 -> v253
+ NewMigration("Fix incorrect admin team unit access mode", v1_20.FixIncorrectAdminTeamUnitAccessMode),
+ // v253 -> v254
+ NewMigration("Fix ExternalTracker and ExternalWiki accessMode in owner and admin team", v1_20.FixExternalTrackerAndExternalWikiAccessModeInOwnerAndAdminTeam),
+ // v254 -> v255
+ NewMigration("Add ActionTaskOutput table", v1_20.AddActionTaskOutputTable),
+ // v255 -> v256
+ NewMigration("Add ArchivedUnix Column", v1_20.AddArchivedUnixToRepository),
+ // v256 -> v257
+ NewMigration("Add is_internal column to package", v1_20.AddIsInternalColumnToPackage),
+ // v257 -> v258
+ NewMigration("Add Actions Artifact table", v1_20.CreateActionArtifactTable),
+ // v258 -> v259
+ NewMigration("Add PinOrder Column", v1_20.AddPinOrderToIssue),
+ // v259 -> v260
+ NewMigration("Convert scoped access tokens", v1_20.ConvertScopedAccessTokens),
+
+ // Gitea 1.20.0 ends at 260
+
+ // v260 -> v261
+ NewMigration("Drop custom_labels column of action_runner table", v1_21.DropCustomLabelsColumnOfActionRunner),
+ // v261 -> v262
+ NewMigration("Add variable table", v1_21.CreateVariableTable),
+ // v262 -> v263
+ NewMigration("Add TriggerEvent to action_run table", v1_21.AddTriggerEventToActionRun),
+ // v263 -> v264
+ NewMigration("Add git_size and lfs_size columns to repository table", v1_21.AddGitSizeAndLFSSizeToRepositoryTable),
+ // v264 -> v265
+ NewMigration("Add branch table", v1_21.AddBranchTable),
+ // v265 -> v266
+ NewMigration("Alter Actions Artifact table", v1_21.AlterActionArtifactTable),
+ // v266 -> v267
+ NewMigration("Reduce commit status", v1_21.ReduceCommitStatus),
+ // v267 -> v268
+ NewMigration("Add action_tasks_version table", v1_21.CreateActionTasksVersionTable),
+ // v268 -> v269
+ NewMigration("Update Action Ref", v1_21.UpdateActionsRefIndex),
+ // v269 -> v270
+ NewMigration("Drop deleted branch table", v1_21.DropDeletedBranchTable),
+ // v270 -> v271
+ NewMigration("Fix PackageProperty typo", v1_21.FixPackagePropertyTypo),
+ // v271 -> v272
+ NewMigration("Allow archiving labels", v1_21.AddArchivedUnixColumInLabelTable),
+ // v272 -> v273
+ NewMigration("Add Version to ActionRun table", v1_21.AddVersionToActionRunTable),
+ // v273 -> v274
+ NewMigration("Add Action Schedule Table", v1_21.AddActionScheduleTable),
+ // v274 -> v275
+ NewMigration("Add Actions artifacts expiration date", v1_21.AddExpiredUnixColumnInActionArtifactTable),
+ // v275 -> v276
+ NewMigration("Add ScheduleID for ActionRun", v1_21.AddScheduleIDForActionRun),
+ // v276 -> v277
+ NewMigration("Add RemoteAddress to mirrors", v1_21.AddRemoteAddressToMirrors),
+ // v277 -> v278
+ NewMigration("Add Index to issue_user.issue_id", v1_21.AddIndexToIssueUserIssueID),
+ // v278 -> v279
+ NewMigration("Add Index to comment.dependent_issue_id", v1_21.AddIndexToCommentDependentIssueID),
+ // v279 -> v280
+ NewMigration("Add Index to action.user_id", v1_21.AddIndexToActionUserID),
+
+ // Gitea 1.21.0 ends at 280
+
+ // v280 -> v281
+ NewMigration("Rename user themes", v1_22.RenameUserThemes),
+ // v281 -> v282
+ NewMigration("Add auth_token table", v1_22.CreateAuthTokenTable),
+ // v282 -> v283
+ NewMigration("Add Index to pull_auto_merge.doer_id", v1_22.AddIndexToPullAutoMergeDoerID),
+ // v283 -> v284
+ NewMigration("Add combined Index to issue_user.uid and issue_id", v1_22.AddCombinedIndexToIssueUser),
+ // v284 -> v285
+ NewMigration("Add ignore stale approval column on branch table", v1_22.AddIgnoreStaleApprovalsColumnToProtectedBranchTable),
+ // v285 -> v286
+ NewMigration("Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun),
+ // v286 -> v287
+ NewMigration("Add support for SHA256 git repositories", v1_22.AdjustDBForSha256),
+ // v287 -> v288
+ NewMigration("Use Slug instead of ID for Badges", v1_22.UseSlugInsteadOfIDForBadges),
+ // v288 -> v289
+ NewMigration("Add user_blocking table", v1_22.AddUserBlockingTable),
+ // v289 -> v290
+ NewMigration("Add default_wiki_branch to repository table", v1_22.AddDefaultWikiBranch),
+ // v290 -> v291
+ NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable),
+ // v291 -> v292
+ NewMigration("Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment),
+ // v292 -> v293
+ NewMigration("Ensure every project has exactly one default column - No Op", noopMigration),
+ // v293 -> v294
+ NewMigration("Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency),
+
+ // Gitea 1.22.0-rc0 ends at 294
+
+ // v294 -> v295
+ NewMigration("Add unique index for project issue table", v1_22.AddUniqueIndexForProjectIssue),
+ // v295 -> v296
+ NewMigration("Add commit status summary table", v1_22.AddCommitStatusSummary),
+ // v296 -> v297
+ NewMigration("Add missing field of commit status summary table", v1_22.AddCommitStatusSummary2),
+ // v297 -> v298
+ NewMigration("Add everyone_access_mode for repo_unit", noopMigration),
+ // v298 -> v299
+ NewMigration("Drop wrongly created table o_auth2_application", v1_22.DropWronglyCreatedTable),
+
+ // Gitea 1.22.0-rc1 ends at 299
+
+ // v299 -> v300
+ NewMigration("Add content version to issue and comment table", v1_23.AddContentVersionToIssueAndComment),
+}
+
+// GetCurrentDBVersion returns the current db version
+func GetCurrentDBVersion(x *xorm.Engine) (int64, error) {
+ if err := x.Sync(new(Version)); err != nil {
+ return -1, fmt.Errorf("sync: %w", err)
+ }
+
+ currentVersion := &Version{ID: 1}
+ has, err := x.Get(currentVersion)
+ if err != nil {
+ return -1, fmt.Errorf("get: %w", err)
+ }
+ if !has {
+ return -1, nil
+ }
+ return currentVersion.Version, nil
+}
+
+// ExpectedVersion returns the expected db version
+func ExpectedVersion() int64 {
+ return int64(minDBVersion + len(migrations))
+}
+
+// EnsureUpToDate will check if the db is at the correct version
+func EnsureUpToDate(x *xorm.Engine) error {
+ currentDB, err := GetCurrentDBVersion(x)
+ if err != nil {
+ return err
+ }
+
+ if currentDB < 0 {
+ return fmt.Errorf("Database has not been initialized")
+ }
+
+ if minDBVersion > currentDB {
+ return fmt.Errorf("DB version %d (<= %d) is too old for auto-migration. Upgrade to Gitea 1.6.4 first then upgrade to this version", currentDB, minDBVersion)
+ }
+
+ expected := ExpectedVersion()
+
+ if currentDB != expected {
+ return fmt.Errorf(`Current database version %d is not equal to the expected version %d. Please run "forgejo [--config /path/to/app.ini] migrate" to update the database version`, currentDB, expected)
+ }
+
+ return forgejo_migrations.EnsureUpToDate(x)
+}
+
+// Migrate database to current version
+func Migrate(x *xorm.Engine) error {
+ // Set a new clean the default mapper to GonicMapper as that is the default for Gitea.
+ x.SetMapper(names.GonicMapper{})
+ if err := x.Sync(new(Version)); err != nil {
+ return fmt.Errorf("sync: %w", err)
+ }
+
+ var previousVersion int64
+ currentVersion := &Version{ID: 1}
+ has, err := x.Get(currentVersion)
+ if err != nil {
+ return fmt.Errorf("get: %w", err)
+ } else if !has {
+ // If the version record does not exist we think
+ // it is a fresh installation and we can skip all migrations.
+ currentVersion.ID = 0
+ currentVersion.Version = int64(minDBVersion + len(migrations))
+
+ if _, err = x.InsertOne(currentVersion); err != nil {
+ return fmt.Errorf("insert: %w", err)
+ }
+ } else {
+ previousVersion = currentVersion.Version
+ }
+
+ v := currentVersion.Version
+ if minDBVersion > v {
+ log.Fatal(`Forgejo no longer supports auto-migration from your previously installed version.
+Please try upgrading to a lower version first (suggested v1.6.4), then upgrade to this version.`)
+ return nil
+ }
+
+ // Downgrading Forgejo database version is not supported
+ if int(v-minDBVersion) > len(migrations) {
+ msg := fmt.Sprintf("Your database (migration version: %d) is for a newer Forgejo, you can not use the newer database for this old Forgejo release (%d).", v, minDBVersion+len(migrations))
+ msg += "\nForgejo will exit to keep your database safe and unchanged. Please use the correct Forgejo release, do not change the migration version manually (incorrect manual operation may lose data)."
+ if !setting.IsProd {
+ msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE version SET version=%d WHERE id=1;", minDBVersion+len(migrations))
+ }
+ log.Fatal("Migration Error: %s", msg)
+ return nil
+ }
+
+ // Some migration tasks depend on the git command
+ if git.DefaultContext == nil {
+ if err = git.InitSimple(context.Background()); err != nil {
+ return err
+ }
+ }
+
+ if err := forgejo_services.PreMigrationSanityChecks(x, previousVersion, setting.CfgProvider); err != nil {
+ return err
+ }
+
+ // Migrate
+ for i, m := range migrations[v-minDBVersion:] {
+ log.Info("Migration[%d]: %s", v+int64(i), m.Description())
+ // Reset the mapper between each migration - migrations are not supposed to depend on each other
+ x.SetMapper(names.GonicMapper{})
+ if err = m.Migrate(x); err != nil {
+ return fmt.Errorf("migration[%d]: %s failed: %w", v+int64(i), m.Description(), err)
+ }
+ currentVersion.Version = v + int64(i) + 1
+ if _, err = x.ID(1).Update(currentVersion); err != nil {
+ return err
+ }
+ }
+
+ // Execute Forgejo specific migrations.
+ return forgejo_migrations.Migrate(x)
+}
diff --git a/models/migrations/v1_10/v100.go b/models/migrations/v1_10/v100.go
new file mode 100644
index 00000000..5d2fd8e2
--- /dev/null
+++ b/models/migrations/v1_10/v100.go
@@ -0,0 +1,82 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import (
+ "net/url"
+ "strings"
+ "time"
+
+ "xorm.io/xorm"
+)
+
+func UpdateMigrationServiceTypes(x *xorm.Engine) error {
+ type Repository struct {
+ ID int64
+ OriginalServiceType int `xorm:"index default(0)"`
+ OriginalURL string `xorm:"VARCHAR(2048)"`
+ }
+
+ if err := x.Sync(new(Repository)); err != nil {
+ return err
+ }
+
+ var last int
+ const batchSize = 50
+ for {
+ results := make([]Repository, 0, batchSize)
+ err := x.Where("original_url <> '' AND original_url IS NOT NULL").
+ And("original_service_type = 0 OR original_service_type IS NULL").
+ OrderBy("id").
+ Limit(batchSize, last).
+ Find(&results)
+ if err != nil {
+ return err
+ }
+ if len(results) == 0 {
+ break
+ }
+ last += len(results)
+
+ const PlainGitService = 1 // 1 plain git service
+ const GithubService = 2 // 2 github.com
+
+ for _, res := range results {
+ u, err := url.Parse(res.OriginalURL)
+ if err != nil {
+ return err
+ }
+ serviceType := PlainGitService
+ if strings.EqualFold(u.Host, "github.com") {
+ serviceType = GithubService
+ }
+ _, err = x.Exec("UPDATE repository SET original_service_type = ? WHERE id = ?", serviceType, res.ID)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ type ExternalLoginUser struct {
+ ExternalID string `xorm:"pk NOT NULL"`
+ UserID int64 `xorm:"INDEX NOT NULL"`
+ LoginSourceID int64 `xorm:"pk NOT NULL"`
+ RawData map[string]any `xorm:"TEXT JSON"`
+ Provider string `xorm:"index VARCHAR(25)"`
+ Email string
+ Name string
+ FirstName string
+ LastName string
+ NickName string
+ Description string
+ AvatarURL string
+ Location string
+ AccessToken string
+ AccessTokenSecret string
+ RefreshToken string
+ ExpiresAt time.Time
+ }
+
+ return x.Sync(new(ExternalLoginUser))
+}
diff --git a/models/migrations/v1_10/v101.go b/models/migrations/v1_10/v101.go
new file mode 100644
index 00000000..f023a2a0
--- /dev/null
+++ b/models/migrations/v1_10/v101.go
@@ -0,0 +1,18 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func ChangeSomeColumnsLengthOfExternalLoginUser(x *xorm.Engine) error {
+ type ExternalLoginUser struct {
+ AccessToken string `xorm:"TEXT"`
+ AccessTokenSecret string `xorm:"TEXT"`
+ RefreshToken string `xorm:"TEXT"`
+ }
+
+ return x.Sync(new(ExternalLoginUser))
+}
diff --git a/models/migrations/v1_10/v88.go b/models/migrations/v1_10/v88.go
new file mode 100644
index 00000000..7e86ac36
--- /dev/null
+++ b/models/migrations/v1_10/v88.go
@@ -0,0 +1,65 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import (
+ "crypto/sha1"
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func hashContext(context string) string {
+ return fmt.Sprintf("%x", sha1.Sum([]byte(context)))
+}
+
+func AddCommitStatusContext(x *xorm.Engine) error {
+ type CommitStatus struct {
+ ID int64 `xorm:"pk autoincr"`
+ ContextHash string `xorm:"char(40) index"`
+ Context string `xorm:"TEXT"`
+ }
+
+ if err := x.Sync(new(CommitStatus)); err != nil {
+ return err
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ start := 0
+ for {
+ statuses := make([]*CommitStatus, 0, 100)
+ err := sess.OrderBy("id").Limit(100, start).Find(&statuses)
+ if err != nil {
+ return err
+ }
+ if len(statuses) == 0 {
+ break
+ }
+
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ for _, status := range statuses {
+ status.ContextHash = hashContext(status.Context)
+ if _, err := sess.ID(status.ID).Cols("context_hash").Update(status); err != nil {
+ return err
+ }
+ }
+
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+
+ if len(statuses) < 100 {
+ break
+ }
+
+ start += len(statuses)
+ }
+
+ return nil
+}
diff --git a/models/migrations/v1_10/v89.go b/models/migrations/v1_10/v89.go
new file mode 100644
index 00000000..d5f27ffd
--- /dev/null
+++ b/models/migrations/v1_10/v89.go
@@ -0,0 +1,35 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import "xorm.io/xorm"
+
+func AddOriginalMigrationInfo(x *xorm.Engine) error {
+ // Issue see models/issue.go
+ type Issue struct {
+ OriginalAuthor string
+ OriginalAuthorID int64
+ }
+
+ if err := x.Sync(new(Issue)); err != nil {
+ return err
+ }
+
+ // Issue see models/issue_comment.go
+ type Comment struct {
+ OriginalAuthor string
+ OriginalAuthorID int64
+ }
+
+ if err := x.Sync(new(Comment)); err != nil {
+ return err
+ }
+
+ // Issue see models/repo.go
+ type Repository struct {
+ OriginalURL string
+ }
+
+ return x.Sync(new(Repository))
+}
diff --git a/models/migrations/v1_10/v90.go b/models/migrations/v1_10/v90.go
new file mode 100644
index 00000000..295d4b1c
--- /dev/null
+++ b/models/migrations/v1_10/v90.go
@@ -0,0 +1,17 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import "xorm.io/xorm"
+
+func ChangeSomeColumnsLengthOfRepo(x *xorm.Engine) error {
+ type Repository struct {
+ ID int64 `xorm:"pk autoincr"`
+ Description string `xorm:"TEXT"`
+ Website string `xorm:"VARCHAR(2048)"`
+ OriginalURL string `xorm:"VARCHAR(2048)"`
+ }
+
+ return x.Sync(new(Repository))
+}
diff --git a/models/migrations/v1_10/v91.go b/models/migrations/v1_10/v91.go
new file mode 100644
index 00000000..48cac2de
--- /dev/null
+++ b/models/migrations/v1_10/v91.go
@@ -0,0 +1,25 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import "xorm.io/xorm"
+
+func AddIndexOnRepositoryAndComment(x *xorm.Engine) error {
+ type Repository struct {
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"index"`
+ }
+
+ if err := x.Sync(new(Repository)); err != nil {
+ return err
+ }
+
+ type Comment struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type int `xorm:"index"`
+ ReviewID int64 `xorm:"index"`
+ }
+
+ return x.Sync(new(Comment))
+}
diff --git a/models/migrations/v1_10/v92.go b/models/migrations/v1_10/v92.go
new file mode 100644
index 00000000..90801085
--- /dev/null
+++ b/models/migrations/v1_10/v92.go
@@ -0,0 +1,14 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import (
+ "xorm.io/builder"
+ "xorm.io/xorm"
+)
+
+func RemoveLingeringIndexStatus(x *xorm.Engine) error {
+ _, err := x.Exec(builder.Delete(builder.NotIn("`repo_id`", builder.Select("`id`").From("`repository`"))).From("`repo_indexer_status`"))
+ return err
+}
diff --git a/models/migrations/v1_10/v93.go b/models/migrations/v1_10/v93.go
new file mode 100644
index 00000000..ee59a8db
--- /dev/null
+++ b/models/migrations/v1_10/v93.go
@@ -0,0 +1,15 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import "xorm.io/xorm"
+
+func AddEmailNotificationEnabledToUser(x *xorm.Engine) error {
+ // User see models/user.go
+ type User struct {
+ EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"`
+ }
+
+ return x.Sync(new(User))
+}
diff --git a/models/migrations/v1_10/v94.go b/models/migrations/v1_10/v94.go
new file mode 100644
index 00000000..c131af16
--- /dev/null
+++ b/models/migrations/v1_10/v94.go
@@ -0,0 +1,23 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import "xorm.io/xorm"
+
+func AddStatusCheckColumnsForProtectedBranches(x *xorm.Engine) error {
+ type ProtectedBranch struct {
+ EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
+ StatusCheckContexts []string `xorm:"JSON TEXT"`
+ }
+
+ if err := x.Sync(new(ProtectedBranch)); err != nil {
+ return err
+ }
+
+ _, err := x.Cols("enable_status_check", "status_check_contexts").Update(&ProtectedBranch{
+ EnableStatusCheck: false,
+ StatusCheckContexts: []string{},
+ })
+ return err
+}
diff --git a/models/migrations/v1_10/v95.go b/models/migrations/v1_10/v95.go
new file mode 100644
index 00000000..3b1f67fd
--- /dev/null
+++ b/models/migrations/v1_10/v95.go
@@ -0,0 +1,19 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import "xorm.io/xorm"
+
+func AddCrossReferenceColumns(x *xorm.Engine) error {
+ // Comment see models/comment.go
+ type Comment struct {
+ RefRepoID int64 `xorm:"index"`
+ RefIssueID int64 `xorm:"index"`
+ RefCommentID int64 `xorm:"index"`
+ RefAction int64 `xorm:"SMALLINT"`
+ RefIsPull bool
+ }
+
+ return x.Sync(new(Comment))
+}
diff --git a/models/migrations/v1_10/v96.go b/models/migrations/v1_10/v96.go
new file mode 100644
index 00000000..34c82400
--- /dev/null
+++ b/models/migrations/v1_10/v96.go
@@ -0,0 +1,64 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import (
+ "path/filepath"
+
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+
+ "xorm.io/xorm"
+)
+
+func DeleteOrphanedAttachments(x *xorm.Engine) error {
+ type Attachment struct {
+ ID int64 `xorm:"pk autoincr"`
+ UUID string `xorm:"uuid UNIQUE"`
+ IssueID int64 `xorm:"INDEX"`
+ ReleaseID int64 `xorm:"INDEX"`
+ CommentID int64
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ limit := setting.Database.IterateBufferSize
+ if limit <= 0 {
+ limit = 50
+ }
+
+ for {
+ attachments := make([]Attachment, 0, limit)
+ if err := sess.Where("`issue_id` = 0 and (`release_id` = 0 or `release_id` not in (select `id` from `release`))").
+ Cols("id, uuid").Limit(limit).
+ Asc("id").
+ Find(&attachments); err != nil {
+ return err
+ }
+ if len(attachments) == 0 {
+ return nil
+ }
+
+ ids := make([]int64, 0, limit)
+ for _, attachment := range attachments {
+ ids = append(ids, attachment.ID)
+ }
+ if len(ids) > 0 {
+ if _, err := sess.In("id", ids).Delete(new(Attachment)); err != nil {
+ return err
+ }
+ }
+
+ for _, attachment := range attachments {
+ uuid := attachment.UUID
+ if err := util.RemoveAll(filepath.Join(setting.Attachment.Storage.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
+ return err
+ }
+ }
+ if len(attachments) < limit {
+ return nil
+ }
+ }
+}
diff --git a/models/migrations/v1_10/v97.go b/models/migrations/v1_10/v97.go
new file mode 100644
index 00000000..dee45b32
--- /dev/null
+++ b/models/migrations/v1_10/v97.go
@@ -0,0 +1,14 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import "xorm.io/xorm"
+
+func AddRepoAdminChangeTeamAccessColumnForUser(x *xorm.Engine) error {
+ type User struct {
+ RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(User))
+}
diff --git a/models/migrations/v1_10/v98.go b/models/migrations/v1_10/v98.go
new file mode 100644
index 00000000..bdd9aed0
--- /dev/null
+++ b/models/migrations/v1_10/v98.go
@@ -0,0 +1,16 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import "xorm.io/xorm"
+
+func AddOriginalAuthorOnMigratedReleases(x *xorm.Engine) error {
+ type Release struct {
+ ID int64
+ OriginalAuthor string
+ OriginalAuthorID int64 `xorm:"index"`
+ }
+
+ return x.Sync(new(Release))
+}
diff --git a/models/migrations/v1_10/v99.go b/models/migrations/v1_10/v99.go
new file mode 100644
index 00000000..ebe6597f
--- /dev/null
+++ b/models/migrations/v1_10/v99.go
@@ -0,0 +1,38 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_10 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddTaskTable(x *xorm.Engine) error {
+ // TaskType defines task type
+ type TaskType int
+
+ // TaskStatus defines task status
+ type TaskStatus int
+
+ type Task struct {
+ ID int64
+ DoerID int64 `xorm:"index"` // operator
+ OwnerID int64 `xorm:"index"` // repo owner id, when creating, the repoID maybe zero
+ RepoID int64 `xorm:"index"`
+ Type TaskType
+ Status TaskStatus `xorm:"index"`
+ StartTime timeutil.TimeStamp
+ EndTime timeutil.TimeStamp
+ PayloadContent string `xorm:"TEXT"`
+ Errors string `xorm:"TEXT"` // if task failed, saved the error reason
+ Created timeutil.TimeStamp `xorm:"created"`
+ }
+
+ type Repository struct {
+ Status int `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ return x.Sync(new(Task), new(Repository))
+}
diff --git a/models/migrations/v1_11/v102.go b/models/migrations/v1_11/v102.go
new file mode 100644
index 00000000..9358e4ce
--- /dev/null
+++ b/models/migrations/v1_11/v102.go
@@ -0,0 +1,22 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "xorm.io/xorm"
+)
+
+func DropColumnHeadUserNameOnPullRequest(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ if err := base.DropTableColumns(sess, "pull_request", "head_user_name"); err != nil {
+ return err
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_11/v103.go b/models/migrations/v1_11/v103.go
new file mode 100644
index 00000000..53527dac
--- /dev/null
+++ b/models/migrations/v1_11/v103.go
@@ -0,0 +1,17 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddWhitelistDeployKeysToBranches(x *xorm.Engine) error {
+ type ProtectedBranch struct {
+ ID int64
+ WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(ProtectedBranch))
+}
diff --git a/models/migrations/v1_11/v104.go b/models/migrations/v1_11/v104.go
new file mode 100644
index 00000000..3e8ee64b
--- /dev/null
+++ b/models/migrations/v1_11/v104.go
@@ -0,0 +1,34 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "xorm.io/xorm"
+)
+
+func RemoveLabelUneededCols(x *xorm.Engine) error {
+ // Make sure the columns exist before dropping them
+ type Label struct {
+ QueryString string
+ IsSelected bool
+ }
+ if err := x.Sync(new(Label)); err != nil {
+ return err
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ if err := base.DropTableColumns(sess, "label", "query_string"); err != nil {
+ return err
+ }
+ if err := base.DropTableColumns(sess, "label", "is_selected"); err != nil {
+ return err
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_11/v105.go b/models/migrations/v1_11/v105.go
new file mode 100644
index 00000000..b91340c3
--- /dev/null
+++ b/models/migrations/v1_11/v105.go
@@ -0,0 +1,23 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddTeamIncludesAllRepositories(x *xorm.Engine) error {
+ type Team struct {
+ ID int64 `xorm:"pk autoincr"`
+ IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ if err := x.Sync(new(Team)); err != nil {
+ return err
+ }
+
+ _, err := x.Exec("UPDATE `team` SET `includes_all_repositories` = ? WHERE `name`=?",
+ true, "Owners")
+ return err
+}
diff --git a/models/migrations/v1_11/v106.go b/models/migrations/v1_11/v106.go
new file mode 100644
index 00000000..ecb11cdd
--- /dev/null
+++ b/models/migrations/v1_11/v106.go
@@ -0,0 +1,25 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+// RepoWatchMode specifies what kind of watch the user has on a repository
+type RepoWatchMode int8
+
+// Watch is connection request for receiving repository notification.
+type Watch struct {
+ ID int64 `xorm:"pk autoincr"`
+ Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
+}
+
+func AddModeColumnToWatch(x *xorm.Engine) error {
+ if err := x.Sync(new(Watch)); err != nil {
+ return err
+ }
+ _, err := x.Exec("UPDATE `watch` SET `mode` = 1")
+ return err
+}
diff --git a/models/migrations/v1_11/v107.go b/models/migrations/v1_11/v107.go
new file mode 100644
index 00000000..f0bfe586
--- /dev/null
+++ b/models/migrations/v1_11/v107.go
@@ -0,0 +1,17 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddTemplateToRepo(x *xorm.Engine) error {
+ type Repository struct {
+ IsTemplate bool `xorm:"INDEX NOT NULL DEFAULT false"`
+ TemplateID int64 `xorm:"INDEX"`
+ }
+
+ return x.Sync(new(Repository))
+}
diff --git a/models/migrations/v1_11/v108.go b/models/migrations/v1_11/v108.go
new file mode 100644
index 00000000..a8509623
--- /dev/null
+++ b/models/migrations/v1_11/v108.go
@@ -0,0 +1,17 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddCommentIDOnNotification(x *xorm.Engine) error {
+ type Notification struct {
+ ID int64 `xorm:"pk autoincr"`
+ CommentID int64
+ }
+
+ return x.Sync(new(Notification))
+}
diff --git a/models/migrations/v1_11/v109.go b/models/migrations/v1_11/v109.go
new file mode 100644
index 00000000..ea565ccd
--- /dev/null
+++ b/models/migrations/v1_11/v109.go
@@ -0,0 +1,16 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddCanCreateOrgRepoColumnForTeam(x *xorm.Engine) error {
+ type Team struct {
+ CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(Team))
+}
diff --git a/models/migrations/v1_11/v110.go b/models/migrations/v1_11/v110.go
new file mode 100644
index 00000000..fce9be84
--- /dev/null
+++ b/models/migrations/v1_11/v110.go
@@ -0,0 +1,26 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+func ChangeReviewContentToText(x *xorm.Engine) error {
+ switch x.Dialect().URI().DBType {
+ case schemas.MYSQL:
+ _, err := x.Exec("ALTER TABLE review MODIFY COLUMN content TEXT")
+ return err
+ case schemas.ORACLE:
+ _, err := x.Exec("ALTER TABLE review MODIFY content TEXT")
+ return err
+ case schemas.POSTGRES:
+ _, err := x.Exec("ALTER TABLE review ALTER COLUMN content TYPE TEXT")
+ return err
+ default:
+ // SQLite doesn't support ALTER COLUMN, and it seem to already make String to _TEXT_ default so no migration needed
+ return nil
+ }
+}
diff --git a/models/migrations/v1_11/v111.go b/models/migrations/v1_11/v111.go
new file mode 100644
index 00000000..cc3dc0d5
--- /dev/null
+++ b/models/migrations/v1_11/v111.go
@@ -0,0 +1,437 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
+ type ProtectedBranch struct {
+ CanPush bool `xorm:"NOT NULL DEFAULT false"`
+ EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
+ ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
+ ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
+ RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ type User struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type int
+
+ // Permissions
+ IsAdmin bool
+ // IsRestricted bool `xorm:"NOT NULL DEFAULT false"` glitch: this column was added in v1_12/v121.go
+ // Visibility int `xorm:"NOT NULL DEFAULT 0"` glitch: this column was added in v1_12/v124.go
+ }
+
+ type Review struct {
+ ID int64 `xorm:"pk autoincr"`
+ Official bool `xorm:"NOT NULL DEFAULT false"`
+
+ ReviewerID int64 `xorm:"index"`
+ IssueID int64 `xorm:"index"`
+ }
+
+ if err := x.Sync(new(ProtectedBranch)); err != nil {
+ return err
+ }
+
+ if err := x.Sync(new(Review)); err != nil {
+ return err
+ }
+
+ const (
+ // ReviewTypeApprove approves changes
+ ReviewTypeApprove int = 1
+ // ReviewTypeReject gives feedback blocking merge
+ ReviewTypeReject int = 3
+
+ // VisibleTypePublic Visible for everyone
+ // VisibleTypePublic int = 0
+ // VisibleTypePrivate Visible only for organization's members
+ // VisibleTypePrivate int = 2
+
+ // unit.UnitTypeCode is unit type code
+ UnitTypeCode int = 1
+
+ // AccessModeNone no access
+ AccessModeNone int = 0
+ // AccessModeRead read access
+ AccessModeRead int = 1
+ // AccessModeWrite write access
+ AccessModeWrite int = 2
+ // AccessModeOwner owner access
+ AccessModeOwner int = 4
+ )
+
+ // Repository represents a git repository.
+ type Repository struct {
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"UNIQUE(s) index"`
+
+ IsPrivate bool `xorm:"INDEX"`
+ }
+
+ type PullRequest struct {
+ ID int64 `xorm:"pk autoincr"`
+
+ BaseRepoID int64 `xorm:"INDEX"`
+ BaseBranch string
+ }
+
+ // RepoUnit describes all units of a repository
+ type RepoUnit struct {
+ ID int64
+ RepoID int64 `xorm:"INDEX(s)"`
+ Type int `xorm:"INDEX(s)"`
+ }
+
+ type Permission struct {
+ AccessMode int
+ Units []*RepoUnit
+ UnitsMode map[int]int
+ }
+
+ type TeamUser struct {
+ ID int64 `xorm:"pk autoincr"`
+ TeamID int64 `xorm:"UNIQUE(s)"`
+ UID int64 `xorm:"UNIQUE(s)"`
+ }
+
+ type Collaboration struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ Mode int `xorm:"DEFAULT 2 NOT NULL"`
+ }
+
+ type Access struct {
+ ID int64 `xorm:"pk autoincr"`
+ UserID int64 `xorm:"UNIQUE(s)"`
+ RepoID int64 `xorm:"UNIQUE(s)"`
+ Mode int
+ }
+
+ type TeamUnit struct {
+ ID int64 `xorm:"pk autoincr"`
+ OrgID int64 `xorm:"INDEX"`
+ TeamID int64 `xorm:"UNIQUE(s)"`
+ Type int `xorm:"UNIQUE(s)"`
+ }
+
+ // Team represents a organization team.
+ type Team struct {
+ ID int64 `xorm:"pk autoincr"`
+ OrgID int64 `xorm:"INDEX"`
+ Authorize int
+ }
+
+ // getUserRepoPermission static function based on issues_model.IsOfficialReviewer at 5d78792385
+ getUserRepoPermission := func(sess *xorm.Session, repo *Repository, user *User) (Permission, error) {
+ var perm Permission
+
+ repoOwner := new(User)
+ has, err := sess.ID(repo.OwnerID).Get(repoOwner)
+ if err != nil || !has {
+ return perm, err
+ }
+
+ // Prevent strangers from checking out public repo of private organization
+ // Allow user if they are collaborator of a repo within a private organization but not a member of the organization itself
+ hasOrgVisible := true
+ // Not SignedUser
+ if user == nil {
+ // hasOrgVisible = repoOwner.Visibility == VisibleTypePublic // VisibleTypePublic is the default
+ } else if !user.IsAdmin {
+ _, err := sess.
+ Where("uid=?", user.ID).
+ And("org_id=?", repoOwner.ID).
+ Table("org_user").
+ Exist()
+ if err != nil {
+ hasOrgVisible = false
+ }
+ // VisibleTypePublic is the default so the condition below is always false
+ // if (repoOwner.Visibility == VisibleTypePrivate) && !hasMemberWithUserID {
+ // hasOrgVisible = false
+ // }
+ }
+
+ isCollaborator, err := sess.Get(&Collaboration{RepoID: repo.ID, UserID: user.ID})
+ if err != nil {
+ return perm, err
+ }
+
+ if repoOwner.Type == 1 && !hasOrgVisible && !isCollaborator {
+ perm.AccessMode = AccessModeNone
+ return perm, err
+ }
+
+ var units []*RepoUnit
+ if err := sess.Where("repo_id = ?", repo.ID).Find(&units); err != nil {
+ return perm, err
+ }
+ perm.Units = units
+
+ // anonymous visit public repo
+ if user == nil {
+ perm.AccessMode = AccessModeRead
+ return perm, err
+ }
+
+ // Admin or the owner has super access to the repository
+ if user.IsAdmin || user.ID == repo.OwnerID {
+ perm.AccessMode = AccessModeOwner
+ return perm, err
+ }
+
+ accessLevel := func(user *User, repo *Repository) (int, error) {
+ mode := AccessModeNone
+ var userID int64
+ restricted := false
+
+ if user != nil {
+ userID = user.ID
+ restricted = false
+ }
+
+ if !restricted && !repo.IsPrivate {
+ mode = AccessModeRead
+ }
+
+ if userID == 0 {
+ return mode, nil
+ }
+
+ if userID == repo.OwnerID {
+ return AccessModeOwner, nil
+ }
+
+ a := &Access{UserID: userID, RepoID: repo.ID}
+ if has, err := sess.Get(a); !has || err != nil {
+ return mode, err
+ }
+ return a.Mode, nil
+ }
+
+ // plain user
+ perm.AccessMode, err = accessLevel(user, repo)
+ if err != nil {
+ return perm, err
+ }
+
+ // If Owner is no Org
+ if repoOwner.Type != 1 {
+ return perm, err
+ }
+
+ perm.UnitsMode = make(map[int]int)
+
+ // Collaborators on organization
+ if isCollaborator {
+ for _, u := range units {
+ perm.UnitsMode[u.Type] = perm.AccessMode
+ }
+ }
+
+ // get units mode from teams
+ var teams []*Team
+ err = sess.
+ Join("INNER", "team_user", "team_user.team_id = team.id").
+ Join("INNER", "team_repo", "team_repo.team_id = team.id").
+ Where("team.org_id = ?", repo.OwnerID).
+ And("team_user.uid=?", user.ID).
+ And("team_repo.repo_id=?", repo.ID).
+ Find(&teams)
+ if err != nil {
+ return perm, err
+ }
+
+ // if user in an owner team
+ for _, team := range teams {
+ if team.Authorize >= AccessModeOwner {
+ perm.AccessMode = AccessModeOwner
+ perm.UnitsMode = nil
+ return perm, err
+ }
+ }
+
+ for _, u := range units {
+ var found bool
+ for _, team := range teams {
+ var teamU []*TeamUnit
+ var unitEnabled bool
+ err = sess.Where("team_id = ?", team.ID).Find(&teamU)
+
+ for _, tu := range teamU {
+ if tu.Type == u.Type {
+ unitEnabled = true
+ break
+ }
+ }
+
+ if unitEnabled {
+ m := perm.UnitsMode[u.Type]
+ if m < team.Authorize {
+ perm.UnitsMode[u.Type] = team.Authorize
+ }
+ found = true
+ }
+ }
+
+ // for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
+ if !found && !repo.IsPrivate {
+ if _, ok := perm.UnitsMode[u.Type]; !ok {
+ perm.UnitsMode[u.Type] = AccessModeRead
+ }
+ }
+ }
+
+ // remove no permission units
+ perm.Units = make([]*RepoUnit, 0, len(units))
+ for t := range perm.UnitsMode {
+ for _, u := range units {
+ if u.Type == t {
+ perm.Units = append(perm.Units, u)
+ }
+ }
+ }
+
+ return perm, err
+ }
+
+ // isOfficialReviewer static function based on 5d78792385
+ isOfficialReviewer := func(sess *xorm.Session, issueID int64, reviewer *User) (bool, error) {
+ pr := new(PullRequest)
+ has, err := sess.ID(issueID).Get(pr)
+ if err != nil {
+ return false, err
+ } else if !has {
+ return false, fmt.Errorf("PullRequest for issueID %d not exist", issueID)
+ }
+
+ baseRepo := new(Repository)
+ has, err = sess.ID(pr.BaseRepoID).Get(baseRepo)
+ if err != nil {
+ return false, err
+ } else if !has {
+ return false, fmt.Errorf("baseRepo with id %d not exist", pr.BaseRepoID)
+ }
+ protectedBranch := new(ProtectedBranch)
+ has, err = sess.Where("repo_id=? AND branch_name=?", baseRepo.ID, pr.BaseBranch).Get(protectedBranch)
+ if err != nil {
+ return false, err
+ }
+ if !has {
+ return false, nil
+ }
+
+ if !protectedBranch.EnableApprovalsWhitelist {
+ perm, err := getUserRepoPermission(sess, baseRepo, reviewer)
+ if err != nil {
+ return false, err
+ }
+ if perm.UnitsMode == nil {
+ for _, u := range perm.Units {
+ if u.Type == UnitTypeCode {
+ return AccessModeWrite <= perm.AccessMode, nil
+ }
+ }
+ return false, nil
+ }
+ return AccessModeWrite <= perm.UnitsMode[UnitTypeCode], nil
+ }
+ for _, id := range protectedBranch.ApprovalsWhitelistUserIDs {
+ if id == reviewer.ID {
+ return true, nil
+ }
+ }
+
+ // isUserInTeams
+ return sess.Where("uid=?", reviewer.ID).In("team_id", protectedBranch.ApprovalsWhitelistTeamIDs).Exist(new(TeamUser))
+ }
+
+ if _, err := x.Exec("UPDATE `protected_branch` SET `enable_whitelist` = ? WHERE enable_whitelist IS NULL", false); err != nil {
+ return err
+ }
+ if _, err := x.Exec("UPDATE `protected_branch` SET `can_push` = `enable_whitelist`"); err != nil {
+ return err
+ }
+ if _, err := x.Exec("UPDATE `protected_branch` SET `enable_approvals_whitelist` = ? WHERE `required_approvals` > ?", true, 0); err != nil {
+ return err
+ }
+
+ var pageSize int64 = 20
+ qresult, err := x.QueryInterface("SELECT max(id) as max_id FROM issue")
+ if err != nil {
+ return err
+ }
+ var totalIssues int64
+ totalIssues, ok := qresult[0]["max_id"].(int64)
+ if !ok {
+ // If there are no issues at all we ignore it
+ return nil
+ }
+ totalPages := totalIssues / pageSize
+
+ executeBody := func(page, pageSize int64) error {
+ // Find latest review of each user in each pull request, and set official field if appropriate
+ reviews := []*Review{}
+
+ if err := x.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id > ? AND issue_id <= ? AND type in (?, ?) GROUP BY issue_id, reviewer_id)",
+ page*pageSize, (page+1)*pageSize, ReviewTypeApprove, ReviewTypeReject).
+ Find(&reviews); err != nil {
+ return err
+ }
+
+ if len(reviews) == 0 {
+ return nil
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ var updated int
+ for _, review := range reviews {
+ reviewer := new(User)
+ has, err := sess.ID(review.ReviewerID).Get(reviewer)
+ if err != nil || !has {
+ // Error might occur if user doesn't exist, ignore it.
+ continue
+ }
+
+ official, err := isOfficialReviewer(sess, review.IssueID, reviewer)
+ if err != nil {
+ // Branch might not be proteced or other error, ignore it.
+ continue
+ }
+ review.Official = official
+ updated++
+ if _, err := sess.ID(review.ID).Cols("official").Update(review); err != nil {
+ return err
+ }
+ }
+
+ if updated > 0 {
+ return sess.Commit()
+ }
+ return nil
+ }
+
+ var page int64
+ for page = 0; page <= totalPages; page++ {
+ if err := executeBody(page, pageSize); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/models/migrations/v1_11/v112.go b/models/migrations/v1_11/v112.go
new file mode 100644
index 00000000..08576631
--- /dev/null
+++ b/models/migrations/v1_11/v112.go
@@ -0,0 +1,47 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "fmt"
+ "path/filepath"
+
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+
+ "xorm.io/builder"
+ "xorm.io/xorm"
+)
+
+func RemoveAttachmentMissedRepo(x *xorm.Engine) error {
+ type Attachment struct {
+ UUID string `xorm:"uuid"`
+ }
+ var start int
+ attachments := make([]*Attachment, 0, 50)
+ for {
+ err := x.Select("uuid").Where(builder.NotIn("release_id", builder.Select("id").From("`release`"))).
+ And("release_id > 0").
+ OrderBy("id").Limit(50, start).Find(&attachments)
+ if err != nil {
+ return err
+ }
+
+ for i := 0; i < len(attachments); i++ {
+ uuid := attachments[i].UUID
+ if err = util.RemoveAll(filepath.Join(setting.Attachment.Storage.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
+ fmt.Printf("Error: %v", err) //nolint:forbidigo
+ }
+ }
+
+ if len(attachments) < 50 {
+ break
+ }
+ start += 50
+ attachments = attachments[:0]
+ }
+
+ _, err := x.Exec("DELETE FROM attachment WHERE release_id > 0 AND release_id NOT IN (SELECT id FROM `release`)")
+ return err
+}
diff --git a/models/migrations/v1_11/v113.go b/models/migrations/v1_11/v113.go
new file mode 100644
index 00000000..dea344a4
--- /dev/null
+++ b/models/migrations/v1_11/v113.go
@@ -0,0 +1,22 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func FeatureChangeTargetBranch(x *xorm.Engine) error {
+ type Comment struct {
+ OldRef string
+ NewRef string
+ }
+
+ if err := x.Sync(new(Comment)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_11/v114.go b/models/migrations/v1_11/v114.go
new file mode 100644
index 00000000..95adcee9
--- /dev/null
+++ b/models/migrations/v1_11/v114.go
@@ -0,0 +1,50 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "net/url"
+
+ "xorm.io/xorm"
+)
+
+func SanitizeOriginalURL(x *xorm.Engine) error {
+ type Repository struct {
+ ID int64
+ OriginalURL string `xorm:"VARCHAR(2048)"`
+ }
+
+ var last int
+ const batchSize = 50
+ for {
+ results := make([]Repository, 0, batchSize)
+ err := x.Where("original_url <> '' AND original_url IS NOT NULL").
+ And("original_service_type = 0 OR original_service_type IS NULL").
+ OrderBy("id").
+ Limit(batchSize, last).
+ Find(&results)
+ if err != nil {
+ return err
+ }
+ if len(results) == 0 {
+ break
+ }
+ last += len(results)
+
+ for _, res := range results {
+ u, err := url.Parse(res.OriginalURL)
+ if err != nil {
+ // it is ok to continue here, we only care about fixing URLs that we can read
+ continue
+ }
+ u.User = nil
+ originalURL := u.String()
+ _, err = x.Exec("UPDATE repository SET original_url = ? WHERE id = ?", originalURL, res.ID)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
diff --git a/models/migrations/v1_11/v115.go b/models/migrations/v1_11/v115.go
new file mode 100644
index 00000000..8c631cfd
--- /dev/null
+++ b/models/migrations/v1_11/v115.go
@@ -0,0 +1,159 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "crypto/md5"
+ "fmt"
+ "io"
+ "math"
+ "os"
+ "path/filepath"
+ "time"
+
+ "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+
+ "xorm.io/xorm"
+)
+
+func RenameExistingUserAvatarName(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+
+ type User struct {
+ ID int64 `xorm:"pk autoincr"`
+ LowerName string `xorm:"UNIQUE NOT NULL"`
+ Avatar string
+ }
+
+ ticker := time.NewTicker(5 * time.Second)
+ defer ticker.Stop()
+
+ count, err := x.Count(new(User))
+ if err != nil {
+ return err
+ }
+ log.Info("%d User Avatar(s) to migrate ...", count)
+
+ deleteList := make(container.Set[string])
+ start := 0
+ migrated := 0
+ for {
+ if err := sess.Begin(); err != nil {
+ return fmt.Errorf("session.Begin: %w", err)
+ }
+ users := make([]*User, 0, 50)
+ if err := sess.Table("user").Asc("id").Limit(50, start).Find(&users); err != nil {
+ return fmt.Errorf("select users from id [%d]: %w", start, err)
+ }
+ if len(users) == 0 {
+ _ = sess.Rollback()
+ break
+ }
+
+ log.Info("select users [%d - %d]", start, start+len(users))
+ start += 50
+
+ for _, user := range users {
+ oldAvatar := user.Avatar
+
+ if stat, err := os.Stat(filepath.Join(setting.Avatar.Storage.Path, oldAvatar)); err != nil || !stat.Mode().IsRegular() {
+ if err == nil {
+ err = fmt.Errorf("Error: \"%s\" is not a regular file", oldAvatar)
+ }
+ log.Warn("[user: %s] os.Stat: %v", user.LowerName, err)
+ // avatar doesn't exist in the storage
+ // no need to move avatar and update database
+ // we can just skip this
+ continue
+ }
+
+ newAvatar, err := copyOldAvatarToNewLocation(user.ID, oldAvatar)
+ if err != nil {
+ _ = sess.Rollback()
+ return fmt.Errorf("[user: %s] %w", user.LowerName, err)
+ } else if newAvatar == oldAvatar {
+ continue
+ }
+
+ user.Avatar = newAvatar
+ if _, err := sess.ID(user.ID).Cols("avatar").Update(user); err != nil {
+ _ = sess.Rollback()
+ return fmt.Errorf("[user: %s] user table update: %w", user.LowerName, err)
+ }
+
+ deleteList.Add(filepath.Join(setting.Avatar.Storage.Path, oldAvatar))
+ migrated++
+ select {
+ case <-ticker.C:
+ log.Info(
+ "%d/%d (%2.0f%%) User Avatar(s) migrated (%d old avatars to be deleted) in %d batches. %d Remaining ...",
+ migrated,
+ count,
+ float64(migrated)/float64(count)*100,
+ len(deleteList),
+ int(math.Ceil(float64(migrated)/float64(50))),
+ count-int64(migrated))
+ default:
+ }
+ }
+ if err := sess.Commit(); err != nil {
+ _ = sess.Rollback()
+ return fmt.Errorf("commit session: %w", err)
+ }
+ }
+
+ deleteCount := len(deleteList)
+ log.Info("Deleting %d old avatars ...", deleteCount)
+ i := 0
+ for file := range deleteList {
+ if err := util.Remove(file); err != nil {
+ log.Warn("util.Remove: %v", err)
+ }
+ i++
+ select {
+ case <-ticker.C:
+ log.Info(
+ "%d/%d (%2.0f%%) Old User Avatar(s) deleted. %d Remaining ...",
+ i,
+ deleteCount,
+ float64(i)/float64(deleteCount)*100,
+ deleteCount-i)
+ default:
+ }
+ }
+
+ log.Info("Completed migrating %d User Avatar(s) and deleting %d Old Avatars", count, deleteCount)
+
+ return nil
+}
+
+// copyOldAvatarToNewLocation copies oldAvatar to newAvatarLocation
+// and returns newAvatar location
+func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) {
+ fr, err := os.Open(filepath.Join(setting.Avatar.Storage.Path, oldAvatar))
+ if err != nil {
+ return "", fmt.Errorf("os.Open: %w", err)
+ }
+ defer fr.Close()
+
+ data, err := io.ReadAll(fr)
+ if err != nil {
+ return "", fmt.Errorf("io.ReadAll: %w", err)
+ }
+
+ newAvatar := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", userID, md5.Sum(data)))))
+ if newAvatar == oldAvatar {
+ return newAvatar, nil
+ }
+
+ if err := os.WriteFile(filepath.Join(setting.Avatar.Storage.Path, newAvatar), data, 0o666); err != nil {
+ return "", fmt.Errorf("os.WriteFile: %w", err)
+ }
+
+ return newAvatar, nil
+}
diff --git a/models/migrations/v1_11/v116.go b/models/migrations/v1_11/v116.go
new file mode 100644
index 00000000..85aa76c1
--- /dev/null
+++ b/models/migrations/v1_11/v116.go
@@ -0,0 +1,32 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func ExtendTrackedTimes(x *xorm.Engine) error {
+ type TrackedTime struct {
+ Time int64 `xorm:"NOT NULL"`
+ Deleted bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if _, err := sess.Exec("DELETE FROM tracked_time WHERE time IS NULL"); err != nil {
+ return err
+ }
+
+ if err := sess.Sync(new(TrackedTime)); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_12/v117.go b/models/migrations/v1_12/v117.go
new file mode 100644
index 00000000..8eadcdef
--- /dev/null
+++ b/models/migrations/v1_12/v117.go
@@ -0,0 +1,16 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddBlockOnRejectedReviews(x *xorm.Engine) error {
+ type ProtectedBranch struct {
+ BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(ProtectedBranch))
+}
diff --git a/models/migrations/v1_12/v118.go b/models/migrations/v1_12/v118.go
new file mode 100644
index 00000000..eb022dc5
--- /dev/null
+++ b/models/migrations/v1_12/v118.go
@@ -0,0 +1,25 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddReviewCommitAndStale(x *xorm.Engine) error {
+ type Review struct {
+ CommitID string `xorm:"VARCHAR(40)"`
+ Stale bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ type ProtectedBranch struct {
+ DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ // Old reviews will have commit ID set to "" and not stale
+ if err := x.Sync(new(Review)); err != nil {
+ return err
+ }
+ return x.Sync(new(ProtectedBranch))
+}
diff --git a/models/migrations/v1_12/v119.go b/models/migrations/v1_12/v119.go
new file mode 100644
index 00000000..60bfe6a5
--- /dev/null
+++ b/models/migrations/v1_12/v119.go
@@ -0,0 +1,15 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func FixMigratedRepositoryServiceType(x *xorm.Engine) error {
+ // structs.GithubService:
+ // GithubService = 2
+ _, err := x.Exec("UPDATE repository SET original_service_type = ? WHERE original_url LIKE 'https://github.com/%'", 2)
+ return err
+}
diff --git a/models/migrations/v1_12/v120.go b/models/migrations/v1_12/v120.go
new file mode 100644
index 00000000..3f7ed8d3
--- /dev/null
+++ b/models/migrations/v1_12/v120.go
@@ -0,0 +1,19 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddOwnerNameOnRepository(x *xorm.Engine) error {
+ type Repository struct {
+ OwnerName string
+ }
+ if err := x.Sync(new(Repository)); err != nil {
+ return err
+ }
+ _, err := x.Exec("UPDATE repository SET owner_name = (SELECT name FROM `user` WHERE `user`.id = repository.owner_id)")
+ return err
+}
diff --git a/models/migrations/v1_12/v121.go b/models/migrations/v1_12/v121.go
new file mode 100644
index 00000000..175ec916
--- /dev/null
+++ b/models/migrations/v1_12/v121.go
@@ -0,0 +1,16 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import "xorm.io/xorm"
+
+func AddIsRestricted(x *xorm.Engine) error {
+ // User see models/user.go
+ type User struct {
+ ID int64 `xorm:"pk autoincr"`
+ IsRestricted bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(User))
+}
diff --git a/models/migrations/v1_12/v122.go b/models/migrations/v1_12/v122.go
new file mode 100644
index 00000000..6e31d863
--- /dev/null
+++ b/models/migrations/v1_12/v122.go
@@ -0,0 +1,16 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddRequireSignedCommits(x *xorm.Engine) error {
+ type ProtectedBranch struct {
+ RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(ProtectedBranch))
+}
diff --git a/models/migrations/v1_12/v123.go b/models/migrations/v1_12/v123.go
new file mode 100644
index 00000000..b0c3af07
--- /dev/null
+++ b/models/migrations/v1_12/v123.go
@@ -0,0 +1,17 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddReactionOriginals(x *xorm.Engine) error {
+ type Reaction struct {
+ OriginalAuthorID int64 `xorm:"INDEX NOT NULL DEFAULT(0)"`
+ OriginalAuthor string
+ }
+
+ return x.Sync(new(Reaction))
+}
diff --git a/models/migrations/v1_12/v124.go b/models/migrations/v1_12/v124.go
new file mode 100644
index 00000000..d2ba03ff
--- /dev/null
+++ b/models/migrations/v1_12/v124.go
@@ -0,0 +1,23 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddUserRepoMissingColumns(x *xorm.Engine) error {
+ type VisibleType int
+ type User struct {
+ PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'pbkdf2'"`
+ Visibility VisibleType `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ type Repository struct {
+ IsArchived bool `xorm:"INDEX"`
+ Topics []string `xorm:"TEXT JSON"`
+ }
+
+ return x.Sync(new(User), new(Repository))
+}
diff --git a/models/migrations/v1_12/v125.go b/models/migrations/v1_12/v125.go
new file mode 100644
index 00000000..ec4ffaab
--- /dev/null
+++ b/models/migrations/v1_12/v125.go
@@ -0,0 +1,22 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddReviewMigrateInfo(x *xorm.Engine) error {
+ type Review struct {
+ OriginalAuthor string
+ OriginalAuthorID int64
+ }
+
+ if err := x.Sync(new(Review)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_12/v126.go b/models/migrations/v1_12/v126.go
new file mode 100644
index 00000000..ca9ec3aa
--- /dev/null
+++ b/models/migrations/v1_12/v126.go
@@ -0,0 +1,24 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "xorm.io/builder"
+ "xorm.io/xorm"
+)
+
+func FixTopicRepositoryCount(x *xorm.Engine) error {
+ _, err := x.Exec(builder.Delete(builder.NotIn("`repo_id`", builder.Select("`id`").From("`repository`"))).From("`repo_topic`"))
+ if err != nil {
+ return err
+ }
+
+ _, err = x.Exec(builder.Update(
+ builder.Eq{
+ "`repo_count`": builder.Select("count(*)").From("`repo_topic`").Where(builder.Eq{
+ "`repo_topic`.`topic_id`": builder.Expr("`topic`.`id`"),
+ }),
+ }).From("`topic`").Where(builder.Eq{"'1'": "1"}))
+ return err
+}
diff --git a/models/migrations/v1_12/v127.go b/models/migrations/v1_12/v127.go
new file mode 100644
index 00000000..00e391dc
--- /dev/null
+++ b/models/migrations/v1_12/v127.go
@@ -0,0 +1,44 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddLanguageStats(x *xorm.Engine) error {
+ // LanguageStat see models/repo_language_stats.go
+ type LanguageStat struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ CommitID string
+ IsPrimary bool
+ Language string `xorm:"VARCHAR(30) UNIQUE(s) INDEX NOT NULL"`
+ Percentage float32 `xorm:"NUMERIC(5,2) NOT NULL DEFAULT 0"`
+ Color string `xorm:"-"`
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
+ }
+
+ type RepoIndexerType int
+
+ // RepoIndexerStatus see models/repo_stats_indexer.go
+ type RepoIndexerStatus struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX(s)"`
+ CommitSha string `xorm:"VARCHAR(40)"`
+ IndexerType RepoIndexerType `xorm:"INDEX(s) NOT NULL DEFAULT 0"`
+ }
+
+ if err := x.Sync(new(LanguageStat)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ if err := x.Sync(new(RepoIndexerStatus)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_12/v128.go b/models/migrations/v1_12/v128.go
new file mode 100644
index 00000000..6eea1337
--- /dev/null
+++ b/models/migrations/v1_12/v128.go
@@ -0,0 +1,127 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "fmt"
+ "math"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func FixMergeBase(x *xorm.Engine) error {
+ type Repository struct {
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"UNIQUE(s) index"`
+ OwnerName string
+ LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ Name string `xorm:"INDEX NOT NULL"`
+ }
+
+ type PullRequest struct {
+ ID int64 `xorm:"pk autoincr"`
+ Index int64
+ HeadRepoID int64 `xorm:"INDEX"`
+ BaseRepoID int64 `xorm:"INDEX"`
+ HeadBranch string
+ BaseBranch string
+ MergeBase string `xorm:"VARCHAR(40)"`
+
+ HasMerged bool `xorm:"INDEX"`
+ MergedCommitID string `xorm:"VARCHAR(40)"`
+ }
+
+ limit := setting.Database.IterateBufferSize
+ if limit <= 0 {
+ limit = 50
+ }
+
+ ticker := time.NewTicker(5 * time.Second)
+ defer ticker.Stop()
+
+ count, err := x.Count(new(PullRequest))
+ if err != nil {
+ return err
+ }
+ log.Info("%d Pull Request(s) to migrate ...", count)
+
+ i := 0
+ start := 0
+ for {
+ prs := make([]PullRequest, 0, 50)
+ if err := x.Limit(limit, start).Asc("id").Find(&prs); err != nil {
+ return fmt.Errorf("Find: %w", err)
+ }
+ if len(prs) == 0 {
+ break
+ }
+
+ start += 50
+ for _, pr := range prs {
+ baseRepo := &Repository{ID: pr.BaseRepoID}
+ has, err := x.Table("repository").Get(baseRepo)
+ if err != nil {
+ return fmt.Errorf("Unable to get base repo %d %w", pr.BaseRepoID, err)
+ }
+ if !has {
+ log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
+ continue
+ }
+ userPath := filepath.Join(setting.RepoRootPath, strings.ToLower(baseRepo.OwnerName))
+ repoPath := filepath.Join(userPath, strings.ToLower(baseRepo.Name)+".git")
+
+ gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
+
+ if !pr.HasMerged {
+ var err error
+ pr.MergeBase, _, err = git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(&git.RunOpts{Dir: repoPath})
+ if err != nil {
+ var err2 error
+ pr.MergeBase, _, err2 = git.NewCommand(git.DefaultContext, "rev-parse").AddDynamicArguments(git.BranchPrefix + pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath})
+ if err2 != nil {
+ log.Error("Unable to get merge base for PR ID %d, Index %d in %s/%s. Error: %v & %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err, err2)
+ continue
+ }
+ }
+ } else {
+ parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath})
+ if err != nil {
+ log.Warn("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
+ continue
+ }
+ parents := strings.Split(strings.TrimSpace(parentsString), " ")
+ if len(parents) < 2 {
+ continue
+ }
+
+ refs := append([]string{}, parents[1:]...)
+ refs = append(refs, gitRefName)
+ cmd := git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(refs...)
+
+ pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath})
+ if err != nil {
+ log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
+ continue
+ }
+ }
+ pr.MergeBase = strings.TrimSpace(pr.MergeBase)
+ x.ID(pr.ID).Cols("merge_base").Update(pr)
+ i++
+ select {
+ case <-ticker.C:
+ log.Info("%d/%d (%2.0f%%) Pull Request(s) migrated in %d batches. %d PRs Remaining ...", i, count, float64(i)/float64(count)*100, int(math.Ceil(float64(i)/float64(limit))), count-int64(i))
+ default:
+ }
+ }
+ }
+ log.Info("Completed migrating %d Pull Request(s) in: %d batches", count, int(math.Ceil(float64(i)/float64(limit))))
+ return nil
+}
diff --git a/models/migrations/v1_12/v129.go b/models/migrations/v1_12/v129.go
new file mode 100644
index 00000000..cf228242
--- /dev/null
+++ b/models/migrations/v1_12/v129.go
@@ -0,0 +1,16 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func PurgeUnusedDependencies(x *xorm.Engine) error {
+ if _, err := x.Exec("DELETE FROM issue_dependency WHERE issue_id NOT IN (SELECT id FROM issue)"); err != nil {
+ return err
+ }
+ _, err := x.Exec("DELETE FROM issue_dependency WHERE dependency_id NOT IN (SELECT id FROM issue)")
+ return err
+}
diff --git a/models/migrations/v1_12/v130.go b/models/migrations/v1_12/v130.go
new file mode 100644
index 00000000..391810c7
--- /dev/null
+++ b/models/migrations/v1_12/v130.go
@@ -0,0 +1,111 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func ExpandWebhooks(x *xorm.Engine) error {
+ type HookEvents struct {
+ Create bool `json:"create"`
+ Delete bool `json:"delete"`
+ Fork bool `json:"fork"`
+ Issues bool `json:"issues"`
+ IssueAssign bool `json:"issue_assign"`
+ IssueLabel bool `json:"issue_label"`
+ IssueMilestone bool `json:"issue_milestone"`
+ IssueComment bool `json:"issue_comment"`
+ Push bool `json:"push"`
+ PullRequest bool `json:"pull_request"`
+ PullRequestAssign bool `json:"pull_request_assign"`
+ PullRequestLabel bool `json:"pull_request_label"`
+ PullRequestMilestone bool `json:"pull_request_milestone"`
+ PullRequestComment bool `json:"pull_request_comment"`
+ PullRequestReview bool `json:"pull_request_review"`
+ PullRequestSync bool `json:"pull_request_sync"`
+ Repository bool `json:"repository"`
+ Release bool `json:"release"`
+ }
+
+ type HookEvent struct {
+ PushOnly bool `json:"push_only"`
+ SendEverything bool `json:"send_everything"`
+ ChooseEvents bool `json:"choose_events"`
+ BranchFilter string `json:"branch_filter"`
+
+ HookEvents `json:"events"`
+ }
+
+ type Webhook struct {
+ ID int64
+ Events string
+ }
+
+ var bytes []byte
+ var last int
+ batchSize := setting.Database.IterateBufferSize
+ sess := x.NewSession()
+ defer sess.Close()
+ for {
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ results := make([]Webhook, 0, batchSize)
+ err := x.OrderBy("id").
+ Limit(batchSize, last).
+ Find(&results)
+ if err != nil {
+ return err
+ }
+ if len(results) == 0 {
+ break
+ }
+ last += len(results)
+
+ for _, res := range results {
+ var events HookEvent
+ if err = json.Unmarshal([]byte(res.Events), &events); err != nil {
+ return err
+ }
+
+ if !events.ChooseEvents {
+ continue
+ }
+
+ if events.Issues {
+ events.IssueAssign = true
+ events.IssueLabel = true
+ events.IssueMilestone = true
+ events.IssueComment = true
+ }
+
+ if events.PullRequest {
+ events.PullRequestAssign = true
+ events.PullRequestLabel = true
+ events.PullRequestMilestone = true
+ events.PullRequestComment = true
+ events.PullRequestReview = true
+ events.PullRequestSync = true
+ }
+
+ if bytes, err = json.Marshal(&events); err != nil {
+ return err
+ }
+
+ _, err = sess.Exec("UPDATE webhook SET events = ? WHERE id = ?", string(bytes), res.ID)
+ if err != nil {
+ return err
+ }
+ }
+
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/models/migrations/v1_12/v131.go b/models/migrations/v1_12/v131.go
new file mode 100644
index 00000000..5184bc35
--- /dev/null
+++ b/models/migrations/v1_12/v131.go
@@ -0,0 +1,21 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddSystemWebhookColumn(x *xorm.Engine) error {
+ type Webhook struct {
+ IsSystemWebhook bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ if err := x.Sync(new(Webhook)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_12/v132.go b/models/migrations/v1_12/v132.go
new file mode 100644
index 00000000..3b2b28f7
--- /dev/null
+++ b/models/migrations/v1_12/v132.go
@@ -0,0 +1,21 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddBranchProtectionProtectedFilesColumn(x *xorm.Engine) error {
+ type ProtectedBranch struct {
+ ProtectedFilePatterns 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_12/v133.go b/models/migrations/v1_12/v133.go
new file mode 100644
index 00000000..c9087fc8
--- /dev/null
+++ b/models/migrations/v1_12/v133.go
@@ -0,0 +1,15 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import "xorm.io/xorm"
+
+func AddEmailHashTable(x *xorm.Engine) error {
+ // EmailHash represents a pre-generated hash map
+ type EmailHash struct {
+ Hash string `xorm:"pk varchar(32)"`
+ Email string `xorm:"UNIQUE NOT NULL"`
+ }
+ return x.Sync(new(EmailHash))
+}
diff --git a/models/migrations/v1_12/v134.go b/models/migrations/v1_12/v134.go
new file mode 100644
index 00000000..23c2916b
--- /dev/null
+++ b/models/migrations/v1_12/v134.go
@@ -0,0 +1,115 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "fmt"
+ "math"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func RefixMergeBase(x *xorm.Engine) error {
+ type Repository struct {
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"UNIQUE(s) index"`
+ OwnerName string
+ LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ Name string `xorm:"INDEX NOT NULL"`
+ }
+
+ type PullRequest struct {
+ ID int64 `xorm:"pk autoincr"`
+ Index int64
+ HeadRepoID int64 `xorm:"INDEX"`
+ BaseRepoID int64 `xorm:"INDEX"`
+ HeadBranch string
+ BaseBranch string
+ MergeBase string `xorm:"VARCHAR(40)"`
+
+ HasMerged bool `xorm:"INDEX"`
+ MergedCommitID string `xorm:"VARCHAR(40)"`
+ }
+
+ limit := setting.Database.IterateBufferSize
+ if limit <= 0 {
+ limit = 50
+ }
+
+ ticker := time.NewTicker(5 * time.Second)
+ defer ticker.Stop()
+ count, err := x.Where("has_merged = ?", true).Count(new(PullRequest))
+ if err != nil {
+ return err
+ }
+ log.Info("%d Merged Pull Request(s) to migrate ...", count)
+
+ i := 0
+ start := 0
+ for {
+ prs := make([]PullRequest, 0, 50)
+ if err := x.Limit(limit, start).Asc("id").Where("has_merged = ?", true).Find(&prs); err != nil {
+ return fmt.Errorf("Find: %w", err)
+ }
+ if len(prs) == 0 {
+ break
+ }
+
+ start += 50
+ for _, pr := range prs {
+ baseRepo := &Repository{ID: pr.BaseRepoID}
+ has, err := x.Table("repository").Get(baseRepo)
+ if err != nil {
+ return fmt.Errorf("Unable to get base repo %d %w", pr.BaseRepoID, err)
+ }
+ if !has {
+ log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
+ continue
+ }
+ userPath := filepath.Join(setting.RepoRootPath, strings.ToLower(baseRepo.OwnerName))
+ repoPath := filepath.Join(userPath, strings.ToLower(baseRepo.Name)+".git")
+
+ gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
+
+ parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath})
+ if err != nil {
+ log.Warn("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
+ continue
+ }
+ parents := strings.Split(strings.TrimSpace(parentsString), " ")
+ if len(parents) < 3 {
+ continue
+ }
+
+ // we should recalculate
+ refs := append([]string{}, parents[1:]...)
+ refs = append(refs, gitRefName)
+ cmd := git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(refs...)
+
+ pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath})
+ if err != nil {
+ log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
+ continue
+ }
+ pr.MergeBase = strings.TrimSpace(pr.MergeBase)
+ x.ID(pr.ID).Cols("merge_base").Update(pr)
+ i++
+ select {
+ case <-ticker.C:
+ log.Info("%d/%d (%2.0f%%) Pull Request(s) migrated in %d batches. %d PRs Remaining ...", i, count, float64(i)/float64(count)*100, int(math.Ceil(float64(i)/float64(limit))), count-int64(i))
+ default:
+ }
+ }
+ }
+
+ log.Info("Completed migrating %d Pull Request(s) in: %d batches", count, int(math.Ceil(float64(i)/float64(limit))))
+ return nil
+}
diff --git a/models/migrations/v1_12/v135.go b/models/migrations/v1_12/v135.go
new file mode 100644
index 00000000..8898011d
--- /dev/null
+++ b/models/migrations/v1_12/v135.go
@@ -0,0 +1,21 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddOrgIDLabelColumn(x *xorm.Engine) error {
+ type Label struct {
+ OrgID int64 `xorm:"INDEX"`
+ }
+
+ if err := x.Sync(new(Label)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_12/v136.go b/models/migrations/v1_12/v136.go
new file mode 100644
index 00000000..d91ff92f
--- /dev/null
+++ b/models/migrations/v1_12/v136.go
@@ -0,0 +1,125 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "fmt"
+ "math"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/graceful"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func AddCommitDivergenceToPulls(x *xorm.Engine) error {
+ type Repository struct {
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"UNIQUE(s) index"`
+ OwnerName string
+ LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ Name string `xorm:"INDEX NOT NULL"`
+ }
+
+ type PullRequest struct {
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"INDEX"`
+ Index int64
+
+ CommitsAhead int
+ CommitsBehind int
+
+ BaseRepoID int64 `xorm:"INDEX"`
+ BaseBranch string
+
+ HasMerged bool `xorm:"INDEX"`
+ MergedCommitID string `xorm:"VARCHAR(40)"`
+ }
+
+ if err := x.Sync(new(PullRequest)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+
+ last := 0
+ migrated := 0
+
+ batchSize := setting.Database.IterateBufferSize
+ sess := x.NewSession()
+ defer sess.Close()
+
+ ticker := time.NewTicker(5 * time.Second)
+ defer ticker.Stop()
+ count, err := sess.Where("has_merged = ?", false).Count(new(PullRequest))
+ if err != nil {
+ return err
+ }
+ log.Info("%d Unmerged Pull Request(s) to migrate ...", count)
+
+ for {
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ results := make([]*PullRequest, 0, batchSize)
+ err := sess.Where("has_merged = ?", false).OrderBy("id").Limit(batchSize, last).Find(&results)
+ if err != nil {
+ return err
+ }
+ if len(results) == 0 {
+ break
+ }
+ last += batchSize
+
+ for _, pr := range results {
+ baseRepo := &Repository{ID: pr.BaseRepoID}
+ has, err := x.Table("repository").Get(baseRepo)
+ if err != nil {
+ return fmt.Errorf("Unable to get base repo %d %w", pr.BaseRepoID, err)
+ }
+ if !has {
+ log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
+ continue
+ }
+ userPath := filepath.Join(setting.RepoRootPath, strings.ToLower(baseRepo.OwnerName))
+ repoPath := filepath.Join(userPath, strings.ToLower(baseRepo.Name)+".git")
+
+ gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
+
+ divergence, err := git.GetDivergingCommits(graceful.GetManager().HammerContext(), repoPath, pr.BaseBranch, gitRefName)
+ if err != nil {
+ log.Warn("Could not recalculate Divergence for pull: %d", pr.ID)
+ pr.CommitsAhead = 0
+ pr.CommitsBehind = 0
+ }
+ pr.CommitsAhead = divergence.Ahead
+ pr.CommitsBehind = divergence.Behind
+
+ if _, err = sess.ID(pr.ID).Cols("commits_ahead", "commits_behind").Update(pr); err != nil {
+ return fmt.Errorf("Update Cols: %w", err)
+ }
+ migrated++
+ }
+
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+ select {
+ case <-ticker.C:
+ log.Info(
+ "%d/%d (%2.0f%%) Pull Request(s) migrated in %d batches. %d PRs Remaining ...",
+ migrated,
+ count,
+ float64(migrated)/float64(count)*100,
+ int(math.Ceil(float64(migrated)/float64(batchSize))),
+ count-int64(migrated))
+ default:
+ }
+ }
+ log.Info("Completed migrating %d Pull Request(s) in: %d batches", count, int(math.Ceil(float64(migrated)/float64(batchSize))))
+ return nil
+}
diff --git a/models/migrations/v1_12/v137.go b/models/migrations/v1_12/v137.go
new file mode 100644
index 00000000..0d86b720
--- /dev/null
+++ b/models/migrations/v1_12/v137.go
@@ -0,0 +1,15 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddBlockOnOutdatedBranch(x *xorm.Engine) error {
+ type ProtectedBranch struct {
+ BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
+ }
+ return x.Sync(new(ProtectedBranch))
+}
diff --git a/models/migrations/v1_12/v138.go b/models/migrations/v1_12/v138.go
new file mode 100644
index 00000000..8c8d353f
--- /dev/null
+++ b/models/migrations/v1_12/v138.go
@@ -0,0 +1,21 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddResolveDoerIDCommentColumn(x *xorm.Engine) error {
+ type Comment struct {
+ ResolveDoerID int64
+ }
+
+ if err := x.Sync(new(Comment)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_12/v139.go b/models/migrations/v1_12/v139.go
new file mode 100644
index 00000000..5b657695
--- /dev/null
+++ b/models/migrations/v1_12/v139.go
@@ -0,0 +1,23 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_12 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func PrependRefsHeadsToIssueRefs(x *xorm.Engine) error {
+ var query string
+
+ if setting.Database.Type.IsMySQL() {
+ query = "UPDATE `issue` SET `ref` = CONCAT('refs/heads/', `ref`) WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%';"
+ } else {
+ query = "UPDATE `issue` SET `ref` = 'refs/heads/' || `ref` WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%'"
+ }
+
+ _, err := x.Exec(query)
+ return err
+}
diff --git a/models/migrations/v1_13/v140.go b/models/migrations/v1_13/v140.go
new file mode 100644
index 00000000..2d333701
--- /dev/null
+++ b/models/migrations/v1_13/v140.go
@@ -0,0 +1,56 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func FixLanguageStatsToSaveSize(x *xorm.Engine) error {
+ // LanguageStat see models/repo_language_stats.go
+ type LanguageStat struct {
+ Size int64 `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ // RepoIndexerType specifies the repository indexer type
+ type RepoIndexerType int
+
+ const (
+ // RepoIndexerTypeCode code indexer - 0
+ RepoIndexerTypeCode RepoIndexerType = iota //nolint:unused
+ // RepoIndexerTypeStats repository stats indexer - 1
+ RepoIndexerTypeStats
+ )
+
+ // RepoIndexerStatus see models/repo_indexer.go
+ type RepoIndexerStatus struct {
+ IndexerType RepoIndexerType `xorm:"INDEX(s) NOT NULL DEFAULT 0"`
+ }
+
+ if err := x.Sync(new(LanguageStat)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+
+ x.Delete(&RepoIndexerStatus{IndexerType: RepoIndexerTypeStats})
+
+ // Delete language stat statuses
+ truncExpr := "TRUNCATE TABLE"
+ if setting.Database.Type.IsSQLite3() {
+ truncExpr = "DELETE FROM"
+ }
+
+ // Delete language stats
+ if _, err := x.Exec(fmt.Sprintf("%s language_stat", truncExpr)); err != nil {
+ return err
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ return base.DropTableColumns(sess, "language_stat", "percentage")
+}
diff --git a/models/migrations/v1_13/v141.go b/models/migrations/v1_13/v141.go
new file mode 100644
index 00000000..ae211e0e
--- /dev/null
+++ b/models/migrations/v1_13/v141.go
@@ -0,0 +1,21 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func AddKeepActivityPrivateUserColumn(x *xorm.Engine) error {
+ type User struct {
+ KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ if err := x.Sync(new(User)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_13/v142.go b/models/migrations/v1_13/v142.go
new file mode 100644
index 00000000..7c7c01ad
--- /dev/null
+++ b/models/migrations/v1_13/v142.go
@@ -0,0 +1,24 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/log"
+
+ "xorm.io/builder"
+ "xorm.io/xorm"
+)
+
+func SetIsArchivedToFalse(x *xorm.Engine) error {
+ type Repository struct {
+ IsArchived bool `xorm:"INDEX"`
+ }
+ count, err := x.Where(builder.IsNull{"is_archived"}).Cols("is_archived").Update(&Repository{
+ IsArchived: false,
+ })
+ if err == nil {
+ log.Debug("Updated %d repositories with is_archived IS NULL", count)
+ }
+ return err
+}
diff --git a/models/migrations/v1_13/v143.go b/models/migrations/v1_13/v143.go
new file mode 100644
index 00000000..885768df
--- /dev/null
+++ b/models/migrations/v1_13/v143.go
@@ -0,0 +1,51 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/log"
+
+ "xorm.io/xorm"
+)
+
+func RecalculateStars(x *xorm.Engine) (err error) {
+ // because of issue https://github.com/go-gitea/gitea/issues/11949,
+ // recalculate Stars number for all users to fully fix it.
+
+ type User struct {
+ ID int64 `xorm:"pk autoincr"`
+ }
+
+ const batchSize = 100
+ sess := x.NewSession()
+ defer sess.Close()
+
+ for start := 0; ; start += batchSize {
+ users := make([]User, 0, batchSize)
+ if err := sess.Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil {
+ return err
+ }
+ if len(users) == 0 {
+ break
+ }
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ for _, user := range users {
+ if _, err := sess.Exec("UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil {
+ return err
+ }
+ }
+
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+ }
+
+ log.Debug("recalculate Stars number for all user finished")
+
+ return err
+}
diff --git a/models/migrations/v1_13/v144.go b/models/migrations/v1_13/v144.go
new file mode 100644
index 00000000..f5a0bc57
--- /dev/null
+++ b/models/migrations/v1_13/v144.go
@@ -0,0 +1,25 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/log"
+
+ "xorm.io/builder"
+ "xorm.io/xorm"
+)
+
+func UpdateMatrixWebhookHTTPMethod(x *xorm.Engine) error {
+ matrixHookTaskType := 9 // value comes from the models package
+ type Webhook struct {
+ HTTPMethod string
+ }
+
+ cond := builder.Eq{"hook_task_type": matrixHookTaskType}.And(builder.Neq{"http_method": "PUT"})
+ count, err := x.Where(cond).Cols("http_method").Update(&Webhook{HTTPMethod: "PUT"})
+ if err == nil {
+ log.Debug("Updated %d Matrix webhooks with http_method 'PUT'", count)
+ }
+ return err
+}
diff --git a/models/migrations/v1_13/v145.go b/models/migrations/v1_13/v145.go
new file mode 100644
index 00000000..5b38f1cd
--- /dev/null
+++ b/models/migrations/v1_13/v145.go
@@ -0,0 +1,55 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func IncreaseLanguageField(x *xorm.Engine) error {
+ type LanguageStat struct {
+ RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ Language string `xorm:"VARCHAR(50) UNIQUE(s) INDEX NOT NULL"`
+ }
+
+ if err := x.Sync(new(LanguageStat)); err != nil {
+ return err
+ }
+
+ if setting.Database.Type.IsSQLite3() {
+ // SQLite maps VARCHAR to TEXT without size so we're done
+ return nil
+ }
+
+ // need to get the correct type for the new column
+ inferredTable, err := x.TableInfo(new(LanguageStat))
+ if err != nil {
+ return err
+ }
+ column := inferredTable.GetColumn("language")
+ sqlType := x.Dialect().SQLType(column)
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ switch {
+ case setting.Database.Type.IsMySQL():
+ if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat MODIFY COLUMN language %s", sqlType)); err != nil {
+ return err
+ }
+ case setting.Database.Type.IsPostgreSQL():
+ if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat ALTER COLUMN language TYPE %s", sqlType)); err != nil {
+ return err
+ }
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_13/v146.go b/models/migrations/v1_13/v146.go
new file mode 100644
index 00000000..7d9a8787
--- /dev/null
+++ b/models/migrations/v1_13/v146.go
@@ -0,0 +1,83 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddProjectsInfo(x *xorm.Engine) error {
+ // Create new tables
+ type (
+ ProjectType uint8
+ ProjectBoardType uint8
+ )
+
+ type Project struct {
+ ID int64 `xorm:"pk autoincr"`
+ Title string `xorm:"INDEX NOT NULL"`
+ Description string `xorm:"TEXT"`
+ RepoID int64 `xorm:"INDEX"`
+ CreatorID int64 `xorm:"NOT NULL"`
+ IsClosed bool `xorm:"INDEX"`
+
+ BoardType ProjectBoardType
+ Type ProjectType
+
+ ClosedDateUnix timeutil.TimeStamp
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ if err := x.Sync(new(Project)); err != nil {
+ return err
+ }
+
+ type Comment struct {
+ OldProjectID int64
+ ProjectID int64
+ }
+
+ if err := x.Sync(new(Comment)); err != nil {
+ return err
+ }
+
+ type Repository struct {
+ ID int64
+ NumProjects int `xorm:"NOT NULL DEFAULT 0"`
+ NumClosedProjects int `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ if err := x.Sync(new(Repository)); err != nil {
+ return err
+ }
+
+ // ProjectIssue saves relation from issue to a project
+ type ProjectIssue struct {
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"INDEX"`
+ ProjectID int64 `xorm:"INDEX"`
+ ProjectBoardID int64 `xorm:"INDEX"`
+ }
+
+ if err := x.Sync(new(ProjectIssue)); err != nil {
+ return err
+ }
+
+ type ProjectBoard struct {
+ ID int64 `xorm:"pk autoincr"`
+ Title string
+ Default bool `xorm:"NOT NULL DEFAULT false"`
+
+ ProjectID int64 `xorm:"INDEX NOT NULL"`
+ CreatorID int64 `xorm:"NOT NULL"`
+
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ return x.Sync(new(ProjectBoard))
+}
diff --git a/models/migrations/v1_13/v147.go b/models/migrations/v1_13/v147.go
new file mode 100644
index 00000000..510ef39b
--- /dev/null
+++ b/models/migrations/v1_13/v147.go
@@ -0,0 +1,153 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func CreateReviewsForCodeComments(x *xorm.Engine) error {
+ // Review
+ type Review struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type int
+ ReviewerID int64 `xorm:"index"`
+ OriginalAuthor string
+ OriginalAuthorID int64
+ IssueID int64 `xorm:"index"`
+ Content string `xorm:"TEXT"`
+ // Official is a review made by an assigned approver (counts towards approval)
+ Official bool `xorm:"NOT NULL DEFAULT false"`
+ CommitID string `xorm:"VARCHAR(40)"`
+ Stale bool `xorm:"NOT NULL DEFAULT false"`
+
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ const ReviewTypeComment = 2
+
+ // Comment represents a comment in commit and issue page.
+ type Comment struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type int `xorm:"INDEX"`
+ PosterID int64 `xorm:"INDEX"`
+ OriginalAuthor string
+ OriginalAuthorID int64
+ IssueID int64 `xorm:"INDEX"`
+ LabelID int64
+ OldProjectID int64
+ ProjectID int64
+ OldMilestoneID int64
+ MilestoneID int64
+ AssigneeID int64
+ RemovedAssignee bool
+ ResolveDoerID int64
+ OldTitle string
+ NewTitle string
+ OldRef string
+ NewRef string
+ DependentIssueID int64
+
+ CommitID int64
+ Line int64 // - previous line / + proposed line
+ TreePath string
+ Content string `xorm:"TEXT"`
+
+ // Path represents the 4 lines of code cemented by this comment
+ PatchQuoted string `xorm:"TEXT patch"`
+
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+
+ // Reference issue in commit message
+ CommitSHA string `xorm:"VARCHAR(40)"`
+
+ ReviewID int64 `xorm:"index"`
+ Invalidated bool
+
+ // Reference an issue or pull from another comment, issue or PR
+ // All information is about the origin of the reference
+ RefRepoID int64 `xorm:"index"` // Repo where the referencing
+ RefIssueID int64 `xorm:"index"`
+ RefCommentID int64 `xorm:"index"` // 0 if origin is Issue title or content (or PR's)
+ RefAction int `xorm:"SMALLINT"` // What happens if RefIssueID resolves
+ RefIsPull bool
+ }
+
+ if err := x.Sync(new(Review), new(Comment)); err != nil {
+ return err
+ }
+
+ updateComment := func(comments []*Comment) error {
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ for _, comment := range comments {
+ review := &Review{
+ Type: ReviewTypeComment,
+ ReviewerID: comment.PosterID,
+ IssueID: comment.IssueID,
+ Official: false,
+ CommitID: comment.CommitSHA,
+ Stale: comment.Invalidated,
+ OriginalAuthor: comment.OriginalAuthor,
+ OriginalAuthorID: comment.OriginalAuthorID,
+ CreatedUnix: comment.CreatedUnix,
+ UpdatedUnix: comment.CreatedUnix,
+ }
+ if _, err := sess.NoAutoTime().Insert(review); err != nil {
+ return err
+ }
+
+ reviewComment := &Comment{
+ Type: 22,
+ PosterID: comment.PosterID,
+ Content: "",
+ IssueID: comment.IssueID,
+ ReviewID: review.ID,
+ OriginalAuthor: comment.OriginalAuthor,
+ OriginalAuthorID: comment.OriginalAuthorID,
+ CreatedUnix: comment.CreatedUnix,
+ UpdatedUnix: comment.CreatedUnix,
+ }
+ if _, err := sess.NoAutoTime().Insert(reviewComment); err != nil {
+ return err
+ }
+
+ comment.ReviewID = review.ID
+ if _, err := sess.ID(comment.ID).Cols("review_id").NoAutoTime().Update(comment); err != nil {
+ return err
+ }
+ }
+
+ return sess.Commit()
+ }
+
+ start := 0
+ batchSize := 100
+ for {
+ comments := make([]*Comment, 0, batchSize)
+ if err := x.Where("review_id = 0 and type = 21").Limit(batchSize, start).Find(&comments); err != nil {
+ return err
+ }
+
+ if err := updateComment(comments); err != nil {
+ return err
+ }
+
+ start += len(comments)
+
+ if len(comments) < batchSize {
+ break
+ }
+ }
+
+ return nil
+}
diff --git a/models/migrations/v1_13/v148.go b/models/migrations/v1_13/v148.go
new file mode 100644
index 00000000..7bb8ab70
--- /dev/null
+++ b/models/migrations/v1_13/v148.go
@@ -0,0 +1,13 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func PurgeInvalidDependenciesComments(x *xorm.Engine) error {
+ _, err := x.Exec("DELETE FROM comment WHERE dependent_issue_id != 0 AND dependent_issue_id NOT IN (SELECT id FROM issue)")
+ return err
+}
diff --git a/models/migrations/v1_13/v149.go b/models/migrations/v1_13/v149.go
new file mode 100644
index 00000000..2a1db04c
--- /dev/null
+++ b/models/migrations/v1_13/v149.go
@@ -0,0 +1,24 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddCreatedAndUpdatedToMilestones(x *xorm.Engine) error {
+ type Milestone struct {
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ if err := x.Sync(new(Milestone)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_13/v150.go b/models/migrations/v1_13/v150.go
new file mode 100644
index 00000000..d5ba4895
--- /dev/null
+++ b/models/migrations/v1_13/v150.go
@@ -0,0 +1,39 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddPrimaryKeyToRepoTopic(x *xorm.Engine) error {
+ // Topic represents a topic of repositories
+ type Topic struct {
+ ID int64 `xorm:"pk autoincr"`
+ Name string `xorm:"UNIQUE VARCHAR(25)"`
+ RepoCount int
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ // RepoTopic represents associated repositories and topics
+ type RepoTopic struct {
+ RepoID int64 `xorm:"pk"`
+ TopicID int64 `xorm:"pk"`
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ base.RecreateTable(sess, &Topic{})
+ base.RecreateTable(sess, &RepoTopic{})
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_13/v151.go b/models/migrations/v1_13/v151.go
new file mode 100644
index 00000000..ea4a8eae
--- /dev/null
+++ b/models/migrations/v1_13/v151.go
@@ -0,0 +1,166 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+func SetDefaultPasswordToArgon2(x *xorm.Engine) error {
+ switch {
+ case setting.Database.Type.IsMySQL():
+ _, err := x.Exec("ALTER TABLE `user` ALTER passwd_hash_algo SET DEFAULT 'argon2';")
+ return err
+ case setting.Database.Type.IsPostgreSQL():
+ _, err := x.Exec("ALTER TABLE `user` ALTER COLUMN passwd_hash_algo SET DEFAULT 'argon2';")
+ return err
+ case setting.Database.Type.IsSQLite3():
+ // drop through
+ default:
+ log.Fatal("Unrecognized DB")
+ }
+
+ tables, err := x.DBMetas()
+ if err != nil {
+ return err
+ }
+
+ // Now for SQLite we have to recreate the table
+ var table *schemas.Table
+ tableName := "user"
+
+ for _, table = range tables {
+ if table.Name == tableName {
+ break
+ }
+ }
+ if table == nil || table.Name != tableName {
+ type User struct {
+ PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"`
+ }
+ return x.Sync(new(User))
+ }
+ column := table.GetColumn("passwd_hash_algo")
+ if column == nil {
+ type User struct {
+ PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"`
+ }
+ return x.Sync(new(User))
+ }
+
+ tempTableName := "tmp_recreate__user"
+ column.Default = "'argon2'"
+
+ createTableSQL, _, err := x.Dialect().CreateTableSQL(context.Background(), x.DB(), table, tempTableName)
+ if err != nil {
+ return err
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ if _, err := sess.Exec(createTableSQL); err != nil {
+ log.Error("Unable to create table %s. Error: %v\n", tempTableName, err, createTableSQL)
+ return err
+ }
+ for _, index := range table.Indexes {
+ if _, err := sess.Exec(x.Dialect().CreateIndexSQL(tempTableName, index)); err != nil {
+ log.Error("Unable to create indexes on temporary table %s. Error: %v", tempTableName, err)
+ return err
+ }
+ }
+
+ newTableColumns := table.Columns()
+ if len(newTableColumns) == 0 {
+ return fmt.Errorf("no columns in new table")
+ }
+ hasID := false
+ for _, column := range newTableColumns {
+ hasID = hasID || (column.IsPrimaryKey && column.IsAutoIncrement)
+ }
+
+ sqlStringBuilder := &strings.Builder{}
+ _, _ = sqlStringBuilder.WriteString("INSERT INTO `")
+ _, _ = sqlStringBuilder.WriteString(tempTableName)
+ _, _ = sqlStringBuilder.WriteString("` (`")
+ _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
+ _, _ = sqlStringBuilder.WriteString("`")
+ for _, column := range newTableColumns[1:] {
+ _, _ = sqlStringBuilder.WriteString(", `")
+ _, _ = sqlStringBuilder.WriteString(column.Name)
+ _, _ = sqlStringBuilder.WriteString("`")
+ }
+ _, _ = sqlStringBuilder.WriteString(")")
+ _, _ = sqlStringBuilder.WriteString(" SELECT ")
+ if newTableColumns[0].Default != "" {
+ _, _ = sqlStringBuilder.WriteString("COALESCE(`")
+ _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
+ _, _ = sqlStringBuilder.WriteString("`, ")
+ _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Default)
+ _, _ = sqlStringBuilder.WriteString(")")
+ } else {
+ _, _ = sqlStringBuilder.WriteString("`")
+ _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
+ _, _ = sqlStringBuilder.WriteString("`")
+ }
+
+ for _, column := range newTableColumns[1:] {
+ if column.Default != "" {
+ _, _ = sqlStringBuilder.WriteString(", COALESCE(`")
+ _, _ = sqlStringBuilder.WriteString(column.Name)
+ _, _ = sqlStringBuilder.WriteString("`, ")
+ _, _ = sqlStringBuilder.WriteString(column.Default)
+ _, _ = sqlStringBuilder.WriteString(")")
+ } else {
+ _, _ = sqlStringBuilder.WriteString(", `")
+ _, _ = sqlStringBuilder.WriteString(column.Name)
+ _, _ = sqlStringBuilder.WriteString("`")
+ }
+ }
+ _, _ = sqlStringBuilder.WriteString(" FROM `")
+ _, _ = sqlStringBuilder.WriteString(tableName)
+ _, _ = sqlStringBuilder.WriteString("`")
+
+ if _, err := sess.Exec(sqlStringBuilder.String()); err != nil {
+ log.Error("Unable to set copy data in to temp table %s. Error: %v", tempTableName, err)
+ return err
+ }
+
+ // SQLite will drop all the constraints on the old table
+ if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
+ log.Error("Unable to drop old table %s. Error: %v", tableName, err)
+ return err
+ }
+
+ for _, index := range table.Indexes {
+ if _, err := sess.Exec(x.Dialect().DropIndexSQL(tempTableName, index)); err != nil {
+ log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err)
+ return err
+ }
+ }
+
+ if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
+ log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
+ return err
+ }
+
+ for _, index := range table.Indexes {
+ if _, err := sess.Exec(x.Dialect().CreateIndexSQL(tableName, index)); err != nil {
+ log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err)
+ return err
+ }
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_13/v152.go b/models/migrations/v1_13/v152.go
new file mode 100644
index 00000000..502c82a4
--- /dev/null
+++ b/models/migrations/v1_13/v152.go
@@ -0,0 +1,13 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import "xorm.io/xorm"
+
+func AddTrustModelToRepository(x *xorm.Engine) error {
+ type Repository struct {
+ TrustModel int
+ }
+ return x.Sync(new(Repository))
+}
diff --git a/models/migrations/v1_13/v153.go b/models/migrations/v1_13/v153.go
new file mode 100644
index 00000000..0b2dd3eb
--- /dev/null
+++ b/models/migrations/v1_13/v153.go
@@ -0,0 +1,24 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddTeamReviewRequestSupport(x *xorm.Engine) error {
+ type Review struct {
+ ReviewerTeamID int64 `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ type Comment struct {
+ AssigneeTeamID int64 `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ if err := x.Sync(new(Review)); err != nil {
+ return err
+ }
+
+ return x.Sync(new(Comment))
+}
diff --git a/models/migrations/v1_13/v154.go b/models/migrations/v1_13/v154.go
new file mode 100644
index 00000000..60cc5671
--- /dev/null
+++ b/models/migrations/v1_13/v154.go
@@ -0,0 +1,55 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_13 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddTimeStamps(x *xorm.Engine) error {
+ // this will add timestamps where it is useful to have
+
+ // Star represents a starred repo by an user.
+ type Star struct {
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ }
+ if err := x.Sync(new(Star)); err != nil {
+ return err
+ }
+
+ // Label represents a label of repository for issues.
+ type Label struct {
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+ if err := x.Sync(new(Label)); err != nil {
+ return err
+ }
+
+ // Follow represents relations of user and their followers.
+ type Follow struct {
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ }
+ if err := x.Sync(new(Follow)); err != nil {
+ return err
+ }
+
+ // Watch is connection request for receiving repository notification.
+ type Watch struct {
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+ if err := x.Sync(new(Watch)); err != nil {
+ return err
+ }
+
+ // Collaboration represent the relation between an individual and a repository.
+ type Collaboration struct {
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+ return x.Sync(new(Collaboration))
+}
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)
+ }
+}
diff --git a/models/migrations/v1_15/main_test.go b/models/migrations/v1_15/main_test.go
new file mode 100644
index 00000000..366f1978
--- /dev/null
+++ b/models/migrations/v1_15/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+)
+
+func TestMain(m *testing.M) {
+ base.MainTest(m)
+}
diff --git a/models/migrations/v1_15/v178.go b/models/migrations/v1_15/v178.go
new file mode 100644
index 00000000..6d236eb0
--- /dev/null
+++ b/models/migrations/v1_15/v178.go
@@ -0,0 +1,17 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddLFSMirrorColumns(x *xorm.Engine) error {
+ type Mirror struct {
+ LFS bool `xorm:"lfs_enabled NOT NULL DEFAULT false"`
+ LFSEndpoint string `xorm:"lfs_endpoint TEXT"`
+ }
+
+ return x.Sync(new(Mirror))
+}
diff --git a/models/migrations/v1_15/v179.go b/models/migrations/v1_15/v179.go
new file mode 100644
index 00000000..f6b142eb
--- /dev/null
+++ b/models/migrations/v1_15/v179.go
@@ -0,0 +1,28 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+func ConvertAvatarURLToText(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
+ }
+
+ // Some oauth2 providers may give very long avatar urls (i.e. Google)
+ return base.ModifyColumn(x, "external_login_user", &schemas.Column{
+ Name: "avatar_url",
+ SQLType: schemas.SQLType{
+ Name: schemas.Text,
+ },
+ Nullable: true,
+ DefaultIsEmpty: true,
+ })
+}
diff --git a/models/migrations/v1_15/v180.go b/models/migrations/v1_15/v180.go
new file mode 100644
index 00000000..c71e7718
--- /dev/null
+++ b/models/migrations/v1_15/v180.go
@@ -0,0 +1,121 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/util"
+
+ "xorm.io/builder"
+ "xorm.io/xorm"
+)
+
+func DeleteMigrationCredentials(x *xorm.Engine) (err error) {
+ // Task represents a task
+ type Task struct {
+ ID int64
+ DoerID int64 `xorm:"index"` // operator
+ OwnerID int64 `xorm:"index"` // repo owner id, when creating, the repoID maybe zero
+ RepoID int64 `xorm:"index"`
+ Type int
+ Status int `xorm:"index"`
+ StartTime int64
+ EndTime int64
+ PayloadContent string `xorm:"TEXT"`
+ Errors string `xorm:"TEXT"` // if task failed, saved the error reason
+ Created int64 `xorm:"created"`
+ }
+
+ const TaskTypeMigrateRepo = 0
+ const TaskStatusStopped = 2
+
+ const batchSize = 100
+
+ // only match migration tasks, that are not pending or running
+ cond := builder.Eq{
+ "type": TaskTypeMigrateRepo,
+ }.And(builder.Gte{
+ "status": TaskStatusStopped,
+ })
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ for start := 0; ; start += batchSize {
+ tasks := make([]*Task, 0, batchSize)
+ if err := sess.Limit(batchSize, start).Where(cond, 0).Find(&tasks); err != nil {
+ return err
+ }
+ if len(tasks) == 0 {
+ break
+ }
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ for _, t := range tasks {
+ if t.PayloadContent, err = removeCredentials(t.PayloadContent); err != nil {
+ return err
+ }
+ if _, err := sess.ID(t.ID).Cols("payload_content").Update(t); err != nil {
+ return err
+ }
+ }
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+ }
+ return err
+}
+
+func removeCredentials(payload string) (string, error) {
+ // MigrateOptions defines the way a repository gets migrated
+ // this is for internal usage by migrations module and func who interact with it
+ type MigrateOptions struct {
+ // required: true
+ CloneAddr string `json:"clone_addr" binding:"Required"`
+ CloneAddrEncrypted string `json:"clone_addr_encrypted,omitempty"`
+ AuthUsername string `json:"auth_username"`
+ AuthPassword string `json:"-"`
+ AuthPasswordEncrypted string `json:"auth_password_encrypted,omitempty"`
+ AuthToken string `json:"-"`
+ AuthTokenEncrypted string `json:"auth_token_encrypted,omitempty"`
+ // required: true
+ UID int `json:"uid" binding:"Required"`
+ // required: true
+ RepoName string `json:"repo_name" binding:"Required"`
+ Mirror bool `json:"mirror"`
+ LFS bool `json:"lfs"`
+ LFSEndpoint string `json:"lfs_endpoint"`
+ Private bool `json:"private"`
+ Description string `json:"description"`
+ OriginalURL string
+ GitServiceType int
+ Wiki bool
+ Issues bool
+ Milestones bool
+ Labels bool
+ Releases bool
+ Comments bool
+ PullRequests bool
+ ReleaseAssets bool
+ MigrateToRepoID int64
+ MirrorInterval string `json:"mirror_interval"`
+ }
+
+ var opts MigrateOptions
+ err := json.Unmarshal([]byte(payload), &opts)
+ if err != nil {
+ return "", err
+ }
+
+ opts.AuthPassword = ""
+ opts.AuthToken = ""
+ opts.CloneAddr = util.SanitizeCredentialURLs(opts.CloneAddr)
+
+ confBytes, err := json.Marshal(opts)
+ if err != nil {
+ return "", err
+ }
+ return string(confBytes), nil
+}
diff --git a/models/migrations/v1_15/v181.go b/models/migrations/v1_15/v181.go
new file mode 100644
index 00000000..2185ed02
--- /dev/null
+++ b/models/migrations/v1_15/v181.go
@@ -0,0 +1,91 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "strings"
+
+ "xorm.io/xorm"
+)
+
+func AddPrimaryEmail2EmailAddress(x *xorm.Engine) error {
+ type User struct {
+ ID int64 `xorm:"pk autoincr"`
+ Email string `xorm:"NOT NULL"`
+ IsActive bool `xorm:"INDEX"` // Activate primary email
+ }
+
+ type EmailAddress1 struct {
+ ID int64 `xorm:"pk autoincr"`
+ UID int64 `xorm:"INDEX NOT NULL"`
+ Email string `xorm:"UNIQUE NOT NULL"`
+ LowerEmail string
+ IsActivated bool
+ IsPrimary bool `xorm:"DEFAULT(false) NOT NULL"`
+ }
+
+ // Add lower_email and is_primary columns
+ if err := x.Table("email_address").Sync(new(EmailAddress1)); err != nil {
+ return err
+ }
+
+ if _, err := x.Exec("UPDATE email_address SET lower_email=LOWER(email), is_primary=?", false); err != nil {
+ return err
+ }
+
+ type EmailAddress struct {
+ ID int64 `xorm:"pk autoincr"`
+ UID int64 `xorm:"INDEX NOT NULL"`
+ Email string `xorm:"UNIQUE NOT NULL"`
+ LowerEmail string `xorm:"UNIQUE NOT NULL"`
+ IsActivated bool
+ IsPrimary bool `xorm:"DEFAULT(false) NOT NULL"`
+ }
+
+ // change lower_email as unique
+ if err := x.Sync(new(EmailAddress)); err != nil {
+ return err
+ }
+
+ 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).Find(&users); err != nil {
+ return err
+ }
+ if len(users) == 0 {
+ break
+ }
+
+ for _, user := range users {
+ exist, err := sess.Where("email=?", user.Email).Table("email_address").Exist()
+ if err != nil {
+ return err
+ }
+ if !exist {
+ if _, err := sess.Insert(&EmailAddress{
+ UID: user.ID,
+ Email: user.Email,
+ LowerEmail: strings.ToLower(user.Email),
+ IsActivated: user.IsActive,
+ IsPrimary: true,
+ }); err != nil {
+ return err
+ }
+ } else {
+ if _, err := sess.Where("email=?", user.Email).Cols("is_primary").Update(&EmailAddress{
+ IsPrimary: true,
+ }); err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/models/migrations/v1_15/v181_test.go b/models/migrations/v1_15/v181_test.go
new file mode 100644
index 00000000..de44e4ae
--- /dev/null
+++ b/models/migrations/v1_15/v181_test.go
@@ -0,0 +1,56 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "strings"
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_AddPrimaryEmail2EmailAddress(t *testing.T) {
+ type User struct {
+ ID int64
+ Email string
+ IsActive bool
+ }
+
+ // Prepare and load the testing database
+ x, deferable := base.PrepareTestEnv(t, 0, new(User))
+ if x == nil || t.Failed() {
+ defer deferable()
+ return
+ }
+ defer deferable()
+
+ err := AddPrimaryEmail2EmailAddress(x)
+ require.NoError(t, err)
+
+ type EmailAddress struct {
+ ID int64 `xorm:"pk autoincr"`
+ UID int64 `xorm:"INDEX NOT NULL"`
+ Email string `xorm:"UNIQUE NOT NULL"`
+ LowerEmail string `xorm:"UNIQUE NOT NULL"`
+ IsActivated bool
+ IsPrimary bool `xorm:"DEFAULT(false) NOT NULL"`
+ }
+
+ users := make([]User, 0, 20)
+ err = x.Find(&users)
+ require.NoError(t, err)
+
+ for _, user := range users {
+ var emailAddress EmailAddress
+ has, err := x.Where("lower_email=?", strings.ToLower(user.Email)).Get(&emailAddress)
+ require.NoError(t, err)
+ assert.True(t, has)
+ assert.True(t, emailAddress.IsPrimary)
+ assert.EqualValues(t, user.IsActive, emailAddress.IsActivated)
+ assert.EqualValues(t, user.ID, emailAddress.UID)
+ }
+}
diff --git a/models/migrations/v1_15/v182.go b/models/migrations/v1_15/v182.go
new file mode 100644
index 00000000..9ca500c0
--- /dev/null
+++ b/models/migrations/v1_15/v182.go
@@ -0,0 +1,41 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddIssueResourceIndexTable(x *xorm.Engine) error {
+ type ResourceIndex struct {
+ GroupID int64 `xorm:"pk"`
+ MaxIndex int64 `xorm:"index"`
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if err := sess.Table("issue_index").Sync(new(ResourceIndex)); err != nil {
+ return err
+ }
+
+ // Remove data we're goint to rebuild
+ if _, err := sess.Table("issue_index").Where("1=1").Delete(&ResourceIndex{}); err != nil {
+ return err
+ }
+
+ // Create current data for all repositories with issues and PRs
+ if _, err := sess.Exec("INSERT INTO issue_index (group_id, max_index) " +
+ "SELECT max_data.repo_id, max_data.max_index " +
+ "FROM ( SELECT issue.repo_id AS repo_id, max(issue.`index`) AS max_index " +
+ "FROM issue GROUP BY issue.repo_id) AS max_data"); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_15/v182_test.go b/models/migrations/v1_15/v182_test.go
new file mode 100644
index 00000000..e0fee18a
--- /dev/null
+++ b/models/migrations/v1_15/v182_test.go
@@ -0,0 +1,61 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_AddIssueResourceIndexTable(t *testing.T) {
+ // Create the models used in the migration
+ type Issue struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"UNIQUE(s)"`
+ Index int64 `xorm:"UNIQUE(s)"`
+ }
+
+ // Prepare and load the testing database
+ x, deferable := base.PrepareTestEnv(t, 0, new(Issue))
+ if x == nil || t.Failed() {
+ defer deferable()
+ return
+ }
+ defer deferable()
+
+ // Run the migration
+ if err := AddIssueResourceIndexTable(x); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ type ResourceIndex struct {
+ GroupID int64 `xorm:"pk"`
+ MaxIndex int64 `xorm:"index"`
+ }
+
+ start := 0
+ const batchSize = 1000
+ for {
+ indexes := make([]ResourceIndex, 0, batchSize)
+ err := x.Table("issue_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 issue WHERE repo_id = ?", idx.GroupID).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_15/v183.go b/models/migrations/v1_15/v183.go
new file mode 100644
index 00000000..effad1b4
--- /dev/null
+++ b/models/migrations/v1_15/v183.go
@@ -0,0 +1,38 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "fmt"
+ "time"
+
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func CreatePushMirrorTable(x *xorm.Engine) error {
+ type PushMirror struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX"`
+ RemoteName string
+
+ Interval time.Duration
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+ LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
+ LastError string `xorm:"text"`
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if err := sess.Sync(new(PushMirror)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_15/v184.go b/models/migrations/v1_15/v184.go
new file mode 100644
index 00000000..871c9db1
--- /dev/null
+++ b/models/migrations/v1_15/v184.go
@@ -0,0 +1,66 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func RenameTaskErrorsToMessage(x *xorm.Engine) error {
+ type Task struct {
+ Errors string `xorm:"TEXT"` // if task failed, saved the error reason
+ Type int
+ Status int `xorm:"index"`
+ }
+
+ // This migration maybe rerun so that we should check if it has been run
+ messageExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "task", "message")
+ if err != nil {
+ return err
+ }
+
+ if messageExist {
+ errorsExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "task", "errors")
+ if err != nil {
+ return err
+ }
+ if !errorsExist {
+ return nil
+ }
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if err := sess.Sync(new(Task)); err != nil {
+ return fmt.Errorf("error on Sync: %w", err)
+ }
+
+ if messageExist {
+ // if both errors and message exist, drop message at first
+ if err := base.DropTableColumns(sess, "task", "message"); err != nil {
+ return err
+ }
+ }
+
+ if setting.Database.Type.IsMySQL() {
+ if _, err := sess.Exec("ALTER TABLE `task` CHANGE errors message text"); err != nil {
+ return err
+ }
+ } else {
+ if _, err := sess.Exec("ALTER TABLE `task` RENAME COLUMN errors TO message"); err != nil {
+ return err
+ }
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_15/v185.go b/models/migrations/v1_15/v185.go
new file mode 100644
index 00000000..e5878ec1
--- /dev/null
+++ b/models/migrations/v1_15/v185.go
@@ -0,0 +1,21 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddRepoArchiver(x *xorm.Engine) error {
+ // RepoArchiver represents all archivers
+ type RepoArchiver struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"index unique(s)"`
+ Type int `xorm:"unique(s)"`
+ Status int
+ CommitID string `xorm:"VARCHAR(40) unique(s)"`
+ CreatedUnix int64 `xorm:"INDEX NOT NULL created"`
+ }
+ return x.Sync(new(RepoArchiver))
+}
diff --git a/models/migrations/v1_15/v186.go b/models/migrations/v1_15/v186.go
new file mode 100644
index 00000000..01aab3ad
--- /dev/null
+++ b/models/migrations/v1_15/v186.go
@@ -0,0 +1,25 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func CreateProtectedTagTable(x *xorm.Engine) error {
+ type ProtectedTag struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64
+ NamePattern string
+ AllowlistUserIDs []int64 `xorm:"JSON TEXT"`
+ AllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
+
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
+ }
+
+ return x.Sync(new(ProtectedTag))
+}
diff --git a/models/migrations/v1_15/v187.go b/models/migrations/v1_15/v187.go
new file mode 100644
index 00000000..21cd6772
--- /dev/null
+++ b/models/migrations/v1_15/v187.go
@@ -0,0 +1,47 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import (
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "xorm.io/xorm"
+)
+
+func DropWebhookColumns(x *xorm.Engine) error {
+ // Make sure the columns exist before dropping them
+ type Webhook struct {
+ Signature string `xorm:"TEXT"`
+ IsSSL bool `xorm:"is_ssl"`
+ }
+ if err := x.Sync(new(Webhook)); err != nil {
+ return err
+ }
+
+ type HookTask struct {
+ Typ string `xorm:"VARCHAR(16) index"`
+ URL string `xorm:"TEXT"`
+ Signature string `xorm:"TEXT"`
+ HTTPMethod string `xorm:"http_method"`
+ ContentType int
+ IsSSL bool
+ }
+ if err := x.Sync(new(HookTask)); err != nil {
+ return err
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ if err := base.DropTableColumns(sess, "webhook", "signature", "is_ssl"); err != nil {
+ return err
+ }
+ if err := base.DropTableColumns(sess, "hook_task", "typ", "url", "signature", "http_method", "content_type", "is_ssl"); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_15/v188.go b/models/migrations/v1_15/v188.go
new file mode 100644
index 00000000..71e45cab
--- /dev/null
+++ b/models/migrations/v1_15/v188.go
@@ -0,0 +1,14 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_15 //nolint
+
+import "xorm.io/xorm"
+
+func AddKeyIsVerified(x *xorm.Engine) error {
+ type GPGKey struct {
+ Verified bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(GPGKey))
+}
diff --git a/models/migrations/v1_16/main_test.go b/models/migrations/v1_16/main_test.go
new file mode 100644
index 00000000..817a0c13
--- /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"
+
+ "code.gitea.io/gitea/models/migrations/base"
+)
+
+func TestMain(m *testing.M) {
+ base.MainTest(m)
+}
diff --git a/models/migrations/v1_16/v189.go b/models/migrations/v1_16/v189.go
new file mode 100644
index 00000000..afd93b0e
--- /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 wrapped.Source != nil && 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 00000000..c4e45e1e
--- /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"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "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 := base.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 00000000..59538028
--- /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 00000000..c618783c
--- /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 00000000..2d5d158a
--- /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 00000000..8d3ce7a5
--- /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 00000000..1cfa7fba
--- /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"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "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 := base.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.Greater(t, attach.RepoID, int64(0))
+ assert.Greater(t, attach.IssueID, int64(0))
+ 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.Greater(t, attach.RepoID, int64(0))
+ assert.Greater(t, attach.ReleaseID, int64(0))
+ 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 00000000..6aa13c50
--- /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 00000000..6d7e9414
--- /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 00000000..dbe27634
--- /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"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "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 := base.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 00000000..7cbafc61
--- /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 00000000..97888b28
--- /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 00000000..115bb313
--- /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 00000000..6adcf890
--- /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 00000000..c08c20e5
--- /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 00000000..35e0c9f2
--- /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 00000000..6ba36152
--- /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 00000000..e8e6b524
--- /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 00000000..ece03e13
--- /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 00000000..d6c57708
--- /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 00000000..581a7d76
--- /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 00000000..91208f06
--- /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 00000000..1a11ef09
--- /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 00000000..be3100e0
--- /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 00000000..db45b11a
--- /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 00000000..11e6f8d9
--- /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"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "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 := base.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)
+}
diff --git a/models/migrations/v1_17/main_test.go b/models/migrations/v1_17/main_test.go
new file mode 100644
index 00000000..79cb3fa0
--- /dev/null
+++ b/models/migrations/v1_17/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+)
+
+func TestMain(m *testing.M) {
+ base.MainTest(m)
+}
diff --git a/models/migrations/v1_17/v211.go b/models/migrations/v1_17/v211.go
new file mode 100644
index 00000000..9b72c861
--- /dev/null
+++ b/models/migrations/v1_17/v211.go
@@ -0,0 +1,12 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func CreateForeignReferenceTable(_ *xorm.Engine) error {
+ return nil // This table was dropped in v1_19/v237.go
+}
diff --git a/models/migrations/v1_17/v212.go b/models/migrations/v1_17/v212.go
new file mode 100644
index 00000000..e3f94371
--- /dev/null
+++ b/models/migrations/v1_17/v212.go
@@ -0,0 +1,93 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddPackageTables(x *xorm.Engine) error {
+ type Package struct {
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ RepoID int64 `xorm:"INDEX"`
+ Type string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ Name string `xorm:"NOT NULL"`
+ LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ SemverCompatible bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ if err := x.Sync(new(Package)); err != nil {
+ return err
+ }
+
+ type PackageVersion struct {
+ ID int64 `xorm:"pk autoincr"`
+ PackageID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ CreatorID int64 `xorm:"NOT NULL DEFAULT 0"`
+ Version string `xorm:"NOT NULL"`
+ LowerVersion string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
+ IsInternal bool `xorm:"INDEX NOT NULL DEFAULT false"`
+ MetadataJSON string `xorm:"metadata_json TEXT"`
+ DownloadCount int64 `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ if err := x.Sync(new(PackageVersion)); err != nil {
+ return err
+ }
+
+ type PackageProperty struct {
+ ID int64 `xorm:"pk autoincr"`
+ RefType int64 `xorm:"INDEX NOT NULL"`
+ RefID int64 `xorm:"INDEX NOT NULL"`
+ Name string `xorm:"INDEX NOT NULL"`
+ Value string `xorm:"TEXT NOT NULL"`
+ }
+
+ if err := x.Sync(new(PackageProperty)); err != nil {
+ return err
+ }
+
+ type PackageFile struct {
+ ID int64 `xorm:"pk autoincr"`
+ VersionID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ BlobID int64 `xorm:"INDEX NOT NULL"`
+ Name string `xorm:"NOT NULL"`
+ LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ CompositeKey string `xorm:"UNIQUE(s) INDEX"`
+ IsLead bool `xorm:"NOT NULL DEFAULT false"`
+ CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
+ }
+
+ if err := x.Sync(new(PackageFile)); err != nil {
+ return err
+ }
+
+ type PackageBlob struct {
+ ID int64 `xorm:"pk autoincr"`
+ Size int64 `xorm:"NOT NULL DEFAULT 0"`
+ HashMD5 string `xorm:"hash_md5 char(32) UNIQUE(md5) INDEX NOT NULL"`
+ HashSHA1 string `xorm:"hash_sha1 char(40) UNIQUE(sha1) INDEX NOT NULL"`
+ HashSHA256 string `xorm:"hash_sha256 char(64) UNIQUE(sha256) INDEX NOT NULL"`
+ HashSHA512 string `xorm:"hash_sha512 char(128) UNIQUE(sha512) INDEX NOT NULL"`
+ CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
+ }
+
+ if err := x.Sync(new(PackageBlob)); err != nil {
+ return err
+ }
+
+ type PackageBlobUpload struct {
+ ID string `xorm:"pk"`
+ BytesReceived int64 `xorm:"NOT NULL DEFAULT 0"`
+ HashStateBytes []byte `xorm:"BLOB"`
+ CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"`
+ }
+
+ return x.Sync(new(PackageBlobUpload))
+}
diff --git a/models/migrations/v1_17/v213.go b/models/migrations/v1_17/v213.go
new file mode 100644
index 00000000..bb3f466e
--- /dev/null
+++ b/models/migrations/v1_17/v213.go
@@ -0,0 +1,17 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddAllowMaintainerEdit(x *xorm.Engine) error {
+ // PullRequest represents relation between pull request and repositories.
+ type PullRequest struct {
+ AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(PullRequest))
+}
diff --git a/models/migrations/v1_17/v214.go b/models/migrations/v1_17/v214.go
new file mode 100644
index 00000000..22681649
--- /dev/null
+++ b/models/migrations/v1_17/v214.go
@@ -0,0 +1,22 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddAutoMergeTable(x *xorm.Engine) error {
+ type MergeStyle string
+ type PullAutoMerge struct {
+ ID int64 `xorm:"pk autoincr"`
+ PullID int64 `xorm:"UNIQUE"`
+ DoerID int64 `xorm:"NOT NULL"`
+ MergeStyle MergeStyle `xorm:"varchar(30)"`
+ Message string `xorm:"LONGTEXT"`
+ CreatedUnix int64 `xorm:"created"`
+ }
+
+ return x.Sync(&PullAutoMerge{})
+}
diff --git a/models/migrations/v1_17/v215.go b/models/migrations/v1_17/v215.go
new file mode 100644
index 00000000..b338f854
--- /dev/null
+++ b/models/migrations/v1_17/v215.go
@@ -0,0 +1,24 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "code.gitea.io/gitea/models/pull"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddReviewViewedFiles(x *xorm.Engine) error {
+ type ReviewState struct {
+ ID int64 `xorm:"pk autoincr"`
+ 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)"`
+ UpdatedFiles map[string]pull.ViewedState `xorm:"NOT NULL LONGTEXT JSON"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
+ }
+
+ return x.Sync(new(ReviewState))
+}
diff --git a/models/migrations/v1_17/v216.go b/models/migrations/v1_17/v216.go
new file mode 100644
index 00000000..268f472a
--- /dev/null
+++ b/models/migrations/v1_17/v216.go
@@ -0,0 +1,7 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+// This migration added non-ideal indices to the action table which on larger datasets slowed things down
+// it has been superseded by v218.go
diff --git a/models/migrations/v1_17/v217.go b/models/migrations/v1_17/v217.go
new file mode 100644
index 00000000..3f970b68
--- /dev/null
+++ b/models/migrations/v1_17/v217.go
@@ -0,0 +1,25 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func AlterHookTaskTextFieldsToLongText(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 `hook_task` CHANGE `payload_content` `payload_content` LONGTEXT, CHANGE `request_content` `request_content` LONGTEXT, change `response_content` `response_content` LONGTEXT"); err != nil {
+ return err
+ }
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_17/v218.go b/models/migrations/v1_17/v218.go
new file mode 100644
index 00000000..4c05a9b5
--- /dev/null
+++ b/models/migrations/v1_17/v218.go
@@ -0,0 +1,52 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+type improveActionTableIndicesAction struct {
+ ID int64 `xorm:"pk autoincr"`
+ UserID int64 // Receiver user id.
+ OpType int
+ ActUserID int64 // Action user id.
+ RepoID int64
+ CommentID int64 `xorm:"INDEX"`
+ IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
+ RefName string
+ IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
+ Content string `xorm:"TEXT"`
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+}
+
+// TableName sets the name of this table
+func (*improveActionTableIndicesAction) TableName() string {
+ return "action"
+}
+
+// TableIndices implements xorm's TableIndices interface
+func (*improveActionTableIndicesAction) TableIndices() []*schemas.Index {
+ repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
+ repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
+
+ actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
+ actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
+ indices := []*schemas.Index{actUserIndex, repoIndex}
+ if setting.Database.Type.IsPostgreSQL() {
+ cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
+ cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
+ indices = append(indices, cudIndex)
+ }
+
+ return indices
+}
+
+func ImproveActionTableIndices(x *xorm.Engine) error {
+ return x.Sync(&improveActionTableIndicesAction{})
+}
diff --git a/models/migrations/v1_17/v219.go b/models/migrations/v1_17/v219.go
new file mode 100644
index 00000000..d266029f
--- /dev/null
+++ b/models/migrations/v1_17/v219.go
@@ -0,0 +1,30 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "time"
+
+ "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddSyncOnCommitColForPushMirror(x *xorm.Engine) error {
+ type PushMirror struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX"`
+ Repo *repo.Repository `xorm:"-"`
+ RemoteName string
+
+ SyncOnCommit bool `xorm:"NOT NULL DEFAULT true"`
+ Interval time.Duration
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+ LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
+ LastError string `xorm:"text"`
+ }
+
+ return x.Sync(new(PushMirror))
+}
diff --git a/models/migrations/v1_17/v220.go b/models/migrations/v1_17/v220.go
new file mode 100644
index 00000000..d4007163
--- /dev/null
+++ b/models/migrations/v1_17/v220.go
@@ -0,0 +1,23 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ packages_model "code.gitea.io/gitea/models/packages"
+ container_module "code.gitea.io/gitea/modules/packages/container"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+func AddContainerRepositoryProperty(x *xorm.Engine) (err error) {
+ if x.Dialect().URI().DBType == schemas.SQLITE {
+ _, err = x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, u.lower_name || '/' || p.lower_name FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?",
+ packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer)
+ } else {
+ _, err = x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, CONCAT(u.lower_name, '/', p.lower_name) FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?",
+ packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer)
+ }
+ return err
+}
diff --git a/models/migrations/v1_17/v221.go b/models/migrations/v1_17/v221.go
new file mode 100644
index 00000000..9e159388
--- /dev/null
+++ b/models/migrations/v1_17/v221.go
@@ -0,0 +1,74 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "encoding/base32"
+ "fmt"
+
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func StoreWebauthnCredentialIDAsBytes(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)"`
+ // Note the lack of INDEX here - these will be created once the column is renamed in v223.go
+ CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+ 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
+ }
+
+ var start int
+ creds := make([]*webauthnCredential, 0, 50)
+ for {
+ err := x.Select("id, credential_id").OrderBy("id").Limit(50, start).Find(&creds)
+ 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 _, cred := range creds {
+ cred.CredentialIDBytes, err = base32.HexEncoding.DecodeString(cred.CredentialID)
+ if err != nil {
+ return fmt.Errorf("unable to parse credential id %s for credential[%d]: %w", cred.CredentialID, cred.ID, err)
+ }
+ count, err := sess.ID(cred.ID).Cols("credential_id_bytes").Update(cred)
+ if count != 1 || err != nil {
+ return fmt.Errorf("unable to update credential id bytes for credential[%d]: %d,%w", cred.ID, count, err)
+ }
+ }
+ return sess.Commit()
+ }()
+ if err != nil {
+ return err
+ }
+
+ if len(creds) < 50 {
+ break
+ }
+ start += 50
+ creds = creds[:0]
+ }
+ return nil
+}
diff --git a/models/migrations/v1_17/v221_test.go b/models/migrations/v1_17/v221_test.go
new file mode 100644
index 00000000..0aad7d67
--- /dev/null
+++ b/models/migrations/v1_17/v221_test.go
@@ -0,0 +1,63 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "encoding/base32"
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_StoreWebauthnCredentialIDAsBytes(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)"`
+ PublicKey []byte
+ AttestationType string
+ AAGUID []byte
+ SignCount uint32 `xorm:"BIGINT"`
+ CloneWarning bool
+ }
+
+ type ExpectedWebauthnCredential struct {
+ ID int64 `xorm:"pk autoincr"`
+ CredentialID string // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+ }
+
+ type ConvertedWebauthnCredential struct {
+ ID int64 `xorm:"pk autoincr"`
+ CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+ }
+
+ // Prepare and load the testing database
+ x, deferable := base.PrepareTestEnv(t, 0, new(WebauthnCredential), new(ExpectedWebauthnCredential))
+ defer deferable()
+ if x == nil || t.Failed() {
+ return
+ }
+
+ err := StoreWebauthnCredentialIDAsBytes(x)
+ require.NoError(t, err)
+
+ expected := []ExpectedWebauthnCredential{}
+ err = x.Table("expected_webauthn_credential").Asc("id").Find(&expected)
+ require.NoError(t, err)
+
+ got := []ConvertedWebauthnCredential{}
+ err = x.Table("webauthn_credential").Select("id, credential_id_bytes").Asc("id").Find(&got)
+ require.NoError(t, err)
+
+ for i, e := range expected {
+ credIDBytes, _ := base32.HexEncoding.DecodeString(e.CredentialID)
+ assert.Equal(t, credIDBytes, got[i].CredentialIDBytes)
+ }
+}
diff --git a/models/migrations/v1_17/v222.go b/models/migrations/v1_17/v222.go
new file mode 100644
index 00000000..2ffb94eb
--- /dev/null
+++ b/models/migrations/v1_17/v222.go
@@ -0,0 +1,64 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func DropOldCredentialIDColumn(x *xorm.Engine) error {
+ // This migration maybe rerun so that we should check if it has been run
+ credentialIDExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id")
+ if err != nil {
+ return err
+ }
+ if !credentialIDExist {
+ // Column is already non-extant
+ return nil
+ }
+ credentialIDBytesExists, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id_bytes")
+ if err != nil {
+ return err
+ }
+ if !credentialIDBytesExists {
+ // looks like 221 hasn't properly run
+ return fmt.Errorf("webauthn_credential does not have a credential_id_bytes column... it is not safe to run this migration")
+ }
+
+ // 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)"`
+ // Note the lack of the INDEX on CredentialIDBytes - we will add this in v223.go
+ CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+ 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
+ }
+
+ // Drop the old credential ID
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := base.DropTableColumns(sess, "webauthn_credential", "credential_id"); err != nil {
+ return fmt.Errorf("unable to drop old credentialID column: %w", err)
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_17/v223.go b/models/migrations/v1_17/v223.go
new file mode 100644
index 00000000..3592eb1b
--- /dev/null
+++ b/models/migrations/v1_17/v223.go
@@ -0,0 +1,98 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_17 //nolint
+
+import (
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func RenameCredentialIDBytes(x *xorm.Engine) error {
+ // This migration maybe rerun so that we should check if it has been run
+ credentialIDExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id")
+ if err != nil {
+ return err
+ }
+ if credentialIDExist {
+ credentialIDBytesExists, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id_bytes")
+ if err != nil {
+ return err
+ }
+ if !credentialIDBytesExists {
+ return nil
+ }
+ }
+
+ err = func() error {
+ // webauthnCredential table
+ type webauthnCredential struct {
+ ID int64 `xorm:"pk autoincr"`
+ Name string
+ LowerName string `xorm:"unique(s)"`
+ UserID int64 `xorm:"INDEX unique(s)"`
+ // Note the lack of INDEX here
+ CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+ PublicKey []byte
+ AttestationType string
+ AAGUID []byte
+ SignCount uint32 `xorm:"BIGINT"`
+ CloneWarning bool
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if err := sess.Sync(new(webauthnCredential)); err != nil {
+ return fmt.Errorf("error on Sync: %w", err)
+ }
+
+ if credentialIDExist {
+ // if both errors and message exist, drop message at first
+ if err := base.DropTableColumns(sess, "webauthn_credential", "credential_id"); err != nil {
+ return err
+ }
+ }
+
+ if setting.Database.Type.IsMySQL() {
+ if _, err := sess.Exec("ALTER TABLE `webauthn_credential` CHANGE credential_id_bytes credential_id VARBINARY(1024)"); err != nil {
+ return err
+ }
+ } else {
+ if _, err := sess.Exec("ALTER TABLE `webauthn_credential` RENAME COLUMN credential_id_bytes TO credential_id"); err != nil {
+ return err
+ }
+ }
+ return sess.Commit()
+ }()
+ if err != nil {
+ return err
+ }
+
+ // 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 []byte `xorm:"INDEX VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+ PublicKey []byte
+ AttestationType string
+ AAGUID []byte
+ SignCount uint32 `xorm:"BIGINT"`
+ CloneWarning bool
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+ return x.Sync(&webauthnCredential{})
+}
diff --git a/models/migrations/v1_18/main_test.go b/models/migrations/v1_18/main_test.go
new file mode 100644
index 00000000..f71a21d1
--- /dev/null
+++ b/models/migrations/v1_18/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_18 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+)
+
+func TestMain(m *testing.M) {
+ base.MainTest(m)
+}
diff --git a/models/migrations/v1_18/v224.go b/models/migrations/v1_18/v224.go
new file mode 100644
index 00000000..f3d522b9
--- /dev/null
+++ b/models/migrations/v1_18/v224.go
@@ -0,0 +1,27 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_18 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func CreateUserBadgesTable(x *xorm.Engine) error {
+ type Badge struct {
+ ID int64 `xorm:"pk autoincr"`
+ Description string
+ ImageURL string
+ }
+
+ type userBadge struct {
+ ID int64 `xorm:"pk autoincr"`
+ BadgeID int64
+ UserID int64 `xorm:"INDEX"`
+ }
+
+ if err := x.Sync(new(Badge)); err != nil {
+ return err
+ }
+ return x.Sync(new(userBadge))
+}
diff --git a/models/migrations/v1_18/v225.go b/models/migrations/v1_18/v225.go
new file mode 100644
index 00000000..b0ac3777
--- /dev/null
+++ b/models/migrations/v1_18/v225.go
@@ -0,0 +1,28 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_18 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func AlterPublicGPGKeyContentFieldsToMediumText(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 `gpg_key` CHANGE `content` `content` MEDIUMTEXT"); err != nil {
+ return err
+ }
+ if _, err := sess.Exec("ALTER TABLE `public_key` CHANGE `content` `content` MEDIUMTEXT"); err != nil {
+ return err
+ }
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_18/v226.go b/models/migrations/v1_18/v226.go
new file mode 100644
index 00000000..f87e24b1
--- /dev/null
+++ b/models/migrations/v1_18/v226.go
@@ -0,0 +1,14 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_18 //nolint
+
+import (
+ "xorm.io/builder"
+ "xorm.io/xorm"
+)
+
+func FixPackageSemverField(x *xorm.Engine) error {
+ _, err := x.Exec(builder.Update(builder.Eq{"semver_compatible": false}).From("`package`").Where(builder.In("`type`", "conan", "generic")))
+ return err
+}
diff --git a/models/migrations/v1_18/v227.go b/models/migrations/v1_18/v227.go
new file mode 100644
index 00000000..5fe5dcd0
--- /dev/null
+++ b/models/migrations/v1_18/v227.go
@@ -0,0 +1,23 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_18 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+type SystemSetting struct {
+ ID int64 `xorm:"pk autoincr"`
+ SettingKey string `xorm:"varchar(255) unique"` // ensure key is always lowercase
+ SettingValue string `xorm:"text"`
+ Version int `xorm:"version"` // prevent to override
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated"`
+}
+
+func CreateSystemSettingsTable(x *xorm.Engine) error {
+ return x.Sync(new(SystemSetting))
+}
diff --git a/models/migrations/v1_18/v228.go b/models/migrations/v1_18/v228.go
new file mode 100644
index 00000000..3e7a36de
--- /dev/null
+++ b/models/migrations/v1_18/v228.go
@@ -0,0 +1,25 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_18 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddTeamInviteTable(x *xorm.Engine) error {
+ type TeamInvite struct {
+ ID int64 `xorm:"pk autoincr"`
+ Token string `xorm:"UNIQUE(token) INDEX NOT NULL DEFAULT ''"`
+ InviterID int64 `xorm:"NOT NULL DEFAULT 0"`
+ OrgID int64 `xorm:"INDEX NOT NULL DEFAULT 0"`
+ TeamID int64 `xorm:"UNIQUE(team_mail) INDEX NOT NULL DEFAULT 0"`
+ Email string `xorm:"UNIQUE(team_mail) NOT NULL DEFAULT ''"`
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ return x.Sync(new(TeamInvite))
+}
diff --git a/models/migrations/v1_18/v229.go b/models/migrations/v1_18/v229.go
new file mode 100644
index 00000000..10d9f350
--- /dev/null
+++ b/models/migrations/v1_18/v229.go
@@ -0,0 +1,46 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_18 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/models/issues"
+
+ "xorm.io/builder"
+ "xorm.io/xorm"
+)
+
+func UpdateOpenMilestoneCounts(x *xorm.Engine) error {
+ var openMilestoneIDs []int64
+ err := x.Table("milestone").Select("id").Where(builder.Neq{"is_closed": 1}).Find(&openMilestoneIDs)
+ if err != nil {
+ return fmt.Errorf("error selecting open milestone IDs: %w", err)
+ }
+
+ for _, id := range openMilestoneIDs {
+ _, err := x.ID(id).
+ SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
+ builder.Eq{"milestone_id": id},
+ )).
+ SetExpr("num_closed_issues", builder.Select("count(*)").From("issue").Where(
+ builder.Eq{
+ "milestone_id": id,
+ "is_closed": true,
+ },
+ )).
+ Update(&issues.Milestone{})
+ if err != nil {
+ return fmt.Errorf("error updating issue counts in milestone %d: %w", id, err)
+ }
+ _, err = x.Exec("UPDATE `milestone` SET completeness=100*num_closed_issues/(CASE WHEN num_issues > 0 THEN num_issues ELSE 1 END) WHERE id=?",
+ id,
+ )
+ if err != nil {
+ return fmt.Errorf("error setting completeness on milestone %d: %w", id, err)
+ }
+ }
+
+ return nil
+}
diff --git a/models/migrations/v1_18/v229_test.go b/models/migrations/v1_18/v229_test.go
new file mode 100644
index 00000000..b4dd6f44
--- /dev/null
+++ b/models/migrations/v1_18/v229_test.go
@@ -0,0 +1,45 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_18 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_UpdateOpenMilestoneCounts(t *testing.T) {
+ type ExpectedMilestone issues.Milestone
+
+ // Prepare and load the testing database
+ x, deferable := base.PrepareTestEnv(t, 0, new(issues.Milestone), new(ExpectedMilestone), new(issues.Issue))
+ defer deferable()
+ if x == nil || t.Failed() {
+ return
+ }
+
+ if err := UpdateOpenMilestoneCounts(x); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ expected := []ExpectedMilestone{}
+ err := x.Table("expected_milestone").Asc("id").Find(&expected)
+ require.NoError(t, err)
+
+ got := []issues.Milestone{}
+ err = x.Table("milestone").Asc("id").Find(&got)
+ require.NoError(t, err)
+
+ for i, e := range expected {
+ got := got[i]
+ assert.Equal(t, e.ID, got.ID)
+ assert.Equal(t, e.NumIssues, got.NumIssues)
+ assert.Equal(t, e.NumClosedIssues, got.NumClosedIssues)
+ }
+}
diff --git a/models/migrations/v1_18/v230.go b/models/migrations/v1_18/v230.go
new file mode 100644
index 00000000..ea5b4d02
--- /dev/null
+++ b/models/migrations/v1_18/v230.go
@@ -0,0 +1,17 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_18 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+// AddConfidentialColumnToOAuth2ApplicationTable: add ConfidentialClient column, setting existing rows to true
+func AddConfidentialClientColumnToOAuth2ApplicationTable(x *xorm.Engine) error {
+ type oauth2Application struct {
+ ID int64
+ ConfidentialClient bool `xorm:"NOT NULL DEFAULT TRUE"`
+ }
+ return x.Sync(new(oauth2Application))
+}
diff --git a/models/migrations/v1_18/v230_test.go b/models/migrations/v1_18/v230_test.go
new file mode 100644
index 00000000..7ccb2156
--- /dev/null
+++ b/models/migrations/v1_18/v230_test.go
@@ -0,0 +1,47 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_18 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_AddConfidentialClientColumnToOAuth2ApplicationTable(t *testing.T) {
+ // premigration
+ type oauth2Application struct {
+ ID int64
+ }
+
+ // Prepare and load the testing database
+ x, deferable := base.PrepareTestEnv(t, 0, new(oauth2Application))
+ defer deferable()
+ if x == nil || t.Failed() {
+ return
+ }
+
+ if err := AddConfidentialClientColumnToOAuth2ApplicationTable(x); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ // postmigration
+ type ExpectedOAuth2Application struct {
+ ID int64
+ ConfidentialClient bool
+ }
+
+ got := []ExpectedOAuth2Application{}
+ err := x.Table("oauth2_application").Select("id, confidential_client").Find(&got)
+ require.NoError(t, err)
+
+ assert.NotEmpty(t, got)
+ for _, e := range got {
+ assert.True(t, e.ConfidentialClient)
+ }
+}
diff --git a/models/migrations/v1_19/main_test.go b/models/migrations/v1_19/main_test.go
new file mode 100644
index 00000000..59f42af1
--- /dev/null
+++ b/models/migrations/v1_19/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+)
+
+func TestMain(m *testing.M) {
+ base.MainTest(m)
+}
diff --git a/models/migrations/v1_19/v231.go b/models/migrations/v1_19/v231.go
new file mode 100644
index 00000000..79e46132
--- /dev/null
+++ b/models/migrations/v1_19/v231.go
@@ -0,0 +1,18 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddIndexForHookTask(x *xorm.Engine) error {
+ type HookTask struct {
+ ID int64 `xorm:"pk autoincr"`
+ HookID int64 `xorm:"index"`
+ UUID string `xorm:"unique"`
+ }
+
+ return x.Sync(new(HookTask))
+}
diff --git a/models/migrations/v1_19/v232.go b/models/migrations/v1_19/v232.go
new file mode 100644
index 00000000..9caf587c
--- /dev/null
+++ b/models/migrations/v1_19/v232.go
@@ -0,0 +1,25 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func AlterPackageVersionMetadataToLongText(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 `package_version` MODIFY COLUMN `metadata_json` LONGTEXT"); err != nil {
+ return err
+ }
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_19/v233.go b/models/migrations/v1_19/v233.go
new file mode 100644
index 00000000..ba4cd8e2
--- /dev/null
+++ b/models/migrations/v1_19/v233.go
@@ -0,0 +1,181 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/secret"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+
+ "xorm.io/builder"
+ "xorm.io/xorm"
+)
+
+func batchProcess[T any](x *xorm.Engine, buf []T, query func(limit, start int) *xorm.Session, process func(*xorm.Session, T) error) error {
+ size := cap(buf)
+ start := 0
+ for {
+ err := query(size, start).Find(&buf)
+ if err != nil {
+ return err
+ }
+ if len(buf) == 0 {
+ return nil
+ }
+
+ 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 _, record := range buf {
+ if err := process(sess, record); err != nil {
+ return err
+ }
+ }
+ return sess.Commit()
+ }()
+ if err != nil {
+ return err
+ }
+
+ if len(buf) < size {
+ return nil
+ }
+ start += size
+ buf = buf[:0]
+ }
+}
+
+func AddHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error {
+ // Add the column to the table
+ type Webhook struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type string `xorm:"VARCHAR(16) 'type'"`
+ Meta string `xorm:"TEXT"` // store hook-specific attributes
+
+ // HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization()
+ HeaderAuthorizationEncrypted string `xorm:"TEXT"`
+ }
+ err := x.Sync(new(Webhook))
+ if err != nil {
+ return err
+ }
+
+ // Migrate the matrix webhooks
+
+ type MatrixMeta struct {
+ HomeserverURL string `json:"homeserver_url"`
+ Room string `json:"room_id"`
+ MessageType int `json:"message_type"`
+ }
+ type MatrixMetaWithAccessToken struct {
+ MatrixMeta
+ AccessToken string `json:"access_token"`
+ }
+
+ err = batchProcess(x,
+ make([]*Webhook, 0, 50),
+ func(limit, start int) *xorm.Session {
+ return x.Where("type=?", "matrix").OrderBy("id").Limit(limit, start)
+ },
+ func(sess *xorm.Session, hook *Webhook) error {
+ // retrieve token from meta
+ var withToken MatrixMetaWithAccessToken
+ err := json.Unmarshal([]byte(hook.Meta), &withToken)
+ if err != nil {
+ return fmt.Errorf("unable to unmarshal matrix meta for webhook[id=%d]: %w", hook.ID, err)
+ }
+ if withToken.AccessToken == "" {
+ return nil
+ }
+
+ // encrypt token
+ authorization := "Bearer " + withToken.AccessToken
+ hook.HeaderAuthorizationEncrypted, err = secret.EncryptSecret(setting.SecretKey, authorization)
+ if err != nil {
+ return fmt.Errorf("unable to encrypt access token for webhook[id=%d]: %w", hook.ID, err)
+ }
+
+ // remove token from meta
+ withoutToken, err := json.Marshal(withToken.MatrixMeta)
+ if err != nil {
+ return fmt.Errorf("unable to marshal matrix meta for webhook[id=%d]: %w", hook.ID, err)
+ }
+ hook.Meta = string(withoutToken)
+
+ // save in database
+ count, err := sess.ID(hook.ID).Cols("meta", "header_authorization_encrypted").Update(hook)
+ if count != 1 || err != nil {
+ return fmt.Errorf("unable to update header_authorization_encrypted for webhook[id=%d]: %d,%w", hook.ID, count, err)
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ // Remove access_token from HookTask
+
+ type HookTask struct {
+ ID int64 `xorm:"pk autoincr"`
+ HookID int64
+ PayloadContent string `xorm:"LONGTEXT"`
+ }
+
+ type MatrixPayloadSafe struct {
+ Body string `json:"body"`
+ MsgType string `json:"msgtype"`
+ Format string `json:"format"`
+ FormattedBody string `json:"formatted_body"`
+ Commits []*api.PayloadCommit `json:"io.gitea.commits,omitempty"`
+ }
+ type MatrixPayloadUnsafe struct {
+ MatrixPayloadSafe
+ AccessToken string `json:"access_token"`
+ }
+
+ err = batchProcess(x,
+ make([]*HookTask, 0, 50),
+ func(limit, start int) *xorm.Session {
+ return x.Where(builder.And(
+ builder.In("hook_id", builder.Select("id").From("webhook").Where(builder.Eq{"type": "matrix"})),
+ builder.Like{"payload_content", "access_token"},
+ )).OrderBy("id").Limit(limit, 0) // ignore the provided "start", since other payload were already converted and don't contain 'payload_content' anymore
+ },
+ func(sess *xorm.Session, hookTask *HookTask) error {
+ // retrieve token from payload_content
+ var withToken MatrixPayloadUnsafe
+ err := json.Unmarshal([]byte(hookTask.PayloadContent), &withToken)
+ if err != nil {
+ return fmt.Errorf("unable to unmarshal payload_content for hook_task[id=%d]: %w", hookTask.ID, err)
+ }
+ if withToken.AccessToken == "" {
+ return nil
+ }
+
+ // remove token from payload_content
+ withoutToken, err := json.Marshal(withToken.MatrixPayloadSafe)
+ if err != nil {
+ return fmt.Errorf("unable to marshal payload_content for hook_task[id=%d]: %w", hookTask.ID, err)
+ }
+ hookTask.PayloadContent = string(withoutToken)
+
+ // save in database
+ count, err := sess.ID(hookTask.ID).Cols("payload_content").Update(hookTask)
+ if count != 1 || err != nil {
+ return fmt.Errorf("unable to update payload_content for hook_task[id=%d]: %d,%w", hookTask.ID, count, err)
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/models/migrations/v1_19/v233_test.go b/models/migrations/v1_19/v233_test.go
new file mode 100644
index 00000000..84f1dcdb
--- /dev/null
+++ b/models/migrations/v1_19/v233_test.go
@@ -0,0 +1,86 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/secret"
+ "code.gitea.io/gitea/modules/setting"
+ webhook_module "code.gitea.io/gitea/modules/webhook"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_AddHeaderAuthorizationEncryptedColWebhook(t *testing.T) {
+ // Create Webhook table
+ type Webhook struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type webhook_module.HookType `xorm:"VARCHAR(16) 'type'"`
+ Meta string `xorm:"TEXT"` // store hook-specific attributes
+
+ // HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization()
+ HeaderAuthorizationEncrypted string `xorm:"TEXT"`
+ }
+
+ type ExpectedWebhook struct {
+ ID int64 `xorm:"pk autoincr"`
+ Meta string
+ HeaderAuthorization string
+ }
+
+ type HookTask struct {
+ ID int64 `xorm:"pk autoincr"`
+ HookID int64
+ PayloadContent string `xorm:"LONGTEXT"`
+ }
+
+ // Prepare and load the testing database
+ x, deferable := base.PrepareTestEnv(t, 0, new(Webhook), new(ExpectedWebhook), new(HookTask))
+ defer deferable()
+ if x == nil || t.Failed() {
+ return
+ }
+
+ if err := AddHeaderAuthorizationEncryptedColWebhook(x); err != nil {
+ require.NoError(t, err)
+ return
+ }
+
+ expected := []ExpectedWebhook{}
+ err := x.Table("expected_webhook").Asc("id").Find(&expected)
+ require.NoError(t, err)
+
+ got := []Webhook{}
+ err = x.Table("webhook").Select("id, meta, header_authorization_encrypted").Asc("id").Find(&got)
+ require.NoError(t, err)
+
+ for i, e := range expected {
+ assert.Equal(t, e.Meta, got[i].Meta)
+
+ if e.HeaderAuthorization == "" {
+ assert.Equal(t, "", got[i].HeaderAuthorizationEncrypted)
+ } else {
+ cipherhex := got[i].HeaderAuthorizationEncrypted
+ cleartext, err := secret.DecryptSecret(setting.SecretKey, cipherhex)
+ require.NoError(t, err)
+ assert.Equal(t, e.HeaderAuthorization, cleartext)
+ }
+ }
+
+ // ensure that no hook_task has some remaining "access_token"
+ hookTasks := []HookTask{}
+ err = x.Table("hook_task").Select("id, payload_content").Asc("id").Find(&hookTasks)
+ require.NoError(t, err)
+
+ for _, h := range hookTasks {
+ var m map[string]any
+ err := json.Unmarshal([]byte(h.PayloadContent), &m)
+ require.NoError(t, err)
+ assert.Nil(t, m["access_token"])
+ }
+}
diff --git a/models/migrations/v1_19/v234.go b/models/migrations/v1_19/v234.go
new file mode 100644
index 00000000..728a5808
--- /dev/null
+++ b/models/migrations/v1_19/v234.go
@@ -0,0 +1,28 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func CreatePackageCleanupRuleTable(x *xorm.Engine) error {
+ type PackageCleanupRule struct {
+ ID int64 `xorm:"pk autoincr"`
+ Enabled bool `xorm:"INDEX NOT NULL DEFAULT false"`
+ OwnerID int64 `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT 0"`
+ Type string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ KeepCount int `xorm:"NOT NULL DEFAULT 0"`
+ KeepPattern string `xorm:"NOT NULL DEFAULT ''"`
+ RemoveDays int `xorm:"NOT NULL DEFAULT 0"`
+ RemovePattern string `xorm:"NOT NULL DEFAULT ''"`
+ MatchFullName bool `xorm:"NOT NULL DEFAULT false"`
+ CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL DEFAULT 0"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL DEFAULT 0"`
+ }
+
+ return x.Sync(new(PackageCleanupRule))
+}
diff --git a/models/migrations/v1_19/v235.go b/models/migrations/v1_19/v235.go
new file mode 100644
index 00000000..3715de39
--- /dev/null
+++ b/models/migrations/v1_19/v235.go
@@ -0,0 +1,16 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddIndexForAccessToken(x *xorm.Engine) error {
+ type AccessToken struct {
+ TokenLastEight string `xorm:"INDEX token_last_eight"`
+ }
+
+ return x.Sync(new(AccessToken))
+}
diff --git a/models/migrations/v1_19/v236.go b/models/migrations/v1_19/v236.go
new file mode 100644
index 00000000..f172a85b
--- /dev/null
+++ b/models/migrations/v1_19/v236.go
@@ -0,0 +1,23 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func CreateSecretsTable(x *xorm.Engine) error {
+ type Secret struct {
+ ID int64
+ OwnerID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL"`
+ RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL DEFAULT 0"`
+ Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
+ Data string `xorm:"LONGTEXT"`
+ CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
+ }
+
+ return x.Sync(new(Secret))
+}
diff --git a/models/migrations/v1_19/v237.go b/models/migrations/v1_19/v237.go
new file mode 100644
index 00000000..b23c765a
--- /dev/null
+++ b/models/migrations/v1_19/v237.go
@@ -0,0 +1,15 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func DropForeignReferenceTable(x *xorm.Engine) error {
+ // Drop the table introduced in `v211`, it's considered badly designed and doesn't look like to be used.
+ // See: https://github.com/go-gitea/gitea/issues/21086#issuecomment-1318217453
+ type ForeignReference struct{}
+ return x.DropTables(new(ForeignReference))
+}
diff --git a/models/migrations/v1_19/v238.go b/models/migrations/v1_19/v238.go
new file mode 100644
index 00000000..266e6cea
--- /dev/null
+++ b/models/migrations/v1_19/v238.go
@@ -0,0 +1,27 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+// AddUpdatedUnixToLFSMetaObject adds an updated column to the LFSMetaObject to allow for garbage collection
+func AddUpdatedUnixToLFSMetaObject(x *xorm.Engine) error {
+ // Drop the table introduced in `v211`, it's considered badly designed and doesn't look like to be used.
+ // See: https://github.com/go-gitea/gitea/issues/21086#issuecomment-1318217453
+ // LFSMetaObject stores metadata for LFS tracked files.
+ type LFSMetaObject struct {
+ ID int64 `xorm:"pk autoincr"`
+ Oid string `json:"oid" xorm:"UNIQUE(s) INDEX NOT NULL"`
+ Size int64 `json:"size" xorm:"NOT NULL"`
+ RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ return x.Sync(new(LFSMetaObject))
+}
diff --git a/models/migrations/v1_19/v239.go b/models/migrations/v1_19/v239.go
new file mode 100644
index 00000000..10076f24
--- /dev/null
+++ b/models/migrations/v1_19/v239.go
@@ -0,0 +1,22 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddScopeForAccessTokens(x *xorm.Engine) error {
+ type AccessToken struct {
+ Scope string
+ }
+
+ if err := x.Sync(new(AccessToken)); err != nil {
+ return err
+ }
+
+ // all previous tokens have `all` and `sudo` scopes
+ _, err := x.Exec("UPDATE access_token SET scope = ? WHERE scope IS NULL OR scope = ''", "all,sudo")
+ return err
+}
diff --git a/models/migrations/v1_19/v240.go b/models/migrations/v1_19/v240.go
new file mode 100644
index 00000000..4505f862
--- /dev/null
+++ b/models/migrations/v1_19/v240.go
@@ -0,0 +1,176 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddActionsTables(x *xorm.Engine) error {
+ type ActionRunner struct {
+ ID int64
+ UUID string `xorm:"CHAR(36) UNIQUE"`
+ Name string `xorm:"VARCHAR(255)"`
+ OwnerID int64 `xorm:"index"` // org level runner, 0 means system
+ RepoID int64 `xorm:"index"` // repo level runner, if orgid also is zero, then it's a global
+ Description string `xorm:"TEXT"`
+ Base int // 0 native 1 docker 2 virtual machine
+ RepoRange string // glob match which repositories could use this runner
+
+ Token string `xorm:"-"`
+ TokenHash string `xorm:"UNIQUE"` // sha256 of token
+ TokenSalt string
+ // TokenLastEight string `xorm:"token_last_eight"` // it's unnecessary because we don't find runners by token
+
+ LastOnline timeutil.TimeStamp `xorm:"index"`
+ LastActive timeutil.TimeStamp `xorm:"index"`
+
+ // Store OS and Artch.
+ AgentLabels []string
+ // Store custom labes use defined.
+ CustomLabels []string
+
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated"`
+ Deleted timeutil.TimeStamp `xorm:"deleted"`
+ }
+
+ type ActionRunnerToken struct {
+ ID int64
+ Token string `xorm:"UNIQUE"`
+ OwnerID int64 `xorm:"index"` // org level runner, 0 means system
+ RepoID int64 `xorm:"index"` // repo level runner, if orgid also is zero, then it's a global
+ IsActive bool
+
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated"`
+ Deleted timeutil.TimeStamp `xorm:"deleted"`
+ }
+
+ type ActionRun struct {
+ ID int64
+ Title string
+ RepoID int64 `xorm:"index unique(repo_index)"`
+ OwnerID int64 `xorm:"index"`
+ WorkflowID string `xorm:"index"` // the name of workflow file
+ Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository
+ TriggerUserID int64
+ Ref string
+ CommitSHA string
+ Event string
+ IsForkPullRequest bool
+ EventPayload string `xorm:"LONGTEXT"`
+ Status int `xorm:"index"`
+ Started timeutil.TimeStamp
+ Stopped timeutil.TimeStamp
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated"`
+ }
+
+ type ActionRunJob struct {
+ ID int64
+ RunID int64 `xorm:"index"`
+ RepoID int64 `xorm:"index"`
+ OwnerID int64 `xorm:"index"`
+ CommitSHA string `xorm:"index"`
+ IsForkPullRequest bool
+ Name string `xorm:"VARCHAR(255)"`
+ Attempt int64
+ WorkflowPayload []byte
+ JobID string `xorm:"VARCHAR(255)"` // job id in workflow, not job's id
+ Needs []string `xorm:"JSON TEXT"`
+ RunsOn []string `xorm:"JSON TEXT"`
+ TaskID int64 // the latest task of the job
+ Status int `xorm:"index"`
+ Started timeutil.TimeStamp
+ Stopped timeutil.TimeStamp
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated index"`
+ }
+
+ type Repository struct {
+ NumActionRuns int `xorm:"NOT NULL DEFAULT 0"`
+ NumClosedActionRuns int `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ type ActionRunIndex db.ResourceIndex
+
+ type ActionTask struct {
+ ID int64
+ JobID int64
+ Attempt int64
+ RunnerID int64 `xorm:"index"`
+ Status int `xorm:"index"`
+ Started timeutil.TimeStamp `xorm:"index"`
+ Stopped timeutil.TimeStamp
+
+ RepoID int64 `xorm:"index"`
+ OwnerID int64 `xorm:"index"`
+ CommitSHA string `xorm:"index"`
+ IsForkPullRequest bool
+
+ TokenHash string `xorm:"UNIQUE"` // sha256 of token
+ TokenSalt string
+ TokenLastEight string `xorm:"index token_last_eight"`
+
+ LogFilename string // file name of log
+ LogInStorage bool // read log from database or from storage
+ LogLength int64 // lines count
+ LogSize int64 // blob size
+ LogIndexes []int64 `xorm:"LONGBLOB"` // line number to offset
+ LogExpired bool // files that are too old will be deleted
+
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated index"`
+ }
+
+ type ActionTaskStep struct {
+ ID int64
+ Name string `xorm:"VARCHAR(255)"`
+ TaskID int64 `xorm:"index unique(task_index)"`
+ Index int64 `xorm:"index unique(task_index)"`
+ RepoID int64 `xorm:"index"`
+ Status int `xorm:"index"`
+ LogIndex int64
+ LogLength int64
+ Started timeutil.TimeStamp
+ Stopped timeutil.TimeStamp
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated"`
+ }
+
+ type dbfsMeta struct {
+ ID int64 `xorm:"pk autoincr"`
+ FullPath string `xorm:"VARCHAR(500) UNIQUE NOT NULL"`
+ BlockSize int64 `xorm:"BIGINT NOT NULL"`
+ FileSize int64 `xorm:"BIGINT NOT NULL"`
+ CreateTimestamp int64 `xorm:"BIGINT NOT NULL"`
+ ModifyTimestamp int64 `xorm:"BIGINT NOT NULL"`
+ }
+
+ type dbfsData struct {
+ ID int64 `xorm:"pk autoincr"`
+ Revision int64 `xorm:"BIGINT NOT NULL"`
+ MetaID int64 `xorm:"BIGINT index(meta_offset) NOT NULL"`
+ BlobOffset int64 `xorm:"BIGINT index(meta_offset) NOT NULL"`
+ BlobSize int64 `xorm:"BIGINT NOT NULL"`
+ BlobData []byte `xorm:"BLOB NOT NULL"`
+ }
+
+ return x.Sync(
+ new(ActionRunner),
+ new(ActionRunnerToken),
+ new(ActionRun),
+ new(ActionRunJob),
+ new(Repository),
+ new(ActionRunIndex),
+ new(ActionTask),
+ new(ActionTaskStep),
+ new(dbfsMeta),
+ new(dbfsData),
+ )
+}
diff --git a/models/migrations/v1_19/v241.go b/models/migrations/v1_19/v241.go
new file mode 100644
index 00000000..a617d6fd
--- /dev/null
+++ b/models/migrations/v1_19/v241.go
@@ -0,0 +1,17 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+// AddCardTypeToProjectTable: add CardType column, setting existing rows to CardTypeTextOnly
+func AddCardTypeToProjectTable(x *xorm.Engine) error {
+ type Project struct {
+ CardType int `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ return x.Sync(new(Project))
+}
diff --git a/models/migrations/v1_19/v242.go b/models/migrations/v1_19/v242.go
new file mode 100644
index 00000000..44708352
--- /dev/null
+++ b/models/migrations/v1_19/v242.go
@@ -0,0 +1,26 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+// AlterPublicGPGKeyImportContentFieldToMediumText: set GPGKeyImport Content field to MEDIUMTEXT
+func AlterPublicGPGKeyImportContentFieldToMediumText(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 `gpg_key_import` CHANGE `content` `content` MEDIUMTEXT"); err != nil {
+ return err
+ }
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_19/v243.go b/models/migrations/v1_19/v243.go
new file mode 100644
index 00000000..55bbfafb
--- /dev/null
+++ b/models/migrations/v1_19/v243.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddExclusiveLabel(x *xorm.Engine) error {
+ type Label struct {
+ Exclusive bool
+ }
+
+ return x.Sync(new(Label))
+}
diff --git a/models/migrations/v1_20/main_test.go b/models/migrations/v1_20/main_test.go
new file mode 100644
index 00000000..92a1a9f6
--- /dev/null
+++ b/models/migrations/v1_20/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+)
+
+func TestMain(m *testing.M) {
+ base.MainTest(m)
+}
diff --git a/models/migrations/v1_20/v244.go b/models/migrations/v1_20/v244.go
new file mode 100644
index 00000000..977566ad
--- /dev/null
+++ b/models/migrations/v1_20/v244.go
@@ -0,0 +1,22 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddNeedApprovalToActionRun(x *xorm.Engine) error {
+ /*
+ New index: TriggerUserID
+ New fields: NeedApproval, ApprovedBy
+ */
+ type ActionRun struct {
+ TriggerUserID int64 `xorm:"index"`
+ NeedApproval bool // may need approval if it's a fork pull request
+ ApprovedBy int64 `xorm:"index"` // who approved
+ }
+
+ return x.Sync(new(ActionRun))
+}
diff --git a/models/migrations/v1_20/v245.go b/models/migrations/v1_20/v245.go
new file mode 100644
index 00000000..b0d4c215
--- /dev/null
+++ b/models/migrations/v1_20/v245.go
@@ -0,0 +1,69 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func RenameWebhookOrgToOwner(x *xorm.Engine) error {
+ type Webhook struct {
+ OrgID int64 `xorm:"INDEX"`
+ }
+
+ // This migration maybe rerun so that we should check if it has been run
+ ownerExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webhook", "owner_id")
+ if err != nil {
+ return err
+ }
+
+ if ownerExist {
+ orgExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webhook", "org_id")
+ if err != nil {
+ return err
+ }
+ if !orgExist {
+ return nil
+ }
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if err := sess.Sync(new(Webhook)); err != nil {
+ return err
+ }
+
+ if ownerExist {
+ if err := base.DropTableColumns(sess, "webhook", "owner_id"); err != nil {
+ return err
+ }
+ }
+
+ if setting.Database.Type.IsMySQL() {
+ inferredTable, err := x.TableInfo(new(Webhook))
+ if err != nil {
+ return err
+ }
+ sqlType := x.Dialect().SQLType(inferredTable.GetColumn("org_id"))
+ if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `webhook` CHANGE org_id owner_id %s", sqlType)); err != nil {
+ return err
+ }
+ } else {
+ if _, err := sess.Exec("ALTER TABLE `webhook` RENAME COLUMN org_id TO owner_id"); err != nil {
+ return err
+ }
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_20/v246.go b/models/migrations/v1_20/v246.go
new file mode 100644
index 00000000..e6340ef0
--- /dev/null
+++ b/models/migrations/v1_20/v246.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddNewColumnForProject(x *xorm.Engine) error {
+ type Project struct {
+ OwnerID int64 `xorm:"INDEX"`
+ }
+
+ return x.Sync(new(Project))
+}
diff --git a/models/migrations/v1_20/v247.go b/models/migrations/v1_20/v247.go
new file mode 100644
index 00000000..59fc5c46
--- /dev/null
+++ b/models/migrations/v1_20/v247.go
@@ -0,0 +1,50 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/log"
+
+ "xorm.io/xorm"
+)
+
+// FixIncorrectProjectType: set individual project's type from 3(TypeOrganization) to 1(TypeIndividual)
+func FixIncorrectProjectType(x *xorm.Engine) error {
+ type User struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type int
+ }
+
+ const (
+ UserTypeIndividual int = 0
+
+ TypeIndividual uint8 = 1
+ TypeOrganization uint8 = 3
+ )
+
+ type Project struct {
+ OwnerID int64 `xorm:"INDEX"`
+ Type uint8
+ Owner *User `xorm:"extends"`
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ count, err := sess.Table("project").
+ Where("type = ? AND owner_id IN (SELECT id FROM `user` WHERE type = ?)", TypeOrganization, UserTypeIndividual).
+ Update(&Project{
+ Type: TypeIndividual,
+ })
+ if err != nil {
+ return err
+ }
+ log.Debug("Updated %d projects to belong to a user instead of an organization", count)
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_20/v248.go b/models/migrations/v1_20/v248.go
new file mode 100644
index 00000000..40555210
--- /dev/null
+++ b/models/migrations/v1_20/v248.go
@@ -0,0 +1,14 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import "xorm.io/xorm"
+
+func AddVersionToActionRunner(x *xorm.Engine) error {
+ type ActionRunner struct {
+ Version string `xorm:"VARCHAR(64)"` // the version of act_runner
+ }
+
+ return x.Sync(new(ActionRunner))
+}
diff --git a/models/migrations/v1_20/v249.go b/models/migrations/v1_20/v249.go
new file mode 100644
index 00000000..02951a74
--- /dev/null
+++ b/models/migrations/v1_20/v249.go
@@ -0,0 +1,45 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+type Action struct {
+ UserID int64 // Receiver user id.
+ ActUserID int64 // Action user id.
+ RepoID int64
+ IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
+ IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+}
+
+// TableName sets the name of this table
+func (a *Action) TableName() string {
+ return "action"
+}
+
+// TableIndices implements xorm's TableIndices interface
+func (a *Action) TableIndices() []*schemas.Index {
+ repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
+ repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
+
+ actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
+ actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
+
+ cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
+ cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
+
+ indices := []*schemas.Index{actUserIndex, repoIndex, cudIndex}
+
+ return indices
+}
+
+func ImproveActionTableIndices(x *xorm.Engine) error {
+ return x.Sync(new(Action))
+}
diff --git a/models/migrations/v1_20/v250.go b/models/migrations/v1_20/v250.go
new file mode 100644
index 00000000..86388ef0
--- /dev/null
+++ b/models/migrations/v1_20/v250.go
@@ -0,0 +1,135 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "strings"
+
+ "code.gitea.io/gitea/modules/json"
+
+ "xorm.io/xorm"
+)
+
+func ChangeContainerMetadataMultiArch(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ type PackageVersion struct {
+ ID int64 `xorm:"pk autoincr"`
+ MetadataJSON string `xorm:"metadata_json"`
+ }
+
+ type PackageBlob struct{}
+
+ // Get all relevant packages (manifest list images have a container.manifest.reference property)
+
+ var pvs []*PackageVersion
+ err := sess.
+ Table("package_version").
+ Select("id, metadata_json").
+ Where("id IN (SELECT DISTINCT ref_id FROM package_property WHERE ref_type = 0 AND name = 'container.manifest.reference')").
+ Find(&pvs)
+ if err != nil {
+ return err
+ }
+
+ type MetadataOld struct {
+ Type string `json:"type"`
+ IsTagged bool `json:"is_tagged"`
+ Platform string `json:"platform,omitempty"`
+ Description string `json:"description,omitempty"`
+ Authors []string `json:"authors,omitempty"`
+ Licenses string `json:"license,omitempty"`
+ ProjectURL string `json:"project_url,omitempty"`
+ RepositoryURL string `json:"repository_url,omitempty"`
+ DocumentationURL string `json:"documentation_url,omitempty"`
+ Labels map[string]string `json:"labels,omitempty"`
+ ImageLayers []string `json:"layer_creation,omitempty"`
+ MultiArch map[string]string `json:"multiarch,omitempty"`
+ }
+
+ type Manifest struct {
+ Platform string `json:"platform"`
+ Digest string `json:"digest"`
+ Size int64 `json:"size"`
+ }
+
+ type MetadataNew struct {
+ Type string `json:"type"`
+ IsTagged bool `json:"is_tagged"`
+ Platform string `json:"platform,omitempty"`
+ Description string `json:"description,omitempty"`
+ Authors []string `json:"authors,omitempty"`
+ Licenses string `json:"license,omitempty"`
+ ProjectURL string `json:"project_url,omitempty"`
+ RepositoryURL string `json:"repository_url,omitempty"`
+ DocumentationURL string `json:"documentation_url,omitempty"`
+ Labels map[string]string `json:"labels,omitempty"`
+ ImageLayers []string `json:"layer_creation,omitempty"`
+ Manifests []*Manifest `json:"manifests,omitempty"`
+ }
+
+ for _, pv := range pvs {
+ var old *MetadataOld
+ if err := json.Unmarshal([]byte(pv.MetadataJSON), &old); err != nil {
+ return err
+ }
+
+ // Calculate the size of every contained manifest
+
+ manifests := make([]*Manifest, 0, len(old.MultiArch))
+ for platform, digest := range old.MultiArch {
+ size, err := sess.
+ Table("package_blob").
+ Join("INNER", "package_file", "package_blob.id = package_file.blob_id").
+ Join("INNER", "package_version pv", "pv.id = package_file.version_id").
+ Join("INNER", "package_version pv2", "pv2.package_id = pv.package_id").
+ Where("pv.lower_version = ? AND pv2.id = ?", strings.ToLower(digest), pv.ID).
+ SumInt(new(PackageBlob), "size")
+ if err != nil {
+ return err
+ }
+
+ manifests = append(manifests, &Manifest{
+ Platform: platform,
+ Digest: digest,
+ Size: size,
+ })
+ }
+
+ // Convert to new metadata format
+
+ newMetadata := &MetadataNew{
+ Type: old.Type,
+ IsTagged: old.IsTagged,
+ Platform: old.Platform,
+ Description: old.Description,
+ Authors: old.Authors,
+ Licenses: old.Licenses,
+ ProjectURL: old.ProjectURL,
+ RepositoryURL: old.RepositoryURL,
+ DocumentationURL: old.DocumentationURL,
+ Labels: old.Labels,
+ ImageLayers: old.ImageLayers,
+ Manifests: manifests,
+ }
+
+ metadataJSON, err := json.Marshal(newMetadata)
+ if err != nil {
+ return err
+ }
+
+ pv.MetadataJSON = string(metadataJSON)
+
+ if _, err := sess.ID(pv.ID).Update(pv); err != nil {
+ return err
+ }
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_20/v251.go b/models/migrations/v1_20/v251.go
new file mode 100644
index 00000000..7743248a
--- /dev/null
+++ b/models/migrations/v1_20/v251.go
@@ -0,0 +1,47 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/log"
+
+ "xorm.io/xorm"
+)
+
+func FixIncorrectOwnerTeamUnitAccessMode(x *xorm.Engine) error {
+ type UnitType int
+ type AccessMode int
+
+ type TeamUnit struct {
+ ID int64 `xorm:"pk autoincr"`
+ OrgID int64 `xorm:"INDEX"`
+ TeamID int64 `xorm:"UNIQUE(s)"`
+ Type UnitType `xorm:"UNIQUE(s)"`
+ AccessMode AccessMode
+ }
+
+ const (
+ // AccessModeOwner owner access
+ AccessModeOwner = 4
+ )
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ count, err := sess.Table("team_unit").
+ Where("team_id IN (SELECT id FROM team WHERE authorize = ?)", AccessModeOwner).
+ Update(&TeamUnit{
+ AccessMode: AccessModeOwner,
+ })
+ if err != nil {
+ return err
+ }
+ log.Debug("Updated %d owner team unit access mode to belong to owner instead of none", count)
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_20/v252.go b/models/migrations/v1_20/v252.go
new file mode 100644
index 00000000..ab61cd9b
--- /dev/null
+++ b/models/migrations/v1_20/v252.go
@@ -0,0 +1,47 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/log"
+
+ "xorm.io/xorm"
+)
+
+func FixIncorrectAdminTeamUnitAccessMode(x *xorm.Engine) error {
+ type UnitType int
+ type AccessMode int
+
+ type TeamUnit struct {
+ ID int64 `xorm:"pk autoincr"`
+ OrgID int64 `xorm:"INDEX"`
+ TeamID int64 `xorm:"UNIQUE(s)"`
+ Type UnitType `xorm:"UNIQUE(s)"`
+ AccessMode AccessMode
+ }
+
+ const (
+ // AccessModeAdmin admin access
+ AccessModeAdmin = 3
+ )
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ count, err := sess.Table("team_unit").
+ Where("team_id IN (SELECT id FROM team WHERE authorize = ?)", AccessModeAdmin).
+ Update(&TeamUnit{
+ AccessMode: AccessModeAdmin,
+ })
+ if err != nil {
+ return err
+ }
+ log.Debug("Updated %d admin team unit access mode to belong to admin instead of none", count)
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_20/v253.go b/models/migrations/v1_20/v253.go
new file mode 100644
index 00000000..96c494bd
--- /dev/null
+++ b/models/migrations/v1_20/v253.go
@@ -0,0 +1,49 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/log"
+
+ "xorm.io/xorm"
+)
+
+func FixExternalTrackerAndExternalWikiAccessModeInOwnerAndAdminTeam(x *xorm.Engine) error {
+ type UnitType int
+ type AccessMode int
+
+ type TeamUnit struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type UnitType `xorm:"UNIQUE(s)"`
+ AccessMode AccessMode
+ }
+
+ const (
+ // AccessModeRead read access
+ AccessModeRead = 1
+
+ // Unit Type
+ TypeExternalWiki = 6
+ TypeExternalTracker = 7
+ )
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ count, err := sess.Table("team_unit").
+ Where("type IN (?, ?) AND access_mode > ?", TypeExternalWiki, TypeExternalTracker, AccessModeRead).
+ Update(&TeamUnit{
+ AccessMode: AccessModeRead,
+ })
+ if err != nil {
+ return err
+ }
+ log.Debug("Updated %d ExternalTracker and ExternalWiki access mode to belong to owner and admin", count)
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_20/v254.go b/models/migrations/v1_20/v254.go
new file mode 100644
index 00000000..1e26979a
--- /dev/null
+++ b/models/migrations/v1_20/v254.go
@@ -0,0 +1,18 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddActionTaskOutputTable(x *xorm.Engine) error {
+ type ActionTaskOutput struct {
+ ID int64
+ TaskID int64 `xorm:"INDEX UNIQUE(task_id_output_key)"`
+ OutputKey string `xorm:"VARCHAR(255) UNIQUE(task_id_output_key)"`
+ OutputValue string `xorm:"MEDIUMTEXT"`
+ }
+ return x.Sync(new(ActionTaskOutput))
+}
diff --git a/models/migrations/v1_20/v255.go b/models/migrations/v1_20/v255.go
new file mode 100644
index 00000000..14b70f8f
--- /dev/null
+++ b/models/migrations/v1_20/v255.go
@@ -0,0 +1,23 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddArchivedUnixToRepository(x *xorm.Engine) error {
+ type Repository struct {
+ ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT 0"`
+ }
+
+ if err := x.Sync(new(Repository)); err != nil {
+ return err
+ }
+
+ _, err := x.Exec("UPDATE repository SET archived_unix = updated_unix WHERE is_archived = ? AND archived_unix = 0", true)
+ return err
+}
diff --git a/models/migrations/v1_20/v256.go b/models/migrations/v1_20/v256.go
new file mode 100644
index 00000000..822153b9
--- /dev/null
+++ b/models/migrations/v1_20/v256.go
@@ -0,0 +1,23 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddIsInternalColumnToPackage(x *xorm.Engine) error {
+ type Package struct {
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ RepoID int64 `xorm:"INDEX"`
+ Type string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ Name string `xorm:"NOT NULL"`
+ LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ SemverCompatible bool `xorm:"NOT NULL DEFAULT false"`
+ IsInternal bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(Package))
+}
diff --git a/models/migrations/v1_20/v257.go b/models/migrations/v1_20/v257.go
new file mode 100644
index 00000000..6c6ca4c7
--- /dev/null
+++ b/models/migrations/v1_20/v257.go
@@ -0,0 +1,33 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func CreateActionArtifactTable(x *xorm.Engine) error {
+ // ActionArtifact is a file that is stored in the artifact storage.
+ type ActionArtifact struct {
+ ID int64 `xorm:"pk autoincr"`
+ RunID int64 `xorm:"index UNIQUE(runid_name)"` // The run id of the artifact
+ RunnerID int64
+ RepoID int64 `xorm:"index"`
+ OwnerID int64
+ CommitSHA string
+ StoragePath string // The path to the artifact in the storage
+ FileSize int64 // The size of the artifact in bytes
+ FileCompressedSize int64 // The size of the artifact in bytes after gzip compression
+ ContentEncoding string // The content encoding of the artifact
+ ArtifactPath string // The path to the artifact when runner uploads it
+ ArtifactName string `xorm:"UNIQUE(runid_name)"` // The name of the artifact when runner uploads it
+ Status int64 `xorm:"index"` // The status of the artifact
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"updated index"`
+ }
+
+ return x.Sync(new(ActionArtifact))
+}
diff --git a/models/migrations/v1_20/v258.go b/models/migrations/v1_20/v258.go
new file mode 100644
index 00000000..47174ce8
--- /dev/null
+++ b/models/migrations/v1_20/v258.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddPinOrderToIssue(x *xorm.Engine) error {
+ type Issue struct {
+ PinOrder int `xorm:"DEFAULT 0"`
+ }
+
+ return x.Sync(new(Issue))
+}
diff --git a/models/migrations/v1_20/v259.go b/models/migrations/v1_20/v259.go
new file mode 100644
index 00000000..5b8ced4a
--- /dev/null
+++ b/models/migrations/v1_20/v259.go
@@ -0,0 +1,360 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "fmt"
+ "strings"
+
+ "code.gitea.io/gitea/modules/log"
+
+ "xorm.io/xorm"
+)
+
+// unknownAccessTokenScope represents the scope for an access token that isn't
+// known be an old token or a new token.
+type unknownAccessTokenScope string
+
+// AccessTokenScope represents the scope for an access token.
+type AccessTokenScope string
+
+// for all categories, write implies read
+const (
+ AccessTokenScopeAll AccessTokenScope = "all"
+ AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos
+
+ AccessTokenScopeReadActivityPub AccessTokenScope = "read:activitypub"
+ AccessTokenScopeWriteActivityPub AccessTokenScope = "write:activitypub"
+
+ AccessTokenScopeReadAdmin AccessTokenScope = "read:admin"
+ AccessTokenScopeWriteAdmin AccessTokenScope = "write:admin"
+
+ AccessTokenScopeReadMisc AccessTokenScope = "read:misc"
+ AccessTokenScopeWriteMisc AccessTokenScope = "write:misc"
+
+ AccessTokenScopeReadNotification AccessTokenScope = "read:notification"
+ AccessTokenScopeWriteNotification AccessTokenScope = "write:notification"
+
+ AccessTokenScopeReadOrganization AccessTokenScope = "read:organization"
+ AccessTokenScopeWriteOrganization AccessTokenScope = "write:organization"
+
+ AccessTokenScopeReadPackage AccessTokenScope = "read:package"
+ AccessTokenScopeWritePackage AccessTokenScope = "write:package"
+
+ AccessTokenScopeReadIssue AccessTokenScope = "read:issue"
+ AccessTokenScopeWriteIssue AccessTokenScope = "write:issue"
+
+ AccessTokenScopeReadRepository AccessTokenScope = "read:repository"
+ AccessTokenScopeWriteRepository AccessTokenScope = "write:repository"
+
+ AccessTokenScopeReadUser AccessTokenScope = "read:user"
+ AccessTokenScopeWriteUser AccessTokenScope = "write:user"
+)
+
+// accessTokenScopeBitmap represents a bitmap of access token scopes.
+type accessTokenScopeBitmap uint64
+
+// Bitmap of each scope, including the child scopes.
+const (
+ // AccessTokenScopeAllBits is the bitmap of all access token scopes
+ accessTokenScopeAllBits accessTokenScopeBitmap = accessTokenScopeWriteActivityPubBits |
+ accessTokenScopeWriteAdminBits | accessTokenScopeWriteMiscBits | accessTokenScopeWriteNotificationBits |
+ accessTokenScopeWriteOrganizationBits | accessTokenScopeWritePackageBits | accessTokenScopeWriteIssueBits |
+ accessTokenScopeWriteRepositoryBits | accessTokenScopeWriteUserBits
+
+ accessTokenScopePublicOnlyBits accessTokenScopeBitmap = 1 << iota
+
+ accessTokenScopeReadActivityPubBits accessTokenScopeBitmap = 1 << iota
+ accessTokenScopeWriteActivityPubBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadActivityPubBits
+
+ accessTokenScopeReadAdminBits accessTokenScopeBitmap = 1 << iota
+ accessTokenScopeWriteAdminBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadAdminBits
+
+ accessTokenScopeReadMiscBits accessTokenScopeBitmap = 1 << iota
+ accessTokenScopeWriteMiscBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadMiscBits
+
+ accessTokenScopeReadNotificationBits accessTokenScopeBitmap = 1 << iota
+ accessTokenScopeWriteNotificationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadNotificationBits
+
+ accessTokenScopeReadOrganizationBits accessTokenScopeBitmap = 1 << iota
+ accessTokenScopeWriteOrganizationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadOrganizationBits
+
+ accessTokenScopeReadPackageBits accessTokenScopeBitmap = 1 << iota
+ accessTokenScopeWritePackageBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadPackageBits
+
+ accessTokenScopeReadIssueBits accessTokenScopeBitmap = 1 << iota
+ accessTokenScopeWriteIssueBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadIssueBits
+
+ accessTokenScopeReadRepositoryBits accessTokenScopeBitmap = 1 << iota
+ accessTokenScopeWriteRepositoryBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadRepositoryBits
+
+ accessTokenScopeReadUserBits accessTokenScopeBitmap = 1 << iota
+ accessTokenScopeWriteUserBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadUserBits
+
+ // The current implementation only supports up to 64 token scopes.
+ // If we need to support > 64 scopes,
+ // refactoring the whole implementation in this file (and only this file) is needed.
+)
+
+// allAccessTokenScopes contains all access token scopes.
+// The order is important: parent scope must precede child scopes.
+var allAccessTokenScopes = []AccessTokenScope{
+ AccessTokenScopePublicOnly,
+ AccessTokenScopeWriteActivityPub, AccessTokenScopeReadActivityPub,
+ AccessTokenScopeWriteAdmin, AccessTokenScopeReadAdmin,
+ AccessTokenScopeWriteMisc, AccessTokenScopeReadMisc,
+ AccessTokenScopeWriteNotification, AccessTokenScopeReadNotification,
+ AccessTokenScopeWriteOrganization, AccessTokenScopeReadOrganization,
+ AccessTokenScopeWritePackage, AccessTokenScopeReadPackage,
+ AccessTokenScopeWriteIssue, AccessTokenScopeReadIssue,
+ AccessTokenScopeWriteRepository, AccessTokenScopeReadRepository,
+ AccessTokenScopeWriteUser, AccessTokenScopeReadUser,
+}
+
+// allAccessTokenScopeBits contains all access token scopes.
+var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{
+ AccessTokenScopeAll: accessTokenScopeAllBits,
+ AccessTokenScopePublicOnly: accessTokenScopePublicOnlyBits,
+ AccessTokenScopeReadActivityPub: accessTokenScopeReadActivityPubBits,
+ AccessTokenScopeWriteActivityPub: accessTokenScopeWriteActivityPubBits,
+ AccessTokenScopeReadAdmin: accessTokenScopeReadAdminBits,
+ AccessTokenScopeWriteAdmin: accessTokenScopeWriteAdminBits,
+ AccessTokenScopeReadMisc: accessTokenScopeReadMiscBits,
+ AccessTokenScopeWriteMisc: accessTokenScopeWriteMiscBits,
+ AccessTokenScopeReadNotification: accessTokenScopeReadNotificationBits,
+ AccessTokenScopeWriteNotification: accessTokenScopeWriteNotificationBits,
+ AccessTokenScopeReadOrganization: accessTokenScopeReadOrganizationBits,
+ AccessTokenScopeWriteOrganization: accessTokenScopeWriteOrganizationBits,
+ AccessTokenScopeReadPackage: accessTokenScopeReadPackageBits,
+ AccessTokenScopeWritePackage: accessTokenScopeWritePackageBits,
+ AccessTokenScopeReadIssue: accessTokenScopeReadIssueBits,
+ AccessTokenScopeWriteIssue: accessTokenScopeWriteIssueBits,
+ AccessTokenScopeReadRepository: accessTokenScopeReadRepositoryBits,
+ AccessTokenScopeWriteRepository: accessTokenScopeWriteRepositoryBits,
+ AccessTokenScopeReadUser: accessTokenScopeReadUserBits,
+ AccessTokenScopeWriteUser: accessTokenScopeWriteUserBits,
+}
+
+// hasScope returns true if the string has the given scope
+func (bitmap accessTokenScopeBitmap) hasScope(scope AccessTokenScope) (bool, error) {
+ expectedBits, ok := allAccessTokenScopeBits[scope]
+ if !ok {
+ return false, fmt.Errorf("invalid access token scope: %s", scope)
+ }
+
+ return bitmap&expectedBits == expectedBits, nil
+}
+
+// toScope returns a normalized scope string without any duplicates.
+func (bitmap accessTokenScopeBitmap) toScope(unknownScopes *[]unknownAccessTokenScope) AccessTokenScope {
+ var scopes []string
+
+ // Preserve unknown scopes, and put them at the beginning so that it's clear
+ // when debugging.
+ if unknownScopes != nil {
+ for _, unknownScope := range *unknownScopes {
+ scopes = append(scopes, string(unknownScope))
+ }
+ }
+
+ // iterate over all scopes, and reconstruct the bitmap
+ // if the reconstructed bitmap doesn't change, then the scope is already included
+ var reconstruct accessTokenScopeBitmap
+
+ for _, singleScope := range allAccessTokenScopes {
+ // no need for error checking here, since we know the scope is valid
+ if ok, _ := bitmap.hasScope(singleScope); ok {
+ current := reconstruct | allAccessTokenScopeBits[singleScope]
+ if current == reconstruct {
+ continue
+ }
+
+ reconstruct = current
+ scopes = append(scopes, string(singleScope))
+ }
+ }
+
+ scope := AccessTokenScope(strings.Join(scopes, ","))
+ scope = AccessTokenScope(strings.ReplaceAll(
+ string(scope),
+ "write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user",
+ "all",
+ ))
+ return scope
+}
+
+// parse the scope string into a bitmap, thus removing possible duplicates.
+func (s AccessTokenScope) parse() (accessTokenScopeBitmap, *[]unknownAccessTokenScope) {
+ var bitmap accessTokenScopeBitmap
+ var unknownScopes []unknownAccessTokenScope
+
+ // The following is the more performant equivalent of 'for _, v := range strings.Split(remainingScope, ",")' as this is hot code
+ remainingScopes := string(s)
+ for len(remainingScopes) > 0 {
+ i := strings.IndexByte(remainingScopes, ',')
+ var v string
+ if i < 0 {
+ v = remainingScopes
+ remainingScopes = ""
+ } else if i+1 >= len(remainingScopes) {
+ v = remainingScopes[:i]
+ remainingScopes = ""
+ } else {
+ v = remainingScopes[:i]
+ remainingScopes = remainingScopes[i+1:]
+ }
+ singleScope := AccessTokenScope(v)
+ if singleScope == "" {
+ continue
+ }
+ if singleScope == AccessTokenScopeAll {
+ bitmap |= accessTokenScopeAllBits
+ continue
+ }
+
+ bits, ok := allAccessTokenScopeBits[singleScope]
+ if !ok {
+ unknownScopes = append(unknownScopes, unknownAccessTokenScope(string(singleScope)))
+ }
+ bitmap |= bits
+ }
+
+ return bitmap, &unknownScopes
+}
+
+// NormalizePreservingUnknown returns a normalized scope string without any
+// duplicates. Unknown scopes are included.
+func (s AccessTokenScope) NormalizePreservingUnknown() AccessTokenScope {
+ bitmap, unknownScopes := s.parse()
+
+ return bitmap.toScope(unknownScopes)
+}
+
+// OldAccessTokenScope represents the scope for an access token.
+type OldAccessTokenScope string
+
+const (
+ OldAccessTokenScopeAll OldAccessTokenScope = "all"
+
+ OldAccessTokenScopeRepo OldAccessTokenScope = "repo"
+ OldAccessTokenScopeRepoStatus OldAccessTokenScope = "repo:status"
+ OldAccessTokenScopePublicRepo OldAccessTokenScope = "public_repo"
+
+ OldAccessTokenScopeAdminOrg OldAccessTokenScope = "admin:org"
+ OldAccessTokenScopeWriteOrg OldAccessTokenScope = "write:org"
+ OldAccessTokenScopeReadOrg OldAccessTokenScope = "read:org"
+
+ OldAccessTokenScopeAdminPublicKey OldAccessTokenScope = "admin:public_key"
+ OldAccessTokenScopeWritePublicKey OldAccessTokenScope = "write:public_key"
+ OldAccessTokenScopeReadPublicKey OldAccessTokenScope = "read:public_key"
+
+ OldAccessTokenScopeAdminRepoHook OldAccessTokenScope = "admin:repo_hook"
+ OldAccessTokenScopeWriteRepoHook OldAccessTokenScope = "write:repo_hook"
+ OldAccessTokenScopeReadRepoHook OldAccessTokenScope = "read:repo_hook"
+
+ OldAccessTokenScopeAdminOrgHook OldAccessTokenScope = "admin:org_hook"
+
+ OldAccessTokenScopeNotification OldAccessTokenScope = "notification"
+
+ OldAccessTokenScopeUser OldAccessTokenScope = "user"
+ OldAccessTokenScopeReadUser OldAccessTokenScope = "read:user"
+ OldAccessTokenScopeUserEmail OldAccessTokenScope = "user:email"
+ OldAccessTokenScopeUserFollow OldAccessTokenScope = "user:follow"
+
+ OldAccessTokenScopeDeleteRepo OldAccessTokenScope = "delete_repo"
+
+ OldAccessTokenScopePackage OldAccessTokenScope = "package"
+ OldAccessTokenScopeWritePackage OldAccessTokenScope = "write:package"
+ OldAccessTokenScopeReadPackage OldAccessTokenScope = "read:package"
+ OldAccessTokenScopeDeletePackage OldAccessTokenScope = "delete:package"
+
+ OldAccessTokenScopeAdminGPGKey OldAccessTokenScope = "admin:gpg_key"
+ OldAccessTokenScopeWriteGPGKey OldAccessTokenScope = "write:gpg_key"
+ OldAccessTokenScopeReadGPGKey OldAccessTokenScope = "read:gpg_key"
+
+ OldAccessTokenScopeAdminApplication OldAccessTokenScope = "admin:application"
+ OldAccessTokenScopeWriteApplication OldAccessTokenScope = "write:application"
+ OldAccessTokenScopeReadApplication OldAccessTokenScope = "read:application"
+
+ OldAccessTokenScopeSudo OldAccessTokenScope = "sudo"
+)
+
+var accessTokenScopeMap = map[OldAccessTokenScope][]AccessTokenScope{
+ OldAccessTokenScopeAll: {AccessTokenScopeAll},
+ OldAccessTokenScopeRepo: {AccessTokenScopeWriteRepository},
+ OldAccessTokenScopeRepoStatus: {AccessTokenScopeWriteRepository},
+ OldAccessTokenScopePublicRepo: {AccessTokenScopePublicOnly, AccessTokenScopeWriteRepository},
+ OldAccessTokenScopeAdminOrg: {AccessTokenScopeWriteOrganization},
+ OldAccessTokenScopeWriteOrg: {AccessTokenScopeWriteOrganization},
+ OldAccessTokenScopeReadOrg: {AccessTokenScopeReadOrganization},
+ OldAccessTokenScopeAdminPublicKey: {AccessTokenScopeWriteUser},
+ OldAccessTokenScopeWritePublicKey: {AccessTokenScopeWriteUser},
+ OldAccessTokenScopeReadPublicKey: {AccessTokenScopeReadUser},
+ OldAccessTokenScopeAdminRepoHook: {AccessTokenScopeWriteRepository},
+ OldAccessTokenScopeWriteRepoHook: {AccessTokenScopeWriteRepository},
+ OldAccessTokenScopeReadRepoHook: {AccessTokenScopeReadRepository},
+ OldAccessTokenScopeAdminOrgHook: {AccessTokenScopeWriteOrganization},
+ OldAccessTokenScopeNotification: {AccessTokenScopeWriteNotification},
+ OldAccessTokenScopeUser: {AccessTokenScopeWriteUser},
+ OldAccessTokenScopeReadUser: {AccessTokenScopeReadUser},
+ OldAccessTokenScopeUserEmail: {AccessTokenScopeWriteUser},
+ OldAccessTokenScopeUserFollow: {AccessTokenScopeWriteUser},
+ OldAccessTokenScopeDeleteRepo: {AccessTokenScopeWriteRepository},
+ OldAccessTokenScopePackage: {AccessTokenScopeWritePackage},
+ OldAccessTokenScopeWritePackage: {AccessTokenScopeWritePackage},
+ OldAccessTokenScopeReadPackage: {AccessTokenScopeReadPackage},
+ OldAccessTokenScopeDeletePackage: {AccessTokenScopeWritePackage},
+ OldAccessTokenScopeAdminGPGKey: {AccessTokenScopeWriteUser},
+ OldAccessTokenScopeWriteGPGKey: {AccessTokenScopeWriteUser},
+ OldAccessTokenScopeReadGPGKey: {AccessTokenScopeReadUser},
+ OldAccessTokenScopeAdminApplication: {AccessTokenScopeWriteUser},
+ OldAccessTokenScopeWriteApplication: {AccessTokenScopeWriteUser},
+ OldAccessTokenScopeReadApplication: {AccessTokenScopeReadUser},
+ OldAccessTokenScopeSudo: {AccessTokenScopeWriteAdmin},
+}
+
+type AccessToken struct {
+ ID int64 `xorm:"pk autoincr"`
+ Scope string
+}
+
+func ConvertScopedAccessTokens(x *xorm.Engine) error {
+ var tokens []*AccessToken
+
+ if err := x.Find(&tokens); err != nil {
+ return err
+ }
+
+ for _, token := range tokens {
+ var scopes []string
+ allNewScopesMap := make(map[AccessTokenScope]bool)
+ for _, oldScope := range strings.Split(token.Scope, ",") {
+ if newScopes, exists := accessTokenScopeMap[OldAccessTokenScope(oldScope)]; exists {
+ for _, newScope := range newScopes {
+ allNewScopesMap[newScope] = true
+ }
+ } else {
+ log.Debug("access token scope not recognized as old token scope %s; preserving it", oldScope)
+ scopes = append(scopes, oldScope)
+ }
+ }
+
+ for s := range allNewScopesMap {
+ scopes = append(scopes, string(s))
+ }
+ scope := AccessTokenScope(strings.Join(scopes, ","))
+
+ // normalize the scope
+ normScope := scope.NormalizePreservingUnknown()
+
+ token.Scope = string(normScope)
+
+ // update the db entry with the new scope
+ if _, err := x.Cols("scope").ID(token.ID).Update(token); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/models/migrations/v1_20/v259_test.go b/models/migrations/v1_20/v259_test.go
new file mode 100644
index 00000000..45c8eef0
--- /dev/null
+++ b/models/migrations/v1_20/v259_test.go
@@ -0,0 +1,111 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+ "sort"
+ "strings"
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+type testCase struct {
+ Old OldAccessTokenScope
+ New AccessTokenScope
+}
+
+func createOldTokenScope(scopes ...OldAccessTokenScope) OldAccessTokenScope {
+ s := make([]string, 0, len(scopes))
+ for _, os := range scopes {
+ s = append(s, string(os))
+ }
+ return OldAccessTokenScope(strings.Join(s, ","))
+}
+
+func createNewTokenScope(scopes ...AccessTokenScope) AccessTokenScope {
+ s := make([]string, 0, len(scopes))
+ for _, os := range scopes {
+ s = append(s, string(os))
+ }
+ return AccessTokenScope(strings.Join(s, ","))
+}
+
+func Test_ConvertScopedAccessTokens(t *testing.T) {
+ tests := []testCase{
+ {
+ createOldTokenScope(OldAccessTokenScopeRepo, OldAccessTokenScopeUserFollow),
+ createNewTokenScope(AccessTokenScopeWriteRepository, AccessTokenScopeWriteUser),
+ },
+ {
+ createOldTokenScope(OldAccessTokenScopeUser, OldAccessTokenScopeWritePackage, OldAccessTokenScopeSudo),
+ createNewTokenScope(AccessTokenScopeWriteAdmin, AccessTokenScopeWritePackage, AccessTokenScopeWriteUser),
+ },
+ {
+ createOldTokenScope(),
+ createNewTokenScope(),
+ },
+ {
+ createOldTokenScope(OldAccessTokenScopeReadGPGKey, OldAccessTokenScopeReadOrg, OldAccessTokenScopeAll),
+ createNewTokenScope(AccessTokenScopeAll),
+ },
+ {
+ createOldTokenScope(OldAccessTokenScopeReadGPGKey, "invalid"),
+ createNewTokenScope("invalid", AccessTokenScopeReadUser),
+ },
+ }
+
+ // add a test for each individual mapping
+ for oldScope, newScope := range accessTokenScopeMap {
+ tests = append(tests, testCase{
+ oldScope,
+ createNewTokenScope(newScope...),
+ })
+ }
+
+ x, deferable := base.PrepareTestEnv(t, 0, new(AccessToken))
+ defer deferable()
+ if x == nil || t.Failed() {
+ t.Skip()
+ return
+ }
+
+ // verify that no fixtures were loaded
+ count, err := x.Count(&AccessToken{})
+ require.NoError(t, err)
+ assert.Equal(t, int64(0), count)
+
+ for _, tc := range tests {
+ _, err = x.Insert(&AccessToken{
+ Scope: string(tc.Old),
+ })
+ require.NoError(t, err)
+ }
+
+ // migrate the scopes
+ err = ConvertScopedAccessTokens(x)
+ require.NoError(t, err)
+
+ // migrate the scopes again (migration should be idempotent)
+ err = ConvertScopedAccessTokens(x)
+ require.NoError(t, err)
+
+ tokens := make([]AccessToken, 0)
+ err = x.Find(&tokens)
+ require.NoError(t, err)
+ assert.Equal(t, len(tests), len(tokens))
+
+ // sort the tokens (insertion order by auto-incrementing primary key)
+ sort.Slice(tokens, func(i, j int) bool {
+ return tokens[i].ID < tokens[j].ID
+ })
+
+ // verify that the converted scopes are equal to the expected test result
+ for idx, newToken := range tokens {
+ assert.Equal(t, string(tests[idx].New), newToken.Scope)
+ }
+}
diff --git a/models/migrations/v1_21/main_test.go b/models/migrations/v1_21/main_test.go
new file mode 100644
index 00000000..9afdea16
--- /dev/null
+++ b/models/migrations/v1_21/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+)
+
+func TestMain(m *testing.M) {
+ base.MainTest(m)
+}
diff --git a/models/migrations/v1_21/v260.go b/models/migrations/v1_21/v260.go
new file mode 100644
index 00000000..6ca52c59
--- /dev/null
+++ b/models/migrations/v1_21/v260.go
@@ -0,0 +1,26 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "xorm.io/xorm"
+)
+
+func DropCustomLabelsColumnOfActionRunner(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ // drop "custom_labels" cols
+ if err := base.DropTableColumns(sess, "action_runner", "custom_labels"); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_21/v261.go b/models/migrations/v1_21/v261.go
new file mode 100644
index 00000000..4ec1160d
--- /dev/null
+++ b/models/migrations/v1_21/v261.go
@@ -0,0 +1,24 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func CreateVariableTable(x *xorm.Engine) error {
+ type ActionVariable struct {
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"UNIQUE(owner_repo_name)"`
+ RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name)"`
+ Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
+ Data string `xorm:"LONGTEXT NOT NULL"`
+ CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
+ }
+
+ return x.Sync(new(ActionVariable))
+}
diff --git a/models/migrations/v1_21/v262.go b/models/migrations/v1_21/v262.go
new file mode 100644
index 00000000..23e90057
--- /dev/null
+++ b/models/migrations/v1_21/v262.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddTriggerEventToActionRun(x *xorm.Engine) error {
+ type ActionRun struct {
+ TriggerEvent string
+ }
+
+ return x.Sync(new(ActionRun))
+}
diff --git a/models/migrations/v1_21/v263.go b/models/migrations/v1_21/v263.go
new file mode 100644
index 00000000..2c7cbadf
--- /dev/null
+++ b/models/migrations/v1_21/v263.go
@@ -0,0 +1,46 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+// AddGitSizeAndLFSSizeToRepositoryTable: add GitSize and LFSSize columns to Repository
+func AddGitSizeAndLFSSizeToRepositoryTable(x *xorm.Engine) error {
+ type Repository struct {
+ GitSize int64 `xorm:"NOT NULL DEFAULT 0"`
+ LFSSize int64 `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if err := sess.Sync(new(Repository)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+
+ _, err := sess.Exec(`UPDATE repository SET lfs_size=(SELECT SUM(size) FROM lfs_meta_object WHERE lfs_meta_object.repository_id=repository.ID) WHERE EXISTS (SELECT 1 FROM lfs_meta_object WHERE lfs_meta_object.repository_id=repository.ID)`)
+ if err != nil {
+ return err
+ }
+
+ _, err = sess.Exec(`UPDATE repository SET size = 0 WHERE size IS NULL`)
+ if err != nil {
+ return err
+ }
+
+ _, err = sess.Exec(`UPDATE repository SET git_size = size - lfs_size WHERE size > lfs_size`)
+ if err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_21/v264.go b/models/migrations/v1_21/v264.go
new file mode 100644
index 00000000..e81a17ad
--- /dev/null
+++ b/models/migrations/v1_21/v264.go
@@ -0,0 +1,93 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddBranchTable(x *xorm.Engine) error {
+ type Branch struct {
+ ID int64
+ RepoID int64 `xorm:"UNIQUE(s)"`
+ Name string `xorm:"UNIQUE(s) NOT NULL"`
+ CommitID string
+ CommitMessage string `xorm:"TEXT"`
+ PusherID int64
+ IsDeleted bool `xorm:"index"`
+ DeletedByID int64
+ DeletedUnix timeutil.TimeStamp `xorm:"index"`
+ CommitTime timeutil.TimeStamp // The commit
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
+ }
+
+ if err := x.Sync(new(Branch)); err != nil {
+ return err
+ }
+
+ if exist, err := x.IsTableExist("deleted_branches"); err != nil {
+ return err
+ } else if !exist {
+ return nil
+ }
+
+ type DeletedBranch struct {
+ ID int64
+ RepoID int64 `xorm:"index UNIQUE(s)"`
+ Name string `xorm:"UNIQUE(s) NOT NULL"`
+ Commit string
+ DeletedByID int64
+ DeletedUnix timeutil.TimeStamp
+ }
+
+ var adminUserID int64
+ has, err := x.Table("user").
+ Select("id").
+ Where("is_admin=?", true).
+ Asc("id"). // Reliably get the admin with the lowest ID.
+ Get(&adminUserID)
+ if err != nil {
+ return err
+ } else if !has {
+ return fmt.Errorf("no admin user found")
+ }
+
+ branches := make([]Branch, 0, 100)
+ if err := db.Iterate(context.Background(), nil, func(ctx context.Context, deletedBranch *DeletedBranch) error {
+ branches = append(branches, Branch{
+ RepoID: deletedBranch.RepoID,
+ Name: deletedBranch.Name,
+ CommitID: deletedBranch.Commit,
+ PusherID: adminUserID,
+ IsDeleted: true,
+ DeletedByID: deletedBranch.DeletedByID,
+ DeletedUnix: deletedBranch.DeletedUnix,
+ })
+ if len(branches) >= 100 {
+ _, err := x.Insert(&branches)
+ if err != nil {
+ return err
+ }
+ branches = branches[:0]
+ }
+ return nil
+ }); err != nil {
+ return err
+ }
+
+ if len(branches) > 0 {
+ if _, err := x.Insert(&branches); err != nil {
+ return err
+ }
+ }
+
+ return x.DropTables(new(DeletedBranch))
+}
diff --git a/models/migrations/v1_21/v265.go b/models/migrations/v1_21/v265.go
new file mode 100644
index 00000000..800eb95f
--- /dev/null
+++ b/models/migrations/v1_21/v265.go
@@ -0,0 +1,19 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AlterActionArtifactTable(x *xorm.Engine) error {
+ // ActionArtifact is a file that is stored in the artifact storage.
+ type ActionArtifact struct {
+ RunID int64 `xorm:"index unique(runid_name_path)"` // The run id of the artifact
+ ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it
+ ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when
+ }
+
+ return x.Sync(new(ActionArtifact))
+}
diff --git a/models/migrations/v1_21/v266.go b/models/migrations/v1_21/v266.go
new file mode 100644
index 00000000..79a5f5e1
--- /dev/null
+++ b/models/migrations/v1_21/v266.go
@@ -0,0 +1,23 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func ReduceCommitStatus(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if _, err := sess.Exec(`UPDATE commit_status SET state='pending' WHERE state='running'`); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_21/v267.go b/models/migrations/v1_21/v267.go
new file mode 100644
index 00000000..bc0e954b
--- /dev/null
+++ b/models/migrations/v1_21/v267.go
@@ -0,0 +1,23 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func CreateActionTasksVersionTable(x *xorm.Engine) error {
+ type ActionTasksVersion struct {
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"UNIQUE(owner_repo)"`
+ RepoID int64 `xorm:"INDEX UNIQUE(owner_repo)"`
+ Version int64
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
+ }
+
+ return x.Sync(new(ActionTasksVersion))
+}
diff --git a/models/migrations/v1_21/v268.go b/models/migrations/v1_21/v268.go
new file mode 100644
index 00000000..332793ff
--- /dev/null
+++ b/models/migrations/v1_21/v268.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+// UpdateActionsRefIndex updates the index of actions ref field
+func UpdateActionsRefIndex(x *xorm.Engine) error {
+ type ActionRun struct {
+ Ref string `xorm:"index"` // the commit/tag/… causing the run
+ }
+ return x.Sync(new(ActionRun))
+}
diff --git a/models/migrations/v1_21/v269.go b/models/migrations/v1_21/v269.go
new file mode 100644
index 00000000..475ec023
--- /dev/null
+++ b/models/migrations/v1_21/v269.go
@@ -0,0 +1,12 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func DropDeletedBranchTable(x *xorm.Engine) error {
+ return x.DropTables("deleted_branch")
+}
diff --git a/models/migrations/v1_21/v270.go b/models/migrations/v1_21/v270.go
new file mode 100644
index 00000000..b9cc84d3
--- /dev/null
+++ b/models/migrations/v1_21/v270.go
@@ -0,0 +1,26 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func FixPackagePropertyTypo(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if _, err := sess.Exec(`UPDATE package_property SET name = 'rpm.metadata' WHERE name = 'rpm.metdata'`); err != nil {
+ return err
+ }
+ if _, err := sess.Exec(`UPDATE package_property SET name = 'conda.metadata' WHERE name = 'conda.metdata'`); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_21/v271.go b/models/migrations/v1_21/v271.go
new file mode 100644
index 00000000..098f6499
--- /dev/null
+++ b/models/migrations/v1_21/v271.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddArchivedUnixColumInLabelTable(x *xorm.Engine) error {
+ type Label struct {
+ ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT NULL"`
+ }
+ return x.Sync(new(Label))
+}
diff --git a/models/migrations/v1_21/v272.go b/models/migrations/v1_21/v272.go
new file mode 100644
index 00000000..a729c49f
--- /dev/null
+++ b/models/migrations/v1_21/v272.go
@@ -0,0 +1,14 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+import (
+ "xorm.io/xorm"
+)
+
+func AddVersionToActionRunTable(x *xorm.Engine) error {
+ type ActionRun struct {
+ Version int `xorm:"version default 0"`
+ }
+ return x.Sync(new(ActionRun))
+}
diff --git a/models/migrations/v1_21/v273.go b/models/migrations/v1_21/v273.go
new file mode 100644
index 00000000..61c79f4a
--- /dev/null
+++ b/models/migrations/v1_21/v273.go
@@ -0,0 +1,45 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddActionScheduleTable(x *xorm.Engine) error {
+ type ActionSchedule struct {
+ ID int64
+ Title string
+ Specs []string
+ RepoID int64 `xorm:"index"`
+ OwnerID int64 `xorm:"index"`
+ WorkflowID string
+ TriggerUserID int64
+ Ref string
+ CommitSHA string
+ Event string
+ EventPayload string `xorm:"LONGTEXT"`
+ Content []byte
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated"`
+ }
+
+ type ActionScheduleSpec struct {
+ ID int64
+ RepoID int64 `xorm:"index"`
+ ScheduleID int64 `xorm:"index"`
+ Spec string
+ Next timeutil.TimeStamp `xorm:"index"`
+ Prev timeutil.TimeStamp
+
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated"`
+ }
+
+ return x.Sync(
+ new(ActionSchedule),
+ new(ActionScheduleSpec),
+ )
+}
diff --git a/models/migrations/v1_21/v274.go b/models/migrations/v1_21/v274.go
new file mode 100644
index 00000000..df5994f1
--- /dev/null
+++ b/models/migrations/v1_21/v274.go
@@ -0,0 +1,36 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+import (
+ "time"
+
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddExpiredUnixColumnInActionArtifactTable(x *xorm.Engine) error {
+ type ActionArtifact struct {
+ ExpiredUnix timeutil.TimeStamp `xorm:"index"` // time when the artifact will be expired
+ }
+ if err := x.Sync(new(ActionArtifact)); err != nil {
+ return err
+ }
+ return updateArtifactsExpiredUnixTo90Days(x)
+}
+
+func updateArtifactsExpiredUnixTo90Days(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ expiredTime := time.Now().AddDate(0, 0, 90).Unix()
+ if _, err := sess.Exec(`UPDATE action_artifact SET expired_unix=? WHERE status='2' AND expired_unix is NULL`, expiredTime); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_21/v275.go b/models/migrations/v1_21/v275.go
new file mode 100644
index 00000000..78804a59
--- /dev/null
+++ b/models/migrations/v1_21/v275.go
@@ -0,0 +1,15 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddScheduleIDForActionRun(x *xorm.Engine) error {
+ type ActionRun struct {
+ ScheduleID int64
+ }
+ return x.Sync(new(ActionRun))
+}
diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go
new file mode 100644
index 00000000..67e95017
--- /dev/null
+++ b/models/migrations/v1_21/v276.go
@@ -0,0 +1,156 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func AddRemoteAddressToMirrors(x *xorm.Engine) error {
+ type Mirror struct {
+ RemoteAddress string `xorm:"VARCHAR(2048)"`
+ }
+
+ type PushMirror struct {
+ RemoteAddress string `xorm:"VARCHAR(2048)"`
+ }
+
+ if err := x.Sync(new(Mirror), new(PushMirror)); err != nil {
+ return err
+ }
+
+ if err := migratePullMirrors(x); err != nil {
+ return err
+ }
+
+ return migratePushMirrors(x)
+}
+
+func migratePullMirrors(x *xorm.Engine) error {
+ type Mirror struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX"`
+ RemoteAddress string `xorm:"VARCHAR(2048)"`
+ RepoOwner string
+ RepoName string
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ limit := setting.Database.IterateBufferSize
+ if limit <= 0 {
+ limit = 50
+ }
+
+ start := 0
+
+ for {
+ var mirrors []Mirror
+ if err := sess.Select("mirror.id, mirror.repo_id, mirror.remote_address, repository.owner_name as repo_owner, repository.name as repo_name").
+ Join("INNER", "repository", "repository.id = mirror.repo_id").
+ Limit(limit, start).Find(&mirrors); err != nil {
+ return err
+ }
+
+ if len(mirrors) == 0 {
+ break
+ }
+ start += len(mirrors)
+
+ for _, m := range mirrors {
+ remoteAddress, err := repo_model.GetPushMirrorRemoteAddress(m.RepoOwner, m.RepoName, "origin")
+ if err != nil {
+ return err
+ }
+
+ m.RemoteAddress = remoteAddress
+
+ if _, err = sess.ID(m.ID).Cols("remote_address").Update(m); err != nil {
+ return err
+ }
+ }
+
+ if start%1000 == 0 { // avoid a too big transaction
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ }
+ }
+
+ return sess.Commit()
+}
+
+func migratePushMirrors(x *xorm.Engine) error {
+ type PushMirror struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX"`
+ RemoteName string
+ RemoteAddress string `xorm:"VARCHAR(2048)"`
+ RepoOwner string
+ RepoName string
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ limit := setting.Database.IterateBufferSize
+ if limit <= 0 {
+ limit = 50
+ }
+
+ start := 0
+
+ for {
+ var mirrors []PushMirror
+ if err := sess.Select("push_mirror.id, push_mirror.repo_id, push_mirror.remote_name, push_mirror.remote_address, repository.owner_name as repo_owner, repository.name as repo_name").
+ Join("INNER", "repository", "repository.id = push_mirror.repo_id").
+ Limit(limit, start).Find(&mirrors); err != nil {
+ return err
+ }
+
+ if len(mirrors) == 0 {
+ break
+ }
+ start += len(mirrors)
+
+ for _, m := range mirrors {
+ remoteAddress, err := repo_model.GetPushMirrorRemoteAddress(m.RepoOwner, m.RepoName, m.RemoteName)
+ if err != nil {
+ return err
+ }
+
+ m.RemoteAddress = remoteAddress
+
+ if _, err = sess.ID(m.ID).Cols("remote_address").Update(m); err != nil {
+ return err
+ }
+ }
+
+ if start%1000 == 0 { // avoid a too big transaction
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ }
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_21/v277.go b/models/migrations/v1_21/v277.go
new file mode 100644
index 00000000..12529160
--- /dev/null
+++ b/models/migrations/v1_21/v277.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddIndexToIssueUserIssueID(x *xorm.Engine) error {
+ type IssueUser struct {
+ IssueID int64 `xorm:"INDEX"`
+ }
+
+ return x.Sync(new(IssueUser))
+}
diff --git a/models/migrations/v1_21/v278.go b/models/migrations/v1_21/v278.go
new file mode 100644
index 00000000..d6a462d1
--- /dev/null
+++ b/models/migrations/v1_21/v278.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddIndexToCommentDependentIssueID(x *xorm.Engine) error {
+ type Comment struct {
+ DependentIssueID int64 `xorm:"index"`
+ }
+
+ return x.Sync(new(Comment))
+}
diff --git a/models/migrations/v1_21/v279.go b/models/migrations/v1_21/v279.go
new file mode 100644
index 00000000..19647225
--- /dev/null
+++ b/models/migrations/v1_21/v279.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddIndexToActionUserID(x *xorm.Engine) error {
+ type Action struct {
+ UserID int64 `xorm:"INDEX"`
+ }
+
+ return x.Sync(new(Action))
+}
diff --git a/models/migrations/v1_22/main_test.go b/models/migrations/v1_22/main_test.go
new file mode 100644
index 00000000..efd8dbaa
--- /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"
+
+ "code.gitea.io/gitea/models/migrations/base"
+)
+
+func TestMain(m *testing.M) {
+ base.MainTest(m)
+}
diff --git a/models/migrations/v1_22/v280.go b/models/migrations/v1_22/v280.go
new file mode 100644
index 00000000..a8ee4a3b
--- /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 00000000..fc1866aa
--- /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 00000000..baad9e09
--- /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 00000000..86946d1c
--- /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 00000000..db1ef3de
--- /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"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "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 := base.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 00000000..1a4c7869
--- /dev/null
+++ b/models/migrations/v1_22/v284.go
@@ -0,0 +1,14 @@
+// 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"`
+ }
+ return x.Sync(new(ProtectedBranch))
+}
diff --git a/models/migrations/v1_22/v285.go b/models/migrations/v1_22/v285.go
new file mode 100644
index 00000000..c0dacd40
--- /dev/null
+++ b/models/migrations/v1_22/v285.go
@@ -0,0 +1,18 @@
+// 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
+ }
+
+ return x.Sync(&ActionRun{})
+}
diff --git a/models/migrations/v1_22/v286.go b/models/migrations/v1_22/v286.go
new file mode 100644
index 00000000..6a5f4512
--- /dev/null
+++ b/models/migrations/v1_22/v286.go
@@ -0,0 +1,72 @@
+// 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.Sync(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 00000000..617ed5a3
--- /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"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "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 base.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 00000000..c8b15932
--- /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 00000000..7c93bfcc
--- /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 00000000..e2dfc487
--- /dev/null
+++ b/models/migrations/v1_22/v289.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 AddDefaultWikiBranch(x *xorm.Engine) error {
+ type Repository struct {
+ ID int64
+ DefaultWikiBranch string
+ }
+ if err := x.Sync(&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 00000000..e9c471b3
--- /dev/null
+++ b/models/migrations/v1_22/v290.go
@@ -0,0 +1,39 @@
+// 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
+ return x.Sync(new(HookTask))
+}
diff --git a/models/migrations/v1_22/v290_test.go b/models/migrations/v1_22/v290_test.go
new file mode 100644
index 00000000..ea154085
--- /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"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "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 := base.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 00000000..0bfffe5d
--- /dev/null
+++ b/models/migrations/v1_22/v291.go
@@ -0,0 +1,14 @@
+// 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"`
+ }
+
+ return x.Sync(&Attachment{})
+}
diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go
new file mode 100644
index 00000000..beca556a
--- /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 00000000..53cc7192
--- /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 00000000..bc9eb46b
--- /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"
+ "code.gitea.io/gitea/models/migrations/base"
+ "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 := base.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 00000000..314b4519
--- /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 00000000..006ea7b7
--- /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"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "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 := base.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 00000000..17bdadb4
--- /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 00000000..1ecacab9
--- /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 00000000..b9f3b95a
--- /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")
+}
diff --git a/models/migrations/v1_23/main_test.go b/models/migrations/v1_23/main_test.go
new file mode 100644
index 00000000..b7948bd4
--- /dev/null
+++ b/models/migrations/v1_23/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_23 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+)
+
+func TestMain(m *testing.M) {
+ base.MainTest(m)
+}
diff --git a/models/migrations/v1_23/v299.go b/models/migrations/v1_23/v299.go
new file mode 100644
index 00000000..f6db960c
--- /dev/null
+++ b/models/migrations/v1_23/v299.go
@@ -0,0 +1,18 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_23 //nolint
+
+import "xorm.io/xorm"
+
+func AddContentVersionToIssueAndComment(x *xorm.Engine) error {
+ type Issue struct {
+ ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ type Comment struct {
+ ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ return x.Sync(new(Comment), new(Issue))
+}
diff --git a/models/migrations/v1_6/v70.go b/models/migrations/v1_6/v70.go
new file mode 100644
index 00000000..74434a84
--- /dev/null
+++ b/models/migrations/v1_6/v70.go
@@ -0,0 +1,110 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_6 //nolint
+
+import (
+ "fmt"
+ "time"
+
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func AddIssueDependencies(x *xorm.Engine) (err error) {
+ type IssueDependency struct {
+ ID int64 `xorm:"pk autoincr"`
+ UserID int64 `xorm:"NOT NULL"`
+ IssueID int64 `xorm:"NOT NULL"`
+ DependencyID int64 `xorm:"NOT NULL"`
+ Created time.Time `xorm:"-"`
+ CreatedUnix int64 `xorm:"created"`
+ Updated time.Time `xorm:"-"`
+ UpdatedUnix int64 `xorm:"updated"`
+ }
+
+ const (
+ v16UnitTypeCode = iota + 1 // 1 code
+ v16UnitTypeIssues // 2 issues
+ v16UnitTypePRs // 3 PRs
+ v16UnitTypeCommits // 4 Commits
+ v16UnitTypeReleases // 5 Releases
+ v16UnitTypeWiki // 6 Wiki
+ v16UnitTypeSettings // 7 Settings
+ v16UnitTypeExternalWiki // 8 ExternalWiki
+ v16UnitTypeExternalTracker // 9 ExternalTracker
+ )
+
+ if err = x.Sync(new(IssueDependency)); err != nil {
+ return fmt.Errorf("Error creating issue_dependency_table column definition: %w", err)
+ }
+
+ // Update Comment definition
+ // This (copied) struct does only contain fields used by xorm as the only use here is to update the database
+
+ // CommentType defines the comment type
+ type CommentType int
+
+ // TimeStamp defines a timestamp
+ type TimeStamp int64
+
+ type Comment struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type CommentType
+ PosterID int64 `xorm:"INDEX"`
+ IssueID int64 `xorm:"INDEX"`
+ LabelID int64
+ OldMilestoneID int64
+ MilestoneID int64
+ OldAssigneeID int64
+ AssigneeID int64
+ OldTitle string
+ NewTitle string
+ DependentIssueID int64
+
+ CommitID int64
+ Line int64
+ Content string `xorm:"TEXT"`
+
+ CreatedUnix TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix TimeStamp `xorm:"INDEX updated"`
+
+ // Reference issue in commit message
+ CommitSHA string `xorm:"VARCHAR(40)"`
+ }
+
+ if err = x.Sync(new(Comment)); err != nil {
+ return fmt.Errorf("Error updating issue_comment table column definition: %w", err)
+ }
+
+ // RepoUnit describes all units of a repository
+ type RepoUnit struct {
+ ID int64
+ RepoID int64 `xorm:"INDEX(s)"`
+ Type int `xorm:"INDEX(s)"`
+ Config map[string]any `xorm:"JSON"`
+ CreatedUnix int64 `xorm:"INDEX CREATED"`
+ Created time.Time `xorm:"-"`
+ }
+
+ // Updating existing issue units
+ units := make([]*RepoUnit, 0, 100)
+ err = x.Where("`type` = ?", v16UnitTypeIssues).Find(&units)
+ if err != nil {
+ return fmt.Errorf("Query repo units: %w", err)
+ }
+ for _, unit := range units {
+ if unit.Config == nil {
+ unit.Config = make(map[string]any)
+ }
+ if _, ok := unit.Config["EnableDependencies"]; !ok {
+ unit.Config["EnableDependencies"] = setting.Service.DefaultEnableDependencies
+ }
+ if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
+ return err
+ }
+ }
+
+ return err
+}
diff --git a/models/migrations/v1_6/v71.go b/models/migrations/v1_6/v71.go
new file mode 100644
index 00000000..58618722
--- /dev/null
+++ b/models/migrations/v1_6/v71.go
@@ -0,0 +1,79 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_6 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/util"
+
+ "xorm.io/xorm"
+)
+
+func AddScratchHash(x *xorm.Engine) error {
+ // TwoFactor see models/twofactor.go
+ type TwoFactor struct {
+ ID int64 `xorm:"pk autoincr"`
+ UID int64 `xorm:"UNIQUE"`
+ Secret string
+ ScratchToken string
+ ScratchSalt string
+ ScratchHash string
+ LastUsedPasscode string `xorm:"VARCHAR(10)"`
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ if err := x.Sync(new(TwoFactor)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ // transform all tokens to hashes
+ const batchSize = 100
+ for start := 0; ; start += batchSize {
+ tfas := make([]*TwoFactor, 0, batchSize)
+ if err := sess.Limit(batchSize, start).Find(&tfas); err != nil {
+ return err
+ }
+ if len(tfas) == 0 {
+ break
+ }
+
+ for _, tfa := range tfas {
+ // generate salt
+ salt, err := util.CryptoRandomString(10)
+ if err != nil {
+ return err
+ }
+ tfa.ScratchSalt = salt
+ tfa.ScratchHash = base.HashToken(tfa.ScratchToken, salt)
+
+ if _, err := sess.ID(tfa.ID).Cols("scratch_salt, scratch_hash").Update(tfa); err != nil {
+ return fmt.Errorf("couldn't add in scratch_hash and scratch_salt: %w", err)
+ }
+ }
+ }
+
+ // Commit and begin new transaction for dropping columns
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if err := base.DropTableColumns(sess, "two_factor", "scratch_token"); err != nil {
+ return err
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_6/v72.go b/models/migrations/v1_6/v72.go
new file mode 100644
index 00000000..04cef9a1
--- /dev/null
+++ b/models/migrations/v1_6/v72.go
@@ -0,0 +1,30 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_6 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddReview(x *xorm.Engine) error {
+ // Review see models/review.go
+ type Review struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type string
+ ReviewerID int64 `xorm:"index"`
+ IssueID int64 `xorm:"index"`
+ Content string
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ }
+
+ if err := x.Sync(new(Review)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return nil
+}
diff --git a/models/migrations/v1_7/v73.go b/models/migrations/v1_7/v73.go
new file mode 100644
index 00000000..b5a748aa
--- /dev/null
+++ b/models/migrations/v1_7/v73.go
@@ -0,0 +1,18 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_7 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddMustChangePassword(x *xorm.Engine) error {
+ // User see models/user.go
+ type User struct {
+ ID int64 `xorm:"pk autoincr"`
+ MustChangePassword bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(User))
+}
diff --git a/models/migrations/v1_7/v74.go b/models/migrations/v1_7/v74.go
new file mode 100644
index 00000000..f0567e3c
--- /dev/null
+++ b/models/migrations/v1_7/v74.go
@@ -0,0 +1,15 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_7 //nolint
+
+import "xorm.io/xorm"
+
+func AddApprovalWhitelistsToProtectedBranches(x *xorm.Engine) error {
+ type ProtectedBranch struct {
+ ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
+ ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
+ RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
+ }
+ return x.Sync(new(ProtectedBranch))
+}
diff --git a/models/migrations/v1_7/v75.go b/models/migrations/v1_7/v75.go
new file mode 100644
index 00000000..fa743097
--- /dev/null
+++ b/models/migrations/v1_7/v75.go
@@ -0,0 +1,32 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_7 //nolint
+
+import (
+ "xorm.io/builder"
+ "xorm.io/xorm"
+)
+
+func ClearNonusedData(x *xorm.Engine) error {
+ condDelete := func(colName string) builder.Cond {
+ return builder.NotIn(colName, builder.Select("id").From("`user`"))
+ }
+
+ if _, err := x.Exec(builder.Delete(condDelete("uid")).From("team_user")); err != nil {
+ return err
+ }
+
+ if _, err := x.Exec(builder.Delete(condDelete("user_id")).From("collaboration")); err != nil {
+ return err
+ }
+
+ if _, err := x.Exec(builder.Delete(condDelete("user_id")).From("stopwatch")); err != nil {
+ return err
+ }
+
+ if _, err := x.Exec(builder.Delete(condDelete("owner_id")).From("gpg_key")); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/models/migrations/v1_8/v76.go b/models/migrations/v1_8/v76.go
new file mode 100644
index 00000000..d3fbd94d
--- /dev/null
+++ b/models/migrations/v1_8/v76.go
@@ -0,0 +1,74 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_8 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddPullRequestRebaseWithMerge(x *xorm.Engine) error {
+ // RepoUnit describes all units of a repository
+ type RepoUnit struct {
+ ID int64
+ RepoID int64 `xorm:"INDEX(s)"`
+ Type int `xorm:"INDEX(s)"`
+ Config map[string]any `xorm:"JSON"`
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
+ }
+
+ const (
+ v16UnitTypeCode = iota + 1 // 1 code
+ v16UnitTypeIssues // 2 issues
+ v16UnitTypePRs // 3 PRs
+ v16UnitTypeCommits // 4 Commits
+ v16UnitTypeReleases // 5 Releases
+ v16UnitTypeWiki // 6 Wiki
+ v16UnitTypeSettings // 7 Settings
+ v16UnitTypeExternalWiki // 8 ExternalWiki
+ v16UnitTypeExternalTracker // 9 ExternalTracker
+ )
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ // Updating existing issue units
+ units := make([]*RepoUnit, 0, 100)
+ if err := sess.Where("`type` = ?", v16UnitTypePRs).Find(&units); err != nil {
+ return fmt.Errorf("Query repo units: %w", err)
+ }
+ for _, unit := range units {
+ if unit.Config == nil {
+ unit.Config = make(map[string]any)
+ }
+ // Allow the new merge style if all other merge styles are allowed
+ allowMergeRebase := true
+
+ if allowMerge, ok := unit.Config["AllowMerge"]; ok {
+ allowMergeRebase = allowMergeRebase && allowMerge.(bool)
+ }
+
+ if allowRebase, ok := unit.Config["AllowRebase"]; ok {
+ allowMergeRebase = allowMergeRebase && allowRebase.(bool)
+ }
+
+ if allowSquash, ok := unit.Config["AllowSquash"]; ok {
+ allowMergeRebase = allowMergeRebase && allowSquash.(bool)
+ }
+
+ if _, ok := unit.Config["AllowRebaseMerge"]; !ok {
+ unit.Config["AllowRebaseMerge"] = allowMergeRebase
+ }
+ if _, err := sess.ID(unit.ID).Cols("config").Update(unit); err != nil {
+ return err
+ }
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_8/v77.go b/models/migrations/v1_8/v77.go
new file mode 100644
index 00000000..8b199939
--- /dev/null
+++ b/models/migrations/v1_8/v77.go
@@ -0,0 +1,16 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_8 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddUserDefaultTheme(x *xorm.Engine) error {
+ type User struct {
+ Theme string `xorm:"VARCHAR(30) NOT NULL DEFAULT ''"`
+ }
+
+ return x.Sync(new(User))
+}
diff --git a/models/migrations/v1_8/v78.go b/models/migrations/v1_8/v78.go
new file mode 100644
index 00000000..8f041c14
--- /dev/null
+++ b/models/migrations/v1_8/v78.go
@@ -0,0 +1,43 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_8 //nolint
+
+import (
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "xorm.io/xorm"
+)
+
+func RenameRepoIsBareToIsEmpty(x *xorm.Engine) error {
+ type Repository struct {
+ ID int64 `xorm:"pk autoincr"`
+ IsBare bool
+ IsEmpty bool `xorm:"INDEX"`
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if err := sess.Sync(new(Repository)); err != nil {
+ return err
+ }
+ if _, err := sess.Exec("UPDATE repository SET is_empty = is_bare;"); err != nil {
+ return err
+ }
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ if err := base.DropTableColumns(sess, "repository", "is_bare"); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_8/v79.go b/models/migrations/v1_8/v79.go
new file mode 100644
index 00000000..eb3a9ed0
--- /dev/null
+++ b/models/migrations/v1_8/v79.go
@@ -0,0 +1,25 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_8 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func AddCanCloseIssuesViaCommitInAnyBranch(x *xorm.Engine) error {
+ type Repository struct {
+ ID int64 `xorm:"pk autoincr"`
+ CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ if err := x.Sync(new(Repository)); err != nil {
+ return err
+ }
+
+ _, err := x.Exec("UPDATE repository SET close_issues_via_commit_in_any_branch = ?",
+ setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch)
+ return err
+}
diff --git a/models/migrations/v1_8/v80.go b/models/migrations/v1_8/v80.go
new file mode 100644
index 00000000..cebbbead
--- /dev/null
+++ b/models/migrations/v1_8/v80.go
@@ -0,0 +1,16 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_8 //nolint
+
+import "xorm.io/xorm"
+
+func AddIsLockedToIssues(x *xorm.Engine) error {
+ // Issue see models/issue.go
+ type Issue struct {
+ ID int64 `xorm:"pk autoincr"`
+ IsLocked bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync(new(Issue))
+}
diff --git a/models/migrations/v1_8/v81.go b/models/migrations/v1_8/v81.go
new file mode 100644
index 00000000..734fc246
--- /dev/null
+++ b/models/migrations/v1_8/v81.go
@@ -0,0 +1,28 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_8 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+func ChangeU2FCounterType(x *xorm.Engine) error {
+ var err error
+
+ switch x.Dialect().URI().DBType {
+ case schemas.MYSQL:
+ _, err = x.Exec("ALTER TABLE `u2f_registration` MODIFY `counter` BIGINT")
+ case schemas.POSTGRES:
+ _, err = x.Exec("ALTER TABLE `u2f_registration` ALTER COLUMN `counter` SET DATA TYPE bigint")
+ }
+
+ if err != nil {
+ return fmt.Errorf("Error changing u2f_registration counter column type: %w", err)
+ }
+
+ return nil
+}
diff --git a/models/migrations/v1_9/v82.go b/models/migrations/v1_9/v82.go
new file mode 100644
index 00000000..26806dd6
--- /dev/null
+++ b/models/migrations/v1_9/v82.go
@@ -0,0 +1,133 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_9 //nolint
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func FixReleaseSha1OnReleaseTable(x *xorm.Engine) error {
+ type Release struct {
+ ID int64
+ RepoID int64
+ Sha1 string
+ TagName string
+ }
+
+ type Repository struct {
+ ID int64
+ OwnerID int64
+ Name string
+ }
+
+ type User struct {
+ ID int64
+ Name string
+ }
+
+ // UserPath returns the path absolute path of user repositories.
+ UserPath := func(userName string) string {
+ return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
+ }
+
+ // RepoPath returns repository path by given user and repository name.
+ RepoPath := func(userName, repoName string) string {
+ return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git")
+ }
+
+ // Update release sha1
+ const batchSize = 100
+ sess := x.NewSession()
+ defer sess.Close()
+
+ var (
+ err error
+ count int
+ gitRepoCache = make(map[int64]*git.Repository)
+ repoCache = make(map[int64]*Repository)
+ userCache = make(map[int64]*User)
+ )
+
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ for start := 0; ; start += batchSize {
+ releases := make([]*Release, 0, batchSize)
+ if err = sess.Limit(batchSize, start).Asc("id").Where("is_tag=?", false).Find(&releases); err != nil {
+ return err
+ }
+ if len(releases) == 0 {
+ break
+ }
+
+ for _, release := range releases {
+ gitRepo, ok := gitRepoCache[release.RepoID]
+ if !ok {
+ repo, ok := repoCache[release.RepoID]
+ if !ok {
+ repo = new(Repository)
+ has, err := sess.ID(release.RepoID).Get(repo)
+ if err != nil {
+ return err
+ } else if !has {
+ return fmt.Errorf("Repository %d is not exist", release.RepoID)
+ }
+
+ repoCache[release.RepoID] = repo
+ }
+
+ user, ok := userCache[repo.OwnerID]
+ if !ok {
+ user = new(User)
+ has, err := sess.ID(repo.OwnerID).Get(user)
+ if err != nil {
+ return err
+ } else if !has {
+ return fmt.Errorf("User %d is not exist", repo.OwnerID)
+ }
+
+ userCache[repo.OwnerID] = user
+ }
+
+ gitRepo, err = git.OpenRepository(git.DefaultContext, RepoPath(user.Name, repo.Name))
+ if err != nil {
+ return err
+ }
+ defer gitRepo.Close()
+ gitRepoCache[release.RepoID] = gitRepo
+ }
+
+ release.Sha1, err = gitRepo.GetTagCommitID(release.TagName)
+ if err != nil && !git.IsErrNotExist(err) {
+ return err
+ }
+
+ if err == nil {
+ if _, err = sess.ID(release.ID).Cols("sha1").Update(release); err != nil {
+ return err
+ }
+ }
+
+ count++
+ if count >= 1000 {
+ if err = sess.Commit(); err != nil {
+ return err
+ }
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+ count = 0
+ }
+ }
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_9/v83.go b/models/migrations/v1_9/v83.go
new file mode 100644
index 00000000..10e6c458
--- /dev/null
+++ b/models/migrations/v1_9/v83.go
@@ -0,0 +1,27 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_9 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddUploaderIDForAttachment(x *xorm.Engine) error {
+ type Attachment struct {
+ ID int64 `xorm:"pk autoincr"`
+ UUID string `xorm:"uuid UNIQUE"`
+ IssueID int64 `xorm:"INDEX"`
+ ReleaseID int64 `xorm:"INDEX"`
+ UploaderID int64 `xorm:"INDEX DEFAULT 0"`
+ CommentID int64
+ Name string
+ DownloadCount int64 `xorm:"DEFAULT 0"`
+ Size int64 `xorm:"DEFAULT 0"`
+ CreatedUnix timeutil.TimeStamp `xorm:"created"`
+ }
+
+ return x.Sync(new(Attachment))
+}
diff --git a/models/migrations/v1_9/v84.go b/models/migrations/v1_9/v84.go
new file mode 100644
index 00000000..c7155fe9
--- /dev/null
+++ b/models/migrations/v1_9/v84.go
@@ -0,0 +1,17 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_9 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddGPGKeyImport(x *xorm.Engine) error {
+ type GPGKeyImport struct {
+ KeyID string `xorm:"pk CHAR(16) NOT NULL"`
+ Content string `xorm:"TEXT NOT NULL"`
+ }
+
+ return x.Sync(new(GPGKeyImport))
+}
diff --git a/models/migrations/v1_9/v85.go b/models/migrations/v1_9/v85.go
new file mode 100644
index 00000000..a23d7c5d
--- /dev/null
+++ b/models/migrations/v1_9/v85.go
@@ -0,0 +1,118 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_9 //nolint
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/util"
+
+ "xorm.io/xorm"
+)
+
+func HashAppToken(x *xorm.Engine) error {
+ // AccessToken see models/token.go
+ type AccessToken struct {
+ ID int64 `xorm:"pk autoincr"`
+ UID int64 `xorm:"INDEX"`
+ Name string
+ Sha1 string
+ Token string `xorm:"-"`
+ TokenHash string // sha256 of token - we will ensure UNIQUE later
+ TokenSalt string
+ TokenLastEight string `xorm:"token_last_eight"`
+
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+ HasRecentActivity bool `xorm:"-"`
+ HasUsed bool `xorm:"-"`
+ }
+
+ // First remove the index
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if err := sess.Sync(new(AccessToken)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ // transform all tokens to hashes
+ const batchSize = 100
+ for start := 0; ; start += batchSize {
+ tokens := make([]*AccessToken, 0, batchSize)
+ if err := sess.Limit(batchSize, start).Find(&tokens); err != nil {
+ return err
+ }
+ if len(tokens) == 0 {
+ break
+ }
+
+ for _, token := range tokens {
+ // generate salt
+ salt, err := util.CryptoRandomString(10)
+ if err != nil {
+ return err
+ }
+ token.TokenSalt = salt
+ token.TokenHash = base.HashToken(token.Sha1, salt)
+ if len(token.Sha1) < 8 {
+ log.Warn("Unable to transform token %s with name %s belonging to user ID %d, skipping transformation", token.Sha1, token.Name, token.UID)
+ continue
+ }
+ token.TokenLastEight = token.Sha1[len(token.Sha1)-8:]
+ token.Sha1 = "" // ensure to blank out column in case drop column doesn't work
+
+ if _, err := sess.ID(token.ID).Cols("token_hash, token_salt, token_last_eight, sha1").Update(token); err != nil {
+ return fmt.Errorf("couldn't add in sha1, token_hash, token_salt and token_last_eight: %w", err)
+ }
+ }
+ }
+
+ // Commit and begin new transaction for dropping columns
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if err := base.DropTableColumns(sess, "access_token", "sha1"); err != nil {
+ return err
+ }
+ if err := sess.Commit(); err != nil {
+ return err
+ }
+ return resyncHashAppTokenWithUniqueHash(x)
+}
+
+func resyncHashAppTokenWithUniqueHash(x *xorm.Engine) error {
+ // AccessToken see models/token.go
+ type AccessToken struct {
+ TokenHash string `xorm:"UNIQUE"` // sha256 of token - we will ensure UNIQUE later
+ }
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ if err := sess.Sync(new(AccessToken)); err != nil {
+ return fmt.Errorf("Sync: %w", err)
+ }
+ return sess.Commit()
+}
diff --git a/models/migrations/v1_9/v86.go b/models/migrations/v1_9/v86.go
new file mode 100644
index 00000000..cf2725d1
--- /dev/null
+++ b/models/migrations/v1_9/v86.go
@@ -0,0 +1,16 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_9 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddHTTPMethodToWebhook(x *xorm.Engine) error {
+ type Webhook struct {
+ HTTPMethod string `xorm:"http_method DEFAULT 'POST'"`
+ }
+
+ return x.Sync(new(Webhook))
+}
diff --git a/models/migrations/v1_9/v87.go b/models/migrations/v1_9/v87.go
new file mode 100644
index 00000000..fa01b6e5
--- /dev/null
+++ b/models/migrations/v1_9/v87.go
@@ -0,0 +1,17 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_9 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+func AddAvatarFieldToRepository(x *xorm.Engine) error {
+ type Repository struct {
+ // ID(10-20)-md5(32) - must fit into 64 symbols
+ Avatar string `xorm:"VARCHAR(64)"`
+ }
+
+ return x.Sync(new(Repository))
+}