forked from hinterland/hearth
feat: vendor gitea 1.16.2
This commit is contained in:
@@ -5,21 +5,14 @@ package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
@@ -409,7 +402,7 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
||||
cols += "DROP COLUMN `" + col + "` CASCADE"
|
||||
}
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
|
||||
return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
|
||||
return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err)
|
||||
}
|
||||
case setting.Database.Type.IsMySQL():
|
||||
// Drop indexes on columns first
|
||||
@@ -437,7 +430,7 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
||||
cols += "DROP COLUMN `" + col + "`"
|
||||
}
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
|
||||
return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
|
||||
return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err)
|
||||
}
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
cols := ""
|
||||
@@ -451,27 +444,27 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
||||
tableName, strings.ReplaceAll(cols, "`", "'"))
|
||||
constraints := make([]string, 0)
|
||||
if err := sess.SQL(sql).Find(&constraints); err != nil {
|
||||
return fmt.Errorf("Find constraints: %v", err)
|
||||
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`: %v", tableName, constraint, err)
|
||||
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: %v", err)
|
||||
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`: %v", tableName, constraint, err)
|
||||
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: %v", tableName, columnNames, err)
|
||||
return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err)
|
||||
}
|
||||
default:
|
||||
log.Fatal("Unrecognized DB")
|
||||
@@ -515,114 +508,3 @@ func ModifyColumn(x *xorm.Engine, tableName string, col *schemas.Column) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeAllWithRetry(dir string) error {
|
||||
var err error
|
||||
for range 20 {
|
||||
err = os.RemoveAll(dir)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func newXORMEngine() (*xorm.Engine, error) {
|
||||
if err := db.InitEngine(context.Background()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := unittest.GetXORMEngine()
|
||||
return x, nil
|
||||
}
|
||||
|
||||
func deleteDB() error {
|
||||
switch {
|
||||
case setting.Database.Type.IsSQLite3():
|
||||
if err := util.Remove(setting.Database.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm)
|
||||
|
||||
case setting.Database.Type.IsMySQL():
|
||||
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if _, err = db.Exec("DROP DATABASE IF EXISTS " + setting.Database.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + setting.Database.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if _, err = db.Exec("DROP DATABASE IF EXISTS " + setting.Database.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = db.Exec("CREATE DATABASE " + setting.Database.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
db.Close()
|
||||
|
||||
// Check if we need to setup a specific schema
|
||||
if len(setting.Database.Schema) != 0 {
|
||||
db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer schrows.Close()
|
||||
|
||||
if !schrows.Next() {
|
||||
// Create and setup a DB schema
|
||||
_, err = db.Exec("CREATE SCHEMA " + setting.Database.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Make the user's default search path the created schema; this will affect new connections
|
||||
_, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
host, port := setting.ParseMSSQLHostPort(setting.Database.Host)
|
||||
db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
|
||||
host, port, "master", setting.Database.User, setting.Database.Passwd))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS [%s]", setting.Database.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE [%s]", setting.Database.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import (
|
||||
"xorm.io/xorm/names"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
MainTest(m)
|
||||
}
|
||||
|
||||
func Test_DropTableColumns(t *testing.T) {
|
||||
x, deferable := PrepareTestEnv(t, 0)
|
||||
if x == nil || t.Failed() {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
MainTest(m)
|
||||
}
|
||||
@@ -4,25 +4,127 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/tempdir"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/testlogger"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// FIXME: this file shouldn't be in a normal package, it should only be compiled for tests
|
||||
|
||||
func newXORMEngine(t *testing.T) (*xorm.Engine, error) {
|
||||
if err := db.InitEngine(t.Context()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := unittest.GetXORMEngine()
|
||||
return x, nil
|
||||
}
|
||||
|
||||
func deleteDB() error {
|
||||
switch {
|
||||
case setting.Database.Type.IsSQLite3():
|
||||
if err := util.Remove(setting.Database.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm)
|
||||
|
||||
case setting.Database.Type.IsMySQL():
|
||||
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if _, err = db.Exec("DROP DATABASE IF EXISTS " + setting.Database.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + setting.Database.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if _, err = db.Exec("DROP DATABASE IF EXISTS " + setting.Database.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = db.Exec("CREATE DATABASE " + setting.Database.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
db.Close()
|
||||
|
||||
// Check if we need to setup a specific schema
|
||||
if len(setting.Database.Schema) != 0 {
|
||||
db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer schrows.Close()
|
||||
|
||||
if !schrows.Next() {
|
||||
// Create and setup a DB schema
|
||||
_, err = db.Exec("CREATE SCHEMA " + setting.Database.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Make the user's default search path the created schema; this will affect new connections
|
||||
_, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
host, port := setting.ParseMSSQLHostPort(setting.Database.Host)
|
||||
db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
|
||||
host, port, "master", setting.Database.User, setting.Database.Passwd))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS [%s]", setting.Database.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE [%s]", setting.Database.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
@@ -39,7 +141,7 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu
|
||||
return nil, deferFn
|
||||
}
|
||||
|
||||
x, err := newXORMEngine()
|
||||
x, err := newXORMEngine(t)
|
||||
require.NoError(t, err)
|
||||
if x != nil {
|
||||
oldDefer := deferFn
|
||||
@@ -88,51 +190,36 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu
|
||||
return x, deferFn
|
||||
}
|
||||
|
||||
func MainTest(m *testing.M) {
|
||||
func LoadTableSchemasMap(t *testing.T, x *xorm.Engine) 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()
|
||||
|
||||
giteaRoot := test.SetupGiteaRoot()
|
||||
giteaBinary := "gitea"
|
||||
if runtime.GOOS == "windows" {
|
||||
giteaBinary += ".exe"
|
||||
}
|
||||
setting.AppPath = filepath.Join(giteaRoot, giteaBinary)
|
||||
if _, err := os.Stat(setting.AppPath); err != nil {
|
||||
testlogger.Fatalf("Could not find gitea binary at %s\n", setting.AppPath)
|
||||
}
|
||||
|
||||
giteaConf := os.Getenv("GITEA_CONF")
|
||||
if giteaConf == "" {
|
||||
giteaConf = filepath.Join(filepath.Dir(setting.AppPath), "tests/sqlite.ini")
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(giteaConf) {
|
||||
setting.CustomConf = filepath.Join(giteaRoot, giteaConf)
|
||||
} else {
|
||||
setting.CustomConf = giteaConf
|
||||
}
|
||||
|
||||
tmpDataPath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("data")
|
||||
if err != nil {
|
||||
testlogger.Fatalf("Unable to create temporary data path %v\n", err)
|
||||
testlogger.Panicf("Unable to create temporary data path %v\n", err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
||||
setting.AppDataPath = tmpDataPath
|
||||
|
||||
unittest.InitSettingsForTesting()
|
||||
if err = git.InitFull(); err != nil {
|
||||
testlogger.Fatalf("Unable to InitFull: %v\n", err)
|
||||
testlogger.Panicf("Unable to InitFull: %v\n", err)
|
||||
}
|
||||
setting.LoadDBSetting()
|
||||
setting.InitLoggersForTest()
|
||||
|
||||
exitStatus := m.Run()
|
||||
|
||||
if err := removeAllWithRetry(setting.RepoRootPath); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
|
||||
}
|
||||
os.Exit(exitStatus)
|
||||
return m.Run()
|
||||
}
|
||||
|
||||
func MainTest(m *testing.M) {
|
||||
os.Exit(mainTest(m))
|
||||
}
|
||||
|
||||
+29
@@ -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
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
# type Repository struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# OwnerName string
|
||||
# Name string
|
||||
# }
|
||||
-
|
||||
id: 100
|
||||
owner_name: testuser
|
||||
name: repo1
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"code.gitea.io/gitea/models/migrations/v1_23"
|
||||
"code.gitea.io/gitea/models/migrations/v1_24"
|
||||
"code.gitea.io/gitea/models/migrations/v1_25"
|
||||
"code.gitea.io/gitea/models/migrations/v1_26"
|
||||
"code.gitea.io/gitea/models/migrations/v1_6"
|
||||
"code.gitea.io/gitea/models/migrations/v1_7"
|
||||
"code.gitea.io/gitea/models/migrations/v1_8"
|
||||
@@ -379,8 +380,8 @@ func prepareMigrationTasks() []*migration {
|
||||
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),
|
||||
@@ -390,10 +391,20 @@ func prepareMigrationTasks() []*migration {
|
||||
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)
|
||||
|
||||
// Gitea 1.24.0 ends at 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),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
@@ -84,17 +84,17 @@ func FixMergeBase(ctx context.Context, x *xorm.Engine) error {
|
||||
|
||||
if !pr.HasMerged {
|
||||
var err error
|
||||
pr.MergeBase, _, err = gitcmd.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
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).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
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).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
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
|
||||
@@ -108,7 +108,7 @@ func FixMergeBase(ctx context.Context, x *xorm.Engine) error {
|
||||
refs = append(refs, gitRefName)
|
||||
cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...)
|
||||
|
||||
pr.MergeBase, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
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
|
||||
|
||||
@@ -80,7 +80,7 @@ func RefixMergeBase(ctx context.Context, x *xorm.Engine) error {
|
||||
|
||||
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
|
||||
|
||||
parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
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
|
||||
@@ -95,7 +95,7 @@ func RefixMergeBase(ctx context.Context, x *xorm.Engine) error {
|
||||
refs = append(refs, gitRefName)
|
||||
cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...)
|
||||
|
||||
pr.MergeBase, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
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
|
||||
|
||||
@@ -6,11 +6,10 @@ package v1_12
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -85,12 +84,9 @@ func AddCommitDivergenceToPulls(x *xorm.Engine) error {
|
||||
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")
|
||||
|
||||
repoStore := repo_model.StorageRepo(repo_model.RelativePath(baseRepo.OwnerName, baseRepo.Name))
|
||||
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
|
||||
|
||||
divergence, err := git.GetDivergingCommits(graceful.GetManager().HammerContext(), repoPath, pr.BaseBranch, gitRefName)
|
||||
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
|
||||
|
||||
@@ -5,14 +5,10 @@ package v1_21
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
giturl "code.gitea.io/gitea/modules/git/url"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
@@ -163,16 +159,13 @@ func migratePushMirrors(x *xorm.Engine) error {
|
||||
}
|
||||
|
||||
func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
|
||||
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(ownerName), strings.ToLower(repoName)+".git")
|
||||
if exist, _ := util.IsExist(repoPath); !exist {
|
||||
ctx := context.Background()
|
||||
relativePath := repo_model.RelativePath(ownerName, repoName)
|
||||
if exist, _ := gitrepo.IsRepositoryExist(ctx, repo_model.StorageRepo(relativePath)); !exist {
|
||||
return "", nil
|
||||
}
|
||||
remoteURL, err := git.GetRemoteAddress(context.Background(), repoPath, remoteName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err)
|
||||
}
|
||||
|
||||
u, err := giturl.ParseGitURL(remoteURL)
|
||||
u, err := gitrepo.GitRemoteGetURL(ctx, repo_model.StorageRepo(relativePath), remoteName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_UseLongTextInSomeColumnsAndFixBugs(t *testing.T) {
|
||||
@@ -38,33 +39,26 @@ func Test_UseLongTextInSomeColumnsAndFixBugs(t *testing.T) {
|
||||
type Notice struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type int
|
||||
Description string `xorm:"LONGTEXT"`
|
||||
Description string `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
}
|
||||
|
||||
// Prepare and load the testing database
|
||||
x, deferable := base.PrepareTestEnv(t, 0, new(ReviewState), new(PackageProperty), new(Notice))
|
||||
defer deferable()
|
||||
x, deferrable := base.PrepareTestEnv(t, 0, new(ReviewState), new(PackageProperty), new(Notice))
|
||||
defer deferrable()
|
||||
|
||||
assert.NoError(t, UseLongTextInSomeColumnsAndFixBugs(x))
|
||||
require.NoError(t, UseLongTextInSomeColumnsAndFixBugs(x))
|
||||
|
||||
tables, err := x.DBMetas()
|
||||
assert.NoError(t, err)
|
||||
tables := base.LoadTableSchemasMap(t, x)
|
||||
table := tables["review_state"]
|
||||
column := table.GetColumn("updated_files")
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
|
||||
for _, table := range tables {
|
||||
switch table.Name {
|
||||
case "review_state":
|
||||
column := table.GetColumn("updated_files")
|
||||
assert.NotNil(t, column)
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
case "package_property":
|
||||
column := table.GetColumn("value")
|
||||
assert.NotNil(t, column)
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
case "notice":
|
||||
column := table.GetColumn("description")
|
||||
assert.NotNil(t, column)
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
}
|
||||
}
|
||||
table = tables["package_property"]
|
||||
column = table.GetColumn("value")
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
|
||||
table = tables["notice"]
|
||||
column = table.GetColumn("description")
|
||||
assert.Equal(t, "LONGTEXT", column.SQLType.Name)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_25
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_ExtendCommentTreePathLength(t *testing.T) {
|
||||
if setting.Database.Type.IsSQLite3() {
|
||||
t.Skip("For SQLITE, varchar or char will always be represented as TEXT")
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
TreePath string `xorm:"VARCHAR(255)"`
|
||||
}
|
||||
|
||||
x, deferrable := base.PrepareTestEnv(t, 0, new(Comment))
|
||||
defer deferrable()
|
||||
|
||||
require.NoError(t, ExtendCommentTreePathLength(x))
|
||||
table := base.LoadTableSchemasMap(t, x)["comment"]
|
||||
column := table.GetColumn("tree_path")
|
||||
assert.Contains(t, []string{"NVARCHAR", "VARCHAR"}, column.SQLType.Name)
|
||||
assert.EqualValues(t, 4000, column.Length)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
base.MainTest(m)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddActionsConcurrency(x *xorm.Engine) error {
|
||||
type ActionRun struct {
|
||||
RepoID int64 `xorm:"index(repo_concurrency)"`
|
||||
RawConcurrency string
|
||||
ConcurrencyGroup string `xorm:"index(repo_concurrency) NOT NULL DEFAULT ''"`
|
||||
ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"`
|
||||
}
|
||||
|
||||
if _, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
}, new(ActionRun)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := x.Sync(new(ActionRun)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type ActionRunJob struct {
|
||||
RepoID int64 `xorm:"index(repo_concurrency)"`
|
||||
RawConcurrency string
|
||||
IsConcurrencyEvaluated bool
|
||||
ConcurrencyGroup string `xorm:"index(repo_concurrency) NOT NULL DEFAULT ''"`
|
||||
ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"`
|
||||
}
|
||||
|
||||
if _, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
}, new(ActionRunJob)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func FixClosedMilestoneCompleteness(x *xorm.Engine) error {
|
||||
// Update all milestones to recalculate completeness with the new logic:
|
||||
// - Closed milestones with 0 issues should show 100%
|
||||
// - All other milestones should calculate based on closed/total ratio
|
||||
_, err := x.Exec("UPDATE `milestone` SET completeness=(CASE WHEN is_closed = ? AND num_issues = 0 THEN 100 ELSE 100*num_closed_issues/(CASE WHEN num_issues > 0 THEN num_issues ELSE 1 END) END)",
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating milestone completeness: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func FixMissedRepoIDWhenMigrateAttachments(x *xorm.Engine) error {
|
||||
_, err := x.Exec("UPDATE `attachment` SET `repo_id` = (SELECT `repo_id` FROM `issue` WHERE `issue`.`id` = `attachment`.`issue_id`) WHERE `issue_id` > 0 AND (`repo_id` IS NULL OR `repo_id` = 0);")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = x.Exec("UPDATE `attachment` SET `repo_id` = (SELECT `repo_id` FROM `release` WHERE `release`.`id` = `attachment`.`release_id`) WHERE `release_id` > 0 AND (`repo_id` IS NULL OR `repo_id` = 0);")
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_FixMissedRepoIDWhenMigrateAttachments(t *testing.T) {
|
||||
type Attachment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
RepoID int64 `xorm:"INDEX"` // this should not be zero
|
||||
IssueID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
UploaderID int64 `xorm:"INDEX DEFAULT 0"` // Notice: will be zero before this column added
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
DownloadCount int64 `xorm:"DEFAULT 0"`
|
||||
Size int64 `xorm:"DEFAULT 0"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
// Prepare and load the testing database
|
||||
x, deferrable := base.PrepareTestEnv(t, 0, new(Attachment), new(Issue), new(Release))
|
||||
defer deferrable()
|
||||
|
||||
require.NoError(t, FixMissedRepoIDWhenMigrateAttachments(x))
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
const (
|
||||
actionsRunPath = "/actions/runs/"
|
||||
|
||||
// Only commit status target URLs whose resolved run ID is smaller than this threshold are rewritten by this partial migration.
|
||||
// The fixed value 1000 is a conservative cutoff chosen to cover the smaller legacy run indexes that are most likely to be confused with ID-based URLs at runtime.
|
||||
// Larger legacy {run} or {job} numbers are usually easier to disambiguate. For example:
|
||||
// * /actions/runs/1200/jobs/1420 is most likely an ID-based URL, because a run should not contain more than 256 jobs.
|
||||
// * /actions/runs/1500/jobs/3 is most likely an index-based URL, because a job ID cannot be smaller than its run ID.
|
||||
// But URLs with small numbers, such as /actions/runs/5/jobs/6, are much harder to distinguish reliably.
|
||||
// This migration therefore prioritizes rewriting target URLs for runs in that lower range.
|
||||
legacyURLIDThreshold int64 = 1000
|
||||
)
|
||||
|
||||
type migrationRepository struct {
|
||||
ID int64
|
||||
OwnerName string
|
||||
Name string
|
||||
}
|
||||
|
||||
type migrationActionRun struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
Index int64
|
||||
CommitSHA string `xorm:"commit_sha"`
|
||||
Event webhook_module.HookEventType
|
||||
TriggerEvent string
|
||||
EventPayload string
|
||||
}
|
||||
|
||||
type migrationActionRunJob struct {
|
||||
ID int64
|
||||
RunID int64
|
||||
}
|
||||
|
||||
type migrationCommitStatus struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
TargetURL string
|
||||
}
|
||||
|
||||
type commitSHAAndRuns struct {
|
||||
commitSHA string
|
||||
runs map[int64]*migrationActionRun
|
||||
}
|
||||
|
||||
// FixCommitStatusTargetURLToUseRunAndJobID partially migrates legacy Actions
|
||||
// commit status target URLs to the new run/job ID-based form.
|
||||
//
|
||||
// Only rows whose resolved run ID is below legacyURLIDThreshold are rewritten.
|
||||
// This is because smaller legacy run indexes are more likely to collide with run ID URLs during runtime resolution,
|
||||
// so this migration prioritizes that lower range and leaves the remaining legacy target URLs to the web compatibility logic.
|
||||
func FixCommitStatusTargetURLToUseRunAndJobID(x *xorm.Engine) error {
|
||||
jobsByRunIDCache := make(map[int64][]int64)
|
||||
repoLinkCache := make(map[int64]string)
|
||||
groups, err := loadLegacyMigrationRunGroups(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for repoID, groupsBySHA := range groups {
|
||||
for _, group := range groupsBySHA {
|
||||
if err := migrateCommitStatusTargetURLForGroup(x, "commit_status", repoID, group.commitSHA, group.runs, jobsByRunIDCache, repoLinkCache); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := migrateCommitStatusTargetURLForGroup(x, "commit_status_summary", repoID, group.commitSHA, group.runs, jobsByRunIDCache, repoLinkCache); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadLegacyMigrationRunGroups(x *xorm.Engine) (map[int64]map[string]*commitSHAAndRuns, error) {
|
||||
var runs []migrationActionRun
|
||||
if err := x.Table("action_run").
|
||||
Where("id < ?", legacyURLIDThreshold).
|
||||
Cols("id", "repo_id", "`index`", "commit_sha", "event", "trigger_event", "event_payload").
|
||||
Find(&runs); err != nil {
|
||||
return nil, fmt.Errorf("query action_run: %w", err)
|
||||
}
|
||||
|
||||
groups := make(map[int64]map[string]*commitSHAAndRuns)
|
||||
for i := range runs {
|
||||
run := runs[i]
|
||||
commitID, err := getCommitStatusCommitID(&run)
|
||||
if err != nil {
|
||||
log.Warn("skip action_run id=%d when resolving commit status commit SHA: %v", run.ID, err)
|
||||
continue
|
||||
}
|
||||
if commitID == "" {
|
||||
// empty commitID means the run didn't create any commit status records, just skip
|
||||
continue
|
||||
}
|
||||
if groups[run.RepoID] == nil {
|
||||
groups[run.RepoID] = make(map[string]*commitSHAAndRuns)
|
||||
}
|
||||
if groups[run.RepoID][commitID] == nil {
|
||||
groups[run.RepoID][commitID] = &commitSHAAndRuns{
|
||||
commitSHA: commitID,
|
||||
runs: make(map[int64]*migrationActionRun),
|
||||
}
|
||||
}
|
||||
groups[run.RepoID][commitID].runs[run.Index] = &run
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func migrateCommitStatusTargetURLForGroup(
|
||||
x *xorm.Engine,
|
||||
table string,
|
||||
repoID int64,
|
||||
sha string,
|
||||
runs map[int64]*migrationActionRun,
|
||||
jobsByRunIDCache map[int64][]int64,
|
||||
repoLinkCache map[int64]string,
|
||||
) error {
|
||||
var rows []migrationCommitStatus
|
||||
if err := x.Table(table).
|
||||
Where("repo_id = ?", repoID).
|
||||
And("sha = ?", sha).
|
||||
Cols("id", "repo_id", "target_url").
|
||||
Find(&rows); err != nil {
|
||||
return fmt.Errorf("query %s for repo_id=%d sha=%s: %w", table, repoID, sha, err)
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
repoLink, err := getRepoLinkCached(x, repoLinkCache, row.RepoID)
|
||||
if err != nil || repoLink == "" {
|
||||
if err != nil {
|
||||
log.Warn("convert %s id=%d getRepoLinkCached: %v", table, row.ID, err)
|
||||
} else {
|
||||
log.Warn("convert %s id=%d: repo=%d not found", table, row.ID, row.RepoID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
runNum, jobNum, ok := parseTargetURL(row.TargetURL, repoLink)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
run, ok := runs[runNum]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
jobID, ok, err := getJobIDByIndexCached(x, jobsByRunIDCache, run.ID, jobNum)
|
||||
if err != nil || !ok {
|
||||
if err != nil {
|
||||
log.Warn("convert %s id=%d getJobIDByIndexCached: %v", table, row.ID, err)
|
||||
} else {
|
||||
log.Warn("convert %s id=%d: job not found for run_id=%d job_index=%d", table, row.ID, run.ID, jobNum)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
oldURL := row.TargetURL
|
||||
newURL := fmt.Sprintf("%s%s%d/jobs/%d", repoLink, actionsRunPath, run.ID, jobID)
|
||||
if oldURL == newURL {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := x.Table(table).ID(row.ID).Cols("target_url").Update(&migrationCommitStatus{TargetURL: newURL}); err != nil {
|
||||
return fmt.Errorf("update %s id=%d target_url from %s to %s: %w", table, row.ID, oldURL, newURL, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRepoLinkCached(x *xorm.Engine, cache map[int64]string, repoID int64) (string, error) {
|
||||
if link, ok := cache[repoID]; ok {
|
||||
return link, nil
|
||||
}
|
||||
repo := &migrationRepository{}
|
||||
has, err := x.Table("repository").Where("id=?", repoID).Get(repo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !has {
|
||||
cache[repoID] = ""
|
||||
return "", nil
|
||||
}
|
||||
link := setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
|
||||
cache[repoID] = link
|
||||
return link, nil
|
||||
}
|
||||
|
||||
func getJobIDByIndexCached(x *xorm.Engine, cache map[int64][]int64, runID, jobIndex int64) (int64, bool, error) {
|
||||
jobIDs, ok := cache[runID]
|
||||
if !ok {
|
||||
var jobs []migrationActionRunJob
|
||||
if err := x.Table("action_run_job").Where("run_id=?", runID).Asc("id").Cols("id").Find(&jobs); err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
jobIDs = make([]int64, 0, len(jobs))
|
||||
for _, job := range jobs {
|
||||
jobIDs = append(jobIDs, job.ID)
|
||||
}
|
||||
cache[runID] = jobIDs
|
||||
}
|
||||
if jobIndex < 0 || jobIndex >= int64(len(jobIDs)) {
|
||||
return 0, false, nil
|
||||
}
|
||||
return jobIDs[jobIndex], true, nil
|
||||
}
|
||||
|
||||
func parseTargetURL(targetURL, repoLink string) (runNum, jobNum int64, ok bool) {
|
||||
prefix := repoLink + actionsRunPath
|
||||
if !strings.HasPrefix(targetURL, prefix) {
|
||||
return 0, 0, false
|
||||
}
|
||||
rest := targetURL[len(prefix):]
|
||||
|
||||
parts := strings.Split(rest, "/")
|
||||
if len(parts) == 3 && parts[1] == "jobs" {
|
||||
runNum, err1 := strconv.ParseInt(parts[0], 10, 64)
|
||||
jobNum, err2 := strconv.ParseInt(parts[2], 10, 64)
|
||||
if err1 != nil || err2 != nil {
|
||||
return 0, 0, false
|
||||
}
|
||||
return runNum, jobNum, true
|
||||
}
|
||||
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
func getCommitStatusCommitID(run *migrationActionRun) (string, error) {
|
||||
switch run.Event {
|
||||
case webhook_module.HookEventPush:
|
||||
payload, err := getPushEventPayload(run)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getPushEventPayload: %w", err)
|
||||
}
|
||||
if payload.HeadCommit == nil {
|
||||
return "", errors.New("head commit is missing in event payload")
|
||||
}
|
||||
return payload.HeadCommit.ID, nil
|
||||
case webhook_module.HookEventPullRequest,
|
||||
webhook_module.HookEventPullRequestSync,
|
||||
webhook_module.HookEventPullRequestAssign,
|
||||
webhook_module.HookEventPullRequestLabel,
|
||||
webhook_module.HookEventPullRequestReviewRequest,
|
||||
webhook_module.HookEventPullRequestMilestone:
|
||||
payload, err := getPullRequestEventPayload(run)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getPullRequestEventPayload: %w", err)
|
||||
}
|
||||
if payload.PullRequest == nil {
|
||||
return "", errors.New("pull request is missing in event payload")
|
||||
} else if payload.PullRequest.Head == nil {
|
||||
return "", errors.New("head of pull request is missing in event payload")
|
||||
}
|
||||
return payload.PullRequest.Head.Sha, nil
|
||||
case webhook_module.HookEventPullRequestReviewApproved,
|
||||
webhook_module.HookEventPullRequestReviewRejected,
|
||||
webhook_module.HookEventPullRequestReviewComment:
|
||||
payload, err := getPullRequestEventPayload(run)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getPullRequestEventPayload: %w", err)
|
||||
}
|
||||
if payload.PullRequest == nil {
|
||||
return "", errors.New("pull request is missing in event payload")
|
||||
} else if payload.PullRequest.Head == nil {
|
||||
return "", errors.New("head of pull request is missing in event payload")
|
||||
}
|
||||
return payload.PullRequest.Head.Sha, nil
|
||||
case webhook_module.HookEventRelease:
|
||||
return run.CommitSHA, nil
|
||||
default:
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
func getPushEventPayload(run *migrationActionRun) (*api.PushPayload, error) {
|
||||
if run.Event != webhook_module.HookEventPush {
|
||||
return nil, fmt.Errorf("event %s is not a push event", run.Event)
|
||||
}
|
||||
var payload api.PushPayload
|
||||
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func getPullRequestEventPayload(run *migrationActionRun) (*api.PullRequestPayload, error) {
|
||||
if !run.Event.IsPullRequest() && !run.Event.IsPullRequestReview() {
|
||||
return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
|
||||
}
|
||||
var payload api.PullRequestPayload
|
||||
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &payload, nil
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
_ "code.gitea.io/gitea/models/actions"
|
||||
_ "code.gitea.io/gitea/models/git"
|
||||
_ "code.gitea.io/gitea/models/repo"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func Test_FixCommitStatusTargetURLToUseRunAndJobID(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
||||
|
||||
type Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerName string
|
||||
Name string
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type ActionRunJob struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RunID int64 `xorm:"index"`
|
||||
}
|
||||
|
||||
type CommitStatus struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"index"`
|
||||
SHA string
|
||||
TargetURL string
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
x, deferable := base.PrepareTestEnv(t, 0,
|
||||
new(Repository),
|
||||
new(ActionRun),
|
||||
new(ActionRunJob),
|
||||
new(CommitStatus),
|
||||
new(CommitStatusSummary),
|
||||
)
|
||||
defer deferable()
|
||||
|
||||
require.NoError(t, FixCommitStatusTargetURLToUseRunAndJobID(x))
|
||||
|
||||
cases := []struct {
|
||||
table string
|
||||
id int64
|
||||
want string
|
||||
}{
|
||||
// Legacy URLs for runs whose resolved run IDs are below the threshold should be rewritten.
|
||||
{table: "commit_status", id: 10010, want: "/testuser/repo1/actions/runs/990/jobs/997"},
|
||||
{table: "commit_status", id: 10011, want: "/testuser/repo1/actions/runs/990/jobs/998"},
|
||||
{table: "commit_status", id: 10012, want: "/testuser/repo1/actions/runs/991/jobs/1997"},
|
||||
|
||||
// Runs whose resolved IDs are above the threshold are intentionally left unchanged.
|
||||
{table: "commit_status", id: 10013, want: "/testuser/repo1/actions/runs/9/jobs/0"},
|
||||
|
||||
// URLs that do not resolve cleanly as legacy Actions URLs should remain untouched.
|
||||
{table: "commit_status", id: 10014, want: "/otheruser/badrepo/actions/runs/7/jobs/0"},
|
||||
{table: "commit_status", id: 10015, want: "/testuser/repo1/actions/runs/10/jobs/0"},
|
||||
{table: "commit_status", id: 10016, want: "/testuser/repo1/actions/runs/7/jobs/3"},
|
||||
{table: "commit_status", id: 10017, want: "https://ci.example.com/build/123"},
|
||||
|
||||
// Already ID-based URLs are valid inputs and should not be rewritten again.
|
||||
{table: "commit_status", id: 10018, want: "/testuser/repo1/actions/runs/990/jobs/997"},
|
||||
|
||||
// The same rewrite rules apply to commit_status_summary rows.
|
||||
{table: "commit_status_summary", id: 10020, want: "/testuser/repo1/actions/runs/990/jobs/997"},
|
||||
{table: "commit_status_summary", id: 10021, want: "/testuser/repo1/actions/runs/9/jobs/0"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
assertTargetURL(t, x, tc.table, tc.id, tc.want)
|
||||
}
|
||||
}
|
||||
|
||||
func assertTargetURL(t *testing.T, x *xorm.Engine, table string, id int64, want string) {
|
||||
t.Helper()
|
||||
|
||||
var row struct {
|
||||
TargetURL string
|
||||
}
|
||||
has, err := x.Table(table).Where("id=?", id).Cols("target_url").Get(&row)
|
||||
require.NoError(t, err)
|
||||
require.Truef(t, has, "row not found: table=%s id=%d", table, id)
|
||||
require.Equal(t, want, row.TargetURL)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
||||
func AddDisabledToActionRunner(x *xorm.Engine) error {
|
||||
type ActionRunner struct {
|
||||
IsDisabled bool `xorm:"is_disabled NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
}, new(ActionRunner))
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_AddDisabledToActionRunner(t *testing.T) {
|
||||
type ActionRunner struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Name string
|
||||
}
|
||||
|
||||
x, deferable := base.PrepareTestEnv(t, 0, new(ActionRunner))
|
||||
defer deferable()
|
||||
|
||||
_, err := x.Insert(&ActionRunner{Name: "runner"})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, AddDisabledToActionRunner(x))
|
||||
|
||||
var isDisabled bool
|
||||
has, err := x.SQL("SELECT is_disabled FROM action_runner WHERE id = ?", 1).Get(&isDisabled)
|
||||
require.NoError(t, err)
|
||||
require.True(t, has)
|
||||
require.False(t, isDisabled)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddTokenPermissionsToActionRunJob(x *xorm.Engine) error {
|
||||
type ActionRunJob struct {
|
||||
TokenPermissions string `xorm:"JSON TEXT"`
|
||||
}
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(ActionRunJob))
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type UserBadge struct { //revive:disable-line:exported
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
BadgeID int64
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// TableIndices implements xorm's TableIndices interface
|
||||
func (n *UserBadge) TableIndices() []*schemas.Index {
|
||||
indices := make([]*schemas.Index, 0, 1)
|
||||
ubUnique := schemas.NewIndex("unique_user_badge", schemas.UniqueType)
|
||||
ubUnique.AddColumn("user_id", "badge_id")
|
||||
indices = append(indices, ubUnique)
|
||||
return indices
|
||||
}
|
||||
|
||||
// AddUniqueIndexForUserBadge adds a compound unique indexes for user badge table
|
||||
// and it replaces an old index on user_id
|
||||
func AddUniqueIndexForUserBadge(x *xorm.Engine) error {
|
||||
// remove possible duplicated records in table user_badge
|
||||
type result struct {
|
||||
UserID int64
|
||||
BadgeID int64
|
||||
Cnt int
|
||||
}
|
||||
var results []result
|
||||
if err := x.Select("user_id, badge_id, count(*) as cnt").
|
||||
Table("user_badge").
|
||||
GroupBy("user_id, badge_id").
|
||||
Having("count(*) > 1").
|
||||
Find(&results); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range results {
|
||||
if x.Dialect().URI().DBType == schemas.MSSQL {
|
||||
if _, err := x.Exec(fmt.Sprintf("delete from user_badge where id in (SELECT top %d id FROM user_badge WHERE user_id = ? and badge_id = ?)", r.Cnt-1), r.UserID, r.BadgeID); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var ids []int64
|
||||
if err := x.SQL("SELECT id FROM user_badge WHERE user_id = ? and badge_id = ? limit ?", r.UserID, r.BadgeID, r.Cnt-1).Find(&ids); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := x.Table("user_badge").In("id", ids).Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return x.Sync(new(UserBadge))
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type UserBadgeBefore struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
BadgeID int64
|
||||
UserID int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
func (UserBadgeBefore) TableName() string {
|
||||
return "user_badge"
|
||||
}
|
||||
|
||||
func Test_AddUniqueIndexForUserBadge(t *testing.T) {
|
||||
x, deferable := base.PrepareTestEnv(t, 0, new(UserBadgeBefore))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
testData := []*UserBadgeBefore{
|
||||
{UserID: 1, BadgeID: 1},
|
||||
{UserID: 1, BadgeID: 1}, // duplicate
|
||||
{UserID: 2, BadgeID: 1},
|
||||
{UserID: 1, BadgeID: 2},
|
||||
{UserID: 3, BadgeID: 3},
|
||||
{UserID: 3, BadgeID: 3}, // duplicate
|
||||
}
|
||||
|
||||
for _, data := range testData {
|
||||
_, err := x.Insert(data)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// check that we have duplicates
|
||||
count, err := x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(2), count)
|
||||
|
||||
count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(2), count)
|
||||
|
||||
totalCount, err := x.Count(&UserBadgeBefore{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(6), totalCount)
|
||||
|
||||
// run the migration
|
||||
if err := AddUniqueIndexForUserBadge(x); err != nil {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
// verify the duplicates were removed
|
||||
count, err = x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1), count)
|
||||
|
||||
count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1), count)
|
||||
|
||||
// check total count
|
||||
totalCount, err = x.Count(&UserBadgeBefore{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(4), totalCount)
|
||||
|
||||
// fail to insert a duplicate
|
||||
_, err = x.Insert(&UserBadge{UserID: 1, BadgeID: 1})
|
||||
assert.Error(t, err)
|
||||
|
||||
// succeed adding a non-duplicate
|
||||
_, err = x.Insert(&UserBadge{UserID: 4, BadgeID: 1})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddNameToWebhook(x *xorm.Engine) error {
|
||||
type Webhook struct {
|
||||
Name string `xorm:"VARCHAR(255) NOT NULL DEFAULT ''"`
|
||||
}
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(Webhook))
|
||||
return err
|
||||
}
|
||||
@@ -6,11 +6,10 @@ package v1_9
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
@@ -34,16 +33,6 @@ func FixReleaseSha1OnReleaseTable(ctx context.Context, x *xorm.Engine) error {
|
||||
Name string
|
||||
}
|
||||
|
||||
// UserPath returns the path absolute path of user repositories.
|
||||
UserPath := func(userName string) string {
|
||||
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
|
||||
}
|
||||
|
||||
// RepoPath returns repository path by given user and repository name.
|
||||
RepoPath := func(userName, repoName string) string {
|
||||
return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git")
|
||||
}
|
||||
|
||||
// Update release sha1
|
||||
const batchSize = 100
|
||||
sess := x.NewSession()
|
||||
@@ -99,7 +88,7 @@ func FixReleaseSha1OnReleaseTable(ctx context.Context, x *xorm.Engine) error {
|
||||
userCache[repo.OwnerID] = user
|
||||
}
|
||||
|
||||
gitRepo, err = git.OpenRepository(ctx, RepoPath(user.Name, repo.Name))
|
||||
gitRepo, err = gitrepo.OpenRepository(ctx, repo_model.StorageRepo(repo_model.RelativePath(user.Name, repo.Name)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user