2015-02-11 21:58:37 -05:00
// Copyright 2015 The Gogs Authors. All rights reserved.
2019-02-18 17:00:27 +01:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2015-02-11 21:58:37 -05:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2015-01-22 14:49:52 +02:00
package migrations
import (
2021-01-06 23:11:23 +08:00
"context"
2015-02-11 21:58:37 -05:00
"fmt"
2020-10-12 16:35:56 +02:00
"os"
2020-09-06 22:52:01 +01:00
"reflect"
2019-07-01 20:26:59 +01:00
"regexp"
2015-01-23 09:54:16 +02:00
"strings"
2015-01-22 14:56:50 +02:00
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2019-08-23 09:40:30 -07:00
2019-10-17 17:26:49 +08:00
"xorm.io/xorm"
2021-01-30 15:24:25 +00:00
"xorm.io/xorm/names"
2021-01-06 23:11:23 +08:00
"xorm.io/xorm/schemas"
2015-01-22 14:49:52 +02:00
)
2020-01-31 10:42:45 -03:00
const minDBVersion = 70 // Gitea 1.5.3
2015-02-11 21:58:37 -05:00
2016-11-28 23:44:17 +08:00
// Migration describes on migration from lower version to high version
2015-02-11 23:10:30 -05:00
type Migration interface {
Description ( ) string
Migrate ( * xorm . Engine ) error
}
type migration struct {
description string
migrate func ( * xorm . Engine ) error
}
2016-11-28 23:44:17 +08:00
// NewMigration creates a new migration
2015-02-11 23:10:30 -05:00
func NewMigration ( desc string , fn func ( * xorm . Engine ) error ) Migration {
return & migration { desc , fn }
}
2016-11-28 23:44:17 +08:00
// Description returns the migration's description
2015-02-11 23:10:30 -05:00
func ( m * migration ) Description ( ) string {
return m . description
}
2016-11-28 23:44:17 +08:00
// Migrate executes the migration
2015-02-11 23:10:30 -05:00
func ( m * migration ) Migrate ( x * xorm . Engine ) error {
return m . migrate ( x )
}
2015-01-22 14:49:52 +02:00
2016-11-28 23:44:17 +08:00
// Version describes the version table. Should have only one row with id==1
2015-01-22 14:49:52 +02:00
type Version struct {
2016-07-16 10:08:04 +08:00
ID int64 ` xorm:"pk autoincr" `
2015-01-22 14:49:52 +02:00
Version int64
}
// This is a sequence of migrations. Add new migrations to the bottom of the list.
2015-02-12 12:46:21 -05:00
// If you want to "retire" a migration, remove it from the top of the list and
2016-11-28 23:44:17 +08:00
// update minDBVersion accordingly
2015-02-11 23:10:30 -05:00
var migrations = [ ] Migration {
2016-08-26 14:07:21 -07:00
2020-12-02 17:04:19 +01:00
// Gitea 1.5.0 ends at v69
2020-01-31 10:42:45 -03:00
2018-07-17 23:23:58 +02:00
// v70 -> v71
NewMigration ( "add issue_dependencies" , addIssueDependencies ) ,
2018-08-06 07:43:22 +03:00
// v71 -> v72
2018-07-27 08:54:50 -04:00
NewMigration ( "protect each scratch token" , addScratchHash ) ,
2018-08-06 07:43:22 +03:00
// v72 -> v73
NewMigration ( "add review" , addReview ) ,
2020-01-31 10:42:45 -03:00
2020-12-02 17:04:19 +01:00
// Gitea 1.6.0 ends at v73
2020-01-31 10:42:45 -03:00
2018-09-13 13:04:25 +01:00
// v73 -> v74
NewMigration ( "add must_change_password column for users table" , addMustChangePassword ) ,
2018-12-11 12:28:37 +01:00
// v74 -> v75
NewMigration ( "add approval whitelists to protected branches" , addApprovalWhitelistsToProtectedBranches ) ,
2018-12-19 00:26:26 +08:00
// v75 -> v76
NewMigration ( "clear nonused data which not deleted when user was deleted" , clearNonusedData ) ,
2020-01-31 10:42:45 -03:00
2020-12-02 17:04:19 +01:00
// Gitea 1.7.0 ends at v76
2020-01-31 10:42:45 -03:00
2018-12-27 11:27:08 +01:00
// v76 -> v77
NewMigration ( "add pull request rebase with merge commit" , addPullRequestRebaseWithMerge ) ,
2019-01-09 18:22:57 +01:00
// v77 -> v78
NewMigration ( "add theme to users" , addUserDefaultTheme ) ,
2019-01-18 00:01:04 +00:00
// v78 -> v79
NewMigration ( "rename repo is_bare to repo is_empty" , renameRepoIsBareToIsEmpty ) ,
2019-02-10 20:27:19 +01:00
// v79 -> v80
NewMigration ( "add can close issues via commit in any branch" , addCanCloseIssuesViaCommitInAnyBranch ) ,
2019-02-18 21:55:04 +01:00
// v80 -> v81
NewMigration ( "add is locked to issues" , addIsLockedToIssues ) ,
2019-03-05 02:34:52 +00:00
// v81 -> v82
NewMigration ( "update U2F counter type" , changeU2FCounterType ) ,
2020-01-31 10:42:45 -03:00
2020-12-02 17:04:19 +01:00
// Gitea 1.8.0 ends at v82
2020-01-31 10:42:45 -03:00
2019-03-11 11:44:58 +08:00
// v82 -> v83
NewMigration ( "hot fix for wrong release sha1 on release table" , fixReleaseSha1OnReleaseTable ) ,
2019-04-03 03:25:05 +08:00
// v83 -> v84
NewMigration ( "add uploader id for table attachment" , addUploaderIDForAttachment ) ,
2019-04-14 18:43:56 +02:00
// v84 -> v85
NewMigration ( "add table to store original imported gpg keys" , addGPGKeyImport ) ,
2019-05-04 11:45:34 -04:00
// v85 -> v86
NewMigration ( "hash application token" , hashAppToken ) ,
2019-05-05 20:09:02 +02:00
// v86 -> v87
NewMigration ( "add http method to webhook" , addHTTPMethodToWebhook ) ,
2019-05-30 05:22:26 +03:00
// v87 -> v88
NewMigration ( "add avatar field to repository" , addAvatarFieldToRepository ) ,
2020-01-31 10:42:45 -03:00
2020-12-02 17:04:19 +01:00
// Gitea 1.9.0 ends at v88
2020-01-31 10:42:45 -03:00
2019-06-30 15:57:59 +08:00
// v88 -> v89
NewMigration ( "add commit status context field to commit_status" , addCommitStatusContext ) ,
2019-07-07 22:14:12 -04:00
// v89 -> v90
NewMigration ( "add original author/url migration info to issues, comments, and repo " , addOriginalMigrationInfo ) ,
2019-07-29 11:29:42 +08:00
// v90 -> v91
NewMigration ( "change length of some repository columns" , changeSomeColumnsLengthOfRepo ) ,
2019-08-05 22:29:40 +08:00
// v91 -> v92
NewMigration ( "add index on owner_id of repository and type, review_id of comment" , addIndexOnRepositoryAndComment ) ,
2019-08-06 05:57:55 -03:00
// v92 -> v93
NewMigration ( "remove orphaned repository index statuses" , removeLingeringIndexStatus ) ,
2019-08-29 14:05:42 +00:00
// v93 -> v94
NewMigration ( "add email notification enabled preference to user" , addEmailNotificationEnabledToUser ) ,
2019-09-18 13:39:45 +08:00
// v94 -> v95
NewMigration ( "add enable_status_check, status_check_contexts to protected_branch" , addStatusCheckColumnsForProtectedBranches ) ,
2019-09-20 02:45:38 -03:00
// v95 -> v96
NewMigration ( "add table columns for cross referencing issues" , addCrossReferenceColumns ) ,
2019-09-22 10:05:48 +01:00
// v96 -> v97
NewMigration ( "delete orphaned attachments" , deleteOrphanedAttachments ) ,
2019-09-23 22:08:03 +02:00
// v97 -> v98
NewMigration ( "add repo_admin_change_team_access to user" , addRepoAdminChangeTeamAccessColumnForUser ) ,
2019-10-05 19:09:27 +08:00
// v98 -> v99
NewMigration ( "add original author name and id on migrated release" , addOriginalAuthorOnMigratedReleases ) ,
2019-10-13 21:23:14 +08:00
// v99 -> v100
NewMigration ( "add task table and status column for repository table" , addTaskTable ) ,
2019-10-14 14:10:42 +08:00
// v100 -> v101
NewMigration ( "update migration repositories' service type" , updateMigrationServiceTypes ) ,
2019-10-17 23:58:36 -07:00
// v101 -> v102
2020-12-08 23:24:23 +00:00
NewMigration ( "change length of some external login users columns" , changeSomeColumnsLengthOfExternalLoginUser ) ,
2020-12-02 17:04:19 +01:00
// Gitea 1.10.0 ends at v102
2019-10-18 19:13:31 +08:00
// v102 -> v103
NewMigration ( "update migration repositories' service type" , dropColumnHeadUserNameOnPullRequest ) ,
2019-10-21 09:21:45 +01:00
// v103 -> v104
NewMigration ( "Add WhitelistDeployKeys to protected branch" , addWhitelistDeployKeysToBranches ) ,
2019-10-23 08:48:32 -03:00
// v104 -> v105
NewMigration ( "remove unnecessary columns from label" , removeLabelUneededCols ) ,
2019-11-06 10:37:14 +01:00
// v105 -> v106
NewMigration ( "add includes_all_repositories to teams" , addTeamIncludesAllRepositories ) ,
2019-11-10 06:22:19 -03:00
// v106 -> v107
NewMigration ( "add column `mode` to table watch" , addModeColumnToWatch ) ,
2019-11-11 09:15:29 -06:00
// v107 -> v108
NewMigration ( "Add template options to repository" , addTemplateToRepo ) ,
2019-11-12 16:33:34 +08:00
// v108 -> v109
NewMigration ( "Add comment_id on table notification" , addCommentIDOnNotification ) ,
2019-11-20 12:27:49 +01:00
// v109 -> v110
NewMigration ( "add can_create_org_repo to team" , addCanCreateOrgRepoColumnForTeam ) ,
2019-12-02 19:32:40 +01:00
// v110 -> v111
NewMigration ( "change review content type to text" , changeReviewContentToText ) ,
2019-12-04 02:08:56 +01:00
// v111 -> v112
NewMigration ( "update branch protection for can push and whitelist enable" , addBranchProtectionCanPushAndEnableWhitelist ) ,
2019-12-14 11:30:39 +08:00
// v112 -> v113
NewMigration ( "remove release attachments which repository deleted" , removeAttachmentMissedRepo ) ,
2019-12-16 07:20:25 +01:00
// v113 -> v114
NewMigration ( "new feature: change target branch of pull requests" , featureChangeTargetBranch ) ,
2019-12-19 04:49:48 -05:00
// v114 -> v115
NewMigration ( "Remove authentication credentials from stored URL" , sanitizeOriginalURL ) ,
2019-12-28 00:27:59 +06:00
// v115 -> v116
NewMigration ( "add user_id prefix to existing user avatar name" , renameExistingUserAvatarName ) ,
2019-12-27 21:30:58 +01:00
// v116 -> v117
2020-12-08 23:24:23 +00:00
NewMigration ( "Extend TrackedTimes" , extendTrackedTimes ) ,
2020-12-02 17:04:19 +01:00
// Gitea 1.11.0 ends at v117
2020-01-03 18:47:10 +01:00
// v117 -> v118
NewMigration ( "Add block on rejected reviews branch protection" , addBlockOnRejectedReviews ) ,
2020-01-09 02:47:45 +01:00
// v118 -> v119
NewMigration ( "Add commit id and stale to reviews" , addReviewCommitAndStale ) ,
2020-01-10 23:35:17 +08:00
// v119 -> v120
NewMigration ( "Fix migrated repositories' git service type" , fixMigratedRepositoryServiceType ) ,
2020-01-12 17:36:21 +08:00
// v120 -> v121
NewMigration ( "Add owner_name on table repository" , addOwnerNameOnRepository ) ,
2020-01-13 19:33:46 +02:00
// v121 -> v122
NewMigration ( "add is_restricted column for users table" , addIsRestricted ) ,
2020-01-15 08:32:57 +00:00
// v122 -> v123
NewMigration ( "Add Require Signed Commits to ProtectedBranch" , addRequireSignedCommits ) ,
2020-01-15 19:14:07 +08:00
// v123 -> v124
NewMigration ( "Add original informations for reactions" , addReactionOriginals ) ,
2020-01-19 19:27:44 -03:00
// v124 -> v125
NewMigration ( "Add columns to user and repository" , addUserRepoMissingColumns ) ,
2020-01-24 01:28:15 +08:00
// v125 -> v126
NewMigration ( "Add some columns on review for migration" , addReviewMigrateInfo ) ,
2020-01-31 08:57:19 +02:00
// v126 -> v127
NewMigration ( "Fix topic repository count" , fixTopicRepositoryCount ) ,
2020-02-11 11:34:17 +02:00
// v127 -> v128
NewMigration ( "add repository code language statistics" , addLanguageStats ) ,
2020-03-05 09:18:07 +02:00
// v128 -> v129
NewMigration ( "fix merge base for pull requests" , fixMergeBase ) ,
2020-03-05 16:54:50 +01:00
// v129 -> v130
NewMigration ( "remove dependencies from deleted repositories" , purgeUnusedDependencies ) ,
2020-03-05 23:10:48 -06:00
// v130 -> v131
NewMigration ( "Expand webhooks for more granularity" , expandWebhooks ) ,
2020-03-08 22:08:05 +00:00
// v131 -> v132
NewMigration ( "Add IsSystemWebhook column to webhooks table" , addSystemWebhookColumn ) ,
2020-03-27 00:26:34 +02:00
// v132 -> v133
NewMigration ( "Add Branch Protection Protected Files Column" , addBranchProtectionProtectedFilesColumn ) ,
2020-03-27 12:34:39 +00:00
// v133 -> v134
NewMigration ( "Add EmailHash Table" , addEmailHashTable ) ,
2020-03-31 14:42:44 +01:00
// v134 -> v135
NewMigration ( "Refix merge base for merged pull requests" , refixMergeBase ) ,
2020-04-17 03:00:36 +02:00
// v135 -> v136
2020-04-01 00:14:46 -04:00
NewMigration ( "Add OrgID column to Labels table" , addOrgIDLabelColumn ) ,
2020-04-17 03:00:36 +02:00
// v136 -> v137
2020-04-14 15:53:34 +02:00
NewMigration ( "Add CommitsAhead and CommitsBehind Column to PullRequest Table" , addCommitDivergenceToPulls ) ,
2020-04-17 03:00:36 +02:00
// v137 -> v138
NewMigration ( "Add Branch Protection Block Outdated Branch" , addBlockOnOutdatedBranch ) ,
2020-04-18 21:50:25 +08:00
// v138 -> v139
NewMigration ( "Add ResolveDoerID to Comment table" , addResolveDoerIDCommentColumn ) ,
2020-05-15 00:55:43 +02:00
// v139 -> v140
2020-12-08 23:24:23 +00:00
NewMigration ( "prepend refs/heads/ to issue refs" , prependRefsHeadsToIssueRefs ) ,
2020-12-02 17:04:19 +01:00
// Gitea 1.12.0 ends at v140
2020-05-30 10:46:15 +03:00
// v140 -> v141
NewMigration ( "Save detected language file size to database instead of percent" , fixLanguageStatsToSaveSize ) ,
2020-06-11 21:18:11 +01:00
// v141 -> v142
2020-06-05 22:01:53 +02:00
NewMigration ( "Add KeepActivityPrivate to User table" , addKeepActivityPrivateUserColumn ) ,
2020-06-11 21:18:11 +01:00
// v142 -> v143
NewMigration ( "Ensure Repository.IsArchived is not null" , setIsArchivedToFalse ) ,
2020-07-08 03:16:34 +08:00
// v143 -> v144
NewMigration ( "recalculate Stars number for all user" , recalculateStars ) ,
2020-07-31 00:04:19 +02:00
// v144 -> v145
NewMigration ( "update Matrix Webhook http method to 'PUT'" , updateMatrixWebhookHTTPMethod ) ,
2020-08-04 14:54:29 +01:00
// v145 -> v146
NewMigration ( "Increase Language field to 50 in LanguageStats" , increaseLanguageField ) ,
2020-08-17 04:07:38 +01:00
// v146 -> v147
NewMigration ( "Add projects info to repository table" , addProjectsInfo ) ,
2020-08-21 08:53:14 +01:00
// v147 -> v148
NewMigration ( "create review for 0 review id code comments" , createReviewsForCodeComments ) ,
2020-09-04 03:36:56 +02:00
// v148 -> v149
NewMigration ( "remove issue dependency comments who refer to non existing issues" , purgeInvalidDependenciesComments ) ,
2020-09-05 19:38:54 +02:00
// v149 -> v150
NewMigration ( "Add Created and Updated to Milestone table" , addCreatedAndUpdatedToMilestones ) ,
2020-09-10 20:45:01 +01:00
// v150 -> v151
NewMigration ( "add primary key to repo_topic" , addPrimaryKeyToRepoTopic ) ,
2020-09-15 23:02:41 +01:00
// v151 -> v152
NewMigration ( "set default password algorithm to Argon2" , setDefaultPasswordToArgon2 ) ,
2020-09-19 17:44:55 +01:00
// v152 -> v153
NewMigration ( "add TrustModel field to Repository" , addTrustModelToRepository ) ,
2020-10-13 03:55:13 +08:00
// v153 > v154
NewMigration ( "add Team review request support" , addTeamReviewRequestSupport ) ,
2020-10-13 02:01:57 +02:00
// v154 > v155
2020-12-08 23:24:23 +00:00
NewMigration ( "add timestamps to Star, Label, Follow, Watch and Collaboration" , addTimeStamps ) ,
2020-12-02 17:04:19 +01:00
// Gitea 1.13.0 ends at v155
2020-10-14 02:50:57 +08:00
// v155 -> v156
NewMigration ( "add changed_protected_files column for pull_request table" , addChangedProtectedFilesPullRequestColumn ) ,
2020-10-22 01:55:25 +01:00
// v156 -> v157
NewMigration ( "fix publisher ID for tag releases" , fixPublisherIDforTagReleases ) ,
2020-10-24 15:11:30 +01:00
// v157 -> v158
NewMigration ( "ensure repo topics are up-to-date" , fixRepoTopics ) ,
2020-11-09 06:15:09 +00:00
// v158 -> v159
NewMigration ( "code comment replies should have the commitID of the review they are replying to" , updateCodeCommentReplies ) ,
2020-11-10 22:37:11 +00:00
// v159 -> v160
NewMigration ( "update reactions constraint" , updateReactionConstraint ) ,
2020-11-29 03:30:46 +08:00
// v160 -> v161
NewMigration ( "Add block on official review requests branch protection" , addBlockOnOfficialReviewRequests ) ,
2020-12-08 18:41:14 +08:00
// v161 -> v162
NewMigration ( "Convert task type from int to string" , convertTaskTypeToString ) ,
2020-12-10 01:20:13 +08:00
// v162 -> v163
NewMigration ( "Convert webhook task type from int to string" , convertWebhookTaskTypeToString ) ,
2020-12-27 07:28:47 +08:00
// v163 -> v164
NewMigration ( "Convert topic name from 25 to 50" , convertTopicNameFrom25To50 ) ,
2021-01-01 17:33:27 +01:00
// v164 -> v165
NewMigration ( "Add scope and nonce columns to oauth2_grant table" , addScopeAndNonceColumnsToOAuth2Grant ) ,
2021-01-06 23:11:23 +08:00
// v165 -> v166
NewMigration ( "Convert hook task type from char(16) to varchar(16) and trim the column" , convertHookTaskTypeToVarcharAndTrim ) ,
2021-01-10 19:05:18 +01:00
// v166 -> v167
NewMigration ( "Where Password is Valid with Empty String delete it" , recalculateUserEmptyPWD ) ,
2021-01-24 10:23:05 -05:00
// v167 -> v168
NewMigration ( "Add user redirect" , addUserRedirect ) ,
2021-01-28 23:58:33 +01:00
// v168 -> v169
NewMigration ( "Recreate user table to fix default values" , recreateUserTableToFixDefaultValues ) ,
2021-02-08 03:09:14 +00:00
// v169 -> v170
NewMigration ( "Update DeleteBranch comments to set the old_ref to the commit_sha" , commentTypeDeleteBranchUseOldRef ) ,
2021-02-12 01:32:25 +08:00
// v170 -> v171
NewMigration ( "Add Dismissed to Review table" , addDismissedReviewColumn ) ,
2021-02-12 12:01:26 +01:00
// v171 -> v172
NewMigration ( "Add Sorting to ProjectBoard table" , addSortingColToProjectBoard ) ,
2021-02-15 05:33:31 +00:00
// v172 -> v173
NewMigration ( "Add sessions table for go-chi/session" , addSessionTable ) ,
2021-02-19 10:52:11 +00:00
// v173 -> v174
NewMigration ( "Add time_id column to Comment" , addTimeIDCommentColumn ) ,
2021-03-01 01:47:30 +01:00
// v174 -> v175
NewMigration ( "create repo transfer table" , addRepoTransfer ) ,
2015-01-23 09:54:16 +02:00
}
2015-01-22 14:49:52 +02:00
2020-04-06 11:44:47 +01:00
// 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: %v" , err )
}
currentVersion := & Version { ID : 1 }
has , err := x . Get ( currentVersion )
if err != nil {
return - 1 , fmt . Errorf ( "get: %v" , 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 initialised" )
}
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 "gitea [--config /path/to/app.ini] migrate" to update the database version ` , currentDB , expected )
}
return nil
}
2015-01-22 14:49:52 +02:00
// Migrate database to current version
func Migrate ( x * xorm . Engine ) error {
2021-01-30 15:24:25 +00:00
// Set a new clean the default mapper to GonicMapper as that is the default for Gitea.
x . SetMapper ( names . GonicMapper { } )
2015-01-22 15:01:45 +02:00
if err := x . Sync ( new ( Version ) ) ; err != nil {
2015-02-11 21:58:37 -05:00
return fmt . Errorf ( "sync: %v" , err )
2015-01-22 15:01:45 +02:00
}
2015-01-22 14:49:52 +02:00
2016-07-16 10:08:04 +08:00
currentVersion := & Version { ID : 1 }
2015-01-22 14:49:52 +02:00
has , err := x . Get ( currentVersion )
if err != nil {
2015-02-11 21:58:37 -05:00
return fmt . Errorf ( "get: %v" , err )
2015-01-22 14:56:50 +02:00
} else if ! has {
2015-12-10 19:52:06 -05:00
// If the version record does not exist we think
// it is a fresh installation and we can skip all migrations.
2016-12-24 09:37:35 +08:00
currentVersion . ID = 0
2016-11-28 23:44:17 +08:00
currentVersion . Version = int64 ( minDBVersion + len ( migrations ) )
2015-01-23 09:54:16 +02:00
2015-01-22 14:56:50 +02:00
if _ , err = x . InsertOne ( currentVersion ) ; err != nil {
2015-02-11 21:58:37 -05:00
return fmt . Errorf ( "insert: %v" , err )
2015-01-22 14:56:50 +02:00
}
2015-01-22 14:49:52 +02:00
}
v := currentVersion . Version
2016-11-28 23:44:17 +08:00
if minDBVersion > v {
2019-04-02 08:48:31 +01:00
log . Fatal ( ` Gitea no longer supports auto-migration from your previously installed version.
2020-01-31 10:42:45 -03:00
Please try upgrading to a lower version first (suggested v1.6.4), then upgrade to this version. ` )
2015-11-25 09:27:27 -05:00
return nil
}
2020-10-13 02:41:49 +02:00
// Downgrading Gitea's database version not supported
2016-11-28 23:44:17 +08:00
if int ( v - minDBVersion ) > len ( migrations ) {
2020-10-13 02:41:49 +02:00
msg := fmt . Sprintf ( "Downgrading database version from '%d' to '%d' is not supported and may result in loss of data integrity.\nIf you really know what you're doing, execute `UPDATE version SET version=%d WHERE id=1;`\n" ,
2020-10-12 16:35:56 +02:00
v , minDBVersion + len ( migrations ) , minDBVersion + len ( migrations ) )
fmt . Fprint ( os . Stderr , msg )
log . Fatal ( msg )
return nil
2015-08-10 22:59:12 +08:00
}
2020-10-12 16:35:56 +02:00
// Migrate
2016-11-28 23:44:17 +08:00
for i , m := range migrations [ v - minDBVersion : ] {
2019-05-06 00:42:29 +01:00
log . Info ( "Migration[%d]: %s" , v + int64 ( i ) , m . Description ( ) )
2021-01-30 15:24:25 +00:00
// Reset the mapper between each migration - migrations are not supposed to depend on each other
x . SetMapper ( names . GonicMapper { } )
2015-02-11 23:10:30 -05:00
if err = m . Migrate ( x ) ; err != nil {
return fmt . Errorf ( "do migrate: %v" , err )
2015-01-22 14:49:52 +02:00
}
currentVersion . Version = v + int64 ( i ) + 1
2017-10-04 21:43:04 -07:00
if _ , err = x . ID ( 1 ) . Update ( currentVersion ) ; err != nil {
2015-01-22 15:01:45 +02:00
return err
}
2015-01-22 14:49:52 +02:00
}
return nil
}
2020-09-06 22:52:01 +01:00
// 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 ... interface { } ) 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 interface { } ) 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 )
}
if hasID && setting . Database . UseMSSQL {
if _ , err := sess . Exec ( fmt . Sprintf ( "SET IDENTITY_INSERT `%s` ON" , tempTableName ) ) ; err != nil {
log . Error ( "Unable to set identity insert for table %s. Error: %v" , tempTableName , err )
return err
}
}
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
}
if hasID && setting . Database . UseMSSQL {
if _ , err := sess . Exec ( fmt . Sprintf ( "SET IDENTITY_INSERT `%s` OFF" , tempTableName ) ) ; err != nil {
log . Error ( "Unable to switch off identity insert for table %s. Error: %v" , tempTableName , err )
return err
}
}
switch {
case setting . Database . UseSQLite3 :
// 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 . UseMySQL :
// 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
}
// 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
}
case setting . Database . UsePostgreSQL :
// 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
schema := sess . Engine ( ) . Dialect ( ) . URI ( ) . Schema
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
}
}
case setting . Database . UseMSSQL :
// MSSQL 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
}
// MSSQL sp_rename will move all the constraints from the temporary table to the new table
if _ , err := sess . Exec ( fmt . Sprintf ( "sp_rename `%s`,`%s`" , tempTableName , tableName ) ) ; err != nil {
log . Error ( "Unable to rename %s to %s. Error: %v" , tempTableName , tableName , err )
return err
}
default :
log . Fatal ( "Unrecognized DB" )
}
return nil
}
// WARNING: YOU MUST COMMIT THE SESSION AT THE END
2018-05-09 18:29:04 +02:00
func dropTableColumns ( sess * xorm . Session , tableName string , columnNames ... string ) ( err error ) {
2018-03-07 08:44:12 +02:00
if tableName == "" || len ( columnNames ) == 0 {
return nil
}
2019-07-01 20:26:59 +01:00
// TODO: This will not work if there are foreign keys
2018-03-07 08:44:12 +02:00
switch {
2019-08-24 17:24:45 +08:00
case setting . Database . UseSQLite3 :
2019-07-01 20:26:59 +01:00
// 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-05 22:49:49 +01:00
// Separate out the column definitions
2019-07-01 20:26:59 +01:00
tableSQL = tableSQL [ strings . Index ( tableSQL , "(" ) : ]
2019-08-05 22:49:49 +01:00
// Remove the required columnNames
2019-07-01 20:26:59 +01:00
for _ , name := range columnNames {
2019-08-05 22:49:49 +01:00
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-01 20:26:59 +01:00
}
2019-08-05 22:49:49 +01:00
// Find all the columns in the table
2019-07-01 20:26:59 +01:00
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 17:24:45 +08:00
case setting . Database . UsePostgreSQL :
2019-07-01 20:26:59 +01:00
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 17:24:45 +08:00
case setting . Database . UseMySQL :
2019-07-01 20:26:59 +01:00
// 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 01:55:07 -03:00
if len ( indexName ) > 0 {
_ , err := sess . Exec ( fmt . Sprintf ( "DROP INDEX `%s` ON `%s`" , indexName , tableName ) )
if err != nil {
return err
}
2019-07-01 20:26:59 +01:00
}
}
// Now drop the columns
2018-03-07 08:44:12 +02:00
cols := ""
for _ , col := range columnNames {
if cols != "" {
cols += ", "
}
cols += "DROP COLUMN `" + col + "`"
}
2018-05-09 18:29:04 +02:00
if _ , err := sess . Exec ( fmt . Sprintf ( "ALTER TABLE `%s` %s" , tableName , cols ) ) ; err != nil {
2018-03-07 08:44:12 +02:00
return fmt . Errorf ( "Drop table `%s` columns %v: %v" , tableName , columnNames , err )
}
2019-08-24 17:24:45 +08:00
case setting . Database . UseMSSQL :
2018-03-07 08:44:12 +02:00
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'))" ,
2020-10-11 22:27:20 +02:00
tableName , strings . ReplaceAll ( cols , "`" , "'" ) )
2018-03-07 08:44:12 +02:00
constraints := make ( [ ] string , 0 )
if err := sess . SQL ( sql ) . Find ( & constraints ) ; err != nil {
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 {
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 {
return fmt . Errorf ( "Drop table `%s` columns %v: %v" , tableName , columnNames , err )
}
default :
2019-04-02 08:48:31 +01:00
log . Fatal ( "Unrecognized DB" )
2018-03-07 08:44:12 +02:00
}
return nil
}
2021-01-06 23:11:23 +08:00
// modifyColumn will modify column's type or other propertity. SQLITE is not supported
func modifyColumn ( x * xorm . Engine , tableName string , col * schemas . Column ) error {
var indexes map [ string ] * schemas . Index
var err error
// MSSQL have to remove index at first, otherwise alter column will fail
// ref. https://sqlzealots.com/2018/05/09/error-message-the-index-is-dependent-on-column-alter-table-alter-column-failed-because-one-or-more-objects-access-this-column/
if x . Dialect ( ) . URI ( ) . DBType == schemas . MSSQL {
indexes , err = x . Dialect ( ) . GetIndexes ( x . DB ( ) , context . Background ( ) , tableName )
if err != nil {
return err
}
for _ , index := range indexes {
_ , err = x . Exec ( x . Dialect ( ) . DropIndexSQL ( tableName , index ) )
if err != nil {
return err
}
}
}
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
}