2015-02-12 08:28:37 +05:30
// Copyright 2015 The Gogs Authors. All rights reserved.
2019-02-18 21:30:27 +05:30
// Copyright 2017 The Gitea Authors. All rights reserved.
2015-02-12 08:28:37 +05:30
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2015-01-22 18:19:52 +05:30
package migrations
import (
2015-02-12 08:28:37 +05:30
"fmt"
2019-07-02 00:56:59 +05:30
"regexp"
2015-01-23 13:24:16 +05:30
"strings"
2015-01-22 18:26:50 +05:30
2016-11-10 21:54:48 +05:30
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2019-08-23 22:10:30 +05:30
2019-10-17 14:56:49 +05:30
"xorm.io/xorm"
2015-01-22 18:19:52 +05:30
)
2020-01-31 19:12:45 +05:30
const minDBVersion = 70 // Gitea 1.5.3
2015-02-12 08:28:37 +05:30
2016-11-28 21:14:17 +05:30
// Migration describes on migration from lower version to high version
2015-02-12 09:40:30 +05:30
type Migration interface {
Description ( ) string
Migrate ( * xorm . Engine ) error
}
type migration struct {
description string
migrate func ( * xorm . Engine ) error
}
2016-11-28 21:14:17 +05:30
// NewMigration creates a new migration
2015-02-12 09:40:30 +05:30
func NewMigration ( desc string , fn func ( * xorm . Engine ) error ) Migration {
return & migration { desc , fn }
}
2016-11-28 21:14:17 +05:30
// Description returns the migration's description
2015-02-12 09:40:30 +05:30
func ( m * migration ) Description ( ) string {
return m . description
}
2016-11-28 21:14:17 +05:30
// Migrate executes the migration
2015-02-12 09:40:30 +05:30
func ( m * migration ) Migrate ( x * xorm . Engine ) error {
return m . migrate ( x )
}
2015-01-22 18:19:52 +05:30
2016-11-28 21:14:17 +05:30
// Version describes the version table. Should have only one row with id==1
2015-01-22 18:19:52 +05:30
type Version struct {
2016-07-16 07:38:04 +05:30
ID int64 ` xorm:"pk autoincr" `
2015-01-22 18:19:52 +05:30
Version int64
}
// This is a sequence of migrations. Add new migrations to the bottom of the list.
2015-02-12 23:16:21 +05:30
// If you want to "retire" a migration, remove it from the top of the list and
2016-11-28 21:14:17 +05:30
// update minDBVersion accordingly
2015-02-12 09:40:30 +05:30
var migrations = [ ] Migration {
2016-08-27 02:37:21 +05:30
2020-01-31 19:12:45 +05:30
// Gitea 1.5.3 ends at v70
2018-07-18 02:53:58 +05:30
// v70 -> v71
NewMigration ( "add issue_dependencies" , addIssueDependencies ) ,
2018-08-06 10:13:22 +05:30
// v71 -> v72
2018-07-27 18:24:50 +05:30
NewMigration ( "protect each scratch token" , addScratchHash ) ,
2018-08-06 10:13:22 +05:30
// v72 -> v73
NewMigration ( "add review" , addReview ) ,
2020-01-31 19:12:45 +05:30
// Gitea 1.6.4 ends at v73
2018-09-13 17:34:25 +05:30
// v73 -> v74
NewMigration ( "add must_change_password column for users table" , addMustChangePassword ) ,
2018-12-11 16:58:37 +05:30
// v74 -> v75
NewMigration ( "add approval whitelists to protected branches" , addApprovalWhitelistsToProtectedBranches ) ,
2018-12-18 21:56:26 +05:30
// v75 -> v76
NewMigration ( "clear nonused data which not deleted when user was deleted" , clearNonusedData ) ,
2020-01-31 19:12:45 +05:30
// Gitea 1.7.6 ends at v76
2018-12-27 15:57:08 +05:30
// v76 -> v77
NewMigration ( "add pull request rebase with merge commit" , addPullRequestRebaseWithMerge ) ,
2019-01-09 22:52:57 +05:30
// v77 -> v78
NewMigration ( "add theme to users" , addUserDefaultTheme ) ,
2019-01-18 05:31:04 +05:30
// v78 -> v79
NewMigration ( "rename repo is_bare to repo is_empty" , renameRepoIsBareToIsEmpty ) ,
2019-02-11 00:57:19 +05:30
// v79 -> v80
NewMigration ( "add can close issues via commit in any branch" , addCanCloseIssuesViaCommitInAnyBranch ) ,
2019-02-19 02:25:04 +05:30
// v80 -> v81
NewMigration ( "add is locked to issues" , addIsLockedToIssues ) ,
2019-03-05 08:04:52 +05:30
// v81 -> v82
NewMigration ( "update U2F counter type" , changeU2FCounterType ) ,
2020-01-31 19:12:45 +05:30
// Gitea 1.8.3 ends at v82
2019-03-11 09:14:58 +05:30
// v82 -> v83
NewMigration ( "hot fix for wrong release sha1 on release table" , fixReleaseSha1OnReleaseTable ) ,
2019-04-03 00:55:05 +05:30
// v83 -> v84
NewMigration ( "add uploader id for table attachment" , addUploaderIDForAttachment ) ,
2019-04-14 22:13:56 +05:30
// v84 -> v85
NewMigration ( "add table to store original imported gpg keys" , addGPGKeyImport ) ,
2019-05-04 21:15:34 +05:30
// v85 -> v86
NewMigration ( "hash application token" , hashAppToken ) ,
2019-05-05 23:39:02 +05:30
// v86 -> v87
NewMigration ( "add http method to webhook" , addHTTPMethodToWebhook ) ,
2019-05-30 07:52:26 +05:30
// v87 -> v88
NewMigration ( "add avatar field to repository" , addAvatarFieldToRepository ) ,
2020-01-31 19:12:45 +05:30
// Gitea 1.9.6 ends at v88
2019-06-30 13:27:59 +05:30
// v88 -> v89
NewMigration ( "add commit status context field to commit_status" , addCommitStatusContext ) ,
2019-07-08 07:44:12 +05:30
// v89 -> v90
NewMigration ( "add original author/url migration info to issues, comments, and repo " , addOriginalMigrationInfo ) ,
2019-07-29 08:59:42 +05:30
// v90 -> v91
NewMigration ( "change length of some repository columns" , changeSomeColumnsLengthOfRepo ) ,
2019-08-05 19:59:40 +05:30
// v91 -> v92
NewMigration ( "add index on owner_id of repository and type, review_id of comment" , addIndexOnRepositoryAndComment ) ,
2019-08-06 14:27:55 +05:30
// v92 -> v93
NewMigration ( "remove orphaned repository index statuses" , removeLingeringIndexStatus ) ,
2019-08-29 19:35:42 +05:30
// v93 -> v94
NewMigration ( "add email notification enabled preference to user" , addEmailNotificationEnabledToUser ) ,
2019-09-18 11:09:45 +05:30
// v94 -> v95
NewMigration ( "add enable_status_check, status_check_contexts to protected_branch" , addStatusCheckColumnsForProtectedBranches ) ,
2019-09-20 11:15:38 +05:30
// v95 -> v96
NewMigration ( "add table columns for cross referencing issues" , addCrossReferenceColumns ) ,
2019-09-22 14:35:48 +05:30
// v96 -> v97
NewMigration ( "delete orphaned attachments" , deleteOrphanedAttachments ) ,
2019-09-24 01:38:03 +05:30
// v97 -> v98
NewMigration ( "add repo_admin_change_team_access to user" , addRepoAdminChangeTeamAccessColumnForUser ) ,
2019-10-05 16:39:27 +05:30
// v98 -> v99
NewMigration ( "add original author name and id on migrated release" , addOriginalAuthorOnMigratedReleases ) ,
2020-01-31 19:12:45 +05:30
// Gitea 1.10.3 ends at v99
2019-10-13 18:53:14 +05:30
// v99 -> v100
NewMigration ( "add task table and status column for repository table" , addTaskTable ) ,
2019-10-14 11:40:42 +05:30
// v100 -> v101
NewMigration ( "update migration repositories' service type" , updateMigrationServiceTypes ) ,
2019-10-18 12:28:36 +05:30
// v101 -> v102
NewMigration ( "change length of some external login users columns" , changeSomeColumnsLengthOfExternalLoginUser ) ,
2019-10-18 16:43:31 +05:30
// v102 -> v103
NewMigration ( "update migration repositories' service type" , dropColumnHeadUserNameOnPullRequest ) ,
2019-10-21 13:51:45 +05:30
// v103 -> v104
NewMigration ( "Add WhitelistDeployKeys to protected branch" , addWhitelistDeployKeysToBranches ) ,
2019-10-23 17:18:32 +05:30
// v104 -> v105
NewMigration ( "remove unnecessary columns from label" , removeLabelUneededCols ) ,
2019-11-06 15:07:14 +05:30
// v105 -> v106
NewMigration ( "add includes_all_repositories to teams" , addTeamIncludesAllRepositories ) ,
2019-11-10 14:52:19 +05:30
// v106 -> v107
NewMigration ( "add column `mode` to table watch" , addModeColumnToWatch ) ,
2019-11-11 20:45:29 +05:30
// v107 -> v108
NewMigration ( "Add template options to repository" , addTemplateToRepo ) ,
2019-11-12 14:03:34 +05:30
// v108 -> v109
NewMigration ( "Add comment_id on table notification" , addCommentIDOnNotification ) ,
2019-11-20 16:57:49 +05:30
// v109 -> v110
NewMigration ( "add can_create_org_repo to team" , addCanCreateOrgRepoColumnForTeam ) ,
2019-12-03 00:02:40 +05:30
// v110 -> v111
NewMigration ( "change review content type to text" , changeReviewContentToText ) ,
2019-12-04 06:38:56 +05:30
// v111 -> v112
NewMigration ( "update branch protection for can push and whitelist enable" , addBranchProtectionCanPushAndEnableWhitelist ) ,
2019-12-14 09:00:39 +05:30
// v112 -> v113
NewMigration ( "remove release attachments which repository deleted" , removeAttachmentMissedRepo ) ,
2019-12-16 11:50:25 +05:30
// v113 -> v114
NewMigration ( "new feature: change target branch of pull requests" , featureChangeTargetBranch ) ,
2019-12-19 15:19:48 +05:30
// v114 -> v115
NewMigration ( "Remove authentication credentials from stored URL" , sanitizeOriginalURL ) ,
2019-12-27 23:57:59 +05:30
// v115 -> v116
NewMigration ( "add user_id prefix to existing user avatar name" , renameExistingUserAvatarName ) ,
2019-12-28 02:00:58 +05:30
// v116 -> v117
NewMigration ( "Extend TrackedTimes" , extendTrackedTimes ) ,
2020-01-03 23:17:10 +05:30
// v117 -> v118
NewMigration ( "Add block on rejected reviews branch protection" , addBlockOnRejectedReviews ) ,
2020-01-09 07:17:45 +05:30
// v118 -> v119
NewMigration ( "Add commit id and stale to reviews" , addReviewCommitAndStale ) ,
2020-01-10 21:05:17 +05:30
// v119 -> v120
NewMigration ( "Fix migrated repositories' git service type" , fixMigratedRepositoryServiceType ) ,
2020-01-12 15:06:21 +05:30
// v120 -> v121
NewMigration ( "Add owner_name on table repository" , addOwnerNameOnRepository ) ,
2020-01-13 23:03:46 +05:30
// v121 -> v122
NewMigration ( "add is_restricted column for users table" , addIsRestricted ) ,
2020-01-15 14:02:57 +05:30
// v122 -> v123
NewMigration ( "Add Require Signed Commits to ProtectedBranch" , addRequireSignedCommits ) ,
2020-01-15 16:44:07 +05:30
// v123 -> v124
NewMigration ( "Add original informations for reactions" , addReactionOriginals ) ,
2020-01-20 03:57:44 +05:30
// v124 -> v125
NewMigration ( "Add columns to user and repository" , addUserRepoMissingColumns ) ,
2020-01-23 22:58:15 +05:30
// v125 -> v126
NewMigration ( "Add some columns on review for migration" , addReviewMigrateInfo ) ,
2020-01-31 12:27:19 +05:30
// v126 -> v127
NewMigration ( "Fix topic repository count" , fixTopicRepositoryCount ) ,
2020-02-11 15:04:17 +05:30
// v127 -> v128
NewMigration ( "add repository code language statistics" , addLanguageStats ) ,
2015-01-23 13:24:16 +05:30
}
2015-01-22 18:19:52 +05:30
// Migrate database to current version
func Migrate ( x * xorm . Engine ) error {
2015-01-22 18:31:45 +05:30
if err := x . Sync ( new ( Version ) ) ; err != nil {
2015-02-12 08:28:37 +05:30
return fmt . Errorf ( "sync: %v" , err )
2015-01-22 18:31:45 +05:30
}
2015-01-22 18:19:52 +05:30
2016-07-16 07:38:04 +05:30
currentVersion := & Version { ID : 1 }
2015-01-22 18:19:52 +05:30
has , err := x . Get ( currentVersion )
if err != nil {
2015-02-12 08:28:37 +05:30
return fmt . Errorf ( "get: %v" , err )
2015-01-22 18:26:50 +05:30
} else if ! has {
2015-12-11 06:22:06 +05:30
// If the version record does not exist we think
// it is a fresh installation and we can skip all migrations.
2016-12-24 07:07:35 +05:30
currentVersion . ID = 0
2016-11-28 21:14:17 +05:30
currentVersion . Version = int64 ( minDBVersion + len ( migrations ) )
2015-01-23 13:24:16 +05:30
2015-01-22 18:26:50 +05:30
if _ , err = x . InsertOne ( currentVersion ) ; err != nil {
2015-02-12 08:28:37 +05:30
return fmt . Errorf ( "insert: %v" , err )
2015-01-22 18:26:50 +05:30
}
2015-01-22 18:19:52 +05:30
}
v := currentVersion . Version
2016-11-28 21:14:17 +05:30
if minDBVersion > v {
2019-04-02 13:18:31 +05:30
log . Fatal ( ` Gitea no longer supports auto - migration from your previously installed version .
2020-01-31 19:12:45 +05:30
Please try upgrading to a lower version first ( suggested v1 .6 .4 ) , then upgrade to this version . ` )
2015-11-25 19:57:27 +05:30
return nil
}
2016-11-28 21:14:17 +05:30
if int ( v - minDBVersion ) > len ( migrations ) {
2016-12-28 14:03:21 +05:30
// User downgraded Gitea.
2016-11-28 21:14:17 +05:30
currentVersion . Version = int64 ( len ( migrations ) + minDBVersion )
2017-10-05 10:13:04 +05:30
_ , err = x . ID ( 1 ) . Update ( currentVersion )
2015-08-11 20:54:40 +05:30
return err
2015-08-10 20:29:12 +05:30
}
2016-11-28 21:14:17 +05:30
for i , m := range migrations [ v - minDBVersion : ] {
2019-05-06 05:12:29 +05:30
log . Info ( "Migration[%d]: %s" , v + int64 ( i ) , m . Description ( ) )
2015-02-12 09:40:30 +05:30
if err = m . Migrate ( x ) ; err != nil {
return fmt . Errorf ( "do migrate: %v" , err )
2015-01-22 18:19:52 +05:30
}
currentVersion . Version = v + int64 ( i ) + 1
2017-10-05 10:13:04 +05:30
if _ , err = x . ID ( 1 ) . Update ( currentVersion ) ; err != nil {
2015-01-22 18:31:45 +05:30
return err
}
2015-01-22 18:19:52 +05:30
}
return nil
}
2018-05-09 21:59:04 +05:30
func dropTableColumns ( sess * xorm . Session , tableName string , columnNames ... string ) ( err error ) {
2018-03-07 12:14:12 +05:30
if tableName == "" || len ( columnNames ) == 0 {
return nil
}
2019-07-02 00:56:59 +05:30
// TODO: This will not work if there are foreign keys
2018-03-07 12:14:12 +05:30
switch {
2019-08-24 14:54:45 +05:30
case setting . Database . UseSQLite3 :
2019-07-02 00:56:59 +05:30
// 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" ] )
2019-08-06 03:19:49 +05:30
// Separate out the column definitions
2019-07-02 00:56:59 +05:30
tableSQL = tableSQL [ strings . Index ( tableSQL , "(" ) : ]
2019-08-06 03:19:49 +05:30
// Remove the required columnNames
2019-07-02 00:56:59 +05:30
for _ , name := range columnNames {
2019-08-06 03:19:49 +05:30
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 += ")"
2019-07-02 00:56:59 +05:30
}
2019-08-06 03:19:49 +05:30
// Find all the columns in the table
2019-07-02 00:56:59 +05:30
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
}
2019-08-24 14:54:45 +05:30
case setting . Database . UsePostgreSQL :
2019-07-02 00:56:59 +05:30
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 )
}
2019-08-24 14:54:45 +05:30
case setting . Database . UseMySQL :
2019-07-02 00:56:59 +05:30
// 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" ]
2019-10-12 10:25:07 +05:30
if len ( indexName ) > 0 {
_ , err := sess . Exec ( fmt . Sprintf ( "DROP INDEX `%s` ON `%s`" , indexName , tableName ) )
if err != nil {
return err
}
2019-07-02 00:56:59 +05:30
}
}
// Now drop the columns
2018-03-07 12:14:12 +05:30
cols := ""
for _ , col := range columnNames {
if cols != "" {
cols += ", "
}
cols += "DROP COLUMN `" + col + "`"
}
2018-05-09 21:59:04 +05:30
if _ , err := sess . Exec ( fmt . Sprintf ( "ALTER TABLE `%s` %s" , tableName , cols ) ) ; err != nil {
2018-03-07 12:14:12 +05:30
return fmt . Errorf ( "Drop table `%s` columns %v: %v" , tableName , columnNames , err )
}
2019-08-24 14:54:45 +05:30
case setting . Database . UseMSSQL :
2018-03-07 12:14:12 +05:30
cols := ""
for _ , col := range columnNames {
if cols != "" {
cols += ", "
}
cols += "`" + strings . ToLower ( col ) + "`"
}
sql := fmt . Sprintf ( "SELECT Name FROM SYS.DEFAULT_CONSTRAINTS WHERE PARENT_OBJECT_ID = OBJECT_ID('%[1]s') AND PARENT_COLUMN_ID IN (SELECT column_id FROM sys.columns WHERE lower(NAME) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))" ,
tableName , strings . Replace ( cols , "`" , "'" , - 1 ) )
constraints := make ( [ ] string , 0 )
if err := sess . SQL ( sql ) . Find ( & constraints ) ; err != nil {
sess . Rollback ( )
return fmt . Errorf ( "Find constraints: %v" , err )
}
for _ , constraint := range constraints {
if _ , err := sess . Exec ( fmt . Sprintf ( "ALTER TABLE `%s` DROP CONSTRAINT `%s`" , tableName , constraint ) ) ; err != nil {
sess . Rollback ( )
return fmt . Errorf ( "Drop table `%s` constraint `%s`: %v" , tableName , constraint , err )
}
}
if _ , err := sess . Exec ( fmt . Sprintf ( "ALTER TABLE `%s` DROP COLUMN %s" , tableName , cols ) ) ; err != nil {
sess . Rollback ( )
return fmt . Errorf ( "Drop table `%s` columns %v: %v" , tableName , columnNames , err )
}
return sess . Commit ( )
default :
2019-04-02 13:18:31 +05:30
log . Fatal ( "Unrecognized DB" )
2018-03-07 12:14:12 +05:30
}
return nil
}