Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions models/issues/milestone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestGetMilestones(t *testing.T) {
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
PageSize: setting.Config().UI.IssuePagingNum.Value(t.Context()),
},
RepoID: repo.ID,
IsClosed: optional.Some(false),
Expand All @@ -115,7 +115,7 @@ func TestGetMilestones(t *testing.T) {
milestones, err = db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
PageSize: setting.Config().UI.IssuePagingNum.Value(t.Context()),
},
RepoID: repo.ID,
IsClosed: optional.Some(true),
Expand Down Expand Up @@ -231,7 +231,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
openMilestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
PageSize: setting.Config().UI.IssuePagingNum.Value(t.Context()),
},
RepoIDs: []int64{repo1.ID, repo2.ID},
IsClosed: optional.Some(false),
Expand All @@ -249,7 +249,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
issues_model.FindMilestoneOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
PageSize: setting.Config().UI.IssuePagingNum.Value(t.Context()),
},
RepoIDs: []int64{repo1.ID, repo2.ID},
IsClosed: optional.Some(true),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# type Setting struct {
# ID int64 `xorm:"pk autoincr"`
# SettingKey string `xorm:"varchar(255) unique"` // key should be lowercase
# SettingValue string `xorm:"text"`
# Version int `xorm:"version"`
# Created timeutil.TimeStamp `xorm:"created"`
# Updated timeutil.TimeStamp `xorm:"updated"`
# }
-
id: 1
setting_key: revision
version: 1

-
id: 2
setting_key: picture.enable_federated_avatar
setting_value: false
version: 1

-
id: 3
setting_key: picture.disable_gravatar
setting_value: true
version: 1
1 change: 1 addition & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ func prepareMigrationTasks() []*migration {
newMigration(313, "Move PinOrder from issue table to a new table issue_pin", v1_24.MovePinOrderToTableIssuePin),
newMigration(314, "Update OwnerID as zero for repository level action tables", v1_24.UpdateOwnerIDOfRepoLevelActionsTables),
newMigration(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner),
newMigration(316, "Migrate the configuration of the ui section of the ini configuration file to the system setting table.", v1_24.MigrateIniToDatabase),
}
return preparedMigrations
}
Expand Down
14 changes: 14 additions & 0 deletions models/migrations/v1_24/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_24 //nolint

import (
"testing"

"code.gitea.io/gitea/models/migrations/base"
)

func TestMain(m *testing.M) {
base.MainTest(m)
}
98 changes: 98 additions & 0 deletions models/migrations/v1_24/v316.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_24 //nolint

import (
"math"

"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"

"xorm.io/xorm"
)

const keyRevision = "revision"

type Setting struct {
ID int64 `xorm:"pk autoincr"`
SettingKey string `xorm:"varchar(255) unique"` // key should be lowercase
SettingValue string `xorm:"text"`
Version int `xorm:"version"`
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
}

// TableName sets the table name for the settings struct
func (s *Setting) TableName() string {
return "system_setting"
}

func MigrateIniToDatabase(x *xorm.Engine) error {
uiMap, err := util.ConfigSectionToMap(
setting.UI, "ui",
[]string{
"GraphMaxCommitNum", "ReactionMaxUserNum", "MaxDisplayFileSize", "DefaultShowFullName", "DefaultTheme", "Themes",
"FileIconTheme", "Reactions", "CustomEmojis", "PreferredTimestampTense", "AmbiguousUnicodeDetection",
}...,
)
if err != nil {
return err
}

sess := x.NewSession()
defer sess.Close()

if err = sess.Begin(); err != nil {
return err
}

if err = sess.Sync(new(Setting)); err != nil {
return err
}

_ = getRevision(sess) // prepare the "revision" key ahead

_, err = sess.Exec("UPDATE system_setting SET version=version+1 WHERE setting_key=?", keyRevision)
if err != nil {
return err
}
for k, v := range uiMap {
res, err := sess.Exec("UPDATE system_setting SET version=version+1, setting_value=? WHERE setting_key=?", v, k)
if err != nil {
return err
}
rows, _ := res.RowsAffected()
if rows == 0 { // if no existing row, insert a new row
if _, err = sess.Insert(&Setting{SettingKey: k, SettingValue: v}); err != nil {
return err
}
}
}

return sess.Commit()
}

func getRevision(sess *xorm.Session) int {
revision := &Setting{}
exist, err := sess.Where("setting_key = ?", keyRevision).Get(revision)
if err != nil {
return 0
} else if !exist {
_, err = sess.Insert(&Setting{SettingKey: keyRevision, Version: 1})
if err != nil {
return 0
}
return 1
}

if revision.Version <= 0 || revision.Version >= math.MaxInt-1 {
_, err = sess.Exec("UPDATE system_setting SET version=1 WHERE setting_key=?", keyRevision)
if err != nil {
return 0
}
return 1
}
return revision.Version
}
27 changes: 27 additions & 0 deletions models/migrations/v1_24/v316_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_24 //nolint

import (
"testing"

"code.gitea.io/gitea/models/migrations/base"

"github.com/stretchr/testify/assert"
)

func Test_MigrateIniToDatabase(t *testing.T) {
// Prepare and load the testing database
x, deferable := base.PrepareTestEnv(t, 0, new(Setting))
defer deferable()
if x == nil || t.Failed() {
return
}

assert.NoError(t, MigrateIniToDatabase(x))

cnt, err := x.Table("system_setting").Where("setting_key LIKE 'ui.%'").Count()
assert.NoError(t, err)
assert.EqualValues(t, 13, cnt)
}
4 changes: 2 additions & 2 deletions modules/indexer/code/gitgrep/gitgrep.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ func PerformSearch(ctx context.Context, page int, repoID int64, gitRepo *git.Rep
}

total = len(res)
pageStart := min((page-1)*setting.UI.RepoSearchPagingNum, len(res))
pageEnd := min(page*setting.UI.RepoSearchPagingNum, len(res))
pageStart := min((page-1)*setting.Config().UI.RepoSearchPagingNum.Value(ctx), len(res))
pageEnd := min(page*setting.Config().UI.RepoSearchPagingNum.Value(ctx), len(res))
res = res[pageStart:pageEnd]
for _, r := range res {
searchResults = append(searchResults, &code_indexer.Result{
Expand Down
69 changes: 69 additions & 0 deletions modules/setting/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package setting

import (
"context"
"sync"

"code.gitea.io/gitea/modules/log"
Expand Down Expand Up @@ -51,9 +52,62 @@ type RepositoryStruct struct {
OpenWithEditorApps *config.Value[OpenWithEditorAppsType]
}

type UIStruct struct {
ExplorePagingNum *config.Value[int]
SitemapPagingNum *config.Value[int]
IssuePagingNum *config.Value[int]
RepoSearchPagingNum *config.Value[int]
MembersPagingNum *config.Value[int]
FeedMaxCommitNum *config.Value[int]
FeedPagingNum *config.Value[int]
PackagesPagingNum *config.Value[int]
CodeCommentLines *config.Value[int]
ShowUserEmail *config.Value[bool]
SearchRepoDescription *config.Value[bool]
OnlyShowRelevantRepos *config.Value[bool]
ExploreDefaultSort *config.Value[string]
}

func (u *UIStruct) ToStruct(ctx context.Context) UIForm {
return UIForm{
ExplorePagingNum: u.ExplorePagingNum.Value(ctx),
SitemapPagingNum: u.SitemapPagingNum.Value(ctx),
IssuePagingNum: u.IssuePagingNum.Value(ctx),
RepoSearchPagingNum: u.RepoSearchPagingNum.Value(ctx),
MembersPagingNum: u.MembersPagingNum.Value(ctx),
FeedMaxCommitNum: u.FeedMaxCommitNum.Value(ctx),
FeedPagingNum: u.FeedPagingNum.Value(ctx),
PackagesPagingNum: u.PackagesPagingNum.Value(ctx),
CodeCommentLines: u.CodeCommentLines.Value(ctx),
ShowUserEmail: u.ShowUserEmail.Value(ctx),
SearchRepoDescription: u.SearchRepoDescription.Value(ctx),
OnlyShowRelevantRepos: u.OnlyShowRelevantRepos.Value(ctx),
ExplorePagingDefaultSort: u.ExploreDefaultSort.Value(ctx),
ExplorePagingSortOption: []string{"recentupdate", "alphabetically", "reverselastlogin", "newest", "oldest"},
}
}

type UIForm struct {
ExplorePagingNum int
SitemapPagingNum int
IssuePagingNum int
RepoSearchPagingNum int
MembersPagingNum int
FeedMaxCommitNum int
FeedPagingNum int
PackagesPagingNum int
CodeCommentLines int
ShowUserEmail bool
SearchRepoDescription bool
OnlyShowRelevantRepos bool
ExplorePagingDefaultSort string
ExplorePagingSortOption []string
}

type ConfigStruct struct {
Picture *PictureStruct
Repository *RepositoryStruct
UI *UIStruct
}

var (
Expand All @@ -71,6 +125,21 @@ func initDefaultConfig() {
Repository: &RepositoryStruct{
OpenWithEditorApps: config.ValueJSON[OpenWithEditorAppsType]("repository.open-with.editor-apps"),
},
UI: &UIStruct{
ExplorePagingNum: config.ValueJSON[int]("ui.explore_paging_num").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "EXPLORE_PAGING_NUM"}).WithDefault(20),
SitemapPagingNum: config.ValueJSON[int]("ui.sitemap_paging_num").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "SITEMAP_PAGING_NUM"}).WithDefault(20),
IssuePagingNum: config.ValueJSON[int]("ui.issue_paging_num").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "ISSUE_PAGING_NUM"}).WithDefault(20),
RepoSearchPagingNum: config.ValueJSON[int]("ui.repo_search_paging_num").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "REPO_SEARCH_PAGING_NUM"}).WithDefault(20),
MembersPagingNum: config.ValueJSON[int]("ui.members_paging_num").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "MEMBERS_PAGING_NUM"}).WithDefault(20),
FeedMaxCommitNum: config.ValueJSON[int]("ui.feed_max_commit_num").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "FEED_MAX_COMMIT_NUM"}).WithDefault(20),
FeedPagingNum: config.ValueJSON[int]("ui.feed_paging_num").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "FEED_PAGE_NUM"}).WithDefault(20),
PackagesPagingNum: config.ValueJSON[int]("ui.packages_paging_num").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "PACKAGES_PAGING_NUM"}).WithDefault(20),
CodeCommentLines: config.ValueJSON[int]("ui.code_comment_lines").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "CODE_COMMENT_LINES"}).WithDefault(4),
ShowUserEmail: config.ValueJSON[bool]("ui.show_user_email").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "SHOW_USER_EMAIL"}).WithDefault(true),
SearchRepoDescription: config.ValueJSON[bool]("ui.search_repo_description").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "SEARCH_REPO_DESCRIPTION"}).WithDefault(false),
OnlyShowRelevantRepos: config.ValueJSON[bool]("ui.only_show_relevant_repos").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "ONLY_SHOW_RELEVANT_REPOS"}).WithDefault(false),
ExploreDefaultSort: config.ValueJSON[string]("ui.explore_paging_default_sort").WithFileConfig(config.CfgSecKey{Sec: "ui", Key: "EXPLORE_PAGING_DEFAULT_SORT"}).WithDefault("recentupdate"),
},
}
}

Expand Down
56 changes: 56 additions & 0 deletions modules/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import (
"crypto/rand"
"fmt"
"math/big"
"reflect"
"slices"
"strconv"
"strings"

"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/optional"

"golang.org/x/text/cases"
Expand Down Expand Up @@ -257,3 +260,56 @@ func ReserveLineBreakForTextarea(input string) string {
// Other than this, we should respect the original content, even leading or trailing spaces.
return strings.ReplaceAll(input, "\r\n", "\n")
}

func ConfigSectionToMap(in any, section string, skipFields ...string) (map[string]string, error) {
if section == "" {
return nil, fmt.Errorf("section is empty")
}
out := map[string]string{}

v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("in is not a struct")
}

t := v.Type()
for i := 0; i < v.NumField(); i++ {
fi := t.Field(i)
fieldName := fi.Name
if slices.Contains(skipFields, fieldName) {
continue
}
if tagValue := fi.Tag.Get("ini"); tagValue == "-" {
continue
} else if tagValue != "" {
fieldName = tagValue
}
switch v.FieldByName(fi.Name).Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
out[fmt.Sprintf("%s.%s", section, ToSnakeCase(fieldName))] = fmt.Sprintf("%v", v.FieldByName(fi.Name).Interface())
case reflect.String:
marshal, err := json.Marshal(v.FieldByName(fi.Name).Interface())
if err != nil {
return nil, err
}
out[fmt.Sprintf("%s.%s", section, ToSnakeCase(fieldName))] = fmt.Sprintf("%v", string(marshal))
case reflect.Slice, reflect.Array:
if v.FieldByName(fi.Name).Len() == 0 {
continue
}
marshal, err := json.Marshal(v.FieldByName(fi.Name).Interface())
if err != nil {
return nil, err
}
out[fmt.Sprintf("%s.%s", section, ToSnakeCase(fieldName))] = fmt.Sprintf("%v", string(marshal))
default:
}
}

return out, nil
}
Loading