Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Anthony Wang 2022-09-22 23:57:01 +00:00
commit 26f57be49c
Signed by: a
GPG key ID: 42A5B952E6DD8D38
44 changed files with 378 additions and 188 deletions

View file

@ -40,7 +40,7 @@ linters-settings:
stylecheck: stylecheck:
checks: ["all", "-ST1005", "-ST1003"] checks: ["all", "-ST1005", "-ST1003"]
nakedret: nakedret:
max-func-lines: 0 max-func-lines: 0
gocritic: gocritic:
disabled-checks: disabled-checks:
- ifElseChain - ifElseChain
@ -86,6 +86,8 @@ linters-settings:
- github.com/unknwon/com: "use gitea's util and replacements" - github.com/unknwon/com: "use gitea's util and replacements"
issues: issues:
max-issues-per-linter: 0
max-same-issues: 0
exclude-rules: exclude-rules:
# Exclude some linters from running on tests files. # Exclude some linters from running on tests files.
- path: _test\.go - path: _test\.go

File diff suppressed because one or more lines are too long

View file

@ -64,8 +64,8 @@ func main() {
} }
entries = append(entries, LicenseEntry{ entries = append(entries, LicenseEntry{
Name: name, Name: name,
Path: path, Path: path,
LicenseText: string(licenseText), LicenseText: string(licenseText),
}) })
} }

View file

@ -43,7 +43,7 @@ const (
var CmdServ = cli.Command{ var CmdServ = cli.Command{
Name: "serv", Name: "serv",
Usage: "This command should only be called by SSH shell", Usage: "This command should only be called by SSH shell",
Description: `Serv provide access auth for repositories`, Description: "Serv provides access auth for repositories",
Action: runServ, Action: runServ,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ cli.BoolFlag{

View file

@ -2153,7 +2153,7 @@ ROUTER = console
;[api] ;[api]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Enables Swagger. True or false; default is true. ;; Enables the API documentation endpoints (/api/swagger, /api/v1/swagger, …). True or false.
;ENABLE_SWAGGER = true ;ENABLE_SWAGGER = true
;; Max number of items in a page ;; Max number of items in a page
;MAX_RESPONSE_ITEMS = 50 ;MAX_RESPONSE_ITEMS = 50
@ -2161,7 +2161,7 @@ ROUTER = console
;DEFAULT_PAGING_NUM = 30 ;DEFAULT_PAGING_NUM = 30
;; Default and maximum number of items per page for git trees api ;; Default and maximum number of items per page for git trees api
;DEFAULT_GIT_TREES_PER_PAGE = 1000 ;DEFAULT_GIT_TREES_PER_PAGE = 1000
;; Default size of a blob returned by the blobs API (default is 10MiB) ;; Default max size of a blob returned by the blobs API (default is 10MiB)
;DEFAULT_MAX_BLOB_SIZE = 10485760 ;DEFAULT_MAX_BLOB_SIZE = 10485760
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -1015,11 +1015,11 @@ Default templates for project boards:
## API (`api`) ## API (`api`)
- `ENABLE_SWAGGER`: **true**: Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true. - `ENABLE_SWAGGER`: **true**: Enables the API documentation endpoints (`/api/swagger`, `/api/v1/swagger`, …). True or false.
- `MAX_RESPONSE_ITEMS`: **50**: Max number of items in a page. - `MAX_RESPONSE_ITEMS`: **50**: Max number of items in a page.
- `DEFAULT_PAGING_NUM`: **30**: Default paging number of API. - `DEFAULT_PAGING_NUM`: **30**: Default paging number of API.
- `DEFAULT_GIT_TREES_PER_PAGE`: **1000**: Default and maximum number of items per page for Git trees API. - `DEFAULT_GIT_TREES_PER_PAGE`: **1000**: Default and maximum number of items per page for Git trees API.
- `DEFAULT_MAX_BLOB_SIZE`: **10485760**: Default max size of a blob that can be return by the blobs API. - `DEFAULT_MAX_BLOB_SIZE`: **10485760** (10MiB): Default max size of a blob that can be returned by the blobs API.
## OAuth2 (`oauth2`) ## OAuth2 (`oauth2`)

View file

@ -299,7 +299,7 @@ test01.xls: application/vnd.ms-excel; charset=binary
## API (`api`) ## API (`api`)
- `ENABLE_SWAGGER`: **true**: 是否启用swagger路由 /api/swagger, /api/v1/swagger etc. endpoints. True 或 false; 默认是 true. - `ENABLE_SWAGGER`: **true**: 是否启用swagger路由 /api/swagger, /api/v1/swagger etc. endpoints. True 或 false.
- `MAX_RESPONSE_ITEMS`: **50**: 一个页面最大的项目数。 - `MAX_RESPONSE_ITEMS`: **50**: 一个页面最大的项目数。
- `DEFAULT_PAGING_NUM`: **30**: API中默认分页条数。 - `DEFAULT_PAGING_NUM`: **30**: API中默认分页条数。
- `DEFAULT_GIT_TREES_PER_PAGE`: **1000**: GIT TREES API每页的默认最大项数. - `DEFAULT_GIT_TREES_PER_PAGE`: **1000**: GIT TREES API每页的默认最大项数.

View file

@ -126,13 +126,13 @@ A "login prohibited" user is a user that is not allowed to log in to Gitea anymo
## What is Swagger? ## What is Swagger?
[Swagger](https://swagger.io/) is what Gitea uses for its API. [Swagger](https://swagger.io/) is what Gitea uses for its API documentation.
All Gitea instances have the built-in API, though it can be disabled by setting `ENABLE_SWAGGER` to `false` in the `api` section of your `app.ini` All Gitea instances have the built-in API and there is no way to disable it completely.
You can, however, disable showing its documentation by setting `ENABLE_SWAGGER` to `false` in the `api` section of your `app.ini`.
For more information, refer to Gitea's [API docs]({{< relref "doc/developers/api-usage.en-us.md" >}}).
For more information, refer to Gitea's [API docs]({{< relref "doc/developers/api-usage.en-us.md" >}}) You can see the latest API (for example) on <https://try.gitea.io/api/swagger>.
[Swagger Example](https://try.gitea.io/api/swagger)
## Adjusting your server for public/private use ## Adjusting your server for public/private use

View file

@ -218,6 +218,11 @@ func (a *Action) GetRepoLink() string {
return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName()), url.PathEscape(a.GetRepoName())) return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName()), url.PathEscape(a.GetRepoName()))
} }
// GetRepoAbsoluteLink returns the absolute link to action repository.
func (a *Action) GetRepoAbsoluteLink() string {
return setting.AppURL + url.PathEscape(a.GetRepoUserName()) + "/" + url.PathEscape(a.GetRepoName())
}
// GetCommentLink returns link to action comment. // GetCommentLink returns link to action comment.
func (a *Action) GetCommentLink() string { func (a *Action) GetCommentLink() string {
return a.getCommentLink(db.DefaultContext) return a.getCommentLink(db.DefaultContext)

View file

@ -10,6 +10,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issue_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -20,7 +21,7 @@ import (
func TestAction_GetRepoPath(t *testing.T) { func TestAction_GetRepoPath(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
action := &activities_model.Action{RepoID: repo.ID} action := &activities_model.Action{RepoID: repo.ID}
assert.Equal(t, path.Join(owner.Name, repo.Name), action.GetRepoPath()) assert.Equal(t, path.Join(owner.Name, repo.Name), action.GetRepoPath())
@ -28,12 +29,15 @@ func TestAction_GetRepoPath(t *testing.T) {
func TestAction_GetRepoLink(t *testing.T) { func TestAction_GetRepoLink(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
action := &activities_model.Action{RepoID: repo.ID} comment := unittest.AssertExistsAndLoadBean(t, &issue_model.Comment{ID: 2})
action := &activities_model.Action{RepoID: repo.ID, CommentID: comment.ID}
setting.AppSubURL = "/suburl" setting.AppSubURL = "/suburl"
expected := path.Join(setting.AppSubURL, owner.Name, repo.Name) expected := path.Join(setting.AppSubURL, owner.Name, repo.Name)
assert.Equal(t, expected, action.GetRepoLink()) assert.Equal(t, expected, action.GetRepoLink())
assert.Equal(t, repo.HTMLURL(), action.GetRepoAbsoluteLink())
assert.Equal(t, comment.HTMLURL(), action.GetCommentLink())
} }
func TestGetFeeds(t *testing.T) { func TestGetFeeds(t *testing.T) {

View file

@ -124,3 +124,15 @@
repo_id: 24 repo_id: 24
mode: 1 mode: 1
-
id: 22
user_id: 31
repo_id: 27
mode: 4
-
id: 23
user_id: 31
repo_id: 28
mode: 4

View file

@ -12,3 +12,8 @@
id: 3 id: 3
user_id: 2 user_id: 2
follow_id: 8 follow_id: 8
-
id: 4
user_id: 31
follow_id: 33

View file

@ -69,3 +69,9 @@
uid: 2 uid: 2
org_id: 17 org_id: 17
is_public: true is_public: true
-
id: 13
uid: 31
org_id: 19
is_public: true

View file

@ -55,7 +55,7 @@
name: Owners name: Owners
authorize: 4 # owner authorize: 4 # owner
num_repos: 2 num_repos: 2
num_members: 1 num_members: 2
can_create_org_repo: true can_create_org_repo: true
- -

View file

@ -87,3 +87,9 @@
org_id: 17 org_id: 17
team_id: 9 team_id: 9
uid: 29 uid: 29
-
id: 16
org_id: 19
team_id: 6
uid: 31

View file

@ -345,7 +345,7 @@
avatar_email: user19@example.com avatar_email: user19@example.com
num_repos: 2 num_repos: 2
is_active: true is_active: true
num_members: 1 num_members: 2
num_teams: 1 num_teams: 1
- -
@ -572,6 +572,8 @@
avatar: avatar31 avatar: avatar31
avatar_email: user31@example.com avatar_email: user31@example.com
num_repos: 0 num_repos: 0
num_followers: 0
num_following: 1
is_active: true is_active: true
- -
@ -590,3 +592,23 @@
avatar_email: user30@example.com avatar_email: user30@example.com
num_repos: 0 num_repos: 0
is_active: true is_active: true
-
id: 33
lower_name: user33
name: user33
login_name: user33
full_name: User 33 (Limited Visibility)
email: user33@example.com
passwd_hash_algo: argon2
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
type: 0 # individual
salt: ZogKvWdyEx
is_admin: false
visibility: 1
avatar: avatar33
avatar_email: user33@example.com
num_repos: 0
num_followers: 1
num_following: 0
is_active: true

View file

@ -181,6 +181,10 @@ func createReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, erro
Reaction: opts.Type, Reaction: opts.Type,
UserID: opts.DoerID, UserID: opts.DoerID,
} }
if findOpts.CommentID == 0 {
// explicit search of Issue Reactions where CommentID = 0
findOpts.CommentID = -1
}
existingR, _, err := FindReactions(ctx, findOpts) existingR, _, err := FindReactions(ctx, findOpts)
if err != nil { if err != nil {
@ -256,16 +260,23 @@ func DeleteReaction(ctx context.Context, opts *ReactionOptions) error {
CommentID: opts.CommentID, CommentID: opts.CommentID,
} }
_, err := db.GetEngine(ctx).Where("original_author_id = 0").Delete(reaction) sess := db.GetEngine(ctx).Where("original_author_id = 0")
if opts.CommentID == -1 {
reaction.CommentID = 0
sess.MustCols("comment_id")
}
_, err := sess.Delete(reaction)
return err return err
} }
// DeleteIssueReaction deletes a reaction on issue. // DeleteIssueReaction deletes a reaction on issue.
func DeleteIssueReaction(doerID, issueID int64, content string) error { func DeleteIssueReaction(doerID, issueID int64, content string) error {
return DeleteReaction(db.DefaultContext, &ReactionOptions{ return DeleteReaction(db.DefaultContext, &ReactionOptions{
Type: content, Type: content,
DoerID: doerID, DoerID: doerID,
IssueID: issueID, IssueID: issueID,
CommentID: -1,
}) })
} }

View file

@ -129,29 +129,11 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
if opts.UserID > 0 { if opts.UserID > 0 {
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id") sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id")
} }
sess = db.SetSessionPagination(sess, opts)
count, err := sess.
Where(cond).
Count(new(Team))
if err != nil {
return nil, 0, err
}
if opts.UserID > 0 {
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id")
}
if opts.PageSize == -1 {
opts.PageSize = int(count)
} else {
sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
}
teams := make([]*Team, 0, opts.PageSize) teams := make([]*Team, 0, opts.PageSize)
if err = sess. count, err := sess.Where(cond).OrderBy("lower_name").FindAndCount(&teams)
Where(cond). if err != nil {
OrderBy("lower_name").
Find(&teams); err != nil {
return nil, 0, err return nil, 0, err
} }

View file

@ -1267,7 +1267,7 @@ func isUserVisibleToViewerCond(viewer *User) builder.Cond {
// IsUserVisibleToViewer check if viewer is able to see user profile // IsUserVisibleToViewer check if viewer is able to see user profile
func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool { func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool {
if viewer != nil && viewer.IsAdmin { if viewer != nil && (viewer.IsAdmin || viewer.ID == u.ID) {
return true return true
} }
@ -1306,7 +1306,7 @@ func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool {
return false return false
} }
if count < 0 { if count == 0 {
// No common organization // No common organization
return false return false
} }

View file

@ -400,3 +400,56 @@ func TestUnfollowUser(t *testing.T) {
unittest.CheckConsistencyFor(t, &user_model.User{}) unittest.CheckConsistencyFor(t, &user_model.User{})
} }
func TestIsUserVisibleToViewer(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) // admin, public
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // normal, public
user20 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20}) // public, same team as user31
user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) // public, is restricted
user31 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31}) // private, same team as user20
user33 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 33}) // limited, follows 31
test := func(u, viewer *user_model.User, expected bool) {
name := func(u *user_model.User) string {
if u == nil {
return "<nil>"
}
return u.Name
}
assert.Equal(t, expected, user_model.IsUserVisibleToViewer(db.DefaultContext, u, viewer), "user %v should be visible to viewer %v: %v", name(u), name(viewer), expected)
}
// admin viewer
test(user1, user1, true)
test(user20, user1, true)
test(user31, user1, true)
test(user33, user1, true)
// non admin viewer
test(user4, user4, true)
test(user20, user4, true)
test(user31, user4, false)
test(user33, user4, true)
test(user4, nil, true)
// public user
test(user4, user20, true)
test(user4, user31, true)
test(user4, user33, true)
// limited user
test(user33, user33, true)
test(user33, user4, true)
test(user33, user29, false)
test(user33, nil, false)
// private user
test(user31, user31, true)
test(user31, user4, false)
test(user31, user20, true)
test(user31, user29, false)
test(user31, user33, true)
test(user31, nil, false)
}

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
) )
@ -54,47 +55,11 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
Owner: ctx.ContextUser, Owner: ctx.ContextUser,
} }
if ctx.Package.Owner.IsOrganization() { var err error
org := organization.OrgFromUser(ctx.Package.Owner) ctx.Package.AccessMode, err = determineAccessMode(ctx)
if err != nil {
// 1. Get user max authorize level for the org (may be none, if user is not member of the org) errCb(http.StatusInternalServerError, "determineAccessMode", err)
if ctx.Doer != nil { return
var err error
ctx.Package.AccessMode, err = org.GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID)
if err != nil {
errCb(http.StatusInternalServerError, "GetOrgUserMaxAuthorizeLevel", err)
return
}
// If access mode is less than write check every team for more permissions
if ctx.Package.AccessMode < perm.AccessModeWrite {
teams, err := organization.GetUserOrgTeams(ctx, org.ID, ctx.Doer.ID)
if err != nil {
errCb(http.StatusInternalServerError, "GetUserOrgTeams", err)
return
}
for _, t := range teams {
perm := t.UnitAccessModeCtx(ctx, unit.TypePackages)
if ctx.Package.AccessMode < perm {
ctx.Package.AccessMode = perm
}
}
}
}
// 2. If authorize level is none, check if org is visible to user
if ctx.Package.AccessMode == perm.AccessModeNone && organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) {
ctx.Package.AccessMode = perm.AccessModeRead
}
} else {
if ctx.Doer != nil && !ctx.Doer.IsGhost() {
// 1. Check if user is package owner
if ctx.Doer.ID == ctx.Package.Owner.ID {
ctx.Package.AccessMode = perm.AccessModeOwner
} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic || ctx.Package.Owner.Visibility == structs.VisibleTypeLimited { // 2. Check if package owner is public or limited
ctx.Package.AccessMode = perm.AccessModeRead
}
} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic { // 3. Check if package owner is public
ctx.Package.AccessMode = perm.AccessModeRead
}
} }
packageType := ctx.Params("type") packageType := ctx.Params("type")
@ -119,6 +84,57 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
} }
} }
func determineAccessMode(ctx *Context) (perm.AccessMode, error) {
accessMode := perm.AccessModeNone
if setting.Service.RequireSignInView && ctx.Doer == nil {
return accessMode, nil
}
if ctx.Package.Owner.IsOrganization() {
org := organization.OrgFromUser(ctx.Package.Owner)
// 1. Get user max authorize level for the org (may be none, if user is not member of the org)
if ctx.Doer != nil {
var err error
accessMode, err = org.GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID)
if err != nil {
return accessMode, err
}
// If access mode is less than write check every team for more permissions
if accessMode < perm.AccessModeWrite {
teams, err := organization.GetUserOrgTeams(ctx, org.ID, ctx.Doer.ID)
if err != nil {
return accessMode, err
}
for _, t := range teams {
perm := t.UnitAccessModeCtx(ctx, unit.TypePackages)
if accessMode < perm {
accessMode = perm
}
}
}
}
// 2. If authorize level is none, check if org is visible to user
if accessMode == perm.AccessModeNone && organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) {
accessMode = perm.AccessModeRead
}
} else {
if ctx.Doer != nil && !ctx.Doer.IsGhost() {
// 1. Check if user is package owner
if ctx.Doer.ID == ctx.Package.Owner.ID {
accessMode = perm.AccessModeOwner
} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic || ctx.Package.Owner.Visibility == structs.VisibleTypeLimited { // 2. Check if package owner is public or limited
accessMode = perm.AccessModeRead
}
} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic { // 3. Check if package owner is public
accessMode = perm.AccessModeRead
}
}
return accessMode, nil
}
// PackageContexter initializes a package context for a request. // PackageContexter initializes a package context for a request.
func PackageContexter(ctx gocontext.Context) func(next http.Handler) http.Handler { func PackageContexter(ctx gocontext.Context) func(next http.Handler) http.Handler {
_, rnd := templates.HTMLRenderer(ctx) _, rnd := templates.HTMLRenderer(ctx)

View file

@ -44,7 +44,7 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
case "160000": case "160000":
entry.entryMode = EntryModeCommit entry.entryMode = EntryModeCommit
pos += 14 // skip over "160000 object " pos += 14 // skip over "160000 object "
case "040000": case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
entry.entryMode = EntryModeTree entry.entryMode = EntryModeTree
pos += 12 // skip over "040000 tree " pos += 12 // skip over "040000 tree "
default: default:
@ -119,7 +119,7 @@ loop:
entry.entryMode = EntryModeSymlink entry.entryMode = EntryModeSymlink
case "160000": case "160000":
entry.entryMode = EntryModeCommit entry.entryMode = EntryModeCommit
case "40000": case "40000", "40755": // git uses 40000 for tree object, but some users may get 40755 for unknown reasons
entry.entryMode = EntryModeTree entry.entryMode = EntryModeTree
default: default:
log.Debug("Unknown mode: %v", string(mode)) log.Debug("Unknown mode: %v", string(mode))

View file

@ -11,6 +11,7 @@ import (
"unicode/utf8" "unicode/utf8"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )

View file

@ -8,6 +8,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )

View file

@ -10,7 +10,6 @@ import (
"os" "os"
"path" "path"
"strings" "strings"
"unicode/utf8"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
@ -337,13 +336,6 @@ func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error
func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) {
repo.LowerName = strings.ToLower(repo.Name) repo.LowerName = strings.ToLower(repo.Name)
if utf8.RuneCountInString(repo.Description) > 255 {
repo.Description = string([]rune(repo.Description)[:255])
}
if utf8.RuneCountInString(repo.Website) > 255 {
repo.Website = string([]rune(repo.Website)[:255])
}
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil {

View file

@ -111,7 +111,7 @@ type CreateRepoOption struct {
// unique: true // unique: true
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"` Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"`
// Description of the repository to create // Description of the repository to create
Description string `json:"description" binding:"MaxSize(255)"` Description string `json:"description" binding:"MaxSize(2048)"`
// Whether the repository is private // Whether the repository is private
Private bool `json:"private"` Private bool `json:"private"`
// Label-Set to use // Label-Set to use
@ -140,9 +140,9 @@ type EditRepoOption struct {
// unique: true // unique: true
Name *string `json:"name,omitempty" binding:"OmitEmpty;AlphaDashDot;MaxSize(100);"` Name *string `json:"name,omitempty" binding:"OmitEmpty;AlphaDashDot;MaxSize(100);"`
// a short description of the repository. // a short description of the repository.
Description *string `json:"description,omitempty" binding:"MaxSize(255)"` Description *string `json:"description,omitempty" binding:"MaxSize(2048)"`
// a URL with more information about the repository. // a URL with more information about the repository.
Website *string `json:"website,omitempty" binding:"MaxSize(255)"` Website *string `json:"website,omitempty" binding:"MaxSize(1024)"`
// either `true` to make the repository private or `false` to make it public. // either `true` to make the repository private or `false` to make it public.
// Note: you will get a 422 error if the organization restricts changing repository visibility to organization // Note: you will get a 422 error if the organization restricts changing repository visibility to organization
// owners and a non-owner tries to change the value of private. // owners and a non-owner tries to change the value of private.
@ -208,7 +208,7 @@ type GenerateRepoOption struct {
// Default branch of the new repository // Default branch of the new repository
DefaultBranch string `json:"default_branch"` DefaultBranch string `json:"default_branch"`
// Description of the repository to create // Description of the repository to create
Description string `json:"description" binding:"MaxSize(255)"` Description string `json:"description" binding:"MaxSize(2048)"`
// Whether the repository is private // Whether the repository is private
Private bool `json:"private"` Private bool `json:"private"`
// include git content of default branch in template repo // include git content of default branch in template repo
@ -316,7 +316,7 @@ type MigrateRepoOptions struct {
LFS bool `json:"lfs"` LFS bool `json:"lfs"`
LFSEndpoint string `json:"lfs_endpoint"` LFSEndpoint string `json:"lfs_endpoint"`
Private bool `json:"private"` Private bool `json:"private"`
Description string `json:"description" binding:"MaxSize(255)"` Description string `json:"description" binding:"MaxSize(2048)"`
Wiki bool `json:"wiki"` Wiki bool `json:"wiki"`
Milestones bool `json:"milestones"` Milestones bool `json:"milestones"`
Labels bool `json:"labels"` Labels bool `json:"labels"`

View file

@ -377,17 +377,17 @@ func NewFuncMap() []template.FuncMap {
return "" return ""
}, },
"RenderLabels": func(labels []*issues_model.Label, repoLink string) template.HTML { "RenderLabels": func(labels []*issues_model.Label, repoLink string) template.HTML {
html := `<span class="labels-list">` htmlCode := `<span class="labels-list">`
for _, label := range labels { for _, label := range labels {
// Protect against nil value in labels - shouldn't happen but would cause a panic if so // Protect against nil value in labels - shouldn't happen but would cause a panic if so
if label == nil { if label == nil {
continue continue
} }
html += fmt.Sprintf("<a href='%s/issues?labels=%d' class='ui label' style='color: %s !important; background-color: %s !important'>%s</a> ", htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d' class='ui label' style='color: %s !important; background-color: %s !important' title='%s'>%s</a> ",
repoLink, label.ID, label.ForegroundColor(), label.Color, RenderEmoji(label.Name)) repoLink, label.ID, label.ForegroundColor(), label.Color, html.EscapeString(label.Description), RenderEmoji(label.Name))
} }
html += "</span>" htmlCode += "</span>"
return template.HTML(html) return template.HTML(htmlCode)
}, },
"MermaidMaxSourceCharacters": func() int { "MermaidMaxSourceCharacters": func() int {
return setting.MermaidMaxSourceCharacters return setting.MermaidMaxSourceCharacters

View file

@ -3092,6 +3092,7 @@ container.details.platform=Plataforma
container.details.repository_site=Página web do repositório container.details.repository_site=Página web do repositório
container.details.documentation_site=Página web da documentação container.details.documentation_site=Página web da documentação
container.pull=Puxar a imagem usando a linha de comandos: container.pull=Puxar a imagem usando a linha de comandos:
container.digest=Resumo:
container.documentation=Para obter mais informações sobre o registo do Container, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/container/">a documentação</a>. container.documentation=Para obter mais informações sobre o registo do Container, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/container/">a documentação</a>.
container.multi_arch=S.O. / Arquit. container.multi_arch=S.O. / Arquit.
container.layers=Camadas de imagem container.layers=Camadas de imagem

View file

@ -759,13 +759,17 @@ func SearchTeam(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx) listOptions := utils.GetListOptions(ctx)
opts := &organization.SearchTeamOptions{ opts := &organization.SearchTeamOptions{
UserID: ctx.Doer.ID,
Keyword: ctx.FormTrim("q"), Keyword: ctx.FormTrim("q"),
OrgID: ctx.Org.Organization.ID, OrgID: ctx.Org.Organization.ID,
IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"), IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),
ListOptions: listOptions, ListOptions: listOptions,
} }
// Only admin is allowd to search for all teams
if !ctx.Doer.IsAdmin {
opts.UserID = ctx.Doer.ID
}
teams, maxResults, err := organization.SearchTeam(opts) teams, maxResults, err := organization.SearchTeam(opts)
if err != nil { if err != nil {
log.Error("SearchTeam failed: %v", err) log.Error("SearchTeam failed: %v", err)

View file

@ -24,27 +24,27 @@ import (
) )
func toBranchLink(act *activities_model.Action) string { func toBranchLink(act *activities_model.Action) string {
return act.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(act.GetBranch()) return act.GetRepoAbsoluteLink() + "/src/branch/" + util.PathEscapeSegments(act.GetBranch())
} }
func toTagLink(act *activities_model.Action) string { func toTagLink(act *activities_model.Action) string {
return act.GetRepoLink() + "/src/tag/" + util.PathEscapeSegments(act.GetTag()) return act.GetRepoAbsoluteLink() + "/src/tag/" + util.PathEscapeSegments(act.GetTag())
} }
func toIssueLink(act *activities_model.Action) string { func toIssueLink(act *activities_model.Action) string {
return act.GetRepoLink() + "/issues/" + url.PathEscape(act.GetIssueInfos()[0]) return act.GetRepoAbsoluteLink() + "/issues/" + url.PathEscape(act.GetIssueInfos()[0])
} }
func toPullLink(act *activities_model.Action) string { func toPullLink(act *activities_model.Action) string {
return act.GetRepoLink() + "/pulls/" + url.PathEscape(act.GetIssueInfos()[0]) return act.GetRepoAbsoluteLink() + "/pulls/" + url.PathEscape(act.GetIssueInfos()[0])
} }
func toSrcLink(act *activities_model.Action) string { func toSrcLink(act *activities_model.Action) string {
return act.GetRepoLink() + "/src/" + util.PathEscapeSegments(act.GetBranch()) return act.GetRepoAbsoluteLink() + "/src/" + util.PathEscapeSegments(act.GetBranch())
} }
func toReleaseLink(act *activities_model.Action) string { func toReleaseLink(act *activities_model.Action) string {
return act.GetRepoLink() + "/releases/tag/" + util.PathEscapeSegments(act.GetBranch()) return act.GetRepoAbsoluteLink() + "/releases/tag/" + util.PathEscapeSegments(act.GetBranch())
} }
// renderMarkdown creates a minimal markdown render context from an action. // renderMarkdown creates a minimal markdown render context from an action.
@ -79,17 +79,17 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
title = act.ActUser.DisplayName() + " " title = act.ActUser.DisplayName() + " "
switch act.OpType { switch act.OpType {
case activities_model.ActionCreateRepo: case activities_model.ActionCreateRepo:
title += ctx.TrHTMLEscapeArgs("action.create_repo", act.GetRepoLink(), act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.create_repo", act.GetRepoAbsoluteLink(), act.ShortRepoPath())
link.Href = act.GetRepoLink() link.Href = act.GetRepoAbsoluteLink()
case activities_model.ActionRenameRepo: case activities_model.ActionRenameRepo:
title += ctx.TrHTMLEscapeArgs("action.rename_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.rename_repo", act.GetContent(), act.GetRepoAbsoluteLink(), act.ShortRepoPath())
link.Href = act.GetRepoLink() link.Href = act.GetRepoAbsoluteLink()
case activities_model.ActionCommitRepo: case activities_model.ActionCommitRepo:
link.Href = toBranchLink(act) link.Href = toBranchLink(act)
if len(act.Content) != 0 { if len(act.Content) != 0 {
title += ctx.TrHTMLEscapeArgs("action.commit_repo", act.GetRepoLink(), link.Href, act.GetBranch(), act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.commit_repo", act.GetRepoAbsoluteLink(), link.Href, act.GetBranch(), act.ShortRepoPath())
} else { } else {
title += ctx.TrHTMLEscapeArgs("action.create_branch", act.GetRepoLink(), link.Href, act.GetBranch(), act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.create_branch", act.GetRepoAbsoluteLink(), link.Href, act.GetBranch(), act.ShortRepoPath())
} }
case activities_model.ActionCreateIssue: case activities_model.ActionCreateIssue:
link.Href = toIssueLink(act) link.Href = toIssueLink(act)
@ -98,11 +98,11 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
link.Href = toPullLink(act) link.Href = toPullLink(act)
title += ctx.TrHTMLEscapeArgs("action.create_pull_request", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.create_pull_request", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath())
case activities_model.ActionTransferRepo: case activities_model.ActionTransferRepo:
link.Href = act.GetRepoLink() link.Href = act.GetRepoAbsoluteLink()
title += ctx.TrHTMLEscapeArgs("action.transfer_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.transfer_repo", act.GetContent(), act.GetRepoAbsoluteLink(), act.ShortRepoPath())
case activities_model.ActionPushTag: case activities_model.ActionPushTag:
link.Href = toTagLink(act) link.Href = toTagLink(act)
title += ctx.TrHTMLEscapeArgs("action.push_tag", act.GetRepoLink(), link.Href, act.GetTag(), act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.push_tag", act.GetRepoAbsoluteLink(), link.Href, act.GetTag(), act.ShortRepoPath())
case activities_model.ActionCommentIssue: case activities_model.ActionCommentIssue:
issueLink := toIssueLink(act) issueLink := toIssueLink(act)
if link.Href == "#" { if link.Href == "#" {
@ -140,26 +140,26 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
} }
title += ctx.TrHTMLEscapeArgs("action.reopen_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.reopen_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath())
case activities_model.ActionDeleteTag: case activities_model.ActionDeleteTag:
link.Href = act.GetRepoLink() link.Href = act.GetRepoAbsoluteLink()
title += ctx.TrHTMLEscapeArgs("action.delete_tag", act.GetRepoLink(), act.GetTag(), act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.delete_tag", act.GetRepoAbsoluteLink(), act.GetTag(), act.ShortRepoPath())
case activities_model.ActionDeleteBranch: case activities_model.ActionDeleteBranch:
link.Href = act.GetRepoLink() link.Href = act.GetRepoAbsoluteLink()
title += ctx.TrHTMLEscapeArgs("action.delete_branch", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.delete_branch", act.GetRepoAbsoluteLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath())
case activities_model.ActionMirrorSyncPush: case activities_model.ActionMirrorSyncPush:
srcLink := toSrcLink(act) srcLink := toSrcLink(act)
if link.Href == "#" { if link.Href == "#" {
link.Href = srcLink link.Href = srcLink
} }
title += ctx.TrHTMLEscapeArgs("action.mirror_sync_push", act.GetRepoLink(), srcLink, act.GetBranch(), act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.mirror_sync_push", act.GetRepoAbsoluteLink(), srcLink, act.GetBranch(), act.ShortRepoPath())
case activities_model.ActionMirrorSyncCreate: case activities_model.ActionMirrorSyncCreate:
srcLink := toSrcLink(act) srcLink := toSrcLink(act)
if link.Href == "#" { if link.Href == "#" {
link.Href = srcLink link.Href = srcLink
} }
title += ctx.TrHTMLEscapeArgs("action.mirror_sync_create", act.GetRepoLink(), srcLink, act.GetBranch(), act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.mirror_sync_create", act.GetRepoAbsoluteLink(), srcLink, act.GetBranch(), act.ShortRepoPath())
case activities_model.ActionMirrorSyncDelete: case activities_model.ActionMirrorSyncDelete:
link.Href = act.GetRepoLink() link.Href = act.GetRepoAbsoluteLink()
title += ctx.TrHTMLEscapeArgs("action.mirror_sync_delete", act.GetRepoLink(), act.GetBranch(), act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.mirror_sync_delete", act.GetRepoAbsoluteLink(), act.GetBranch(), act.ShortRepoPath())
case activities_model.ActionApprovePullRequest: case activities_model.ActionApprovePullRequest:
pullLink := toPullLink(act) pullLink := toPullLink(act)
title += ctx.TrHTMLEscapeArgs("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath()) title += ctx.TrHTMLEscapeArgs("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath())
@ -174,16 +174,16 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
if link.Href == "#" { if link.Href == "#" {
link.Href = releaseLink link.Href = releaseLink
} }
title += ctx.TrHTMLEscapeArgs("action.publish_release", act.GetRepoLink(), releaseLink, act.ShortRepoPath(), act.Content) title += ctx.TrHTMLEscapeArgs("action.publish_release", act.GetRepoAbsoluteLink(), releaseLink, act.ShortRepoPath(), act.Content)
case activities_model.ActionPullReviewDismissed: case activities_model.ActionPullReviewDismissed:
pullLink := toPullLink(act) pullLink := toPullLink(act)
title += ctx.TrHTMLEscapeArgs("action.review_dismissed", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(), act.GetIssueInfos()[1]) title += ctx.TrHTMLEscapeArgs("action.review_dismissed", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(), act.GetIssueInfos()[1])
case activities_model.ActionStarRepo: case activities_model.ActionStarRepo:
link.Href = act.GetRepoLink() link.Href = act.GetRepoAbsoluteLink()
title += ctx.TrHTMLEscapeArgs("action.starred_repo", act.GetRepoLink(), act.GetRepoPath()) title += ctx.TrHTMLEscapeArgs("action.starred_repo", act.GetRepoAbsoluteLink(), act.GetRepoPath())
case activities_model.ActionWatchRepo: case activities_model.ActionWatchRepo:
link.Href = act.GetRepoLink() link.Href = act.GetRepoAbsoluteLink()
title += ctx.TrHTMLEscapeArgs("action.watched_repo", act.GetRepoLink(), act.GetRepoPath()) title += ctx.TrHTMLEscapeArgs("action.watched_repo", act.GetRepoAbsoluteLink(), act.GetRepoPath())
default: default:
return nil, fmt.Errorf("unknown action type: %v", act.OpType) return nil, fmt.Errorf("unknown action type: %v", act.OpType)
} }
@ -193,14 +193,14 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
switch act.OpType { switch act.OpType {
case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush: case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush:
push := templates.ActionContent2Commits(act) push := templates.ActionContent2Commits(act)
repoLink := act.GetRepoLink() repoLink := act.GetRepoAbsoluteLink()
for _, commit := range push.Commits { for _, commit := range push.Commits {
if len(desc) != 0 { if len(desc) != 0 {
desc += "\n\n" desc += "\n\n"
} }
desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s", desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), commit.Sha1)), html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(), commit.Sha1)),
commit.Sha1, commit.Sha1,
templates.RenderCommitMessage(ctx, commit.Message, repoLink, nil), templates.RenderCommitMessage(ctx, commit.Message, repoLink, nil),
) )
@ -209,7 +209,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
if push.Len > 1 { if push.Len > 1 {
link = &feeds.Link{Href: fmt.Sprintf("%s/%s", setting.AppSubURL, push.CompareURL)} link = &feeds.Link{Href: fmt.Sprintf("%s/%s", setting.AppSubURL, push.CompareURL)}
} else if push.Len == 1 { } else if push.Len == 1 {
link = &feeds.Link{Href: fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), push.Commits[0].Sha1)} link = &feeds.Link{Href: fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(), push.Commits[0].Sha1)}
} }
case activities_model.ActionCreateIssue, activities_model.ActionCreatePullRequest: case activities_model.ActionCreateIssue, activities_model.ActionCreatePullRequest:

View file

@ -112,17 +112,17 @@ func setCsvCompareContext(ctx *context.Context) {
Error string Error string
} }
ctx.Data["CreateCsvDiff"] = func(diffFile *gitdiff.DiffFile, baseCommit, headCommit *git.Commit) CsvDiffResult { ctx.Data["CreateCsvDiff"] = func(diffFile *gitdiff.DiffFile, baseBlob, headBlob *git.Blob) CsvDiffResult {
if diffFile == nil || baseCommit == nil || headCommit == nil { if diffFile == nil {
return CsvDiffResult{nil, ""} return CsvDiffResult{nil, ""}
} }
errTooLarge := errors.New(ctx.Locale.Tr("repo.error.csv.too_large")) errTooLarge := errors.New(ctx.Locale.Tr("repo.error.csv.too_large"))
csvReaderFromCommit := func(ctx *markup.RenderContext, c *git.Commit) (*csv.Reader, io.Closer, error) { csvReaderFromCommit := func(ctx *markup.RenderContext, blob *git.Blob) (*csv.Reader, io.Closer, error) {
blob, err := c.GetBlobByPath(diffFile.Name) if blob == nil {
if err != nil { // It's ok for blob to be nil (file added or deleted)
return nil, nil, err return nil, nil, nil
} }
if setting.UI.CSV.MaxFileSize != 0 && setting.UI.CSV.MaxFileSize < blob.Size() { if setting.UI.CSV.MaxFileSize != 0 && setting.UI.CSV.MaxFileSize < blob.Size() {
@ -138,28 +138,28 @@ func setCsvCompareContext(ctx *context.Context) {
return csvReader, reader, err return csvReader, reader, err
} }
baseReader, baseBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, RelativePath: diffFile.OldName}, baseCommit) baseReader, baseBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, RelativePath: diffFile.OldName}, baseBlob)
if baseBlobCloser != nil { if baseBlobCloser != nil {
defer baseBlobCloser.Close() defer baseBlobCloser.Close()
} }
if err == errTooLarge {
return CsvDiffResult{nil, err.Error()}
}
if err != nil { if err != nil {
log.Error("CreateCsvDiff error whilst creating baseReader from file %s in commit %s in %s: %v", diffFile.Name, baseCommit.ID.String(), ctx.Repo.Repository.Name, err) if err == errTooLarge {
return CsvDiffResult{nil, "unable to load file from base commit"} return CsvDiffResult{nil, err.Error()}
}
log.Error("error whilst creating csv.Reader from file %s in base commit %s in %s: %v", diffFile.Name, baseBlob.ID.String(), ctx.Repo.Repository.Name, err)
return CsvDiffResult{nil, "unable to load file"}
} }
headReader, headBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, RelativePath: diffFile.Name}, headCommit) headReader, headBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, RelativePath: diffFile.Name}, headBlob)
if headBlobCloser != nil { if headBlobCloser != nil {
defer headBlobCloser.Close() defer headBlobCloser.Close()
} }
if err == errTooLarge {
return CsvDiffResult{nil, err.Error()}
}
if err != nil { if err != nil {
log.Error("CreateCsvDiff error whilst creating headReader from file %s in commit %s in %s: %v", diffFile.Name, headCommit.ID.String(), ctx.Repo.Repository.Name, err) if err == errTooLarge {
return CsvDiffResult{nil, "unable to load file from head commit"} return CsvDiffResult{nil, err.Error()}
}
log.Error("error whilst creating csv.Reader from file %s in head commit %s in %s: %v", diffFile.Name, headBlob.ID.String(), ctx.Repo.Repository.Name, err)
return CsvDiffResult{nil, "unable to load file"}
} }
sections, err := gitdiff.CreateCsvDiff(diffFile, baseReader, headReader) sections, err := gitdiff.CreateCsvDiff(diffFile, baseReader, headReader)

View file

@ -34,7 +34,7 @@ type CreateRepoForm struct {
UID int64 `binding:"Required"` UID int64 `binding:"Required"`
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
Private bool Private bool
Description string `binding:"MaxSize(255)"` Description string `binding:"MaxSize(2048)"`
DefaultBranch string `binding:"GitRefName;MaxSize(100)"` DefaultBranch string `binding:"GitRefName;MaxSize(100)"`
AutoInit bool AutoInit bool
Gitignores string Gitignores string
@ -76,7 +76,7 @@ type MigrateRepoForm struct {
LFS bool `json:"lfs"` LFS bool `json:"lfs"`
LFSEndpoint string `json:"lfs_endpoint"` LFSEndpoint string `json:"lfs_endpoint"`
Private bool `json:"private"` Private bool `json:"private"`
Description string `json:"description" binding:"MaxSize(255)"` Description string `json:"description" binding:"MaxSize(2048)"`
Wiki bool `json:"wiki"` Wiki bool `json:"wiki"`
Milestones bool `json:"milestones"` Milestones bool `json:"milestones"`
Labels bool `json:"labels"` Labels bool `json:"labels"`
@ -116,8 +116,8 @@ func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, err
// RepoSettingForm form for changing repository settings // RepoSettingForm form for changing repository settings
type RepoSettingForm struct { type RepoSettingForm struct {
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
Description string `binding:"MaxSize(255)"` Description string `binding:"MaxSize(2048)"`
Website string `binding:"ValidUrl;MaxSize(255)"` Website string `binding:"ValidUrl;MaxSize(1024)"`
Interval string Interval string
MirrorAddress string MirrorAddress string
MirrorUsername string MirrorUsername string

View file

@ -157,7 +157,7 @@
<tbody> <tbody>
{{range .Queue.Workers}} {{range .Queue.Workers}}
<tr> <tr>
<td>{{.Workers}}{{if .IsFlusher}}<span title="{{.locale.Tr "admin.monitor.queue.flush"}}">{{svg "octicon-sync"}}</span>{{end}}</td> <td>{{.Workers}}{{if .IsFlusher}}<span title="{{$.locale.Tr "admin.monitor.queue.flush"}}">{{svg "octicon-sync"}}</span>{{end}}</td>
<td>{{DateFmtLong .Start}}</td> <td>{{DateFmtLong .Start}}</td>
<td>{{if .HasTimeout}}{{DateFmtLong .Timeout}}{{else}}-{{end}}</td> <td>{{if .HasTimeout}}{{DateFmtLong .Timeout}}{{else}}-{{end}}</td>
<td> <td>

View file

@ -57,7 +57,8 @@
{{end}} {{end}}
</ol> </ol>
<div id="diff-file-boxes"> <div id="diff-file-boxes">
{{range $i, $file := .Diff.Files}} {{range $file := .Diff.Files}}
{{/*notice: the index of Diff.Files should not be used for element ID, because the index will be restarted from 0 when doing load-more for PRs with a lot of files*/}}
{{$blobBase := call $.GetBlobByPathForCommit $.BaseCommit $file.OldName}} {{$blobBase := call $.GetBlobByPathForCommit $.BaseCommit $file.OldName}}
{{$blobHead := call $.GetBlobByPathForCommit $.HeadCommit $file.Name}} {{$blobHead := call $.GetBlobByPathForCommit $.HeadCommit $file.Name}}
{{$isImage := or (call $.IsBlobAnImage $blobBase) (call $.IsBlobAnImage $blobHead)}} {{$isImage := or (call $.IsBlobAnImage $blobBase) (call $.IsBlobAnImage $blobHead)}}
@ -93,8 +94,8 @@
<div class="diff-file-header-actions df ac"> <div class="diff-file-header-actions df ac">
{{if $showFileViewToggle}} {{if $showFileViewToggle}}
<div class="ui compact icon buttons"> <div class="ui compact icon buttons">
<span class="ui tiny basic button tooltip file-view-toggle" data-toggle-selector="#diff-source-{{$i}}" data-content="{{$.locale.Tr "repo.file_view_source"}}" data-position="bottom center">{{svg "octicon-code"}}</span> <span class="ui tiny basic button tooltip file-view-toggle" data-toggle-selector="#diff-source-{{$file.NameHash}}" data-content="{{$.locale.Tr "repo.file_view_source"}}" data-position="bottom center">{{svg "octicon-code"}}</span>
<span class="ui tiny basic button tooltip file-view-toggle active" data-toggle-selector="#diff-rendered-{{$i}}" data-content="{{$.locale.Tr "repo.file_view_rendered"}}" data-position="bottom center">{{svg "octicon-file"}}</span> <span class="ui tiny basic button tooltip file-view-toggle active" data-toggle-selector="#diff-rendered-{{$file.NameHash}}" data-content="{{$.locale.Tr "repo.file_view_rendered"}}" data-position="bottom center">{{svg "octicon-file"}}</span>
</div> </div>
{{end}} {{end}}
{{if $file.IsProtected}} {{if $file.IsProtected}}
@ -115,15 +116,14 @@
{{if $file.HasChangedSinceLastReview}} {{if $file.HasChangedSinceLastReview}}
<span class="changed-since-last-review unselectable">{{$.locale.Tr "repo.pulls.has_changed_since_last_review"}}</span> <span class="changed-since-last-review unselectable">{{$.locale.Tr "repo.pulls.has_changed_since_last_review"}}</span>
{{end}} {{end}}
<div data-link="{{$.Issue.Link}}/viewed-files" data-headcommit="{{$.PullHeadCommitID}}" class="viewed-file-form unselectable{{if $file.IsViewed}} viewed-file-checked-form{{end}}"> <label data-link="{{$.Issue.Link}}/viewed-files" data-headcommit="{{$.PullHeadCommitID}}" class="viewed-file-form unselectable{{if $file.IsViewed}} viewed-file-checked-form{{end}}">
<input type="checkbox" name="{{$file.GetDiffFileName}}" id="viewed-file-checkbox-{{$i}}" autocomplete="off" {{if $file.IsViewed}}checked{{end}}></input> <input type="checkbox" name="{{$file.GetDiffFileName}}" autocomplete="off"{{if $file.IsViewed}} checked{{end}}> {{$.locale.Tr "repo.pulls.has_viewed_file"}}
<label for="viewed-file-checkbox-{{$i}}">{{$.locale.Tr "repo.pulls.has_viewed_file"}}</label> </label>
</div>
{{end}} {{end}}
</div> </div>
</h4> </h4>
<div class="diff-file-body ui attached unstackable table segment" {{if $file.IsViewed}}data-folded="true"{{end}}> <div class="diff-file-body ui attached unstackable table segment" {{if $file.IsViewed}}data-folded="true"{{end}}>
<div id="diff-source-{{$i}}" class="file-body file-code unicode-escaped code-diff{{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}{{if $showFileViewToggle}} hide{{end}}"> <div id="diff-source-{{$file.NameHash}}" class="file-body file-code unicode-escaped code-diff{{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}{{if $showFileViewToggle}} hide{{end}}">
{{if or $file.IsIncomplete $file.IsBin}} {{if or $file.IsIncomplete $file.IsBin}}
<div class="diff-file-body binary" style="padding: 5px 10px;"> <div class="diff-file-body binary" style="padding: 5px 10px;">
{{if $file.IsIncomplete}} {{if $file.IsIncomplete}}
@ -148,12 +148,12 @@
{{end}} {{end}}
</div> </div>
{{if $showFileViewToggle}} {{if $showFileViewToggle}}
<div id="diff-rendered-{{$i}}" class="file-body file-code {{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}"> <div id="diff-rendered-{{$file.NameHash}}" class="file-body file-code {{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}">
<table class="chroma w-100"> <table class="chroma w-100">
{{if $isImage}} {{if $isImage}}
{{template "repo/diff/image_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead}} {{template "repo/diff/image_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead}}
{{else}} {{else}}
{{template "repo/diff/csv_diff" dict "file" . "root" $}} {{template "repo/diff/csv_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead}}
{{end}} {{end}}
</table> </table>
</div> </div>

View file

@ -1,6 +1,6 @@
<tr> <tr>
<td> <td>
{{$result := call .root.CreateCsvDiff .file .root.BaseCommit .root.HeadCommit}} {{$result := call .root.CreateCsvDiff .file .blobBase .blobHead}}
{{if $result.Error}} {{if $result.Error}}
<div class="ui center">{{$result.Error}}</div> <div class="ui center">{{$result.Error}}</div>
{{else if $result.Sections}} {{else if $result.Sections}}

View file

@ -117,7 +117,6 @@
{{if eq $n 0}} {{if eq $n 0}}
<div class="ui action tiny input" id="clone-panel"> <div class="ui action tiny input" id="clone-panel">
{{template "repo/clone_buttons" .}} {{template "repo/clone_buttons" .}}
{{template "repo/clone_script" .}}
<button id="download-btn" class="ui basic small compact jump dropdown icon button tooltip" data-content="{{.locale.Tr "repo.download_archive"}}" data-position="top right"> <button id="download-btn" class="ui basic small compact jump dropdown icon button tooltip" data-content="{{.locale.Tr "repo.download_archive"}}" data-position="top right">
{{svg "octicon-download"}} {{svg "octicon-download"}}
<div class="menu"> <div class="menu">
@ -129,6 +128,7 @@
<a class="item js-clone-url-vsc" href="vscode://vscode.git/clone?url={{.CloneButtonOriginLink.HTTPS}}">{{svg "gitea-vscode" 16 "mr-3"}}{{.locale.Tr "repo.clone_in_vsc"}}</a> <a class="item js-clone-url-vsc" href="vscode://vscode.git/clone?url={{.CloneButtonOriginLink.HTTPS}}">{{svg "gitea-vscode" 16 "mr-3"}}{{.locale.Tr "repo.clone_in_vsc"}}</a>
</div> </div>
</button> </button>
{{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}}
</div> </div>
{{end}} {{end}}
{{if and (ne $n 0) (not .IsViewFile) (not .IsBlame)}} {{if and (ne $n 0) (not .IsViewFile) (not .IsBlame)}}

View file

@ -42,11 +42,11 @@
{{end}} {{end}}
<div class="field {{if .Err_Description}}error{{end}}"> <div class="field {{if .Err_Description}}error{{end}}">
<label for="description">{{$.locale.Tr "repo.repo_desc"}}</label> <label for="description">{{$.locale.Tr "repo.repo_desc"}}</label>
<textarea id="description" name="description" rows="2">{{.Repository.Description}}</textarea> <textarea id="description" name="description" rows="2" maxlength="2048">{{.Repository.Description}}</textarea>
</div> </div>
<div class="field {{if .Err_Website}}error{{end}}"> <div class="field {{if .Err_Website}}error{{end}}">
<label for="website">{{.locale.Tr "repo.settings.site"}}</label> <label for="website">{{.locale.Tr "repo.settings.site"}}</label>
<input id="website" name="website" type="url" value="{{.Repository.Website}}"> <input id="website" name="website" type="url" maxlength="1024" value="{{.Repository.Website}}">
</div> </div>
<div class="field"> <div class="field">

View file

@ -72,8 +72,8 @@ func TestMain(m *testing.M) {
os.Exit(exitVal) os.Exit(exitVal)
} }
// This should be the only test e2e necessary. It will collect all "*.test.e2e.js" // TestE2e should be the only test e2e necessary. It will collect all "*.test.e2e.js"
// files in this directory and build a test for each. // files in this directory and build a test for each.
func TestE2e(t *testing.T) { func TestE2e(t *testing.T) {
// Find the paths of all e2e test files in test test directory. // Find the paths of all e2e test files in test test directory.
searchGlob := filepath.Join(filepath.Dir(setting.AppPath), "tests", "e2e", "*.test.e2e.js") searchGlob := filepath.Join(filepath.Dir(setting.AppPath), "tests", "e2e", "*.test.e2e.js")

View file

@ -32,7 +32,7 @@ func TestNodeinfo(t *testing.T) {
DecodeJSON(t, resp, &nodeinfo) DecodeJSON(t, resp, &nodeinfo)
assert.True(t, nodeinfo.OpenRegistrations) assert.True(t, nodeinfo.OpenRegistrations)
assert.Equal(t, "gitea", nodeinfo.Software.Name) assert.Equal(t, "gitea", nodeinfo.Software.Name)
assert.Equal(t, 23, nodeinfo.Usage.Users.Total) assert.Equal(t, 24, nodeinfo.Usage.Users.Total)
assert.Equal(t, 17, nodeinfo.Usage.LocalPosts) assert.Equal(t, 17, nodeinfo.Usage.LocalPosts)
assert.Equal(t, 2, nodeinfo.Usage.LocalComments) assert.Equal(t, 2, nodeinfo.Usage.LocalComments)
}) })

View file

@ -5,6 +5,7 @@
package integration package integration
import ( import (
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@ -151,3 +152,38 @@ func TestAPIGetAll(t *testing.T) {
assert.Equal(t, "org25", apiOrgList[0].FullName) assert.Equal(t, "org25", apiOrgList[0].FullName)
assert.Equal(t, "public", apiOrgList[0].Visibility) assert.Equal(t, "public", apiOrgList[0].Visibility)
} }
func TestAPIOrgSearchEmptyTeam(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
token := getUserToken(t, "user1")
orgName := "org_with_empty_team"
// create org
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &api.CreateOrgOption{
UserName: orgName,
})
MakeRequest(t, req, http.StatusCreated)
// create team with no member
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", orgName, token), &api.CreateTeamOption{
Name: "Empty",
IncludesAllRepositories: true,
Permission: "read",
Units: []string{"repo.code", "repo.issues", "repo.ext_issues", "repo.wiki", "repo.pulls"},
})
MakeRequest(t, req, http.StatusCreated)
// case-insensitive search for teams that have no members
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/teams/search?q=%s&token=%s", orgName, "empty", token))
resp := MakeRequest(t, req, http.StatusOK)
data := struct {
Ok bool
Data []*api.Team
}{}
DecodeJSON(t, resp, &data)
assert.True(t, data.Ok)
if assert.Len(t, data.Data, 1) {
assert.EqualValues(t, "Empty", data.Data[0].Name)
}
})
}

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -126,6 +127,18 @@ func TestPackageGeneric(t *testing.T) {
req := NewRequest(t, "GET", url+"/not.found") req := NewRequest(t, "GET", url+"/not.found")
MakeRequest(t, req, http.StatusNotFound) MakeRequest(t, req, http.StatusNotFound)
}) })
t.Run("RequireSignInView", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
setting.Service.RequireSignInView = true
defer func() {
setting.Service.RequireSignInView = false
}()
req = NewRequest(t, "GET", url+"/dummy.bin")
MakeRequest(t, req, http.StatusUnauthorized)
})
}) })
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {

View file

@ -737,6 +737,13 @@ a.ui.card:hover,
padding-bottom: 80px; padding-bottom: 80px;
} }
/* enable fluid page widths for medium size viewports */
@media @mediaMdAndUp and @mediaLgAndDown {
.ui.ui.ui.container:not(.fluid) {
width: calc(100vw - 3em);
}
}
.following.bar { .following.bar {
z-index: 900; z-index: 900;
left: 0; left: 0;

View file

@ -409,10 +409,6 @@
font-size: .5em; font-size: .5em;
} }
.file-info {
font-size: 13px;
}
.file-actions { .file-actions {
.btn-octicon { .btn-octicon {
line-height: 1; line-height: 1;
@ -3051,7 +3047,8 @@ td.blob-excerpt {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
overflow-x: auto; overflow-x: auto;
padding: 8px 12px !important; padding: 6px 12px !important;
font-size: 13px !important;
} }
.file-info { .file-info {

View file

@ -272,13 +272,22 @@ a.blob-excerpt:hover {
} }
.viewed-file-form { .viewed-file-form {
margin: 0 3px; display: flex;
padding: 0 3px; align-items: center;
border-radius: 3px; border: 1px none;
padding: 4px 8px;
margin: -8px 0; // just like other buttons in the diff box header
border-radius: .285rem; // just like .ui.tiny.button
font-size: .857rem; // just like .ui.tiny.button
}
.viewed-file-form input {
margin-right: 4px;
} }
.viewed-file-checked-form { .viewed-file-checked-form {
background-color: var(--color-primary-light-4); background-color: var(--color-primary-light-6);
border: 1px solid var(--color-primary-light-4);
} }
#viewed-files-summary { #viewed-files-summary {