Skip to content

Commit 573a55d

Browse files
committed
Merge branch 'main' into lunny/fix_migrate_issue_bug
2 parents 97b5cdc + c422f17 commit 573a55d

File tree

226 files changed

+5687
-1900
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

226 files changed

+5687
-1900
lines changed

.changelog.yml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,25 @@ groups:
2222
name: FEATURES
2323
labels:
2424
- type/feature
25-
-
26-
name: API
27-
labels:
28-
- modifies/api
2925
-
3026
name: ENHANCEMENTS
3127
labels:
3228
- type/enhancement
33-
- type/refactoring
34-
- topic/ui
29+
-
30+
name: PERFORMANCE
31+
labels:
32+
- performance/memory
33+
- performance/speed
34+
- performance/bigrepo
35+
- performance/cpu
3536
-
3637
name: BUGFIXES
3738
labels:
3839
- type/bug
40+
-
41+
name: API
42+
labels:
43+
- modifies/api
3944
-
4045
name: TESTING
4146
labels:

.github/workflows/cron-licenses.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
name: cron-licenses
22

33
on:
4-
schedule:
5-
- cron: "7 0 * * 1" # every Monday at 00:07 UTC
4+
#schedule:
5+
# - cron: "7 0 * * 1" # every Monday at 00:07 UTC
66
workflow_dispatch:
77

88
jobs:

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,4 @@ Kemal Zebari <[email protected]> (@kemzeb)
6363
Rowan Bohde <[email protected]> (@bohde)
6464
hiifong <[email protected]> (@hiifong)
6565
metiftikci <[email protected]> (@metiftikci)
66+
Christopher Homberger <[email protected]> (@ChristopherHX)

cmd/admin_user_create.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ var microcmdUserCreate = &cli.Command{
3131
Name: "username",
3232
Usage: "Username",
3333
},
34+
&cli.StringFlag{
35+
Name: "user-type",
36+
Usage: "Set user's type: individual or bot",
37+
Value: "individual",
38+
},
3439
&cli.StringFlag{
3540
Name: "password",
3641
Usage: "User password",
@@ -77,6 +82,22 @@ func runCreateUser(c *cli.Context) error {
7782
return err
7883
}
7984

85+
userTypes := map[string]user_model.UserType{
86+
"individual": user_model.UserTypeIndividual,
87+
"bot": user_model.UserTypeBot,
88+
}
89+
userType, ok := userTypes[c.String("user-type")]
90+
if !ok {
91+
return fmt.Errorf("invalid user type: %s", c.String("user-type"))
92+
}
93+
if userType != user_model.UserTypeIndividual {
94+
// Some other commands like "change-password" also only support individual users.
95+
// It needs to clarify the "password" behavior for bot users in the future.
96+
// At the moment, we do not allow setting password for bot users.
97+
if c.IsSet("password") || c.IsSet("random-password") {
98+
return errors.New("password can only be set for individual users")
99+
}
100+
}
80101
if c.IsSet("name") && c.IsSet("username") {
81102
return errors.New("cannot set both --name and --username flags")
82103
}
@@ -118,16 +139,19 @@ func runCreateUser(c *cli.Context) error {
118139
return err
119140
}
120141
fmt.Printf("generated random password is '%s'\n", password)
121-
} else {
142+
} else if userType == user_model.UserTypeIndividual {
122143
return errors.New("must set either password or random-password flag")
123144
}
124145

125146
isAdmin := c.Bool("admin")
126147
mustChangePassword := true // always default to true
127148
if c.IsSet("must-change-password") {
149+
if userType != user_model.UserTypeIndividual {
150+
return errors.New("must-change-password flag can only be set for individual users")
151+
}
128152
// if the flag is set, use the value provided by the user
129153
mustChangePassword = c.Bool("must-change-password")
130-
} else {
154+
} else if userType == user_model.UserTypeIndividual {
131155
// check whether there are users in the database
132156
hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{})
133157
if err != nil {
@@ -151,8 +175,9 @@ func runCreateUser(c *cli.Context) error {
151175
u := &user_model.User{
152176
Name: username,
153177
Email: c.String("email"),
154-
Passwd: password,
155178
IsAdmin: isAdmin,
179+
Type: userType,
180+
Passwd: password,
156181
MustChangePassword: mustChangePassword,
157182
Visibility: visibility,
158183
}

cmd/admin_user_create_test.go

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,54 @@ import (
1313
user_model "code.gitea.io/gitea/models/user"
1414

1515
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
1617
)
1718

1819
func TestAdminUserCreate(t *testing.T) {
1920
app := NewMainApp(AppVersion{})
2021

2122
reset := func() {
22-
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
23-
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
23+
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
24+
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
2425
}
2526

26-
type createCheck struct{ IsAdmin, MustChangePassword bool }
27-
createUser := func(name, args string) createCheck {
28-
assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %[email protected] %s --password foobar", name, name, args))))
29-
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
30-
return createCheck{u.IsAdmin, u.MustChangePassword}
31-
}
32-
reset()
33-
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u", ""), "first non-admin user doesn't need to change password")
34-
35-
reset()
36-
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password")
37-
38-
reset()
39-
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password"))
40-
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin"))
41-
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false"))
42-
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", ""))
43-
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false"))
27+
t.Run("MustChangePassword", func(t *testing.T) {
28+
type check struct {
29+
IsAdmin bool
30+
MustChangePassword bool
31+
}
32+
createCheck := func(name, args string) check {
33+
require.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %[email protected] %s --password foobar", name, name, args))))
34+
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
35+
return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword}
36+
}
37+
reset()
38+
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u", ""), "first non-admin user doesn't need to change password")
39+
40+
reset()
41+
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u", "--admin"), "first admin user doesn't need to change password")
42+
43+
reset()
44+
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u", "--admin --must-change-password"))
45+
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u2", "--admin"))
46+
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u3", "--admin --must-change-password=false"))
47+
assert.Equal(t, check{IsAdmin: false, MustChangePassword: true}, createCheck("u4", ""))
48+
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
49+
})
50+
51+
t.Run("UserType", func(t *testing.T) {
52+
createUser := func(name, args string) error {
53+
return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %[email protected] %s", name, name, args)))
54+
}
55+
56+
reset()
57+
assert.ErrorContains(t, createUser("u", "--user-type invalid"), "invalid user type")
58+
assert.ErrorContains(t, createUser("u", "--user-type bot --password 123"), "can only be set for individual users")
59+
assert.ErrorContains(t, createUser("u", "--user-type bot --must-change-password"), "can only be set for individual users")
60+
61+
assert.NoError(t, createUser("u", "--user-type bot"))
62+
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u"})
63+
assert.Equal(t, user_model.UserTypeBot, u.Type)
64+
assert.Equal(t, "", u.Passwd)
65+
})
4466
}

main_timezones.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
//go:build windows
5+
6+
package main
7+
8+
// Golang has the ability to load OS's timezone data from most UNIX systems (https://github.com/golang/go/blob/master/src/time/zoneinfo_unix.go)
9+
// Even if the timezone data is missing, users could install the related packages to get it.
10+
// But on Windows, although `zoneinfo_windows.go` tries to load the timezone data from Windows registry,
11+
// some users still suffer from the issue that the timezone data is missing: https://github.com/go-gitea/gitea/issues/33235
12+
// So we import the tzdata package to make sure the timezone data is included in the binary.
13+
//
14+
// For non-Windows package builders, they could still use the "TAGS=timetzdata" to include the tzdata package in the binary.
15+
// If we decided to add the tzdata for other platforms, modify the "go:build" directive above.
16+
import _ "time/tzdata"

models/actions/variable.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,23 @@ func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data strin
5858

5959
type FindVariablesOpts struct {
6060
db.ListOptions
61+
IDs []int64
6162
RepoID int64
6263
OwnerID int64 // it will be ignored if RepoID is set
6364
Name string
6465
}
6566

6667
func (opts FindVariablesOpts) ToConds() builder.Cond {
6768
cond := builder.NewCond()
69+
70+
if len(opts.IDs) > 0 {
71+
if len(opts.IDs) == 1 {
72+
cond = cond.And(builder.Eq{"id": opts.IDs[0]})
73+
} else {
74+
cond = cond.And(builder.In("id", opts.IDs))
75+
}
76+
}
77+
6878
// Since we now support instance-level variables,
6979
// there is no need to check for null values for `owner_id` and `repo_id`
7080
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
@@ -85,12 +95,12 @@ func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariab
8595
return db.Find[ActionVariable](ctx, opts)
8696
}
8797

88-
func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
89-
count, err := db.GetEngine(ctx).ID(variable.ID).Cols("name", "data").
90-
Update(&ActionVariable{
91-
Name: variable.Name,
92-
Data: variable.Data,
93-
})
98+
func UpdateVariableCols(ctx context.Context, variable *ActionVariable, cols ...string) (bool, error) {
99+
variable.Name = strings.ToUpper(variable.Name)
100+
count, err := db.GetEngine(ctx).
101+
ID(variable.ID).
102+
Cols(cols...).
103+
Update(variable)
94104
return count != 0, err
95105
}
96106

models/issues/issue.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
user_model "code.gitea.io/gitea/models/user"
1818
"code.gitea.io/gitea/modules/container"
1919
"code.gitea.io/gitea/modules/log"
20+
"code.gitea.io/gitea/modules/optional"
2021
"code.gitea.io/gitea/modules/setting"
2122
api "code.gitea.io/gitea/modules/structs"
2223
"code.gitea.io/gitea/modules/timeutil"
@@ -501,6 +502,45 @@ func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
501502
return issue, nil
502503
}
503504

505+
func isPullToCond(isPull optional.Option[bool]) builder.Cond {
506+
if isPull.Has() {
507+
return builder.Eq{"is_pull": isPull.Value()}
508+
}
509+
return builder.NewCond()
510+
}
511+
512+
func FindLatestUpdatedIssues(ctx context.Context, repoID int64, isPull optional.Option[bool], pageSize int) (IssueList, error) {
513+
issues := make([]*Issue, 0, pageSize)
514+
err := db.GetEngine(ctx).Where("repo_id = ?", repoID).
515+
And(isPullToCond(isPull)).
516+
OrderBy("updated_unix DESC").
517+
Limit(pageSize).
518+
Find(&issues)
519+
return issues, err
520+
}
521+
522+
func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], excludedID int64, pageSize int) (IssueList, error) {
523+
cond := builder.NewCond()
524+
if excludedID > 0 {
525+
cond = cond.And(builder.Neq{"`id`": excludedID})
526+
}
527+
528+
// It seems that GitHub searches both title and content (maybe sorting by the search engine's ranking system?)
529+
// The first PR (https://github.com/go-gitea/gitea/pull/32327) uses "search indexer" to search "name(title) + content"
530+
// But it seems that searching "content" (especially LIKE by DB engine) generates worse (unusable) results.
531+
// So now (https://github.com/go-gitea/gitea/pull/33538) it only searches "name(title)", leave the improvements to the future.
532+
cond = cond.And(db.BuildCaseInsensitiveLike("`name`", keyword))
533+
534+
issues := make([]*Issue, 0, pageSize)
535+
err := db.GetEngine(ctx).Where("repo_id = ?", repoID).
536+
And(isPullToCond(isPull)).
537+
And(cond).
538+
OrderBy("updated_unix DESC, `index` DESC").
539+
Limit(pageSize).
540+
Find(&issues)
541+
return issues, err
542+
}
543+
504544
// GetIssueWithAttrsByIndex returns issue by index in a repository.
505545
func GetIssueWithAttrsByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
506546
issue, err := GetIssueByIndex(ctx, repoID, index)

models/issues/issue_project.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ func (issue *Issue) projectID(ctx context.Context) int64 {
3838
}
3939

4040
// ProjectColumnID return project column id if issue was assigned to one
41-
func (issue *Issue) ProjectColumnID(ctx context.Context) int64 {
41+
func (issue *Issue) ProjectColumnID(ctx context.Context) (int64, error) {
4242
var ip project_model.ProjectIssue
4343
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
44-
if err != nil || !has {
45-
return 0
44+
if err != nil {
45+
return 0, err
46+
} else if !has {
47+
return 0, nil
4648
}
47-
return ip.ProjectColumnID
49+
return ip.ProjectColumnID, nil
4850
}
4951

5052
// LoadIssuesFromColumn load issues assigned to this column

models/issues/issue_stats.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func GetIssueStats(ctx context.Context, opts *IssuesOptions) (*IssueStats, error
107107
accum.YourRepositoriesCount += stats.YourRepositoriesCount
108108
accum.AssignCount += stats.AssignCount
109109
accum.CreateCount += stats.CreateCount
110-
accum.OpenCount += stats.MentionCount
110+
accum.MentionCount += stats.MentionCount
111111
accum.ReviewRequestedCount += stats.ReviewRequestedCount
112112
accum.ReviewedCount += stats.ReviewedCount
113113
i = chunk

0 commit comments

Comments
 (0)