mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-28 21:01:08 +00:00
Inline model types for migrations (#4293)
This commit is contained in:
parent
066926f952
commit
5139401b53
11 changed files with 268 additions and 350 deletions
|
@ -5,18 +5,14 @@ import (
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type v000Migrations struct {
|
|
||||||
Name string `xorm:"UNIQUE"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *v000Migrations) TableName() string {
|
|
||||||
return "migrations"
|
|
||||||
}
|
|
||||||
|
|
||||||
var legacyToXormigrate = xormigrate.Migration{
|
var legacyToXormigrate = xormigrate.Migration{
|
||||||
ID: "legacy-to-xormigrate",
|
ID: "legacy-to-xormigrate",
|
||||||
MigrateSession: func(sess *xorm.Session) error {
|
MigrateSession: func(sess *xorm.Session) error {
|
||||||
var mig []*v000Migrations
|
type migrations struct {
|
||||||
|
Name string `xorm:"UNIQUE"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var mig []*migrations
|
||||||
if err := sess.Find(&mig); err != nil {
|
if err := sess.Find(&mig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,30 +19,39 @@ import (
|
||||||
|
|
||||||
"src.techknowlogick.com/xormigrate"
|
"src.techknowlogick.com/xormigrate"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var addOrgID = xormigrate.Migration{
|
var addOrgID = xormigrate.Migration{
|
||||||
ID: "add-org-id",
|
ID: "add-org-id",
|
||||||
MigrateSession: func(sess *xorm.Session) error {
|
MigrateSession: func(sess *xorm.Session) error {
|
||||||
if err := sess.Sync(new(userV009)); err != nil {
|
type users struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'user_id'"`
|
||||||
|
Login string `xorm:"UNIQUE 'user_login'"`
|
||||||
|
OrgID int64 `xorm:"user_org_id"`
|
||||||
|
}
|
||||||
|
type orgs struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'id'"`
|
||||||
|
Name string `xorm:"UNIQUE 'name'"`
|
||||||
|
IsUser bool `xorm:"is_user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sess.Sync(new(users), new(orgs)); err != nil {
|
||||||
return fmt.Errorf("sync new models failed: %w", err)
|
return fmt.Errorf("sync new models failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all users
|
// get all users
|
||||||
var users []*userV009
|
var us []*users
|
||||||
if err := sess.Find(&users); err != nil {
|
if err := sess.Find(&us); err != nil {
|
||||||
return fmt.Errorf("find all repos failed: %w", err)
|
return fmt.Errorf("find all repos failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, user := range users {
|
for _, user := range us {
|
||||||
org := &model.Org{}
|
org := &orgs{}
|
||||||
has, err := sess.Where("name = ?", user.Login).Get(org)
|
has, err := sess.Where("name = ?", user.Login).Get(org)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting org failed: %w", err)
|
return fmt.Errorf("getting org failed: %w", err)
|
||||||
} else if !has {
|
} else if !has {
|
||||||
org = &model.Org{
|
org = &orgs{
|
||||||
Name: user.Login,
|
Name: user.Login,
|
||||||
IsUser: true,
|
IsUser: true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,23 +19,19 @@ import (
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type oldSecret004 struct {
|
|
||||||
ID int64 `json:"id" xorm:"pk autoincr 'secret_id'"`
|
|
||||||
PluginsOnly bool `json:"plugins_only" xorm:"secret_plugins_only"`
|
|
||||||
SkipVerify bool `json:"-" xorm:"secret_skip_verify"`
|
|
||||||
Conceal bool `json:"-" xorm:"secret_conceal"`
|
|
||||||
Images []string `json:"images" xorm:"json 'secret_images'"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (oldSecret004) TableName() string {
|
|
||||||
return "secrets"
|
|
||||||
}
|
|
||||||
|
|
||||||
var removePluginOnlyOptionFromSecretsTable = xormigrate.Migration{
|
var removePluginOnlyOptionFromSecretsTable = xormigrate.Migration{
|
||||||
ID: "remove-plugin-only-option-from-secrets-table",
|
ID: "remove-plugin-only-option-from-secrets-table",
|
||||||
MigrateSession: func(sess *xorm.Session) (err error) {
|
MigrateSession: func(sess *xorm.Session) (err error) {
|
||||||
|
type secrets struct {
|
||||||
|
ID int64 `json:"id" xorm:"pk autoincr 'secret_id'"`
|
||||||
|
PluginsOnly bool `json:"plugins_only" xorm:"secret_plugins_only"`
|
||||||
|
SkipVerify bool `json:"-" xorm:"secret_skip_verify"`
|
||||||
|
Conceal bool `json:"-" xorm:"secret_conceal"`
|
||||||
|
Images []string `json:"images" xorm:"json 'secret_images'"`
|
||||||
|
}
|
||||||
|
|
||||||
// make sure plugin_only column exists
|
// make sure plugin_only column exists
|
||||||
if err := sess.Sync(new(oldSecret004)); err != nil {
|
if err := sess.Sync(new(secrets)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,41 +17,35 @@ package migration
|
||||||
import (
|
import (
|
||||||
"src.techknowlogick.com/xormigrate"
|
"src.techknowlogick.com/xormigrate"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|
||||||
errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// perPage005 set the size of the slice to read per page.
|
// perPage005 set the size of the slice to read per page.
|
||||||
var perPage005 = 100
|
var perPage005 = 100
|
||||||
|
|
||||||
type pipeline005 struct {
|
|
||||||
ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"`
|
|
||||||
Error string `json:"error" xorm:"LONGTEXT 'pipeline_error'"` // old error format
|
|
||||||
Errors []*errorTypes.PipelineError `json:"errors" xorm:"json 'pipeline_errors'"` // new error format
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pipeline005) TableName() string {
|
|
||||||
return "pipelines"
|
|
||||||
}
|
|
||||||
|
|
||||||
type PipelineError005 struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
IsWarning bool `json:"is_warning"`
|
|
||||||
Data any `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var convertToNewPipelineErrorFormat = xormigrate.Migration{
|
var convertToNewPipelineErrorFormat = xormigrate.Migration{
|
||||||
ID: "convert-to-new-pipeline-error-format",
|
ID: "convert-to-new-pipeline-error-format",
|
||||||
Long: true,
|
Long: true,
|
||||||
MigrateSession: func(sess *xorm.Session) (err error) {
|
MigrateSession: func(sess *xorm.Session) (err error) {
|
||||||
|
type pipelineError struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
IsWarning bool `json:"is_warning"`
|
||||||
|
Data any `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipelines struct {
|
||||||
|
ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"`
|
||||||
|
Error string `json:"error" xorm:"LONGTEXT 'pipeline_error'"` // old error format
|
||||||
|
Errors []*pipelineError `json:"errors" xorm:"json 'pipeline_errors'"` // new error format
|
||||||
|
}
|
||||||
|
|
||||||
// make sure pipeline_error column exists
|
// make sure pipeline_error column exists
|
||||||
if err := sess.Sync(new(pipeline005)); err != nil {
|
if err := sess.Sync(new(pipelines)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
page := 0
|
page := 0
|
||||||
oldPipelines := make([]*pipeline005, 0, perPage005)
|
oldPipelines := make([]*pipelines, 0, perPage005)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
oldPipelines = oldPipelines[:0]
|
oldPipelines = oldPipelines[:0]
|
||||||
|
@ -62,9 +56,9 @@ var convertToNewPipelineErrorFormat = xormigrate.Migration{
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, oldPipeline := range oldPipelines {
|
for _, oldPipeline := range oldPipelines {
|
||||||
var newPipeline pipeline005
|
var newPipeline pipelines
|
||||||
newPipeline.ID = oldPipeline.ID
|
newPipeline.ID = oldPipeline.ID
|
||||||
newPipeline.Errors = []*errorTypes.PipelineError{{
|
newPipeline.Errors = []*pipelineError{{
|
||||||
Type: "generic",
|
Type: "generic",
|
||||||
Message: oldPipeline.Error,
|
Message: oldPipeline.Error,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -19,32 +19,24 @@ import (
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type oldRegistry007 struct {
|
|
||||||
ID int64 `json:"id" xorm:"pk autoincr 'registry_id'"`
|
|
||||||
Token string `json:"token" xorm:"TEXT 'registry_token'"`
|
|
||||||
Email string `json:"email" xorm:"varchar(500) 'registry_email'"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (oldRegistry007) TableName() string {
|
|
||||||
return "registry"
|
|
||||||
}
|
|
||||||
|
|
||||||
type oldPipeline007 struct {
|
|
||||||
ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"`
|
|
||||||
ConfigID int64 `json:"-" xorm:"pipeline_config_id"`
|
|
||||||
Enqueued int64 `json:"enqueued_at" xorm:"pipeline_enqueued"`
|
|
||||||
CloneURL string `json:"clone_url" xorm:"pipeline_clone_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TableName return database table name for xorm.
|
|
||||||
func (oldPipeline007) TableName() string {
|
|
||||||
return "pipelines"
|
|
||||||
}
|
|
||||||
|
|
||||||
var cleanRegistryPipeline = xormigrate.Migration{
|
var cleanRegistryPipeline = xormigrate.Migration{
|
||||||
ID: "clean-registry-pipeline",
|
ID: "clean-registry-pipeline",
|
||||||
MigrateSession: func(sess *xorm.Session) (err error) {
|
MigrateSession: func(sess *xorm.Session) (err error) {
|
||||||
if err := sess.Sync(new(oldRegistry007), new(oldPipeline007)); err != nil {
|
type registry struct {
|
||||||
|
ID int64 `json:"id" xorm:"pk autoincr 'registry_id'"`
|
||||||
|
Token string `json:"token" xorm:"TEXT 'registry_token'"`
|
||||||
|
Email string `json:"email" xorm:"varchar(500) 'registry_email'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipelines struct {
|
||||||
|
ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"`
|
||||||
|
ConfigID int64 `json:"-" xorm:"pipeline_config_id"`
|
||||||
|
Enqueued int64 `json:"enqueued_at" xorm:"pipeline_enqueued"`
|
||||||
|
CloneURL string `json:"clone_url" xorm:"pipeline_clone_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure columns to drop exist
|
||||||
|
if err := sess.Sync(new(registry), new(pipelines)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ func (repoV008) TableName() string {
|
||||||
return "repos"
|
return "repos"
|
||||||
}
|
}
|
||||||
|
|
||||||
type forgeV008 struct {
|
type forge struct {
|
||||||
ID int64 `xorm:"pk autoincr 'id'"`
|
ID int64 `xorm:"pk autoincr 'id'"`
|
||||||
Type model.ForgeType `xorm:"VARCHAR(250) 'type'"`
|
Type model.ForgeType `xorm:"VARCHAR(250) 'type'"`
|
||||||
URL string `xorm:"VARCHAR(500) 'url'"`
|
URL string `xorm:"VARCHAR(500) 'url'"`
|
||||||
|
@ -88,14 +88,14 @@ type forgeV008 struct {
|
||||||
AdditionalOptions map[string]any `xorm:"json 'additional_options'"`
|
AdditionalOptions map[string]any `xorm:"json 'additional_options'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (forgeV008) TableName() string {
|
func (forge) TableName() string {
|
||||||
return "forge"
|
return "forge"
|
||||||
}
|
}
|
||||||
|
|
||||||
var setForgeID = xormigrate.Migration{
|
var setForgeID = xormigrate.Migration{
|
||||||
ID: "set-forge-id",
|
ID: "set-forge-id",
|
||||||
MigrateSession: func(sess *xorm.Session) (err error) {
|
MigrateSession: func(sess *xorm.Session) (err error) {
|
||||||
if err := sess.Sync(new(userV008), new(repoV008), new(forgeV008), new(model.Org)); err != nil {
|
if err := sess.Sync(new(userV008), new(repoV008), new(forge), new(model.Org)); err != nil {
|
||||||
return fmt.Errorf("sync new models failed: %w", err)
|
return fmt.Errorf("sync new models failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,230 +19,182 @@ import (
|
||||||
|
|
||||||
"src.techknowlogick.com/xormigrate"
|
"src.techknowlogick.com/xormigrate"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types"
|
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type configV009 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'config_id'"`
|
|
||||||
RepoID int64 `xorm:"UNIQUE(s) 'config_repo_id'"`
|
|
||||||
Hash string `xorm:"UNIQUE(s) 'config_hash'"`
|
|
||||||
Name string `xorm:"UNIQUE(s) 'config_name'"`
|
|
||||||
Data []byte `xorm:"LONGBLOB 'config_data'"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (configV009) TableName() string {
|
|
||||||
return "config"
|
|
||||||
}
|
|
||||||
|
|
||||||
type cronV009 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'i_d'"`
|
|
||||||
Name string `xorm:"name UNIQUE(s) INDEX"`
|
|
||||||
RepoID int64 `xorm:"repo_id UNIQUE(s) INDEX"`
|
|
||||||
CreatorID int64 `xorm:"creator_id INDEX"`
|
|
||||||
NextExec int64 `xorm:"next_exec"`
|
|
||||||
Schedule string `xorm:"schedule NOT NULL"`
|
|
||||||
Created int64 `xorm:"created NOT NULL DEFAULT 0"`
|
|
||||||
Branch string `xorm:"branch"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cronV009) TableName() string {
|
|
||||||
return "crons"
|
|
||||||
}
|
|
||||||
|
|
||||||
type permV009 struct {
|
|
||||||
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL 'perm_user_id'"`
|
|
||||||
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL 'perm_repo_id'"`
|
|
||||||
Pull bool `xorm:"perm_pull"`
|
|
||||||
Push bool `xorm:"perm_push"`
|
|
||||||
Admin bool `xorm:"perm_admin"`
|
|
||||||
Synced int64 `xorm:"perm_synced"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (permV009) TableName() string {
|
|
||||||
return "perms"
|
|
||||||
}
|
|
||||||
|
|
||||||
type pipelineV009 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'pipeline_id'"`
|
|
||||||
RepoID int64 `xorm:"UNIQUE(s) INDEX 'pipeline_repo_id'"`
|
|
||||||
Number int64 `xorm:"UNIQUE(s) 'pipeline_number'"`
|
|
||||||
Author string `xorm:"INDEX 'pipeline_author'"`
|
|
||||||
Parent int64 `xorm:"pipeline_parent"`
|
|
||||||
Event model.WebhookEvent `xorm:"pipeline_event"`
|
|
||||||
Status model.StatusValue `xorm:"INDEX 'pipeline_status'"`
|
|
||||||
Errors []*types.PipelineError `xorm:"json 'pipeline_errors'"`
|
|
||||||
Created int64 `xorm:"pipeline_created"`
|
|
||||||
Started int64 `xorm:"pipeline_started"`
|
|
||||||
Finished int64 `xorm:"pipeline_finished"`
|
|
||||||
Deploy string `xorm:"pipeline_deploy"`
|
|
||||||
DeployTask string `xorm:"pipeline_deploy_task"`
|
|
||||||
Commit string `xorm:"pipeline_commit"`
|
|
||||||
Branch string `xorm:"pipeline_branch"`
|
|
||||||
Ref string `xorm:"pipeline_ref"`
|
|
||||||
Refspec string `xorm:"pipeline_refspec"`
|
|
||||||
Title string `xorm:"pipeline_title"`
|
|
||||||
Message string `xorm:"TEXT 'pipeline_message'"`
|
|
||||||
Timestamp int64 `xorm:"pipeline_timestamp"`
|
|
||||||
Sender string `xorm:"pipeline_sender"` // uses reported user for webhooks and name of cron for cron pipelines
|
|
||||||
Avatar string `xorm:"pipeline_avatar"`
|
|
||||||
Email string `xorm:"pipeline_email"`
|
|
||||||
ForgeURL string `xorm:"pipeline_forge_url"`
|
|
||||||
Reviewer string `xorm:"pipeline_reviewer"`
|
|
||||||
Reviewed int64 `xorm:"pipeline_reviewed"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pipelineV009) TableName() string {
|
|
||||||
return "pipelines"
|
|
||||||
}
|
|
||||||
|
|
||||||
type redirectionV009 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'redirection_id'"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r redirectionV009) TableName() string {
|
|
||||||
return "redirections"
|
|
||||||
}
|
|
||||||
|
|
||||||
type registryV009 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'registry_id'"`
|
|
||||||
RepoID int64 `xorm:"UNIQUE(s) INDEX 'registry_repo_id'"`
|
|
||||||
Address string `xorm:"UNIQUE(s) INDEX 'registry_addr'"`
|
|
||||||
Username string `xorm:"varchar(2000) 'registry_username'"`
|
|
||||||
Password string `xorm:"TEXT 'registry_password'"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (registryV009) TableName() string {
|
|
||||||
return "registry"
|
|
||||||
}
|
|
||||||
|
|
||||||
type repoV009 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'repo_id'"`
|
|
||||||
UserID int64 `xorm:"repo_user_id"`
|
|
||||||
OrgID int64 `xorm:"repo_org_id"`
|
|
||||||
Owner string `xorm:"UNIQUE(name) 'repo_owner'"`
|
|
||||||
Name string `xorm:"UNIQUE(name) 'repo_name'"`
|
|
||||||
FullName string `xorm:"UNIQUE 'repo_full_name'"`
|
|
||||||
Avatar string `xorm:"varchar(500) 'repo_avatar'"`
|
|
||||||
ForgeURL string `xorm:"varchar(1000) 'repo_forge_url'"`
|
|
||||||
Clone string `xorm:"varchar(1000) 'repo_clone'"`
|
|
||||||
CloneSSH string `xorm:"varchar(1000) 'repo_clone_ssh'"`
|
|
||||||
Branch string `xorm:"varchar(500) 'repo_branch'"`
|
|
||||||
SCMKind model.SCMKind `xorm:"varchar(50) 'repo_scm'"`
|
|
||||||
PREnabled bool `xorm:"DEFAULT TRUE 'repo_pr_enabled'"`
|
|
||||||
Timeout int64 `xorm:"repo_timeout"`
|
|
||||||
Visibility model.RepoVisibility `xorm:"varchar(10) 'repo_visibility'"`
|
|
||||||
IsSCMPrivate bool `xorm:"repo_private"`
|
|
||||||
IsTrusted bool `xorm:"repo_trusted"`
|
|
||||||
IsGated bool `xorm:"repo_gated"`
|
|
||||||
IsActive bool `xorm:"repo_active"`
|
|
||||||
AllowPull bool `xorm:"repo_allow_pr"`
|
|
||||||
AllowDeploy bool `xorm:"repo_allow_deploy"`
|
|
||||||
Config string `xorm:"varchar(500) 'repo_config_path'"`
|
|
||||||
Hash string `xorm:"varchar(500) 'repo_hash'"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (repoV009) TableName() string {
|
|
||||||
return "repos"
|
|
||||||
}
|
|
||||||
|
|
||||||
type secretV009 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'secret_id'"`
|
|
||||||
OrgID int64 `xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_org_id'"`
|
|
||||||
RepoID int64 `xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_repo_id'"`
|
|
||||||
Name string `xorm:"NOT NULL UNIQUE(s) INDEX 'secret_name'"`
|
|
||||||
Value string `xorm:"TEXT 'secret_value'"`
|
|
||||||
Images []string `xorm:"json 'secret_images'"`
|
|
||||||
Events []model.WebhookEvent `xorm:"json 'secret_events'"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (secretV009) TableName() string {
|
|
||||||
return "secrets"
|
|
||||||
}
|
|
||||||
|
|
||||||
type stepV009 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'step_id'"`
|
|
||||||
UUID string `xorm:"INDEX 'step_uuid'"`
|
|
||||||
PipelineID int64 `xorm:"UNIQUE(s) INDEX 'step_pipeline_id'"`
|
|
||||||
PID int `xorm:"UNIQUE(s) 'step_pid'"`
|
|
||||||
PPID int `xorm:"step_ppid"`
|
|
||||||
Name string `xorm:"step_name"`
|
|
||||||
State model.StatusValue `xorm:"step_state"`
|
|
||||||
Error string `xorm:"TEXT 'step_error'"`
|
|
||||||
Failure string `xorm:"step_failure"`
|
|
||||||
ExitCode int `xorm:"step_exit_code"`
|
|
||||||
Started int64 `xorm:"step_started"`
|
|
||||||
Stopped int64 `xorm:"step_stopped"`
|
|
||||||
Type model.StepType `xorm:"step_type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stepV009) TableName() string {
|
|
||||||
return "steps"
|
|
||||||
}
|
|
||||||
|
|
||||||
type taskV009 struct {
|
|
||||||
ID string `xorm:"PK UNIQUE 'task_id'"`
|
|
||||||
Data []byte `xorm:"LONGBLOB 'task_data'"`
|
|
||||||
Labels map[string]string `xorm:"json 'task_labels'"`
|
|
||||||
Dependencies []string `xorm:"json 'task_dependencies'"`
|
|
||||||
RunOn []string `xorm:"json 'task_run_on'"`
|
|
||||||
DepStatus map[string]model.StatusValue `xorm:"json 'task_dep_status'"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (taskV009) TableName() string {
|
|
||||||
return "tasks"
|
|
||||||
}
|
|
||||||
|
|
||||||
type userV009 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'user_id'"`
|
|
||||||
Login string `xorm:"UNIQUE 'user_login'"`
|
|
||||||
Token string `xorm:"TEXT 'user_token'"`
|
|
||||||
Secret string `xorm:"TEXT 'user_secret'"`
|
|
||||||
Expiry int64 `xorm:"user_expiry"`
|
|
||||||
Email string `xorm:" varchar(500) 'user_email'"`
|
|
||||||
Avatar string `xorm:" varchar(500) 'user_avatar'"`
|
|
||||||
Admin bool `xorm:"user_admin"`
|
|
||||||
Hash string `xorm:"UNIQUE varchar(500) 'user_hash'"`
|
|
||||||
OrgID int64 `xorm:"user_org_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (userV009) TableName() string {
|
|
||||||
return "users"
|
|
||||||
}
|
|
||||||
|
|
||||||
type workflowV009 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'workflow_id'"`
|
|
||||||
PipelineID int64 `xorm:"UNIQUE(s) INDEX 'workflow_pipeline_id'"`
|
|
||||||
PID int `xorm:"UNIQUE(s) 'workflow_pid'"`
|
|
||||||
Name string `xorm:"workflow_name"`
|
|
||||||
State model.StatusValue `xorm:"workflow_state"`
|
|
||||||
Error string `xorm:"TEXT 'workflow_error'"`
|
|
||||||
Started int64 `xorm:"workflow_started"`
|
|
||||||
Stopped int64 `xorm:"workflow_stopped"`
|
|
||||||
AgentID int64 `xorm:"workflow_agent_id"`
|
|
||||||
Platform string `xorm:"workflow_platform"`
|
|
||||||
Environ map[string]string `xorm:"json 'workflow_environ'"`
|
|
||||||
AxisID int `xorm:"workflow_axis_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (workflowV009) TableName() string {
|
|
||||||
return "workflows"
|
|
||||||
}
|
|
||||||
|
|
||||||
type serverConfigV009 struct {
|
|
||||||
Key string `xorm:"pk 'key'"`
|
|
||||||
Value string `xorm:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (serverConfigV009) TableName() string {
|
|
||||||
return "server_config"
|
|
||||||
}
|
|
||||||
|
|
||||||
var unifyColumnsTables = xormigrate.Migration{
|
var unifyColumnsTables = xormigrate.Migration{
|
||||||
ID: "unify-columns-tables",
|
ID: "unify-columns-tables",
|
||||||
MigrateSession: func(sess *xorm.Session) (err error) {
|
MigrateSession: func(sess *xorm.Session) (err error) {
|
||||||
if err := sess.Sync(new(configV009), new(cronV009), new(permV009), new(pipelineV009), new(redirectionV009), new(registryV009), new(repoV009), new(secretV009), new(stepV009), new(taskV009), new(userV009), new(workflowV009), new(serverConfigV009)); err != nil {
|
type config struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'config_id'"`
|
||||||
|
RepoID int64 `xorm:"UNIQUE(s) 'config_repo_id'"`
|
||||||
|
Hash string `xorm:"UNIQUE(s) 'config_hash'"`
|
||||||
|
Name string `xorm:"UNIQUE(s) 'config_name'"`
|
||||||
|
Data []byte `xorm:"LONGBLOB 'config_data'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type crons struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'i_d'"`
|
||||||
|
Name string `xorm:"name UNIQUE(s) INDEX"`
|
||||||
|
RepoID int64 `xorm:"repo_id UNIQUE(s) INDEX"`
|
||||||
|
CreatorID int64 `xorm:"creator_id INDEX"`
|
||||||
|
NextExec int64 `xorm:"next_exec"`
|
||||||
|
Schedule string `xorm:"schedule NOT NULL"`
|
||||||
|
Created int64 `xorm:"created NOT NULL DEFAULT 0"`
|
||||||
|
Branch string `xorm:"branch"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type perms struct {
|
||||||
|
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL 'perm_user_id'"`
|
||||||
|
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL 'perm_repo_id'"`
|
||||||
|
Pull bool `xorm:"perm_pull"`
|
||||||
|
Push bool `xorm:"perm_push"`
|
||||||
|
Admin bool `xorm:"perm_admin"`
|
||||||
|
Synced int64 `xorm:"perm_synced"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipelineError struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
IsWarning bool `json:"is_warning"`
|
||||||
|
Data any `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipelines struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'pipeline_id'"`
|
||||||
|
RepoID int64 `xorm:"UNIQUE(s) INDEX 'pipeline_repo_id'"`
|
||||||
|
Number int64 `xorm:"UNIQUE(s) 'pipeline_number'"`
|
||||||
|
Author string `xorm:"INDEX 'pipeline_author'"`
|
||||||
|
Parent int64 `xorm:"pipeline_parent"`
|
||||||
|
Event string `xorm:"pipeline_event"`
|
||||||
|
Status string `xorm:"INDEX 'pipeline_status'"`
|
||||||
|
Errors []*pipelineError `xorm:"json 'pipeline_errors'"`
|
||||||
|
Created int64 `xorm:"pipeline_created"`
|
||||||
|
Started int64 `xorm:"pipeline_started"`
|
||||||
|
Finished int64 `xorm:"pipeline_finished"`
|
||||||
|
Deploy string `xorm:"pipeline_deploy"`
|
||||||
|
DeployTask string `xorm:"pipeline_deploy_task"`
|
||||||
|
Commit string `xorm:"pipeline_commit"`
|
||||||
|
Branch string `xorm:"pipeline_branch"`
|
||||||
|
Ref string `xorm:"pipeline_ref"`
|
||||||
|
Refspec string `xorm:"pipeline_refspec"`
|
||||||
|
Title string `xorm:"pipeline_title"`
|
||||||
|
Message string `xorm:"TEXT 'pipeline_message'"`
|
||||||
|
Timestamp int64 `xorm:"pipeline_timestamp"`
|
||||||
|
Sender string `xorm:"pipeline_sender"` // uses reported user for webhooks and name of cron for cron pipelines
|
||||||
|
Avatar string `xorm:"pipeline_avatar"`
|
||||||
|
Email string `xorm:"pipeline_email"`
|
||||||
|
ForgeURL string `xorm:"pipeline_forge_url"`
|
||||||
|
Reviewer string `xorm:"pipeline_reviewer"`
|
||||||
|
Reviewed int64 `xorm:"pipeline_reviewed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type redirections struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'redirection_id'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type registry struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'registry_id'"`
|
||||||
|
RepoID int64 `xorm:"UNIQUE(s) INDEX 'registry_repo_id'"`
|
||||||
|
Address string `xorm:"UNIQUE(s) INDEX 'registry_addr'"`
|
||||||
|
Username string `xorm:"varchar(2000) 'registry_username'"`
|
||||||
|
Password string `xorm:"TEXT 'registry_password'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type repos struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'repo_id'"`
|
||||||
|
UserID int64 `xorm:"repo_user_id"`
|
||||||
|
OrgID int64 `xorm:"repo_org_id"`
|
||||||
|
Owner string `xorm:"UNIQUE(name) 'repo_owner'"`
|
||||||
|
Name string `xorm:"UNIQUE(name) 'repo_name'"`
|
||||||
|
FullName string `xorm:"UNIQUE 'repo_full_name'"`
|
||||||
|
Avatar string `xorm:"varchar(500) 'repo_avatar'"`
|
||||||
|
ForgeURL string `xorm:"varchar(1000) 'repo_forge_url'"`
|
||||||
|
Clone string `xorm:"varchar(1000) 'repo_clone'"`
|
||||||
|
CloneSSH string `xorm:"varchar(1000) 'repo_clone_ssh'"`
|
||||||
|
Branch string `xorm:"varchar(500) 'repo_branch'"`
|
||||||
|
SCMKind string `xorm:"varchar(50) 'repo_scm'"`
|
||||||
|
PREnabled bool `xorm:"DEFAULT TRUE 'repo_pr_enabled'"`
|
||||||
|
Timeout int64 `xorm:"repo_timeout"`
|
||||||
|
Visibility string `xorm:"varchar(10) 'repo_visibility'"`
|
||||||
|
IsSCMPrivate bool `xorm:"repo_private"`
|
||||||
|
IsTrusted bool `xorm:"repo_trusted"`
|
||||||
|
IsGated bool `xorm:"repo_gated"`
|
||||||
|
IsActive bool `xorm:"repo_active"`
|
||||||
|
AllowPull bool `xorm:"repo_allow_pr"`
|
||||||
|
AllowDeploy bool `xorm:"repo_allow_deploy"`
|
||||||
|
Config string `xorm:"varchar(500) 'repo_config_path'"`
|
||||||
|
Hash string `xorm:"varchar(500) 'repo_hash'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type secrets struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'secret_id'"`
|
||||||
|
OrgID int64 `xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_org_id'"`
|
||||||
|
RepoID int64 `xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_repo_id'"`
|
||||||
|
Name string `xorm:"NOT NULL UNIQUE(s) INDEX 'secret_name'"`
|
||||||
|
Value string `xorm:"TEXT 'secret_value'"`
|
||||||
|
Images []string `xorm:"json 'secret_images'"`
|
||||||
|
Events []string `xorm:"json 'secret_events'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type steps struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'step_id'"`
|
||||||
|
UUID string `xorm:"INDEX 'step_uuid'"`
|
||||||
|
PipelineID int64 `xorm:"UNIQUE(s) INDEX 'step_pipeline_id'"`
|
||||||
|
PID int `xorm:"UNIQUE(s) 'step_pid'"`
|
||||||
|
PPID int `xorm:"step_ppid"`
|
||||||
|
Name string `xorm:"step_name"`
|
||||||
|
State string `xorm:"step_state"`
|
||||||
|
Error string `xorm:"TEXT 'step_error'"`
|
||||||
|
Failure string `xorm:"step_failure"`
|
||||||
|
ExitCode int `xorm:"step_exit_code"`
|
||||||
|
Started int64 `xorm:"step_started"`
|
||||||
|
Stopped int64 `xorm:"step_stopped"`
|
||||||
|
Type string `xorm:"step_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type tasks struct {
|
||||||
|
ID string `xorm:"PK UNIQUE 'task_id'"`
|
||||||
|
Data []byte `xorm:"LONGBLOB 'task_data'"`
|
||||||
|
Labels map[string]string `xorm:"json 'task_labels'"`
|
||||||
|
Dependencies []string `xorm:"json 'task_dependencies'"`
|
||||||
|
RunOn []string `xorm:"json 'task_run_on'"`
|
||||||
|
DepStatus map[string]string `xorm:"json 'task_dep_status'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type users struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'user_id'"`
|
||||||
|
Login string `xorm:"UNIQUE 'user_login'"`
|
||||||
|
Token string `xorm:"TEXT 'user_token'"`
|
||||||
|
Secret string `xorm:"TEXT 'user_secret'"`
|
||||||
|
Expiry int64 `xorm:"user_expiry"`
|
||||||
|
Email string `xorm:" varchar(500) 'user_email'"`
|
||||||
|
Avatar string `xorm:" varchar(500) 'user_avatar'"`
|
||||||
|
Admin bool `xorm:"user_admin"`
|
||||||
|
Hash string `xorm:"UNIQUE varchar(500) 'user_hash'"`
|
||||||
|
OrgID int64 `xorm:"user_org_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type workflows struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'workflow_id'"`
|
||||||
|
PipelineID int64 `xorm:"UNIQUE(s) INDEX 'workflow_pipeline_id'"`
|
||||||
|
PID int `xorm:"UNIQUE(s) 'workflow_pid'"`
|
||||||
|
Name string `xorm:"workflow_name"`
|
||||||
|
State string `xorm:"workflow_state"`
|
||||||
|
Error string `xorm:"TEXT 'workflow_error'"`
|
||||||
|
Started int64 `xorm:"workflow_started"`
|
||||||
|
Stopped int64 `xorm:"workflow_stopped"`
|
||||||
|
AgentID int64 `xorm:"workflow_agent_id"`
|
||||||
|
Platform string `xorm:"workflow_platform"`
|
||||||
|
Environ map[string]string `xorm:"json 'workflow_environ'"`
|
||||||
|
AxisID int `xorm:"workflow_axis_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverConfig struct {
|
||||||
|
Key string `xorm:"pk 'key'"`
|
||||||
|
Value string `xorm:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sess.Sync(new(config), new(crons), new(perms), new(pipelines), new(redirections), new(registry), new(repos), new(secrets), new(steps), new(tasks), new(users), new(workflows), new(serverConfig)); err != nil {
|
||||||
return fmt.Errorf("sync models failed: %w", err)
|
return fmt.Errorf("sync models failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,26 +21,17 @@ import (
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stepV012 struct {
|
|
||||||
Finished int64 `xorm:"stopped"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stepV012) TableName() string {
|
|
||||||
return "steps"
|
|
||||||
}
|
|
||||||
|
|
||||||
type workflowV012 struct {
|
|
||||||
Finished int64 `xorm:"stopped"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (workflowV012) TableName() string {
|
|
||||||
return "workflows"
|
|
||||||
}
|
|
||||||
|
|
||||||
var renameStartEndTime = xormigrate.Migration{
|
var renameStartEndTime = xormigrate.Migration{
|
||||||
ID: "rename-start-end-time",
|
ID: "rename-start-end-time",
|
||||||
MigrateSession: func(sess *xorm.Session) (err error) {
|
MigrateSession: func(sess *xorm.Session) (err error) {
|
||||||
if err := sess.Sync(new(stepV012), new(workflowV012)); err != nil {
|
type steps struct {
|
||||||
|
Finished int64 `xorm:"stopped"`
|
||||||
|
}
|
||||||
|
type workflows struct {
|
||||||
|
Finished int64 `xorm:"stopped"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sess.Sync(new(steps), new(workflows)); err != nil {
|
||||||
return fmt.Errorf("sync models failed: %w", err)
|
return fmt.Errorf("sync models failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,25 +23,21 @@ import (
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type agentV015 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'id'"`
|
|
||||||
OwnerID int64 `xorm:"INDEX 'owner_id'"`
|
|
||||||
OrgID int64 `xorm:"INDEX 'org_id'"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (agentV015) TableName() string {
|
|
||||||
return "agents"
|
|
||||||
}
|
|
||||||
|
|
||||||
var addOrgAgents = xormigrate.Migration{
|
var addOrgAgents = xormigrate.Migration{
|
||||||
ID: "add-org-agents",
|
ID: "add-org-agents",
|
||||||
MigrateSession: func(sess *xorm.Session) (err error) {
|
MigrateSession: func(sess *xorm.Session) (err error) {
|
||||||
if err := sess.Sync(new(agentV015)); err != nil {
|
type agents struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'id'"`
|
||||||
|
OwnerID int64 `xorm:"INDEX 'owner_id'"`
|
||||||
|
OrgID int64 `xorm:"INDEX 'org_id'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sess.Sync(new(agents)); err != nil {
|
||||||
return fmt.Errorf("sync models failed: %w", err)
|
return fmt.Errorf("sync models failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update all existing agents to be global agents
|
// Update all existing agents to be global agents
|
||||||
_, err = sess.Cols("org_id").Update(&model.Agent{
|
_, err = sess.Cols("org_id").Update(&agents{
|
||||||
OrgID: model.IDNotSet,
|
OrgID: model.IDNotSet,
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -21,19 +21,15 @@ import (
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type agentV016 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'id'"`
|
|
||||||
CustomLabels map[string]string `xorm:"JSON 'custom_labels'"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (agentV016) TableName() string {
|
|
||||||
return "agents"
|
|
||||||
}
|
|
||||||
|
|
||||||
var addCustomLabelsToAgent = xormigrate.Migration{
|
var addCustomLabelsToAgent = xormigrate.Migration{
|
||||||
ID: "add-custom-labels-to-agent",
|
ID: "add-custom-labels-to-agent",
|
||||||
MigrateSession: func(sess *xorm.Session) (err error) {
|
MigrateSession: func(sess *xorm.Session) (err error) {
|
||||||
if err := sess.Sync(new(agentV016)); err != nil {
|
type agents struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'id'"`
|
||||||
|
CustomLabels map[string]string `xorm:"JSON 'custom_labels'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sess.Sync(new(agents)); err != nil {
|
||||||
return fmt.Errorf("sync models failed: %w", err)
|
return fmt.Errorf("sync models failed: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -23,24 +23,20 @@ import (
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type repoV035 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr 'id'"`
|
|
||||||
IsTrusted bool `xorm:"'trusted'"`
|
|
||||||
Trusted model.TrustedConfiguration `xorm:"json 'trusted_conf'"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (repoV035) TableName() string {
|
|
||||||
return "repos"
|
|
||||||
}
|
|
||||||
|
|
||||||
var splitTrusted = xormigrate.Migration{
|
var splitTrusted = xormigrate.Migration{
|
||||||
ID: "split-trusted",
|
ID: "split-trusted",
|
||||||
MigrateSession: func(sess *xorm.Session) error {
|
MigrateSession: func(sess *xorm.Session) error {
|
||||||
if err := sess.Sync(new(repoV035)); err != nil {
|
type repos struct {
|
||||||
|
ID int64 `xorm:"pk autoincr 'id'"`
|
||||||
|
IsTrusted bool `xorm:"'trusted'"`
|
||||||
|
Trusted model.TrustedConfiguration `xorm:"json 'trusted_conf'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sess.Sync(new(repos)); err != nil {
|
||||||
return fmt.Errorf("sync new models failed: %w", err)
|
return fmt.Errorf("sync new models failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := sess.Where("trusted = ?", false).Cols("trusted_conf").Update(&repoV035{
|
if _, err := sess.Where("trusted = ?", false).Cols("trusted_conf").Update(&repos{
|
||||||
Trusted: model.TrustedConfiguration{
|
Trusted: model.TrustedConfiguration{
|
||||||
Network: false,
|
Network: false,
|
||||||
Security: false,
|
Security: false,
|
||||||
|
@ -50,7 +46,7 @@ var splitTrusted = xormigrate.Migration{
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := sess.Where("trusted = ?", true).Cols("trusted_conf").Update(&repoV035{
|
if _, err := sess.Where("trusted = ?", true).Cols("trusted_conf").Update(&repos{
|
||||||
Trusted: model.TrustedConfiguration{
|
Trusted: model.TrustedConfiguration{
|
||||||
Network: true,
|
Network: true,
|
||||||
Security: true,
|
Security: true,
|
||||||
|
|
Loading…
Reference in a new issue