1
1
mirror of https://github.com/go-gitea/gitea synced 2025-12-07 13:28:25 +00:00

Merge branch 'main' into allow-force-push-protected-branches

This commit is contained in:
Henry Goodman
2024-01-11 18:59:27 +11:00
committed by GitHub
480 changed files with 4706 additions and 3755 deletions
-13
View File
@@ -90,19 +90,6 @@ func getArtifactByNameAndPath(ctx context.Context, runID int64, name, fpath stri
return &art, nil
}
// GetArtifactByID returns an artifact by id
func GetArtifactByID(ctx context.Context, id int64) (*ActionArtifact, error) {
var art ActionArtifact
has, err := db.GetEngine(ctx).ID(id).Get(&art)
if err != nil {
return nil, err
} else if !has {
return nil, util.ErrNotExist
}
return &art, nil
}
// UpdateArtifactByID updates an artifact by id
func UpdateArtifactByID(ctx context.Context, id int64, art *ActionArtifact) error {
art.ID = id
+1 -1
View File
@@ -254,7 +254,7 @@ func DeleteRunner(ctx context.Context, id int64) error {
return err
}
_, err := db.GetEngine(ctx).Delete(&ActionRunner{ID: id})
_, err := db.DeleteByID[ActionRunner](ctx, id)
return err
}
+4 -8
View File
@@ -31,8 +31,8 @@ func init() {
}
func (v *ActionVariable) Validate() error {
if v.OwnerID == 0 && v.RepoID == 0 {
return errors.New("the variable is not bound to any scope")
if v.OwnerID != 0 && v.RepoID != 0 {
return errors.New("a variable should not be bound to an owner and a repository at the same time")
}
return nil
}
@@ -58,12 +58,8 @@ type FindVariablesOpts struct {
func (opts FindVariablesOpts) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
return cond
}
+2 -2
View File
@@ -59,8 +59,8 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// Mock time
timeutil.Set(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
defer timeutil.Unset()
timeutil.MockSet(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
defer timeutil.MockUnset()
for _, tc := range testCases {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.userID})
+3 -2
View File
@@ -107,8 +107,9 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st
// VerificationToken returns token for the user that will be valid in minutes (time)
func VerificationToken(user *user_model.User, minutes int) string {
return base.EncodeSha256(
time.Now().Truncate(1*time.Minute).Add(time.Duration(minutes)*time.Minute).Format(time.RFC1123Z) + ":" +
user.CreatedUnix.FormatLong() + ":" +
time.Now().Truncate(1*time.Minute).Add(time.Duration(minutes)*time.Minute).Format(
time.RFC1123Z) + ":" +
user.CreatedUnix.Format(time.RFC1123Z) + ":" +
user.Name + ":" +
user.Email + ":" +
strconv.FormatInt(user.ID, 10))
+2 -12
View File
@@ -227,16 +227,6 @@ func UpdatePublicKeyUpdated(ctx context.Context, id int64) error {
return nil
}
// DeletePublicKeys does the actual key deletion but does not update authorized_keys file.
func DeletePublicKeys(ctx context.Context, keyIDs ...int64) error {
if len(keyIDs) == 0 {
return nil
}
_, err := db.GetEngine(ctx).In("id", keyIDs).Delete(new(PublicKey))
return err
}
// PublicKeysAreExternallyManaged returns whether the provided KeyID represents an externally managed Key
func PublicKeysAreExternallyManaged(ctx context.Context, keys []*PublicKey) ([]bool, error) {
sources := make([]*auth.Source, 0, 5)
@@ -322,8 +312,8 @@ func deleteKeysMarkedForDeletion(ctx context.Context, keys []string) (bool, erro
log.Error("SearchPublicKeyByContent: %v", err)
continue
}
if err = DeletePublicKeys(ctx, key.ID); err != nil {
log.Error("deletePublicKeys: %v", err)
if _, err = db.DeleteByID[PublicKey](ctx, key.ID); err != nil {
log.Error("DeleteByID[PublicKey]: %v", err)
continue
}
sshKeysNeedUpdate = true
+10 -7
View File
@@ -41,12 +41,15 @@ func ReadSession(ctx context.Context, key string) (*Session, error) {
}
defer committer.Close()
session, exist, err := db.Get[Session](ctx, builder.Eq{"key": key})
session, exist, err := db.Get[Session](ctx, builder.Eq{"`key`": key})
if err != nil {
return nil, err
} else if !exist {
session.Expiry = timeutil.TimeStampNow()
if err := db.Insert(ctx, &session); err != nil {
session = &Session{
Key: key,
Expiry: timeutil.TimeStampNow(),
}
if err := db.Insert(ctx, session); err != nil {
return nil, err
}
}
@@ -56,7 +59,7 @@ func ReadSession(ctx context.Context, key string) (*Session, error) {
// ExistSession checks if a session exists
func ExistSession(ctx context.Context, key string) (bool, error) {
return db.Exist[Session](ctx, builder.Eq{"key": key})
return db.Exist[Session](ctx, builder.Eq{"`key`": key})
}
// DestroySession destroys a session
@@ -75,13 +78,13 @@ func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, er
}
defer committer.Close()
if has, err := db.Exist[Session](ctx, builder.Eq{"key": newKey}); err != nil {
if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": newKey}); err != nil {
return nil, err
} else if has {
return nil, fmt.Errorf("session Key: %s already exists", newKey)
}
if has, err := db.Exist[Session](ctx, builder.Eq{"key": oldKey}); err != nil {
if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": oldKey}); err != nil {
return nil, err
} else if !has {
if err := db.Insert(ctx, &Session{
@@ -96,7 +99,7 @@ func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, er
return nil, err
}
s, _, err := db.Get[Session](ctx, builder.Eq{"key": newKey})
s, _, err := db.Get[Session](ctx, builder.Eq{"`key`": newKey})
if err != nil {
// is not exist, it should be impossible
return nil, err
+1 -5
View File
@@ -261,16 +261,12 @@ func (opts FindSourcesOptions) ToConds() builder.Cond {
// IsSSPIEnabled returns true if there is at least one activated login
// source of type LoginSSPI
func IsSSPIEnabled(ctx context.Context) bool {
if !db.HasEngine {
return false
}
exist, err := db.Exist[Source](ctx, FindSourcesOptions{
IsActive: util.OptionalBoolTrue,
LoginType: SSPI,
}.ToConds())
if err != nil {
log.Error("Active SSPI Sources: %v", err)
log.Error("IsSSPIEnabled: failed to query active SSPI sources: %v", err)
return false
}
return exist
+5 -2
View File
@@ -5,6 +5,8 @@ package avatars
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"net/url"
"path"
@@ -13,7 +15,6 @@ import (
"sync/atomic"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -90,7 +91,9 @@ func DefaultAvatarLink() string {
// HashEmail hashes email address to MD5 string. https://en.gravatar.com/site/implement/hash/
func HashEmail(email string) string {
return base.EncodeMD5(strings.ToLower(strings.TrimSpace(email)))
m := md5.New()
_, _ = m.Write([]byte(strings.ToLower(strings.TrimSpace(email))))
return hex.EncodeToString(m.Sum(nil))
}
// GetEmailForHash converts a provided md5sum to the email
+191
View File
@@ -0,0 +1,191 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package db
import (
"errors"
"fmt"
"strings"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
type CheckCollationsResult struct {
ExpectedCollation string
AvailableCollation container.Set[string]
DatabaseCollation string
IsCollationCaseSensitive func(s string) bool
CollationEquals func(a, b string) bool
ExistingTableNumber int
InconsistentCollationColumns []string
}
func findAvailableCollationsMySQL(x *xorm.Engine) (ret container.Set[string], err error) {
var res []struct {
Collation string
}
if err = x.SQL("SHOW COLLATION WHERE (Collation = 'utf8mb4_bin') OR (Collation LIKE '%\\_as\\_cs%')").Find(&res); err != nil {
return nil, err
}
ret = make(container.Set[string], len(res))
for _, r := range res {
ret.Add(r.Collation)
}
return ret, nil
}
func findAvailableCollationsMSSQL(x *xorm.Engine) (ret container.Set[string], err error) {
var res []struct {
Name string
}
if err = x.SQL("SELECT * FROM sys.fn_helpcollations() WHERE name LIKE '%[_]CS[_]AS%'").Find(&res); err != nil {
return nil, err
}
ret = make(container.Set[string], len(res))
for _, r := range res {
ret.Add(r.Name)
}
return ret, nil
}
func CheckCollations(x *xorm.Engine) (*CheckCollationsResult, error) {
dbTables, err := x.DBMetas()
if err != nil {
return nil, err
}
res := &CheckCollationsResult{
ExistingTableNumber: len(dbTables),
CollationEquals: func(a, b string) bool { return a == b },
}
var candidateCollations []string
if x.Dialect().URI().DBType == schemas.MYSQL {
if _, err = x.SQL("SELECT @@collation_database").Get(&res.DatabaseCollation); err != nil {
return nil, err
}
res.IsCollationCaseSensitive = func(s string) bool {
return s == "utf8mb4_bin" || strings.HasSuffix(s, "_as_cs")
}
candidateCollations = []string{"utf8mb4_0900_as_cs", "uca1400_as_cs", "utf8mb4_bin"}
res.AvailableCollation, err = findAvailableCollationsMySQL(x)
if err != nil {
return nil, err
}
res.CollationEquals = func(a, b string) bool {
// MariaDB adds the "utf8mb4_" prefix, eg: "utf8mb4_uca1400_as_cs", but not the name "uca1400_as_cs" in "SHOW COLLATION"
// At the moment, it's safe to ignore the database difference, just trim the prefix and compare. It could be fixed easily if there is any problem in the future.
return a == b || strings.TrimPrefix(a, "utf8mb4_") == strings.TrimPrefix(b, "utf8mb4_")
}
} else if x.Dialect().URI().DBType == schemas.MSSQL {
if _, err = x.SQL("SELECT DATABASEPROPERTYEX(DB_NAME(), 'Collation')").Get(&res.DatabaseCollation); err != nil {
return nil, err
}
res.IsCollationCaseSensitive = func(s string) bool {
return strings.HasSuffix(s, "_CS_AS")
}
candidateCollations = []string{"Latin1_General_CS_AS"}
res.AvailableCollation, err = findAvailableCollationsMSSQL(x)
if err != nil {
return nil, err
}
} else {
return nil, nil
}
if res.DatabaseCollation == "" {
return nil, errors.New("unable to get collation for current database")
}
res.ExpectedCollation = setting.Database.CharsetCollation
if res.ExpectedCollation == "" {
for _, collation := range candidateCollations {
if res.AvailableCollation.Contains(collation) {
res.ExpectedCollation = collation
break
}
}
}
if res.ExpectedCollation == "" {
return nil, errors.New("unable to find a suitable collation for current database")
}
allColumnsMatchExpected := true
allColumnsMatchDatabase := true
for _, table := range dbTables {
for _, col := range table.Columns() {
if col.Collation != "" {
allColumnsMatchExpected = allColumnsMatchExpected && res.CollationEquals(col.Collation, res.ExpectedCollation)
allColumnsMatchDatabase = allColumnsMatchDatabase && res.CollationEquals(col.Collation, res.DatabaseCollation)
if !res.IsCollationCaseSensitive(col.Collation) || !res.CollationEquals(col.Collation, res.DatabaseCollation) {
res.InconsistentCollationColumns = append(res.InconsistentCollationColumns, fmt.Sprintf("%s.%s", table.Name, col.Name))
}
}
}
}
// if all columns match expected collation or all match database collation, then it could also be considered as "consistent"
if allColumnsMatchExpected || allColumnsMatchDatabase {
res.InconsistentCollationColumns = nil
}
return res, nil
}
func CheckCollationsDefaultEngine() (*CheckCollationsResult, error) {
return CheckCollations(x)
}
func alterDatabaseCollation(x *xorm.Engine, collation string) error {
if x.Dialect().URI().DBType == schemas.MYSQL {
_, err := x.Exec("ALTER DATABASE CHARACTER SET utf8mb4 COLLATE " + collation)
return err
} else if x.Dialect().URI().DBType == schemas.MSSQL {
// TODO: MSSQL has many limitations on changing database collation, it could fail in many cases.
_, err := x.Exec("ALTER DATABASE CURRENT COLLATE " + collation)
return err
}
return errors.New("unsupported database type")
}
// preprocessDatabaseCollation checks database & table column collation, and alter the database collation if needed
func preprocessDatabaseCollation(x *xorm.Engine) {
r, err := CheckCollations(x)
if err != nil {
log.Error("Failed to check database collation: %v", err)
}
if r == nil {
return // no check result means the database doesn't need to do such check/process (at the moment ....)
}
// try to alter database collation to expected if the database is empty, it might fail in some cases (and it isn't necessary to succeed)
// at the moment, there is no "altering" solution for MSSQL, site admin should manually change the database collation
// and there is a bug https://github.com/go-testfixtures/testfixtures/pull/182 mssql: Invalid object name 'information_schema.tables'.
if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 && x.Dialect().URI().DBType == schemas.MYSQL {
if err = alterDatabaseCollation(x, r.ExpectedCollation); err != nil {
log.Error("Failed to change database collation to %q: %v", r.ExpectedCollation, err)
} else {
_, _ = x.Exec("SELECT 1") // after "altering", MSSQL's session becomes invalid, so make a simple query to "refresh" the session
if r, err = CheckCollations(x); err != nil {
log.Error("Failed to check database collation again after altering: %v", err) // impossible case
return
}
log.Warn("Current database has been altered to use collation %q", r.DatabaseCollation)
}
}
// check column collation, and show warning/error to end users -- no need to fatal, do not block the startup
if !r.IsCollationCaseSensitive(r.DatabaseCollation) {
log.Warn("Current database is using a case-insensitive collation %q, although Gitea could work with it, there might be some rare cases which don't work as expected.", r.DatabaseCollation)
}
if len(r.InconsistentCollationColumns) > 0 {
log.Error("There are %d table columns using inconsistent collation, they should use %q. Please go to admin panel Self Check page", len(r.InconsistentCollationColumns), r.DatabaseCollation)
}
}
+27 -7
View File
@@ -175,7 +175,7 @@ func Exec(ctx context.Context, sqlAndArgs ...any) (sql.Result, error) {
func Get[T any](ctx context.Context, cond builder.Cond) (object *T, exist bool, err error) {
if !cond.IsValid() {
return nil, false, ErrConditionRequired{}
panic("cond is invalid in db.Get(ctx, cond). This should not be possible.")
}
var bean T
@@ -201,7 +201,7 @@ func GetByID[T any](ctx context.Context, id int64) (object *T, exist bool, err e
func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) {
if !cond.IsValid() {
return false, ErrConditionRequired{}
panic("cond is invalid in db.Exist(ctx, cond). This should not be possible.")
}
var bean T
@@ -213,16 +213,36 @@ func ExistByID[T any](ctx context.Context, id int64) (bool, error) {
return GetEngine(ctx).ID(id).NoAutoCondition().Exist(&bean)
}
// DeleteByID deletes the given bean with the given ID
func DeleteByID[T any](ctx context.Context, id int64) (int64, error) {
var bean T
return GetEngine(ctx).ID(id).NoAutoCondition().NoAutoTime().Delete(&bean)
}
func DeleteByIDs[T any](ctx context.Context, ids ...int64) error {
if len(ids) == 0 {
return nil
}
var bean T
_, err := GetEngine(ctx).In("id", ids).NoAutoCondition().NoAutoTime().Delete(&bean)
return err
}
func Delete[T any](ctx context.Context, opts FindOptions) (int64, error) {
if opts == nil || !opts.ToConds().IsValid() {
panic("opts are empty or invalid in db.Delete(ctx, opts). This should not be possible.")
}
var bean T
return GetEngine(ctx).Where(opts.ToConds()).NoAutoCondition().NoAutoTime().Delete(&bean)
}
// DeleteByBean deletes all records according non-empty fields of the bean as conditions.
func DeleteByBean(ctx context.Context, bean any) (int64, error) {
return GetEngine(ctx).Delete(bean)
}
// DeleteByID deletes the given bean with the given ID
func DeleteByID(ctx context.Context, id int64, bean any) (int64, error) {
return GetEngine(ctx).ID(id).NoAutoCondition().NoAutoTime().Delete(bean)
}
// FindIDs finds the IDs for the given table name satisfying the given condition
// By passing a different value than "id" for "idCol", you can query for foreign IDs, i.e. the repo IDs which satisfy the condition
func FindIDs(ctx context.Context, tableName, idCol string, cond builder.Cond) ([]int64, error) {
+10 -5
View File
@@ -14,13 +14,18 @@ import (
"xorm.io/xorm/schemas"
)
// ConvertUtf8ToUtf8mb4 converts database and tables from utf8 to utf8mb4 if it's mysql and set ROW_FORMAT=dynamic
func ConvertUtf8ToUtf8mb4() error {
// ConvertDatabaseTable converts database and tables from utf8 to utf8mb4 if it's mysql and set ROW_FORMAT=dynamic
func ConvertDatabaseTable() error {
if x.Dialect().URI().DBType != schemas.MYSQL {
return nil
}
_, err := x.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci", setting.Database.Name))
r, err := CheckCollations(x)
if err != nil {
return err
}
_, err = x.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET utf8mb4 COLLATE %s", setting.Database.Name, r.ExpectedCollation))
if err != nil {
return err
}
@@ -30,11 +35,11 @@ func ConvertUtf8ToUtf8mb4() error {
return err
}
for _, table := range tables {
if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` ROW_FORMAT=dynamic;", table.Name)); err != nil {
if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` ROW_FORMAT=dynamic", table.Name)); err != nil {
return err
}
if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;", table.Name)); err != nil {
if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` CONVERT TO CHARACTER SET utf8mb4 COLLATE %s", table.Name, r.ExpectedCollation)); err != nil {
return err
}
}
+2 -3
View File
@@ -27,9 +27,6 @@ var (
x *xorm.Engine
tables []any
initFuncs []func() error
// HasEngine specifies if we have a xorm.Engine
HasEngine bool
)
// Engine represents a xorm engine or session.
@@ -185,6 +182,8 @@ func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine)
return err
}
preprocessDatabaseCollation(x)
// We have to run migrateFunc here in case the user is re-running installation on a previously created DB.
// If we do not then table schemas will be changed and there will be conflicts when the migrations run properly.
//
-18
View File
@@ -72,21 +72,3 @@ func (err ErrNotExist) Error() string {
func (err ErrNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrConditionRequired represents an error which require condition.
type ErrConditionRequired struct{}
// IsErrConditionRequired checks if an error is an ErrConditionRequired
func IsErrConditionRequired(err error) bool {
_, ok := err.(ErrConditionRequired)
return ok
}
func (err ErrConditionRequired) Error() string {
return "condition is required"
}
// Unwrap unwraps this as a ErrNotExist err
func (err ErrConditionRequired) Unwrap() error {
return util.ErrInvalidArgument
}
+3 -4
View File
@@ -17,7 +17,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -132,7 +131,7 @@ func (protectBranch *ProtectedBranch) CanUserPush(ctx context.Context, user *use
return writeAccess
}
if base.Int64sContains(protectBranch.WhitelistUserIDs, user.ID) {
if slices.Contains(protectBranch.WhitelistUserIDs, user.ID) {
return true
}
@@ -182,7 +181,7 @@ func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch,
return permissionInRepo.CanWrite(unit.TypeCode)
}
if base.Int64sContains(protectBranch.MergeWhitelistUserIDs, userID) {
if slices.Contains(protectBranch.MergeWhitelistUserIDs, userID) {
return true
}
@@ -214,7 +213,7 @@ func IsUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch,
return writeAccess, nil
}
if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) {
if slices.Contains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) {
return true, nil
}
+2 -2
View File
@@ -6,11 +6,11 @@ package git
import (
"context"
"regexp"
"slices"
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/timeutil"
"github.com/gobwas/glob"
@@ -76,7 +76,7 @@ func DeleteProtectedTag(ctx context.Context, pt *ProtectedTag) error {
// IsUserAllowedModifyTag returns true if the user is allowed to modify the tag
func IsUserAllowedModifyTag(ctx context.Context, pt *ProtectedTag, userID int64) (bool, error) {
if base.Int64sContains(pt.AllowlistUserIDs, userID) {
if slices.Contains(pt.AllowlistUserIDs, userID) {
return true, nil
}
+4 -1
View File
@@ -18,7 +18,10 @@ func TestUpdateAssignee(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// Fake issue with assignees
issue, err := issues_model.GetIssueWithAttrsByID(db.DefaultContext, 1)
issue, err := issues_model.GetIssueByID(db.DefaultContext, 1)
assert.NoError(t, err)
err = issue.LoadAttributes(db.DefaultContext)
assert.NoError(t, err)
// Assign multiple users
+6 -11
View File
@@ -899,15 +899,15 @@ func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Is
// newDeadline = 0 means deleting
if newDeadlineUnix == 0 {
commentType = CommentTypeRemovedDeadline
content = issue.DeadlineUnix.Format("2006-01-02")
content = issue.DeadlineUnix.FormatDate()
} else if issue.DeadlineUnix == 0 {
// Check if the new date was added or modified
// If the actual deadline is 0 => deadline added
commentType = CommentTypeAddedDeadline
content = newDeadlineUnix.Format("2006-01-02")
content = newDeadlineUnix.FormatDate()
} else { // Otherwise modified
commentType = CommentTypeModifiedDeadline
content = newDeadlineUnix.Format("2006-01-02") + "|" + issue.DeadlineUnix.Format("2006-01-02")
content = newDeadlineUnix.FormatDate() + "|" + issue.DeadlineUnix.FormatDate()
}
if err := issue.LoadRepo(ctx); err != nil {
@@ -1161,14 +1161,9 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
func UpdateCommentsMigrationsByType(ctx context.Context, tp structs.GitServiceType, originalAuthorID string, posterID int64) error {
_, err := db.GetEngine(ctx).Table("comment").
Where(builder.In("issue_id",
builder.Select("issue.id").
From("issue").
InnerJoin("repository", "issue.repo_id = repository.id").
Where(builder.Eq{
"repository.original_service_type": tp,
}),
)).
Join("INNER", "issue", "issue.id = comment.issue_id").
Join("INNER", "repository", "issue.repo_id = repository.id").
Where("repository.original_service_type = ?", tp).
And("comment.original_author_id = ?", originalAuthorID).
Update(map[string]any{
"poster_id": posterID,
-9
View File
@@ -534,15 +534,6 @@ func GetIssueByID(ctx context.Context, id int64) (*Issue, error) {
return issue, nil
}
// GetIssueWithAttrsByID returns an issue with attributes by given ID.
func GetIssueWithAttrsByID(ctx context.Context, id int64) (*Issue, error) {
issue, err := GetIssueByID(ctx, id)
if err != nil {
return nil, err
}
return issue, issue.LoadAttributes(ctx)
}
// GetIssuesByIDs return issues with the given IDs.
// If keepOrder is true, the order of the returned issues will be the same as the given IDs.
func GetIssuesByIDs(ctx context.Context, issueIDs []int64, keepOrder ...bool) (IssueList, error) {
-20
View File
@@ -455,26 +455,6 @@ func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Sess
)
}
// GetRepoIDsForIssuesOptions find all repo ids for the given options
func GetRepoIDsForIssuesOptions(ctx context.Context, opts *IssuesOptions, user *user_model.User) ([]int64, error) {
repoIDs := make([]int64, 0, 5)
e := db.GetEngine(ctx)
sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
applyConditions(sess, opts)
accessCond := repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)
if err := sess.Where(accessCond).
Distinct("issue.repo_id").
Table("issue").
Find(&repoIDs); err != nil {
return nil, fmt.Errorf("unable to GetRepoIDsForIssuesOptions: %w", err)
}
return repoIDs, nil
}
// Issues returns a list of issues by given conditions.
func Issues(ctx context.Context, opts *IssuesOptions) (IssueList, error) {
sess := db.GetEngine(ctx).
+2 -32
View File
@@ -216,36 +216,6 @@ func TestIssue_loadTotalTimes(t *testing.T) {
assert.Equal(t, int64(3682), ms.TotalTrackedTime)
}
func TestGetRepoIDsForIssuesOptions(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
for _, test := range []struct {
Opts issues_model.IssuesOptions
ExpectedRepoIDs []int64
}{
{
issues_model.IssuesOptions{
AssigneeID: 2,
},
[]int64{3, 32},
},
{
issues_model.IssuesOptions{
RepoCond: builder.In("repo_id", 1, 2),
},
[]int64{1, 2},
},
} {
repoIDs, err := issues_model.GetRepoIDsForIssuesOptions(db.DefaultContext, &test.Opts, user)
assert.NoError(t, err)
if assert.Len(t, repoIDs, len(test.ExpectedRepoIDs)) {
for i, repoID := range repoIDs {
assert.EqualValues(t, test.ExpectedRepoIDs[i], repoID)
}
}
}
}
func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *issues_model.Issue {
var newIssue issues_model.Issue
t.Run(title, func(t *testing.T) {
@@ -279,11 +249,11 @@ func TestIssue_InsertIssue(t *testing.T) {
// there are 5 issues and max index is 5 on repository 1, so this one should 6
issue := testInsertIssue(t, "my issue1", "special issue's comments?", 6)
_, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Delete(new(issues_model.Issue))
_, err := db.DeleteByID[issues_model.Issue](db.DefaultContext, issue.ID)
assert.NoError(t, err)
issue = testInsertIssue(t, `my issue2, this is my son's love \n \r \ `, "special issue's '' comments?", 7)
_, err = db.GetEngine(db.DefaultContext).ID(issue.ID).Delete(new(issues_model.Issue))
_, err = db.DeleteByID[issues_model.Issue](db.DefaultContext, issue.ID)
assert.NoError(t, err)
}
+1 -17
View File
@@ -256,7 +256,7 @@ func DeleteLabel(ctx context.Context, id, labelID int64) error {
return nil
}
if _, err = sess.ID(labelID).Delete(new(Label)); err != nil {
if _, err = db.DeleteByID[Label](ctx, labelID); err != nil {
return err
} else if _, err = sess.
Where("label_id = ?", labelID).
@@ -424,22 +424,6 @@ func GetLabelInOrgByID(ctx context.Context, orgID, labelID int64) (*Label, error
return l, nil
}
// GetLabelIDsInOrgByNames returns a list of labelIDs by names in a given
// organization.
func GetLabelIDsInOrgByNames(ctx context.Context, orgID int64, labelNames []string) ([]int64, error) {
if orgID <= 0 {
return nil, ErrOrgLabelNotExist{0, orgID}
}
labelIDs := make([]int64, 0, len(labelNames))
return labelIDs, db.GetEngine(ctx).Table("label").
Where("org_id = ?", orgID).
In("name", labelNames).
Asc("name").
Cols("id").
Find(&labelIDs)
}
// GetLabelsInOrgByIDs returns a list of labels by IDs in given organization,
// it silently ignores label IDs that do not belong to the organization.
func GetLabelsInOrgByIDs(ctx context.Context, orgID int64, labelIDs []int64) ([]*Label, error) {
-24
View File
@@ -164,30 +164,6 @@ func TestGetLabelInOrgByName(t *testing.T) {
assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
}
func TestGetLabelInOrgByNames(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
labelIDs, err := issues_model.GetLabelIDsInOrgByNames(db.DefaultContext, 3, []string{"orglabel3", "orglabel4"})
assert.NoError(t, err)
assert.Len(t, labelIDs, 2)
assert.Equal(t, int64(3), labelIDs[0])
assert.Equal(t, int64(4), labelIDs[1])
}
func TestGetLabelInOrgByNamesDiscardsNonExistentLabels(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// orglabel99 doesn't exists.. See labels.yml
labelIDs, err := issues_model.GetLabelIDsInOrgByNames(db.DefaultContext, 3, []string{"orglabel3", "orglabel4", "orglabel99"})
assert.NoError(t, err)
assert.Len(t, labelIDs, 2)
assert.Equal(t, int64(3), labelIDs[0])
assert.Equal(t, int64(4), labelIDs[1])
assert.NoError(t, err)
}
func TestGetLabelInOrgByID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
label, err := issues_model.GetLabelInOrgByID(db.DefaultContext, 3, 3)
+3 -5
View File
@@ -86,7 +86,7 @@ func (m *Milestone) AfterLoad() {
return
}
m.DeadlineString = m.DeadlineUnix.Format("2006-01-02")
m.DeadlineString = m.DeadlineUnix.FormatDate()
if m.IsClosed {
m.IsOverdue = m.ClosedDateUnix >= m.DeadlineUnix
} else {
@@ -289,9 +289,7 @@ func DeleteMilestoneByRepoID(ctx context.Context, repoID, id int64) error {
}
defer committer.Close()
sess := db.GetEngine(ctx)
if _, err = sess.ID(m.ID).Delete(new(Milestone)); err != nil {
if _, err = db.DeleteByID[Milestone](ctx, m.ID); err != nil {
return err
}
@@ -311,7 +309,7 @@ func DeleteMilestoneByRepoID(ctx context.Context, repoID, id int64) error {
repo.NumMilestones = int(numMilestones)
repo.NumClosedMilestones = int(numClosedMilestones)
if _, err = sess.ID(repo.ID).Cols("num_milestones, num_closed_milestones").Update(repo); err != nil {
if _, err = db.GetEngine(ctx).ID(repo.ID).Cols("num_milestones, num_closed_milestones").Update(repo); err != nil {
return err
}
-26
View File
@@ -160,32 +160,6 @@ func (m MilestonesStats) Total() int64 {
return m.OpenCount + m.ClosedCount
}
// GetMilestonesStatsByRepoCond returns milestone statistic information for dashboard by given conditions.
func GetMilestonesStatsByRepoCond(ctx context.Context, repoCond builder.Cond) (*MilestonesStats, error) {
var err error
stats := &MilestonesStats{}
sess := db.GetEngine(ctx).Where("is_closed = ?", false)
if repoCond.IsValid() {
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
}
stats.OpenCount, err = sess.Count(new(Milestone))
if err != nil {
return nil, err
}
sess = db.GetEngine(ctx).Where("is_closed = ?", true)
if repoCond.IsValid() {
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
}
stats.ClosedCount, err = sess.Count(new(Milestone))
if err != nil {
return nil, err
}
return stats, nil
}
// GetMilestonesStatsByRepoCondAndKw returns milestone statistic information for dashboard by given repo conditions and name keyword.
func GetMilestonesStatsByRepoCondAndKw(ctx context.Context, repoCond builder.Cond, keyword string) (*MilestonesStats, error) {
var err error
-29
View File
@@ -17,7 +17,6 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
"xorm.io/builder"
)
func TestMilestone_State(t *testing.T) {
@@ -285,34 +284,6 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
})
}
func TestGetMilestonesStats(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
test := func(repoID int64) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
stats, err := issues_model.GetMilestonesStatsByRepoCond(db.DefaultContext, builder.And(builder.Eq{"repo_id": repoID}))
assert.NoError(t, err)
assert.EqualValues(t, repo.NumMilestones-repo.NumClosedMilestones, stats.OpenCount)
assert.EqualValues(t, repo.NumClosedMilestones, stats.ClosedCount)
}
test(1)
test(2)
test(3)
stats, err := issues_model.GetMilestonesStatsByRepoCond(db.DefaultContext, builder.And(builder.Eq{"repo_id": unittest.NonexistentID}))
assert.NoError(t, err)
assert.EqualValues(t, 0, stats.OpenCount)
assert.EqualValues(t, 0, stats.ClosedCount)
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
milestoneStats, err := issues_model.GetMilestonesStatsByRepoCond(db.DefaultContext, builder.In("repo_id", []int64{repo1.ID, repo2.ID}))
assert.NoError(t, err)
assert.EqualValues(t, repo1.NumOpenMilestones+repo2.NumOpenMilestones, milestoneStats.OpenCount)
assert.EqualValues(t, repo1.NumClosedMilestones+repo2.NumClosedMilestones, milestoneStats.ClosedCount)
}
func TestNewMilestone(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
milestone := &issues_model.Milestone{
-30
View File
@@ -78,24 +78,6 @@ func (err ErrPullRequestAlreadyExists) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrPullRequestHeadRepoMissing represents a "ErrPullRequestHeadRepoMissing" error
type ErrPullRequestHeadRepoMissing struct {
ID int64
HeadRepoID int64
}
// IsErrErrPullRequestHeadRepoMissing checks if an error is a ErrPullRequestHeadRepoMissing.
func IsErrErrPullRequestHeadRepoMissing(err error) bool {
_, ok := err.(ErrPullRequestHeadRepoMissing)
return ok
}
// Error does pretty-printing :D
func (err ErrPullRequestHeadRepoMissing) Error() string {
return fmt.Sprintf("pull request head repo missing [id: %d, head_repo_id: %d]",
err.ID, err.HeadRepoID)
}
// ErrPullWasClosed is used close a closed pull request
type ErrPullWasClosed struct {
ID int64
@@ -758,18 +740,6 @@ func (pr *PullRequest) IsSameRepo() bool {
return pr.BaseRepoID == pr.HeadRepoID
}
// GetPullRequestsByHeadBranch returns all prs by head branch
// Since there could be multiple prs with the same head branch, this function returns a slice of prs
func GetPullRequestsByHeadBranch(ctx context.Context, headBranch string, headRepoID int64) ([]*PullRequest, error) {
log.Trace("GetPullRequestsByHeadBranch: headBranch: '%s', headRepoID: '%d'", headBranch, headRepoID)
prs := make([]*PullRequest, 0, 2)
if err := db.GetEngine(ctx).Where(builder.Eq{"head_branch": headBranch, "head_repo_id": headRepoID}).
Find(&prs); err != nil {
return nil, err
}
return prs, nil
}
// GetBaseBranchLink returns the relative URL of the base branch
func (pr *PullRequest) GetBaseBranchLink(ctx context.Context) string {
if err := pr.LoadBaseRepo(ctx); err != nil {
+17 -16
View File
@@ -6,6 +6,7 @@ package issues
import (
"context"
"fmt"
"slices"
"strings"
"code.gitea.io/gitea/models/db"
@@ -15,7 +16,6 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -279,7 +279,7 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio
return team.UnitAccessMode(ctx, unit.TypeCode) >= perm.AccessModeWrite, nil
}
return base.Int64sContains(pb.ApprovalsWhitelistTeamIDs, team.ID), nil
return slices.Contains(pb.ApprovalsWhitelistTeamIDs, team.ID), nil
}
// CreateReview creates a new review based on opts
@@ -446,7 +446,7 @@ func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, revi
continue
}
if _, err := sess.ID(teamReviewRequest.ID).NoAutoCondition().Delete(teamReviewRequest); err != nil {
if _, err := db.DeleteByID[Review](ctx, teamReviewRequest.ID); err != nil {
return nil, nil, err
}
}
@@ -460,8 +460,10 @@ func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, revi
func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*Review, error) {
review := new(Review)
has, err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_id = ? AND original_author_id = 0 AND type in (?, ?, ?))",
issueID, userID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
has, err := db.GetEngine(ctx).Where(
builder.In("type", ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
And(builder.Eq{"issue_id": issueID, "reviewer_id": userID, "original_author_id": 0})).
Desc("id").
Get(review)
if err != nil {
return nil, err
@@ -475,13 +477,13 @@ func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*R
}
// GetTeamReviewerByIssueIDAndTeamID get the latest review request of reviewer team for a pull request
func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (review *Review, err error) {
review = new(Review)
func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (*Review, error) {
review := new(Review)
var has bool
if has, err = db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = ?)",
issueID, teamID).
Get(review); err != nil {
has, err := db.GetEngine(ctx).Where(builder.Eq{"issue_id": issueID, "reviewer_team_id": teamID}).
Desc("id").
Get(review)
if err != nil {
return nil, err
}
@@ -867,7 +869,6 @@ func DeleteReview(ctx context.Context, r *Review) error {
return err
}
defer committer.Close()
sess := db.GetEngine(ctx)
if r.ID == 0 {
return fmt.Errorf("review is not allowed to be 0")
@@ -883,7 +884,7 @@ func DeleteReview(ctx context.Context, r *Review) error {
ReviewID: r.ID,
}
if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil {
if _, err := db.Delete[Comment](ctx, opts); err != nil {
return err
}
@@ -893,7 +894,7 @@ func DeleteReview(ctx context.Context, r *Review) error {
ReviewID: r.ID,
}
if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil {
if _, err := db.Delete[Comment](ctx, opts); err != nil {
return err
}
@@ -903,11 +904,11 @@ func DeleteReview(ctx context.Context, r *Review) error {
ReviewID: r.ID,
}
if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil {
if _, err := db.Delete[Comment](ctx, opts); err != nil {
return err
}
if _, err := sess.ID(r.ID).Delete(new(Review)); err != nil {
if _, err := db.DeleteByID[Review](ctx, r.ID); err != nil {
return err
}
-14
View File
@@ -29,20 +29,6 @@ func (err ErrIssueStopwatchNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrIssueStopwatchAlreadyExist represents an error that stopwatch is already exist
type ErrIssueStopwatchAlreadyExist struct {
UserID int64
IssueID int64
}
func (err ErrIssueStopwatchAlreadyExist) Error() string {
return fmt.Sprintf("issue stopwatch already exists[uid: %d, issue_id: %d", err.UserID, err.IssueID)
}
func (err ErrIssueStopwatchAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
// Stopwatch represents a stopwatch for time tracking.
type Stopwatch struct {
ID int64 `xorm:"pk autoincr"`
+9 -9
View File
@@ -7,22 +7,22 @@ import (
"testing"
"code.gitea.io/gitea/models/migrations/base"
"github.com/stretchr/testify/assert"
)
func Test_AddCombinedIndexToIssueUser(t *testing.T) {
type IssueUser struct {
UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID.
IssueID int64 `xorm:"INDEX unique(uid_to_issue)"`
type IssueUser struct { // old struct
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX"` // User ID.
IssueID int64 `xorm:"INDEX"`
IsRead bool
IsMentioned bool
}
// Prepare and load the testing database
x, deferable := base.PrepareTestEnv(t, 0, new(IssueUser))
defer deferable()
if x == nil || t.Failed() {
return
}
if err := AddCombinedIndexToIssueUser(x); err != nil {
t.Fatal(err)
}
assert.NoError(t, AddCombinedIndexToIssueUser(x))
}
+1 -1
View File
@@ -57,7 +57,7 @@ func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
}
defer committer.Close()
if _, err := db.GetEngine(ctx).ID(ou.ID).Delete(ou); err != nil {
if _, err := db.DeleteByID[organization.OrgUser](ctx, ou.ID); err != nil {
return err
} else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members=num_members-1 WHERE id=?", orgID); err != nil {
return err
+2 -4
View File
@@ -344,9 +344,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
}
}
if _, err := db.DeleteByBean(ctx, &asymkey_model.DeployKey{
ID: key.ID,
}); err != nil {
if _, err := db.DeleteByID[asymkey_model.DeployKey](ctx, key.ID); err != nil {
return fmt.Errorf("delete deploy key [%d]: %w", key.ID, err)
}
@@ -355,7 +353,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
if err != nil {
return err
} else if !has {
if err = asymkey_model.DeletePublicKeys(ctx, key.KeyID); err != nil {
if _, err = db.DeleteByID[asymkey_model.PublicKey](ctx, key.KeyID); err != nil {
return err
}
}
+1 -14
View File
@@ -68,14 +68,6 @@ func repoArchiverForRelativePath(relativePath string) (*RepoArchiver, error) {
}, nil
}
var delRepoArchiver = new(RepoArchiver)
// DeleteRepoArchiver delete archiver
func DeleteRepoArchiver(ctx context.Context, archiver *RepoArchiver) error {
_, err := db.GetEngine(ctx).ID(archiver.ID).Delete(delRepoArchiver)
return err
}
// GetRepoArchiver get an archiver
func GetRepoArchiver(ctx context.Context, repoID int64, tp git.ArchiveType, commitID string) (*RepoArchiver, error) {
var archiver RepoArchiver
@@ -100,12 +92,6 @@ func ExistsRepoArchiverWithStoragePath(ctx context.Context, storagePath string)
return db.GetEngine(ctx).Exist(archiver)
}
// AddRepoArchiver adds an archiver
func AddRepoArchiver(ctx context.Context, archiver *RepoArchiver) error {
_, err := db.GetEngine(ctx).Insert(archiver)
return err
}
// UpdateRepoArchiverStatus updates archiver's status
func UpdateRepoArchiverStatus(ctx context.Context, archiver *RepoArchiver) error {
_, err := db.GetEngine(ctx).ID(archiver.ID).Cols("status").Update(archiver)
@@ -114,6 +100,7 @@ func UpdateRepoArchiverStatus(ctx context.Context, archiver *RepoArchiver) error
// DeleteAllRepoArchives deletes all repo archives records
func DeleteAllRepoArchives(ctx context.Context) error {
// 1=1 to enforce delete all data, otherwise it will delete nothing
_, err := db.GetEngine(ctx).Where("1=1").Delete(new(RepoArchiver))
return err
}
+3 -19
View File
@@ -34,12 +34,13 @@ type PushMirror struct {
}
type PushMirrorOptions struct {
db.ListOptions
ID int64
RepoID int64
RemoteName string
}
func (opts *PushMirrorOptions) toConds() builder.Cond {
func (opts PushMirrorOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
@@ -75,12 +76,6 @@ func (m *PushMirror) GetRemoteName() string {
return m.RemoteName
}
// InsertPushMirror inserts a push-mirror to database
func InsertPushMirror(ctx context.Context, m *PushMirror) error {
_, err := db.GetEngine(ctx).Insert(m)
return err
}
// UpdatePushMirror updates the push-mirror
func UpdatePushMirror(ctx context.Context, m *PushMirror) error {
_, err := db.GetEngine(ctx).ID(m.ID).AllCols().Update(m)
@@ -95,23 +90,12 @@ func UpdatePushMirrorInterval(ctx context.Context, m *PushMirror) error {
func DeletePushMirrors(ctx context.Context, opts PushMirrorOptions) error {
if opts.RepoID > 0 {
_, err := db.GetEngine(ctx).Where(opts.toConds()).Delete(&PushMirror{})
_, err := db.Delete[PushMirror](ctx, opts)
return err
}
return util.NewInvalidArgumentErrorf("repoID required and must be set")
}
func GetPushMirror(ctx context.Context, opts PushMirrorOptions) (*PushMirror, error) {
mirror := &PushMirror{}
exist, err := db.GetEngine(ctx).Where(opts.toConds()).Get(mirror)
if err != nil {
return nil, err
} else if !exist {
return nil, ErrPushMirrorNotExist
}
return mirror, nil
}
// GetPushMirrorsByRepoID returns push-mirror information of a repository.
func GetPushMirrorsByRepoID(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*PushMirror, int64, error) {
sess := db.GetEngine(ctx).Where("repo_id = ?", repoID)
+3 -3
View File
@@ -20,20 +20,20 @@ func TestPushMirrorsIterate(t *testing.T) {
now := timeutil.TimeStampNow()
repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{
db.Insert(db.DefaultContext, &repo_model.PushMirror{
RemoteName: "test-1",
LastUpdateUnix: now,
Interval: 1,
})
long, _ := time.ParseDuration("24h")
repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{
db.Insert(db.DefaultContext, &repo_model.PushMirror{
RemoteName: "test-2",
LastUpdateUnix: now,
Interval: long,
})
repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{
db.Insert(db.DefaultContext, &repo_model.PushMirror{
RemoteName: "test-3",
LastUpdateUnix: now,
Interval: 0,
+1 -7
View File
@@ -450,12 +450,6 @@ func SortReleases(rels []*Release) {
sort.Sort(sorter)
}
// DeleteReleaseByID deletes a release from database by given ID.
func DeleteReleaseByID(ctx context.Context, id int64) error {
_, err := db.GetEngine(ctx).ID(id).Delete(new(Release))
return err
}
// UpdateReleasesMigrationsByType updates all migrated repositories' releases from gitServiceType to replace originalAuthorID to posterID
func UpdateReleasesMigrationsByType(ctx context.Context, gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error {
_, err := db.GetEngine(ctx).Table("release").
@@ -509,7 +503,7 @@ func PushUpdateDeleteTag(ctx context.Context, repo *Repository, tagName string)
return fmt.Errorf("GetRelease: %w", err)
}
if rel.IsTag {
if _, err = db.GetEngine(ctx).ID(rel.ID).Delete(new(Release)); err != nil {
if _, err = db.DeleteByID[Release](ctx, rel.ID); err != nil {
return fmt.Errorf("Delete: %w", err)
}
} else {
+1 -3
View File
@@ -131,9 +131,7 @@ func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) {
for i := 0; i < len(uploads); i++ {
ids[i] = uploads[i].ID
}
if _, err = db.GetEngine(ctx).
In("id", ids).
Delete(new(Upload)); err != nil {
if err = db.DeleteByIDs[Upload](ctx, ids...); err != nil {
return fmt.Errorf("delete uploads: %w", err)
}
+4 -6
View File
@@ -85,23 +85,21 @@ func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error)
watch.Mode = mode
e := db.GetEngine(ctx)
if !hadrec && needsrec {
watch.Mode = mode
if _, err = e.Insert(watch); err != nil {
if err = db.Insert(ctx, watch); err != nil {
return err
}
} else if needsrec {
watch.Mode = mode
if _, err := e.ID(watch.ID).AllCols().Update(watch); err != nil {
if _, err := db.GetEngine(ctx).ID(watch.ID).AllCols().Update(watch); err != nil {
return err
}
} else if _, err = e.Delete(Watch{ID: watch.ID}); err != nil {
} else if _, err = db.DeleteByID[Watch](ctx, watch.ID); err != nil {
return err
}
if repodiff != 0 {
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + ? WHERE id = ?", repodiff, watch.RepoID)
_, err = db.GetEngine(ctx).Exec("UPDATE `repository` SET num_watches = num_watches + ? WHERE id = ?", repodiff, watch.RepoID)
}
return err
}
-17
View File
@@ -102,12 +102,6 @@ func Notices(ctx context.Context, page, pageSize int) ([]*Notice, error) {
Find(&notices)
}
// DeleteNotice deletes a system notice by given ID.
func DeleteNotice(ctx context.Context, id int64) error {
_, err := db.GetEngine(ctx).ID(id).Delete(new(Notice))
return err
}
// DeleteNotices deletes all notices with ID from start to end (inclusive).
func DeleteNotices(ctx context.Context, start, end int64) error {
if start == 0 && end == 0 {
@@ -123,17 +117,6 @@ func DeleteNotices(ctx context.Context, start, end int64) error {
return err
}
// DeleteNoticesByIDs deletes notices by given IDs.
func DeleteNoticesByIDs(ctx context.Context, ids []int64) error {
if len(ids) == 0 {
return nil
}
_, err := db.GetEngine(ctx).
In("id", ids).
Delete(new(Notice))
return err
}
// DeleteOldSystemNotices deletes all old system notices from database.
func DeleteOldSystemNotices(ctx context.Context, olderThan time.Duration) (err error) {
if olderThan <= 0 {
+2 -9
View File
@@ -69,14 +69,6 @@ func TestNotices(t *testing.T) {
}
}
func TestDeleteNotice(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
assert.NoError(t, system.DeleteNotice(db.DefaultContext, 3))
unittest.AssertNotExistsBean(t, &system.Notice{ID: 3})
}
func TestDeleteNotices(t *testing.T) {
// delete a non-empty range
assert.NoError(t, unittest.PrepareTestDatabase())
@@ -109,7 +101,8 @@ func TestDeleteNoticesByIDs(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1})
unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
assert.NoError(t, system.DeleteNoticesByIDs(db.DefaultContext, []int64{1, 3}))
err := db.DeleteByIDs[system.Notice](db.DefaultContext, 1, 3)
assert.NoError(t, err)
unittest.AssertNotExistsBean(t, &system.Notice{ID: 1})
unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
unittest.AssertNotExistsBean(t, &system.Notice{ID: 3})
+25
View File
@@ -10,8 +10,10 @@ import (
"fmt"
"net/url"
"path/filepath"
"regexp"
"strings"
"time"
"unicode"
_ "image/jpeg" // Needed for jpeg support
@@ -29,6 +31,9 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
"golang.org/x/text/runes"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
"xorm.io/builder"
)
@@ -515,6 +520,26 @@ func GetUserSalt() (string, error) {
return hex.EncodeToString(rBytes), nil
}
// Note: The set of characters here can safely expand without a breaking change,
// but characters removed from this set can cause user account linking to break
var (
customCharsReplacement = strings.NewReplacer("Æ", "AE")
removeCharsRE = regexp.MustCompile(`['´\x60]`)
removeDiacriticsTransform = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
replaceCharsHyphenRE = regexp.MustCompile(`[\s~+]`)
)
// normalizeUserName returns a string with single-quotes and diacritics
// removed, and any other non-supported username characters replaced with
// a `-` character
func NormalizeUserName(s string) (string, error) {
strDiacriticsRemoved, n, err := transform.String(removeDiacriticsTransform, customCharsReplacement.Replace(s))
if err != nil {
return "", fmt.Errorf("Failed to normalize character `%v` in provided username `%v`", s[n], s)
}
return replaceCharsHyphenRE.ReplaceAllLiteralString(removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil
}
var (
reservedUsernames = []string{
".",
+28
View File
@@ -544,3 +544,31 @@ func Test_ValidateUser(t *testing.T) {
assert.EqualValues(t, expected, err == nil, fmt.Sprintf("case: %+v", kase))
}
}
func Test_NormalizeUserFromEmail(t *testing.T) {
testCases := []struct {
Input string
Expected string
IsNormalizedValid bool
}{
{"test", "test", true},
{"Sinéad.O'Connor", "Sinead.OConnor", true},
{"Æsir", "AEsir", true},
// \u00e9\u0065\u0301
{"éé", "ee", true},
{"Awareness Hub", "Awareness-Hub", true},
{"double__underscore", "double__underscore", false}, // We should consider squashing double non-alpha characters
{".bad.", ".bad.", false},
{"new😀user", "new😀user", false}, // No plans to support
}
for _, testCase := range testCases {
normalizedName, err := user_model.NormalizeUserName(testCase.Input)
assert.NoError(t, err)
assert.EqualValues(t, testCase.Expected, normalizedName)
if testCase.IsNormalizedValid {
assert.NoError(t, user_model.IsUsableUsername(normalizedName))
} else {
assert.Error(t, user_model.IsUsableUsername(normalizedName))
}
}
}
+1 -1
View File
@@ -471,7 +471,7 @@ func DeleteWebhookByID(ctx context.Context, id int64) (err error) {
}
defer committer.Close()
if count, err := db.DeleteByID(ctx, id, new(Webhook)); err != nil {
if count, err := db.DeleteByID[Webhook](ctx, id); err != nil {
return err
} else if count == 0 {
return ErrWebhookNotExist{ID: id}