Merge branch 'forgejo' into feat/xorm-EngineGroup

This commit is contained in:
pat-s 2025-03-19 23:32:04 +01:00
commit a8a18e67a3
64 changed files with 1287 additions and 417 deletions

View file

@ -28,7 +28,7 @@ jobs:
runs-on: docker
container:
image: data.forgejo.org/renovate/renovate:39.195.1
image: data.forgejo.org/renovate/renovate:39.205.1
steps:
- name: Load renovate repo cache

View file

@ -49,7 +49,7 @@ GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasour
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.31.0 # renovate: datasource=go
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.1 # renovate: datasource=go
RENOVATE_NPM_PACKAGE ?= renovate@39.195.1 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
RENOVATE_NPM_PACKAGE ?= renovate@39.205.1 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...

File diff suppressed because one or more lines are too long

12
go.mod
View file

@ -5,7 +5,7 @@ go 1.24
toolchain go1.24.1
require (
code.forgejo.org/f3/gof3/v3 v3.10.4
code.forgejo.org/f3/gof3/v3 v3.10.6
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251
code.forgejo.org/forgejo/levelqueue v1.0.0
code.forgejo.org/forgejo/reply v1.0.2
@ -33,7 +33,7 @@ require (
github.com/djherbis/nio/v3 v3.0.1
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
github.com/dustin/go-humanize v1.0.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
github.com/emersion/go-imap v1.2.1
github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.8.0
@ -99,8 +99,8 @@ require (
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/gitlab-org/api/client-go v0.123.0
go.uber.org/mock v0.4.0
gitlab.com/gitlab-org/api/client-go v0.126.0
go.uber.org/mock v0.5.0
golang.org/x/crypto v0.36.0
golang.org/x/image v0.25.0
golang.org/x/net v0.37.0
@ -108,7 +108,6 @@ require (
golang.org/x/sync v0.12.0
golang.org/x/sys v0.31.0
golang.org/x/text v0.23.0
google.golang.org/grpc v1.71.0
google.golang.org/protobuf v1.36.4
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
@ -270,12 +269,13 @@ require (
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/time v0.10.0 // indirect
golang.org/x/tools v0.31.0 // indirect
google.golang.org/api v0.203.0 // indirect
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
google.golang.org/grpc v1.71.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

20
go.sum
View file

@ -610,8 +610,8 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS
cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M=
cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=
cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=
code.forgejo.org/f3/gof3/v3 v3.10.4 h1:NOkw8lG3PhgYykdR5pJ19KFLdYmRFVII43YBSNKtpvk=
code.forgejo.org/f3/gof3/v3 v3.10.4/go.mod h1:aQSlSGpISIf016g1dBrmm16h53WUKjCR7nSCuoO8dq8=
code.forgejo.org/f3/gof3/v3 v3.10.6 h1:Ru/Iz+pqM8IPi7atUHE7+q7v3O3DRbYgMFqrFTsO1m8=
code.forgejo.org/f3/gof3/v3 v3.10.6/go.mod h1:K6lQCWQIyN/5rjP/OJL9fMA6fd++satndE20w/I6Kss=
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
code.forgejo.org/forgejo/act v1.23.1 h1:4LU7u3FE380DicHz1oZ4ohsWU9I527OHeslVHnr9eP4=
@ -845,8 +845,8 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2/go.mod h1:7dvD3GCm7eBw53xZ/lsiq72LqobdMg3ITbMBxnmJmqY=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 h1:XVUp6qW3BIkmM3/1EkrHpa6bL56APOynfXcZEmIgOhs=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3/go.mod h1:ThHVc+hqbUsmE1wmK/MASpQEhCleWu1JDJDNhUOMy0c=
github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM=
github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
@ -1452,8 +1452,8 @@ github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCR
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
gitlab.com/gitlab-org/api/client-go v0.123.0 h1:W3LZ5QNyiSCJA0Zchkwz8nQIUzOuDoSWMZtRDT5DjPI=
gitlab.com/gitlab-org/api/client-go v0.123.0/go.mod h1:Jh0qjLILEdbO6z/OY94RD+3NDQRUKiuFSFYozN6cpKM=
gitlab.com/gitlab-org/api/client-go v0.126.0 h1:VV5TdkF6pMbEdFGvbR2CwEgJwg6qdg1u3bj5eD2tiWk=
gitlab.com/gitlab-org/api/client-go v0.126.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAjE7UeNatu2VWHRf4/LE=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -1491,8 +1491,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
@ -1839,8 +1839,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View file

@ -0,0 +1,5 @@
-
id: 1001
user_id: 29
repo_id: 3
mode: 1

View file

@ -0,0 +1,11 @@
-
id: 1001
title: Org project that contains private issues
owner_id: 3
repo_id: 0
is_closed: false
creator_id: 2
board_type: 1
type: 3
created_unix: 1738000000
updated_unix: 1738000000

View file

@ -0,0 +1,8 @@
-
id: 1001
project_id: 1001
title: Triage
creator_id: 2
default: true
created_unix: 1738000000
updated_unix: 1738000000

View file

@ -0,0 +1,11 @@
-
id: 1001
issue_id: 6
project_id: 1001
project_board_id: 1001
-
id: 1002
issue_id: 15
project_id: 1001
project_board_id: 1001

View file

@ -7,9 +7,11 @@ import (
"context"
"crypto/md5"
"encoding/base64"
"fmt"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting"
@ -57,19 +59,38 @@ func MigrateTwoFactorToKeying(x *xorm.Engine) error {
oldEncryptionKey := md5.Sum([]byte(setting.SecretKey))
return db.Iterate(context.Background(), nil, func(ctx context.Context, bean *auth.TwoFactor) error {
messages := make([]string, 0, 100)
ids := make([]int64, 0, 100)
err = db.Iterate(context.Background(), nil, func(ctx context.Context, bean *auth.TwoFactor) error {
decodedStoredSecret, err := base64.StdEncoding.DecodeString(string(bean.Secret))
if err != nil {
return err
messages = append(messages, fmt.Sprintf("two_factor.id=%d, two_factor.uid=%d: base64.StdEncoding.DecodeString: %v", bean.ID, bean.UID, err))
ids = append(ids, bean.ID)
return nil
}
secretBytes, err := secret.AesDecrypt(oldEncryptionKey[:], decodedStoredSecret)
if err != nil {
return err
messages = append(messages, fmt.Sprintf("two_factor.id=%d, two_factor.uid=%d: secret.AesDecrypt: %v", bean.ID, bean.UID, err))
ids = append(ids, bean.ID)
return nil
}
bean.SetSecret(string(secretBytes))
_, err = db.GetEngine(ctx).Cols("secret").ID(bean.ID).Update(bean)
return err
})
if err == nil {
if len(ids) > 0 {
log.Error("Forgejo migration[25]: The following TOTP secrets were found to be corrupted and removed from the database. TOTP is no longer required to login with the associated users. They should be informed because they will need to visit their security settings and configure TOTP again. No other action is required. See https://codeberg.org/forgejo/forgejo/issues/6637 for more context on the various causes for such a corruption.")
for _, message := range messages {
log.Error("Forgejo migration[25]: %s", message)
}
_, err = db.GetEngine(context.Background()).In("id", ids).NoAutoCondition().NoAutoTime().Delete(&auth.TwoFactor{})
}
}
return err
}

View file

@ -36,10 +36,14 @@ func Test_MigrateTwoFactorToKeying(t *testing.T) {
cnt, err := x.Table("two_factor").Count()
require.NoError(t, err)
assert.EqualValues(t, 1, cnt)
assert.EqualValues(t, 2, cnt)
require.NoError(t, MigrateTwoFactorToKeying(x))
cnt, err = x.Table("two_factor").Count()
require.NoError(t, err)
assert.EqualValues(t, 1, cnt)
var twofactor auth.TwoFactor
_, err = x.Table("two_factor").ID(1).Get(&twofactor)
require.NoError(t, err)

View file

@ -117,3 +117,57 @@ func TestPrivateIssueProjects(t *testing.T) {
})
})
}
func TestPrivateRepoProjects(t *testing.T) {
defer tests.AddFixtures("models/fixtures/TestPrivateRepoProjects/")()
require.NoError(t, unittest.PrepareTestDatabase())
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
orgProject := unittest.AssertExistsAndLoadBean(t, &project.Project{ID: 1001, OwnerID: org.ID})
column := unittest.AssertExistsAndLoadBean(t, &project.Column{ID: 1001, ProjectID: orgProject.ID})
t.Run("Partial access", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29})
issueList, err := issues.LoadIssuesFromColumn(db.DefaultContext, column, user29, org, optional.None[bool]())
require.NoError(t, err)
assert.Len(t, issueList, 1)
assert.EqualValues(t, 6, issueList[0].ID)
issuesNum, err := issues.NumIssuesInProject(db.DefaultContext, orgProject, user29, org, optional.None[bool]())
require.NoError(t, err)
assert.EqualValues(t, 1, issuesNum)
issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, orgProject, user29, org, optional.Some(true))
require.NoError(t, err)
assert.EqualValues(t, 0, issuesNum)
issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, orgProject, user29, org, optional.Some(false))
require.NoError(t, err)
assert.EqualValues(t, 1, issuesNum)
})
t.Run("Full access", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
issueList, err := issues.LoadIssuesFromColumn(db.DefaultContext, column, user2, org, optional.None[bool]())
require.NoError(t, err)
assert.Len(t, issueList, 2)
assert.EqualValues(t, 15, issueList[0].ID)
assert.EqualValues(t, 6, issueList[1].ID)
issuesNum, err := issues.NumIssuesInProject(db.DefaultContext, orgProject, user2, org, optional.None[bool]())
require.NoError(t, err)
assert.EqualValues(t, 2, issuesNum)
issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, orgProject, user2, org, optional.Some(true))
require.NoError(t, err)
assert.EqualValues(t, 0, issuesNum)
issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, orgProject, user2, org, optional.Some(false))
require.NoError(t, err)
assert.EqualValues(t, 2, issuesNum)
})
}

View file

@ -341,6 +341,9 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organizati
builder.Or(
repo_model.UserOrgUnitRepoCond(repoIDstr, userID, org.ID, unitType), // team member repos
repo_model.UserOrgPublicUnitRepoCond(userID, org.ID), // user org public non-member repos, TODO: check repo has issues
builder.And(
builder.In("issue.repo_id", builder.Select("id").From("repository").Where(builder.Eq{"owner_id": org.ID})),
repo_model.UserAccessRepoCond(repoIDstr, userID)), // user can access org repo in a unit independent way
),
)
}

View file

@ -7,3 +7,12 @@
last_used_passcode:
created_unix: 1564253724
updated_unix: 1564253724
-
id: 2
uid: 23
secret: badbad
scratch_salt: badbad
scratch_hash: badbad
last_used_passcode:
created_unix: 1564253724
updated_unix: 1564253724

View file

@ -0,0 +1,45 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"context"
user_model "code.gitea.io/gitea/models/user"
)
type ReleaseList []*Release
// LoadAttributes loads the repository and publisher for the releases.
func (r ReleaseList) LoadAttributes(ctx context.Context) error {
repoCache := make(map[int64]*Repository)
userCache := make(map[int64]*user_model.User)
for _, release := range r {
var err error
repo, ok := repoCache[release.RepoID]
if !ok {
repo, err = GetRepositoryByID(ctx, release.RepoID)
if err != nil {
return err
}
repoCache[release.RepoID] = repo
}
release.Repo = repo
publisher, ok := userCache[release.PublisherID]
if !ok {
publisher, err = user_model.GetUserByID(ctx, release.PublisherID)
if err != nil {
if !user_model.IsErrUserNotExist(err) {
return err
}
publisher = user_model.NewGhostUser()
}
userCache[release.PublisherID] = publisher
}
release.Publisher = publisher
}
return nil
}

View file

@ -0,0 +1,42 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"testing"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestReleaseListLoadAttributes(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
releases := ReleaseList{&Release{
RepoID: 1,
PublisherID: 1,
}, &Release{
RepoID: 2,
PublisherID: 2,
}, &Release{
RepoID: 1,
PublisherID: 2,
}, &Release{
RepoID: 2,
PublisherID: 1,
}}
require.NoError(t, releases.LoadAttributes(t.Context()))
assert.EqualValues(t, 1, releases[0].Repo.ID)
assert.EqualValues(t, 1, releases[0].Publisher.ID)
assert.EqualValues(t, 2, releases[1].Repo.ID)
assert.EqualValues(t, 2, releases[1].Publisher.ID)
assert.EqualValues(t, 1, releases[2].Repo.ID)
assert.EqualValues(t, 2, releases[2].Publisher.ID)
assert.EqualValues(t, 2, releases[3].Repo.ID)
assert.EqualValues(t, 1, releases[3].Publisher.ID)
}

View file

@ -529,7 +529,6 @@ func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
Join("INNER", "team", "team.id = team_repo.team_id").
Where("team_repo.repo_id = ?", repo.ID).
Select("team.lower_name").
OrderBy("team.lower_name").
Find(&teams)
metas["teams"] = "," + strings.Join(teams, ",") + ","
metas["org"] = strings.ToLower(repo.OwnerName)

View file

@ -8,6 +8,7 @@ import (
"fmt"
"os"
"runtime/pprof"
"strings"
"sync/atomic"
"time"
@ -280,6 +281,33 @@ const (
SortByDeadlineAsc = internal.SortByDeadlineAsc
)
// ParseSortBy parses the `sortBy` string and returns the associated `SortBy`
// value, if one exists. Otherwise return `defaultSortBy`.
func ParseSortBy(sortBy string, defaultSortBy internal.SortBy) internal.SortBy {
switch strings.ToLower(sortBy) {
case "relevance":
return SortByScore
case "latest":
return SortByCreatedDesc
case "oldest":
return SortByCreatedAsc
case "recentupdate":
return SortByUpdatedDesc
case "leastupdate":
return SortByUpdatedAsc
case "mostcomment":
return SortByCommentsDesc
case "leastcomment":
return SortByCommentsAsc
case "nearduedate":
return SortByDeadlineAsc
case "farduedate":
return SortByDeadlineDesc
default:
return defaultSortBy
}
}
// SearchIssues search issues by options.
func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, error) {
indexer := *globalIndexer.Load()

652
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -67,12 +67,12 @@
"@stylistic/eslint-plugin-js": "4.2.0",
"@stylistic/stylelint-plugin": "3.1.2",
"@typescript-eslint/parser": "8.26.1",
"@vitejs/plugin-vue": "5.1.5",
"@vitejs/plugin-vue": "5.2.3",
"@vitest/coverage-v8": "3.0.8",
"@vitest/eslint-plugin": "1.1.25",
"@vue/test-utils": "2.4.6",
"eslint": "9.22.0",
"eslint-import-resolver-typescript": "3.8.7",
"eslint-import-resolver-typescript": "4.1.1",
"eslint-plugin-array-func": "5.0.2",
"eslint-plugin-import-x": "4.7.0",
"eslint-plugin-no-jquery": "3.1.1",

8
poetry.lock generated
View file

@ -404,13 +404,13 @@ files = [
[[package]]
name = "yamllint"
version = "1.36.1"
version = "1.36.2"
description = "A linter for YAML files."
optional = false
python-versions = ">=3.9"
files = [
{file = "yamllint-1.36.1-py3-none-any.whl", hash = "sha256:3e2ccd47ea12449837adf6b2c56fd9e31172ce42bc1470380806be461f25df66"},
{file = "yamllint-1.36.1.tar.gz", hash = "sha256:a287689daaafc301a80549b2d0170452ebfdcabd826e3fe3b4c66e322d4851fa"},
{file = "yamllint-1.36.2-py3-none-any.whl", hash = "sha256:6ba19800513d86c987ed3cf48c0aba4e82b3624596e1dab55723b3733e11f35c"},
{file = "yamllint-1.36.2.tar.gz", hash = "sha256:c9ccc818659736e7b13f7e2f9c3c9bb9ac77445be13e789e7d843e92cb8428ef"},
]
[package.dependencies]
@ -423,4 +423,4 @@ dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "15925764be9383752b2d6eaba27c7f65e3a07456123bb69a2837fa54c916d7cb"
content-hash = "6e4e2a74db7e165f0aa8cae9bb8932cbe1e84ea9bef64db42371e6d620d77c93"

View file

@ -6,7 +6,7 @@ python = "^3.10"
[tool.poetry.group.dev.dependencies]
djlint = "1.36.4"
yamllint = "1.36.1"
yamllint = "1.36.2"
codespell = "2.4.1"
[tool.djlint]

View file

@ -0,0 +1,14 @@
<!--start release-notes-assistant-->
## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Bug fixes
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7143) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7145)): <!--number 7145 --><!--line 0 --><!--description Zml4OiBjb25zaWRlciBwdWJsaWMgaXNzdWVzIGZvciBwcm9qZWN0IGJvYXJkcyAoIzcxNDMp-->consider public issues for project boards<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7038) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7049)): <!--number 7049 --><!--line 0 --><!--description Zml4KHJlbGVhc2UpOiB0aGUgcm9vdGxlc3MgaW1hZ2UgdmVyc2lvbiBsYWJlbCBpcyBub3Qgc2V0-->the rootless image version label is not set<!--description-->
- Included for completeness but not worth a release note
- [PR](https://codeberg.org/forgejo/forgejo/pulls/6299) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7233)): <!--number 7233 --><!--line 0 --><!--description Y2hvcmU6IE1ha2UgRm9yZ2VqbyBidWlsZCB3aXRoIGdvMS4yNA==-->chore: Make Forgejo build with go1.24<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7081): <!--number 7081 --><!--line 0 --><!--description VXBkYXRlIGdvbGFuZyBwYWNrYWdlcyB0byB2MS4yMyAodjcuMC9mb3JnZWpvKSAobWlub3Ip-->Update golang packages to v1.23 (v7.0/forgejo) (minor)<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7051): <!--number 7051 --><!--line 0 --><!--description VXBkYXRlIGh0dHBzOi8vY29kZS5mb3JnZWpvLm9yZy9mb3JnZWpvL2Zvcmdlam8tYnVpbGQtcHVibGlzaCBhY3Rpb24gdG8gdjUuMy40ICh2Ny4wL2Zvcmdlam8p-->Update https://code.forgejo.org/forgejo/forgejo-build-publish action to v5.3.4 (v7.0/forgejo)<!--description-->
<!--end release-notes-assistant-->

1
release-notes/7251.md Normal file
View file

@ -0,0 +1 @@
When migrating from a Forgejo version lower than v10, the TOTP secrets found to be corrupted are now transparently removed from the database instead of failing the migration. TOTP is no longer required to login with the associated users. They should be informed because they will need to visit their security settings and configure TOTP again. No other action is required.

View file

@ -137,6 +137,8 @@
"@playwright/**",
"@stoplight/spectral-cli",
"@stylistic/**",
"@vitejs/plugin-vue",
"@vue/test-utils",
"djlint",
"github.com/editorconfig-checker/editorconfig-checker/v2/cmd/editorconfig-checker",
"github.com/golangci/golangci-lint/cmd/golangci-lint",
@ -148,7 +150,7 @@
"mvdan.cc/gofumpt",
"updates",
"vite-string-plugin",
"@vue/test-utils"
"yamllint"
],
"automerge": true
},

View file

@ -16,8 +16,6 @@ import (
"code.gitea.io/gitea/modules/util"
"connectrpc.com/connect"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
@ -37,12 +35,12 @@ var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unar
runner, err := actions_model.GetRunnerByUUID(ctx, uuid)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
return nil, status.Error(codes.Unauthenticated, "unregistered runner")
return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unregistered runner"))
}
return nil, status.Error(codes.Internal, err.Error())
return nil, connect.NewError(connect.CodeInternal, err)
}
if subtle.ConstantTimeCompare([]byte(runner.TokenHash), []byte(auth_model.HashToken(token, runner.TokenSalt))) != 1 {
return nil, status.Error(codes.Unauthenticated, "unregistered runner")
return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unregistered runner"))
}
cols := []string{"last_online"}

View file

@ -6,6 +6,7 @@ package runner
import (
"context"
"errors"
"fmt"
"net/http"
actions_model "code.gitea.io/gitea/models/actions"
@ -20,8 +21,6 @@ import (
"code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
"connectrpc.com/connect"
gouuid "github.com/google/uuid"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func NewRunnerServiceHandler() (string, http.Handler) {
@ -44,27 +43,27 @@ func (s *Service) Register(
req *connect.Request[runnerv1.RegisterRequest],
) (*connect.Response[runnerv1.RegisterResponse], error) {
if req.Msg.Token == "" || req.Msg.Name == "" {
return nil, errors.New("missing runner token, name")
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("missing runner token, name"))
}
runnerToken, err := actions_model.GetRunnerToken(ctx, req.Msg.Token)
if err != nil {
return nil, errors.New("runner registration token not found")
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("runner registration token not found"))
}
if !runnerToken.IsActive {
return nil, errors.New("runner registration token has been invalidated, please use the latest one")
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("runner registration token has been invalidated, please use the latest one"))
}
if runnerToken.OwnerID > 0 {
if _, err := user_model.GetUserByID(ctx, runnerToken.OwnerID); err != nil {
return nil, errors.New("owner of the token not found")
return nil, connect.NewError(connect.CodeInternal, errors.New("owner of the token not found"))
}
}
if runnerToken.RepoID > 0 {
if _, err := repo_model.GetRepositoryByID(ctx, runnerToken.RepoID); err != nil {
return nil, errors.New("repository of the token not found")
return nil, connect.NewError(connect.CodeInternal, errors.New("repository of the token not found"))
}
}
@ -81,18 +80,18 @@ func (s *Service) Register(
AgentLabels: labels,
}
if err := runner.GenerateToken(); err != nil {
return nil, errors.New("can't generate token")
return nil, connect.NewError(connect.CodeInternal, errors.New("can't generate token"))
}
// create new runner
if err := actions_model.CreateRunner(ctx, runner); err != nil {
return nil, errors.New("can't create new runner")
return nil, connect.NewError(connect.CodeInternal, errors.New("can't create new runner"))
}
// update token status
runnerToken.IsActive = true
if err := actions_model.UpdateRunnerToken(ctx, runnerToken, "is_active"); err != nil {
return nil, errors.New("can't update runner token status")
return nil, connect.NewError(connect.CodeInternal, errors.New("can't update runner token status"))
}
res := connect.NewResponse(&runnerv1.RegisterResponse{
@ -117,7 +116,7 @@ func (s *Service) Declare(
runner.AgentLabels = req.Msg.Labels
runner.Version = req.Msg.Version
if err := actions_model.UpdateRunner(ctx, runner, "agent_labels", "version"); err != nil {
return nil, status.Errorf(codes.Internal, "update runner: %v", err)
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("update runner: %w", err))
}
return connect.NewResponse(&runnerv1.DeclareResponse{
@ -143,10 +142,10 @@ func (s *Service) FetchTask(
tasksVersion := req.Msg.TasksVersion // task version from runner
latestVersion, err := actions_model.GetTasksVersionByScope(ctx, runner.OwnerID, runner.RepoID)
if err != nil {
return nil, status.Errorf(codes.Internal, "query tasks version failed: %v", err)
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("query tasks version failed: %w", err))
} else if latestVersion == 0 {
if err := actions_model.IncreaseTaskVersion(ctx, runner.OwnerID, runner.RepoID); err != nil {
return nil, status.Errorf(codes.Internal, "fail to increase task version: %v", err)
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("fail to increase task version: %w", err))
}
// if we don't increase the value of `latestVersion` here,
// the response of FetchTask will return tasksVersion as zero.
@ -160,7 +159,7 @@ func (s *Service) FetchTask(
// try to pick a task for the runner that send the request.
if t, ok, err := actions_service.PickTask(ctx, runner); err != nil {
log.Error("pick task failed: %v", err)
return nil, status.Errorf(codes.Internal, "pick task: %v", err)
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("pick task: %w", err))
} else if ok {
task = t
}
@ -181,7 +180,7 @@ func (s *Service) UpdateTask(
task, err := actions_model.UpdateTaskByState(ctx, runner.ID, req.Msg.State)
if err != nil {
return nil, status.Errorf(codes.Internal, "update task: %v", err)
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("update task: %w", err))
}
for k, v := range req.Msg.Outputs {
@ -210,10 +209,10 @@ func (s *Service) UpdateTask(
}
if err := task.LoadJob(ctx); err != nil {
return nil, status.Errorf(codes.Internal, "load job: %v", err)
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("load job: %w", err))
}
if err := task.Job.LoadRun(ctx); err != nil {
return nil, status.Errorf(codes.Internal, "load run: %v", err)
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("load run: %w", err))
}
// don't create commit status for cron job
@ -247,9 +246,9 @@ func (s *Service) UpdateLog(
task, err := actions_model.GetTaskByID(ctx, req.Msg.TaskId)
if err != nil {
return nil, status.Errorf(codes.Internal, "get task: %v", err)
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("get task: %w", err))
} else if runner.ID != task.RunnerID {
return nil, status.Errorf(codes.Internal, "invalid runner for task")
return nil, connect.NewError(connect.CodeInternal, errors.New("invalid runner for task"))
}
ack := task.LogLength
@ -259,13 +258,13 @@ func (s *Service) UpdateLog(
}
if task.LogInStorage {
return nil, status.Errorf(codes.AlreadyExists, "log file has been archived")
return nil, connect.NewError(connect.CodeAlreadyExists, errors.New("log file has been archived"))
}
rows := req.Msg.Rows[ack-req.Msg.Index:]
ns, err := actions.WriteLogs(ctx, task.LogFilename, task.LogSize, rows)
if err != nil {
return nil, status.Errorf(codes.Internal, "write logs: %v", err)
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("write logs: %w", err))
}
task.LogLength += int64(len(rows))
for _, n := range ns {
@ -280,12 +279,12 @@ func (s *Service) UpdateLog(
task.LogInStorage = true
remove, err = actions.TransferLogs(ctx, task.LogFilename)
if err != nil {
return nil, status.Errorf(codes.Internal, "transfer logs: %v", err)
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("transfer logs: %w", err))
}
}
if err := actions_model.UpdateTask(ctx, task, "log_indexes", "log_length", "log_size", "log_in_storage"); err != nil {
return nil, status.Errorf(codes.Internal, "update task: %v", err)
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("update task: %w", err))
}
if remove != nil {
remove()

View file

@ -105,9 +105,11 @@ func ServePackageInfo(ctx *context.Context) {
ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, packageName)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if len(versions) == 0 {
apiError(ctx, http.StatusNotFound, fmt.Sprintf("Could not find package %s", packageName))
return
}
result, err := buildInfoFileForPackage(ctx, versions)
@ -135,6 +137,7 @@ func ServeVersionsFile(ctx *context.Context) {
ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, pack.Name)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if len(versions) == 0 {
// No versions left for this package, we should continue.
@ -144,6 +147,20 @@ func ServeVersionsFile(ctx *context.Context) {
fmt.Fprintf(result, "%s ", pack.Name)
for i, v := range versions {
result.WriteString(v.Version)
pd, err := packages_model.GetPackageDescriptor(ctx, v)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
metadata := pd.Metadata.(*rubygems_module.Metadata)
if metadata.Platform != "ruby" {
result.WriteString("_")
result.WriteString(metadata.Platform)
}
if i != len(versions)-1 {
result.WriteString(",")
}
@ -152,6 +169,7 @@ func ServeVersionsFile(ctx *context.Context) {
info, err := buildInfoFileForPackage(ctx, versions)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
checksum := md5.Sum([]byte(*info))
@ -413,6 +431,11 @@ func buildRequirementStringFromVersion(ctx *context.Context, version *packages_m
additionalRequirements.WriteString(",rubygems:")
writeRequirements(metadata.RequiredRubygemsVersion, additionalRequirements)
}
if metadata.Platform != "ruby" {
return fmt.Sprintf("%s-%s %s|%s", version.Version, metadata.Platform, dependencyRequirements, additionalRequirements), nil
}
return fmt.Sprintf("%s %s|%s", version.Version, dependencyRequirements, additionalRequirements), nil
}

View file

@ -397,6 +397,12 @@ func ListIssues(ctx *context.APIContext) {
// in: query
// description: page size of results
// type: integer
// - name: sort
// in: query
// description: Type of sort
// type: string
// enum: [relevance, latest, oldest, recentupdate, leastupdate, mostcomment, leastcomment, nearduedate, farduedate]
// default: latest
// responses:
// "200":
// "$ref": "#/responses/IssueList"
@ -510,7 +516,7 @@ func ListIssues(ctx *context.APIContext) {
RepoIDs: []int64{ctx.Repo.Repository.ID},
IsPull: isPull,
IsClosed: isClosed,
SortBy: issue_indexer.SortByCreatedDesc,
SortBy: issue_indexer.ParseSortBy(ctx.FormString("sort"), issue_indexer.SortByCreatedDesc),
}
if since != 0 {
searchOpt.UpdatedAfterUnix = optional.Some(since)

View file

@ -101,8 +101,9 @@ func ListMyRepos(ctx *context.APIContext) {
// type: integer
// - name: order_by
// in: query
// description: order the repositories by name (default), id, or size
// description: order the repositories
// type: string
// enum: [name, id, newest, oldest, recentupdate, leastupdate, reversealphabetically, alphabetically, reversesize, size, reversegitsize, gitsize, reverselfssize, lfssize, moststars, feweststars, mostforks, fewestforks]
// responses:
// "200":
// "$ref": "#/responses/RepositoryList"
@ -124,14 +125,15 @@ func ListMyRepos(ctx *context.APIContext) {
switch orderBy {
case "name":
opts.OrderBy = "name ASC"
case "size":
opts.OrderBy = "size DESC"
case "id":
opts.OrderBy = "id ASC"
case "":
default:
ctx.Error(http.StatusUnprocessableEntity, "", "invalid order_by")
return
if orderBy, ok := repo_model.OrderByFlatMap[orderBy]; ok {
opts.OrderBy = orderBy
} else if orderBy != "" {
ctx.Error(http.StatusUnprocessableEntity, "", "invalid order_by")
return
}
}
repos, count, err := repo_model.SearchRepository(ctx, opts)

View file

@ -298,14 +298,14 @@ func GetFeedType(name string, req *http.Request) (bool, string, string) {
return false, name, ""
}
// feedActionsToFeedItems convert gitea's Repo's Releases to feeds Item
func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release) (items []*feeds.Item, err error) {
for _, rel := range releases {
err := rel.LoadAttributes(ctx)
if err != nil {
return nil, err
}
// feedActionsToFeedItems convert repository releases into feed items.
func releasesToFeedItems(ctx *context.Context, releases repo_model.ReleaseList) (items []*feeds.Item, err error) {
if err := releases.LoadAttributes(ctx); err != nil {
return nil, err
}
composeCache := make(map[int64]map[string]string)
for _, rel := range releases {
var title string
var content template.HTML
@ -315,13 +315,19 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release) (
title = rel.Title
}
metas, ok := composeCache[rel.RepoID]
if !ok {
metas = rel.Repo.ComposeMetas(ctx)
composeCache[rel.RepoID] = metas
}
link := &feeds.Link{Href: rel.HTMLURL()}
content, err = markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
Links: markup.Links{
Base: rel.Repo.Link(),
},
Metas: rel.Repo.ComposeMetas(ctx),
Metas: metas,
}, rel.Note)
if err != nil {
return nil, err

View file

@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/services/attachment"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -131,7 +132,7 @@ func (o *asset) Patch(ctx context.Context) {
}
}
func (o *asset) Put(ctx context.Context) generic.NodeID {
func (o *asset) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
@ -154,7 +155,7 @@ func (o *asset) Put(ctx context.Context) generic.NodeID {
}
o.Trace("asset created %d", o.forgejoAsset.ID)
return generic.NewNodeID(o.forgejoAsset.ID)
return f3_id.NewNodeID(o.forgejoAsset.ID)
}
func (o *asset) Delete(ctx context.Context) {

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -95,7 +96,7 @@ func (o *comment) Patch(ctx context.Context) {
}
}
func (o *comment) Put(ctx context.Context) generic.NodeID {
func (o *comment) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
@ -105,7 +106,7 @@ func (o *comment) Put(ctx context.Context) generic.NodeID {
panic(err)
}
o.Trace("comment created %d", o.forgejoComment.ID)
return generic.NewNodeID(o.forgejoComment.ID)
return f3_id.NewNodeID(o.forgejoComment.ID)
}
func (o *comment) Delete(ctx context.Context) {

View file

@ -7,6 +7,7 @@ package driver
import (
"context"
f3_kind "code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
@ -37,7 +38,7 @@ func (o *common) getPageSize() int {
return o.getTreeDriver().GetPageSize()
}
func (o *common) getKind() generic.Kind {
func (o *common) getKind() f3_kind.Kind {
return o.GetNode().GetKind()
}

View file

@ -8,8 +8,8 @@ import (
"context"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type container struct {
@ -30,7 +30,7 @@ func (o *container) FromFormat(content f3.Interface) {
func (o *container) Get(context.Context) bool { return true }
func (o *container) Put(ctx context.Context) generic.NodeID {
func (o *container) Put(ctx context.Context) f3_id.NodeID {
return o.upsert(ctx)
}
@ -38,6 +38,6 @@ func (o *container) Patch(ctx context.Context) {
o.upsert(ctx)
}
func (o *container) upsert(context.Context) generic.NodeID {
return generic.NewNodeID(o.getKind())
func (o *container) upsert(context.Context) f3_id.NodeID {
return f3_id.NewNodeID(o.getKind())
}

View file

@ -11,6 +11,8 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_kind "code.forgejo.org/f3/gof3/v3/kind"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
@ -19,16 +21,16 @@ import (
type forge struct {
generic.NullDriver
ownersKind map[string]generic.Kind
ownersKind map[string]f3_kind.Kind
}
func newForge() generic.NodeDriverInterface {
return &forge{
ownersKind: make(map[string]generic.Kind),
ownersKind: make(map[string]f3_kind.Kind),
}
}
func (o *forge) getOwnersKind(ctx context.Context, id string) generic.Kind {
func (o *forge) getOwnersKind(ctx context.Context, id string) f3_kind.Kind {
kind, ok := o.ownersKind[id]
if !ok {
user, err := user_model.GetUserByID(ctx, util.ParseInt(id))
@ -50,7 +52,7 @@ func (o *forge) getOwnersPath(ctx context.Context, id string) f3_tree.Path {
func (o *forge) Equals(context.Context, generic.NodeInterface) bool { return true }
func (o *forge) Get(context.Context) bool { return true }
func (o *forge) Put(context.Context) generic.NodeID { return generic.NewNodeID("forge") }
func (o *forge) Put(context.Context) f3_id.NodeID { return f3_id.NewNodeID("forge") }
func (o *forge) Patch(context.Context) {}
func (o *forge) Delete(context.Context) {}
func (o *forge) NewFormat() f3.Interface { return &f3.Forge{} }

View file

@ -17,6 +17,7 @@ import (
issue_service "code.gitea.io/gitea/services/issue"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -210,7 +211,7 @@ func updateIssueLabels(ctx context.Context, issueID int64, labels []*issues_mode
}
}
func (o *issue) Put(ctx context.Context) generic.NodeID {
func (o *issue) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
@ -232,7 +233,7 @@ func (o *issue) Put(ctx context.Context) generic.NodeID {
updateIssueLabels(ctx, o.forgejoIssue.ID, o.forgejoIssue.Labels)
o.Trace("issue created %d/%d", o.forgejoIssue.ID, o.forgejoIssue.Index)
return generic.NewNodeID(o.forgejoIssue.Index)
return f3_id.NewNodeID(o.forgejoIssue.Index)
}
func (o *issue) Delete(ctx context.Context) {

View file

@ -13,6 +13,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -86,7 +87,7 @@ func (o *label) Patch(ctx context.Context) {
}
}
func (o *label) Put(ctx context.Context) generic.NodeID {
func (o *label) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
@ -95,7 +96,7 @@ func (o *label) Put(ctx context.Context) generic.NodeID {
panic(err)
}
o.Trace("label created %d", o.forgejoLabel.ID)
return generic.NewNodeID(o.forgejoLabel.ID)
return f3_id.NewNodeID(o.forgejoLabel.ID)
}
func (o *label) Delete(ctx context.Context) {

View file

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -122,7 +123,7 @@ func (o *milestone) Patch(ctx context.Context) {
}
}
func (o *milestone) Put(ctx context.Context) generic.NodeID {
func (o *milestone) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
@ -131,7 +132,7 @@ func (o *milestone) Put(ctx context.Context) generic.NodeID {
panic(err)
}
o.Trace("milestone created %d", o.forgejoMilestone.ID)
return generic.NewNodeID(o.forgejoMilestone.ID)
return f3_id.NewNodeID(o.forgejoMilestone.ID)
}
func (o *milestone) Delete(ctx context.Context) {

View file

@ -13,6 +13,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -81,7 +82,7 @@ func (o *organization) Patch(ctx context.Context) {
}
}
func (o *organization) Put(ctx context.Context) generic.NodeID {
func (o *organization) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
@ -94,7 +95,7 @@ func (o *organization) Put(ctx context.Context) generic.NodeID {
panic(err)
}
return generic.NewNodeID(o.forgejoOrganization.ID)
return f3_id.NewNodeID(o.forgejoOrganization.ID)
}
func (o *organization) Delete(ctx context.Context) {

View file

@ -12,6 +12,7 @@ import (
org_model "code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
@ -36,13 +37,13 @@ func (o *organizations) ListPage(ctx context.Context, page int) generic.Children
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(organizations...)...)
}
func (o *organizations) GetIDFromName(ctx context.Context, name string) generic.NodeID {
func (o *organizations) GetIDFromName(ctx context.Context, name string) f3_id.NodeID {
organization, err := org_model.GetOrgByName(ctx, name)
if err != nil {
panic(fmt.Errorf("GetOrganizationByName: %v", err))
}
return generic.NewNodeID(organization.ID)
return f3_id.NewNodeID(organization.ID)
}
func newOrganizations() generic.NodeDriverInterface {

View file

@ -14,6 +14,7 @@ import (
repo_service "code.gitea.io/gitea/services/repository"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -121,7 +122,7 @@ func (o *project) Patch(ctx context.Context) {
}
}
func (o *project) Put(ctx context.Context) generic.NodeID {
func (o *project) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
@ -166,7 +167,7 @@ func (o *project) Put(ctx context.Context) generic.NodeID {
o.forgejoProject = repo
o.Trace("project created %d", o.forgejoProject.ID)
}
return generic.NewNodeID(o.forgejoProject.ID)
return f3_id.NewNodeID(o.forgejoProject.ID)
}
func (o *project) Delete(ctx context.Context) {

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
@ -19,18 +20,18 @@ type projects struct {
container
}
func (o *projects) GetIDFromName(ctx context.Context, name string) generic.NodeID {
func (o *projects) GetIDFromName(ctx context.Context, name string) f3_id.NodeID {
owner := f3_tree.GetOwnerName(o.GetNode())
forgejoProject, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, name)
if repo_model.IsErrRepoNotExist(err) {
return generic.NilID
return f3_id.NilID
}
if err != nil {
panic(fmt.Errorf("error GetRepositoryByOwnerAndName(%s, %s): %v", owner, name, err))
}
return generic.NewNodeID(forgejoProject.ID)
return f3_id.NewNodeID(forgejoProject.ID)
}
func (o *projects) ListPage(ctx context.Context, page int) generic.ChildrenSlice {

View file

@ -18,6 +18,8 @@ import (
issue_service "code.gitea.io/gitea/services/issue"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_path "code.forgejo.org/f3/gof3/v3/path"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -51,7 +53,7 @@ func (o *pullRequest) repositoryToReference(ctx context.Context, repository *rep
if repository == nil {
panic("unexpected nil repository")
}
forge := o.getTree().GetRoot().GetChild(generic.NewNodeID(f3_tree.KindForge)).GetDriver().(*forge)
forge := o.getTree().GetRoot().GetChild(f3_id.NewNodeID(f3_tree.KindForge)).GetDriver().(*forge)
owners := forge.getOwnersPath(ctx, fmt.Sprintf("%d", repository.OwnerID))
return f3_tree.NewRepositoryReference(owners.String(), repository.OwnerID, repository.ID)
}
@ -61,7 +63,7 @@ func (o *pullRequest) referenceToRepository(reference *f3.Reference) int64 {
if reference.Get() == "../../repository/vcs" {
project = f3_tree.GetProjectID(o.GetNode())
} else {
p := f3_tree.ToPath(generic.PathAbsolute(o.GetNode().GetCurrentPath().String(), reference.Get()))
p := f3_tree.ToPath(f3_path.PathAbsolute(generic.NewElementNode, o.GetNode().GetCurrentPath().String(), reference.Get()))
o.Trace("%v %v", o.GetNode().GetCurrentPath().String(), p)
_, project = p.OwnerAndProjectID()
}
@ -237,7 +239,7 @@ func (o *pullRequest) GetPullRequestRef() string {
return fmt.Sprintf("refs/pull/%s/head", o.GetNativeID())
}
func (o *pullRequest) Put(ctx context.Context) generic.NodeID {
func (o *pullRequest) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
@ -289,7 +291,7 @@ func (o *pullRequest) Put(ctx context.Context) generic.NodeID {
}
o.Trace("pullRequest created %d/%d", o.forgejoPullRequest.ID, o.forgejoPullRequest.Index)
return generic.NewNodeID(o.forgejoPullRequest.Index)
return f3_id.NewNodeID(o.forgejoPullRequest.Index)
}
func (o *pullRequest) Delete(ctx context.Context) {

View file

@ -13,6 +13,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -87,7 +88,7 @@ func (o *reaction) Patch(ctx context.Context) {
}
}
func (o *reaction) Put(ctx context.Context) generic.NodeID {
func (o *reaction) Put(ctx context.Context) f3_id.NodeID {
o.Error("%v", o.forgejoReaction.User)
sess := db.GetEngine(ctx)
@ -115,7 +116,7 @@ func (o *reaction) Put(ctx context.Context) generic.NodeID {
panic(err)
}
o.Trace("reaction created %d", o.forgejoReaction.ID)
return generic.NewNodeID(o.forgejoReaction.ID)
return f3_id.NewNodeID(o.forgejoReaction.ID)
}
func (o *reaction) Delete(ctx context.Context) {

View file

@ -17,6 +17,7 @@ import (
release_service "code.gitea.io/gitea/services/release"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -115,7 +116,7 @@ func (o *release) Patch(ctx context.Context) {
}
}
func (o *release) Put(ctx context.Context) generic.NodeID {
func (o *release) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
@ -133,7 +134,7 @@ func (o *release) Put(ctx context.Context) generic.NodeID {
panic(err)
}
o.Trace("release created %d", o.forgejoRelease.ID)
return generic.NewNodeID(o.forgejoRelease.ID)
return f3_id.NewNodeID(o.forgejoRelease.ID)
}
func (o *release) Delete(ctx context.Context) {

View file

@ -11,6 +11,7 @@ import (
"code.forgejo.org/f3/gof3/v3/f3"
helpers_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/repository"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
@ -57,7 +58,7 @@ func (o *repository) Get(ctx context.Context) bool {
return o.h.Get(ctx)
}
func (o *repository) Put(ctx context.Context) generic.NodeID {
func (o *repository) Put(ctx context.Context) f3_id.NodeID {
return o.upsert(ctx)
}
@ -65,10 +66,10 @@ func (o *repository) Patch(ctx context.Context) {
o.upsert(ctx)
}
func (o *repository) upsert(ctx context.Context) generic.NodeID {
func (o *repository) upsert(ctx context.Context) f3_id.NodeID {
o.Trace("%s", o.GetNativeID())
o.h.Upsert(ctx, o.f)
return generic.NewNodeID(o.f.Name)
return f3_id.NewNodeID(o.f.Name)
}
func (o *repository) SetFetchFunc(fetchFunc func(ctx context.Context, destination string, internalRefs []string)) {

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -134,7 +135,7 @@ func (o *review) Patch(ctx context.Context) {
}
}
func (o *review) Put(ctx context.Context) generic.NodeID {
func (o *review) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
@ -153,7 +154,7 @@ func (o *review) Put(ctx context.Context) generic.NodeID {
panic(err)
}
o.Trace("review created %d", o.forgejoReview.ID)
return generic.NewNodeID(o.forgejoReview.ID)
return f3_id.NewNodeID(o.forgejoReview.ID)
}
func (o *review) Delete(ctx context.Context) {

View file

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -115,7 +116,7 @@ func (o *reviewComment) Patch(ctx context.Context) {
}
}
func (o *reviewComment) Put(ctx context.Context) generic.NodeID {
func (o *reviewComment) Put(ctx context.Context) f3_id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
@ -125,7 +126,7 @@ func (o *reviewComment) Put(ctx context.Context) generic.NodeID {
panic(err)
}
o.Trace("reviewComment created %d", o.forgejoReviewComment.ID)
return generic.NewNodeID(o.forgejoReviewComment.ID)
return f3_id.NewNodeID(o.forgejoReviewComment.ID)
}
func (o *reviewComment) Delete(ctx context.Context) {

View file

@ -8,6 +8,7 @@ import (
"context"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
@ -33,8 +34,8 @@ func (o *root) ToFormat() f3.Interface {
func (o *root) Get(context.Context) bool { return true }
func (o *root) Put(context.Context) generic.NodeID {
return generic.NilID
func (o *root) Put(context.Context) f3_id.NodeID {
return f3_id.NilID
}
func (o *root) Patch(context.Context) {

View file

@ -9,8 +9,8 @@ import (
driver_options "code.gitea.io/gitea/services/f3/driver/options"
f3_kind "code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/options"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forge_test "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
)
@ -22,8 +22,8 @@ func (o *forgeTest) NewOptions(t *testing.T) options.Interface {
return newTestOptions(t)
}
func (o *forgeTest) GetExceptions() []generic.Kind {
return []generic.Kind{}
func (o *forgeTest) GetExceptions() []f3_kind.Kind {
return []f3_kind.Kind{}
}
func (o *forgeTest) GetNonTestUsers() []string {

View file

@ -12,6 +12,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -79,14 +80,14 @@ func (o *topic) Patch(ctx context.Context) {
}
}
func (o *topic) Put(ctx context.Context) generic.NodeID {
func (o *topic) Put(ctx context.Context) f3_id.NodeID {
sess := db.GetEngine(ctx)
if _, err := sess.Insert(o.forgejoTopic); err != nil {
panic(err)
}
o.Trace("topic created %d", o.forgejoTopic.ID)
return generic.NewNodeID(o.forgejoTopic.ID)
return f3_id.NewNodeID(o.forgejoTopic.ID)
}
func (o *topic) Delete(ctx context.Context) {

View file

@ -10,6 +10,7 @@ import (
forgejo_options "code.gitea.io/gitea/services/f3/driver/options"
f3_kind "code.forgejo.org/f3/gof3/v3/kind"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
@ -24,7 +25,7 @@ func (o *treeDriver) Init() {
o.NullTreeDriver.Init()
}
func (o *treeDriver) Factory(ctx context.Context, kind generic.Kind) generic.NodeDriverInterface {
func (o *treeDriver) Factory(ctx context.Context, kind f3_kind.Kind) generic.NodeDriverInterface {
switch kind {
case f3_tree.KindForge:
return newForge()
@ -88,7 +89,7 @@ func (o *treeDriver) Factory(ctx context.Context, kind generic.Kind) generic.Nod
return newRepositories()
case f3_tree.KindRepository:
return newRepository(ctx)
case generic.KindRoot:
case f3_kind.KindRoot:
return newRoot(o.GetTree().(f3_tree.TreeInterface).NewFormat(kind))
default:
panic(fmt.Errorf("unexpected kind %s", kind))

View file

@ -14,6 +14,7 @@ import (
user_service "code.gitea.io/gitea/services/user"
"code.forgejo.org/f3/gof3/v3/f3"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
@ -96,9 +97,9 @@ func (o *user) Get(ctx context.Context) bool {
func (o *user) Patch(context.Context) {
}
func (o *user) Put(ctx context.Context) generic.NodeID {
func (o *user) Put(ctx context.Context) f3_id.NodeID {
if user := getSystemUserByName(o.forgejoUser.Name); user != nil {
return generic.NewNodeID(user.ID)
return f3_id.NewNodeID(user.ID)
}
o.forgejoUser.LowerName = strings.ToLower(o.forgejoUser.Name)
@ -111,7 +112,7 @@ func (o *user) Put(ctx context.Context) generic.NodeID {
panic(err)
}
return generic.NewNodeID(o.forgejoUser.ID)
return f3_id.NewNodeID(o.forgejoUser.ID)
}
func (o *user) Delete(ctx context.Context) {

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
f3_id "code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
@ -34,13 +35,13 @@ func (o *users) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(users...)...)
}
func (o *users) GetIDFromName(ctx context.Context, name string) generic.NodeID {
func (o *users) GetIDFromName(ctx context.Context, name string) f3_id.NodeID {
user, err := user_model.GetUserByName(ctx, name)
if err != nil {
panic(fmt.Errorf("GetUserByName: %v", err))
}
return generic.NewNodeID(user.ID)
return f3_id.NewNodeID(user.ID)
}
func newUsers() generic.NodeDriverInterface {

View file

@ -1381,10 +1381,8 @@ func GetWhitespaceFlag(whitespaceBehavior string) git.TrustedCmdArgs {
"ignore-eol": {"--ignore-space-at-eol"},
"show-all": nil,
}
if flag, ok := whitespaceFlags[whitespaceBehavior]; ok {
return flag
}
log.Warn("unknown whitespace behavior: %q, default to 'show-all'", whitespaceBehavior)
return nil
}

View file

@ -23,6 +23,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
issue_service "code.gitea.io/gitea/services/issue"
notify_service "code.gitea.io/gitea/services/notify"
pull_service "code.gitea.io/gitea/services/pull"
@ -133,23 +134,26 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
} else { // is new tag
newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
if err != nil {
return fmt.Errorf("gitRepo.GetCommit(%s) in %s/%s[%d]: %w", opts.NewCommitID, repo.OwnerName, repo.Name, repo.ID, err)
// in case there is dirty data, for example, the "github.com/git/git" repository has tags pointing to non-existing commits
if !errors.Is(err, util.ErrNotExist) {
log.Error("Unable to get tag commit: gitRepo.GetCommit(%s) in %s/%s[%d]: %v", opts.NewCommitID, repo.OwnerName, repo.Name, repo.ID, err)
}
} else {
commits := repo_module.NewPushCommits()
commits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
commits.CompareURL = repo.ComposeCompareURL(objectFormat.EmptyObjectID().String(), opts.NewCommitID)
notify_service.PushCommits(
ctx, pusher, repo,
&repo_module.PushUpdateOptions{
RefFullName: opts.RefFullName,
OldCommitID: objectFormat.EmptyObjectID().String(),
NewCommitID: opts.NewCommitID,
}, commits)
addTags = append(addTags, tagName)
notify_service.CreateRef(ctx, pusher, repo, opts.RefFullName, opts.NewCommitID)
}
commits := repo_module.NewPushCommits()
commits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
commits.CompareURL = repo.ComposeCompareURL(objectFormat.EmptyObjectID().String(), opts.NewCommitID)
notify_service.PushCommits(
ctx, pusher, repo,
&repo_module.PushUpdateOptions{
RefFullName: opts.RefFullName,
OldCommitID: objectFormat.EmptyObjectID().String(),
NewCommitID: opts.NewCommitID,
}, commits)
addTags = append(addTags, tagName)
notify_service.CreateRef(ctx, pusher, repo, opts.RefFullName, opts.NewCommitID)
}
} else if opts.RefFullName.IsBranch() {
if pusher == nil || pusher.ID != opts.PusherID {

View file

@ -20,12 +20,6 @@
<link rel="icon" href="{{AssetUrlPrefix}}/img/favicon.svg" type="image/svg+xml">
<link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png">
{{template "base/head_script" .}}
<noscript>
<style>
.dropdown:hover > .menu { display: block; }
.ui.secondary.menu .dropdown.item > .menu { margin-top: 0; }
</style>
</noscript>
{{template "shared/user/mention_highlight" .}}
{{template "base/head_opengraph" .}}
{{template "base/head_style" .}}

View file

@ -8628,6 +8628,24 @@
"description": "page size of results",
"name": "limit",
"in": "query"
},
{
"enum": [
"relevance",
"latest",
"oldest",
"recentupdate",
"leastupdate",
"mostcomment",
"leastcomment",
"nearduedate",
"farduedate"
],
"type": "string",
"default": "latest",
"description": "Type of sort",
"name": "sort",
"in": "query"
}
],
"responses": {
@ -19343,8 +19361,28 @@
"in": "query"
},
{
"enum": [
"name",
"id",
"newest",
"oldest",
"recentupdate",
"leastupdate",
"reversealphabetically",
"alphabetically",
"reversesize",
"size",
"reversegitsize",
"gitsize",
"reverselfssize",
"lfssize",
"moststars",
"feweststars",
"mostforks",
"fewestforks"
],
"type": "string",
"description": "order the repositories by name (default), id, or size",
"description": "order the repositories",
"name": "order_by",
"in": "query"
}

View file

@ -74,6 +74,50 @@ func TestAPIListIssues(t *testing.T) {
if assert.Len(t, apiIssues, 1) {
assert.EqualValues(t, 1, apiIssues[0].ID)
}
t.Run("Sort", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
link.RawQuery = url.Values{"token": {token}, "sort": {"oldest"}}.Encode()
resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 4) {
assert.EqualValues(t, 1, apiIssues[0].ID)
assert.EqualValues(t, 2, apiIssues[1].ID)
assert.EqualValues(t, 3, apiIssues[2].ID)
assert.EqualValues(t, 11, apiIssues[3].ID)
}
link.RawQuery = url.Values{"token": {token}, "sort": {"newest"}}.Encode()
resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 4) {
assert.EqualValues(t, 11, apiIssues[0].ID)
assert.EqualValues(t, 3, apiIssues[1].ID)
assert.EqualValues(t, 2, apiIssues[2].ID)
assert.EqualValues(t, 1, apiIssues[3].ID)
}
link.RawQuery = url.Values{"token": {token}, "sort": {"recentupdate"}}.Encode()
resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 4) {
assert.EqualValues(t, 11, apiIssues[0].ID)
assert.EqualValues(t, 1, apiIssues[1].ID)
assert.EqualValues(t, 2, apiIssues[2].ID)
assert.EqualValues(t, 3, apiIssues[3].ID)
}
link.RawQuery = url.Values{"token": {token}, "sort": {"leastupdate"}}.Encode()
resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 4) {
assert.EqualValues(t, 3, apiIssues[0].ID)
assert.EqualValues(t, 2, apiIssues[1].ID)
assert.EqualValues(t, 1, apiIssues[2].ID)
assert.EqualValues(t, 11, apiIssues[3].ID)
}
})
}
func TestAPIListIssuesPublicOnly(t *testing.T) {

View file

@ -201,6 +201,137 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==`)
holaChecksum := fmt.Sprintf("%x", sha256.Sum256(holaGemContent))
binaryPackageName := "debug_inspector"
binaryPackageVersion := "0.0.3"
binaryRubyGemsRequirements := "ruby:~> 3.1.0"
binaryGemContent, _ := base64.StdEncoding.DecodeString(`bWV0YWRhdGEuZ3oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA0NDQAMDAwMDAw
MAAwMDAwMDAwADAwMDAwMDAxMTM0ADE0NDQyNjI3NzcyADAxMzQ1NAAgMAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMHdoZWVsAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAd2hlZWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAw
MDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf
iwgA+i+LZAIDxVRbb9MwFH73rzB9KTzksnYrkyUmKm1CRaqECuIBhCInOU28xRdsZ7Sa4LdznN7W
bExcHkgi5ficz+d852JHUUSf2TZfJzq/hsKzNyAZe2+gEEtRcC+0IopLYLSEvK0yoRzavLbkFqxD
K3tk+8eNiVC6B6VxGo+JabhfaisZXZ1Psslp1AjVriLZuobw1tfaOkYi+lbXis65sPR5zpVwHuxS
gCpfBJC28LUVFhjJhSqFZRT/pADrs6LmAmN9/kJK7pHyKD15GaVnUXpO05R1X5zuHvqJlGDQK6hC
QBf3YSaXO8Qak9nUQQolPDiPii0TCco/VobFwYzgIziGC5oI38HFq0G3CMunarl59hUdng1R6dcG
ObESbqHRZhvKWLDQAHdoWvLGwaET2RGJ/0K6BFdYYXynmNIFbqbfLDcGLMXhoL4GOl/M6ChO+0NH
p+9mBCQXTWjXtUXJvq7COi60JLCCovU8b7CdYQpg5UGFwPul5ZktdZEtxQ6zkdAbEg8ySour6eX8
KpZlkPkNbNU9LnEFMohoQcdJz5qgrtBqGdscAY3I+4BfGpJtsQKg1hIMr7CNtffGsSSphK/bPGSb
7I5G3wFpRIFpb7Kazz4QCZ7jgeCM3n0nRjsfsJ43TSbBueCedEXRXU82Zdm2PTPc150jZLpTlllo
ePbEBXA8RP0R6gbox8WA/N747OOM45M4PSYRevCPRHaT/AdEhumQHJ+jULEHdMbxOB5NiBOVEqrK
bmDNiLt/sx6gp8S1UnK7/qsTEa6jeyP9E+HiLnPUBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGF0
YS50YXIuZ3oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA0NDQAMDAwMDAwMAAw
MDAwMDAwADAwMDAwMDA1MzMzADE0NDQyNjI3NzcyADAxMzM3NwAgMAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMHdoZWVsAAAAAAAAAAAAAAAAAAAAAAAA
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
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+LCAD6L4tkAgNl
kD2yFEAIhPM9xV5grWH4f5mZuSeAAYxMrBd5escNNaOrmuajX6/X4/u3r5vl4/F8/uzPqPiMLz9+
fzwJjNOXQnXMQG2UxSyqlbBnU0v7VVtpLZeNBsO8dMgBO+ncvHfWZ/x6500p68qBNVRwl5Y5AJep
Z0LobkCdNR5XnVkeiyDF0sM7kf5yMuz/ODmDGF2Sz+6oMt5mMb5zT/PQkcM3NesOZYI5MzsADNzo
PbhIKiAsZCrqa5IcQ7ZAG6Ktw+UXT1dIYIeTph5BaF20//2TSsE1OfAscfPUe1zJLiSagTIZUCLe
5lCp02ap4MA5xT7iRbhh9Tbqi3MpZBBIvGtOlF/vuf8dWJs5tNWlSy6ajvbt1R9/AA8NE1rUAQAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==`)
binaryChecksum := fmt.Sprintf("%x", sha256.Sum256(binaryGemContent))
root := fmt.Sprintf("/api/packages/%s/rubygems", user.Name)
uploadFile := func(t *testing.T, content []byte, expectedStatus int) {
@ -234,6 +365,8 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==`)
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
require.NoError(t, err)
assert.Equal(t, int64(4608), pb.Size)
assert.Equal(t, "ruby", pd.Metadata.(*rubygems.Metadata).Platform)
})
t.Run("UploadExists", func(t *testing.T) {
@ -322,6 +455,33 @@ gAAAAP//MS06Gw==`)
sep, holaPackageVersion, holaPackageDependency, holaChecksum, holaRubyGemsRequirements)
assert.Equal(t, expected, resp.Body.String())
})
t.Run("UploadBinary", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
uploadFile(t, binaryGemContent, http.StatusCreated)
pvs, err := packages.GetVersionsByPackageName(db.DefaultContext, user.ID, packages.TypeRubyGems, binaryPackageName)
require.NoError(t, err)
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
require.NoError(t, err)
assert.Equal(t, binaryPackageName, pd.Package.Name)
assert.Equal(t, "x86_64-linux-musl", pd.Metadata.(*rubygems.Metadata).Platform)
})
t.Run("BinaryPackageInfo", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", fmt.Sprintf("%s/info/%s", root, binaryPackageName)).
AddBasicAuth(user.Name)
resp := MakeRequest(t, req, http.StatusOK)
expected := fmt.Sprintf("%s\n%s-x86_64-linux-musl |checksum:%s,%s\n",
sep, binaryPackageVersion, binaryChecksum, binaryRubyGemsRequirements)
assert.Equal(t, expected, resp.Body.String())
})
t.Run("Versions", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
@ -334,12 +494,16 @@ gAAAAP//MS06Gw==`)
holaInfoReq := NewRequest(t, "GET", fmt.Sprintf("%s/info/%s", root, holaPackageName)).
AddBasicAuth(user.Name)
holaInfoResp := MakeRequest(t, holaInfoReq, http.StatusOK)
binaryInfoReq := NewRequest(t, "GET", fmt.Sprintf("%s/info/%s", root, binaryPackageName)).
AddBasicAuth(user.Name)
binaryInfoResp := MakeRequest(t, binaryInfoReq, http.StatusOK)
// expected := fmt.Sprintf("%s\n%s %s %x\n",
// sep, packageName, packageVersion, md5.Sum(infoResp.Body.Bytes()))
lines := versionsResp.Body.String()
assert.ElementsMatch(t, strings.Split(lines, "\n"), []string{
sep,
fmt.Sprintf("%s %s_x86_64-linux-musl %x", binaryPackageName, binaryPackageVersion, md5.Sum(binaryInfoResp.Body.Bytes())),
fmt.Sprintf("%s %s %x", packageName, packageVersion, md5.Sum(infoResp.Body.Bytes())),
fmt.Sprintf("%s %s %x", holaPackageName, holaPackageVersion, md5.Sum(holaInfoResp.Body.Bytes())),
"",
@ -361,6 +525,21 @@ gAAAAP//MS06Gw==`)
MakeRequest(t, req, http.StatusOK)
})
t.Run("DeleteBinary", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
body := bytes.Buffer{}
writer := multipart.NewWriter(&body)
writer.WriteField("gem_name", binaryPackageName)
writer.WriteField("version", binaryPackageVersion)
writer.Close()
req := NewRequestWithBody(t, "DELETE", fmt.Sprintf("%s/api/v1/gems/yank", root), &body).
SetHeader("Content-Type", writer.FormDataContentType()).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
})
t.Run("Delete", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()

View file

@ -764,3 +764,58 @@ func TestAPIRepoCommitPull(t *testing.T) {
req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/not-a-commit/pull")
MakeRequest(t, req, http.StatusNotFound)
}
func TestAPIListOwnRepoSorting(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopeReadUser)
t.Run("No sorting", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/repos").AddTokenAuth(token), http.StatusOK)
})
t.Run("ID sorting", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
var repos []api.Repository
resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/repos?limit=2&order_by=id").AddTokenAuth(token), http.StatusOK)
DecodeJSON(t, resp, &repos)
assert.Len(t, repos, 2)
assert.EqualValues(t, 1, repos[0].ID)
assert.EqualValues(t, 2, repos[1].ID)
})
t.Run("Name sorting", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
var repos []api.Repository
resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/repos?limit=2&order_by=name").AddTokenAuth(token), http.StatusOK)
DecodeJSON(t, resp, &repos)
assert.Len(t, repos, 2)
assert.EqualValues(t, "big_test_private_4", repos[0].Name)
// Postgres doesn't do ascii sorting.
if setting.Database.Type.IsPostgreSQL() {
assert.EqualValues(t, "commitsonpr", repos[1].Name)
} else {
assert.EqualValues(t, "commits_search_test", repos[1].Name)
}
})
t.Run("Reverse alphabetic sorting", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
var repos []api.Repository
resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/repos?limit=2&order_by=reversealphabetically").AddTokenAuth(token), http.StatusOK)
DecodeJSON(t, resp, &repos)
assert.Len(t, repos, 2)
assert.EqualValues(t, "utf8", repos[0].Name)
assert.EqualValues(t, "test_workflows", repos[1].Name)
})
}

View file

@ -0,0 +1,91 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package integration
import (
"net/http"
"regexp"
"testing"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestReleaseFeed(t *testing.T) {
defer tests.PrepareTestEnv(t)()
normalize := func(body string) string {
// Remove port.
body = regexp.MustCompile(`localhost:\d+`).ReplaceAllString(body, "localhost")
// date is timezone dependent.
body = regexp.MustCompile(`<pubDate>.*</pubDate>`).ReplaceAllString(body, "<pubDate></pubDate>")
body = regexp.MustCompile(`<updated>.*</updated>`).ReplaceAllString(body, "<updated></updated>")
return body
}
t.Run("RSS feed", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
resp := MakeRequest(t, NewRequest(t, "GET", "/user2/repo1/releases.rss"), http.StatusOK)
assert.EqualValues(t, `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Releases for user2/repo1</title>
<link>http://localhost/user2/repo1/release</link>
<description></description>
<pubDate></pubDate>
<item>
<title>pre-release</title>
<link>http://localhost/user2/repo1/releases/tag/v1.0</link>
<description></description>
<content:encoded><![CDATA[<p dir="auto">some text for a pre release</p>
]]></content:encoded>
<author>user2</author>
<guid>5: http://localhost/user2/repo1/releases/tag/v1.0</guid>
<pubDate></pubDate>
</item>
<item>
<title>testing-release</title>
<link>http://localhost/user2/repo1/releases/tag/v1.1</link>
<description></description>
<author>user2</author>
<guid>1: http://localhost/user2/repo1/releases/tag/v1.1</guid>
<pubDate></pubDate>
</item>
</channel>
</rss>`, normalize(resp.Body.String()))
})
t.Run("Atom feed", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
resp := MakeRequest(t, NewRequest(t, "GET", "/user2/repo1/releases.atom"), http.StatusOK)
assert.EqualValues(t, `<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">
<title>Releases for user2/repo1</title>
<id>http://localhost/user2/repo1/release</id>
<updated></updated>
<link href="http://localhost/user2/repo1/release"></link>
<entry>
<title>pre-release</title>
<updated></updated>
<id>5: http://localhost/user2/repo1/releases/tag/v1.0</id>
<content type="html">&lt;p dir=&#34;auto&#34;&gt;some text for a pre release&lt;/p&gt;&#xA;</content>
<link href="http://localhost/user2/repo1/releases/tag/v1.0" rel="alternate"></link>
<author>
<name>user2</name>
<email>user2@noreply.example.org</email>
</author>
</entry>
<entry>
<title>testing-release</title>
<updated></updated>
<id>1: http://localhost/user2/repo1/releases/tag/v1.1</id>
<link href="http://localhost/user2/repo1/releases/tag/v1.1" rel="alternate"></link>
<author>
<name>user2</name>
<email>user2@noreply.example.org</email>
</author>
</entry>
</feed>`, normalize(resp.Body.String()))
})
}

View file

@ -1249,9 +1249,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001703",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz",
"integrity": "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==",
"version": "1.0.30001705",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001705.tgz",
"integrity": "sha512-S0uyMMiYvA7CxNgomYBwwwPUnWzFD83f3B1ce5jHUfHTH//QL6hHsreI8RVC5606R4ssqravelYO5TU6t8sEyg==",
"funding": [
{
"type": "opencollective",
@ -2005,9 +2005,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.113",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.113.tgz",
"integrity": "sha512-wjT2O4hX+wdWPJ76gWSkMhcHAV2PTMX+QetUCPYEdCIe+cxmgzzSSiGRCKW8nuh4mwKZlpv0xvoW7OF2X+wmHg==",
"version": "1.5.119",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.119.tgz",
"integrity": "sha512-Ku4NMzUjz3e3Vweh7PhApPrZSS4fyiCIbcIrG9eKrriYVLmbMepETR/v6SU7xPm98QTqMSYiCwfO89QNjXLkbQ==",
"license": "ISC"
},
"node_modules/emoji-regex": {