mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-08 14:03:24 +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,14 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
migrationtest.MainTest(m)
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type actionRunAttempt struct {
|
||||
ID int64
|
||||
RepoID int64 `xorm:"index(repo_concurrency_status)"`
|
||||
RunID int64 `xorm:"UNIQUE(run_attempt)"`
|
||||
Attempt int64 `xorm:"UNIQUE(run_attempt)"`
|
||||
TriggerUserID int64
|
||||
ConcurrencyGroup string `xorm:"index(repo_concurrency_status) NOT NULL DEFAULT ''"`
|
||||
ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"`
|
||||
Status int `xorm:"index(repo_concurrency_status)"`
|
||||
Started timeutil.TimeStamp
|
||||
Stopped timeutil.TimeStamp
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
func (actionRunAttempt) TableName() string {
|
||||
return "action_run_attempt"
|
||||
}
|
||||
|
||||
type actionArtifact struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RunID int64 `xorm:"index unique(runid_attempt_name_path)"`
|
||||
RunAttemptID int64 `xorm:"index unique(runid_attempt_name_path) NOT NULL DEFAULT 0"`
|
||||
RunnerID int64
|
||||
RepoID int64 `xorm:"index"`
|
||||
OwnerID int64
|
||||
CommitSHA string
|
||||
StoragePath string
|
||||
FileSize int64
|
||||
FileCompressedSize int64
|
||||
ContentEncoding string `xorm:"content_encoding"`
|
||||
ArtifactPath string `xorm:"index unique(runid_attempt_name_path)"`
|
||||
ArtifactName string `xorm:"index unique(runid_attempt_name_path)"`
|
||||
Status int `xorm:"index"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated index"`
|
||||
ExpiredUnix timeutil.TimeStamp `xorm:"index"`
|
||||
}
|
||||
|
||||
func (actionArtifact) TableName() string {
|
||||
return "action_artifact"
|
||||
}
|
||||
|
||||
// actionRun mirrors the post-migration action_run schema.
|
||||
type actionRun struct {
|
||||
ID int64
|
||||
Title string
|
||||
RepoID int64 `xorm:"unique(repo_index)"`
|
||||
OwnerID int64 `xorm:"index"`
|
||||
WorkflowID string `xorm:"index"`
|
||||
Index int64 `xorm:"index unique(repo_index)"`
|
||||
TriggerUserID int64 `xorm:"index"`
|
||||
ScheduleID int64
|
||||
Ref string `xorm:"index"`
|
||||
CommitSHA string
|
||||
IsForkPullRequest bool
|
||||
NeedApproval bool
|
||||
ApprovedBy int64 `xorm:"index"`
|
||||
Event string
|
||||
EventPayload string `xorm:"LONGTEXT"`
|
||||
TriggerEvent string
|
||||
Status int `xorm:"index"`
|
||||
Version int `xorm:"version default 0"`
|
||||
RawConcurrency string
|
||||
Started timeutil.TimeStamp
|
||||
Stopped timeutil.TimeStamp
|
||||
PreviousDuration time.Duration
|
||||
LatestAttemptID int64 `xorm:"index NOT NULL DEFAULT 0"`
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
func (actionRun) TableName() string {
|
||||
return "action_run"
|
||||
}
|
||||
|
||||
// AddActionRunAttemptModel adds the ActionRunAttempt table and the supporting ActionRun/ActionRunJob fields.
|
||||
func AddActionRunAttemptModel(x base.EngineMigration) error {
|
||||
// add "action_run_attempt"
|
||||
if _, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
}, new(actionRunAttempt)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update "action_run_job"
|
||||
type ActionRunJob struct {
|
||||
RunAttemptID int64 `xorm:"index NOT NULL DEFAULT 0"`
|
||||
AttemptJobID int64 `xorm:"index NOT NULL DEFAULT 0"`
|
||||
SourceTaskID int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
if _, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
}, new(ActionRunJob)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update "action_artifact": let xorm sync add the new 4-column unique index (runid_attempt_name_path) and drop the old 3-column unique (runid_name_path)
|
||||
if err := x.Sync(new(actionArtifact)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update "action_run"
|
||||
//
|
||||
// This migration intentionally removes the legacy run-level concurrency columns after
|
||||
// introducing attempt-level concurrency on action_run_attempt.
|
||||
//
|
||||
// Existing values from action_run.concurrency_group / action_run.concurrency_cancel are
|
||||
// not backfilled into action_run_attempt:
|
||||
// - the old fields are only meaningful while a run is actively participating in
|
||||
// concurrency scheduling
|
||||
// - for completed legacy runs, keeping or backfilling those values has no practical
|
||||
// effect on future scheduling behavior
|
||||
// - scanning and backfilling old runs would add significant migration cost for little value
|
||||
//
|
||||
// This means the schema change is destructive for those two legacy columns by design.
|
||||
//
|
||||
// Let xorm sync add the latest_attempt_id column and drop the now-orphan (repo_id, concurrency_group) index.
|
||||
if err := x.Sync(new(actionRun)); err != nil {
|
||||
return err
|
||||
}
|
||||
concurrencyColumns := make([]string, 0, 2)
|
||||
for _, col := range []string{"concurrency_group", "concurrency_cancel"} {
|
||||
exist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "action_run", col)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
concurrencyColumns = append(concurrencyColumns, col)
|
||||
}
|
||||
}
|
||||
if len(concurrencyColumns) == 0 {
|
||||
return nil
|
||||
}
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := base.DropTableColumns(sess, "action_run", concurrencyColumns...); err != nil {
|
||||
return err
|
||||
}
|
||||
// DropTableColumns rebuilds the table on SQLite, which drops all existing indexes.
|
||||
// Re-sync to restore the indexes defined on actionRun.
|
||||
return x.Sync(new(actionRun))
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type actionRunBeforeV331 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
ConcurrencyGroup string
|
||||
ConcurrencyCancel bool
|
||||
LatestAttemptID int64 `xorm:"-"`
|
||||
}
|
||||
|
||||
func (actionRunBeforeV331) TableName() string {
|
||||
return "action_run"
|
||||
}
|
||||
|
||||
type actionRunJobBeforeV331 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RunID int64 `xorm:"index"`
|
||||
RepoID int64 `xorm:"index"`
|
||||
}
|
||||
|
||||
func (actionRunJobBeforeV331) TableName() string {
|
||||
return "action_run_job"
|
||||
}
|
||||
|
||||
type actionArtifactBeforeV331 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RunID int64 `xorm:"index unique(runid_name_path)"`
|
||||
RepoID int64 `xorm:"index"`
|
||||
ArtifactPath string `xorm:"index unique(runid_name_path)"`
|
||||
ArtifactName string `xorm:"index unique(runid_name_path)"`
|
||||
}
|
||||
|
||||
func (actionArtifactBeforeV331) TableName() string {
|
||||
return "action_artifact"
|
||||
}
|
||||
|
||||
func Test_AddActionRunAttemptModel(t *testing.T) {
|
||||
x, deferable := migrationtest.PrepareTestEnv(t, 0,
|
||||
new(actionRunBeforeV331),
|
||||
new(actionRunJobBeforeV331),
|
||||
new(actionArtifactBeforeV331),
|
||||
)
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
_, err := x.Insert(&actionArtifactBeforeV331{
|
||||
RunID: 1,
|
||||
RepoID: 1,
|
||||
ArtifactPath: "artifact/path",
|
||||
ArtifactName: "artifact-name",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, AddActionRunAttemptModel(x))
|
||||
|
||||
tableMap := migrationtest.LoadTableSchemasMap(t, x)
|
||||
|
||||
attemptTable := tableMap["action_run_attempt"]
|
||||
require.NotNil(t, attemptTable)
|
||||
attemptTablCols := []string{"id", "repo_id", "run_id", "attempt", "trigger_user_id", "status", "started", "stopped", "concurrency_group", "concurrency_cancel", "created", "updated"}
|
||||
require.ElementsMatch(t, attemptTable.ColumnsSeq(), attemptTablCols)
|
||||
|
||||
runTable := tableMap["action_run"]
|
||||
require.NotNil(t, runTable)
|
||||
require.Contains(t, runTable.ColumnsSeq(), "latest_attempt_id")
|
||||
require.NotContains(t, runTable.ColumnsSeq(), "concurrency_group")
|
||||
require.NotContains(t, runTable.ColumnsSeq(), "concurrency_cancel")
|
||||
|
||||
jobTable := tableMap["action_run_job"]
|
||||
require.NotNil(t, jobTable)
|
||||
require.Contains(t, jobTable.ColumnsSeq(), "run_attempt_id")
|
||||
require.Contains(t, jobTable.ColumnsSeq(), "attempt_job_id")
|
||||
require.Contains(t, jobTable.ColumnsSeq(), "source_task_id")
|
||||
|
||||
attemptIndexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action_run_attempt")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, hasIndexWithColumns(attemptIndexes, []string{"run_id", "attempt"}, true))
|
||||
assert.True(t, hasIndexWithColumns(attemptIndexes, []string{"repo_id", "concurrency_group", "status"}, false))
|
||||
|
||||
runIndexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action_run")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, hasIndexWithColumns(runIndexes, []string{"latest_attempt_id"}, false))
|
||||
assert.False(t, hasIndexWithColumns(runIndexes, []string{"repo_id", "concurrency_group"}, false))
|
||||
|
||||
jobIndexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action_run_job")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, hasIndexWithColumns(jobIndexes, []string{"run_attempt_id"}, false))
|
||||
assert.True(t, hasIndexWithColumns(jobIndexes, []string{"attempt_job_id"}, false))
|
||||
|
||||
indexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action_artifact")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, hasIndexWithColumns(indexes, []string{"run_id", "artifact_path", "artifact_name"}, true))
|
||||
assert.True(t, hasIndexWithColumns(indexes, []string{"run_id", "run_attempt_id", "artifact_path", "artifact_name"}, true))
|
||||
|
||||
_, err = x.Insert(&actionArtifact{
|
||||
RunID: 1,
|
||||
RunAttemptID: 2,
|
||||
RepoID: 1,
|
||||
ArtifactPath: "artifact/path",
|
||||
ArtifactName: "artifact-name",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = x.Insert(&actionArtifact{
|
||||
RunID: 1,
|
||||
RunAttemptID: 2,
|
||||
RepoID: 1,
|
||||
ArtifactPath: "artifact/path",
|
||||
ArtifactName: "artifact-name",
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = x.Insert(&actionRunAttempt{
|
||||
RepoID: 1,
|
||||
RunID: 1,
|
||||
Attempt: 2,
|
||||
TriggerUserID: 1,
|
||||
Status: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = x.Insert(&actionRunAttempt{
|
||||
RepoID: 1,
|
||||
RunID: 1,
|
||||
Attempt: 2,
|
||||
TriggerUserID: 2,
|
||||
Status: 1,
|
||||
})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func hasIndexWithColumns(indexes map[string]*schemas.Index, cols []string, isUnique bool) bool {
|
||||
for _, index := range indexes {
|
||||
if isUnique && index.Type != schemas.UniqueType {
|
||||
continue
|
||||
}
|
||||
if slices.Equal(index.Cols, cols) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type mirrorWithLastSyncUnix struct {
|
||||
LastSyncUnix int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
func (mirrorWithLastSyncUnix) TableName() string {
|
||||
return "mirror"
|
||||
}
|
||||
|
||||
func AddLastSyncUnixToMirror(x base.EngineMigration) error {
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
}, new(mirrorWithLastSyncUnix))
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddBranchProtectionBypassAllowlist(x base.EngineMigration) error {
|
||||
type ProtectedBranch struct {
|
||||
EnableBypassAllowlist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
BypassAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
BypassAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
}
|
||||
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreConstrains: true,
|
||||
IgnoreIndices: true,
|
||||
}, new(ProtectedBranch))
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_AddBranchProtectionBypassAllowlist(t *testing.T) {
|
||||
type ProtectedBranch struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
BranchName string `xorm:"INDEX"`
|
||||
EnableBypassAllowlist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
BypassAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
BypassAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
}
|
||||
|
||||
x, deferable := migrationtest.PrepareTestEnv(t, 0, new(ProtectedBranch))
|
||||
defer deferable()
|
||||
|
||||
// Test with default values
|
||||
_, err := x.Insert(&ProtectedBranch{RepoID: 1, BranchName: "main"})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test with populated allowlist
|
||||
_, err = x.Insert(&ProtectedBranch{
|
||||
RepoID: 1,
|
||||
BranchName: "develop",
|
||||
EnableBypassAllowlist: true,
|
||||
BypassAllowlistUserIDs: []int64{1, 2, 3},
|
||||
BypassAllowlistTeamIDs: []int64{10, 20},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, AddBranchProtectionBypassAllowlist(x))
|
||||
|
||||
// Verify the default values record
|
||||
var pb ProtectedBranch
|
||||
has, err := x.Where("repo_id = ? AND branch_name = ?", 1, "main").Get(&pb)
|
||||
require.NoError(t, err)
|
||||
require.True(t, has)
|
||||
require.False(t, pb.EnableBypassAllowlist)
|
||||
require.Nil(t, pb.BypassAllowlistUserIDs)
|
||||
require.Nil(t, pb.BypassAllowlistTeamIDs)
|
||||
|
||||
// Verify the populated allowlist record
|
||||
var pb2 ProtectedBranch
|
||||
has, err = x.Where("repo_id = ? AND branch_name = ?", 1, "develop").Get(&pb2)
|
||||
require.NoError(t, err)
|
||||
require.True(t, has)
|
||||
require.True(t, pb2.EnableBypassAllowlist)
|
||||
require.Equal(t, []int64{1, 2, 3}, pb2.BypassAllowlistUserIDs)
|
||||
require.Equal(t, []int64{10, 20}, pb2.BypassAllowlistTeamIDs)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddCancellingSupportToActionRunner(x base.EngineMigration) error {
|
||||
type ActionRunner struct {
|
||||
HasCancellingSupport bool `xorm:"has_cancelling_support NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreConstrains: true,
|
||||
IgnoreDropIndices: true,
|
||||
}, new(ActionRunner))
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAddCancellingSupportToActionRunner(t *testing.T) {
|
||||
type ActionRunner struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Name string
|
||||
}
|
||||
|
||||
x, deferable := migrationtest.PrepareTestEnv(t, 0, new(ActionRunner))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
_, err := x.Insert(&ActionRunner{Name: "runner"})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, AddCancellingSupportToActionRunner(x))
|
||||
|
||||
var hasCancellingSupport bool
|
||||
has, err := x.SQL("SELECT has_cancelling_support FROM action_runner WHERE id = ?", 1).Get(&hasCancellingSupport)
|
||||
require.NoError(t, err)
|
||||
require.True(t, has)
|
||||
require.False(t, hasCancellingSupport)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/models/db"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// AddReusableWorkflowFieldsToActionRunJob adds the ActionRunJob columns that describe the reusable workflow caller hierarchy,
|
||||
// and the ActionRunAttemptJobIDIndex table backing run-wide AttemptJobID allocation.
|
||||
func AddReusableWorkflowFieldsToActionRunJob(x base.EngineMigration) error {
|
||||
type ActionRunJob struct {
|
||||
WorkflowSourceRepoID int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
WorkflowSourceCommitSHA string `xorm:"VARCHAR(64) NOT NULL DEFAULT ''"`
|
||||
IsReusableCaller bool `xorm:"index NOT NULL DEFAULT FALSE"`
|
||||
ParentJobID int64 `xorm:"index NOT NULL DEFAULT 0"`
|
||||
CallUses string `xorm:"VARCHAR(512) NOT NULL DEFAULT ''"`
|
||||
CallSecrets string `xorm:"LONGTEXT"`
|
||||
CallPayload string `xorm:"LONGTEXT"`
|
||||
IsExpanded bool `xorm:"NOT NULL DEFAULT FALSE"`
|
||||
ReusableWorkflowContent []byte `xorm:"LONGBLOB"`
|
||||
}
|
||||
|
||||
type ActionRunAttemptJobIDIndex db.ResourceIndex
|
||||
|
||||
if _, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(ActionRunJob)); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.Sync(new(ActionRunAttemptJobIDIndex))
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/timeutil"
|
||||
)
|
||||
|
||||
func AddActionRunJobSummaryTable(x base.EngineMigration) error {
|
||||
type ActionRunJobSummary struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
|
||||
RepoID int64 `xorm:"UNIQUE(summary_key)"`
|
||||
RunID int64 `xorm:"UNIQUE(summary_key)"`
|
||||
RunAttemptID int64 `xorm:"UNIQUE(summary_key) NOT NULL DEFAULT 0"`
|
||||
JobID int64 `xorm:"UNIQUE(summary_key)"`
|
||||
StepIndex int64 `xorm:"UNIQUE(summary_key)"`
|
||||
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
ContentType string `xorm:"VARCHAR(255) NOT NULL DEFAULT 'text/markdown'"`
|
||||
ContentSize int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
return x.Sync(new(ActionRunJobSummary))
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type VisibleType int
|
||||
|
||||
type teamWithVisibility struct {
|
||||
Visibility VisibleType `xorm:"NOT NULL DEFAULT 2"`
|
||||
}
|
||||
|
||||
func (teamWithVisibility) TableName() string {
|
||||
return "team"
|
||||
}
|
||||
|
||||
func AddVisibilityToTeam(x base.EngineMigration) error {
|
||||
if _, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
IgnoreConstrains: true,
|
||||
}, new(teamWithVisibility)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Owner teams must remain listable to all org members; new orgs create
|
||||
// them as "limited", so make existing owner teams limited too.
|
||||
// Filter on authorize=4 (AccessModeOwner) so a user-created team that
|
||||
// happens to share the name "owners" is not accidentally affected.
|
||||
_, err := x.Exec("UPDATE `team` SET visibility = ? WHERE lower_name = ? AND authorize = ?", 1, "owners", 4)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type issueWithLongTextContent struct {
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
}
|
||||
|
||||
func (issueWithLongTextContent) TableName() string {
|
||||
return "issue"
|
||||
}
|
||||
|
||||
type commentWithLongTextFields struct {
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
PatchQuoted string `xorm:"LONGTEXT patch"`
|
||||
}
|
||||
|
||||
func (commentWithLongTextFields) TableName() string {
|
||||
return "comment"
|
||||
}
|
||||
|
||||
func isMSSQLMaxTextColumn(column *schemas.Column) bool {
|
||||
if column.Length != -1 {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(column.SQLType.Name, schemas.Varchar) || strings.EqualFold(column.SQLType.Name, schemas.NVarchar)
|
||||
}
|
||||
|
||||
func modifyLongTextColumnsForMSSQL(x base.EngineMigration, bean any, columnNames ...string) error {
|
||||
table, err := x.TableInfo(bean)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, columnName := range columnNames {
|
||||
column := table.GetColumn(columnName)
|
||||
if column == nil {
|
||||
return fmt.Errorf("column %s does not exist in table %s", columnName, table.Name)
|
||||
}
|
||||
if isMSSQLMaxTextColumn(column) {
|
||||
continue
|
||||
}
|
||||
if err := base.ModifyColumn(x, table.Name, column); err != nil {
|
||||
return fmt.Errorf("modify %s.%s: %w", table.Name, columnName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExpandIssueAndCommentLongTextFieldsForMSSQL expands legacy MSSQL nvarchar(4000)
|
||||
// columns to nvarchar(max) so PR push comments and long issue content are not truncated.
|
||||
func ExpandIssueAndCommentLongTextFieldsForMSSQL(x base.EngineMigration) error {
|
||||
if x.Dialect().URI().DBType != schemas.MSSQL {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := modifyLongTextColumnsForMSSQL(x, new(issueWithLongTextContent), "content"); err != nil {
|
||||
return err
|
||||
}
|
||||
return modifyLongTextColumnsForMSSQL(x, new(commentWithLongTextFields), "content", "patch")
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type issueBeforeLongTextMSSQLMigration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Content string `xorm:"VARCHAR(4000)"`
|
||||
}
|
||||
|
||||
func (issueBeforeLongTextMSSQLMigration) TableName() string {
|
||||
return "issue"
|
||||
}
|
||||
|
||||
type commentBeforeLongTextMSSQLMigration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Content string `xorm:"VARCHAR(4000)"`
|
||||
Patch string `xorm:"VARCHAR(4000) patch"`
|
||||
}
|
||||
|
||||
func (commentBeforeLongTextMSSQLMigration) TableName() string {
|
||||
return "comment"
|
||||
}
|
||||
|
||||
func Test_ExpandIssueAndCommentLongTextFieldsForMSSQL(t *testing.T) {
|
||||
if !setting.Database.Type.IsMSSQL() {
|
||||
t.Skip("Only MSSQL needs to expand legacy nvarchar(4000) long-text columns")
|
||||
}
|
||||
|
||||
x, deferrable := migrationtest.PrepareTestEnv(t, 0, new(issueBeforeLongTextMSSQLMigration), new(commentBeforeLongTextMSSQLMigration))
|
||||
defer deferrable()
|
||||
|
||||
require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x))
|
||||
require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x))
|
||||
|
||||
longText := strings.Repeat("x", 5000)
|
||||
_, err := x.Insert(&issueBeforeLongTextMSSQLMigration{Content: longText})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = x.Insert(&commentBeforeLongTextMSSQLMigration{Content: longText, Patch: longText})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// AddCreatedUnixToActionUserIsDeletedIndex extends the c_u composite index on
|
||||
// the action table to include created_unix, enabling efficient ORDER BY on the
|
||||
// dashboard feed query without a full sort of all matching rows.
|
||||
func AddCreatedUnixToActionUserIsDeletedIndex(x base.EngineMigration) error {
|
||||
// xorm Sync cannot reliably update an index when another index already
|
||||
// covers the same columns in a different order (Equal() is order-insensitive).
|
||||
// Drop the old c_u index explicitly, then recreate it with the new column set.
|
||||
indexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, idx := range indexes {
|
||||
if idx.Name == "c_u" {
|
||||
if _, err := x.Exec(x.Dialect().DropIndexSQL("action", idx)); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
newIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||
newIndex.AddColumn("user_id", "is_deleted", "created_unix")
|
||||
if _, err := x.Exec(x.Dialect().CreateIndexSQL("action", newIndex)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type actionBeforeV339 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"INDEX"`
|
||||
OpType int
|
||||
ActUserID int64
|
||||
RepoID int64
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Content string `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
}
|
||||
|
||||
func (actionBeforeV339) TableName() string { return "action" }
|
||||
|
||||
func (actionBeforeV339) TableIndices() []*schemas.Index {
|
||||
repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
|
||||
repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
|
||||
|
||||
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||
|
||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||
|
||||
// old 2-column index, before the migration
|
||||
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||
cuIndex.AddColumn("user_id", "is_deleted")
|
||||
|
||||
actUserUserIndex := schemas.NewIndex("au_c_u", schemas.IndexType)
|
||||
actUserUserIndex.AddColumn("act_user_id", "created_unix", "user_id")
|
||||
|
||||
return []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex, actUserUserIndex}
|
||||
}
|
||||
|
||||
func Test_AddCreatedUnixToActionUserIsDeletedIndex(t *testing.T) {
|
||||
x, deferable := migrationtest.PrepareTestEnv(t, 0, new(actionBeforeV339))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
indexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted"}, false), "old c_u index should exist before migration")
|
||||
assert.False(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted", "created_unix"}, false), "new c_u index should not exist before migration")
|
||||
|
||||
require.NoError(t, AddCreatedUnixToActionUserIsDeletedIndex(x))
|
||||
|
||||
indexes, err = x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted"}, false), "old 2-column c_u index should be gone after migration")
|
||||
assert.True(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted", "created_unix"}, false), "new 3-column c_u index must exist after migration")
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// AddContinueOnErrorToActionRunJob adds the ContinueOnError column to ActionRunJob,
|
||||
// storing the job-level continue-on-error value from the workflow YAML.
|
||||
func AddContinueOnErrorToActionRunJob(x base.EngineMigration) error {
|
||||
type ActionRunJob struct {
|
||||
ContinueOnError bool `xorm:"NOT NULL DEFAULT FALSE"`
|
||||
}
|
||||
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
IgnoreConstrains: true,
|
||||
}, new(ActionRunJob))
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// legacyDateTimeColumns are the persisted real datetime columns that old Gitea
|
||||
// versions created as MSSQL DATETIME. Every other time value is stored as a
|
||||
// unix timestamp integer, so these are the only columns affected.
|
||||
var legacyDateTimeColumns = []struct {
|
||||
bean any
|
||||
column string
|
||||
}{
|
||||
{new(externalLoginUserWithExpiresAt), "expires_at"},
|
||||
{new(lfsLockWithCreated), "created"},
|
||||
}
|
||||
|
||||
type externalLoginUserWithExpiresAt struct {
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
func (externalLoginUserWithExpiresAt) TableName() string {
|
||||
return "external_login_user"
|
||||
}
|
||||
|
||||
type lfsLockWithCreated struct {
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
|
||||
func (lfsLockWithCreated) TableName() string {
|
||||
return "lfs_lock"
|
||||
}
|
||||
|
||||
// FixLegacyMSSQLDateTimeColumns converts legacy locale-dependent DATETIME columns
|
||||
// to DATETIME2. Databases created by old Gitea versions stored these columns as
|
||||
// DATETIME, which fails to parse ISO datetime strings ('YYYY-MM-DD HH:MM:SS')
|
||||
// when the MSSQL session language is not English, breaking external account
|
||||
// linking and LFS lock creation. New installs already use DATETIME2, so only
|
||||
// legacy MSSQL columns need converting.
|
||||
func FixLegacyMSSQLDateTimeColumns(x base.EngineMigration) error {
|
||||
if x.Dialect().URI().DBType != schemas.MSSQL {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, c := range legacyDateTimeColumns {
|
||||
table, err := x.TableInfo(c.bean)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var dataType string
|
||||
has, err := x.SQL("SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?", table.Name, c.column).Get(&dataType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has || !strings.EqualFold(dataType, "datetime") {
|
||||
continue
|
||||
}
|
||||
|
||||
column := table.GetColumn(c.column)
|
||||
if column == nil {
|
||||
return fmt.Errorf("column %s does not exist in table %s", c.column, table.Name)
|
||||
}
|
||||
if err := base.ModifyColumn(x, table.Name, column); err != nil {
|
||||
return fmt.Errorf("modify %s.%s: %w", table.Name, c.column, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type externalLoginUserBeforeDateTimeMigration struct {
|
||||
ExternalID string `xorm:"pk NOT NULL"`
|
||||
LoginSourceID int64 `xorm:"pk NOT NULL"`
|
||||
ExpiresAt time.Time // sync creates DATETIME2; downgraded to legacy DATETIME via raw SQL below
|
||||
}
|
||||
|
||||
func (externalLoginUserBeforeDateTimeMigration) TableName() string {
|
||||
return "external_login_user"
|
||||
}
|
||||
|
||||
type lfsLockBeforeDateTimeMigration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
|
||||
func (lfsLockBeforeDateTimeMigration) TableName() string {
|
||||
return "lfs_lock"
|
||||
}
|
||||
|
||||
func Test_FixLegacyMSSQLDateTimeColumns(t *testing.T) {
|
||||
if !setting.Database.Type.IsMSSQL() {
|
||||
t.Skip("Only MSSQL needs to convert the legacy locale-dependent DATETIME columns")
|
||||
}
|
||||
|
||||
x, deferrable := migrationtest.PrepareTestEnv(t, 0,
|
||||
new(externalLoginUserBeforeDateTimeMigration),
|
||||
new(lfsLockBeforeDateTimeMigration),
|
||||
)
|
||||
defer deferrable()
|
||||
|
||||
// Force the legacy DATETIME column type that old Gitea versions created.
|
||||
_, err := x.Exec("ALTER TABLE [external_login_user] ALTER COLUMN [expires_at] DATETIME")
|
||||
require.NoError(t, err)
|
||||
_, err = x.Exec("ALTER TABLE [lfs_lock] ALTER COLUMN [created] DATETIME")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "datetime", mssqlColumnType(t, x, "external_login_user", "expires_at"))
|
||||
require.Equal(t, "datetime", mssqlColumnType(t, x, "lfs_lock", "created"))
|
||||
|
||||
require.NoError(t, FixLegacyMSSQLDateTimeColumns(x))
|
||||
require.NoError(t, FixLegacyMSSQLDateTimeColumns(x)) // idempotent
|
||||
|
||||
require.Equal(t, "datetime2", mssqlColumnType(t, x, "external_login_user", "expires_at"))
|
||||
require.Equal(t, "datetime2", mssqlColumnType(t, x, "lfs_lock", "created"))
|
||||
|
||||
// Inserting an ISO-formatted datetime must succeed even under a non-English
|
||||
// locale, which is the failure the legacy DATETIME columns produced. The
|
||||
// SET LANGUAGE and INSERT run in one Exec so they share a single connection.
|
||||
_, err = x.Exec("SET LANGUAGE German; " +
|
||||
"INSERT INTO [external_login_user] ([external_id], [login_source_id], [expires_at]) " +
|
||||
"VALUES ('ext-id', 1, '2026-06-25 11:58:39')")
|
||||
require.NoError(t, err)
|
||||
_, err = x.Exec("SET LANGUAGE German; " +
|
||||
"INSERT INTO [lfs_lock] ([created]) VALUES ('2026-06-25 11:58:39')")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func mssqlColumnType(t *testing.T, x base.EngineMigration, table, column string) string {
|
||||
t.Helper()
|
||||
var dataType string
|
||||
has, err := x.SQL("SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?", table, column).Get(&dataType)
|
||||
require.NoError(t, err)
|
||||
require.True(t, has)
|
||||
return dataType
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddScopedWorkflowsSchema(x base.EngineMigration) error {
|
||||
// Create the action_scoped_workflow_source table
|
||||
type ScopedWorkflowConfig struct {
|
||||
Required bool `json:"required"`
|
||||
Patterns []string `json:"patterns"`
|
||||
}
|
||||
type ActionScopedWorkflowSource struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64 `xorm:"UNIQUE(owner_repo) NOT NULL DEFAULT 0"`
|
||||
SourceRepoID int64 `xorm:"INDEX UNIQUE(owner_repo) NOT NULL DEFAULT 0"`
|
||||
WorkflowConfigs map[string]*ScopedWorkflowConfig `xorm:"JSON TEXT 'workflow_configs'"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
if err := x.Sync(new(ActionScopedWorkflowSource)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the columns that record where a run's workflow content came from
|
||||
type ActionRun struct {
|
||||
WorkflowRepoID int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
WorkflowCommitSHA string `xorm:"VARCHAR(64) NOT NULL DEFAULT ''"`
|
||||
IsScopedRun bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
IgnoreConstrains: true,
|
||||
}, new(ActionRun))
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user