Compare commits

..

14 Commits

Author SHA1 Message Date
Giteabot
1d9ae7ac23 Load jQuery as early as possible to support custom scripts (#35926) (#35929)
Backport #35926 by wxiaoguang

Fix #35923

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-12 07:24:21 +08:00
Giteabot
01873a99c1 Allow to display embed images/pdfs when SERVE_DIRECT was enabled on MinIO storage (#35882) (#35917)
Backport #35882 by lifegpc

Co-authored-by: lifegpc <g1710431395@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-11 02:24:06 +00:00
Giteabot
ce70863793 Use correct form field for allowed force push users in branch protection API (#35894) (#35908)
Backport #35894 by zorrobiwan

Signed-off-by: Alberty Pascal <github@albertyorban.be>
Co-authored-by: Alberty Pascal <github@albertyorban.be>
2025-11-11 01:39:35 +00:00
Giteabot
327f2207dc Make OAuth2 issuer configurable (#35915) (#35916)
Backport #35915 by wxiaoguang
2025-11-10 16:12:25 +00:00
Giteabot
db876d8f17 Fix #35763: Add proper page title for project pages (#35773) (#35909)
Backport #35773 by @mithileshgupta12

Co-authored-by: Mithilesh Gupta <mithileshgupta059@gmail.com>
Co-authored-by: Mithilesh Gupta <guptamithilesh@protonmail.com>
2025-11-10 09:42:14 +02:00
Giteabot
2b71bf283b Display source code downloads last for release attachments (#35897) (#35903)
Backport #35897 by lutinglt

Typically, you want to download the binaries, not the source code.

Co-authored-by: 鲁汀 <131967983+lutinglt@users.noreply.github.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-11-09 14:51:36 +08:00
Giteabot
1ca4fef611 Fix team member access check (#35899) (#35905)
Backport #35899 by wxiaoguang

Fix #35499

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-09 03:44:53 +00:00
Giteabot
70ee6b9029 Fix conda null depend issue (#35900) (#35902)
Backport #35900 by Luohaothu

This fixes issue #35895

Co-authored-by: Luohao Wang <luohaothu@live.com>
2025-11-08 16:37:00 +00:00
wxiaoguang
e5b404ec53 Fix avatar upload error handling (#35887) (#35890)
Backport #35887
2025-11-07 11:25:34 +08:00
Giteabot
5842cd23a6 Contribution heatmap improvements (#35876) (#35880)
Backport #35876 by @silverwind

1. Set a fixed height on the element, preventing the content after the
element from shifting on page load. This uses CSS [container query
length
units](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries#container_query_length_units)
as I saw no other way because of the non-linear scaling of the element.
2. Move the "total-contributions" text into the existing vue slot,
eliminating the need for absolute positioning.

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-06 08:51:49 +00:00
Giteabot
289bd9694b Remove padding override on .ui .sha.label (#35864) (#35873)
Backport #35864 by @silverwind

Since upgrading to v1.25, I noticed the SHA labels have slightly
different padding than before. I can't pinpoint exactly which change it
was. Fix it by removing the padding override on `.ui .sha.label` and
make the one on`.ui.label` (`2px 6px`) take effect which matches 1.24
rendering.

Before:

<img width="135" height="172" alt="image"
src="https://github.com/user-attachments/assets/2781a854-be08-4a11-bde0-d3699b2b7454"
/>

After:

<img width="139" height="162" alt="image"
src="https://github.com/user-attachments/assets/5c864fa3-c1f9-4452-ae58-5411dd445865"
/>

Co-authored-by: silverwind <me@silverwind.io>
2025-11-06 06:06:36 +00:00
Giteabot
154d7521a5 fix(api/repo/contents): set the dates to now when not specified by the caller (#35861) (#35874)
Backport #35861 by @divyun

Since 1.25.0, the dates get set to `2001-01-01T00:00:00Z`, when not
specified by the caller.

Fixes #35860

Co-authored-by: Divyun Raje Vaid <mail@divyun.com>
2025-11-06 13:08:06 +08:00
Giteabot
24189dcced Fix pull description code label background (#35865) (#35870)
Backport #35865 by @silverwind

Fix visual regression from https://github.com/go-gitea/gitea/pull/35567:

Before:

<img width="612" height="33" alt="image"
src="https://github.com/user-attachments/assets/aee4017c-b8b9-4ac2-9809-9d3eb3fda56c"
/>

After:

<img width="613" height="32" alt="image"
src="https://github.com/user-attachments/assets/ee6624da-b417-4e3b-8773-88c77c2cd672"
/>

Co-authored-by: silverwind <me@silverwind.io>
2025-11-05 17:29:06 +00:00
wxiaoguang
f84bf259ad Fix gogit ListEntriesRecursiveWithSize (#35862)
It needs to use full git path. Fix #35852.
2025-11-05 19:19:47 +02:00
30 changed files with 224 additions and 136 deletions

View File

@@ -567,6 +567,11 @@ ENABLED = true
;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one
;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret
;;
;; The "issuer" claim identifies the principal that issued the JWT.
;; Gitea 1.25 makes it default to "ROOT_URL without the last slash" to follow the standard.
;; If you have old logins from before 1.25, you may want to set it to the old (non-standard) value "ROOT_URL with the last slash".
;JWT_CLAIM_ISSUER =
;;
;; Lifetime of an OAuth2 access token in seconds
;ACCESS_TOKEN_EXPIRATION_TIME = 3600
;;

View File

@@ -466,11 +466,13 @@ func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, c
return currentWhitelist, nil
}
prUserIDs, err := access_model.GetUserIDsWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
if err != nil {
return nil, err
}
whitelist = make([]int64, 0, len(newWhitelist))
for _, userID := range newWhitelist {
if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil {
return nil, err
} else if !reader {
if !prUserIDs.Contains(userID) {
continue
}
whitelist = append(whitelist, userID)

View File

@@ -53,24 +53,45 @@ func RemoveTeamRepo(ctx context.Context, teamID, repoID int64) error {
// GetTeamsWithAccessToAnyRepoUnit returns all teams in an organization that have given access level to the repository special unit.
// This function is only used for finding some teams that can be used as branch protection allowlist or reviewers, it isn't really used for access control.
// FIXME: TEAM-UNIT-PERMISSION this logic is not complete, search the fixme keyword to see more details
func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) ([]*Team, error) {
teams := make([]*Team, 0, 5)
func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teams []*Team, err error) {
teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...)
if err != nil {
return nil, err
}
if len(teamIDs) == 0 {
return teams, nil
}
err = db.GetEngine(ctx).Where(builder.In("id", teamIDs)).OrderBy("team.name").Find(&teams)
return teams, err
}
func getTeamIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teamIDs []int64, err error) {
sub := builder.Select("team_id").From("team_unit").
Where(builder.Expr("team_unit.team_id = team.id")).
And(builder.In("team_unit.type", append([]unit.Type{unitType}, unitTypesMore...))).
And(builder.Expr("team_unit.access_mode >= ?", mode))
err := db.GetEngine(ctx).
err = db.GetEngine(ctx).
Select("team.id").
Table("team").
Join("INNER", "team_repo", "team_repo.team_id = team.id").
And("team_repo.org_id = ?", orgID).
And("team_repo.repo_id = ?", repoID).
And("team_repo.org_id = ? AND team_repo.repo_id = ?", orgID, repoID).
And(builder.Or(
builder.Expr("team.authorize >= ?", mode),
builder.In("team.id", sub),
)).
OrderBy("name").
Find(&teams)
return teams, err
Find(&teamIDs)
return teamIDs, err
}
func GetTeamUserIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (userIDs []int64, err error) {
teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...)
if err != nil {
return nil, err
}
if len(teamIDs) == 0 {
return userIDs, nil
}
err = db.GetEngine(ctx).Table("team_user").Select("uid").Where(builder.In("team_id", teamIDs)).Find(&userIDs)
return userIDs, err
}

View File

@@ -14,6 +14,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -458,54 +459,44 @@ func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Reposi
return perm.HasAnyUnitAccess(), nil
}
// getUsersWithAccessMode returns users that have at least given access mode to the repository.
func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode) (_ []*user_model.User, err error) {
if err = repo.LoadOwner(ctx); err != nil {
func GetUsersWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (users []*user_model.User, err error) {
userIDs, err := GetUserIDsWithUnitAccess(ctx, repo, mode, unitType)
if err != nil {
return nil, err
}
e := db.GetEngine(ctx)
accesses := make([]*Access, 0, 10)
if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
if len(userIDs) == 0 {
return users, nil
}
if err = db.GetEngine(ctx).In("id", userIDs.Values()).OrderBy("`name`").Find(&users); err != nil {
return nil, err
}
// Leave a seat for owner itself to append later, but if owner is an organization
// and just waste 1 unit is cheaper than re-allocate memory once.
users := make([]*user_model.User, 0, len(accesses)+1)
if len(accesses) > 0 {
userIDs := make([]int64, len(accesses))
for i := 0; i < len(accesses); i++ {
userIDs[i] = accesses[i].UserID
}
if err = e.In("id", userIDs).Find(&users); err != nil {
return nil, err
}
}
if !repo.Owner.IsOrganization() {
users = append(users, repo.Owner)
}
return users, nil
}
// GetRepoReaders returns all users that have explicit read access or higher to the repository.
func GetRepoReaders(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeRead)
func GetUserIDsWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (container.Set[int64], error) {
userIDs := container.Set[int64]{}
e := db.GetEngine(ctx)
accesses := make([]*Access, 0, 10)
if err := e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
return nil, err
}
for _, a := range accesses {
userIDs.Add(a.UserID)
}
// GetRepoWriters returns all users that have write access to the repository.
func GetRepoWriters(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeWrite)
if err := repo.LoadOwner(ctx); err != nil {
return nil, err
}
// IsRepoReader returns true if user has explicit read access or higher to the repository.
func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64) (bool, error) {
if repo.OwnerID == userID {
return true, nil
if !repo.Owner.IsOrganization() {
userIDs.Add(repo.Owner.ID)
} else {
teamUserIDs, err := organization.GetTeamUserIDsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, mode, unitType)
if err != nil {
return nil, err
}
return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{})
userIDs.AddMultiple(teamUserIDs...)
}
return userIDs, nil
}
// CheckRepoUnitUser check whether user could visit the unit of this repository

View File

@@ -169,9 +169,9 @@ func TestGetUserRepoPermission(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
team := &organization.Team{OrgID: org.ID, LowerName: "test_team"}
require.NoError(t, db.Insert(ctx, team))
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
t.Run("DoerInTeamWithNoRepo", func(t *testing.T) {
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
perm, err := GetUserRepoPermission(ctx, repo32, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
@@ -219,6 +219,15 @@ func TestGetUserRepoPermission(t *testing.T) {
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeRead, unit.TypeIssues)
require.NoError(t, err)
require.Len(t, users, 1)
assert.Equal(t, user.ID, users[0].ID)
users, err = GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues)
require.NoError(t, err)
require.Empty(t, users)
})
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
@@ -229,5 +238,10 @@ func TestGetUserRepoPermission(t *testing.T) {
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues])
users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues)
require.NoError(t, err)
require.Len(t, users, 1)
assert.Equal(t, user.ID, users[0].ID)
})
}

View File

@@ -19,12 +19,17 @@ type TreeEntry struct {
gogitTreeEntry *object.TreeEntry
ptree *Tree
fullName string
size int64
sized bool
}
// Name returns the name of the entry
func (te *TreeEntry) Name() string {
if te.fullName != "" {
return te.fullName
}
return te.gogitTreeEntry.Name
}

View File

@@ -69,7 +69,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
seen := map[plumbing.Hash]bool{}
walker := object.NewTreeWalker(t.gogitTree, true, seen)
for {
_, entry, err := walker.Next()
fullName, entry, err := walker.Next()
if err == io.EOF {
break
}
@@ -84,6 +84,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
ID: ParseGogitHash(entry.Hash),
gogitTreeEntry: &entry,
ptree: t,
fullName: fullName,
}
entries = append(entries, convertedEntry)
}

View File

@@ -96,6 +96,7 @@ var OAuth2 = struct {
InvalidateRefreshTokens bool
JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"`
JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"`
JWTClaimIssuer string `ini:"JWT_CLAIM_ISSUER"`
MaxTokenLength int
DefaultApplications []string
}{

View File

@@ -250,6 +250,7 @@ func (a *AzureBlobStorage) Delete(path string) error {
func (a *AzureBlobStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) {
blobClient := a.getBlobClient(path)
// TODO: OBJECT-STORAGE-CONTENT-TYPE: "browser inline rendering images/PDF" needs proper Content-Type header from storage
startTime := time.Now()
u, err := blobClient.GetSASURL(sas.BlobPermissions{
Read: true,

View File

@@ -279,20 +279,44 @@ func (m *MinioStorage) Delete(path string) error {
}
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
func (m *MinioStorage) URL(path, name, method string, serveDirectReqParams url.Values) (*url.URL, error) {
func (m *MinioStorage) URL(storePath, name, method string, serveDirectReqParams url.Values) (*url.URL, error) {
// copy serveDirectReqParams
reqParams, err := url.ParseQuery(serveDirectReqParams.Encode())
if err != nil {
return nil, err
}
// TODO it may be good to embed images with 'inline' like ServeData does, but we don't want to have to read the file, do we?
reqParams.Set("response-content-disposition", "attachment; filename=\""+quoteEscaper.Replace(name)+"\"")
// Here we might not know the real filename, and it's quite inefficient to detect the mine type by pre-fetching the object head.
// So we just do a quick detection by extension name, at least if works for the "View Raw File" for an LFS file on the Web UI.
// Detect content type by extension name, only support the well-known safe types for inline rendering.
// TODO: OBJECT-STORAGE-CONTENT-TYPE: need a complete solution and refactor for Azure in the future
ext := path.Ext(name)
inlineExtMimeTypes := map[string]string{
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".webp": "image/webp",
".avif": "image/avif",
// ATTENTION! Don't support unsafe types like HTML/SVG due to security concerns: they can contain JS code, and maybe they need proper Content-Security-Policy
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context, it seems fine to render it inline
".pdf": "application/pdf",
// TODO: refactor with "modules/public/mime_types.go", for example: "DetectWellKnownSafeInlineMimeType"
}
if mimeType, ok := inlineExtMimeTypes[ext]; ok {
reqParams.Set("response-content-type", mimeType)
reqParams.Set("response-content-disposition", "inline")
} else {
reqParams.Set("response-content-disposition", fmt.Sprintf(`attachment; filename="%s"`, quoteEscaper.Replace(name)))
}
expires := 5 * time.Minute
if method == http.MethodHead {
u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams)
u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams)
return u, convertMinioErr(err)
}
u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams)
u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams)
return u, convertMinioErr(err)
}

View File

@@ -148,7 +148,7 @@ func EnumeratePackages(ctx *context.Context) {
Timestamp: fileMetadata.Timestamp,
Build: fileMetadata.Build,
BuildNumber: fileMetadata.BuildNumber,
Dependencies: fileMetadata.Dependencies,
Dependencies: util.SliceNilAsEmpty(fileMetadata.Dependencies),
License: versionMetadata.License,
LicenseFamily: versionMetadata.LicenseFamily,
HashMD5: pfd.Blob.HashMD5,

View File

@@ -897,7 +897,7 @@ func EditBranchProtection(ctx *context.APIContext) {
} else {
whitelistUsers = protectBranch.WhitelistUserIDs
}
if form.ForcePushAllowlistDeployKeys != nil {
if form.ForcePushAllowlistUsernames != nil {
forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {

View File

@@ -369,11 +369,11 @@ func ReqChangeRepoFileOptionsAndCheck(ctx *context.APIContext) {
},
Signoff: commonOpts.Signoff,
}
if commonOpts.Dates.Author.IsZero() {
commonOpts.Dates.Author = time.Now()
if changeFileOpts.Dates.Author.IsZero() {
changeFileOpts.Dates.Author = time.Now()
}
if commonOpts.Dates.Committer.IsZero() {
commonOpts.Dates.Committer = time.Now()
if changeFileOpts.Dates.Committer.IsZero() {
changeFileOpts.Dates.Committer = time.Now()
}
ctx.Data["__APIChangeRepoFilesOptions"] = changeFileOpts
}

View File

@@ -436,6 +436,7 @@ func ViewProject(ctx *context.Context) {
ctx.Data["Project"] = project
ctx.Data["IssuesMap"] = issuesMap
ctx.Data["Columns"] = columns
ctx.Data["Title"] = fmt.Sprintf("%s - %s", project.Title, ctx.ContextUser.DisplayName())
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
ctx.ServerError("RenderUserOrgHeader", err)

View File

@@ -73,10 +73,9 @@ func SettingsProtectedBranch(c *context.Context) {
c.Data["PageIsSettingsBranches"] = true
c.Data["Title"] = c.Locale.TrString("repo.settings.protected_branch") + " - " + rule.RuleName
users, err := access_model.GetRepoReaders(c, c.Repo.Repository)
users, err := access_model.GetUsersWithUnitAccess(c, c.Repo.Repository, perm.AccessModeRead, unit.TypePullRequests)
if err != nil {
c.ServerError("Repo.Repository.GetReaders", err)
c.ServerError("GetUsersWithUnitAccess", err)
return
}
c.Data["Users"] = users

View File

@@ -149,9 +149,9 @@ func setTagsContext(ctx *context.Context) error {
}
ctx.Data["ProtectedTags"] = protectedTags
users, err := access_model.GetRepoReaders(ctx, ctx.Repo.Repository)
users, err := access_model.GetUsersWithUnitAccess(ctx, ctx.Repo.Repository, perm.AccessModeRead, unit.TypePullRequests)
if err != nil {
ctx.ServerError("Repo.Repository.GetReaders", err)
ctx.ServerError("GetUsersWithUnitAccess", err)
return err
}
ctx.Data["Users"] = users

View File

@@ -95,6 +95,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) (buf []b
meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid)
if err != nil { // fallback to a plain file
fi.lfsMeta = &pointer
log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err)
return buf, dataRc, fi, nil
}

View File

@@ -105,9 +105,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
}
}
if source.AttributeAvatar != "" {
if err := user_service.UploadAvatar(ctx, user, sr.Avatar); err != nil {
return user, err
}
_ = user_service.UploadAvatar(ctx, user, sr.Avatar)
}
}

View File

@@ -139,7 +139,7 @@ func getWhitelistEntities[T *user_model.User | *organization.Team](entities []T,
// ToBranchProtection convert a ProtectedBranch to api.BranchProtection
func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection {
readers, err := access_model.GetRepoReaders(ctx, repo)
readers, err := access_model.GetUsersWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
if err != nil {
log.Error("GetRepoReaders: %v", err)
}
@@ -720,7 +720,7 @@ func ToAnnotatedTagObject(repo *repo_model.Repository, commit *git.Commit) *api.
// ToTagProtection convert a git.ProtectedTag to an api.TagProtection
func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo_model.Repository) *api.TagProtection {
readers, err := access_model.GetRepoReaders(ctx, repo)
readers, err := access_model.GetUsersWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
if err != nil {
log.Error("GetRepoReaders: %v", err)
}

View File

@@ -112,8 +112,12 @@ func NewJwtRegisteredClaimsFromUser(clientID string, grantUserID int64, exp *jwt
// to retrieve the configuration information. This MUST also be identical to the "iss" Claim value in ID Tokens issued from this Issuer.
// * https://accounts.google.com/.well-known/openid-configuration
// * https://github.com/login/oauth/.well-known/openid-configuration
issuer := setting.OAuth2.JWTClaimIssuer
if issuer == "" {
issuer = strings.TrimSuffix(setting.AppURL, "/")
}
return jwt.RegisteredClaims{
Issuer: strings.TrimSuffix(setting.AppURL, "/"),
Issuer: issuer,
Audience: []string{clientID},
Subject: strconv.FormatInt(grantUserID, 10),
ExpiresAt: exp,

View File

@@ -78,18 +78,6 @@
{{ctx.Locale.Tr "repo.release.downloads"}}
</summary>
<ul class="ui divided list attachment-list">
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}}
<li class="item">
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow">
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong>
</a>
</li>
<li class="item">
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong>
</a>
</li>
{{end}}
{{range $att := $release.Attachments}}
<li class="item">
<a target="_blank" class="tw-flex-1 gt-ellipsis" rel="nofollow" download href="{{$att.DownloadURL}}">
@@ -105,6 +93,18 @@
</div>
</li>
{{end}}
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}}
<li class="item">
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow">
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong>
</a>
</li>
<li class="item">
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong>
</a>
</li>
{{end}}
</ul>
</details>
</div>

View File

@@ -1,4 +1,5 @@
{{if .HeatmapData}}
<div class="activity-heatmap-container">
<div id="user-heatmap" class="is-loading"
data-heatmap-data="{{JsonUtils.EncodeToString .HeatmapData}}"
data-locale-total-contributions="{{ctx.Locale.Tr "heatmap.number_of_contributions_in_the_last_12_months" (ctx.Locale.PrettyNumber .HeatmapTotalContributions)}}"
@@ -6,5 +7,6 @@
data-locale-more="{{ctx.Locale.Tr "heatmap.more"}}"
data-locale-less="{{ctx.Locale.Tr "heatmap.less"}}"
></div>
</div>
<div class="divider"></div>
{{end}}

View File

@@ -237,6 +237,8 @@ func TestPackageConda(t *testing.T) {
assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5)
assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256)
assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size)
assert.NotNil(t, packageInfo.Dependencies)
assert.Empty(t, packageInfo.Dependencies)
})
t.Run(".conda", func(t *testing.T) {
@@ -268,6 +270,8 @@ func TestPackageConda(t *testing.T) {
assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5)
assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256)
assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size)
assert.NotNil(t, packageInfo.Dependencies)
assert.Empty(t, packageInfo.Dependencies)
})
})
}

View File

@@ -919,9 +919,10 @@ func TestOAuth_GrantScopesClaimAllGroups(t *testing.T) {
}
func testOAuth2WellKnown(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/")()
urlOpenidConfiguration := "/.well-known/openid-configuration"
defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/")()
t.Run("WellKnown", func(t *testing.T) {
req := NewRequest(t, "GET", urlOpenidConfiguration)
resp := MakeRequest(t, req, http.StatusOK)
var respMap map[string]any
@@ -933,6 +934,17 @@ func testOAuth2WellKnown(t *testing.T) {
assert.Equal(t, "https://try.gitea.io/login/oauth/userinfo", respMap["userinfo_endpoint"])
assert.Equal(t, "https://try.gitea.io/login/oauth/introspect", respMap["introspection_endpoint"])
assert.Equal(t, []any{"RS256"}, respMap["id_token_signing_alg_values_supported"])
})
t.Run("WellKnownWithIssuer", func(t *testing.T) {
defer test.MockVariableValue(&setting.OAuth2.JWTClaimIssuer, "https://try.gitea.io/")()
req := NewRequest(t, "GET", urlOpenidConfiguration)
resp := MakeRequest(t, req, http.StatusOK)
var respMap map[string]any
DecodeJSON(t, resp, &respMap)
assert.Equal(t, "https://try.gitea.io/", respMap["issuer"]) // has trailing by JWTClaimIssuer
assert.Equal(t, "https://try.gitea.io/login/oauth/authorize", respMap["authorization_endpoint"])
})
defer test.MockVariableValue(&setting.OAuth2.Enabled, false)()
MakeRequest(t, NewRequest(t, "GET", urlOpenidConfiguration), http.StatusNotFound)

View File

@@ -626,7 +626,6 @@ img.ui.avatar,
font-family: var(--fonts-monospace);
font-size: 13px;
font-weight: var(--font-weight-normal);
padding: 3px 5px;
flex-shrink: 0;
}

View File

@@ -4,23 +4,44 @@
position: relative;
}
/* before the Vue component is mounted, show a loading indicator with dummy size */
/* the ratio is guesswork, see https://github.com/razorness/vue3-calendar-heatmap/issues/26 */
.activity-heatmap-container {
container-type: inline-size;
}
@container (width > 0) {
#user-heatmap {
/* Set element to fixed height so that it does not resize after load. The calculation is complex
because the element does not scale with a fixed aspect ratio. */
height: calc((100cqw / 5) - (100cqw / 25) + 20px);
}
}
/* Fallback height adjustment above for browsers that don't support container queries */
@supports not (container-type: inline-size) {
/* Before the Vue component is mounted, show a loading indicator with dummy size */
/* The ratio is guesswork for legacy browsers, new browsers use the "@container" approach above */
#user-heatmap.is-loading {
aspect-ratio: 5.415; /* the size is about 790 x 145 */
aspect-ratio: 5.4823972051; /* the size is about 816 x 148.84 */
}
.user.profile #user-heatmap.is-loading {
aspect-ratio: 5.645; /* the size is about 953 x 169 */
aspect-ratio: 5.6290608387; /* the size is about 953 x 169.3 */
}
}
#user-heatmap text {
fill: currentcolor !important;
}
/* root legend */
#user-heatmap .vch__container > .vch__legend {
display: flex;
font-size: 11px;
justify-content: space-between;
}
/* for the "Less" and "More" legend */
#user-heatmap .vch__legend .vch__legend {
display: flex;
font-size: 11px;
align-items: center;
justify-content: right;
}
@@ -34,25 +55,3 @@
#user-heatmap .vch__day__square:hover {
outline: 1.5px solid var(--color-text);
}
/* move the "? contributions in the last ? months" text from top to bottom */
#user-heatmap .total-contributions {
font-size: 11px;
position: absolute;
bottom: 0;
left: 25px;
}
@media (max-width: 1200px) {
#user-heatmap .total-contributions {
left: 21px;
}
}
@media (max-width: 1000px) {
#user-heatmap .total-contributions {
font-size: 10px;
left: 17px;
bottom: -4px;
}
}

View File

@@ -387,6 +387,7 @@ td .commit-summary {
.repository.view.issue .pull-desc code {
color: var(--color-primary);
background: transparent;
}
.repository.view.issue .pull-desc a[data-clipboard-text] {

View File

@@ -53,9 +53,6 @@ function handleDayClick(e: Event & {date: Date}) {
}
</script>
<template>
<div class="total-contributions">
{{ locale.textTotalContributions }}
</div>
<calendar-heatmap
:locale="locale.heatMapLocale"
:no-data-text="locale.noDataText"
@@ -65,5 +62,7 @@ function handleDayClick(e: Event & {date: Date}) {
:range-color="colorRange"
@day-click="handleDayClick($event)"
:tippy-props="{theme: 'tooltip'}"
/>
>
<template #vch__legend-left>{{ locale.textTotalContributions }}</template>
</calendar-heatmap>
</template>

View File

@@ -1,4 +1,3 @@
import './globals.ts';
import '../fomantic/build/fomantic.js';
import '../../node_modules/easymde/dist/easymde.min.css'; // TODO: lazy load in "switchToEasyMDE"

View File

@@ -1,5 +1,10 @@
// bootstrap module must be the first one to be imported, it handles webpack lazy-loading and global errors
import './bootstrap.ts';
// many users expect to use jQuery in their custom scripts (https://docs.gitea.com/administration/customizing-gitea#example-plantuml)
// so load globals (including jQuery) as early as possible
import './globals.ts';
import './webcomponents/index.ts';
import {onDomReady} from './utils/dom.ts';