Merge branch 'rebase-forgejo-dependency' into forgejo

This commit is contained in:
Earl Warren 2023-11-06 19:10:59 +01:00
commit 36010fb2ee
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
99 changed files with 1132 additions and 155 deletions

File diff suppressed because one or more lines are too long

View file

@ -410,6 +410,10 @@ USER = root
;;
;; Whether execute database models migrations automatically
;AUTO_MIGRATION = true
;;
;; Threshold value (in seconds) beyond which query execution time is logged as a warning in the xorm logger
;;
;SLOW_QUERY_TRESHOLD = 5s
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -810,6 +814,11 @@ LEVEL = Info
;; Every new user will have restricted permissions depending on this setting
;DEFAULT_USER_IS_RESTRICTED = false
;;
;; Users will be able to use dots when choosing their username. Disabling this is
;; helpful if your usersare having issues with e.g. RSS feeds or advanced third-party
;; extensions that use strange regex patterns.
; ALLOW_DOTS_IN_USERNAMES = true
;;
;; Either "public", "limited" or "private", default is "public"
;; Limited is for users visible only to signed users
;; Private is for users visible only to members of their organizations
@ -1452,6 +1461,8 @@ LEVEL = Info
;;
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
;; Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false
;SEND_NOTIFICATION_EMAIL_ON_NEW_USER = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1766,9 +1777,6 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;AVATAR_UPLOAD_PATH = data/avatars
;REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars
;;
;; How Gitea deals with missing repository avatars
;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
;REPOSITORY_AVATAR_FALLBACK = none

View file

@ -454,6 +454,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `MAX_IDLE_CONNS` **2**: Max idle database connections on connection pool, default is 2 - this will be capped to `MAX_OPEN_CONNS`.
- `CONN_MAX_LIFETIME` **0 or 3s**: Sets the maximum amount of time a DB connection may be reused - default is 0, meaning there is no limit (except on MySQL where it is 3s - see #6804 & #7071).
- `AUTO_MIGRATION` **true**: Whether execute database models migrations automatically.
- `SLOW_QUERY_TRESHOLD` **5s**: Threshold value in seconds beyond which query execution time is logged as a warning in the xorm logger.
[^1]: It may be necessary to specify a hostport even when listening on a unix socket, as the port is part of the socket name. see [#24552](https://github.com/go-gitea/gitea/issues/24552#issuecomment-1681649367) for additional details.
@ -513,6 +514,7 @@ And the following unique queues:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
- `SEND_NOTIFICATION_EMAIL_ON_NEW_USER`: **false**: Send an email to all admins when a new user signs up to inform the admins about this act.
## Security (`security`)

6
go.mod
View file

@ -15,7 +15,6 @@ require (
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
github.com/NYTimes/gziphandler v1.1.1
github.com/PuerkitoBio/goquery v1.8.1
github.com/alecthomas/chroma/v2 v2.10.0
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
@ -78,7 +77,6 @@ require (
github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.26
github.com/minio/minio-go/v7 v7.0.63
github.com/minio/sha256-simd v1.0.1
github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.7.0
@ -101,7 +99,6 @@ require (
github.com/ulikunitz/xz v0.5.11
github.com/urfave/cli/v2 v2.25.7
github.com/xanzy/go-gitlab v0.93.1
github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.5.6
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
@ -232,6 +229,7 @@ require (
github.com/mholt/acmez v1.2.0 // indirect
github.com/miekg/dns v1.1.56 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
@ -277,8 +275,6 @@ require (
github.com/valyala/fastjson v1.6.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect

9
go.sum
View file

@ -101,8 +101,6 @@ github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBa
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
@ -1047,13 +1045,6 @@ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3k
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=

View file

@ -169,7 +169,12 @@ func RewriteAllPublicKeys(ctx context.Context) error {
return err
}
t.Close()
if err := t.Sync(); err != nil {
return err
}
if err := t.Close(); err != nil {
return err
}
return util.Rename(tmpPath, fPath)
}

View file

@ -92,7 +92,12 @@ func RewriteAllPrincipalKeys(ctx context.Context) error {
return err
}
t.Close()
if err := t.Sync(); err != nil {
return err
}
if err := t.Close(); err != nil {
return err
}
return util.Rename(tmpPath, fPath)
}

View file

@ -250,7 +250,7 @@ func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) {
remainingScopes = remainingScopes[i+1:]
}
singleScope := AccessTokenScope(v)
if singleScope == "" {
if singleScope == "" || singleScope == "sudo" {
continue
}
if singleScope == AccessTokenScopeAll {

View file

@ -20,7 +20,7 @@ func TestAccessTokenScope_Normalize(t *testing.T) {
tests := []scopeTestNormalize{
{"", "", nil},
{"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil},
{"all", "all", nil},
{"all,sudo", "all", nil},
{"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", "all", nil},
{"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,public-only", "public-only,all", nil},
}

View file

@ -5,6 +5,7 @@ package auth
import (
"context"
"crypto/sha256"
"encoding/base32"
"encoding/base64"
"fmt"
@ -19,7 +20,6 @@ import (
"code.gitea.io/gitea/modules/util"
uuid "github.com/google/uuid"
"github.com/minio/sha256-simd"
"golang.org/x/crypto/bcrypt"
"xorm.io/builder"
"xorm.io/xorm"

View file

@ -6,6 +6,7 @@ package auth
import (
"context"
"crypto/md5"
"crypto/sha256"
"crypto/subtle"
"encoding/base32"
"encoding/base64"
@ -18,7 +19,6 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/minio/sha256-simd"
"github.com/pquerna/otp/totp"
"golang.org/x/crypto/pbkdf2"
)

View file

@ -11,10 +11,13 @@ import (
"io"
"reflect"
"strings"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm"
"xorm.io/xorm/contexts"
"xorm.io/xorm/names"
"xorm.io/xorm/schemas"
@ -147,6 +150,13 @@ func InitEngine(ctx context.Context) error {
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
xormEngine.SetDefaultContext(ctx)
if setting.Database.SlowQueryTreshold > 0 {
xormEngine.AddHook(&SlowQueryHook{
Treshold: setting.Database.SlowQueryTreshold,
Logger: log.GetLogger("xorm"),
})
}
SetDefaultEngine(ctx, xormEngine)
return nil
}
@ -300,3 +310,21 @@ func SetLogSQL(ctx context.Context, on bool) {
sess.Engine().ShowSQL(on)
}
}
type SlowQueryHook struct {
Treshold time.Duration
Logger log.Logger
}
var _ contexts.Hook = &SlowQueryHook{}
func (SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
return c.Ctx, nil
}
func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error {
if c.ExecuteTime >= h.Treshold {
h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime)
}
return nil
}

View file

@ -6,15 +6,19 @@ package db_test
import (
"path/filepath"
"testing"
"time"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
_ "code.gitea.io/gitea/cmd" // for TestPrimaryKeys
"github.com/stretchr/testify/assert"
"xorm.io/xorm"
)
func TestDumpDatabase(t *testing.T) {
@ -85,3 +89,37 @@ func TestPrimaryKeys(t *testing.T) {
}
}
}
func TestSlowQuery(t *testing.T) {
lc, cleanup := test.NewLogChecker("slow-query")
lc.StopMark("[Slow SQL Query]")
defer cleanup()
e := db.GetEngine(db.DefaultContext)
engine, ok := e.(*xorm.Engine)
assert.True(t, ok)
// It's not possible to clean this up with XORM, but it's luckily not harmful
// to leave around.
engine.AddHook(&db.SlowQueryHook{
Treshold: time.Second * 10,
Logger: log.GetLogger("slow-query"),
})
// NOOP query.
e.Exec("SELECT 1 WHERE false;")
_, stopped := lc.Check(100 * time.Millisecond)
assert.False(t, stopped)
engine.AddHook(&db.SlowQueryHook{
Treshold: 0, // Every query should be logged.
Logger: log.GetLogger("slow-query"),
})
// NOOP query.
e.Exec("SELECT 1 WHERE false;")
_, stopped = lc.Check(100 * time.Millisecond)
assert.True(t, stopped)
}

View file

@ -150,3 +150,17 @@
is_prerelease: false
is_tag: false
created_unix: 946684803
- id: 12
repo_id: 59
publisher_id: 2
tag_name: "v1.0"
lower_tag_name: "v1.0"
target: "main"
title: "v1.0"
sha1: "d8f53dfb33f6ccf4169c34970b5e747511c18beb"
num_commits: 1
is_draft: false
is_prerelease: false
is_tag: false
created_unix: 946684803

View file

@ -608,6 +608,38 @@
type: 1
created_unix: 946684810
# BEGIN Forgejo [GITEA] Improve HTML title on repositories
-
id: 1093
repo_id: 59
type: 1
created_unix: 946684810
-
id: 1094
repo_id: 59
type: 2
created_unix: 946684810
-
id: 1095
repo_id: 59
type: 3
created_unix: 946684810
-
id: 1096
repo_id: 59
type: 4
created_unix: 946684810
-
id: 1097
repo_id: 59
type: 5
created_unix: 946684810
# END Forgejo [GITEA] Improve HTML title on repositories
-
id: 91
repo_id: 58

View file

@ -1467,6 +1467,7 @@
owner_name: user27
lower_name: repo49
name: repo49
description: A wonderful repository with more than just a README.md
default_branch: master
num_watches: 0
num_stars: 0
@ -1693,3 +1694,16 @@
size: 0
is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false
-
id: 59
owner_id: 2
owner_name: user2
lower_name: repo59
name: repo59
default_branch: master
is_empty: false
is_archived: false
is_private: false
status: 0
num_issues: 0

View file

@ -66,7 +66,7 @@
num_followers: 2
num_following: 1
num_stars: 2
num_repos: 14
num_repos: 15
num_teams: 0
num_members: 0
visibility: 0

View file

@ -342,7 +342,7 @@ func (c *Comment) AfterLoad(session *xorm.Session) {
// LoadPoster loads comment poster
func (c *Comment) LoadPoster(ctx context.Context) (err error) {
if c.PosterID <= 0 || c.Poster != nil {
if c.Poster != nil {
return nil
}

View file

@ -4,9 +4,9 @@
package base
import (
"crypto/sha256"
"encoding/hex"
"github.com/minio/sha256-simd"
"golang.org/x/crypto/pbkdf2"
)

View file

@ -4,9 +4,9 @@
package v1_14 //nolint
import (
"crypto/sha256"
"encoding/hex"
"github.com/minio/sha256-simd"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"

View file

@ -584,9 +584,9 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
}, repo.Description)
if err != nil {
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
return template.HTML(markup.Sanitize(repo.Description))
return template.HTML(markup.SanitizeDescription(repo.Description))
}
return template.HTML(markup.Sanitize(desc))
return template.HTML(markup.SanitizeDescription(desc))
}
// CloneLink represents different types of clone URLs of repository.

View file

@ -138,12 +138,12 @@ func getTestCases() []struct {
{
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse},
count: 31,
count: 32,
},
{
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse},
count: 36,
count: 37,
},
{
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
@ -158,7 +158,7 @@ func getTestCases() []struct {
{
name: "AllPublic/PublicRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse},
count: 31,
count: 32,
},
{
name: "AllTemplates",

View file

@ -223,6 +223,12 @@ func GetAllUsers(ctx context.Context) ([]*User, error) {
return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users)
}
// GetAllAdmins returns a slice of all adminusers found in DB.
func GetAllAdmins(ctx context.Context) ([]*User, error) {
users := make([]*User, 0)
return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).And("is_admin = ?", true).Find(&users)
}
// IsLocal returns true if user login type is LoginPlain.
func (u *User) IsLocal() bool {
return u.LoginType <= auth.Plain

View file

@ -550,3 +550,13 @@ func Test_ValidateUser(t *testing.T) {
assert.EqualValues(t, expected, err == nil, fmt.Sprintf("case: %+v", kase))
}
}
func TestGetAllAdmins(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
admins, err := user_model.GetAllAdmins(db.DefaultContext)
assert.NoError(t, err)
assert.Len(t, admins, 1)
assert.Equal(t, int64(1), admins[0].ID)
}

View file

@ -4,12 +4,12 @@
package hash
import (
"crypto/sha256"
"encoding/hex"
"strings"
"code.gitea.io/gitea/modules/log"
"github.com/minio/sha256-simd"
"golang.org/x/crypto/pbkdf2"
)

View file

@ -4,10 +4,9 @@
package avatar
import (
"crypto/sha256"
"encoding/hex"
"strconv"
"github.com/minio/sha256-simd"
)
// HashAvatar will generate a unique string, which ensures that when there's a

View file

@ -7,11 +7,10 @@
package identicon
import (
"crypto/sha256"
"fmt"
"image"
"image/color"
"github.com/minio/sha256-simd"
)
const minImageSize = 16

View file

@ -6,6 +6,7 @@ package base
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
@ -24,7 +25,6 @@ import (
"code.gitea.io/gitea/modules/setting"
"github.com/dustin/go-humanize"
"github.com/minio/sha256-simd"
)
// EncodeMD5 encodes string to md5 hex value.

View file

@ -509,6 +509,62 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
return fileStatus, nil
}
func parseCommitRenames(renames *[][2]string, stdout io.Reader) {
rd := bufio.NewReader(stdout)
for {
// Skip (R || three digits || NULL byte)
_, err := rd.Discard(5)
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
oldFileName, err := rd.ReadString('\x00')
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
newFileName, err := rd.ReadString('\x00')
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
oldFileName = strings.TrimSuffix(oldFileName, "\x00")
newFileName = strings.TrimSuffix(newFileName, "\x00")
*renames = append(*renames, [2]string{oldFileName, newFileName})
}
}
// GetCommitFileRenames returns the renames that the commit contains.
func GetCommitFileRenames(ctx context.Context, repoPath, commitID string) ([][2]string, error) {
renames := [][2]string{}
stdout, w := io.Pipe()
done := make(chan struct{})
go func() {
parseCommitRenames(&renames, stdout)
close(done)
}()
stderr := new(bytes.Buffer)
err := NewCommand(ctx, "show", "--name-status", "--pretty=format:", "-z", "--diff-filter=R").AddDynamicArguments(commitID).Run(&RunOpts{
Dir: repoPath,
Stdout: w,
Stderr: stderr,
})
w.Close() // Close writer to exit parsing goroutine
if err != nil {
return nil, ConcatenateError(err, stderr.String())
}
<-done
return renames, nil
}
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
commitID, _, err := NewCommand(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath})

View file

@ -278,3 +278,30 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
assert.Equal(t, commitFileStatus.Removed, expected.Removed)
assert.Equal(t, commitFileStatus.Modified, expected.Modified)
}
func TestParseCommitRenames(t *testing.T) {
testcases := []struct {
output string
renames [][2]string
}{
{
output: "R090\x00renamed.txt\x00history.txt\x00",
renames: [][2]string{{"renamed.txt", "history.txt"}},
},
{
output: "R090\x00renamed.txt\x00history.txt\x00R000\x00corruptedstdouthere",
renames: [][2]string{{"renamed.txt", "history.txt"}},
},
{
output: "R100\x00renamed.txt\x00history.txt\x00R001\x00readme.md\x00README.md\x00",
renames: [][2]string{{"renamed.txt", "history.txt"}, {"readme.md", "README.md"}},
},
}
for _, testcase := range testcases {
renames := [][2]string{}
parseCommitRenames(&renames, strings.NewReader(testcase.output))
assert.Equal(t, testcase.renames, renames)
}
}

View file

@ -4,12 +4,11 @@
package git
import (
"crypto/sha256"
"fmt"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/minio/sha256-simd"
)
// Cache represents a caching interface

View file

@ -4,6 +4,7 @@
package lfs
import (
"crypto/sha256"
"encoding/hex"
"errors"
"hash"
@ -12,8 +13,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
"github.com/minio/sha256-simd"
)
var (

View file

@ -4,6 +4,7 @@
package lfs
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
@ -12,8 +13,6 @@ import (
"regexp"
"strconv"
"strings"
"github.com/minio/sha256-simd"
)
const (

View file

@ -18,9 +18,10 @@ import (
// Sanitizer is a protection wrapper of *bluemonday.Policy which does not allow
// any modification to the underlying policies once it's been created.
type Sanitizer struct {
defaultPolicy *bluemonday.Policy
rendererPolicies map[string]*bluemonday.Policy
init sync.Once
defaultPolicy *bluemonday.Policy
descriptionPolicy *bluemonday.Policy
rendererPolicies map[string]*bluemonday.Policy
init sync.Once
}
var (
@ -41,6 +42,7 @@ func NewSanitizer() {
func InitializeSanitizer() {
sanitizer.rendererPolicies = map[string]*bluemonday.Policy{}
sanitizer.defaultPolicy = createDefaultPolicy()
sanitizer.descriptionPolicy = createRepoDescriptionPolicy()
for name, renderer := range renderers {
sanitizerRules := renderer.SanitizerRules()
@ -161,6 +163,27 @@ func createDefaultPolicy() *bluemonday.Policy {
return policy
}
// createRepoDescriptionPolicy returns a minimal more strict policy that is used for
// repository descriptions.
func createRepoDescriptionPolicy() *bluemonday.Policy {
policy := bluemonday.NewPolicy()
// Allow italics and bold.
policy.AllowElements("i", "b", "em", "strong")
// Allow code.
policy.AllowElements("code")
// Allow links
policy.AllowAttrs("href", "target", "rel").OnElements("a")
// Allow classes for emojis
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^emoji$`)).OnElements("img", "span")
policy.AllowAttrs("aria-label").OnElements("span")
return policy
}
func addSanitizerRules(policy *bluemonday.Policy, rules []setting.MarkupSanitizerRule) {
for _, rule := range rules {
if rule.AllowDataURIImages {
@ -176,6 +199,12 @@ func addSanitizerRules(policy *bluemonday.Policy, rules []setting.MarkupSanitize
}
}
// SanitizeDescription sanitizes the HTML generated for a repository description.
func SanitizeDescription(s string) string {
NewSanitizer()
return sanitizer.descriptionPolicy.Sanitize(s)
}
// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
func Sanitize(s string) string {
NewSanitizer()

View file

@ -73,6 +73,28 @@ func Test_Sanitizer(t *testing.T) {
}
}
func TestDescriptionSanitizer(t *testing.T) {
NewSanitizer()
testCases := []string{
`<h1>Title</h1>`, `Title`,
`<img src='img.png' alt='image'>`, ``,
`<span class="emoji" aria-label="thumbs up">THUMBS UP</span>`, `<span class="emoji" aria-label="thumbs up">THUMBS UP</span>`,
`<span style="color: red">Hello World</span>`, `<span>Hello World</span>`,
`<br>`, ``,
`<a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>`, `<a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>`,
`<mark>Important!</mark>`, `Important!`,
`<details>Click me! <summary>Nothing to see here.</summary></details>`, `Click me! Nothing to see here.`,
`<input type="hidden">`, ``,
`<b>I</b> have a <i>strong</i> <strong>opinion</strong> about <em>this</em>.`, `<b>I</b> have a <i>strong</i> <strong>opinion</strong> about <em>this</em>.`,
`Provides alternative <code>wg(8)</code> tool`, `Provides alternative <code>wg(8)</code> tool`,
}
for i := 0; i < len(testCases); i += 2 {
assert.Equal(t, testCases[i+1], SanitizeDescription(testCases[i]))
}
}
func TestSanitizeNonEscape(t *testing.T) {
descStr := "<scrİpt>&lt;script&gt;alert(document.domain)&lt;/script&gt;</scrİpt>"

View file

@ -7,13 +7,12 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"github.com/minio/sha256-simd"
)
// AesEncrypt encrypts text and given key with AES.

View file

@ -5,8 +5,9 @@ package setting
// Admin settings
var Admin struct {
DisableRegularOrgCreation bool
DefaultEmailNotification string
DisableRegularOrgCreation bool
DefaultEmailNotification string
SendNotificationEmailOnNewUser bool
}
func loadAdminFrom(rootCfg ConfigProvider) {

View file

@ -45,6 +45,7 @@ var (
ConnMaxLifetime time.Duration
IterateBufferSize int
AutoMigration bool
SlowQueryTreshold time.Duration
}{
Timeout: 500,
IterateBufferSize: 50,
@ -87,6 +88,7 @@ func loadDBSetting(rootCfg ConfigProvider) {
Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10)
Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second)
Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true)
Database.SlowQueryTreshold = sec.Key("SLOW_QUERY_TRESHOLD").MustDuration(5 * time.Second)
}
// DBConnStr returns database connection string

View file

@ -68,6 +68,7 @@ var Service = struct {
DefaultKeepEmailPrivate bool
DefaultAllowCreateOrganization bool
DefaultUserIsRestricted bool
AllowDotsInUsernames bool
EnableTimetracking bool
DefaultEnableTimetracking bool
DefaultEnableDependencies bool
@ -180,6 +181,7 @@ func loadServiceFrom(rootCfg ConfigProvider) {
Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)
Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false)
Service.AllowDotsInUsernames = sec.Key("ALLOW_DOTS_IN_USERNAMES").MustBool(true)
Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true)
if Service.EnableTimetracking {
Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)

View file

@ -7,10 +7,9 @@ import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"github.com/minio/sha256-simd"
)
// GenerateKeyPair generates a public and private keypair

View file

@ -7,12 +7,12 @@ import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"regexp"
"testing"
"github.com/minio/sha256-simd"
"github.com/stretchr/testify/assert"
)

View file

@ -117,13 +117,20 @@ func IsValidExternalTrackerURLFormat(uri string) bool {
}
var (
validUsernamePattern = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`) // No consecutive or trailing non-alphanumeric chars
validUsernamePatternWithDots = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
validUsernamePatternWithoutDots = regexp.MustCompile(`^[\da-zA-Z][-\w]*$`)
// No consecutive or trailing non-alphanumeric chars, catches both cases
invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`)
)
// IsValidUsername checks if username is valid
func IsValidUsername(name string) bool {
// It is difficult to find a single pattern that is both readable and effective,
// but it's easier to use positive and negative checks.
return validUsernamePattern.MatchString(name) && !invalidUsernamePattern.MatchString(name)
if setting.Service.AllowDotsInUsernames {
return validUsernamePatternWithDots.MatchString(name) && !invalidUsernamePattern.MatchString(name)
}
return validUsernamePatternWithoutDots.MatchString(name) && !invalidUsernamePattern.MatchString(name)
}

View file

@ -155,7 +155,8 @@ func Test_IsValidExternalTrackerURLFormat(t *testing.T) {
}
}
func TestIsValidUsername(t *testing.T) {
func TestIsValidUsernameAllowDots(t *testing.T) {
setting.Service.AllowDotsInUsernames = true
tests := []struct {
arg string
want bool
@ -185,3 +186,31 @@ func TestIsValidUsername(t *testing.T) {
})
}
}
func TestIsValidUsernameBanDots(t *testing.T) {
setting.Service.AllowDotsInUsernames = false
defer func() {
setting.Service.AllowDotsInUsernames = true
}()
tests := []struct {
arg string
want bool
}{
{arg: "a", want: true},
{arg: "abc", want: true},
{arg: "0.b-c", want: false},
{arg: "a.b-c_d", want: false},
{arg: ".abc", want: false},
{arg: "abc.", want: false},
{arg: "a..bc", want: false},
{arg: "a...bc", want: false},
{arg: "a.-bc", want: false},
{arg: "a._bc", want: false},
}
for _, tt := range tests {
t.Run(tt.arg, func(t *testing.T) {
assert.Equalf(t, tt.want, IsValidUsername(tt.arg), "IsValidUsername[AllowDotsInUsernames=false](%v)", tt.arg)
})
}
}

View file

@ -147,6 +147,16 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
}
}
if hp, ok := handler.(func(next http.Handler) http.HandlerFunc); ok {
return func(next http.Handler) http.Handler {
h := hp(next) // this handle could be dynamically generated, so we can't use it for debug info
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
routing.UpdateFuncInfo(req.Context(), funcInfo)
h.ServeHTTP(resp, req)
})
}
}
provider := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) {
// wrap the response writer to check whether the response has been written

View file

@ -8,6 +8,7 @@ import (
"reflect"
"strings"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
@ -135,7 +136,11 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo
case validation.ErrRegexPattern:
data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message)
case validation.ErrUsername:
data["ErrorMsg"] = trName + l.Tr("form.username_error")
if setting.Service.AllowDotsInUsernames {
data["ErrorMsg"] = trName + l.Tr("form.username_error")
} else {
data["ErrorMsg"] = trName + l.Tr("form.username_error_no_dots")
}
case validation.ErrInvalidGroupTeamMap:
data["ErrorMsg"] = trName + l.Tr("form.invalid_group_team_map_error", errs[0].Message)
default:

View file

@ -295,6 +295,7 @@ default_allow_create_organization = Allow Creation of Organizations by Default
default_allow_create_organization_popup = Allow new user accounts to create organizations by default.
default_enable_timetracking = Enable Time Tracking by Default
default_enable_timetracking_popup = Enable time tracking for new repositories by default.
allow_dots_in_usernames = Allow users to use dots in their usernames. Doesn't affect existing accounts.
no_reply_address = Hidden Email Domain
no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'.
password_algorithm = Password Hash Algorithm
@ -440,6 +441,10 @@ activate_email = Verify your email address
activate_email.title = %s, please verify your email address
activate_email.text = Please click the following link to verify your email address within <b>%s</b>:
admin.new_user.subject = New user %s just signed up
admin.new_user.user_info = User Information
admin.new_user.text = Please <a href="%s">click here</a> to manage the user from the admin panel.
register_notify = Welcome to Gitea
register_notify.title = %[1]s, welcome to %[2]s
register_notify.text_1 = this is your registration confirmation email for %s!
@ -534,6 +539,7 @@ include_error = ` must contain substring "%s".`
glob_pattern_error = ` glob pattern is invalid: %s.`
regex_pattern_error = ` regex pattern is invalid: %s.`
username_error = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.`
username_error_no_dots = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-') and underscore ('_'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.`
invalid_group_team_map_error = ` mapping is invalid: %s`
unknown_error = Unknown error:
captcha_incorrect = The CAPTCHA code is incorrect.
@ -1289,6 +1295,8 @@ commits.find = Search
commits.search_all = All Branches
commits.author = Author
commits.message = Message
commits.browse_further = Browse further
commits.renamed_from = Renamed from %s
commits.date = Date
commits.older = Older
commits.newer = Newer

View file

@ -8,6 +8,7 @@ import (
"crypto"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
@ -26,8 +27,6 @@ import (
chef_module "code.gitea.io/gitea/modules/packages/chef"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth"
"github.com/minio/sha256-simd"
)
const (

View file

@ -6,6 +6,7 @@ package maven
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"encoding/xml"
@ -26,8 +27,6 @@ import (
maven_module "code.gitea.io/gitea/modules/packages/maven"
"code.gitea.io/gitea/routers/api/packages/helper"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/minio/sha256-simd"
)
const (

View file

@ -55,19 +55,33 @@ func Search(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
UID: ctx.FormInt64("uid"),
Type: user_model.UserTypeIndividual,
ListOptions: listOptions,
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]any{
"ok": false,
"error": err.Error(),
uid := ctx.FormInt64("uid")
var users []*user_model.User
var maxResults int64
var err error
switch uid {
case user_model.GhostUserID:
maxResults = 1
users = []*user_model.User{user_model.NewGhostUser()}
case user_model.ActionsUserID:
maxResults = 1
users = []*user_model.User{user_model.NewActionsUser()}
default:
users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
UID: uid,
Type: user_model.UserTypeIndividual,
ListOptions: listOptions,
})
return
if err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]any{
"ok": false,
"error": err.Error(),
})
return
}
}
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)

View file

@ -360,6 +360,12 @@ func SubmitInstall(ctx *context.Context) {
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form)
return
}
if len(form.AdminPasswd) < setting.MinPasswordLength {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminPasswd"] = true
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplInstall, form)
return
}
}
// Init the engine with migration

View file

@ -30,6 +30,7 @@ import (
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
notify_service "code.gitea.io/gitea/services/notify"
"github.com/markbates/goth"
)
@ -598,6 +599,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
}
}
notify_service.NewUserSignUp(ctx, u)
// update external user information
if gothUser != nil {
if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser); err != nil {
@ -621,7 +623,6 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
ctx.Data["Email"] = u.Email
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
ctx.HTML(http.StatusOK, TplActivate)
if setting.CacheService.Enabled {
if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)

View file

@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/gorilla/feeds"
"github.com/jaytaylor/html2text"
)
func toBranchLink(ctx *context.Context, act *activities_model.Action) string {
@ -239,8 +240,15 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
content = desc
}
// It's a common practice for feed generators to use plain text titles.
// See https://codeberg.org/forgejo/forgejo/pulls/1595
plainTitle, err := html2text.FromString(title, html2text.Options{OmitLinks: true})
if err != nil {
return nil, err
}
items = append(items, &feeds.Item{
Title: title,
Title: plainTitle,
Link: link,
Description: desc,
Author: &feeds.Author{

View file

@ -8,11 +8,12 @@ import (
)
// RenderBranchFeed render format for branch or file
func RenderBranchFeed(ctx *context.Context) {
_, _, showFeedType := GetFeedType(ctx.Params(":reponame"), ctx.Req)
if ctx.Repo.TreePath == "" {
ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
} else {
ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
func RenderBranchFeed(feedType string) func(ctx *context.Context) {
return func(ctx *context.Context) {
if ctx.Repo.TreePath == "" {
ShowBranchFeed(ctx, ctx.Repo.Repository, feedType)
} else {
ShowFileFeed(ctx, ctx.Repo.Repository, feedType)
}
}
}

View file

@ -239,6 +239,22 @@ func FileHistory(ctx *context.Context) {
ctx.ServerError("CommitsByFileAndRange", err)
return
}
oldestCommit := commits[len(commits)-1]
renamedFiles, err := git.GetCommitFileRenames(ctx, ctx.Repo.GitRepo.Path, oldestCommit.ID.String())
if err != nil {
ctx.ServerError("GetCommitFileRenames", err)
return
}
for _, renames := range renamedFiles {
if renames[1] == fileName {
ctx.Data["OldFilename"] = renames[0]
ctx.Data["OldFilenameHistory"] = fmt.Sprintf("%s/commits/commit/%s/%s", ctx.Repo.RepoLink, oldestCommit.ID.String(), renames[0])
break
}
}
ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
ctx.Data["Username"] = ctx.Repo.Owner.Name

View file

@ -390,7 +390,9 @@ func NewReleasePost(ctx *context.Context) {
return
}
if !ctx.Repo.GitRepo.IsBranchExist(form.Target) {
// form.Target can be a branch name or a full commitID.
if !ctx.Repo.GitRepo.IsBranchExist(form.Target) &&
len(form.Target) == git.SHAFullLength && !ctx.Repo.GitRepo.IsCommitExist(form.Target) {
ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form)
return
}

View file

@ -165,7 +165,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
if ctx.Repo.TreePath != "" {
ctx.Data["HideRepoInfo"] = true
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefName)
}
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
@ -344,7 +344,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
}
defer dataRc.Close()
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefName)
ctx.Data["FileIsSymlink"] = entry.IsLink()
ctx.Data["FileName"] = blob.Name()
ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
@ -734,12 +734,19 @@ func Home(ctx *context.Context) {
if setting.Other.EnableFeed {
isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req)
if isFeed {
switch {
case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
if ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType) {
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
case ctx.Repo.TreePath == "":
return
}
if ctx.Repo.Repository.IsEmpty {
ctx.NotFound("MustBeNotEmpty", nil)
return
}
if ctx.Repo.TreePath == "" {
feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
case ctx.Repo.TreePath != "":
} else {
feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
}
return

View file

@ -49,17 +49,12 @@ import (
_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
"gitea.com/go-chi/captcha"
"github.com/NYTimes/gziphandler"
chi_middleware "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/klauspost/compress/gzhttp"
"github.com/prometheus/client_golang/prometheus"
)
const (
// GzipMinSize represents min size to compress for the body size of response
GzipMinSize = 1400
)
// CorsHandler return a http handler who set CORS options if enabled by config
func CorsHandler() func(next http.Handler) http.Handler {
if setting.CORSConfig.Enabled {
@ -230,11 +225,11 @@ func Routes() *web.Route {
var mid []any
if setting.EnableGzip {
h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize))
wrapper, err := gzhttp.NewWrapper(gzhttp.RandomJitter(32, 0, false))
if err != nil {
log.Fatal("GzipHandlerWithOpts failed: %v", err)
log.Fatal("gzhttp.NewWrapper failed: %v", err)
}
mid = append(mid, h)
mid = append(mid, wrapper)
}
if setting.Service.EnableCaptcha {
@ -1478,8 +1473,8 @@ func registerRoutes(m *web.Route) {
m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick)
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
m.Get("/rss/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed)
m.Get("/atom/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed)
m.Get("/rss/branch/*", repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed("rss"))
m.Get("/atom/branch/*", repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed("atom"))
m.Group("/src", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)

View file

@ -365,7 +365,7 @@ func (f *EditVariableForm) Validate(req *http.Request, errs binding.Errors) bind
// NewAccessTokenForm form for creating access token
type NewAccessTokenForm struct {
Name string `binding:"Required;MaxSize(255)"`
Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"`
Scope []string
}

View file

@ -5,6 +5,7 @@ package lfs
import (
stdCtx "context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
@ -33,7 +34,6 @@ import (
"code.gitea.io/gitea/modules/storage"
"github.com/golang-jwt/jwt/v5"
"github.com/minio/sha256-simd"
)
// requestContext contain variables from the HTTP request.

View file

@ -0,0 +1,80 @@
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package mailer
import (
"bytes"
"context"
"strconv"
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/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
)
const (
tplNewUserMail base.TplName = "notify/admin_new_user"
)
var sa = SendAsync
// MailNewUser sends notification emails on new user registrations to all admins
func MailNewUser(ctx context.Context, u *user_model.User) {
if !setting.Admin.SendNotificationEmailOnNewUser {
return
}
if setting.MailService == nil {
// No mail service configured
return
}
recipients, err := user_model.GetAllAdmins(ctx)
if err != nil {
log.Error("user_model.GetAllAdmins: %v", err)
return
}
langMap := make(map[string][]string)
for _, r := range recipients {
langMap[r.Language] = append(langMap[r.Language], r.Email)
}
for lang, tos := range langMap {
mailNewUser(ctx, u, lang, tos)
}
}
func mailNewUser(ctx context.Context, u *user_model.User, lang string, tos []string) {
locale := translation.NewLocale(lang)
subject := locale.Tr("mail.admin.new_user.subject", u.Name)
manageUserURL := setting.AppSubURL + "/admin/users/" + strconv.FormatInt(u.ID, 10)
body := locale.Tr("mail.admin.new_user.text", manageUserURL)
mailMeta := map[string]any{
"NewUser": u,
"Subject": subject,
"Body": body,
"Language": locale.Language(),
"locale": locale,
"Str2html": templates.Str2html,
}
var mailBody bytes.Buffer
if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplNewUserMail), mailMeta); err != nil {
log.Error("ExecuteTemplate [%s]: %v", string(tplNewUserMail)+"/body", err)
return
}
msgs := make([]*Message, 0, len(tos))
for _, to := range tos {
msg := NewMessage(to, subject, mailBody.String())
msg.Info = subject
msgs = append(msgs, msg)
}
sa(msgs...)
}

View file

@ -0,0 +1,88 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package mailer
import (
"context"
"strconv"
"strings"
"testing"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
func getTestUsers() []*user_model.User {
admin := new(user_model.User)
admin.Name = "admin"
admin.IsAdmin = true
admin.Language = "en_US"
admin.Email = "admin@example.com"
newUser := new(user_model.User)
newUser.Name = "new_user"
newUser.Language = "en_US"
newUser.IsAdmin = false
newUser.Email = "new_user@example.com"
newUser.LastLoginUnix = 1693648327
newUser.CreatedUnix = 1693648027
user_model.CreateUser(db.DefaultContext, admin)
user_model.CreateUser(db.DefaultContext, newUser)
users := make([]*user_model.User, 0)
users = append(users, admin)
users = append(users, newUser)
return users
}
func cleanUpUsers(ctx context.Context, users []*user_model.User) {
for _, u := range users {
db.DeleteByID(ctx, u.ID, new(user_model.User))
}
}
func TestAdminNotificationMail_test(t *testing.T) {
mailService := setting.Mailer{
From: "test@example.com",
Protocol: "dummy",
}
setting.MailService = &mailService
setting.Domain = "localhost"
setting.AppSubURL = "http://localhost"
// test with SEND_NOTIFICATION_EMAIL_ON_NEW_USER enabled
setting.Admin.SendNotificationEmailOnNewUser = true
ctx := context.Background()
NewContext(ctx)
users := getTestUsers()
oldSendAsync := sa
defer func() {
sa = oldSendAsync
cleanUpUsers(ctx, users)
}()
sa = func(msgs ...*Message) {
assert.Equal(t, len(msgs), 1, "Test provides only one admin user, so only one email must be sent")
assert.Equal(t, msgs[0].To, users[0].Email, "checks if the recipient is the admin of the instance")
manageUserURL := "/admin/users/" + strconv.FormatInt(users[1].ID, 10)
assert.True(t, strings.ContainsAny(msgs[0].Body, manageUserURL), "checks if the message contains the link to manage the newly created user from the admin panel")
}
MailNewUser(ctx, users[1])
// test with SEND_NOTIFICATION_EMAIL_ON_NEW_USER disabled; emails shouldn't be sent
setting.Admin.SendNotificationEmailOnNewUser = false
sa = func(msgs ...*Message) {
assert.Equal(t, 1, 0, "this shouldn't execute. MailNewUser must exit early since SEND_NOTIFICATION_EMAIL_ON_NEW_USER is disabled")
}
MailNewUser(ctx, users[1])
}

View file

@ -202,3 +202,7 @@ func (m *mailNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *
log.Error("SendRepoTransferNotifyMail: %v", err)
}
}
func (m *mailNotifier) NewUserSignUp(ctx context.Context, newUser *user_model.User) {
MailNewUser(ctx, newUser)
}

View file

@ -6,14 +6,13 @@ package token
import (
"context"
crypto_hmac "crypto/hmac"
"crypto/sha256"
"encoding/base32"
"fmt"
"time"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/util"
"github.com/minio/sha256-simd"
)
// A token is a verifiable container describing an action.

View file

@ -859,6 +859,11 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
}
for _, comment := range review.Comments {
// Skip code comment if it doesn't have a diff it is commeting on.
if comment.DiffHunk == "" {
continue
}
line := comment.Line
if line != 0 {
comment.Position = 1

View file

@ -59,6 +59,8 @@ type Notifier interface {
EditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string)
DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page string)
NewUserSignUp(ctx context.Context, newUser *user_model.User)
NewRelease(ctx context.Context, rel *repo_model.Release)
UpdateRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release)
DeleteRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release)

View file

@ -347,6 +347,13 @@ func RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, r
}
}
// NewUserSignUp notifies about a newly signed up user to notifiers
func NewUserSignUp(ctx context.Context, newUser *user_model.User) {
for _, notifier := range notifiers {
notifier.NewUserSignUp(ctx, newUser)
}
}
// PackageCreate notifies creation of a package to notifiers
func PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
for _, notifier := range notifiers {

View file

@ -197,6 +197,9 @@ func (*NullNotifier) SyncDeleteRef(ctx context.Context, doer *user_model.User, r
func (*NullNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) {
}
func (*NullNotifier) NewUserSignUp(ctx context.Context, newUser *user_model.User) {
}
// PackageCreate places a place holder function
func (*NullNotifier) PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) {
}

View file

@ -7,6 +7,7 @@ import (
"context"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"fmt"
@ -29,7 +30,6 @@ import (
webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/gobwas/glob"
"github.com/minio/sha256-simd"
)
// Deliver deliver hook task

View file

@ -159,6 +159,8 @@
<dd>{{if .Service.DefaultKeepEmailPrivate}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{ctx.Locale.Tr "admin.config.default_allow_create_organization"}}</dt>
<dd>{{if .Service.DefaultAllowCreateOrganization}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{ctx.Locale.Tr "admin.config.allow_dots_in_usernames"}}</dt>
<dd>{{if .Service.AllowDotsInUsernames}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{ctx.Locale.Tr "admin.config.enable_timetracking"}}</dt>
<dd>{{if .Service.EnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
{{if .Service.EnableTimetracking}}

View file

@ -2,7 +2,8 @@
<html lang="{{ctx.Locale.Lang}}" data-theme="{{ThemeName .SignedUser}}">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}}</title>
{{/* Display `- .Repsository.FullName` only if `.Title` does not already start with that. */}}
<title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if and (.Repository.Name) (not (StringUtils.HasPrefix .Title .Repository.FullName))}}{{.Repository.FullName}} - {{end}}{{AppName}}</title>
{{if .ManifestData}}<link rel="manifest" href="data:{{.ManifestData}}">{{end}}
<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}">
<meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}">

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{{.Subject}}</title>
<style>
blockquote { padding-left: 1em; margin: 1em 0; border-left: 1px solid grey; color: #777}
.footer { font-size:small; color:#666;}
</style>
</head>
<body>
<ul>
<h3>{{ctx.Locale.Tr "mail.admin.new_user.user_info"}}</h3>
<li>{{ctx.Locale.Tr "admin.users.created"}}: {{DateTime "full" .NewUser.LastLoginUnix}}</li>
<li>{{ctx.Locale.Tr "admin.users.last_login"}}: {{DateTime "full" .NewUser.CreatedUnix}}</li>
</ul>
<p> {{.Body | Str2html}} </p>
</body>
</html>

View file

@ -7,7 +7,7 @@
<div class="markup"><pre class="code-block"><code>&lt;repositories&gt;
&lt;repository&gt;
&lt;id&gt;gitea&lt;/id&gt;
&lt;url&gt;<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url>&lt;/url&gt;
&lt;url&gt;<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url>&lt;/url&gt;
&lt;/repository&gt;
&lt;/repositories&gt;

View file

@ -13,6 +13,11 @@
</div>
</div>
{{template "repo/commits_table" .}}
{{if .OldFilename}}
<div class="ui bottom attached header">
<span>{{ctx.Locale.Tr "repo.commits.renamed_from" .OldFilename}} (<a href="{{.OldFilenameHistory}}">{{ctx.Locale.Tr "repo.commits.browse_further"}}</a>)</span>
</div>
{{end}}
</div>
</div>
{{template "base/footer" .}}

View file

@ -33,7 +33,7 @@
</div>
{{if .MilestoneID}}
<div class="meta gt-my-2">
<a class="milestone" href="{{$.Page.RepoLink}}/milestone/{{.MilestoneID}}">
<a class="milestone" href="{{.Repo.Link}}/milestone/{{.MilestoneID}}">
{{svg "octicon-milestone" 16 "gt-mr-2 gt-vm"}}
<span class="gt-vm">{{.Milestone.Name}}</span>
</a>
@ -42,7 +42,7 @@
{{if $.Page.LinkedPRs}}
{{range index $.Page.LinkedPRs .ID}}
<div class="meta gt-my-2">
<a href="{{$.Page.RepoLink}}/pulls/{{.Index}}">
<a href="{{$.Issue.Repo.Link}}/pulls/{{.Index}}">
<span class="gt-m-0 text {{if .PullRequest.HasMerged}}purple{{else if .IsClosed}}red{{else}}green{{end}}">{{svg "octicon-git-merge" 16 "gt-mr-2 gt-vm"}}</span>
<span class="gt-vm">{{.Title}} <span class="text light grey">#{{.Index}}</span></span>
</a>
@ -61,7 +61,7 @@
{{if or .Labels .Assignees}}
<div class="extra content labels-list gt-p-0 gt-pt-2">
{{range .Labels}}
<a target="_blank" href="{{$.Page.RepoLink}}/issues?labels={{.ID}}">{{RenderLabel ctx .}}</a>
<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx .}}</a>
{{end}}
<div class="right floated">
{{range .Assignees}}

View file

@ -976,20 +976,23 @@
{{end}}
</div>
<div class="content">
<p>
<div class="ui warning message">
{{if .Repository.IsArchived}}
{{ctx.Locale.Tr "repo.settings.unarchive.text"}}
{{else}}
{{ctx.Locale.Tr "repo.settings.archive.text"}}
{{end}}
</p>
</div>
<form action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="{{if .Repository.IsArchived}}unarchive{{else}}archive{{end}}">
<input type="hidden" name="repo_id" value="{{.Repository.ID}}">
{{template "base/modal_actions_confirm" .}}
</div>
<form action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="{{if .Repository.IsArchived}}unarchive{{else}}archive{{end}}">
<input type="hidden" name="repo_id" value="{{.Repository.ID}}">
<div class="text right actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui red button">{{ctx.Locale.Tr "repo.settings.archive.button"}}</button>
</div>
</form>
</div>
</div>
{{end}}
{{end}}

View file

@ -0,0 +1 @@
ref: refs/heads/master

View file

@ -0,0 +1,4 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true

View file

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View file

@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

View file

@ -0,0 +1,2 @@
P pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack

View file

@ -0,0 +1,4 @@
# pack-refs with: peeled fully-peeled sorted
d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/heads/cake-recipe
80b83c5c8220c3aa3906e081f202a2a7563ec879 refs/heads/master
d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/tags/v1.0

View file

@ -288,6 +288,43 @@ func TestAPIGetComment(t *testing.T) {
assert.Equal(t, expect.Created.Unix(), apiComment.Created.Unix())
}
func TestAPIGetSystemUserComment(t *testing.T) {
defer tests.PrepareTestEnv(t)()
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
for _, systemUser := range []*user_model.User{
user_model.NewGhostUser(),
user_model.NewActionsUser(),
} {
body := fmt.Sprintf("Hello %s", systemUser.Name)
comment, err := issues_model.CreateComment(db.DefaultContext, &issues_model.CreateCommentOptions{
Type: issues_model.CommentTypeComment,
Doer: systemUser,
Repo: repo,
Issue: issue,
Content: body,
})
assert.NoError(t, err)
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID)
resp := MakeRequest(t, req, http.StatusOK)
var apiComment api.Comment
DecodeJSON(t, resp, &apiComment)
if assert.NotNil(t, apiComment.Poster) {
if assert.Equal(t, systemUser.ID, apiComment.Poster.ID) {
assert.NoError(t, comment.LoadPoster(db.DefaultContext))
assert.Equal(t, systemUser.Name, apiComment.Poster.UserName)
}
}
assert.Equal(t, body, apiComment.Body)
}
}
func TestAPIEditComment(t *testing.T) {
defer tests.PrepareTestEnv(t)()
const newCommentBody = "This is the new comment body"

View file

@ -0,0 +1,40 @@
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"testing"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestFeedPlainTextTitles(t *testing.T) {
// This test verifies that items' titles in feeds are generated as plain text.
// See https://codeberg.org/forgejo/forgejo/pulls/1595
t.Run("Feed plain text titles", func(t *testing.T) {
t.Run("Atom", func(t *testing.T) {
defer tests.PrepareTestEnv(t)()
req := NewRequest(t, "GET", "/user2/repo1.atom")
resp := MakeRequest(t, req, http.StatusOK)
data := resp.Body.String()
assert.Contains(t, data, "<title>the_1-user.with.all.allowedChars closed issue user2/repo1#4</title>")
})
t.Run("RSS", func(t *testing.T) {
defer tests.PrepareTestEnv(t)()
req := NewRequest(t, "GET", "/user2/repo1.rss")
resp := MakeRequest(t, req, http.StatusOK)
data := resp.Body.String()
assert.Contains(t, data, "<title>the_1-user.with.all.allowedChars closed issue user2/repo1#4</title>")
})
})
}

View file

@ -7,15 +7,19 @@ import (
"net/http"
"testing"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestFeed(t *testing.T) {
defer tests.PrepareTestEnv(t)()
t.Run("User", func(t *testing.T) {
t.Run("Atom", func(t *testing.T) {
defer tests.PrepareTestEnv(t)()
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2.atom")
resp := MakeRequest(t, req, http.StatusOK)
@ -25,7 +29,7 @@ func TestFeed(t *testing.T) {
})
t.Run("RSS", func(t *testing.T) {
defer tests.PrepareTestEnv(t)()
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2.rss")
resp := MakeRequest(t, req, http.StatusOK)
@ -34,4 +38,51 @@ func TestFeed(t *testing.T) {
assert.Contains(t, data, `<rss version="2.0"`)
})
})
t.Run("Repo", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
t.Run("Atom", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2/repo1/atom/branch/master")
resp := MakeRequest(t, req, http.StatusOK)
data := resp.Body.String()
assert.Contains(t, data, `<feed xmlns="http://www.w3.org/2005/Atom"`)
})
t.Run("RSS", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2/repo1/rss/branch/master")
resp := MakeRequest(t, req, http.StatusOK)
data := resp.Body.String()
assert.Contains(t, data, `<rss version="2.0"`)
})
})
t.Run("Empty", func(t *testing.T) {
err := user_model.UpdateUserCols(db.DefaultContext, &user_model.User{ID: 30, ProhibitLogin: false}, "prohibit_login")
assert.NoError(t, err)
session := loginUser(t, "user30")
t.Run("Atom", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user30/empty/atom/branch/master")
session.MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", "/user30/empty.atom/src/branch/master")
session.MakeRequest(t, req, http.StatusNotFound)
})
t.Run("RSS", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user30/empty/rss/branch/master")
session.MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", "/user30/empty.rss/src/branch/master")
session.MakeRequest(t, req, http.StatusNotFound)
})
})
})
}

View file

@ -11,6 +11,7 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
@ -33,7 +34,6 @@ import (
chef_router "code.gitea.io/gitea/routers/api/packages/chef"
"code.gitea.io/gitea/tests"
"github.com/minio/sha256-simd"
"github.com/stretchr/testify/assert"
)

View file

@ -5,6 +5,7 @@ package integration
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
@ -23,7 +24,6 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"
"github.com/minio/sha256-simd"
oci "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
)

View file

@ -5,6 +5,7 @@ package integration
import (
"bytes"
"crypto/sha256"
"fmt"
"net/http"
"strings"
@ -24,7 +25,6 @@ import (
packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup"
"code.gitea.io/gitea/tests"
"github.com/minio/sha256-simd"
"github.com/stretchr/testify/assert"
)

View file

@ -93,9 +93,9 @@ func TestAPISearchRepo(t *testing.T) {
}{
{
name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{
nil: {count: 33},
user: {count: 33},
user2: {count: 33},
nil: {count: 34},
user: {count: 34},
user2: {count: 34},
},
},
{

View file

@ -56,6 +56,28 @@ func TestAPIUserSearchNotLoggedIn(t *testing.T) {
}
}
func TestAPIUserSearchSystemUsers(t *testing.T) {
defer tests.PrepareTestEnv(t)()
for _, systemUser := range []*user_model.User{
user_model.NewGhostUser(),
user_model.NewActionsUser(),
} {
t.Run(systemUser.Name, func(t *testing.T) {
req := NewRequestf(t, "GET", "/api/v1/users/search?uid=%d", systemUser.ID)
resp := MakeRequest(t, req, http.StatusOK)
var results SearchResults
DecodeJSON(t, resp, &results)
assert.NotEmpty(t, results.Data)
if assert.EqualValues(t, 1, len(results.Data)) {
user := results.Data[0]
assert.EqualValues(t, user.UserName, systemUser.Name)
assert.EqualValues(t, user.ID, systemUser.ID)
}
})
}
}
func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) {
defer tests.PrepareTestEnv(t)()
adminUsername := "user1"

View file

@ -42,8 +42,8 @@ import (
"github.com/markbates/goth"
"github.com/markbates/goth/gothic"
goth_gitlab "github.com/markbates/goth/providers/gitlab"
"github.com/santhosh-tekuri/jsonschema/v5"
"github.com/stretchr/testify/assert"
"github.com/xeipuuv/gojsonschema"
)
var testWebRoutes *web.Route
@ -511,16 +511,15 @@ func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile
_, schemaFileErr := os.Stat(schemaFilePath)
assert.Nil(t, schemaFileErr)
schema, schemaFileReadErr := os.ReadFile(schemaFilePath)
assert.Nil(t, schemaFileReadErr)
assert.True(t, len(schema) > 0)
schema, err := jsonschema.Compile(schemaFilePath)
assert.NoError(t, err)
nodeinfoSchema := gojsonschema.NewStringLoader(string(schema))
nodeinfoString := gojsonschema.NewStringLoader(resp.Body.String())
result, schemaValidationErr := gojsonschema.Validate(nodeinfoSchema, nodeinfoString)
assert.Nil(t, schemaValidationErr)
assert.Empty(t, result.Errors())
assert.True(t, result.Valid())
var data interface{}
err = json.Unmarshal(resp.Body.Bytes(), &data)
assert.NoError(t, err)
schemaValidation := schema.Validate(data)
assert.Nil(t, schemaValidation)
}
func GetCSRF(t testing.TB, session *TestSession, urlStr string) string {
@ -530,3 +529,18 @@ func GetCSRF(t testing.TB, session *TestSession, urlStr string) string {
doc := NewHTMLParser(t, resp.Body)
return doc.GetCSRF()
}
func GetHTMLTitle(t testing.TB, session *TestSession, urlStr string) string {
t.Helper()
req := NewRequest(t, "GET", urlStr)
var resp *httptest.ResponseRecorder
if session == nil {
resp = MakeRequest(t, req, http.StatusOK)
} else {
resp = session.MakeRequest(t, req, http.StatusOK)
}
doc := NewHTMLParser(t, resp.Body)
return doc.Find("head title").Text()
}

View file

@ -18,9 +18,9 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/web"
"code.gitea.io/gitea/tests"
"github.com/klauspost/compress/gzhttp"
gzipp "github.com/klauspost/compress/gzip"
"github.com/stretchr/testify/assert"
)
@ -132,7 +132,7 @@ func TestGetLFSSmallTokenFail(t *testing.T) {
func TestGetLFSLarge(t *testing.T) {
defer tests.PrepareTestEnv(t)()
content := make([]byte, web.GzipMinSize*10)
content := make([]byte, gzhttp.DefaultMinSize*10)
for i := range content {
content[i] = byte(i % 256)
}
@ -143,7 +143,7 @@ func TestGetLFSLarge(t *testing.T) {
func TestGetLFSGzip(t *testing.T) {
defer tests.PrepareTestEnv(t)()
b := make([]byte, web.GzipMinSize*10)
b := make([]byte, gzhttp.DefaultMinSize*10)
for i := range b {
b[i] = byte(i % 256)
}
@ -159,7 +159,7 @@ func TestGetLFSGzip(t *testing.T) {
func TestGetLFSZip(t *testing.T) {
defer tests.PrepareTestEnv(t)()
b := make([]byte, web.GzipMinSize*10)
b := make([]byte, gzhttp.DefaultMinSize*10)
for i := range b {
b[i] = byte(i % 256)
}

View file

@ -21,6 +21,10 @@ import (
)
func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title string, preRelease, draft bool) {
createNewReleaseTarget(t, session, repoURL, tag, title, "master", preRelease, draft)
}
func createNewReleaseTarget(t *testing.T, session *TestSession, repoURL, tag, title, target string, preRelease, draft bool) {
req := NewRequest(t, "GET", repoURL+"/releases/new")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
@ -31,7 +35,7 @@ func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title st
postData := map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"tag_name": tag,
"tag_target": "master",
"tag_target": target,
"title": title,
"content": "",
}
@ -217,6 +221,15 @@ func TestViewReleaseListLogin(t *testing.T) {
}, links)
}
func TestReleaseOnCommit(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
createNewReleaseTarget(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", "65f1bf27bc3bf70f64657658635e66094edbcb4d", false, false)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.stable"), 4)
}
func TestViewTagsList(t *testing.T) {
defer tests.PrepareTestEnv(t)()

View file

@ -15,8 +15,8 @@ import (
"github.com/stretchr/testify/assert"
)
func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName string) *httptest.ResponseRecorder {
req := NewRequest(t, "GET", fmt.Sprintf("/repo/migrate?service_type=%d", structs.PlainGitService)) // render plain git migration page
func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName string, service structs.GitServiceType) *httptest.ResponseRecorder {
req := NewRequest(t, "GET", fmt.Sprintf("/repo/migrate?service_type=%d", service)) // render plain git migration page
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
@ -31,7 +31,7 @@ func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName str
"clone_addr": cloneAddr,
"uid": uid,
"repo_name": repoName,
"service": fmt.Sprintf("%d", structs.PlainGitService),
"service": fmt.Sprintf("%d", service),
})
resp = session.MakeRequest(t, req, http.StatusSeeOther)
@ -41,5 +41,17 @@ func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName str
func TestRepoMigrate(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
testRepoMigrate(t, session, "https://github.com/go-gitea/test_repo.git", "git")
for _, s := range []struct {
testName string
cloneAddr string
repoName string
service structs.GitServiceType
}{
{"TestMigrateGithub", "https://github.com/go-gitea/test_repo.git", "git", structs.PlainGitService},
{"TestMigrateGithub", "https://github.com/go-gitea/test_repo.git", "github", structs.GithubService},
} {
t.Run(s.testName, func(t *testing.T) {
testRepoMigrate(t, session, s.cloneAddr, s.repoName, s.service)
})
}
}

View file

@ -203,6 +203,110 @@ func TestViewAsRepoAdmin(t *testing.T) {
}
}
func TestRepoHTMLTitle(t *testing.T) {
defer tests.PrepareTestEnv(t)()
t.Run("Repository homepage", func(t *testing.T) {
t.Run("Without description", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo1")
assert.EqualValues(t, "user2/repo1 - Gitea: Git with a cup of tea", htmlTitle)
})
t.Run("With description", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user27/repo49")
assert.EqualValues(t, "user27/repo49: A wonderful repository with more than just a README.md - Gitea: Git with a cup of tea", htmlTitle)
})
})
t.Run("Code view", func(t *testing.T) {
t.Run("Directory", func(t *testing.T) {
t.Run("Default branch", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/branch/master/deep/nesting")
assert.EqualValues(t, "repo59/deep/nesting at master - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle)
})
t.Run("Non-default branch", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/branch/cake-recipe/deep/nesting")
assert.EqualValues(t, "repo59/deep/nesting at cake-recipe - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle)
})
t.Run("Commit", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/commit/d8f53dfb33f6ccf4169c34970b5e747511c18beb/deep/nesting/")
assert.EqualValues(t, "repo59/deep/nesting at d8f53dfb33f6ccf4169c34970b5e747511c18beb - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle)
})
t.Run("Tag", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/tag/v1.0/deep/nesting/")
assert.EqualValues(t, "repo59/deep/nesting at v1.0 - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle)
})
})
t.Run("File", func(t *testing.T) {
t.Run("Default branch", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/branch/master/deep/nesting/folder/secret_sauce_recipe.txt")
assert.EqualValues(t, "repo59/deep/nesting/folder/secret_sauce_recipe.txt at master - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle)
})
t.Run("Non-default branch", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/branch/cake-recipe/deep/nesting/folder/secret_sauce_recipe.txt")
assert.EqualValues(t, "repo59/deep/nesting/folder/secret_sauce_recipe.txt at cake-recipe - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle)
})
t.Run("Commit", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/commit/d8f53dfb33f6ccf4169c34970b5e747511c18beb/deep/nesting/folder/secret_sauce_recipe.txt")
assert.EqualValues(t, "repo59/deep/nesting/folder/secret_sauce_recipe.txt at d8f53dfb33f6ccf4169c34970b5e747511c18beb - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle)
})
t.Run("Tag", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/tag/v1.0/deep/nesting/folder/secret_sauce_recipe.txt")
assert.EqualValues(t, "repo59/deep/nesting/folder/secret_sauce_recipe.txt at v1.0 - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle)
})
})
})
t.Run("Issues view", func(t *testing.T) {
t.Run("Overview page", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo1/issues")
assert.EqualValues(t, "Issues - user2/repo1 - Gitea: Git with a cup of tea", htmlTitle)
})
t.Run("View issue page", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo1/issues/1")
assert.EqualValues(t, "#1 - issue1 - user2/repo1 - Gitea: Git with a cup of tea", htmlTitle)
})
})
t.Run("Pull requests view", func(t *testing.T) {
t.Run("Overview page", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo1/pulls")
assert.EqualValues(t, "Pull Requests - user2/repo1 - Gitea: Git with a cup of tea", htmlTitle)
})
t.Run("View pull request", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
htmlTitle := GetHTMLTitle(t, nil, "/user2/repo1/pulls/2")
assert.EqualValues(t, "#2 - issue2 - user2/repo1 - Gitea: Git with a cup of tea", htmlTitle)
})
})
}
// TestViewFileInRepo repo description, topics and summary should not be displayed when viewing a file
func TestViewFileInRepo(t *testing.T) {
defer tests.PrepareTestEnv(t)()
@ -586,3 +690,33 @@ func TestDangerZoneConfirmation(t *testing.T) {
})
})
}
func TestRenamedFileHistory(t *testing.T) {
defer tests.PrepareTestEnv(t)()
t.Run("Renamed file", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2/repo59/commits/branch/master/license")
resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
renameNotice := htmlDoc.doc.Find(".ui.bottom.attached.header")
assert.Equal(t, 1, renameNotice.Length())
assert.Contains(t, renameNotice.Text(), "Renamed from licnse (Browse further)")
oldFileHistoryLink, ok := renameNotice.Find("a").Attr("href")
assert.True(t, ok)
assert.Equal(t, "/user2/repo59/commits/commit/80b83c5c8220c3aa3906e081f202a2a7563ec879/licnse", oldFileHistoryLink)
})
t.Run("Non renamed file", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo59/commits/branch/master/README.md")
resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
htmlDoc.AssertElement(t, ".ui.bottom.attached.header", false)
})
}

View file

@ -17,10 +17,6 @@
}
@media (max-width: 767.98px) {
.issue-list-toolbar-right .dropdown .menu {
left: auto !important;
right: auto !important;
}
.issue-list-navbar {
order: 0;
}
@ -31,6 +27,37 @@
.issue-list-search {
order: 2 !important;
}
/* Don't use flex wrap on mobile as it takes too much vertical space.
* Only set overflow properties on mobile screens, because while the
* CSS trick to pop out from overflowing works on desktop screen, it
* has a massive flaw that it cannot inherited any max width from it's 'real'
* parent and therefor ends up taking more vertical space than is desired.
**/
.issue-list-toolbar-right .filter.menu {
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
}
/* The following few CSS was created with care and built with the information
* from CSS-Tricks: https://css-tricks.com/popping-hidden-overflow/
*/
/* It's important that every element up to .issue-list-toolbar-right doesn't
* have a position set, such that element that wants to pop out will use
* .issue-list-toolbar-right as 'clip parent' and thereby avoids the
* overflow-y: hidden.
*/
.issue-list-toolbar-right .filter.menu > .dropdown.item {
position: initial;
}
/* It's important that this element and not an child has `position` set.
* Set width so that overflow-x knows where to stop overflowing.
*/
.issue-list-toolbar-right {
position: relative;
width: 100%;
}
}
#issue-list .flex-item-title .labels-list {