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,20 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func DropColumnHeadUserNameOnPullRequest(x base.EngineMigration) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := base.DropTableColumns(sess, "pull_request", "head_user_name"); err != nil {
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddWhitelistDeployKeysToBranches(x base.EngineMigration) error {
|
||||
type ProtectedBranch struct {
|
||||
ID int64
|
||||
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync(new(ProtectedBranch))
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func RemoveLabelUneededCols(x base.EngineMigration) error {
|
||||
// Make sure the columns exist before dropping them
|
||||
type Label struct {
|
||||
QueryString string
|
||||
IsSelected bool
|
||||
}
|
||||
if err := x.Sync(new(Label)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := base.DropTableColumns(sess, "label", "query_string"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := base.DropTableColumns(sess, "label", "is_selected"); err != nil {
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddTeamIncludesAllRepositories(x base.EngineMigration) error {
|
||||
type Team struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Team)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := x.Exec("UPDATE `team` SET `includes_all_repositories` = ? WHERE `name`=?",
|
||||
true, "Owners")
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
// RepoWatchMode specifies what kind of watch the user has on a repository
|
||||
type RepoWatchMode int8
|
||||
|
||||
// Watch is connection request for receiving repository notification.
|
||||
type Watch struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
|
||||
}
|
||||
|
||||
func AddModeColumnToWatch(x base.EngineMigration) error {
|
||||
if err := x.Sync(new(Watch)); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := x.Exec("UPDATE `watch` SET `mode` = 1")
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddTemplateToRepo(x base.EngineMigration) error {
|
||||
type Repository struct {
|
||||
IsTemplate bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
||||
TemplateID int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
return x.Sync(new(Repository))
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddCommentIDOnNotification(x base.EngineMigration) error {
|
||||
type Notification struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CommentID int64
|
||||
}
|
||||
|
||||
return x.Sync(new(Notification))
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddCanCreateOrgRepoColumnForTeam(x base.EngineMigration) error {
|
||||
type Team struct {
|
||||
CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync(new(Team))
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func ChangeReviewContentToText(x base.EngineMigration) error {
|
||||
switch x.Dialect().URI().DBType {
|
||||
case schemas.MYSQL:
|
||||
_, err := x.Exec("ALTER TABLE review MODIFY COLUMN content TEXT")
|
||||
return err
|
||||
case schemas.ORACLE:
|
||||
_, err := x.Exec("ALTER TABLE review MODIFY content TEXT")
|
||||
return err
|
||||
case schemas.MSSQL:
|
||||
_, err := x.Exec("ALTER TABLE review ALTER COLUMN content TEXT")
|
||||
return err
|
||||
case schemas.POSTGRES:
|
||||
_, err := x.Exec("ALTER TABLE review ALTER COLUMN content TYPE TEXT")
|
||||
return err
|
||||
default:
|
||||
// SQLite doesn't support ALTER COLUMN, and it seem to already make String to _TEXT_ default so no migration needed
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddBranchProtectionCanPushAndEnableWhitelist(x base.EngineMigration) error {
|
||||
type ProtectedBranch struct {
|
||||
CanPush bool `xorm:"NOT NULL DEFAULT false"`
|
||||
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type int
|
||||
|
||||
// Permissions
|
||||
IsAdmin bool
|
||||
IsRestricted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Visibility int `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
type Review struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Official bool `xorm:"NOT NULL DEFAULT false"`
|
||||
|
||||
ReviewerID int64 `xorm:"index"`
|
||||
IssueID int64 `xorm:"index"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(ProtectedBranch)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Review)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const (
|
||||
// ReviewTypeApprove approves changes
|
||||
ReviewTypeApprove int = 1
|
||||
// ReviewTypeReject gives feedback blocking merge
|
||||
ReviewTypeReject int = 3
|
||||
|
||||
// VisibleTypePublic Visible for everyone
|
||||
VisibleTypePublic int = 0
|
||||
// VisibleTypePrivate Visible only for organization's members
|
||||
VisibleTypePrivate int = 2
|
||||
|
||||
// unit.UnitTypeCode is unit type code
|
||||
UnitTypeCode int = 1
|
||||
|
||||
// AccessModeNone no access
|
||||
AccessModeNone int = 0
|
||||
// AccessModeRead read access
|
||||
AccessModeRead int = 1
|
||||
// AccessModeWrite write access
|
||||
AccessModeWrite int = 2
|
||||
// AccessModeOwner owner access
|
||||
AccessModeOwner int = 4
|
||||
)
|
||||
|
||||
// Repository represents a git repository.
|
||||
type Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64 `xorm:"UNIQUE(s) index"`
|
||||
|
||||
IsPrivate bool `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
type PullRequest struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
|
||||
BaseRepoID int64 `xorm:"INDEX"`
|
||||
BaseBranch string
|
||||
}
|
||||
|
||||
// RepoUnit describes all units of a repository
|
||||
type RepoUnit struct {
|
||||
ID int64
|
||||
RepoID int64 `xorm:"INDEX(s)"`
|
||||
Type int `xorm:"INDEX(s)"`
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
AccessMode int
|
||||
Units []*RepoUnit
|
||||
UnitsMode map[int]int
|
||||
}
|
||||
|
||||
type TeamUser struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
TeamID int64 `xorm:"UNIQUE(s)"`
|
||||
UID int64 `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
type Collaboration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
Mode int `xorm:"DEFAULT 2 NOT NULL"`
|
||||
}
|
||||
|
||||
type Access struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"UNIQUE(s)"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
Mode int
|
||||
}
|
||||
|
||||
type TeamUnit struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
TeamID int64 `xorm:"UNIQUE(s)"`
|
||||
Type int `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
// Team represents a organization team.
|
||||
type Team struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
Authorize int
|
||||
}
|
||||
|
||||
// getUserRepoPermission static function based on issues_model.IsOfficialReviewer at 5d78792385
|
||||
getUserRepoPermission := func(sess base.Session, repo *Repository, user *User) (Permission, error) {
|
||||
var perm Permission
|
||||
|
||||
repoOwner := new(User)
|
||||
has, err := sess.ID(repo.OwnerID).Get(repoOwner)
|
||||
if err != nil || !has {
|
||||
return perm, err
|
||||
}
|
||||
|
||||
// Prevent strangers from checking out public repo of private organization
|
||||
// Allow user if they are collaborator of a repo within a private organization but not a member of the organization itself
|
||||
hasOrgVisible := true
|
||||
// Not SignedUser
|
||||
if user == nil {
|
||||
hasOrgVisible = repoOwner.Visibility == VisibleTypePublic
|
||||
} else if !user.IsAdmin {
|
||||
hasMemberWithUserID, err := sess.
|
||||
Where("uid=?", user.ID).
|
||||
And("org_id=?", repoOwner.ID).
|
||||
Table("org_user").
|
||||
Exist()
|
||||
if err != nil {
|
||||
hasOrgVisible = false
|
||||
}
|
||||
if (repoOwner.Visibility == VisibleTypePrivate || user.IsRestricted) && !hasMemberWithUserID {
|
||||
hasOrgVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
isCollaborator, err := sess.Get(&Collaboration{RepoID: repo.ID, UserID: user.ID})
|
||||
if err != nil {
|
||||
return perm, err
|
||||
}
|
||||
|
||||
if repoOwner.Type == 1 && !hasOrgVisible && !isCollaborator {
|
||||
perm.AccessMode = AccessModeNone
|
||||
return perm, err
|
||||
}
|
||||
|
||||
var units []*RepoUnit
|
||||
if err := sess.Where("repo_id = ?", repo.ID).Find(&units); err != nil {
|
||||
return perm, err
|
||||
}
|
||||
perm.Units = units
|
||||
|
||||
// anonymous visit public repo
|
||||
if user == nil {
|
||||
perm.AccessMode = AccessModeRead
|
||||
return perm, err
|
||||
}
|
||||
|
||||
// Admin or the owner has super access to the repository
|
||||
if user.IsAdmin || user.ID == repo.OwnerID {
|
||||
perm.AccessMode = AccessModeOwner
|
||||
return perm, err
|
||||
}
|
||||
|
||||
accessLevel := func(user *User, repo *Repository) (int, error) {
|
||||
mode := AccessModeNone
|
||||
var userID int64
|
||||
restricted := false
|
||||
|
||||
if user != nil {
|
||||
userID = user.ID
|
||||
restricted = user.IsRestricted
|
||||
}
|
||||
|
||||
if !restricted && !repo.IsPrivate {
|
||||
mode = AccessModeRead
|
||||
}
|
||||
|
||||
if userID == 0 {
|
||||
return mode, nil
|
||||
}
|
||||
|
||||
if userID == repo.OwnerID {
|
||||
return AccessModeOwner, nil
|
||||
}
|
||||
|
||||
a := &Access{UserID: userID, RepoID: repo.ID}
|
||||
if has, err := sess.Get(a); !has || err != nil {
|
||||
return mode, err
|
||||
}
|
||||
return a.Mode, nil
|
||||
}
|
||||
|
||||
// plain user
|
||||
perm.AccessMode, err = accessLevel(user, repo)
|
||||
if err != nil {
|
||||
return perm, err
|
||||
}
|
||||
|
||||
// If Owner is no Org
|
||||
if repoOwner.Type != 1 {
|
||||
return perm, err
|
||||
}
|
||||
|
||||
perm.UnitsMode = make(map[int]int)
|
||||
|
||||
// Collaborators on organization
|
||||
if isCollaborator {
|
||||
for _, u := range units {
|
||||
perm.UnitsMode[u.Type] = perm.AccessMode
|
||||
}
|
||||
}
|
||||
|
||||
// get units mode from teams
|
||||
var teams []*Team
|
||||
err = sess.
|
||||
Join("INNER", "team_user", "team_user.team_id = team.id").
|
||||
Join("INNER", "team_repo", "team_repo.team_id = team.id").
|
||||
Where("team.org_id = ?", repo.OwnerID).
|
||||
And("team_user.uid=?", user.ID).
|
||||
And("team_repo.repo_id=?", repo.ID).
|
||||
Find(&teams)
|
||||
if err != nil {
|
||||
return perm, err
|
||||
}
|
||||
|
||||
// if user in an owner team
|
||||
for _, team := range teams {
|
||||
if team.Authorize >= AccessModeOwner {
|
||||
perm.AccessMode = AccessModeOwner
|
||||
perm.UnitsMode = nil
|
||||
return perm, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, u := range units {
|
||||
var found bool
|
||||
for _, team := range teams {
|
||||
var teamU []*TeamUnit
|
||||
var unitEnabled bool
|
||||
err = sess.Where("team_id = ?", team.ID).Find(&teamU)
|
||||
|
||||
for _, tu := range teamU {
|
||||
if tu.Type == u.Type {
|
||||
unitEnabled = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if unitEnabled {
|
||||
m := perm.UnitsMode[u.Type]
|
||||
if m < team.Authorize {
|
||||
perm.UnitsMode[u.Type] = team.Authorize
|
||||
}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
// for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
|
||||
if !found && !repo.IsPrivate && !user.IsRestricted {
|
||||
if _, ok := perm.UnitsMode[u.Type]; !ok {
|
||||
perm.UnitsMode[u.Type] = AccessModeRead
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove no permission units
|
||||
perm.Units = make([]*RepoUnit, 0, len(units))
|
||||
for t := range perm.UnitsMode {
|
||||
for _, u := range units {
|
||||
if u.Type == t {
|
||||
perm.Units = append(perm.Units, u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return perm, err
|
||||
}
|
||||
|
||||
// isOfficialReviewer static function based on 5d78792385
|
||||
isOfficialReviewer := func(sess base.Session, issueID int64, reviewer *User) (bool, error) {
|
||||
pr := new(PullRequest)
|
||||
has, err := sess.ID(issueID).Get(pr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if !has {
|
||||
return false, fmt.Errorf("PullRequest for issueID %d not exist", issueID)
|
||||
}
|
||||
|
||||
baseRepo := new(Repository)
|
||||
has, err = sess.ID(pr.BaseRepoID).Get(baseRepo)
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if !has {
|
||||
return false, fmt.Errorf("baseRepo with id %d not exist", pr.BaseRepoID)
|
||||
}
|
||||
protectedBranch := new(ProtectedBranch)
|
||||
has, err = sess.Where("repo_id=? AND branch_name=?", baseRepo.ID, pr.BaseBranch).Get(protectedBranch)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !has {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !protectedBranch.EnableApprovalsWhitelist {
|
||||
perm, err := getUserRepoPermission(sess, baseRepo, reviewer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(perm.UnitsMode) == 0 {
|
||||
for _, u := range perm.Units {
|
||||
if u.Type == UnitTypeCode {
|
||||
return AccessModeWrite <= perm.AccessMode, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return AccessModeWrite <= perm.UnitsMode[UnitTypeCode], nil
|
||||
}
|
||||
if slices.Contains(protectedBranch.ApprovalsWhitelistUserIDs, reviewer.ID) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// isUserInTeams
|
||||
return sess.Where("uid=?", reviewer.ID).In("team_id", protectedBranch.ApprovalsWhitelistTeamIDs).Exist(new(TeamUser))
|
||||
}
|
||||
|
||||
if _, err := x.Exec("UPDATE `protected_branch` SET `enable_whitelist` = ? WHERE enable_whitelist IS NULL", false); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := x.Exec("UPDATE `protected_branch` SET `can_push` = `enable_whitelist`"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := x.Exec("UPDATE `protected_branch` SET `enable_approvals_whitelist` = ? WHERE `required_approvals` > ?", true, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pageSize int64 = 20
|
||||
qresult, err := x.QueryInterface("SELECT max(id) as max_id FROM issue")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var totalIssues int64
|
||||
totalIssues, ok := qresult[0]["max_id"].(int64)
|
||||
if !ok {
|
||||
// If there are no issues at all we ignore it
|
||||
return nil
|
||||
}
|
||||
totalPages := totalIssues / pageSize
|
||||
|
||||
executeBody := func(page, pageSize int64) error {
|
||||
// Find latest review of each user in each pull request, and set official field if appropriate
|
||||
reviews := []*Review{}
|
||||
|
||||
if err := x.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id > ? AND issue_id <= ? AND type in (?, ?) GROUP BY issue_id, reviewer_id)",
|
||||
page*pageSize, (page+1)*pageSize, ReviewTypeApprove, ReviewTypeReject).
|
||||
Find(&reviews); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(reviews) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var updated int
|
||||
for _, review := range reviews {
|
||||
reviewer := new(User)
|
||||
has, err := sess.ID(review.ReviewerID).Get(reviewer)
|
||||
if err != nil || !has {
|
||||
// Error might occur if user doesn't exist, ignore it.
|
||||
continue
|
||||
}
|
||||
|
||||
official, err := isOfficialReviewer(sess, review.IssueID, reviewer)
|
||||
if err != nil {
|
||||
// Branch might not be protected or other error, ignore it.
|
||||
continue
|
||||
}
|
||||
review.Official = official
|
||||
updated++
|
||||
if _, err := sess.ID(review.ID).Cols("official").Update(review); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if updated > 0 {
|
||||
return sess.Commit()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var page int64
|
||||
for page = 0; page <= totalPages; page++ {
|
||||
if err := executeBody(page, pageSize); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func RemoveAttachmentMissedRepo(x base.EngineMigration) error {
|
||||
type Attachment struct {
|
||||
UUID string `xorm:"uuid"`
|
||||
}
|
||||
var start int
|
||||
attachments := make([]*Attachment, 0, 50)
|
||||
for {
|
||||
err := x.Select("uuid").Where(builder.NotIn("release_id", builder.Select("id").From("`release`"))).
|
||||
And("release_id > 0").
|
||||
OrderBy("id").Limit(50, start).Find(&attachments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
uuid := attachments[i].UUID
|
||||
if err = util.RemoveAll(filepath.Join(setting.Attachment.Storage.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
|
||||
log.Warn("Unable to remove attachment file by UUID %s: %v", uuid, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(attachments) < 50 {
|
||||
break
|
||||
}
|
||||
start += 50
|
||||
attachments = attachments[:0]
|
||||
}
|
||||
|
||||
_, err := x.Exec("DELETE FROM attachment WHERE release_id > 0 AND release_id NOT IN (SELECT id FROM `release`)")
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func FeatureChangeTargetBranch(x base.EngineMigration) error {
|
||||
type Comment struct {
|
||||
OldRef string
|
||||
NewRef string
|
||||
}
|
||||
|
||||
if err := x.Sync(new(Comment)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func SanitizeOriginalURL(x base.EngineMigration) error {
|
||||
type Repository struct {
|
||||
ID int64
|
||||
OriginalURL string `xorm:"VARCHAR(2048)"`
|
||||
}
|
||||
|
||||
var last int
|
||||
const batchSize = 50
|
||||
for {
|
||||
results := make([]Repository, 0, batchSize)
|
||||
err := x.Where("original_url <> '' AND original_url IS NOT NULL").
|
||||
And("original_service_type = 0 OR original_service_type IS NULL").
|
||||
OrderBy("id").
|
||||
Limit(batchSize, last).
|
||||
Find(&results)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(results) == 0 {
|
||||
break
|
||||
}
|
||||
last += len(results)
|
||||
|
||||
for _, res := range results {
|
||||
u, err := url.Parse(res.OriginalURL)
|
||||
if err != nil {
|
||||
// it is ok to continue here, we only care about fixing URLs that we can read
|
||||
continue
|
||||
}
|
||||
u.User = nil
|
||||
originalURL := u.String()
|
||||
_, err = x.Exec("UPDATE repository SET original_url = ? WHERE id = ?", originalURL, res.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/container"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
func RenameExistingUserAvatarName(x base.EngineMigration) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
LowerName string `xorm:"UNIQUE NOT NULL"`
|
||||
Avatar string
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
count, err := x.Count(new(User))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("%d User Avatar(s) to migrate ...", count)
|
||||
|
||||
deleteList := make(container.Set[string])
|
||||
start := 0
|
||||
migrated := 0
|
||||
for {
|
||||
if err := sess.Begin(); err != nil {
|
||||
return fmt.Errorf("session.Begin: %w", err)
|
||||
}
|
||||
users := make([]*User, 0, 50)
|
||||
if err := sess.Table("user").Asc("id").Limit(50, start).Find(&users); err != nil {
|
||||
return fmt.Errorf("select users from id [%d]: %w", start, err)
|
||||
}
|
||||
if len(users) == 0 {
|
||||
_ = sess.Rollback()
|
||||
break
|
||||
}
|
||||
|
||||
log.Info("select users [%d - %d]", start, start+len(users))
|
||||
start += 50
|
||||
|
||||
for _, user := range users {
|
||||
oldAvatar := user.Avatar
|
||||
|
||||
if stat, err := os.Stat(filepath.Join(setting.Avatar.Storage.Path, oldAvatar)); err != nil || !stat.Mode().IsRegular() {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Error: \"%s\" is not a regular file", oldAvatar)
|
||||
}
|
||||
log.Warn("[user: %s] os.Stat: %v", user.LowerName, err)
|
||||
// avatar doesn't exist in the storage
|
||||
// no need to move avatar and update database
|
||||
// we can just skip this
|
||||
continue
|
||||
}
|
||||
|
||||
newAvatar, err := copyOldAvatarToNewLocation(user.ID, oldAvatar)
|
||||
if err != nil {
|
||||
_ = sess.Rollback()
|
||||
return fmt.Errorf("[user: %s] %w", user.LowerName, err)
|
||||
} else if newAvatar == oldAvatar {
|
||||
continue
|
||||
}
|
||||
|
||||
user.Avatar = newAvatar
|
||||
if _, err := sess.ID(user.ID).Cols("avatar").Update(user); err != nil {
|
||||
_ = sess.Rollback()
|
||||
return fmt.Errorf("[user: %s] user table update: %w", user.LowerName, err)
|
||||
}
|
||||
|
||||
deleteList.Add(filepath.Join(setting.Avatar.Storage.Path, oldAvatar))
|
||||
migrated++
|
||||
select {
|
||||
case <-ticker.C:
|
||||
log.Info(
|
||||
"%d/%d (%2.0f%%) User Avatar(s) migrated (%d old avatars to be deleted) in %d batches. %d Remaining ...",
|
||||
migrated,
|
||||
count,
|
||||
float64(migrated)/float64(count)*100,
|
||||
len(deleteList),
|
||||
int(math.Ceil(float64(migrated)/float64(50))),
|
||||
count-int64(migrated))
|
||||
default:
|
||||
}
|
||||
}
|
||||
if err := sess.Commit(); err != nil {
|
||||
_ = sess.Rollback()
|
||||
return fmt.Errorf("commit session: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
deleteCount := len(deleteList)
|
||||
log.Info("Deleting %d old avatars ...", deleteCount)
|
||||
i := 0
|
||||
for file := range deleteList {
|
||||
if err := util.Remove(file); err != nil {
|
||||
log.Warn("util.Remove: %v", err)
|
||||
}
|
||||
i++
|
||||
select {
|
||||
case <-ticker.C:
|
||||
log.Info(
|
||||
"%d/%d (%2.0f%%) Old User Avatar(s) deleted. %d Remaining ...",
|
||||
i,
|
||||
deleteCount,
|
||||
float64(i)/float64(deleteCount)*100,
|
||||
deleteCount-i)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Completed migrating %d User Avatar(s) and deleting %d Old Avatars", count, deleteCount)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyOldAvatarToNewLocation copies oldAvatar to newAvatarLocation
|
||||
// and returns newAvatar location
|
||||
func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) {
|
||||
fr, err := os.Open(filepath.Join(setting.Avatar.Storage.Path, oldAvatar))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("os.Open: %w", err)
|
||||
}
|
||||
defer fr.Close()
|
||||
|
||||
data, err := io.ReadAll(fr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("io.ReadAll: %w", err)
|
||||
}
|
||||
|
||||
newAvatar := fmt.Sprintf("%x", md5.Sum(fmt.Appendf(nil, "%d-%x", userID, md5.Sum(data))))
|
||||
if newAvatar == oldAvatar {
|
||||
return newAvatar, nil
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(setting.Avatar.Storage.Path, newAvatar), data, 0o666); err != nil {
|
||||
return "", fmt.Errorf("os.WriteFile: %w", err)
|
||||
}
|
||||
|
||||
return newAvatar, nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_11
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func ExtendTrackedTimes(x base.EngineMigration) error {
|
||||
type TrackedTime struct {
|
||||
Time int64 `xorm:"NOT NULL"`
|
||||
Deleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sess.Exec("DELETE FROM tracked_time WHERE time IS NULL"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sess.Sync(new(TrackedTime)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
Reference in New Issue
Block a user