mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-26 05:19:44 +09:00
refactor: prepare to decouple the "model migration" package and "models" package (#38533)
Migrations should never use model structs directly, because the model structs can be different in different releases. e.g. if one migration uses "User" model, it works in the early releases, then one day, when the User model changes, the migration breaks because it will use the new (incorrect) User model, it should only use the old User model. The same to "modules/structs". --------- Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: delvh <dev.lh@web.de>
This commit is contained in:
@@ -0,0 +1,521 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// Migrations should never use model structs directly, because the model structs can be different in different releases.
|
||||
// e.g. if one migration uses "User" model, it works in the early releases, then one day,
|
||||
// when the User model changes, the migration breaks because it will use the new (incorrect) User model,
|
||||
// it should only use the old User model. The same to "modules/structs".
|
||||
// However, many the existing migrations already abuses "modules/structs" (search "gitea.dev/models/" in the migrations).
|
||||
// TODO: need to fully decouple the migration package and models & structs package
|
||||
type (
|
||||
EngineMigration = db.EngineMigration
|
||||
Session = db.Session
|
||||
)
|
||||
|
||||
// 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(EngineMigration) error {
|
||||
return func(x EngineMigration) 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 Session, bean any) error {
|
||||
// TODO: This will not work if there are foreign keys
|
||||
|
||||
tableName := sess.Engine().TableName(bean)
|
||||
tempTableName := "tmp_recreate__" + 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 errors.New("no columns in new table")
|
||||
}
|
||||
hasID := false
|
||||
for _, column := range newTableColumns {
|
||||
hasID = hasID || (column.IsPrimaryKey && column.IsAutoIncrement)
|
||||
}
|
||||
|
||||
if hasID && setting.Database.Type.IsMSSQL() {
|
||||
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.Type.IsMSSQL() {
|
||||
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.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
|
||||
}
|
||||
}
|
||||
}
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
// 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
|
||||
func DropTableColumns(sess 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: %w", 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: %w", tableName, columnNames, err)
|
||||
}
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
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.ReplaceAll(cols, "`", "'"))
|
||||
constraints := make([]string, 0)
|
||||
if err := sess.SQL(sql).Find(&constraints); err != nil {
|
||||
return fmt.Errorf("find constraints: %w", 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` default constraint `%s`: %w", tableName, constraint, err)
|
||||
}
|
||||
}
|
||||
sql = fmt.Sprintf("SELECT DISTINCT Name FROM sys.indexes INNER JOIN sys.index_columns ON indexes.index_id = index_columns.index_id AND indexes.object_id = index_columns.object_id WHERE indexes.object_id = OBJECT_ID('%[1]s') AND index_columns.column_id IN (SELECT column_id FROM sys.columns WHERE LOWER(name) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
|
||||
tableName, strings.ReplaceAll(cols, "`", "'"))
|
||||
constraints = make([]string, 0)
|
||||
if err := sess.SQL(sql).Find(&constraints); err != nil {
|
||||
return fmt.Errorf("find constraints: %w", err)
|
||||
}
|
||||
for _, constraint := range constraints {
|
||||
if _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%[2]s` ON `%[1]s`", tableName, constraint)); err != nil {
|
||||
return fmt.Errorf("drop index `%[2]s` on `%[1]s`: %[3]w", 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: %w", 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 EngineMigration, 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
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package base_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm/names"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
migrationtest.MainTest(m)
|
||||
}
|
||||
|
||||
func Test_DropTableColumns(t *testing.T) {
|
||||
x, deferable := migrationtest.PrepareTestEnv(t, 0)
|
||||
defer deferable()
|
||||
// FIXME: this logic seems wrong. Need to add an assertion here in the future, but it seems causing failure.
|
||||
if x == nil || t.Failed() {
|
||||
t.Skip("PrepareTestEnv did not yield a usable engine")
|
||||
}
|
||||
|
||||
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 := base.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 := base.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
-
|
||||
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
|
||||
|
||||
-
|
||||
id: 3
|
||||
uid: 2
|
||||
issue_id: 1 # duplicated with id 2
|
||||
is_read: false
|
||||
is_mentioned: true
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-
|
||||
id: 1
|
||||
+9
@@ -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: ""
|
||||
@@ -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}'
|
||||
@@ -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: ''
|
||||
@@ -0,0 +1,4 @@
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
index: 1
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
@@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
@@ -0,0 +1,9 @@
|
||||
-
|
||||
id: 1
|
||||
project_id: 1
|
||||
issue_id: 1
|
||||
|
||||
-
|
||||
id: 2
|
||||
project_id: 1
|
||||
issue_id: 1
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,29 @@
|
||||
# type ActionRun struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# RepoID int64 `xorm:"index"`
|
||||
# Index int64
|
||||
# CommitSHA string `xorm:"commit_sha"`
|
||||
# Event string
|
||||
# TriggerEvent string
|
||||
# EventPayload string `xorm:"LONGTEXT"`
|
||||
# }
|
||||
-
|
||||
id: 990
|
||||
repo_id: 100
|
||||
index: 7
|
||||
commit_sha: merge-sha
|
||||
event: pull_request
|
||||
event_payload: '{"pull_request":{"head":{"sha":"sha-shared"}}}'
|
||||
-
|
||||
id: 991
|
||||
repo_id: 100
|
||||
index: 8
|
||||
commit_sha: sha-shared
|
||||
event: push
|
||||
event_payload: '{"head_commit":{"id":"sha-shared"}}'
|
||||
-
|
||||
id: 1991
|
||||
repo_id: 100
|
||||
index: 9
|
||||
commit_sha: sha-other
|
||||
event: release
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# type ActionRunJob struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# RunID int64 `xorm:"index"`
|
||||
# }
|
||||
-
|
||||
id: 997
|
||||
run_id: 990
|
||||
-
|
||||
id: 998
|
||||
run_id: 990
|
||||
-
|
||||
id: 1997
|
||||
run_id: 991
|
||||
-
|
||||
id: 1998
|
||||
run_id: 1991
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# type CommitStatus struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# RepoID int64 `xorm:"index"`
|
||||
# SHA string
|
||||
# TargetURL string
|
||||
# }
|
||||
-
|
||||
id: 10010
|
||||
repo_id: 100
|
||||
sha: sha-shared
|
||||
target_url: /testuser/repo1/actions/runs/7/jobs/0
|
||||
-
|
||||
id: 10011
|
||||
repo_id: 100
|
||||
sha: sha-shared
|
||||
target_url: /testuser/repo1/actions/runs/7/jobs/1
|
||||
-
|
||||
id: 10012
|
||||
repo_id: 100
|
||||
sha: sha-shared
|
||||
target_url: /testuser/repo1/actions/runs/8/jobs/0
|
||||
-
|
||||
id: 10013
|
||||
repo_id: 100
|
||||
sha: sha-other
|
||||
target_url: /testuser/repo1/actions/runs/9/jobs/0
|
||||
-
|
||||
id: 10014
|
||||
repo_id: 100
|
||||
sha: sha-shared
|
||||
target_url: /otheruser/badrepo/actions/runs/7/jobs/0
|
||||
-
|
||||
id: 10015
|
||||
repo_id: 100
|
||||
sha: sha-shared
|
||||
target_url: /testuser/repo1/actions/runs/10/jobs/0
|
||||
-
|
||||
id: 10016
|
||||
repo_id: 100
|
||||
sha: sha-shared
|
||||
target_url: /testuser/repo1/actions/runs/7/jobs/3
|
||||
-
|
||||
id: 10017
|
||||
repo_id: 100
|
||||
sha: sha-shared
|
||||
target_url: https://ci.example.com/build/123
|
||||
-
|
||||
id: 10018
|
||||
repo_id: 100
|
||||
sha: sha-shared
|
||||
target_url: /testuser/repo1/actions/runs/990/jobs/997
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# type CommitStatusSummary struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# RepoID int64 `xorm:"index"`
|
||||
# SHA string `xorm:"VARCHAR(64) NOT NULL"`
|
||||
# State string `xorm:"VARCHAR(7) NOT NULL"`
|
||||
# TargetURL string
|
||||
# }
|
||||
-
|
||||
id: 10020
|
||||
repo_id: 100
|
||||
sha: sha-shared
|
||||
state: pending
|
||||
target_url: /testuser/repo1/actions/runs/7/jobs/0
|
||||
-
|
||||
id: 10021
|
||||
repo_id: 100
|
||||
sha: sha-other
|
||||
state: pending
|
||||
target_url: /testuser/repo1/actions/runs/9/jobs/0
|
||||
@@ -0,0 +1,9 @@
|
||||
# type Repository struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# OwnerName string
|
||||
# Name string
|
||||
# }
|
||||
-
|
||||
id: 100
|
||||
owner_name: testuser
|
||||
name: repo1
|
||||
@@ -0,0 +1,12 @@
|
||||
-
|
||||
id: 1
|
||||
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||
-
|
||||
id: 2
|
||||
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||
-
|
||||
id: 3
|
||||
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||
-
|
||||
id: 4
|
||||
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||
@@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
context_hash: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||
@@ -0,0 +1,5 @@
|
||||
-
|
||||
id: 1
|
||||
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||
merge_base: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||
merged_commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||
@@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
sha1: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||
@@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||
@@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||
@@ -0,0 +1,11 @@
|
||||
# type Repository struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# }
|
||||
-
|
||||
id: 1
|
||||
-
|
||||
id: 2
|
||||
-
|
||||
id: 3
|
||||
-
|
||||
id: 10
|
||||
@@ -0,0 +1,5 @@
|
||||
-
|
||||
id: 1
|
||||
user_id: 1
|
||||
pull_id: 1
|
||||
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
-
|
||||
id: 1
|
||||
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||
-
|
||||
id: 2
|
||||
credential_id: "051CLMMKB62S6M9M2A4H54K7MMCQALFJ36G4TGB2S9A47APLTILU6C6744CEBG4EKCGV357N21BSLH8JD33GQMFAR6DQ70S76P34J6FR="
|
||||
-
|
||||
id: 4
|
||||
credential_id: "APU4B1NDTEVTEM60V4T0FRL7SRJMO9KIE2AKFQ8JDGTQ7VHFI41FDEFTDLBVQEAE4ER49QV2GTGVFDNBO31BPOA3OQN6879OT6MTU3G="
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
@@ -0,0 +1,4 @@
|
||||
-
|
||||
id: 1
|
||||
description: the badge
|
||||
image_url: https://gitea.com/myimage.png
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,556 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package modelmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modelmigration/v1_10"
|
||||
"gitea.dev/modelmigration/v1_11"
|
||||
"gitea.dev/modelmigration/v1_12"
|
||||
"gitea.dev/modelmigration/v1_13"
|
||||
"gitea.dev/modelmigration/v1_14"
|
||||
"gitea.dev/modelmigration/v1_15"
|
||||
"gitea.dev/modelmigration/v1_16"
|
||||
"gitea.dev/modelmigration/v1_17"
|
||||
"gitea.dev/modelmigration/v1_18"
|
||||
"gitea.dev/modelmigration/v1_19"
|
||||
"gitea.dev/modelmigration/v1_20"
|
||||
"gitea.dev/modelmigration/v1_21"
|
||||
"gitea.dev/modelmigration/v1_22"
|
||||
"gitea.dev/modelmigration/v1_23"
|
||||
"gitea.dev/modelmigration/v1_24"
|
||||
"gitea.dev/modelmigration/v1_25"
|
||||
"gitea.dev/modelmigration/v1_26"
|
||||
"gitea.dev/modelmigration/v1_27"
|
||||
"gitea.dev/modelmigration/v1_6"
|
||||
"gitea.dev/modelmigration/v1_7"
|
||||
"gitea.dev/modelmigration/v1_8"
|
||||
"gitea.dev/modelmigration/v1_9"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
"xorm.io/xorm/names"
|
||||
)
|
||||
|
||||
const minDBVersion = 70 // Gitea 1.5.3
|
||||
|
||||
type migration struct {
|
||||
idNumber int64 // DB version is "the last migration's idNumber" + 1
|
||||
description string
|
||||
migrate func(context.Context, base.EngineMigration) error
|
||||
}
|
||||
|
||||
// newMigration creates a new migration
|
||||
func newMigration[T func(base.EngineMigration) error | func(context.Context, base.EngineMigration) error](idNumber int64, desc string, fn T) *migration {
|
||||
m := &migration{idNumber: idNumber, description: desc}
|
||||
var ok bool
|
||||
if m.migrate, ok = any(fn).(func(context.Context, base.EngineMigration) error); !ok {
|
||||
m.migrate = func(ctx context.Context, x base.EngineMigration) error {
|
||||
return any(fn).(func(base.EngineMigration) error)(x)
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Migrate executes the migration
|
||||
func (m *migration) Migrate(ctx context.Context, x base.EngineMigration) error {
|
||||
return m.migrate(ctx, x)
|
||||
}
|
||||
|
||||
// Version describes the version table. Should have only one row with id==1
|
||||
type Version struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Version int64 // DB version is "the last migration's idNumber" + 1
|
||||
}
|
||||
|
||||
// Use noopMigration when there is a migration that has been no-oped
|
||||
var noopMigration = func(_ base.EngineMigration) error { return nil }
|
||||
|
||||
var preparedMigrations []*migration
|
||||
|
||||
// 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
|
||||
func prepareMigrationTasks() []*migration {
|
||||
if preparedMigrations != nil {
|
||||
return preparedMigrations
|
||||
}
|
||||
preparedMigrations = []*migration{
|
||||
// Gitea 1.5.0 ends at database version 69
|
||||
|
||||
newMigration(70, "add issue_dependencies", v1_6.AddIssueDependencies),
|
||||
newMigration(71, "protect each scratch token", v1_6.AddScratchHash),
|
||||
newMigration(72, "add review", v1_6.AddReview),
|
||||
|
||||
// Gitea 1.6.0 ends at database version 73
|
||||
|
||||
newMigration(73, "add must_change_password column for users table", v1_7.AddMustChangePassword),
|
||||
newMigration(74, "add approval whitelists to protected branches", v1_7.AddApprovalWhitelistsToProtectedBranches),
|
||||
newMigration(75, "clear nonused data which not deleted when user was deleted", v1_7.ClearNonusedData),
|
||||
|
||||
// Gitea 1.7.0 ends at database version 76
|
||||
|
||||
newMigration(76, "add pull request rebase with merge commit", v1_8.AddPullRequestRebaseWithMerge),
|
||||
newMigration(77, "add theme to users", v1_8.AddUserDefaultTheme),
|
||||
newMigration(78, "rename repo is_bare to repo is_empty", v1_8.RenameRepoIsBareToIsEmpty),
|
||||
newMigration(79, "add can close issues via commit in any branch", v1_8.AddCanCloseIssuesViaCommitInAnyBranch),
|
||||
newMigration(80, "add is locked to issues", v1_8.AddIsLockedToIssues),
|
||||
newMigration(81, "update U2F counter type", v1_8.ChangeU2FCounterType),
|
||||
|
||||
// Gitea 1.8.0 ends at database version 82
|
||||
|
||||
newMigration(82, "hot fix for wrong release sha1 on release table", v1_9.FixReleaseSha1OnReleaseTable),
|
||||
newMigration(83, "add uploader id for table attachment", v1_9.AddUploaderIDForAttachment),
|
||||
newMigration(84, "add table to store original imported gpg keys", v1_9.AddGPGKeyImport),
|
||||
newMigration(85, "hash application token", v1_9.HashAppToken),
|
||||
newMigration(86, "add http method to webhook", v1_9.AddHTTPMethodToWebhook),
|
||||
newMigration(87, "add avatar field to repository", v1_9.AddAvatarFieldToRepository),
|
||||
|
||||
// Gitea 1.9.0 ends at database version 88
|
||||
|
||||
newMigration(88, "add commit status context field to commit_status", v1_10.AddCommitStatusContext),
|
||||
newMigration(89, "add original author/url migration info to issues, comments, and repo ", v1_10.AddOriginalMigrationInfo),
|
||||
newMigration(90, "change length of some repository columns", v1_10.ChangeSomeColumnsLengthOfRepo),
|
||||
newMigration(91, "add index on owner_id of repository and type, review_id of comment", v1_10.AddIndexOnRepositoryAndComment),
|
||||
newMigration(92, "remove orphaned repository index statuses", v1_10.RemoveLingeringIndexStatus),
|
||||
newMigration(93, "add email notification enabled preference to user", v1_10.AddEmailNotificationEnabledToUser),
|
||||
newMigration(94, "add enable_status_check, status_check_contexts to protected_branch", v1_10.AddStatusCheckColumnsForProtectedBranches),
|
||||
newMigration(95, "add table columns for cross referencing issues", v1_10.AddCrossReferenceColumns),
|
||||
newMigration(96, "delete orphaned attachments", v1_10.DeleteOrphanedAttachments),
|
||||
newMigration(97, "add repo_admin_change_team_access to user", v1_10.AddRepoAdminChangeTeamAccessColumnForUser),
|
||||
newMigration(98, "add original author name and id on migrated release", v1_10.AddOriginalAuthorOnMigratedReleases),
|
||||
newMigration(99, "add task table and status column for repository table", v1_10.AddTaskTable),
|
||||
newMigration(100, "update migration repositories' service type", v1_10.UpdateMigrationServiceTypes),
|
||||
newMigration(101, "change length of some external login users columns", v1_10.ChangeSomeColumnsLengthOfExternalLoginUser),
|
||||
|
||||
// Gitea 1.10.0 ends at database version 102
|
||||
|
||||
newMigration(102, "update migration repositories' service type", v1_11.DropColumnHeadUserNameOnPullRequest),
|
||||
newMigration(103, "Add WhitelistDeployKeys to protected branch", v1_11.AddWhitelistDeployKeysToBranches),
|
||||
newMigration(104, "remove unnecessary columns from label", v1_11.RemoveLabelUneededCols),
|
||||
newMigration(105, "add includes_all_repositories to teams", v1_11.AddTeamIncludesAllRepositories),
|
||||
newMigration(106, "add column `mode` to table watch", v1_11.AddModeColumnToWatch),
|
||||
newMigration(107, "Add template options to repository", v1_11.AddTemplateToRepo),
|
||||
newMigration(108, "Add comment_id on table notification", v1_11.AddCommentIDOnNotification),
|
||||
newMigration(109, "add can_create_org_repo to team", v1_11.AddCanCreateOrgRepoColumnForTeam),
|
||||
newMigration(110, "change review content type to text", v1_11.ChangeReviewContentToText),
|
||||
newMigration(111, "update branch protection for can push and whitelist enable", v1_11.AddBranchProtectionCanPushAndEnableWhitelist),
|
||||
newMigration(112, "remove release attachments which repository deleted", v1_11.RemoveAttachmentMissedRepo),
|
||||
newMigration(113, "new feature: change target branch of pull requests", v1_11.FeatureChangeTargetBranch),
|
||||
newMigration(114, "Remove authentication credentials from stored URL", v1_11.SanitizeOriginalURL),
|
||||
newMigration(115, "add user_id prefix to existing user avatar name", v1_11.RenameExistingUserAvatarName),
|
||||
newMigration(116, "Extend TrackedTimes", v1_11.ExtendTrackedTimes),
|
||||
|
||||
// Gitea 1.11.0 ends at database version 117
|
||||
|
||||
newMigration(117, "Add block on rejected reviews branch protection", v1_12.AddBlockOnRejectedReviews),
|
||||
newMigration(118, "Add commit id and stale to reviews", v1_12.AddReviewCommitAndStale),
|
||||
newMigration(119, "Fix migrated repositories' git service type", v1_12.FixMigratedRepositoryServiceType),
|
||||
newMigration(120, "Add owner_name on table repository", v1_12.AddOwnerNameOnRepository),
|
||||
newMigration(121, "add is_restricted column for users table", v1_12.AddIsRestricted),
|
||||
newMigration(122, "Add Require Signed Commits to ProtectedBranch", v1_12.AddRequireSignedCommits),
|
||||
newMigration(123, "Add original information for reactions", v1_12.AddReactionOriginals),
|
||||
newMigration(124, "Add columns to user and repository", v1_12.AddUserRepoMissingColumns),
|
||||
newMigration(125, "Add some columns on review for migration", v1_12.AddReviewMigrateInfo),
|
||||
newMigration(126, "Fix topic repository count", v1_12.FixTopicRepositoryCount),
|
||||
newMigration(127, "add repository code language statistics", v1_12.AddLanguageStats),
|
||||
newMigration(128, "fix merge base for pull requests", v1_12.FixMergeBase),
|
||||
newMigration(129, "remove dependencies from deleted repositories", v1_12.PurgeUnusedDependencies),
|
||||
newMigration(130, "Expand webhooks for more granularity", v1_12.ExpandWebhooks),
|
||||
newMigration(131, "Add IsSystemWebhook column to webhooks table", v1_12.AddSystemWebhookColumn),
|
||||
newMigration(132, "Add Branch Protection Protected Files Column", v1_12.AddBranchProtectionProtectedFilesColumn),
|
||||
newMigration(133, "Add EmailHash Table", v1_12.AddEmailHashTable),
|
||||
newMigration(134, "Refix merge base for merged pull requests", v1_12.RefixMergeBase),
|
||||
newMigration(135, "Add OrgID column to Labels table", v1_12.AddOrgIDLabelColumn),
|
||||
newMigration(136, "Add CommitsAhead and CommitsBehind Column to PullRequest Table", v1_12.AddCommitDivergenceToPulls),
|
||||
newMigration(137, "Add Branch Protection Block Outdated Branch", v1_12.AddBlockOnOutdatedBranch),
|
||||
newMigration(138, "Add ResolveDoerID to Comment table", v1_12.AddResolveDoerIDCommentColumn),
|
||||
newMigration(139, "prepend refs/heads/ to issue refs", v1_12.PrependRefsHeadsToIssueRefs),
|
||||
|
||||
// Gitea 1.12.0 ends at database version 140
|
||||
|
||||
newMigration(140, "Save detected language file size to database instead of percent", v1_13.FixLanguageStatsToSaveSize),
|
||||
newMigration(141, "Add KeepActivityPrivate to User table", v1_13.AddKeepActivityPrivateUserColumn),
|
||||
newMigration(142, "Ensure Repository.IsArchived is not null", v1_13.SetIsArchivedToFalse),
|
||||
newMigration(143, "recalculate Stars number for all user", v1_13.RecalculateStars),
|
||||
newMigration(144, "update Matrix Webhook http method to 'PUT'", v1_13.UpdateMatrixWebhookHTTPMethod),
|
||||
newMigration(145, "Increase Language field to 50 in LanguageStats", v1_13.IncreaseLanguageField),
|
||||
newMigration(146, "Add projects info to repository table", v1_13.AddProjectsInfo),
|
||||
newMigration(147, "create review for 0 review id code comments", v1_13.CreateReviewsForCodeComments),
|
||||
newMigration(148, "remove issue dependency comments who refer to non existing issues", v1_13.PurgeInvalidDependenciesComments),
|
||||
newMigration(149, "Add Created and Updated to Milestone table", v1_13.AddCreatedAndUpdatedToMilestones),
|
||||
newMigration(150, "add primary key to repo_topic", v1_13.AddPrimaryKeyToRepoTopic),
|
||||
newMigration(151, "set default password algorithm to Argon2", v1_13.SetDefaultPasswordToArgon2),
|
||||
newMigration(152, "add TrustModel field to Repository", v1_13.AddTrustModelToRepository),
|
||||
newMigration(153, "add Team review request support", v1_13.AddTeamReviewRequestSupport),
|
||||
newMigration(154, "add timestamps to Star, Label, Follow, Watch and Collaboration", v1_13.AddTimeStamps),
|
||||
|
||||
// Gitea 1.13.0 ends at database version 155
|
||||
|
||||
newMigration(155, "add changed_protected_files column for pull_request table", v1_14.AddChangedProtectedFilesPullRequestColumn),
|
||||
newMigration(156, "fix publisher ID for tag releases", v1_14.FixPublisherIDforTagReleases),
|
||||
newMigration(157, "ensure repo topics are up-to-date", v1_14.FixRepoTopics),
|
||||
newMigration(158, "code comment replies should have the commitID of the review they are replying to", v1_14.UpdateCodeCommentReplies),
|
||||
newMigration(159, "update reactions constraint", v1_14.UpdateReactionConstraint),
|
||||
newMigration(160, "Add block on official review requests branch protection", v1_14.AddBlockOnOfficialReviewRequests),
|
||||
newMigration(161, "Convert task type from int to string", v1_14.ConvertTaskTypeToString),
|
||||
newMigration(162, "Convert webhook task type from int to string", v1_14.ConvertWebhookTaskTypeToString),
|
||||
newMigration(163, "Convert topic name from 25 to 50", v1_14.ConvertTopicNameFrom25To50),
|
||||
newMigration(164, "Add scope and nonce columns to oauth2_grant table", v1_14.AddScopeAndNonceColumnsToOAuth2Grant),
|
||||
newMigration(165, "Convert hook task type from char(16) to varchar(16) and trim the column", v1_14.ConvertHookTaskTypeToVarcharAndTrim),
|
||||
newMigration(166, "Where Password is Valid with Empty String delete it", v1_14.RecalculateUserEmptyPWD),
|
||||
newMigration(167, "Add user redirect", v1_14.AddUserRedirect),
|
||||
newMigration(168, "Recreate user table to fix default values", v1_14.RecreateUserTableToFixDefaultValues),
|
||||
newMigration(169, "Update DeleteBranch comments to set the old_ref to the commit_sha", v1_14.CommentTypeDeleteBranchUseOldRef),
|
||||
newMigration(170, "Add Dismissed to Review table", v1_14.AddDismissedReviewColumn),
|
||||
newMigration(171, "Add Sorting to ProjectBoard table", v1_14.AddSortingColToProjectBoard),
|
||||
newMigration(172, "Add sessions table for go-chi/session", v1_14.AddSessionTable),
|
||||
newMigration(173, "Add time_id column to Comment", v1_14.AddTimeIDCommentColumn),
|
||||
newMigration(174, "Create repo transfer table", v1_14.AddRepoTransfer),
|
||||
newMigration(175, "Fix Postgres ID Sequences broken by recreate-table", v1_14.FixPostgresIDSequences),
|
||||
newMigration(176, "Remove invalid labels from comments", v1_14.RemoveInvalidLabels),
|
||||
newMigration(177, "Delete orphaned IssueLabels", v1_14.DeleteOrphanedIssueLabels),
|
||||
|
||||
// Gitea 1.14.0 ends at database version 178
|
||||
|
||||
newMigration(178, "Add LFS columns to Mirror", v1_15.AddLFSMirrorColumns),
|
||||
newMigration(179, "Convert avatar url to text", v1_15.ConvertAvatarURLToText),
|
||||
newMigration(180, "Delete credentials from past migrations", v1_15.DeleteMigrationCredentials),
|
||||
newMigration(181, "Always save primary email on email address table", v1_15.AddPrimaryEmail2EmailAddress),
|
||||
newMigration(182, "Add issue resource index table", v1_15.AddIssueResourceIndexTable),
|
||||
newMigration(183, "Create PushMirror table", v1_15.CreatePushMirrorTable),
|
||||
newMigration(184, "Rename Task errors to message", v1_15.RenameTaskErrorsToMessage),
|
||||
newMigration(185, "Add new table repo_archiver", v1_15.AddRepoArchiver),
|
||||
newMigration(186, "Create protected tag table", v1_15.CreateProtectedTagTable),
|
||||
newMigration(187, "Drop unneeded webhook related columns", v1_15.DropWebhookColumns),
|
||||
newMigration(188, "Add key is verified to gpg key", v1_15.AddKeyIsVerified),
|
||||
|
||||
// Gitea 1.15.0 ends at database version 189
|
||||
|
||||
newMigration(189, "Unwrap ldap.Sources", v1_16.UnwrapLDAPSourceCfg),
|
||||
newMigration(190, "Add agit flow pull request support", v1_16.AddAgitFlowPullRequest),
|
||||
newMigration(191, "Alter issue/comment table TEXT fields to LONGTEXT", v1_16.AlterIssueAndCommentTextFieldsToLongText),
|
||||
newMigration(192, "RecreateIssueResourceIndexTable to have a primary key instead of an unique index", v1_16.RecreateIssueResourceIndexTable),
|
||||
newMigration(193, "Add repo id column for attachment table", v1_16.AddRepoIDForAttachment),
|
||||
newMigration(194, "Add Branch Protection Unprotected Files Column", v1_16.AddBranchProtectionUnprotectedFilesColumn),
|
||||
newMigration(195, "Add table commit_status_index", v1_16.AddTableCommitStatusIndex),
|
||||
newMigration(196, "Add Color to ProjectBoard table", v1_16.AddColorColToProjectBoard),
|
||||
newMigration(197, "Add renamed_branch table", v1_16.AddRenamedBranchTable),
|
||||
newMigration(198, "Add issue content history table", v1_16.AddTableIssueContentHistory),
|
||||
newMigration(199, "No-op (remote version is using AppState now)", noopMigration),
|
||||
newMigration(200, "Add table app_state", v1_16.AddTableAppState),
|
||||
newMigration(201, "Drop table remote_version (if exists)", v1_16.DropTableRemoteVersion),
|
||||
newMigration(202, "Create key/value table for user settings", v1_16.CreateUserSettingsTable),
|
||||
newMigration(203, "Add Sorting to ProjectIssue table", v1_16.AddProjectIssueSorting),
|
||||
newMigration(204, "Add key is verified to ssh key", v1_16.AddSSHKeyIsVerified),
|
||||
newMigration(205, "Migrate to higher varchar on user struct", v1_16.MigrateUserPasswordSalt),
|
||||
newMigration(206, "Add authorize column to team_unit table", v1_16.AddAuthorizeColForTeamUnit),
|
||||
newMigration(207, "Add webauthn table and migrate u2f data to webauthn - NO-OPED", v1_16.AddWebAuthnCred),
|
||||
newMigration(208, "Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive - NO-OPED", v1_16.UseBase32HexForCredIDInWebAuthnCredential),
|
||||
newMigration(209, "Increase WebAuthentication CredentialID size to 410 - NO-OPED", v1_16.IncreaseCredentialIDTo410),
|
||||
newMigration(210, "v208 was completely broken - remigrate", v1_16.RemigrateU2FCredentials),
|
||||
|
||||
// Gitea 1.16.2 ends at database version 211
|
||||
|
||||
newMigration(211, "Create ForeignReference table", v1_17.CreateForeignReferenceTable),
|
||||
newMigration(212, "Add package tables", v1_17.AddPackageTables),
|
||||
newMigration(213, "Add allow edits from maintainers to PullRequest table", v1_17.AddAllowMaintainerEdit),
|
||||
newMigration(214, "Add auto merge table", v1_17.AddAutoMergeTable),
|
||||
newMigration(215, "allow to view files in PRs", v1_17.AddReviewViewedFiles),
|
||||
newMigration(216, "No-op (Improve Action table indices v1)", noopMigration),
|
||||
newMigration(217, "Alter hook_task table TEXT fields to LONGTEXT", v1_17.AlterHookTaskTextFieldsToLongText),
|
||||
newMigration(218, "Improve Action table indices v2", v1_17.ImproveActionTableIndices),
|
||||
newMigration(219, "Add sync_on_commit column to push_mirror table", v1_17.AddSyncOnCommitColForPushMirror),
|
||||
newMigration(220, "Add container repository property", v1_17.AddContainerRepositoryProperty),
|
||||
newMigration(221, "Store WebAuthentication CredentialID as bytes and increase size to at least 1024", v1_17.StoreWebauthnCredentialIDAsBytes),
|
||||
newMigration(222, "Drop old CredentialID column", v1_17.DropOldCredentialIDColumn),
|
||||
newMigration(223, "Rename CredentialIDBytes column to CredentialID", v1_17.RenameCredentialIDBytes),
|
||||
|
||||
// Gitea 1.17.0 ends at database version 224
|
||||
|
||||
newMigration(224, "Add badges to users", v1_18.CreateUserBadgesTable),
|
||||
newMigration(225, "Alter gpg_key/public_key content TEXT fields to MEDIUMTEXT", v1_18.AlterPublicGPGKeyContentFieldsToMediumText),
|
||||
newMigration(226, "Conan and generic packages do not need to be semantically versioned", v1_18.FixPackageSemverField),
|
||||
newMigration(227, "Create key/value table for system settings", v1_18.CreateSystemSettingsTable),
|
||||
newMigration(228, "Add TeamInvite table", v1_18.AddTeamInviteTable),
|
||||
newMigration(229, "Update counts of all open milestones", v1_18.UpdateOpenMilestoneCounts),
|
||||
newMigration(230, "Add ConfidentialClient column (default true) to OAuth2Application table", v1_18.AddConfidentialClientColumnToOAuth2ApplicationTable),
|
||||
|
||||
// Gitea 1.18.0 ends at database version 231
|
||||
|
||||
newMigration(231, "Add index for hook_task", v1_19.AddIndexForHookTask),
|
||||
newMigration(232, "Alter package_version.metadata_json to LONGTEXT", v1_19.AlterPackageVersionMetadataToLongText),
|
||||
newMigration(233, "Add header_authorization_encrypted column to webhook table", v1_19.AddHeaderAuthorizationEncryptedColWebhook),
|
||||
newMigration(234, "Add package cleanup rule table", v1_19.CreatePackageCleanupRuleTable),
|
||||
newMigration(235, "Add index for access_token", v1_19.AddIndexForAccessToken),
|
||||
newMigration(236, "Create secrets table", v1_19.CreateSecretsTable),
|
||||
newMigration(237, "Drop ForeignReference table", v1_19.DropForeignReferenceTable),
|
||||
newMigration(238, "Add updated unix to LFSMetaObject", v1_19.AddUpdatedUnixToLFSMetaObject),
|
||||
newMigration(239, "Add scope for access_token", v1_19.AddScopeForAccessTokens),
|
||||
newMigration(240, "Add actions tables", v1_19.AddActionsTables),
|
||||
newMigration(241, "Add card_type column to project table", v1_19.AddCardTypeToProjectTable),
|
||||
newMigration(242, "Alter gpg_key_import content TEXT field to MEDIUMTEXT", v1_19.AlterPublicGPGKeyImportContentFieldToMediumText),
|
||||
newMigration(243, "Add exclusive label", v1_19.AddExclusiveLabel),
|
||||
|
||||
// Gitea 1.19.0 ends at database version 244
|
||||
|
||||
newMigration(244, "Add NeedApproval to actions tables", v1_20.AddNeedApprovalToActionRun),
|
||||
newMigration(245, "Rename Webhook org_id to owner_id", v1_20.RenameWebhookOrgToOwner),
|
||||
newMigration(246, "Add missed column owner_id for project table", v1_20.AddNewColumnForProject),
|
||||
newMigration(247, "Fix incorrect project type", v1_20.FixIncorrectProjectType),
|
||||
newMigration(248, "Add version column to action_runner table", v1_20.AddVersionToActionRunner),
|
||||
newMigration(249, "Improve Action table indices v3", v1_20.ImproveActionTableIndices),
|
||||
newMigration(250, "Change Container Metadata", v1_20.ChangeContainerMetadataMultiArch),
|
||||
newMigration(251, "Fix incorrect owner team unit access mode", v1_20.FixIncorrectOwnerTeamUnitAccessMode),
|
||||
newMigration(252, "Fix incorrect admin team unit access mode", v1_20.FixIncorrectAdminTeamUnitAccessMode),
|
||||
newMigration(253, "Fix ExternalTracker and ExternalWiki accessMode in owner and admin team", v1_20.FixExternalTrackerAndExternalWikiAccessModeInOwnerAndAdminTeam),
|
||||
newMigration(254, "Add ActionTaskOutput table", v1_20.AddActionTaskOutputTable),
|
||||
newMigration(255, "Add ArchivedUnix Column", v1_20.AddArchivedUnixToRepository),
|
||||
newMigration(256, "Add is_internal column to package", v1_20.AddIsInternalColumnToPackage),
|
||||
newMigration(257, "Add Actions Artifact table", v1_20.CreateActionArtifactTable),
|
||||
newMigration(258, "Add PinOrder Column", v1_20.AddPinOrderToIssue),
|
||||
newMigration(259, "Convert scoped access tokens", v1_20.ConvertScopedAccessTokens),
|
||||
|
||||
// Gitea 1.20.0 ends at database version 260
|
||||
|
||||
newMigration(260, "Drop custom_labels column of action_runner table", v1_21.DropCustomLabelsColumnOfActionRunner),
|
||||
newMigration(261, "Add variable table", v1_21.CreateVariableTable),
|
||||
newMigration(262, "Add TriggerEvent to action_run table", v1_21.AddTriggerEventToActionRun),
|
||||
newMigration(263, "Add git_size and lfs_size columns to repository table", v1_21.AddGitSizeAndLFSSizeToRepositoryTable),
|
||||
newMigration(264, "Add branch table", v1_21.AddBranchTable),
|
||||
newMigration(265, "Alter Actions Artifact table", v1_21.AlterActionArtifactTable),
|
||||
newMigration(266, "Reduce commit status", v1_21.ReduceCommitStatus),
|
||||
newMigration(267, "Add action_tasks_version table", v1_21.CreateActionTasksVersionTable),
|
||||
newMigration(268, "Update Action Ref", v1_21.UpdateActionsRefIndex),
|
||||
newMigration(269, "Drop deleted branch table", v1_21.DropDeletedBranchTable),
|
||||
newMigration(270, "Fix PackageProperty typo", v1_21.FixPackagePropertyTypo),
|
||||
newMigration(271, "Allow archiving labels", v1_21.AddArchivedUnixColumInLabelTable),
|
||||
newMigration(272, "Add Version to ActionRun table", v1_21.AddVersionToActionRunTable),
|
||||
newMigration(273, "Add Action Schedule Table", v1_21.AddActionScheduleTable),
|
||||
newMigration(274, "Add Actions artifacts expiration date", v1_21.AddExpiredUnixColumnInActionArtifactTable),
|
||||
newMigration(275, "Add ScheduleID for ActionRun", v1_21.AddScheduleIDForActionRun),
|
||||
newMigration(276, "Add RemoteAddress to mirrors", v1_21.AddRemoteAddressToMirrors),
|
||||
newMigration(277, "Add Index to issue_user.issue_id", v1_21.AddIndexToIssueUserIssueID),
|
||||
newMigration(278, "Add Index to comment.dependent_issue_id", v1_21.AddIndexToCommentDependentIssueID),
|
||||
newMigration(279, "Add Index to action.user_id", v1_21.AddIndexToActionUserID),
|
||||
|
||||
// Gitea 1.21.0 ends at database version 280
|
||||
|
||||
newMigration(280, "Rename user themes", v1_22.RenameUserThemes),
|
||||
newMigration(281, "Add auth_token table", v1_22.CreateAuthTokenTable),
|
||||
newMigration(282, "Add Index to pull_auto_merge.doer_id", v1_22.AddIndexToPullAutoMergeDoerID),
|
||||
newMigration(283, "Add combined Index to issue_user.uid and issue_id", v1_22.AddCombinedIndexToIssueUser),
|
||||
newMigration(284, "Add ignore stale approval column on branch table", v1_22.AddIgnoreStaleApprovalsColumnToProtectedBranchTable),
|
||||
newMigration(285, "Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun),
|
||||
newMigration(286, "Add support for SHA256 git repositories", v1_22.AdjustDBForSha256),
|
||||
newMigration(287, "Use Slug instead of ID for Badges", v1_22.UseSlugInsteadOfIDForBadges),
|
||||
newMigration(288, "Add user_blocking table", v1_22.AddUserBlockingTable),
|
||||
newMigration(289, "Add default_wiki_branch to repository table", v1_22.AddDefaultWikiBranch),
|
||||
newMigration(290, "Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable),
|
||||
newMigration(291, "Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment),
|
||||
newMigration(292, "Ensure every project has exactly one default column - No Op", noopMigration),
|
||||
newMigration(293, "Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency),
|
||||
|
||||
// Gitea 1.22.0-rc0 ends at database version 294
|
||||
|
||||
newMigration(294, "Add unique index for project issue table", v1_22.AddUniqueIndexForProjectIssue),
|
||||
newMigration(295, "Add commit status summary table", v1_22.AddCommitStatusSummary),
|
||||
newMigration(296, "Add missing field of commit status summary table", v1_22.AddCommitStatusSummary2),
|
||||
newMigration(297, "Add everyone_access_mode for repo_unit", v1_22.AddRepoUnitEveryoneAccessMode),
|
||||
newMigration(298, "Drop wrongly created table o_auth2_application", v1_22.DropWronglyCreatedTable),
|
||||
|
||||
// Gitea 1.22.0-rc1 ends at migration ID number 298 (database version 299)
|
||||
|
||||
newMigration(299, "Add content version to issue and comment table", v1_23.AddContentVersionToIssueAndComment),
|
||||
newMigration(300, "Add force-push branch protection support", v1_23.AddForcePushBranchProtection),
|
||||
newMigration(301, "Add skip_secondary_authorization option to oauth2 application table", v1_23.AddSkipSecondaryAuthColumnToOAuth2ApplicationTable),
|
||||
newMigration(302, "Add index to action_task stopped log_expired", v1_23.AddIndexToActionTaskStoppedLogExpired),
|
||||
newMigration(303, "Add metadata column for comment table", v1_23.AddCommentMetaDataColumn),
|
||||
newMigration(304, "Add index for release sha1", v1_23.AddIndexForReleaseSha1),
|
||||
newMigration(305, "Add Repository Licenses", v1_23.AddRepositoryLicenses),
|
||||
newMigration(306, "Add BlockAdminMergeOverride to ProtectedBranch", v1_23.AddBlockAdminMergeOverrideBranchProtection),
|
||||
newMigration(307, "Fix milestone deadline_unix when there is no due date", v1_23.FixMilestoneNoDueDate),
|
||||
newMigration(308, "Add index(user_id, is_deleted) for action table", v1_23.AddNewIndexForUserDashboard),
|
||||
newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices),
|
||||
newMigration(310, "Add Priority to ProtectedBranch", v1_23.AddPriorityToProtectedBranch),
|
||||
newMigration(311, "Add TimeEstimate to Issue table", v1_23.AddTimeEstimateColumnToIssueTable),
|
||||
// Gitea 1.23.0-rc0 ends at migration ID number 311 (database version 312)
|
||||
|
||||
newMigration(312, "Add DeleteBranchAfterMerge to AutoMerge", v1_24.AddDeleteBranchAfterMergeForAutoMerge),
|
||||
newMigration(313, "Move PinOrder from issue table to a new table issue_pin", v1_24.MovePinOrderToTableIssuePin),
|
||||
newMigration(314, "Update OwnerID as zero for repository level action tables", v1_24.UpdateOwnerIDOfRepoLevelActionsTables),
|
||||
newMigration(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner),
|
||||
newMigration(316, "Add description for secrets and variables", v1_24.AddDescriptionForSecretsAndVariables),
|
||||
newMigration(317, "Add new index for action for heatmap", v1_24.AddNewIndexForUserDashboard),
|
||||
newMigration(318, "Add anonymous_access_mode for repo_unit", v1_24.AddRepoUnitAnonymousAccessMode),
|
||||
newMigration(319, "Add ExclusiveOrder to Label table", v1_24.AddExclusiveOrderColumnToLabelTable),
|
||||
newMigration(320, "Migrate two_factor_policy to login_source table", v1_24.MigrateSkipTwoFactor),
|
||||
// Gitea 1.24.0 ends at migration ID number 320 (database version 321)
|
||||
|
||||
newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs),
|
||||
newMigration(322, "Extend comment tree_path length limit", v1_25.ExtendCommentTreePathLength),
|
||||
// Gitea 1.25.0 ends at migration ID number 322 (database version 323)
|
||||
|
||||
newMigration(323, "Add support for actions concurrency", v1_26.AddActionsConcurrency),
|
||||
newMigration(324, "Fix closed milestone completeness for milestones with no issues", v1_26.FixClosedMilestoneCompleteness),
|
||||
newMigration(325, "Fix missed repo_id when migrate attachments", v1_26.FixMissedRepoIDWhenMigrateAttachments),
|
||||
newMigration(326, "Partially migrate commit status target URL to use run ID and job ID", v1_26.FixCommitStatusTargetURLToUseRunAndJobID),
|
||||
newMigration(327, "Add disabled state to action runners", v1_26.AddDisabledToActionRunner),
|
||||
newMigration(328, "Add TokenPermissions column to ActionRunJob", v1_26.AddTokenPermissionsToActionRunJob),
|
||||
newMigration(329, "Add unique constraint for user badge", v1_26.AddUniqueIndexForUserBadge),
|
||||
newMigration(330, "Add name column to webhook", v1_26.AddNameToWebhook),
|
||||
// Gitea 1.26.0 ends at migration ID number 330 (database version 331)
|
||||
|
||||
newMigration(331, "Add ActionRunAttempt model and related action fields", v1_27.AddActionRunAttemptModel),
|
||||
newMigration(332, "Add last_sync_unix to mirror", v1_27.AddLastSyncUnixToMirror),
|
||||
newMigration(333, "Add bypass allowlist to branch protection", v1_27.AddBranchProtectionBypassAllowlist),
|
||||
newMigration(334, "Add cancelling support to action runners", v1_27.AddCancellingSupportToActionRunner),
|
||||
newMigration(335, "Add reusable workflow fields and action_run_attempt_job_id_index table for ActionRunJob", v1_27.AddReusableWorkflowFieldsToActionRunJob),
|
||||
newMigration(336, "Add ActionRunJobSummary table", v1_27.AddActionRunJobSummaryTable),
|
||||
newMigration(337, "Add visibility to team", v1_27.AddVisibilityToTeam),
|
||||
newMigration(338, "Expand legacy MSSQL issue/comment long-text columns", v1_27.ExpandIssueAndCommentLongTextFieldsForMSSQL),
|
||||
newMigration(339, "Extend action c_u index to include created_unix for faster dashboard feed queries", v1_27.AddCreatedUnixToActionUserIsDeletedIndex),
|
||||
newMigration(340, "Add ContinueOnError column to ActionRunJob", v1_27.AddContinueOnErrorToActionRunJob),
|
||||
newMigration(341, "Convert legacy MSSQL DATETIME columns to DATETIME2", v1_27.FixLegacyMSSQLDateTimeColumns),
|
||||
newMigration(342, "Add scoped workflows schema", v1_27.AddScopedWorkflowsSchema),
|
||||
// Gitea 1.27.0 ends at migration ID number 342 (database version 343)
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
func GetCurrentDBVersion(x base.EngineMigration) (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
|
||||
}
|
||||
|
||||
func calcDBVersion(migrations []*migration) int64 {
|
||||
dbVer := int64(minDBVersion + len(migrations))
|
||||
if migrations[0].idNumber != minDBVersion {
|
||||
panic("migrations should start at minDBVersion")
|
||||
}
|
||||
if dbVer != migrations[len(migrations)-1].idNumber+1 {
|
||||
panic("migrations are not in order")
|
||||
}
|
||||
return dbVer
|
||||
}
|
||||
|
||||
// ExpectedDBVersion returns the expected db version
|
||||
func ExpectedDBVersion() int64 {
|
||||
return calcDBVersion(prepareMigrationTasks())
|
||||
}
|
||||
|
||||
// EnsureUpToDate will check if the db is at the correct version
|
||||
func EnsureUpToDate(ctx context.Context, x base.EngineMigration) error {
|
||||
currentDB, err := GetCurrentDBVersion(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if currentDB < 0 {
|
||||
return errors.New("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)
|
||||
}
|
||||
|
||||
expectedDB := ExpectedDBVersion()
|
||||
|
||||
if currentDB != expectedDB {
|
||||
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, expectedDB)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPendingMigrations(curDBVer int64, migrations []*migration) []*migration {
|
||||
return migrations[curDBVer-minDBVersion:]
|
||||
}
|
||||
|
||||
func migrationIDNumberToDBVersion(idNumber int64) int64 {
|
||||
return idNumber + 1
|
||||
}
|
||||
|
||||
// Migrate database to current version
|
||||
func Migrate(ctx context.Context, x base.EngineMigration) error {
|
||||
migrations := prepareMigrationTasks()
|
||||
maxDBVer := calcDBVersion(migrations)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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, it is a fresh installation, and we can skip all migrations.
|
||||
// XORM model framework will create all tables when initializing.
|
||||
currentVersion.ID = 0
|
||||
currentVersion.Version = maxDBVer
|
||||
if _, err = x.Insert(currentVersion); err != nil {
|
||||
return fmt.Errorf("insert: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
curDBVer := currentVersion.Version
|
||||
// Outdated Gitea database version is not supported
|
||||
if curDBVer < minDBVersion {
|
||||
log.Fatal(`Gitea 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 Gitea's database version not supported
|
||||
if maxDBVer < curDBVer {
|
||||
msg := fmt.Sprintf("Your database (migration version: %d) is for a newer Gitea, you can not use the newer database for this old Gitea release (%d).", curDBVer, maxDBVer)
|
||||
msg += "\nGitea will exit to keep your database safe and unchanged. Please use the correct Gitea 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;", maxDBVer)
|
||||
}
|
||||
log.Fatal("Migration Error: %s", msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Some migration tasks depend on the git command
|
||||
if err = git.InitSimple(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Migrate
|
||||
for _, m := range getPendingMigrations(curDBVer, migrations) {
|
||||
log.Info("Migration[%d]: %s", m.idNumber, 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(ctx, x); err != nil {
|
||||
return fmt.Errorf("migration[%d]: %s failed: %w", m.idNumber, m.description, err)
|
||||
}
|
||||
currentVersion.Version = migrationIDNumberToDBVersion(m.idNumber)
|
||||
if _, err = x.ID(1).Update(currentVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package modelmigration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMigrations(t *testing.T) {
|
||||
defer test.MockVariableValue(&preparedMigrations)()
|
||||
preparedMigrations = []*migration{
|
||||
{idNumber: 70},
|
||||
{idNumber: 71},
|
||||
}
|
||||
assert.EqualValues(t, 72, calcDBVersion(preparedMigrations))
|
||||
assert.EqualValues(t, 72, ExpectedDBVersion())
|
||||
|
||||
assert.EqualValues(t, 71, migrationIDNumberToDBVersion(70))
|
||||
|
||||
assert.Equal(t, []*migration{{idNumber: 70}, {idNumber: 71}}, getPendingMigrations(70, preparedMigrations))
|
||||
assert.Equal(t, []*migration{{idNumber: 71}}, getPendingMigrations(71, preparedMigrations))
|
||||
assert.Equal(t, []*migration{}, getPendingMigrations(72, preparedMigrations))
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package migrationtest
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/testlogger"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// 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 `modelmigration/fixtures/<TestName>` will be loaded automatically
|
||||
func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (base.EngineMigration, func()) {
|
||||
t.Helper()
|
||||
ourSkip := 2
|
||||
ourSkip += skip
|
||||
deferFn := testlogger.PrintCurrentTest(t, ourSkip)
|
||||
giteaRoot := setting.GetGiteaTestSourceRoot()
|
||||
require.NoError(t, unittest.SyncDirs(filepath.Join(giteaRoot, "tests/gitea-repositories-meta"), setting.RepoRootPath))
|
||||
|
||||
cleanup, err := unittest.ResetTestDatabase()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to reset database: %v", err)
|
||||
return nil, deferFn
|
||||
}
|
||||
{
|
||||
oldDefer := deferFn
|
||||
deferFn = func() {
|
||||
cleanup()
|
||||
oldDefer()
|
||||
}
|
||||
}
|
||||
|
||||
err = db.InitEngine(t.Context())
|
||||
if !assert.NoError(t, err) {
|
||||
return nil, deferFn
|
||||
}
|
||||
x := unittest.GetXORMEngine()
|
||||
{
|
||||
oldDefer := deferFn
|
||||
deferFn = func() {
|
||||
_ = x.Close()
|
||||
oldDefer()
|
||||
}
|
||||
}
|
||||
|
||||
if len(syncModels) > 0 {
|
||||
if err := x.Sync(syncModels...); err != nil {
|
||||
t.Errorf("error during sync: %v", err)
|
||||
return x, deferFn
|
||||
}
|
||||
}
|
||||
|
||||
fixturesDir := filepath.Join(giteaRoot, "modelmigration/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,
|
||||
}); err != nil {
|
||||
t.Errorf("error whilst initializing fixtures from %s: %v", fixturesDir, err)
|
||||
return x, deferFn
|
||||
}
|
||||
if err := unittest.LoadFixtures(); 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 LoadTableSchemasMap(t *testing.T, x base.EngineMigration) map[string]*schemas.Table {
|
||||
tables, err := x.DBMetas()
|
||||
require.NoError(t, err)
|
||||
tableMap := make(map[string]*schemas.Table)
|
||||
for _, table := range tables {
|
||||
tableMap[table.Name] = table
|
||||
}
|
||||
return tableMap
|
||||
}
|
||||
|
||||
func mainTest(m *testing.M) int {
|
||||
testlogger.Init()
|
||||
err := setting.PrepareIntegrationTestConfig()
|
||||
if err != nil {
|
||||
return testlogger.MainErrorf("Unable to prepare integration test config: %v", err)
|
||||
}
|
||||
setting.SetupGiteaTestEnv()
|
||||
|
||||
if err = git.InitFull(); err != nil {
|
||||
return testlogger.MainErrorf("Unable to InitFull: %v", err)
|
||||
}
|
||||
setting.Database.SlowQueryThreshold = 0
|
||||
setting.LoadDBSetting()
|
||||
setting.InitLoggersForTest()
|
||||
return m.Run()
|
||||
}
|
||||
|
||||
func MainTest(m *testing.M) {
|
||||
os.Exit(mainTest(m))
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func UpdateMigrationServiceTypes(x base.EngineMigration) 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))
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func ChangeSomeColumnsLengthOfExternalLoginUser(x base.EngineMigration) error {
|
||||
type ExternalLoginUser struct {
|
||||
AccessToken string `xorm:"TEXT"`
|
||||
AccessTokenSecret string `xorm:"TEXT"`
|
||||
RefreshToken string `xorm:"TEXT"`
|
||||
}
|
||||
|
||||
return x.Sync(new(ExternalLoginUser))
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func hashContext(context string) string {
|
||||
return fmt.Sprintf("%x", sha1.Sum([]byte(context)))
|
||||
}
|
||||
|
||||
func AddCommitStatusContext(x base.EngineMigration) 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
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddOriginalMigrationInfo(x base.EngineMigration) 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))
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func ChangeSomeColumnsLengthOfRepo(x base.EngineMigration) 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))
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddIndexOnRepositoryAndComment(x base.EngineMigration) 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))
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func RemoveLingeringIndexStatus(x base.EngineMigration) error {
|
||||
_, err := x.Exec(builder.Delete(builder.NotIn("`repo_id`", builder.Select("`id`").From("`repository`"))).From("`repo_indexer_status`"))
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddEmailNotificationEnabledToUser(x base.EngineMigration) error {
|
||||
// User see models/user.go
|
||||
type User struct {
|
||||
EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"`
|
||||
}
|
||||
|
||||
return x.Sync(new(User))
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddStatusCheckColumnsForProtectedBranches(x base.EngineMigration) 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
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddCrossReferenceColumns(x base.EngineMigration) 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))
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
func DeleteOrphanedAttachments(x base.EngineMigration) 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddRepoAdminChangeTeamAccessColumnForUser(x base.EngineMigration) error {
|
||||
type User struct {
|
||||
RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync(new(User))
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddOriginalAuthorOnMigratedReleases(x base.EngineMigration) error {
|
||||
type Release struct {
|
||||
ID int64
|
||||
OriginalAuthor string
|
||||
OriginalAuthorID int64 `xorm:"index"`
|
||||
}
|
||||
|
||||
return x.Sync(new(Release))
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_10
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/timeutil"
|
||||
)
|
||||
|
||||
func AddTaskTable(x base.EngineMigration) 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))
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func DropColumnHeadUserNameOnPullRequest(x base.EngineMigration) 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()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddWhitelistDeployKeysToBranches(x base.EngineMigration) error {
|
||||
type ProtectedBranch struct {
|
||||
ID int64
|
||||
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync(new(ProtectedBranch))
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func RemoveLabelUneededCols(x base.EngineMigration) 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()
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddTeamIncludesAllRepositories(x base.EngineMigration) 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
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
// 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 base.EngineMigration) error {
|
||||
if err := x.Sync(new(Watch)); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := x.Exec("UPDATE `watch` SET `mode` = 1")
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddTemplateToRepo(x base.EngineMigration) error {
|
||||
type Repository struct {
|
||||
IsTemplate bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
||||
TemplateID int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
return x.Sync(new(Repository))
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddCommentIDOnNotification(x base.EngineMigration) error {
|
||||
type Notification struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CommentID int64
|
||||
}
|
||||
|
||||
return x.Sync(new(Notification))
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddCanCreateOrgRepoColumnForTeam(x base.EngineMigration) error {
|
||||
type Team struct {
|
||||
CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync(new(Team))
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func ChangeReviewContentToText(x base.EngineMigration) 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.MSSQL:
|
||||
_, err := x.Exec("ALTER TABLE review ALTER COLUMN 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddBranchProtectionCanPushAndEnableWhitelist(x base.EngineMigration) 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"`
|
||||
Visibility int `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
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 base.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
|
||||
} else if !user.IsAdmin {
|
||||
hasMemberWithUserID, err := sess.
|
||||
Where("uid=?", user.ID).
|
||||
And("org_id=?", repoOwner.ID).
|
||||
Table("org_user").
|
||||
Exist()
|
||||
if err != nil {
|
||||
hasOrgVisible = false
|
||||
}
|
||||
if (repoOwner.Visibility == VisibleTypePrivate || user.IsRestricted) && !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 = user.IsRestricted
|
||||
}
|
||||
|
||||
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 && !user.IsRestricted {
|
||||
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 base.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 len(perm.UnitsMode) == 0 {
|
||||
for _, u := range perm.Units {
|
||||
if u.Type == UnitTypeCode {
|
||||
return AccessModeWrite <= perm.AccessMode, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return AccessModeWrite <= perm.UnitsMode[UnitTypeCode], nil
|
||||
}
|
||||
if slices.Contains(protectedBranch.ApprovalsWhitelistUserIDs, 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 protected 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
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func RemoveAttachmentMissedRepo(x base.EngineMigration) 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 {
|
||||
log.Warn("Unable to remove attachment file by UUID %s: %v", uuid, err)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func FeatureChangeTargetBranch(x base.EngineMigration) error {
|
||||
type Comment struct {
|
||||
OldRef string
|
||||
NewRef string
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Comment)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func SanitizeOriginalURL(x base.EngineMigration) 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
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/container"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
func RenameExistingUserAvatarName(x base.EngineMigration) 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(fmt.Appendf(nil, "%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
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func ExtendTrackedTimes(x base.EngineMigration) 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()
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddBlockOnRejectedReviews(x base.EngineMigration) error {
|
||||
type ProtectedBranch struct {
|
||||
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync(new(ProtectedBranch))
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddReviewCommitAndStale(x base.EngineMigration) 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))
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func FixMigratedRepositoryServiceType(x base.EngineMigration) error {
|
||||
// structs.GithubService:
|
||||
// GithubService = 2
|
||||
_, err := x.Exec("UPDATE repository SET original_service_type = ? WHERE original_url LIKE 'https://github.com/%'", 2)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddOwnerNameOnRepository(x base.EngineMigration) 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
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddIsRestricted(x base.EngineMigration) 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))
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddRequireSignedCommits(x base.EngineMigration) error {
|
||||
type ProtectedBranch struct {
|
||||
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync(new(ProtectedBranch))
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddReactionOriginals(x base.EngineMigration) error {
|
||||
type Reaction struct {
|
||||
OriginalAuthorID int64 `xorm:"INDEX NOT NULL DEFAULT(0)"`
|
||||
OriginalAuthor string
|
||||
}
|
||||
|
||||
return x.Sync(new(Reaction))
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddUserRepoMissingColumns(x base.EngineMigration) 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))
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddReviewMigrateInfo(x base.EngineMigration) error {
|
||||
type Review struct {
|
||||
OriginalAuthor string
|
||||
OriginalAuthorID int64
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Review)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func FixTopicRepositoryCount(x base.EngineMigration) 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
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/timeutil"
|
||||
)
|
||||
|
||||
func AddLanguageStats(x base.EngineMigration) 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
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
func FixMergeBase(ctx context.Context, x base.EngineMigration) 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 = gitcmd.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).WithDir(repoPath).RunStdString(ctx)
|
||||
if err != nil {
|
||||
var err2 error
|
||||
pr.MergeBase, _, err2 = gitcmd.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix + pr.BaseBranch).WithDir(repoPath).RunStdString(ctx)
|
||||
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 := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).WithDir(repoPath).RunStdString(ctx)
|
||||
if err != nil {
|
||||
log.Error("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 := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...)
|
||||
|
||||
pr.MergeBase, _, err = cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func PurgeUnusedDependencies(x base.EngineMigration) 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
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
func ExpandWebhooks(x base.EngineMigration) 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
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddSystemWebhookColumn(x base.EngineMigration) 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
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddBranchProtectionProtectedFilesColumn(x base.EngineMigration) error {
|
||||
type ProtectedBranch struct {
|
||||
ProtectedFilePatterns string `xorm:"TEXT"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(ProtectedBranch)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddEmailHashTable(x base.EngineMigration) 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))
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
func RefixMergeBase(ctx context.Context, x base.EngineMigration) 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 := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).WithDir(repoPath).RunStdString(ctx)
|
||||
if err != nil {
|
||||
log.Error("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 := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...)
|
||||
|
||||
pr.MergeBase, _, err = cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddOrgIDLabelColumn(x base.EngineMigration) error {
|
||||
type Label struct {
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Label)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
"gitea.dev/modules/graceful"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
func AddCommitDivergenceToPulls(x base.EngineMigration) 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
|
||||
}
|
||||
repoStore := repo_model.CodeRepoByName(baseRepo.OwnerName, baseRepo.Name)
|
||||
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
|
||||
divergence, err := gitrepo.GetDivergingCommits(graceful.GetManager().HammerContext(), repoStore, 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
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddBlockOnOutdatedBranch(x base.EngineMigration) error {
|
||||
type ProtectedBranch struct {
|
||||
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
return x.Sync(new(ProtectedBranch))
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddResolveDoerIDCommentColumn(x base.EngineMigration) error {
|
||||
type Comment struct {
|
||||
ResolveDoerID int64
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Comment)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_12
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
func PrependRefsHeadsToIssueRefs(x base.EngineMigration) error {
|
||||
var query string
|
||||
|
||||
switch {
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
query = "UPDATE `issue` SET `ref` = 'refs/heads/' + `ref` WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%'"
|
||||
case 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/%';"
|
||||
default:
|
||||
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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user