Skip to content

Commit db799e0

Browse files
authored
Merge branch 'main' into main
2 parents fb747b1 + a52720b commit db799e0

File tree

79 files changed

+1414
-672
lines changed

Some content is hidden

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

79 files changed

+1414
-672
lines changed

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/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/project/project.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,10 @@ func GetSearchOrderByBySortType(sortType string) db.SearchOrderBy {
244244
return db.SearchOrderByRecentUpdated
245245
case "leastupdate":
246246
return db.SearchOrderByLeastUpdated
247+
case "alphabetically":
248+
return "title ASC"
249+
case "reversealphabetically":
250+
return "title DESC"
247251
default:
248252
return db.SearchOrderByNewest
249253
}

models/user/user.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -385,11 +385,12 @@ func (u *User) ValidatePassword(passwd string) bool {
385385
}
386386

387387
// IsPasswordSet checks if the password is set or left empty
388+
// TODO: It's better to clarify the "password" behavior for different types (individual, bot)
388389
func (u *User) IsPasswordSet() bool {
389-
return len(u.Passwd) != 0
390+
return u.Passwd != ""
390391
}
391392

392-
// IsOrganization returns true if user is actually a organization.
393+
// IsOrganization returns true if user is actually an organization.
393394
func (u *User) IsOrganization() bool {
394395
return u.Type == UserTypeOrganization
395396
}
@@ -399,13 +400,14 @@ func (u *User) IsIndividual() bool {
399400
return u.Type == UserTypeIndividual
400401
}
401402

402-
func (u *User) IsUser() bool {
403-
return u.Type == UserTypeIndividual || u.Type == UserTypeBot
403+
// IsTypeBot returns whether the user is of type bot
404+
func (u *User) IsTypeBot() bool {
405+
return u.Type == UserTypeBot
404406
}
405407

406-
// IsBot returns whether or not the user is of type bot
407-
func (u *User) IsBot() bool {
408-
return u.Type == UserTypeBot
408+
// IsTokenAccessAllowed returns whether the user is an individual or a bot (which allows for token access)
409+
func (u *User) IsTokenAccessAllowed() bool {
410+
return u.Type == UserTypeIndividual || u.Type == UserTypeBot
409411
}
410412

411413
// DisplayName returns full name if it's not empty,

models/user/user_system.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func NewActionsUser() *User {
5656
Email: ActionsUserEmail,
5757
KeepEmailPrivate: true,
5858
LoginName: ActionsUserName,
59-
Type: UserTypeIndividual,
59+
Type: UserTypeBot,
6060
AllowCreateOrganization: true,
6161
Visibility: structs.VisibleTypePublic,
6262
}

modules/git/parse.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,9 @@ func parseLsTreeLine(line []byte) (*LsTreeEntry, error) {
4646
entry.Size = optional.Some(size)
4747
}
4848

49-
switch string(entryMode) {
50-
case "100644":
51-
entry.EntryMode = EntryModeBlob
52-
case "100755":
53-
entry.EntryMode = EntryModeExec
54-
case "120000":
55-
entry.EntryMode = EntryModeSymlink
56-
case "160000":
57-
entry.EntryMode = EntryModeCommit
58-
case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
59-
entry.EntryMode = EntryModeTree
60-
default:
61-
return nil, fmt.Errorf("unknown type: %v", string(entryMode))
49+
entry.EntryMode, err = ParseEntryMode(string(entryMode))
50+
if err != nil || entry.EntryMode == EntryModeNoEntry {
51+
return nil, fmt.Errorf("invalid ls-tree output (invalid mode): %q, err: %w", line, err)
6252
}
6353

6454
entry.ID, err = NewIDFromString(string(entryObjectID))

modules/git/tree_entry_mode.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@
33

44
package git
55

6-
import "strconv"
6+
import (
7+
"fmt"
8+
"strconv"
9+
)
710

811
// EntryMode the type of the object in the git tree
912
type EntryMode int
1013

1114
// There are only a few file modes in Git. They look like unix file modes, but they can only be
1215
// one of these.
1316
const (
17+
// EntryModeNoEntry is possible if the file was added or removed in a commit. In the case of
18+
// added the base commit will not have the file in its tree so a mode of 0o000000 is used.
19+
EntryModeNoEntry EntryMode = 0o000000
1420
// EntryModeBlob
1521
EntryModeBlob EntryMode = 0o100644
1622
// EntryModeExec
@@ -33,3 +39,22 @@ func ToEntryMode(value string) EntryMode {
3339
v, _ := strconv.ParseInt(value, 8, 32)
3440
return EntryMode(v)
3541
}
42+
43+
func ParseEntryMode(mode string) (EntryMode, error) {
44+
switch mode {
45+
case "000000":
46+
return EntryModeNoEntry, nil
47+
case "100644":
48+
return EntryModeBlob, nil
49+
case "100755":
50+
return EntryModeExec, nil
51+
case "120000":
52+
return EntryModeSymlink, nil
53+
case "160000":
54+
return EntryModeCommit, nil
55+
case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
56+
return EntryModeTree, nil
57+
default:
58+
return 0, fmt.Errorf("unparsable entry mode: %s", mode)
59+
}
60+
}

0 commit comments

Comments
 (0)