Merge branch 'main' into allow-force-push-protected-branches

This commit is contained in:
Henry Goodman
2024-01-15 18:33:43 +11:00
committed by GitHub
110 changed files with 1191 additions and 770 deletions
+2 -2
View File
@@ -157,10 +157,10 @@ func runRepoSyncReleases(_ *cli.Context) error {
} }
func getReleaseCount(ctx context.Context, id int64) (int64, error) { func getReleaseCount(ctx context.Context, id int64) (int64, error) {
return repo_model.GetReleaseCountByRepoID( return db.Count[repo_model.Release](
ctx, ctx,
id,
repo_model.FindReleasesOptions{ repo_model.FindReleasesOptions{
RepoID: id,
IncludeTags: true, IncludeTags: true,
}, },
) )
+11 -8
View File
@@ -27,17 +27,18 @@ The following examples use `dnf`.
To register the RPM registry add the url to the list of known apt sources: To register the RPM registry add the url to the list of known apt sources:
```shell ```shell
dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm.repo dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm/{group}.repo
``` ```
| Placeholder | Description | | Placeholder | Description |
| ----------- | ----------- | | ----------- |----------------------------------------------------|
| `owner` | The owner of the package. | | `owner` | The owner of the package. |
| `group` | Everything, e.g. `el7`, `rocky/el9` , `test/fc38`.|
If the registry is private, provide credentials in the url. You can use a password or a [personal access token](development/api-usage.md#authentication): If the registry is private, provide credentials in the url. You can use a password or a [personal access token](development/api-usage.md#authentication):
```shell ```shell
dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm.repo dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm/{group}.repo
``` ```
You have to add the credentials to the urls in the `rpm.repo` file in `/etc/yum.repos.d` too. You have to add the credentials to the urls in the `rpm.repo` file in `/etc/yum.repos.d` too.
@@ -47,19 +48,20 @@ You have to add the credentials to the urls in the `rpm.repo` file in `/etc/yum.
To publish a RPM package (`*.rpm`), perform a HTTP PUT operation with the package content in the request body. To publish a RPM package (`*.rpm`), perform a HTTP PUT operation with the package content in the request body.
``` ```
PUT https://gitea.example.com/api/packages/{owner}/rpm/upload PUT https://gitea.example.com/api/packages/{owner}/rpm/{group}/upload
``` ```
| Parameter | Description | | Parameter | Description |
| --------- | ----------- | | --------- | ----------- |
| `owner` | The owner of the package. | | `owner` | The owner of the package. |
| `group` | Everything, e.g. `el7`, `rocky/el9` , `test/fc38`.|
Example request using HTTP Basic authentication: Example request using HTTP Basic authentication:
```shell ```shell
curl --user your_username:your_password_or_token \ curl --user your_username:your_password_or_token \
--upload-file path/to/file.rpm \ --upload-file path/to/file.rpm \
https://gitea.example.com/api/packages/testuser/rpm/upload https://gitea.example.com/api/packages/testuser/rpm/centos/el7/upload
``` ```
If you are using 2FA or OAuth use a [personal access token](development/api-usage.md#authentication) instead of the password. If you are using 2FA or OAuth use a [personal access token](development/api-usage.md#authentication) instead of the password.
@@ -78,12 +80,13 @@ The server responds with the following HTTP Status codes.
To delete an RPM package perform a HTTP DELETE operation. This will delete the package version too if there is no file left. To delete an RPM package perform a HTTP DELETE operation. This will delete the package version too if there is no file left.
``` ```
DELETE https://gitea.example.com/api/packages/{owner}/rpm/{package_name}/{package_version}/{architecture} DELETE https://gitea.example.com/api/packages/{owner}/rpm/{group}/package/{package_name}/{package_version}/{architecture}
``` ```
| Parameter | Description | | Parameter | Description |
| ----------------- | ----------- | |-------------------|----------------------------|
| `owner` | The owner of the package. | | `owner` | The owner of the package. |
| `group` | The package group . |
| `package_name` | The package name. | | `package_name` | The package name. |
| `package_version` | The package version. | | `package_version` | The package version. |
| `architecture` | The package architecture. | | `architecture` | The package architecture. |
@@ -92,7 +95,7 @@ Example request using HTTP Basic authentication:
```shell ```shell
curl --user your_username:your_token_or_password -X DELETE \ curl --user your_username:your_token_or_password -X DELETE \
https://gitea.example.com/api/packages/testuser/rpm/test-package/1.0.0/x86_64 https://gitea.example.com/api/packages/testuser/rpm/centos/el7/package/test-package/1.0.0/x86_64
``` ```
The server responds with the following HTTP Status codes. The server responds with the following HTTP Status codes.
+11 -8
View File
@@ -27,17 +27,18 @@ menu:
要注册RPM注册表,请将 URL 添加到已知 `apt` 源列表中: 要注册RPM注册表,请将 URL 添加到已知 `apt` 源列表中:
```shell ```shell
dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm.repo dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm/{group}.repo
``` ```
| 占位符 | 描述 | | 占位符 | 描述 |
| ------- | -------------- | | ------- |--------------------------------------|
| `owner` | 软件包的所有者 | | `owner` | 软件包的所有者 |
| `group` | 任何名称,例如 `centos/7``el-7``fc38` |
如果注册表是私有的,请在URL中提供凭据。您可以使用密码或[个人访问令牌](development/api-usage.md#通过-api-认证) 如果注册表是私有的,请在URL中提供凭据。您可以使用密码或[个人访问令牌](development/api-usage.md#通过-api-认证)
```shell ```shell
dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm.repo dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm/{group}.repo
``` ```
您还必须将凭据添加到 `/etc/yum.repos.d` 中的 `rpm.repo` 文件中的URL中。 您还必须将凭据添加到 `/etc/yum.repos.d` 中的 `rpm.repo` 文件中的URL中。
@@ -47,19 +48,20 @@ dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.
要发布RPM软件包(`*.rpm`),请执行带有软件包内容的 HTTP `PUT` 操作。 要发布RPM软件包(`*.rpm`),请执行带有软件包内容的 HTTP `PUT` 操作。
``` ```
PUT https://gitea.example.com/api/packages/{owner}/rpm/upload PUT https://gitea.example.com/api/packages/{owner}/rpm/{group}/upload
``` ```
| 参数 | 描述 | | 参数 | 描述 |
| ------- | -------------- | | ------- |--------------|
| `owner` | 软件包的所有者 | | `owner` | 软件包的所有者 |
| `group` | 软件包自定义分组名称 |
使用HTTP基本身份验证的示例请求: 使用HTTP基本身份验证的示例请求:
```shell ```shell
curl --user your_username:your_password_or_token \ curl --user your_username:your_password_or_token \
--upload-file path/to/file.rpm \ --upload-file path/to/file.rpm \
https://gitea.example.com/api/packages/testuser/rpm/upload https://gitea.example.com/api/packages/testuser/rpm/centos/el7/version/upload
``` ```
如果您使用 2FA 或 OAuth,请使用[个人访问令牌](development/api-usage.md#通过-api-认证)替代密码。您无法将具有相同名称的文件两次发布到软件包中。您必须先删除现有的软件包版本。 如果您使用 2FA 或 OAuth,请使用[个人访问令牌](development/api-usage.md#通过-api-认证)替代密码。您无法将具有相同名称的文件两次发布到软件包中。您必须先删除现有的软件包版本。
@@ -77,12 +79,13 @@ curl --user your_username:your_password_or_token \
要删除 RPM 软件包,请执行 HTTP `DELETE` 操作。如果没有文件剩余,这也将删除软件包版本。 要删除 RPM 软件包,请执行 HTTP `DELETE` 操作。如果没有文件剩余,这也将删除软件包版本。
``` ```
DELETE https://gitea.example.com/api/packages/{owner}/rpm/{package_name}/{package_version}/{architecture} DELETE https://gitea.example.com/api/packages/{owner}/rpm/{group}/package/{package_name}/{package_version}/{architecture}
``` ```
| 参数 | 描述 | | 参数 | 描述 |
| ----------------- | -------------- | | ----------------- | -------------- |
| `owner` | 软件包的所有者 | | `owner` | 软件包的所有者 |
| `group` | 软件包自定义分组 |
| `package_name` | 软件包名称 | | `package_name` | 软件包名称 |
| `package_version` | 软件包版本 | | `package_version` | 软件包版本 |
| `architecture` | 软件包架构 | | `architecture` | 软件包架构 |
@@ -91,7 +94,7 @@ DELETE https://gitea.example.com/api/packages/{owner}/rpm/{package_name}/{packag
```shell ```shell
curl --user your_username:your_token_or_password -X DELETE \ curl --user your_username:your_token_or_password -X DELETE \
https://gitea.example.com/api/packages/testuser/rpm/test-package/1.0.0/x86_64 https://gitea.example.com/api/packages/testuser/rpm/centos/el7/package/test-package/1.0.0/x86_64
``` ```
服务器将以以下HTTP状态码响应: 服务器将以以下HTTP状态码响应:
+1 -1
View File
@@ -165,7 +165,7 @@ require (
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudflare/circl v1.3.6 // indirect github.com/cloudflare/circl v1.3.7 // indirect
github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.3.0 // indirect github.com/couchbase/gomemcached v0.3.0 // indirect
github.com/couchbase/goutils v0.1.2 // indirect github.com/couchbase/goutils v0.1.2 // indirect
+2 -2
View File
@@ -215,8 +215,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+2 -1
View File
@@ -168,12 +168,13 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
} }
// CancelRunningJobs cancels all running and waiting jobs associated with a specific workflow. // CancelRunningJobs cancels all running and waiting jobs associated with a specific workflow.
func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string) error { func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
// Find all runs in the specified repository, reference, and workflow with statuses 'Running' or 'Waiting'. // Find all runs in the specified repository, reference, and workflow with statuses 'Running' or 'Waiting'.
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{ runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
RepoID: repoID, RepoID: repoID,
Ref: ref, Ref: ref,
WorkflowID: workflowID, WorkflowID: workflowID,
TriggerEvent: event,
Status: []Status{StatusRunning, StatusWaiting}, Status: []Status{StatusRunning, StatusWaiting},
}) })
if err != nil { if err != nil {
+5
View File
@@ -10,6 +10,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
webhook_module "code.gitea.io/gitea/modules/webhook"
"xorm.io/builder" "xorm.io/builder"
) )
@@ -71,6 +72,7 @@ type FindRunOptions struct {
WorkflowID string WorkflowID string
Ref string // the commit/tag/… that caused this workflow Ref string // the commit/tag/… that caused this workflow
TriggerUserID int64 TriggerUserID int64
TriggerEvent webhook_module.HookEventType
Approved bool // not util.OptionalBool, it works only when it's true Approved bool // not util.OptionalBool, it works only when it's true
Status []Status Status []Status
} }
@@ -98,6 +100,9 @@ func (opts FindRunOptions) ToConds() builder.Cond {
if opts.Ref != "" { if opts.Ref != "" {
cond = cond.And(builder.Eq{"ref": opts.Ref}) cond = cond.And(builder.Eq{"ref": opts.Ref})
} }
if opts.TriggerEvent != "" {
cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent})
}
return cond return cond
} }
+20
View File
@@ -5,6 +5,7 @@ package actions
import ( import (
"context" "context"
"fmt"
"time" "time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@@ -118,3 +119,22 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
return committer.Commit() return committer.Commit()
} }
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error {
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
// There is no other place we can do this because the app.ini will be changed manually
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
return fmt.Errorf("DeleteCronTaskByRepo: %v", err)
}
// cancel running cron jobs of this repository and delete old schedules
if err := CancelRunningJobs(
ctx,
repo.ID,
repo.DefaultBranch,
"",
webhook_module.HookEventSchedule,
); err != nil {
return fmt.Errorf("CancelRunningJobs: %v", err)
}
return nil
}
+5 -2
View File
@@ -446,8 +446,11 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
return nil, 0, err return nil, 0, err
} }
sess := db.GetEngine(ctx).Where(cond). sess := db.GetEngine(ctx).Where(cond)
Select("`action`.*"). // this line will avoid select other joined table's columns if setting.Database.Type.IsMySQL() {
sess = sess.IndexHint("USE", "JOIN", "IDX_action_c_u_d")
}
sess = sess.Select("`action`.*"). // this line will avoid select other joined table's columns
Join("INNER", "repository", "`repository`.id = `action`.repo_id") Join("INNER", "repository", "`repository`.id = `action`.repo_id")
opts.SetDefaultValues() opts.SetDefaultValues()
+23 -32
View File
@@ -11,21 +11,13 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"github.com/keybase/go-crypto/openpgp" "github.com/keybase/go-crypto/openpgp"
"github.com/keybase/go-crypto/openpgp/packet" "github.com/keybase/go-crypto/openpgp/packet"
"xorm.io/xorm" "xorm.io/builder"
) )
// __________________ ________ ____ __.
// / _____/\______ \/ _____/ | |/ _|____ ___.__.
// / \ ___ | ___/ \ ___ | <_/ __ < | |
// \ \_\ \| | \ \_\ \ | | \ ___/\___ |
// \______ /|____| \______ / |____|__ \___ > ____|
// \/ \/ \/ \/\/
// GPGKey represents a GPG key. // GPGKey represents a GPG key.
type GPGKey struct { type GPGKey struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
@@ -54,12 +46,11 @@ func (key *GPGKey) BeforeInsert() {
key.AddedUnix = timeutil.TimeStampNow() key.AddedUnix = timeutil.TimeStampNow()
} }
// AfterLoad is invoked from XORM after setting the values of all fields of this object. func (key *GPGKey) LoadSubKeys(ctx context.Context) error {
func (key *GPGKey) AfterLoad(session *xorm.Session) { if err := db.GetEngine(ctx).Where("primary_key_id=?", key.KeyID).Find(&key.SubsKey); err != nil {
err := session.Where("primary_key_id=?", key.KeyID).Find(&key.SubsKey) return fmt.Errorf("find Sub GPGkeys[%s]: %v", key.KeyID, err)
if err != nil {
log.Error("Find Sub GPGkeys[%s]: %v", key.KeyID, err)
} }
return nil
} }
// PaddedKeyID show KeyID padded to 16 characters // PaddedKeyID show KeyID padded to 16 characters
@@ -76,20 +67,26 @@ func PaddedKeyID(keyID string) string {
return zeros[0:16-len(keyID)] + keyID return zeros[0:16-len(keyID)] + keyID
} }
// ListGPGKeys returns a list of public keys belongs to given user. type FindGPGKeyOptions struct {
func ListGPGKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*GPGKey, error) { db.ListOptions
sess := db.GetEngine(ctx).Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid) OwnerID int64
if listOptions.Page != 0 { KeyID string
sess = db.SetSessionPagination(sess, &listOptions) IncludeSubKeys bool
}
keys := make([]*GPGKey, 0, 2)
return keys, sess.Find(&keys)
} }
// CountUserGPGKeys return number of gpg keys a user own func (opts FindGPGKeyOptions) ToConds() builder.Cond {
func CountUserGPGKeys(ctx context.Context, userID int64) (int64, error) { cond := builder.NewCond()
return db.GetEngine(ctx).Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{}) if !opts.IncludeSubKeys {
cond = cond.And(builder.Eq{"primary_key_id": ""})
}
if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}
if opts.KeyID != "" {
cond = cond.And(builder.Eq{"key_id": opts.KeyID})
}
return cond
} }
func GetGPGKeyForUserByID(ctx context.Context, ownerID, keyID int64) (*GPGKey, error) { func GetGPGKeyForUserByID(ctx context.Context, ownerID, keyID int64) (*GPGKey, error) {
@@ -103,12 +100,6 @@ func GetGPGKeyForUserByID(ctx context.Context, ownerID, keyID int64) (*GPGKey, e
return key, nil return key, nil
} }
// GetGPGKeysByKeyID returns public key by given ID.
func GetGPGKeysByKeyID(ctx context.Context, keyID string) ([]*GPGKey, error) {
keys := make([]*GPGKey, 0, 1)
return keys, db.GetEngine(ctx).Where("key_id=?", keyID).Find(&keys)
}
// GPGKeyToEntity retrieve the imported key and the traducted entity // GPGKeyToEntity retrieve the imported key and the traducted entity
func GPGKeyToEntity(ctx context.Context, k *GPGKey) (*openpgp.Entity, error) { func GPGKeyToEntity(ctx context.Context, k *GPGKey) (*openpgp.Entity, error) {
impKey, err := GetGPGImportByKeyID(ctx, k.KeyID) impKey, err := GetGPGImportByKeyID(ctx, k.KeyID)
+20 -3
View File
@@ -166,7 +166,9 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific
// Now try to associate the signature with the committer, if present // Now try to associate the signature with the committer, if present
if committer.ID != 0 { if committer.ID != 0 {
keys, err := ListGPGKeys(ctx, committer.ID, db.ListOptions{}) keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
OwnerID: committer.ID,
})
if err != nil { // Skipping failed to get gpg keys of user if err != nil { // Skipping failed to get gpg keys of user
log.Error("ListGPGKeys: %v", err) log.Error("ListGPGKeys: %v", err)
return &CommitVerification{ return &CommitVerification{
@@ -176,6 +178,15 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific
} }
} }
if err := GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
log.Error("LoadSubKeys: %v", err)
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID) committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID)
activated := false activated := false
for _, e := range committerEmailAddresses { for _, e := range committerEmailAddresses {
@@ -392,7 +403,10 @@ func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s
if keyID == "" { if keyID == "" {
return nil return nil
} }
keys, err := GetGPGKeysByKeyID(ctx, keyID) keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
KeyID: keyID,
IncludeSubKeys: true,
})
if err != nil { if err != nil {
log.Error("GetGPGKeysByKeyID: %v", err) log.Error("GetGPGKeysByKeyID: %v", err)
return &CommitVerification{ return &CommitVerification{
@@ -407,7 +421,10 @@ func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s
for _, key := range keys { for _, key := range keys {
var primaryKeys []*GPGKey var primaryKeys []*GPGKey
if key.PrimaryKeyID != "" { if key.PrimaryKeyID != "" {
primaryKeys, err = GetGPGKeysByKeyID(ctx, key.PrimaryKeyID) primaryKeys, err = db.Find[GPGKey](ctx, FindGPGKeyOptions{
KeyID: key.PrimaryKeyID,
IncludeSubKeys: true,
})
if err != nil { if err != nil {
log.Error("GetGPGKeysByKeyID: %v", err) log.Error("GetGPGKeysByKeyID: %v", err)
return &CommitVerification{ return &CommitVerification{
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package asymkey
import (
"context"
"code.gitea.io/gitea/models/db"
)
type GPGKeyList []*GPGKey
func (keys GPGKeyList) keyIDs() []string {
ids := make([]string, len(keys))
for i, key := range keys {
ids[i] = key.KeyID
}
return ids
}
func (keys GPGKeyList) LoadSubKeys(ctx context.Context) error {
subKeys := make([]*GPGKey, 0, len(keys))
if err := db.GetEngine(ctx).In("primary_key_id", keys.keyIDs()).Find(&subKeys); err != nil {
return err
}
subKeysMap := make(map[string][]*GPGKey, len(subKeys))
for _, key := range subKeys {
subKeysMap[key.PrimaryKeyID] = append(subKeysMap[key.PrimaryKeyID], key)
}
for _, key := range keys {
if subKeys, ok := subKeysMap[key.KeyID]; ok {
key.SubsKey = subKeys
}
}
return nil
}
+2 -2
View File
@@ -197,10 +197,10 @@ func (opts FindPublicKeyOptions) ToConds() builder.Cond {
cond = cond.And(builder.Eq{"fingerprint": opts.Fingerprint}) cond = cond.And(builder.Eq{"fingerprint": opts.Fingerprint})
} }
if len(opts.KeyTypes) > 0 { if len(opts.KeyTypes) > 0 {
cond = cond.And(builder.In("type", opts.KeyTypes)) cond = cond.And(builder.In("`type`", opts.KeyTypes))
} }
if opts.NotKeytype > 0 { if opts.NotKeytype > 0 {
cond = cond.And(builder.Neq{"type": opts.NotKeytype}) cond = cond.And(builder.Neq{"`type`": opts.NotKeytype})
} }
if opts.LoginSourceID > 0 { if opts.LoginSourceID > 0 {
cond = cond.And(builder.Eq{"login_source_id": opts.LoginSourceID}) cond = cond.And(builder.Eq{"login_source_id": opts.LoginSourceID})
-23
View File
@@ -15,15 +15,6 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
// __________ .__ .__ .__
// \______ _______|__| ____ ____ |_____________ | | ______
// | ___\_ __ | |/ \_/ ___\| \____ \__ \ | | / ___/
// | | | | \| | | \ \___| | |_> / __ \| |__\___ \
// |____| |__| |__|___| /\___ |__| __(____ |____/____ >
// \/ \/ |__| \/ \/
//
// This file contains functions related to principals
// AddPrincipalKey adds new principal to database and authorized_principals file. // AddPrincipalKey adds new principal to database and authorized_principals file.
func AddPrincipalKey(ctx context.Context, ownerID int64, content string, authSourceID int64) (*PublicKey, error) { func AddPrincipalKey(ctx context.Context, ownerID int64, content string, authSourceID int64) (*PublicKey, error) {
dbCtx, committer, err := db.TxContext(ctx) dbCtx, committer, err := db.TxContext(ctx)
@@ -103,17 +94,3 @@ func CheckPrincipalKeyString(ctx context.Context, user *user_model.User, content
return "", fmt.Errorf("didn't match allowed principals: %s", setting.SSH.AuthorizedPrincipalsAllow) return "", fmt.Errorf("didn't match allowed principals: %s", setting.SSH.AuthorizedPrincipalsAllow)
} }
// ListPrincipalKeys returns a list of principals belongs to given user.
func ListPrincipalKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*PublicKey, error) {
sess := db.GetEngine(ctx).Where("owner_id = ? AND type = ?", uid, KeyTypePrincipal)
if listOptions.Page != 0 {
sess = db.SetSessionPagination(sess, &listOptions)
keys := make([]*PublicKey, 0, listOptions.PageSize)
return keys, sess.Find(&keys)
}
keys := make([]*PublicKey, 0, 5)
return keys, sess.Find(&keys)
}
+1 -2
View File
@@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/go-webauthn/webauthn/webauthn" "github.com/go-webauthn/webauthn/webauthn"
"xorm.io/xorm"
) )
// ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error. // ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error.
@@ -83,7 +82,7 @@ func (cred *WebAuthnCredential) BeforeUpdate() {
} }
// AfterLoad is invoked from XORM after setting the values of all fields of this object. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (cred *WebAuthnCredential) AfterLoad(session *xorm.Session) { func (cred *WebAuthnCredential) AfterLoad() {
cred.LowerName = strings.ToLower(cred.Name) cred.LowerName = strings.ToLower(cred.Name)
} }
+31 -30
View File
@@ -21,17 +21,9 @@ const (
// Paginator is the base for different ListOptions types // Paginator is the base for different ListOptions types
type Paginator interface { type Paginator interface {
GetSkipTake() (skip, take int) GetSkipTake() (skip, take int)
GetStartEnd() (start, end int)
IsListAll() bool IsListAll() bool
} }
// GetPaginatedSession creates a paginated database session
func GetPaginatedSession(p Paginator) *xorm.Session {
skip, take := p.GetSkipTake()
return x.Limit(take, skip)
}
// SetSessionPagination sets pagination for a database session // SetSessionPagination sets pagination for a database session
func SetSessionPagination(sess Engine, p Paginator) *xorm.Session { func SetSessionPagination(sess Engine, p Paginator) *xorm.Session {
skip, take := p.GetSkipTake() skip, take := p.GetSkipTake()
@@ -39,13 +31,6 @@ func SetSessionPagination(sess Engine, p Paginator) *xorm.Session {
return sess.Limit(take, skip) return sess.Limit(take, skip)
} }
// SetEnginePagination sets pagination for a database engine
func SetEnginePagination(e Engine, p Paginator) Engine {
skip, take := p.GetSkipTake()
return e.Limit(take, skip)
}
// ListOptions options to paginate results // ListOptions options to paginate results
type ListOptions struct { type ListOptions struct {
PageSize int PageSize int
@@ -66,13 +51,6 @@ func (opts *ListOptions) GetSkipTake() (skip, take int) {
return (opts.Page - 1) * opts.PageSize, opts.PageSize return (opts.Page - 1) * opts.PageSize, opts.PageSize
} }
// GetStartEnd returns the start and end of the ListOptions
func (opts *ListOptions) GetStartEnd() (start, end int) {
start, take := opts.GetSkipTake()
end = start + take
return start, end
}
func (opts ListOptions) GetPage() int { func (opts ListOptions) GetPage() int {
return opts.Page return opts.Page
} }
@@ -135,11 +113,6 @@ func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) {
return opts.skip, opts.take return opts.skip, opts.take
} }
// GetStartEnd returns the start and end values
func (opts *AbsoluteListOptions) GetStartEnd() (start, end int) {
return opts.skip, opts.skip + opts.take
}
// FindOptions represents a find options // FindOptions represents a find options
type FindOptions interface { type FindOptions interface {
GetPage() int GetPage() int
@@ -148,15 +121,34 @@ type FindOptions interface {
ToConds() builder.Cond ToConds() builder.Cond
} }
type JoinFunc func(sess Engine) error
type FindOptionsJoin interface {
ToJoins() []JoinFunc
}
type FindOptionsOrder interface { type FindOptionsOrder interface {
ToOrders() string ToOrders() string
} }
// Find represents a common find function which accept an options interface // Find represents a common find function which accept an options interface
func Find[T any](ctx context.Context, opts FindOptions) ([]*T, error) { func Find[T any](ctx context.Context, opts FindOptions) ([]*T, error) {
sess := GetEngine(ctx).Where(opts.ToConds()) sess := GetEngine(ctx)
if joinOpt, ok := opts.(FindOptionsJoin); ok && len(joinOpt.ToJoins()) > 0 {
for _, joinFunc := range joinOpt.ToJoins() {
if err := joinFunc(sess); err != nil {
return nil, err
}
}
}
sess = sess.Where(opts.ToConds())
page, pageSize := opts.GetPage(), opts.GetPageSize() page, pageSize := opts.GetPage(), opts.GetPageSize()
if !opts.IsListAll() && pageSize > 0 && page >= 1 { if !opts.IsListAll() && pageSize > 0 {
if page == 0 {
page = 1
}
sess.Limit(pageSize, (page-1)*pageSize) sess.Limit(pageSize, (page-1)*pageSize)
} }
if newOpt, ok := opts.(FindOptionsOrder); ok && newOpt.ToOrders() != "" { if newOpt, ok := opts.(FindOptionsOrder); ok && newOpt.ToOrders() != "" {
@@ -176,8 +168,17 @@ func Find[T any](ctx context.Context, opts FindOptions) ([]*T, error) {
// Count represents a common count function which accept an options interface // Count represents a common count function which accept an options interface
func Count[T any](ctx context.Context, opts FindOptions) (int64, error) { func Count[T any](ctx context.Context, opts FindOptions) (int64, error) {
sess := GetEngine(ctx)
if joinOpt, ok := opts.(FindOptionsJoin); ok && len(joinOpt.ToJoins()) > 0 {
for _, joinFunc := range joinOpt.ToJoins() {
if err := joinFunc(sess); err != nil {
return 0, err
}
}
}
var object T var object T
return GetEngine(ctx).Where(opts.ToConds()).Count(&object) return sess.Where(opts.ToConds()).Count(&object)
} }
// FindAndCount represents a common findandcount function which accept an options interface // FindAndCount represents a common findandcount function which accept an options interface
-3
View File
@@ -52,11 +52,8 @@ func TestPaginator(t *testing.T) {
for _, c := range cases { for _, c := range cases {
skip, take := c.Paginator.GetSkipTake() skip, take := c.Paginator.GetSkipTake()
start, end := c.Paginator.GetStartEnd()
assert.Equal(t, c.Skip, skip) assert.Equal(t, c.Skip, skip)
assert.Equal(t, c.Take, take) assert.Equal(t, c.Take, take)
assert.Equal(t, c.Start, start)
assert.Equal(t, c.End, end)
} }
} }
+15
View File
@@ -57,6 +57,21 @@ func (err ErrUserOwnPackages) Error() string {
return fmt.Sprintf("user still has ownership of packages [uid: %d]", err.UID) return fmt.Sprintf("user still has ownership of packages [uid: %d]", err.UID)
} }
// ErrDeleteLastAdminUser represents a "DeleteLastAdminUser" kind of error.
type ErrDeleteLastAdminUser struct {
UID int64
}
// IsErrDeleteLastAdminUser checks if an error is a ErrDeleteLastAdminUser.
func IsErrDeleteLastAdminUser(err error) bool {
_, ok := err.(ErrDeleteLastAdminUser)
return ok
}
func (err ErrDeleteLastAdminUser) Error() string {
return fmt.Sprintf("can not delete the last admin user [uid: %d]", err.UID)
}
// ErrNoPendingRepoTransfer is an error type for repositories without a pending // ErrNoPendingRepoTransfer is an error type for repositories without a pending
// transfer request // transfer request
type ErrNoPendingRepoTransfer struct { type ErrNoPendingRepoTransfer struct {
+2 -2
View File
@@ -283,7 +283,7 @@ func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *
} }
// RenameBranch rename a branch // RenameBranch rename a branch
func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) { func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(ctx context.Context, isDefault bool) error) (err error) {
ctx, committer, err := db.TxContext(ctx) ctx, committer, err := db.TxContext(ctx)
if err != nil { if err != nil {
return err return err
@@ -358,7 +358,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
} }
// 5. do git action // 5. do git action
if err = gitAction(isDefault); err != nil { if err = gitAction(ctx, isDefault); err != nil {
return err return err
} }
+2 -1
View File
@@ -4,6 +4,7 @@
package git_test package git_test
import ( import (
"context"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@@ -132,7 +133,7 @@ func TestRenameBranch(t *testing.T) {
}, git_model.WhitelistOptions{})) }, git_model.WhitelistOptions{}))
assert.NoError(t, committer.Commit()) assert.NoError(t, committer.Commit())
assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(isDefault bool) error { assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(ctx context.Context, isDefault bool) error {
_isDefault = isDefault _isDefault = isDefault
return nil return nil
})) }))
+1
View File
@@ -59,6 +59,7 @@ type ProtectedBranch struct {
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"` BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"` BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"` DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
IgnoreStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"` RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
ProtectedFilePatterns string `xorm:"TEXT"` ProtectedFilePatterns string `xorm:"TEXT"`
UnprotectedFilePatterns string `xorm:"TEXT"` UnprotectedFilePatterns string `xorm:"TEXT"`
+1 -2
View File
@@ -28,7 +28,6 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm"
) )
// ErrCommentNotExist represents a "CommentNotExist" kind of error. // ErrCommentNotExist represents a "CommentNotExist" kind of error.
@@ -338,7 +337,7 @@ func (c *Comment) BeforeUpdate() {
} }
// AfterLoad is invoked from XORM after setting the values of all fields of this object. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (c *Comment) AfterLoad(session *xorm.Session) { func (c *Comment) AfterLoad() {
c.Patch = c.PatchQuoted c.Patch = c.PatchQuoted
if len(c.PatchQuoted) > 0 && c.PatchQuoted[0] == '"' { if len(c.PatchQuoted) > 0 && c.PatchQuoted[0] == '"' {
unquoted, err := strconv.Unquote(c.PatchQuoted) unquoted, err := strconv.Unquote(c.PatchQuoted)
+1 -1
View File
@@ -801,7 +801,7 @@ func GetGrantedApprovalsCount(ctx context.Context, protectBranch *git_model.Prot
And("type = ?", ReviewTypeApprove). And("type = ?", ReviewTypeApprove).
And("official = ?", true). And("official = ?", true).
And("dismissed = ?", false) And("dismissed = ?", false)
if protectBranch.DismissStaleApprovals { if protectBranch.IgnoreStaleApprovals {
sess = sess.And("stale = ?", false) sess = sess.And("stale = ?", false)
} }
approvals, err := sess.Count(new(Review)) approvals, err := sess.Count(new(Review))
+16 -4
View File
@@ -94,7 +94,7 @@ type FindTrackedTimesOptions struct {
} }
// toCond will convert each condition into a xorm-Cond // toCond will convert each condition into a xorm-Cond
func (opts *FindTrackedTimesOptions) toCond() builder.Cond { func (opts *FindTrackedTimesOptions) ToConds() builder.Cond {
cond := builder.NewCond().And(builder.Eq{"tracked_time.deleted": false}) cond := builder.NewCond().And(builder.Eq{"tracked_time.deleted": false})
if opts.IssueID != 0 { if opts.IssueID != 0 {
cond = cond.And(builder.Eq{"issue_id": opts.IssueID}) cond = cond.And(builder.Eq{"issue_id": opts.IssueID})
@@ -117,6 +117,18 @@ func (opts *FindTrackedTimesOptions) toCond() builder.Cond {
return cond return cond
} }
func (opts *FindTrackedTimesOptions) ToJoins() []db.JoinFunc {
if opts.RepositoryID > 0 || opts.MilestoneID > 0 {
return []db.JoinFunc{
func(e db.Engine) error {
e.Join("INNER", "issue", "issue.id = tracked_time.issue_id")
return nil
},
}
}
return nil
}
// toSession will convert the given options to a xorm Session by using the conditions from toCond and joining with issue table if required // toSession will convert the given options to a xorm Session by using the conditions from toCond and joining with issue table if required
func (opts *FindTrackedTimesOptions) toSession(e db.Engine) db.Engine { func (opts *FindTrackedTimesOptions) toSession(e db.Engine) db.Engine {
sess := e sess := e
@@ -124,10 +136,10 @@ func (opts *FindTrackedTimesOptions) toSession(e db.Engine) db.Engine {
sess = e.Join("INNER", "issue", "issue.id = tracked_time.issue_id") sess = e.Join("INNER", "issue", "issue.id = tracked_time.issue_id")
} }
sess = sess.Where(opts.toCond()) sess = sess.Where(opts.ToConds())
if opts.Page != 0 { if opts.Page != 0 {
sess = db.SetEnginePagination(sess, opts) sess = db.SetSessionPagination(sess, opts)
} }
return sess return sess
@@ -141,7 +153,7 @@ func GetTrackedTimes(ctx context.Context, options *FindTrackedTimesOptions) (tra
// CountTrackedTimes returns count of tracked times that fit to the given options. // CountTrackedTimes returns count of tracked times that fit to the given options.
func CountTrackedTimes(ctx context.Context, opts *FindTrackedTimesOptions) (int64, error) { func CountTrackedTimes(ctx context.Context, opts *FindTrackedTimesOptions) (int64, error) {
sess := db.GetEngine(ctx).Where(opts.toCond()) sess := db.GetEngine(ctx).Where(opts.ToConds())
if opts.RepositoryID > 0 || opts.MilestoneID > 0 { if opts.RepositoryID > 0 || opts.MilestoneID > 0 {
sess = sess.Join("INNER", "issue", "issue.id = tracked_time.issue_id") sess = sess.Join("INNER", "issue", "issue.id = tracked_time.issue_id")
} }
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import (
"xorm.io/xorm"
)
func AddIgnoreStaleApprovalsColumnToProtectedBranchTable(x *xorm.Engine) error {
type ProtectedBranch struct {
IgnoreStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync(new(ProtectedBranch))
}
+3 -10
View File
@@ -111,7 +111,7 @@ type FindRepoArchiversOption struct {
OlderThan time.Duration OlderThan time.Duration
} }
func (opts FindRepoArchiversOption) toConds() builder.Cond { func (opts FindRepoArchiversOption) ToConds() builder.Cond {
cond := builder.NewCond() cond := builder.NewCond()
if opts.OlderThan > 0 { if opts.OlderThan > 0 {
cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-opts.OlderThan).Unix()}) cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-opts.OlderThan).Unix()})
@@ -119,15 +119,8 @@ func (opts FindRepoArchiversOption) toConds() builder.Cond {
return cond return cond
} }
// FindRepoArchives find repo archivers func (opts FindRepoArchiversOption) ToOrders() string {
func FindRepoArchives(ctx context.Context, opts FindRepoArchiversOption) ([]*RepoArchiver, error) { return "created_unix ASC"
archivers := make([]*RepoArchiver, 0, opts.PageSize)
start, limit := opts.GetSkipTake()
err := db.GetEngine(ctx).Where(opts.toConds()).
Asc("created_unix").
Limit(limit, start).
Find(&archivers)
return archivers, err
} }
// SetArchiveRepoState sets if a repo is archived // SetArchiveRepoState sets if a repo is archived
+26 -27
View File
@@ -11,8 +11,9 @@ import (
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
) )
// Collaboration represent the relation between an individual and a repository. // Collaboration represent the relation between an individual and a repository.
@@ -37,35 +38,38 @@ type Collaborator struct {
// GetCollaborators returns the collaborators for a repository // GetCollaborators returns the collaborators for a repository
func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*Collaborator, error) { func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*Collaborator, error) {
collaborations, err := getCollaborations(ctx, repoID, listOptions) collaborations, err := db.Find[Collaboration](ctx, FindCollaborationOptions{
ListOptions: listOptions,
RepoID: repoID,
})
if err != nil { if err != nil {
return nil, fmt.Errorf("getCollaborations: %w", err) return nil, fmt.Errorf("db.Find[Collaboration]: %w", err)
} }
collaborators := make([]*Collaborator, 0, len(collaborations)) collaborators := make([]*Collaborator, 0, len(collaborations))
userIDs := make([]int64, 0, len(collaborations))
for _, c := range collaborations { for _, c := range collaborations {
user, err := user_model.GetUserByID(ctx, c.UserID) userIDs = append(userIDs, c.UserID)
if err != nil {
if user_model.IsErrUserNotExist(err) {
log.Warn("Inconsistent DB: User: %d is listed as collaborator of %-v but does not exist", c.UserID, repoID)
user = user_model.NewGhostUser()
} else {
return nil, err
} }
usersMap := make(map[int64]*user_model.User)
if err := db.GetEngine(ctx).In("id", userIDs).Find(&usersMap); err != nil {
return nil, fmt.Errorf("Find users map by user ids: %w", err)
}
for _, c := range collaborations {
u := usersMap[c.UserID]
if u == nil {
u = user_model.NewGhostUser()
} }
collaborators = append(collaborators, &Collaborator{ collaborators = append(collaborators, &Collaborator{
User: user, User: u,
Collaboration: c, Collaboration: c,
}) })
} }
return collaborators, nil return collaborators, nil
} }
// CountCollaborators returns total number of collaborators for a repository
func CountCollaborators(ctx context.Context, repoID int64) (int64, error) {
return db.GetEngine(ctx).Where("repo_id = ? ", repoID).Count(&Collaboration{})
}
// GetCollaboration get collaboration for a repository id with a user id // GetCollaboration get collaboration for a repository id with a user id
func GetCollaboration(ctx context.Context, repoID, uid int64) (*Collaboration, error) { func GetCollaboration(ctx context.Context, repoID, uid int64) (*Collaboration, error) {
collaboration := &Collaboration{ collaboration := &Collaboration{
@@ -84,18 +88,13 @@ func IsCollaborator(ctx context.Context, repoID, userID int64) (bool, error) {
return db.GetEngine(ctx).Get(&Collaboration{RepoID: repoID, UserID: userID}) return db.GetEngine(ctx).Get(&Collaboration{RepoID: repoID, UserID: userID})
} }
func getCollaborations(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*Collaboration, error) { type FindCollaborationOptions struct {
if listOptions.Page == 0 { db.ListOptions
collaborations := make([]*Collaboration, 0, 8) RepoID int64
return collaborations, db.GetEngine(ctx).Find(&collaborations, &Collaboration{RepoID: repoID}) }
}
e := db.GetEngine(ctx) func (opts FindCollaborationOptions) ToConds() builder.Cond {
return builder.And(builder.Eq{"repo_id": opts.RepoID})
e = db.SetEnginePagination(e, &listOptions)
collaborations := make([]*Collaboration, 0, listOptions.PageSize)
return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repoID})
} }
// ChangeCollaborationAccessMode sets new access mode for the collaboration. // ChangeCollaborationAccessMode sets new access mode for the collaboration.
+9 -3
View File
@@ -89,17 +89,23 @@ func TestRepository_CountCollaborators(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
count, err := repo_model.CountCollaborators(db.DefaultContext, repo1.ID) count, err := db.Count[repo_model.Collaboration](db.DefaultContext, repo_model.FindCollaborationOptions{
RepoID: repo1.ID,
})
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 2, count) assert.EqualValues(t, 2, count)
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22}) repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22})
count, err = repo_model.CountCollaborators(db.DefaultContext, repo2.ID) count, err = db.Count[repo_model.Collaboration](db.DefaultContext, repo_model.FindCollaborationOptions{
RepoID: repo2.ID,
})
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 2, count) assert.EqualValues(t, 2, count)
// Non-existent repository. // Non-existent repository.
count, err = repo_model.CountCollaborators(db.DefaultContext, unittest.NonexistentID) count, err = db.Count[repo_model.Collaboration](db.DefaultContext, repo_model.FindCollaborationOptions{
RepoID: unittest.NonexistentID,
})
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 0, count) assert.EqualValues(t, 0, count)
} }
+7 -4
View File
@@ -56,13 +56,16 @@ func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error)
// GetForks returns all the forks of the repository // GetForks returns all the forks of the repository
func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions) ([]*Repository, error) { func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions) ([]*Repository, error) {
sess := db.GetEngine(ctx)
var forks []*Repository
if listOptions.Page == 0 { if listOptions.Page == 0 {
forks := make([]*Repository, 0, repo.NumForks) forks = make([]*Repository, 0, repo.NumForks)
return forks, db.GetEngine(ctx).Find(&forks, &Repository{ForkID: repo.ID}) } else {
forks = make([]*Repository, 0, listOptions.PageSize)
sess = db.SetSessionPagination(sess, &listOptions)
} }
sess := db.GetPaginatedSession(&listOptions)
forks := make([]*Repository, 0, listOptions.PageSize)
return forks, sess.Find(&forks, &Repository{ForkID: repo.ID}) return forks, sess.Find(&forks, &Repository{ForkID: repo.ID})
} }
+7 -35
View File
@@ -225,6 +225,7 @@ func GetReleaseForRepoByID(ctx context.Context, repoID, id int64) (*Release, err
// FindReleasesOptions describes the conditions to Find releases // FindReleasesOptions describes the conditions to Find releases
type FindReleasesOptions struct { type FindReleasesOptions struct {
db.ListOptions db.ListOptions
RepoID int64
IncludeDrafts bool IncludeDrafts bool
IncludeTags bool IncludeTags bool
IsPreRelease util.OptionalBool IsPreRelease util.OptionalBool
@@ -233,9 +234,8 @@ type FindReleasesOptions struct {
HasSha1 util.OptionalBool // useful to find draft releases which are created with existing tags HasSha1 util.OptionalBool // useful to find draft releases which are created with existing tags
} }
func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond { func (opts FindReleasesOptions) ToConds() builder.Cond {
cond := builder.NewCond() var cond builder.Cond = builder.Eq{"repo_id": opts.RepoID}
cond = cond.And(builder.Eq{"repo_id": repoID})
if !opts.IncludeDrafts { if !opts.IncludeDrafts {
cond = cond.And(builder.Eq{"is_draft": false}) cond = cond.And(builder.Eq{"is_draft": false})
@@ -262,18 +262,8 @@ func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond {
return cond return cond
} }
// GetReleasesByRepoID returns a list of releases of repository. func (opts FindReleasesOptions) ToOrders() string {
func GetReleasesByRepoID(ctx context.Context, repoID int64, opts FindReleasesOptions) ([]*Release, error) { return "created_unix DESC, id DESC"
sess := db.GetEngine(ctx).
Desc("created_unix", "id").
Where(opts.toConds(repoID))
if opts.PageSize != 0 {
sess = db.SetSessionPagination(sess, &opts.ListOptions)
}
rels := make([]*Release, 0, opts.PageSize)
return rels, sess.Find(&rels)
} }
// GetTagNamesByRepoID returns a list of release tag names of repository. // GetTagNamesByRepoID returns a list of release tag names of repository.
@@ -286,23 +276,19 @@ func GetTagNamesByRepoID(ctx context.Context, repoID int64) ([]string, error) {
IncludeDrafts: true, IncludeDrafts: true,
IncludeTags: true, IncludeTags: true,
HasSha1: util.OptionalBoolTrue, HasSha1: util.OptionalBoolTrue,
RepoID: repoID,
} }
tags := make([]string, 0) tags := make([]string, 0)
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).
Table("release"). Table("release").
Desc("created_unix", "id"). Desc("created_unix", "id").
Where(opts.toConds(repoID)). Where(opts.ToConds()).
Cols("tag_name") Cols("tag_name")
return tags, sess.Find(&tags) return tags, sess.Find(&tags)
} }
// CountReleasesByRepoID returns a number of releases matching FindReleaseOptions and RepoID.
func CountReleasesByRepoID(ctx context.Context, repoID int64, opts FindReleasesOptions) (int64, error) {
return db.GetEngine(ctx).Where(opts.toConds(repoID)).Count(new(Release))
}
// GetLatestReleaseByRepoID returns the latest release for a repository // GetLatestReleaseByRepoID returns the latest release for a repository
func GetLatestReleaseByRepoID(ctx context.Context, repoID int64) (*Release, error) { func GetLatestReleaseByRepoID(ctx context.Context, repoID int64) (*Release, error) {
cond := builder.NewCond(). cond := builder.NewCond().
@@ -325,20 +311,6 @@ func GetLatestReleaseByRepoID(ctx context.Context, repoID int64) (*Release, erro
return rel, nil return rel, nil
} }
// GetReleasesByRepoIDAndNames returns a list of releases of repository according repoID and tagNames.
func GetReleasesByRepoIDAndNames(ctx context.Context, repoID int64, tagNames []string) (rels []*Release, err error) {
err = db.GetEngine(ctx).
In("tag_name", tagNames).
Desc("created_unix").
Find(&rels, Release{RepoID: repoID})
return rels, err
}
// GetReleaseCountByRepoID returns the count of releases of repository
func GetReleaseCountByRepoID(ctx context.Context, repoID int64, opts FindReleasesOptions) (int64, error) {
return db.GetEngine(ctx).Where(opts.toConds(repoID)).Count(&Release{})
}
type releaseMetaSearch struct { type releaseMetaSearch struct {
ID []int64 ID []int64
Rel []*Release Rel []*Release
-26
View File
@@ -283,29 +283,3 @@ func UpdateRepoUnit(ctx context.Context, unit *RepoUnit) error {
_, err := db.GetEngine(ctx).ID(unit.ID).Update(unit) _, err := db.GetEngine(ctx).ID(unit.ID).Update(unit)
return err return err
} }
// UpdateRepositoryUnits updates a repository's units
func UpdateRepositoryUnits(ctx context.Context, repo *Repository, units []RepoUnit, deleteUnitTypes []unit.Type) (err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
// Delete existing settings of units before adding again
for _, u := range units {
deleteUnitTypes = append(deleteUnitTypes, u.Type)
}
if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(RepoUnit)); err != nil {
return err
}
if len(units) > 0 {
if err = db.Insert(ctx, units); err != nil {
return err
}
}
return committer.Commit()
}
+27 -4
View File
@@ -730,9 +730,18 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
return committer.Commit() return committer.Commit()
} }
// IsLastAdminUser check whether user is the last admin
func IsLastAdminUser(ctx context.Context, user *User) bool {
if user.IsAdmin && CountUsers(ctx, &CountUserFilter{IsAdmin: util.OptionalBoolTrue}) <= 1 {
return true
}
return false
}
// CountUserFilter represent optional filters for CountUsers // CountUserFilter represent optional filters for CountUsers
type CountUserFilter struct { type CountUserFilter struct {
LastLoginSince *int64 LastLoginSince *int64
IsAdmin util.OptionalBool
} }
// CountUsers returns number of users. // CountUsers returns number of users.
@@ -741,13 +750,25 @@ func CountUsers(ctx context.Context, opts *CountUserFilter) int64 {
} }
func countUsers(ctx context.Context, opts *CountUserFilter) int64 { func countUsers(ctx context.Context, opts *CountUserFilter) int64 {
sess := db.GetEngine(ctx).Where(builder.Eq{"type": "0"}) sess := db.GetEngine(ctx)
cond := builder.NewCond()
cond = cond.And(builder.Eq{"type": UserTypeIndividual})
if opts != nil && opts.LastLoginSince != nil { if opts != nil {
sess = sess.Where(builder.Gte{"last_login_unix": *opts.LastLoginSince}) if opts.LastLoginSince != nil {
cond = cond.And(builder.Gte{"last_login_unix": *opts.LastLoginSince})
}
if !opts.IsAdmin.IsNone() {
cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()})
}
}
count, err := sess.Where(cond).Count(new(User))
if err != nil {
log.Error("user.countUsers: %v", err)
} }
count, _ := sess.Count(new(User))
return count return count
} }
@@ -1230,6 +1251,8 @@ func isUserVisibleToViewerCond(viewer *User) builder.Cond {
return builder.Neq{ return builder.Neq{
"`user`.visibility": structs.VisibleTypePrivate, "`user`.visibility": structs.VisibleTypePrivate,
}.Or( }.Or(
// viewer self
builder.Eq{"`user`.id": viewer.ID},
// viewer's following // viewer's following
builder.In("`user`.id", builder.In("`user`.id",
builder. builder.
+4
View File
@@ -22,6 +22,7 @@ const (
GithubEventRelease = "release" GithubEventRelease = "release"
GithubEventPullRequestComment = "pull_request_comment" GithubEventPullRequestComment = "pull_request_comment"
GithubEventGollum = "gollum" GithubEventGollum = "gollum"
GithubEventSchedule = "schedule"
) )
// canGithubEventMatch check if the input Github event can match any Gitea event. // canGithubEventMatch check if the input Github event can match any Gitea event.
@@ -69,6 +70,9 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent
return false return false
} }
case GithubEventSchedule:
return triggedEvent == webhook_module.HookEventSchedule
default: default:
return eventName == string(triggedEvent) return eventName == string(triggedEvent)
} }
+9 -5
View File
@@ -22,7 +22,7 @@ import (
type DetectedWorkflow struct { type DetectedWorkflow struct {
EntryName string EntryName string
TriggerEvent string TriggerEvent *jobparser.Event
Content []byte Content []byte
} }
@@ -100,6 +100,7 @@ func DetectWorkflows(
commit *git.Commit, commit *git.Commit,
triggedEvent webhook_module.HookEventType, triggedEvent webhook_module.HookEventType,
payload api.Payloader, payload api.Payloader,
detectSchedule bool,
) ([]*DetectedWorkflow, []*DetectedWorkflow, error) { ) ([]*DetectedWorkflow, []*DetectedWorkflow, error) {
entries, err := ListWorkflows(commit) entries, err := ListWorkflows(commit)
if err != nil { if err != nil {
@@ -114,6 +115,7 @@ func DetectWorkflows(
return nil, nil, err return nil, nil, err
} }
// one workflow may have multiple events
events, err := GetEventsFromContent(content) events, err := GetEventsFromContent(content)
if err != nil { if err != nil {
log.Warn("ignore invalid workflow %q: %v", entry.Name(), err) log.Warn("ignore invalid workflow %q: %v", entry.Name(), err)
@@ -122,17 +124,18 @@ func DetectWorkflows(
for _, evt := range events { for _, evt := range events {
log.Trace("detect workflow %q for event %#v matching %q", entry.Name(), evt, triggedEvent) log.Trace("detect workflow %q for event %#v matching %q", entry.Name(), evt, triggedEvent)
if evt.IsSchedule() { if evt.IsSchedule() {
if detectSchedule {
dwf := &DetectedWorkflow{ dwf := &DetectedWorkflow{
EntryName: entry.Name(), EntryName: entry.Name(),
TriggerEvent: evt.Name, TriggerEvent: evt,
Content: content, Content: content,
} }
schedules = append(schedules, dwf) schedules = append(schedules, dwf)
} }
if detectMatched(gitRepo, commit, triggedEvent, payload, evt) { } else if detectMatched(gitRepo, commit, triggedEvent, payload, evt) {
dwf := &DetectedWorkflow{ dwf := &DetectedWorkflow{
EntryName: entry.Name(), EntryName: entry.Name(),
TriggerEvent: evt.Name, TriggerEvent: evt,
Content: content, Content: content,
} }
workflows = append(workflows, dwf) workflows = append(workflows, dwf)
@@ -153,7 +156,8 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
webhook_module.HookEventCreate, webhook_module.HookEventCreate,
webhook_module.HookEventDelete, webhook_module.HookEventDelete,
webhook_module.HookEventFork, webhook_module.HookEventFork,
webhook_module.HookEventWiki: webhook_module.HookEventWiki,
webhook_module.HookEventSchedule:
if len(evt.Acts()) != 0 { if len(evt.Acts()) != 0 {
log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts()) log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts())
} }
+7
View File
@@ -118,6 +118,13 @@ func TestDetectMatched(t *testing.T) {
yamlOn: "on: gollum", yamlOn: "on: gollum",
expected: true, expected: true,
}, },
{
desc: "HookEventSchedue(schedule) matches GithubEventSchedule(schedule)",
triggedEvent: webhook_module.HookEventSchedule,
payload: nil,
yamlOn: "on: schedule",
expected: true,
},
} }
for _, tc := range testCases { for _, tc := range testCases {
+4 -2
View File
@@ -536,18 +536,20 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
} }
ctx.Data["NumTags"], err = repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{ ctx.Data["NumTags"], err = db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{
IncludeDrafts: true, IncludeDrafts: true,
IncludeTags: true, IncludeTags: true,
HasSha1: util.OptionalBoolTrue, // only draft releases which are created with existing tags HasSha1: util.OptionalBoolTrue, // only draft releases which are created with existing tags
RepoID: ctx.Repo.Repository.ID,
}) })
if err != nil { if err != nil {
ctx.ServerError("GetReleaseCountByRepoID", err) ctx.ServerError("GetReleaseCountByRepoID", err)
return nil return nil
} }
ctx.Data["NumReleases"], err = repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{ ctx.Data["NumReleases"], err = db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{
// only show draft releases for users who can write, read-only users shouldn't see draft releases. // only show draft releases for users who can write, read-only users shouldn't see draft releases.
IncludeDrafts: ctx.Repo.CanWrite(unit_model.TypeReleases), IncludeDrafts: ctx.Repo.CanWrite(unit_model.TypeReleases),
RepoID: ctx.Repo.Repository.ID,
}) })
if err != nil { if err != nil {
ctx.ServerError("GetReleaseCountByRepoID", err) ctx.ServerError("GetReleaseCountByRepoID", err)
-1
View File
@@ -16,7 +16,6 @@ import (
const ( const (
PropertyMetadata = "rpm.metadata" PropertyMetadata = "rpm.metadata"
SettingKeyPrivate = "rpm.key.private" SettingKeyPrivate = "rpm.key.private"
SettingKeyPublic = "rpm.key.public" SettingKeyPublic = "rpm.key.public"
+2 -1
View File
@@ -299,10 +299,11 @@ func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitR
IncludeDrafts: true, IncludeDrafts: true,
IncludeTags: true, IncludeTags: true,
ListOptions: db.ListOptions{PageSize: 50}, ListOptions: db.ListOptions{PageSize: 50},
RepoID: repo.ID,
} }
for page := 1; ; page++ { for page := 1; ; page++ {
opts.Page = page opts.Page = page
rels, err := repo_model.GetReleasesByRepoID(gitRepo.Ctx, repo.ID, opts) rels, err := db.Find[repo_model.Release](gitRepo.Ctx, opts)
if err != nil { if err != nil {
return fmt.Errorf("unable to GetReleasesByRepoID in Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) return fmt.Errorf("unable to GetReleasesByRepoID in Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
} }
+4 -1
View File
@@ -159,10 +159,13 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
} }
} }
sectionHasDisableQueryAuthToken := sec.HasKey("DISABLE_QUERY_AUTH_TOKEN")
// TODO: default value should be true in future releases // TODO: default value should be true in future releases
DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false) DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false)
if !DisableQueryAuthToken { // warn if the setting is set to false explicitly
if sectionHasDisableQueryAuthToken && !DisableQueryAuthToken {
log.Warn("Enabling Query API Auth tokens is not recommended. DISABLE_QUERY_AUTH_TOKEN will default to true in gitea 1.23 and will be removed in gitea 1.24.") log.Warn("Enabling Query API Auth tokens is not recommended. DISABLE_QUERY_AUTH_TOKEN will default to true in gitea 1.23 and will be removed in gitea 1.24.")
} }
} }
+3
View File
@@ -48,6 +48,7 @@ type BranchProtection struct {
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
DismissStaleApprovals bool `json:"dismiss_stale_approvals"` DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
IgnoreStaleApprovals bool `json:"ignore_stale_approvals"`
RequireSignedCommits bool `json:"require_signed_commits"` RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"` ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"` UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
@@ -85,6 +86,7 @@ type CreateBranchProtectionOption struct {
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
DismissStaleApprovals bool `json:"dismiss_stale_approvals"` DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
IgnoreStaleApprovals bool `json:"ignore_stale_approvals"`
RequireSignedCommits bool `json:"require_signed_commits"` RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"` ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"` UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
@@ -115,6 +117,7 @@ type EditBranchProtectionOption struct {
BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"` BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"` BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"`
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"` DismissStaleApprovals *bool `json:"dismiss_stale_approvals"`
IgnoreStaleApprovals *bool `json:"ignore_stale_approvals"`
RequireSignedCommits *bool `json:"require_signed_commits"` RequireSignedCommits *bool `json:"require_signed_commits"`
ProtectedFilePatterns *string `json:"protected_file_patterns"` ProtectedFilePatterns *string `json:"protected_file_patterns"`
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"` UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
+2 -2
View File
@@ -143,7 +143,7 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML {
if labelScope == "" { if labelScope == "" {
// Regular label // Regular label
s := fmt.Sprintf("<div class='ui label' style='color: %s !important; background-color: %s !important' title='%s'>%s</div>", s := fmt.Sprintf("<div class='ui label' style='color: %s !important; background-color: %s !important' data-tooltip-content title='%s'>%s</div>",
textColor, label.Color, description, RenderEmoji(ctx, label.Name)) textColor, label.Color, description, RenderEmoji(ctx, label.Name))
return template.HTML(s) return template.HTML(s)
} }
@@ -177,7 +177,7 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML {
itemColor := "#" + hex.EncodeToString(itemBytes) itemColor := "#" + hex.EncodeToString(itemBytes)
scopeColor := "#" + hex.EncodeToString(scopeBytes) scopeColor := "#" + hex.EncodeToString(scopeBytes)
s := fmt.Sprintf("<span class='ui label scope-parent' title='%s'>"+ s := fmt.Sprintf("<span class='ui label scope-parent' data-tooltip-content title='%s'>"+
"<div class='ui label scope-left' style='color: %s !important; background-color: %s !important'>%s</div>"+ "<div class='ui label scope-left' style='color: %s !important; background-color: %s !important'>%s</div>"+
"<div class='ui label scope-right' style='color: %s !important; background-color: %s !important'>%s</div>"+ "<div class='ui label scope-right' style='color: %s !important; background-color: %s !important'>%s</div>"+
"</span>", "</span>",
+5
View File
@@ -4,6 +4,7 @@
package templates package templates
import ( import (
"regexp"
"strings" "strings"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@@ -25,6 +26,10 @@ func (su *StringUtils) Contains(s, substr string) bool {
return strings.Contains(s, substr) return strings.Contains(s, substr)
} }
func (su *StringUtils) ReplaceAllStringRegex(s, regex, new string) string {
return regexp.MustCompile(regex).ReplaceAllString(s, new)
}
func (su *StringUtils) Split(s, sep string) []string { func (su *StringUtils) Split(s, sep string) []string {
return strings.Split(s, sep) return strings.Split(s, sep)
} }
+1
View File
@@ -31,6 +31,7 @@ const (
HookEventRepository HookEventType = "repository" HookEventRepository HookEventType = "repository"
HookEventRelease HookEventType = "release" HookEventRelease HookEventType = "release"
HookEventPackage HookEventType = "package" HookEventPackage HookEventType = "package"
HookEventSchedule HookEventType = "schedule"
) )
// Event returns the HookEventType as an event string // Event returns the HookEventType as an event string
+5
View File
@@ -423,6 +423,7 @@ authorization_failed_desc = The authorization failed because we detected an inva
sspi_auth_failed = SSPI authentication failed sspi_auth_failed = SSPI authentication failed
password_pwned = The password you chose is on a <a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">list of stolen passwords</a> previously exposed in public data breaches. Please try again with a different password and consider changing this password elsewhere too. password_pwned = The password you chose is on a <a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">list of stolen passwords</a> previously exposed in public data breaches. Please try again with a different password and consider changing this password elsewhere too.
password_pwned_err = Could not complete request to HaveIBeenPwned password_pwned_err = Could not complete request to HaveIBeenPwned
last_admin = You cannot remove the last admin. There must be at least one admin.
[mail] [mail]
view_it_on = View it on %s view_it_on = View it on %s
@@ -588,6 +589,8 @@ org_still_own_packages = "This organization still owns one or more packages, del
target_branch_not_exist = Target branch does not exist. target_branch_not_exist = Target branch does not exist.
admin_cannot_delete_self = You cannot delete yourself when you are an admin. Please remove your admin privileges first.
[user] [user]
change_avatar = Change your avatar… change_avatar = Change your avatar…
joined_on = Joined on %s joined_on = Joined on %s
@@ -2322,6 +2325,8 @@ settings.protect_approvals_whitelist_users = Whitelisted reviewers:
settings.protect_approvals_whitelist_teams = Whitelisted teams for reviews: settings.protect_approvals_whitelist_teams = Whitelisted teams for reviews:
settings.dismiss_stale_approvals = Dismiss stale approvals settings.dismiss_stale_approvals = Dismiss stale approvals
settings.dismiss_stale_approvals_desc = When new commits that change the content of the pull request are pushed to the branch, old approvals will be dismissed. settings.dismiss_stale_approvals_desc = When new commits that change the content of the pull request are pushed to the branch, old approvals will be dismissed.
settings.ignore_stale_approvals = Ignore stale approvals
settings.ignore_stale_approvals_desc = Do not count approvals that were made on older commits (stale reviews) towards how many approvals the PR has. Irrelevant if stale reviews are already dismissed.
settings.require_signed_commits = Require Signed Commits settings.require_signed_commits = Require Signed Commits
settings.require_signed_commits_desc = Reject pushes to this branch if they are unsigned or unverifiable. settings.require_signed_commits_desc = Reject pushes to this branch if they are unsigned or unverifiable.
settings.protect_branch_name_pattern = Protected Branch Name Pattern settings.protect_branch_name_pattern = Protected Branch Name Pattern
+77 -13
View File
@@ -512,19 +512,7 @@ func CommonRoutes() *web.Route {
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile) r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
r.Get("/simple/{id}", pypi.PackageMetadata) r.Get("/simple/{id}", pypi.PackageMetadata)
}, reqPackageAccess(perm.AccessModeRead)) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/rpm", func() { r.Group("/rpm", RpmRoutes(r), reqPackageAccess(perm.AccessModeRead))
r.Get(".repo", rpm.GetRepositoryConfig)
r.Get("/repository.key", rpm.GetRepositoryKey)
r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), rpm.UploadPackageFile)
r.Group("/package/{name}/{version}/{architecture}", func() {
r.Get("", rpm.DownloadPackageFile)
r.Delete("", reqPackageAccess(perm.AccessModeWrite), rpm.DeletePackageFile)
})
r.Group("/repodata/{filename}", func() {
r.Head("", rpm.CheckRepositoryFileExistence)
r.Get("", rpm.GetRepositoryFile)
})
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/rubygems", func() { r.Group("/rubygems", func() {
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages) r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest) r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
@@ -589,6 +577,82 @@ func CommonRoutes() *web.Route {
return r return r
} }
// Support for uploading rpm packages with arbitrary depth paths
func RpmRoutes(r *web.Route) func() {
var (
groupRepoInfo = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)\.repo\z`)
groupUpload = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/upload\z`)
groupRpm = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
groupMetadata = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/repodata/([^/]+)\z`)
)
return func() {
r.Methods("HEAD,GET,POST,PUT,PATCH,DELETE", "*", func(ctx *context.Context) {
path := ctx.Params("*")
isHead := ctx.Req.Method == "HEAD"
isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
isPut := ctx.Req.Method == "PUT"
isDelete := ctx.Req.Method == "DELETE"
if path == "/repository.key" && isGetHead {
rpm.GetRepositoryKey(ctx)
return
}
// get repo
m := groupRepoInfo.FindStringSubmatch(path)
if len(m) == 2 && isGetHead {
ctx.SetParams("group", strings.Trim(m[1], "/"))
rpm.GetRepositoryConfig(ctx)
return
}
// get meta
m = groupMetadata.FindStringSubmatch(path)
if len(m) == 3 && isGetHead {
ctx.SetParams("group", strings.Trim(m[1], "/"))
ctx.SetParams("filename", m[2])
if isHead {
rpm.CheckRepositoryFileExistence(ctx)
} else {
rpm.GetRepositoryFile(ctx)
}
return
}
// upload
m = groupUpload.FindStringSubmatch(path)
if len(m) == 2 && isPut {
reqPackageAccess(perm.AccessModeWrite)(ctx)
if ctx.Written() {
return
}
ctx.SetParams("group", strings.Trim(m[1], "/"))
rpm.UploadPackageFile(ctx)
return
}
// rpm down/delete
m = groupRpm.FindStringSubmatch(path)
if len(m) == 6 {
ctx.SetParams("group", strings.Trim(m[1], "/"))
ctx.SetParams("name", m[2])
ctx.SetParams("version", m[3])
ctx.SetParams("architecture", m[4])
if isGetHead {
rpm.DownloadPackageFile(ctx)
return
} else if isDelete {
reqPackageAccess(perm.AccessModeWrite)(ctx)
if ctx.Written() {
return
}
rpm.DeletePackageFile(ctx)
}
}
// default
ctx.Status(http.StatusNotFound)
})
}
}
// ContainerRoutes provides endpoints that implement the OCI API to serve containers // ContainerRoutes provides endpoints that implement the OCI API to serve containers
// These have to be mounted on `/v2/...` to comply with the OCI spec: // These have to be mounted on `/v2/...` to comply with the OCI spec:
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md // https://github.com/opencontainers/distribution-spec/blob/main/spec.md
+25 -14
View File
@@ -33,11 +33,14 @@ func apiError(ctx *context.Context, status int, obj any) {
// https://dnf.readthedocs.io/en/latest/conf_ref.html // https://dnf.readthedocs.io/en/latest/conf_ref.html
func GetRepositoryConfig(ctx *context.Context) { func GetRepositoryConfig(ctx *context.Context) {
group := ctx.Params("group")
if group != "" {
group = fmt.Sprintf("/%s", group)
}
url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name) url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name)
ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+strings.ReplaceAll(group, "/", "-")+`]
ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+`] name=`+ctx.Package.Owner.Name+` - `+setting.AppName+strings.ReplaceAll(group, "/", " - ")+`
name=`+ctx.Package.Owner.Name+` - `+setting.AppName+` baseurl=`+url+group+`/
baseurl=`+url+`
enabled=1 enabled=1
gpgcheck=1 gpgcheck=1
gpgkey=`+url+`/repository.key`) gpgkey=`+url+`/repository.key`)
@@ -64,7 +67,7 @@ func CheckRepositoryFileExistence(ctx *context.Context) {
return return
} }
pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), packages_model.EmptyFileKey) pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), ctx.Params("group"))
if err != nil { if err != nil {
if errors.Is(err, util.ErrNotExist) { if errors.Is(err, util.ErrNotExist) {
ctx.Status(http.StatusNotFound) ctx.Status(http.StatusNotFound)
@@ -94,6 +97,7 @@ func GetRepositoryFile(ctx *context.Context) {
pv, pv,
&packages_service.PackageFileInfo{ &packages_service.PackageFileInfo{
Filename: ctx.Params("filename"), Filename: ctx.Params("filename"),
CompositeKey: ctx.Params("group"),
}, },
) )
if err != nil { if err != nil {
@@ -145,7 +149,7 @@ func UploadPackageFile(ctx *context.Context) {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
return return
} }
group := ctx.Params("group")
_, _, err = packages_service.CreatePackageOrAddFileToExisting( _, _, err = packages_service.CreatePackageOrAddFileToExisting(
ctx, ctx,
&packages_service.PackageCreationInfo{ &packages_service.PackageCreationInfo{
@@ -153,7 +157,7 @@ func UploadPackageFile(ctx *context.Context) {
Owner: ctx.Package.Owner, Owner: ctx.Package.Owner,
PackageType: packages_model.TypeRpm, PackageType: packages_model.TypeRpm,
Name: pck.Name, Name: pck.Name,
Version: pck.Version, Version: strings.Trim(fmt.Sprintf("%s/%s", group, pck.Version), "/"),
}, },
Creator: ctx.Doer, Creator: ctx.Doer,
Metadata: pck.VersionMetadata, Metadata: pck.VersionMetadata,
@@ -161,6 +165,7 @@ func UploadPackageFile(ctx *context.Context) {
&packages_service.PackageFileCreationInfo{ &packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{ PackageFileInfo: packages_service.PackageFileInfo{
Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture), Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture),
CompositeKey: group,
}, },
Creator: ctx.Doer, Creator: ctx.Doer,
Data: buf, Data: buf,
@@ -182,7 +187,7 @@ func UploadPackageFile(ctx *context.Context) {
return return
} }
if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID); err != nil { if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID, group); err != nil {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
return return
} }
@@ -191,19 +196,20 @@ func UploadPackageFile(ctx *context.Context) {
} }
func DownloadPackageFile(ctx *context.Context) { func DownloadPackageFile(ctx *context.Context) {
group := ctx.Params("group")
name := ctx.Params("name") name := ctx.Params("name")
version := ctx.Params("version") version := ctx.Params("version")
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
ctx, ctx,
&packages_service.PackageInfo{ &packages_service.PackageInfo{
Owner: ctx.Package.Owner, Owner: ctx.Package.Owner,
PackageType: packages_model.TypeRpm, PackageType: packages_model.TypeRpm,
Name: name, Name: name,
Version: version, Version: strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"),
}, },
&packages_service.PackageFileInfo{ &packages_service.PackageFileInfo{
Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")), Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")),
CompositeKey: group,
}, },
) )
if err != nil { if err != nil {
@@ -219,14 +225,19 @@ func DownloadPackageFile(ctx *context.Context) {
} }
func DeletePackageFile(webctx *context.Context) { func DeletePackageFile(webctx *context.Context) {
group := webctx.Params("group")
name := webctx.Params("name") name := webctx.Params("name")
version := webctx.Params("version") version := webctx.Params("version")
architecture := webctx.Params("architecture") architecture := webctx.Params("architecture")
var pd *packages_model.PackageDescriptor var pd *packages_model.PackageDescriptor
err := db.WithTx(webctx, func(ctx stdctx.Context) error { err := db.WithTx(webctx, func(ctx stdctx.Context) error {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, webctx.Package.Owner.ID, packages_model.TypeRpm, name, version) pv, err := packages_model.GetVersionByNameAndVersion(ctx,
webctx.Package.Owner.ID,
packages_model.TypeRpm,
name,
strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"),
)
if err != nil { if err != nil {
return err return err
} }
@@ -235,7 +246,7 @@ func DeletePackageFile(webctx *context.Context) {
ctx, ctx,
pv.ID, pv.ID,
fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture), fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture),
packages_model.EmptyFileKey, group,
) )
if err != nil { if err != nil {
return err return err
@@ -275,7 +286,7 @@ func DeletePackageFile(webctx *context.Context) {
notify_service.PackageDelete(webctx, webctx.Doer, pd) notify_service.PackageDelete(webctx, webctx.Doer, pd)
} }
if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID); err != nil { if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID, group); err != nil {
apiError(webctx, http.StatusInternalServerError, err) apiError(webctx, http.StatusInternalServerError, err)
return return
} }
+8 -1
View File
@@ -183,6 +183,8 @@ func EditUser(ctx *context.APIContext) {
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/User" // "$ref": "#/responses/User"
// "400":
// "$ref": "#/responses/error"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "422": // "422":
@@ -264,6 +266,10 @@ func EditUser(ctx *context.APIContext) {
ctx.ContextUser.Visibility = api.VisibilityModes[form.Visibility] ctx.ContextUser.Visibility = api.VisibilityModes[form.Visibility]
} }
if form.Admin != nil { if form.Admin != nil {
if !*form.Admin && user_model.IsLastAdminUser(ctx, ctx.ContextUser) {
ctx.Error(http.StatusBadRequest, "LastAdmin", ctx.Tr("auth.last_admin"))
return
}
ctx.ContextUser.IsAdmin = *form.Admin ctx.ContextUser.IsAdmin = *form.Admin
} }
if form.AllowGitHook != nil { if form.AllowGitHook != nil {
@@ -341,7 +347,8 @@ func DeleteUser(ctx *context.APIContext) {
if err := user_service.DeleteUser(ctx, ctx.ContextUser, ctx.FormBool("purge")); err != nil { if err := user_service.DeleteUser(ctx, ctx.ContextUser, ctx.FormBool("purge")); err != nil {
if models.IsErrUserOwnRepos(err) || if models.IsErrUserOwnRepos(err) ||
models.IsErrUserHasOrgs(err) || models.IsErrUserHasOrgs(err) ||
models.IsErrUserOwnPackages(err) { models.IsErrUserOwnPackages(err) ||
models.IsErrDeleteLastAdminUser(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err) ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(http.StatusInternalServerError, "DeleteUser", err) ctx.Error(http.StatusInternalServerError, "DeleteUser", err)
+3 -3
View File
@@ -1156,9 +1156,9 @@ func Routes() *web.Route {
m.Get("/subscribers", repo.ListSubscribers) m.Get("/subscribers", repo.ListSubscribers)
m.Group("/subscription", func() { m.Group("/subscription", func() {
m.Get("", user.IsWatching) m.Get("", user.IsWatching)
m.Put("", reqToken(), user.Watch) m.Put("", user.Watch)
m.Delete("", reqToken(), user.Unwatch) m.Delete("", user.Unwatch)
}) }, reqToken())
m.Group("/releases", func() { m.Group("/releases", func() {
m.Combo("").Get(repo.ListReleases). m.Combo("").Get(repo.ListReleases).
Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease) Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease)
+5
View File
@@ -636,6 +636,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
BlockOnRejectedReviews: form.BlockOnRejectedReviews, BlockOnRejectedReviews: form.BlockOnRejectedReviews,
BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests, BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
DismissStaleApprovals: form.DismissStaleApprovals, DismissStaleApprovals: form.DismissStaleApprovals,
IgnoreStaleApprovals: form.IgnoreStaleApprovals,
RequireSignedCommits: form.RequireSignedCommits, RequireSignedCommits: form.RequireSignedCommits,
ProtectedFilePatterns: form.ProtectedFilePatterns, ProtectedFilePatterns: form.ProtectedFilePatterns,
UnprotectedFilePatterns: form.UnprotectedFilePatterns, UnprotectedFilePatterns: form.UnprotectedFilePatterns,
@@ -830,6 +831,10 @@ func EditBranchProtection(ctx *context.APIContext) {
protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
} }
if form.IgnoreStaleApprovals != nil {
protectBranch.IgnoreStaleApprovals = *form.IgnoreStaleApprovals
}
if form.RequireSignedCommits != nil { if form.RequireSignedCommits != nil {
protectBranch.RequireSignedCommits = *form.RequireSignedCommits protectBranch.RequireSignedCommits = *form.RequireSignedCommits
} }
+4 -1
View File
@@ -8,6 +8,7 @@ import (
"errors" "errors"
"net/http" "net/http"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@@ -53,7 +54,9 @@ func ListCollaborators(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
count, err := repo_model.CountCollaborators(ctx, ctx.Repo.Repository.ID) count, err := db.Count[repo_model.Collaboration](ctx, repo_model.FindCollaborationOptions{
RepoID: ctx.Repo.Repository.ID,
})
if err != nil { if err != nil {
ctx.InternalServerError(err) ctx.InternalServerError(err)
return return
+20 -17
View File
@@ -102,14 +102,16 @@ func GetIssueDependencies(ctx *context.APIContext) {
return return
} }
var lastRepoID int64 repoPerms := make(map[int64]access_model.Permission)
var lastPerm access_model.Permission repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
for _, blocker := range blockersInfo { for _, blocker := range blockersInfo {
// Get the permissions for this repository // Get the permissions for this repository
perm := lastPerm // If the repo ID exists in the map, return the exist permissions
if lastRepoID != blocker.Repository.ID { // else get the permission and add it to the map
if blocker.Repository.ID == ctx.Repo.Repository.ID { var perm access_model.Permission
perm = ctx.Repo.Permission existPerm, ok := repoPerms[blocker.RepoID]
if ok {
perm = existPerm
} else { } else {
var err error var err error
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer) perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
@@ -117,8 +119,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
ctx.ServerError("GetUserRepoPermission", err) ctx.ServerError("GetUserRepoPermission", err)
return return
} }
} repoPerms[blocker.RepoID] = perm
lastRepoID = blocker.Repository.ID
} }
// check permission // check permission
@@ -345,20 +346,23 @@ func GetIssueBlocks(ctx *context.APIContext) {
return return
} }
var lastRepoID int64
var lastPerm access_model.Permission
var issues []*issues_model.Issue var issues []*issues_model.Issue
repoPerms := make(map[int64]access_model.Permission)
repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
for i, depMeta := range deps { for i, depMeta := range deps {
if i < skip || i >= max { if i < skip || i >= max {
continue continue
} }
// Get the permissions for this repository // Get the permissions for this repository
perm := lastPerm // If the repo ID exists in the map, return the exist permissions
if lastRepoID != depMeta.Repository.ID { // else get the permission and add it to the map
if depMeta.Repository.ID == ctx.Repo.Repository.ID { var perm access_model.Permission
perm = ctx.Repo.Permission existPerm, ok := repoPerms[depMeta.RepoID]
if ok {
perm = existPerm
} else { } else {
var err error var err error
perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer) perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
@@ -366,8 +370,7 @@ func GetIssueBlocks(ctx *context.APIContext) {
ctx.ServerError("GetUserRepoPermission", err) ctx.ServerError("GetUserRepoPermission", err)
return return
} }
} repoPerms[depMeta.RepoID] = perm
lastRepoID = depMeta.Repository.ID
} }
if !perm.CanReadIssuesOrPulls(depMeta.Issue.IsPull) { if !perm.CanReadIssuesOrPulls(depMeta.Issue.IsPull) {
+10 -16
View File
@@ -1329,17 +1329,16 @@ func GetPullRequestCommits(ctx *context.APIContext) {
userCache := make(map[string]*user_model.User) userCache := make(map[string]*user_model.User)
start, end := listOptions.GetStartEnd() start, limit := listOptions.GetSkipTake()
if end > totalNumberOfCommits { limit = min(limit, totalNumberOfCommits-start)
end = totalNumberOfCommits limit = max(limit, 0)
}
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification") verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
files := ctx.FormString("files") == "" || ctx.FormBool("files") files := ctx.FormString("files") == "" || ctx.FormBool("files")
apiCommits := make([]*api.Commit, 0, end-start) apiCommits := make([]*api.Commit, 0, limit)
for i := start; i < end; i++ { for i := start; i < start+limit; i++ {
apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, baseGitRepo, commits[i], userCache, apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, baseGitRepo, commits[i], userCache,
convert.ToCommitOptions{ convert.ToCommitOptions{
Stat: true, Stat: true,
@@ -1477,19 +1476,14 @@ func GetPullRequestFiles(ctx *context.APIContext) {
totalNumberOfFiles := diff.NumFiles totalNumberOfFiles := diff.NumFiles
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize))) totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
start, end := listOptions.GetStartEnd() start, limit := listOptions.GetSkipTake()
if end > totalNumberOfFiles { limit = min(limit, totalNumberOfFiles-start)
end = totalNumberOfFiles
}
lenFiles := end - start limit = max(limit, 0)
if lenFiles < 0 {
lenFiles = 0
}
apiFiles := make([]*api.ChangedFile, 0, lenFiles) apiFiles := make([]*api.ChangedFile, 0, limit)
for i := start; i < end; i++ { for i := start; i < start+limit; i++ {
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID)) apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
} }
+4 -2
View File
@@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
@@ -154,9 +155,10 @@ func ListReleases(ctx *context.APIContext) {
IncludeTags: false, IncludeTags: false,
IsDraft: ctx.FormOptionalBool("draft"), IsDraft: ctx.FormOptionalBool("draft"),
IsPreRelease: ctx.FormOptionalBool("pre-release"), IsPreRelease: ctx.FormOptionalBool("pre-release"),
RepoID: ctx.Repo.Repository.ID,
} }
releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts) releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetReleasesByRepoID", err) ctx.Error(http.StatusInternalServerError, "GetReleasesByRepoID", err)
return return
@@ -170,7 +172,7 @@ func ListReleases(ctx *context.APIContext) {
rels[i] = convert.ToAPIRelease(ctx, ctx.Repo.Repository, release) rels[i] = convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)
} }
filteredCount, err := repo_model.CountReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts) filteredCount, err := db.Count[repo_model.Release](ctx, opts)
if err != nil { if err != nil {
ctx.InternalServerError(err) ctx.InternalServerError(err)
return return
+1 -1
View File
@@ -983,7 +983,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
} }
if len(units)+len(deleteUnitTypes) > 0 { if len(units)+len(deleteUnitTypes) > 0 {
if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil { if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err) ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
return err return err
} }
+18 -9
View File
@@ -18,23 +18,25 @@ import (
) )
func listGPGKeys(ctx *context.APIContext, uid int64, listOptions db.ListOptions) { func listGPGKeys(ctx *context.APIContext, uid int64, listOptions db.ListOptions) {
keys, err := asymkey_model.ListGPGKeys(ctx, uid, listOptions) keys, total, err := db.FindAndCount[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
ListOptions: listOptions,
OwnerID: uid,
})
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err) ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
return return
} }
if err := asymkey_model.GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
return
}
apiKeys := make([]*api.GPGKey, len(keys)) apiKeys := make([]*api.GPGKey, len(keys))
for i := range keys { for i := range keys {
apiKeys[i] = convert.ToGPGKey(keys[i]) apiKeys[i] = convert.ToGPGKey(keys[i])
} }
total, err := asymkey_model.CountUserGPGKeys(ctx, uid)
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.SetTotalCountHeader(total) ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &apiKeys) ctx.JSON(http.StatusOK, &apiKeys)
} }
@@ -121,6 +123,10 @@ func GetGPGKey(ctx *context.APIContext) {
} }
return return
} }
if err := key.LoadSubKeys(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadSubKeys", err)
return
}
ctx.JSON(http.StatusOK, convert.ToGPGKey(key)) ctx.JSON(http.StatusOK, convert.ToGPGKey(key))
} }
@@ -198,7 +204,10 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "VerifyUserGPGKey", err) ctx.Error(http.StatusInternalServerError, "VerifyUserGPGKey", err)
} }
key, err := asymkey_model.GetGPGKeysByKeyID(ctx, form.KeyID) keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
KeyID: form.KeyID,
IncludeSubKeys: true,
})
if err != nil { if err != nil {
if asymkey_model.IsErrGPGKeyNotExist(err) { if asymkey_model.IsErrGPGKeyNotExist(err) {
ctx.NotFound() ctx.NotFound()
@@ -207,7 +216,7 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
} }
return return
} }
ctx.JSON(http.StatusOK, convert.ToGPGKey(key[0])) ctx.JSON(http.StatusOK, convert.ToGPGKey(keys[0]))
} }
// swagger:parameters userCurrentPostGPGKey // swagger:parameters userCurrentPostGPGKey
+10 -1
View File
@@ -436,6 +436,12 @@ func EditUserPost(ctx *context.Context) {
} }
// Check whether user is the last admin
if !form.Admin && user_model.IsLastAdminUser(ctx, u) {
ctx.RenderWithErr(ctx.Tr("auth.last_admin"), tplUserEdit, &form)
return
}
u.LoginName = form.LoginName u.LoginName = form.LoginName
u.FullName = form.FullName u.FullName = form.FullName
emailChanged := !strings.EqualFold(u.Email, form.Email) emailChanged := !strings.EqualFold(u.Email, form.Email)
@@ -503,7 +509,10 @@ func DeleteUser(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid"))) ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
case models.IsErrUserOwnPackages(err): case models.IsErrUserOwnPackages(err):
ctx.Flash.Error(ctx.Tr("admin.users.still_own_packages")) ctx.Flash.Error(ctx.Tr("admin.users.still_own_packages"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + ctx.Params(":userid")) ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
case models.IsErrDeleteLastAdminUser(err):
ctx.Flash.Error(ctx.Tr("auth.last_admin"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
default: default:
ctx.ServerError("DeleteUser", err) ctx.ServerError("DeleteUser", err)
} }
+3 -1
View File
@@ -6,6 +6,7 @@ package feed
import ( import (
"time" "time"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@@ -14,8 +15,9 @@ import (
// shows tags and/or releases on the repo as RSS / Atom feed // shows tags and/or releases on the repo as RSS / Atom feed
func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleasesOnly bool, formatType string) { func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleasesOnly bool, formatType string) {
releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{ releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
IncludeTags: !isReleasesOnly, IncludeTags: !isReleasesOnly,
RepoID: ctx.Repo.Repository.ID,
}) })
if err != nil { if err != nil {
ctx.ServerError("GetReleasesByRepoID", err) ctx.ServerError("GetReleasesByRepoID", err)
+1
View File
@@ -845,6 +845,7 @@ func CompareDiff(ctx *context.Context) {
} }
} }
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanWrite(unit.TypeProjects)
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment") upload.AddUploadContext(ctx, "comment")
+17 -21
View File
@@ -1962,7 +1962,7 @@ func ViewIssue(ctx *context.Context) {
return return
} }
ctx.Data["BlockingDependencies"], ctx.Data["BlockingByDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking) ctx.Data["BlockingDependencies"], ctx.Data["BlockingDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
if ctx.Written() { if ctx.Written() {
return return
} }
@@ -2023,16 +2023,16 @@ func ViewIssue(ctx *context.Context) {
// checkBlockedByIssues return canRead and notPermitted // checkBlockedByIssues return canRead and notPermitted
func checkBlockedByIssues(ctx *context.Context, blockers []*issues_model.DependencyInfo) (canRead, notPermitted []*issues_model.DependencyInfo) { func checkBlockedByIssues(ctx *context.Context, blockers []*issues_model.DependencyInfo) (canRead, notPermitted []*issues_model.DependencyInfo) {
var ( repoPerms := make(map[int64]access_model.Permission)
lastRepoID int64 repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
lastPerm access_model.Permission for _, blocker := range blockers {
)
for i, blocker := range blockers {
// Get the permissions for this repository // Get the permissions for this repository
perm := lastPerm // If the repo ID exists in the map, return the exist permissions
if lastRepoID != blocker.Repository.ID { // else get the permission and add it to the map
if blocker.Repository.ID == ctx.Repo.Repository.ID { var perm access_model.Permission
perm = ctx.Repo.Permission existPerm, ok := repoPerms[blocker.RepoID]
if ok {
perm = existPerm
} else { } else {
var err error var err error
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer) perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
@@ -2040,21 +2040,17 @@ func checkBlockedByIssues(ctx *context.Context, blockers []*issues_model.Depende
ctx.ServerError("GetUserRepoPermission", err) ctx.ServerError("GetUserRepoPermission", err)
return nil, nil return nil, nil
} }
repoPerms[blocker.RepoID] = perm
} }
lastRepoID = blocker.Repository.ID if perm.CanReadIssuesOrPulls(blocker.Issue.IsPull) {
} canRead = append(canRead, blocker)
} else {
// check permission notPermitted = append(notPermitted, blocker)
if !perm.CanReadIssuesOrPulls(blocker.Issue.IsPull) {
blockers[len(notPermitted)], blockers[i] = blocker, blockers[len(notPermitted)]
notPermitted = blockers[:len(notPermitted)+1]
} }
} }
blockers = blockers[len(notPermitted):] sortDependencyInfo(canRead)
sortDependencyInfo(blockers)
sortDependencyInfo(notPermitted) sortDependencyInfo(notPermitted)
return canRead, notPermitted
return blockers, notPermitted
} }
func sortDependencyInfo(blockers []*issues_model.DependencyInfo) { func sortDependencyInfo(blockers []*issues_model.DependencyInfo) {
+34 -26
View File
@@ -1149,30 +1149,28 @@ func MergePullRequest(ctx *context.Context) {
switch { switch {
case errors.Is(err, pull_service.ErrIsClosed): case errors.Is(err, pull_service.ErrIsClosed):
if issue.IsPull { if issue.IsPull {
ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed")) ctx.JSONError(ctx.Tr("repo.pulls.is_closed"))
} else { } else {
ctx.Flash.Error(ctx.Tr("repo.issues.closed_title")) ctx.JSONError(ctx.Tr("repo.issues.closed_title"))
} }
case errors.Is(err, pull_service.ErrUserNotAllowedToMerge): case errors.Is(err, pull_service.ErrUserNotAllowedToMerge):
ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed")) ctx.JSONError(ctx.Tr("repo.pulls.update_not_allowed"))
case errors.Is(err, pull_service.ErrHasMerged): case errors.Is(err, pull_service.ErrHasMerged):
ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged")) ctx.JSONError(ctx.Tr("repo.pulls.has_merged"))
case errors.Is(err, pull_service.ErrIsWorkInProgress): case errors.Is(err, pull_service.ErrIsWorkInProgress):
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip")) ctx.JSONError(ctx.Tr("repo.pulls.no_merge_wip"))
case errors.Is(err, pull_service.ErrNotMergableState): case errors.Is(err, pull_service.ErrNotMergableState):
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
case models.IsErrDisallowedToMerge(err): case models.IsErrDisallowedToMerge(err):
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
case asymkey_service.IsErrWontSign(err): case asymkey_service.IsErrWontSign(err):
ctx.Flash.Error(err.Error()) // has no translation ... ctx.JSONError(err.Error()) // has no translation ...
case errors.Is(err, pull_service.ErrDependenciesLeft): case errors.Is(err, pull_service.ErrDependenciesLeft):
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked")) ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
default: default:
ctx.ServerError("WebCheck", err) ctx.ServerError("WebCheck", err)
return
} }
ctx.Redirect(issue.Link())
return return
} }
@@ -1180,18 +1178,18 @@ func MergePullRequest(ctx *context.Context) {
if manuallyMerged { if manuallyMerged {
if err := pull_service.MergedManually(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil { if err := pull_service.MergedManually(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
switch { switch {
case models.IsErrInvalidMergeStyle(err): case models.IsErrInvalidMergeStyle(err):
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
case strings.Contains(err.Error(), "Wrong commit ID"): case strings.Contains(err.Error(), "Wrong commit ID"):
ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id")) ctx.JSONError(ctx.Tr("repo.pulls.wrong_commit_id"))
default: default:
ctx.ServerError("MergedManually", err) ctx.ServerError("MergedManually", err)
return
}
} }
ctx.Redirect(issue.Link()) return
}
ctx.JSONRedirect(issue.Link())
return return
} }
@@ -1221,15 +1219,14 @@ func MergePullRequest(ctx *context.Context) {
} else if scheduled { } else if scheduled {
// nothing more to do ... // nothing more to do ...
ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_newly_scheduled")) ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_newly_scheduled"))
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index)) ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
return return
} }
} }
if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil { if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
if models.IsErrInvalidMergeStyle(err) { if models.IsErrInvalidMergeStyle(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
ctx.Redirect(issue.Link())
} else if models.IsErrMergeConflicts(err) { } else if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts) conflictError := err.(models.ErrMergeConflicts)
flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
@@ -1242,7 +1239,7 @@ func MergePullRequest(ctx *context.Context) {
return return
} }
ctx.Flash.Error(flashError) ctx.Flash.Error(flashError)
ctx.Redirect(issue.Link()) ctx.JSONRedirect(issue.Link())
} else if models.IsErrRebaseConflicts(err) { } else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts) conflictError := err.(models.ErrRebaseConflicts)
flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
@@ -1286,7 +1283,7 @@ func MergePullRequest(ctx *context.Context) {
} }
ctx.Flash.Error(flashError) ctx.Flash.Error(flashError)
} }
ctx.Redirect(issue.Link()) ctx.JSONRedirect(issue.Link())
} else { } else {
ctx.ServerError("Merge", err) ctx.ServerError("Merge", err)
} }
@@ -1295,7 +1292,7 @@ func MergePullRequest(ctx *context.Context) {
log.Trace("Pull request merged: %d", pr.ID) log.Trace("Pull request merged: %d", pr.ID)
if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil { if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
ctx.ServerError("CreateOrStopIssueStopwatch", err) ctx.ServerError("stopTimerIfAvailable", err)
return return
} }
@@ -1309,7 +1306,7 @@ func MergePullRequest(ctx *context.Context) {
return return
} }
if exist { if exist {
ctx.Redirect(issue.Link()) ctx.JSONRedirect(issue.Link())
return return
} }
@@ -1327,7 +1324,7 @@ func MergePullRequest(ctx *context.Context) {
deleteBranch(ctx, pr, headRepo) deleteBranch(ctx, pr, headRepo)
} }
ctx.Redirect(issue.Link()) ctx.JSONRedirect(issue.Link())
} }
// CancelAutoMergePullRequest cancels a scheduled pr // CancelAutoMergePullRequest cancels a scheduled pr
@@ -1387,7 +1384,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
return return
} }
labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, *form, true) labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, *form, true)
if ctx.Written() { if ctx.Written() {
return return
} }
@@ -1465,6 +1462,17 @@ func CompareAndPullRequestPost(ctx *context.Context) {
return return
} }
if projectID > 0 {
if !ctx.Repo.CanWrite(unit.TypeProjects) {
ctx.Error(http.StatusBadRequest, "user hasn't the permission to write to projects")
return
}
if err := issues_model.ChangeProjectAssign(ctx, pullIssue, ctx.Doer, projectID); err != nil {
ctx.ServerError("ChangeProjectAssign", err)
return
}
}
log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID) log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
ctx.JSONRedirect(pullIssue.Link()) ctx.JSONRedirect(pullIssue.Link())
} }
+4 -2
View File
@@ -95,9 +95,10 @@ func Releases(ctx *context.Context) {
ListOptions: listOptions, ListOptions: listOptions,
// only show draft releases for users who can write, read-only users shouldn't see draft releases. // only show draft releases for users who can write, read-only users shouldn't see draft releases.
IncludeDrafts: writeAccess, IncludeDrafts: writeAccess,
RepoID: ctx.Repo.Repository.ID,
} }
releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts) releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil { if err != nil {
ctx.ServerError("GetReleasesByRepoID", err) ctx.ServerError("GetReleasesByRepoID", err)
return return
@@ -194,9 +195,10 @@ func TagsList(ctx *context.Context) {
IncludeDrafts: true, IncludeDrafts: true,
IncludeTags: true, IncludeTags: true,
HasSha1: util.OptionalBoolTrue, HasSha1: util.OptionalBoolTrue,
RepoID: ctx.Repo.Repository.ID,
} }
releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts) releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil { if err != nil {
ctx.ServerError("GetReleasesByRepoID", err) ctx.ServerError("GetReleasesByRepoID", err)
return return
+3 -1
View File
@@ -6,6 +6,7 @@ package repo
import ( import (
"testing" "testing"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
@@ -74,8 +75,9 @@ func TestCalReleaseNumCommitsBehind(t *testing.T) {
contexttest.LoadGitRepo(t, ctx) contexttest.LoadGitRepo(t, ctx)
t.Cleanup(func() { ctx.Repo.GitRepo.Close() }) t.Cleanup(func() { ctx.Repo.GitRepo.Close() })
releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{ releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
IncludeDrafts: ctx.Repo.CanWrite(unit.TypeReleases), IncludeDrafts: ctx.Repo.CanWrite(unit.TypeReleases),
RepoID: ctx.Repo.Repository.ID,
}) })
assert.NoError(t, err) assert.NoError(t, err)
+4 -1
View File
@@ -379,7 +379,10 @@ func RedirectDownload(ctx *context.Context) {
) )
tagNames := []string{vTag} tagNames := []string{vTag}
curRepo := ctx.Repo.Repository curRepo := ctx.Repo.Repository
releases, err := repo_model.GetReleasesByRepoIDAndNames(ctx, curRepo.ID, tagNames) releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
RepoID: curRepo.ID,
TagNames: tagNames,
})
if err != nil { if err != nil {
ctx.ServerError("RedirectDownload", err) ctx.ServerError("RedirectDownload", err)
return return
+7 -17
View File
@@ -6,13 +6,12 @@ package setting
import ( import (
"net/http" "net/http"
repo_model "code.gitea.io/gitea/models/repo" git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/web/repo" "code.gitea.io/gitea/routers/web/repo"
notify_service "code.gitea.io/gitea/services/notify" repo_service "code.gitea.io/gitea/services/repository"
) )
// SetDefaultBranchPost set default branch // SetDefaultBranchPost set default branch
@@ -35,24 +34,15 @@ func SetDefaultBranchPost(ctx *context.Context) {
} }
branch := ctx.FormString("branch") branch := ctx.FormString("branch")
if !ctx.Repo.GitRepo.IsBranchExist(branch) { if err := repo_service.SetRepoDefaultBranch(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, branch); err != nil {
switch {
case git_model.IsErrBranchNotExist(err):
ctx.Status(http.StatusNotFound) ctx.Status(http.StatusNotFound)
return default:
} else if repo.DefaultBranch != branch {
repo.DefaultBranch = branch
if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
if !git.IsErrUnsupportedVersion(err) {
ctx.ServerError("SetDefaultBranch", err) ctx.ServerError("SetDefaultBranch", err)
}
return return
} }
}
if err := repo_model.UpdateDefaultBranch(ctx, repo); err != nil {
ctx.ServerError("SetDefaultBranch", err)
return
}
notify_service.ChangeDefaultBranch(ctx, repo)
}
log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
@@ -251,6 +251,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
protectBranch.DismissStaleApprovals = f.DismissStaleApprovals protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
protectBranch.IgnoreStaleApprovals = f.IgnoreStaleApprovals
protectBranch.RequireSignedCommits = f.RequireSignedCommits protectBranch.RequireSignedCommits = f.RequireSignedCommits
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
+1 -1
View File
@@ -594,7 +594,7 @@ func SettingsPost(ctx *context.Context) {
return return
} }
if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil { if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
ctx.ServerError("UpdateRepositoryUnits", err) ctx.ServerError("UpdateRepositoryUnits", err)
return return
} }
+4 -1
View File
@@ -663,7 +663,10 @@ func ShowSSHKeys(ctx *context.Context) {
// ShowGPGKeys output all the public GPG keys of user by uid // ShowGPGKeys output all the public GPG keys of user by uid
func ShowGPGKeys(ctx *context.Context) { func ShowGPGKeys(ctx *context.Context) {
keys, err := asymkey_model.ListGPGKeys(ctx, ctx.ContextUser.ID, db.ListOptions{}) keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
ListOptions: db.ListOptionsAll,
OwnerID: ctx.ContextUser.ID,
})
if err != nil { if err != nil {
ctx.ServerError("ListGPGKeys", err) ctx.ServerError("ListGPGKeys", err)
return return
+10
View File
@@ -244,6 +244,13 @@ func DeleteAccount(ctx *context.Context) {
return return
} }
// admin should not delete themself
if ctx.Doer.IsAdmin {
ctx.Flash.Error(ctx.Tr("form.admin_cannot_delete_self"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
return
}
if err := user.DeleteUser(ctx, ctx.Doer, false); err != nil { if err := user.DeleteUser(ctx, ctx.Doer, false); err != nil {
switch { switch {
case models.IsErrUserOwnRepos(err): case models.IsErrUserOwnRepos(err):
@@ -255,6 +262,9 @@ func DeleteAccount(ctx *context.Context) {
case models.IsErrUserOwnPackages(err): case models.IsErrUserOwnPackages(err):
ctx.Flash.Error(ctx.Tr("form.still_own_packages")) ctx.Flash.Error(ctx.Tr("form.still_own_packages"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account") ctx.Redirect(setting.AppSubURL + "/user/settings/account")
case models.IsErrDeleteLastAdminUser(err):
ctx.Flash.Error(ctx.Tr("auth.last_admin"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
default: default:
ctx.ServerError("DeleteUser", err) ctx.ServerError("DeleteUser", err)
} }
+13 -2
View File
@@ -277,18 +277,29 @@ func loadKeysData(ctx *context.Context) {
} }
ctx.Data["ExternalKeys"] = externalKeys ctx.Data["ExternalKeys"] = externalKeys
gpgkeys, err := asymkey_model.ListGPGKeys(ctx, ctx.Doer.ID, db.ListOptions{}) gpgkeys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
ListOptions: db.ListOptionsAll,
OwnerID: ctx.Doer.ID,
})
if err != nil { if err != nil {
ctx.ServerError("ListGPGKeys", err) ctx.ServerError("ListGPGKeys", err)
return return
} }
if err := asymkey_model.GPGKeyList(gpgkeys).LoadSubKeys(ctx); err != nil {
ctx.ServerError("LoadSubKeys", err)
return
}
ctx.Data["GPGKeys"] = gpgkeys ctx.Data["GPGKeys"] = gpgkeys
tokenToSign := asymkey_model.VerificationToken(ctx.Doer, 1) tokenToSign := asymkey_model.VerificationToken(ctx.Doer, 1)
// generate a new aes cipher using the csrfToken // generate a new aes cipher using the csrfToken
ctx.Data["TokenToSign"] = tokenToSign ctx.Data["TokenToSign"] = tokenToSign
principals, err := asymkey_model.ListPrincipalKeys(ctx, ctx.Doer.ID, db.ListOptions{}) principals, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
ListOptions: db.ListOptionsAll,
OwnerID: ctx.Doer.ID,
KeyTypes: []asymkey_model.KeyType{asymkey_model.KeyTypePrincipal},
})
if err != nil { if err != nil {
ctx.ServerError("ListPrincipalKeys", err) ctx.ServerError("ListPrincipalKeys", err)
return return
+15 -20
View File
@@ -117,6 +117,9 @@ func notify(ctx context.Context, input *notifyInput) error {
return nil return nil
} }
if unit_model.TypeActions.UnitGlobalDisabled() { if unit_model.TypeActions.UnitGlobalDisabled() {
if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
log.Error("CleanRepoScheduleTasks: %v", err)
}
return nil return nil
} }
if err := input.Repo.LoadUnits(ctx); err != nil { if err := input.Repo.LoadUnits(ctx); err != nil {
@@ -153,7 +156,11 @@ func notify(ctx context.Context, input *notifyInput) error {
var detectedWorkflows []*actions_module.DetectedWorkflow var detectedWorkflows []*actions_module.DetectedWorkflow
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig() actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit, input.Event, input.Payload) workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit,
input.Event,
input.Payload,
input.Event == webhook_module.HookEventPush && input.Ref == input.Repo.DefaultBranch,
)
if err != nil { if err != nil {
return fmt.Errorf("DetectWorkflows: %w", err) return fmt.Errorf("DetectWorkflows: %w", err)
} }
@@ -167,7 +174,7 @@ func notify(ctx context.Context, input *notifyInput) error {
continue continue
} }
if wf.TriggerEvent != actions_module.GithubEventPullRequestTarget { if wf.TriggerEvent.Name != actions_module.GithubEventPullRequestTarget {
detectedWorkflows = append(detectedWorkflows, wf) detectedWorkflows = append(detectedWorkflows, wf)
} }
} }
@@ -180,7 +187,7 @@ func notify(ctx context.Context, input *notifyInput) error {
if err != nil { if err != nil {
return fmt.Errorf("gitRepo.GetCommit: %w", err) return fmt.Errorf("gitRepo.GetCommit: %w", err)
} }
baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload) baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload, false)
if err != nil { if err != nil {
return fmt.Errorf("DetectWorkflows: %w", err) return fmt.Errorf("DetectWorkflows: %w", err)
} }
@@ -188,7 +195,7 @@ func notify(ctx context.Context, input *notifyInput) error {
log.Trace("repo %s with commit %s couldn't find pull_request_target workflows", input.Repo.RepoPath(), baseCommit.ID) log.Trace("repo %s with commit %s couldn't find pull_request_target workflows", input.Repo.RepoPath(), baseCommit.ID)
} else { } else {
for _, wf := range baseWorkflows { for _, wf := range baseWorkflows {
if wf.TriggerEvent == actions_module.GithubEventPullRequestTarget { if wf.TriggerEvent.Name == actions_module.GithubEventPullRequestTarget {
detectedWorkflows = append(detectedWorkflows, wf) detectedWorkflows = append(detectedWorkflows, wf)
} }
} }
@@ -265,7 +272,7 @@ func handleWorkflows(
IsForkPullRequest: isForkPullRequest, IsForkPullRequest: isForkPullRequest,
Event: input.Event, Event: input.Event,
EventPayload: string(p), EventPayload: string(p),
TriggerEvent: dwf.TriggerEvent, TriggerEvent: dwf.TriggerEvent.Name,
Status: actions_model.StatusWaiting, Status: actions_model.StatusWaiting,
} }
if need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer); err != nil { if need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer); err != nil {
@@ -289,6 +296,7 @@ func handleWorkflows(
run.RepoID, run.RepoID,
run.Ref, run.Ref,
run.WorkflowID, run.WorkflowID,
run.Event,
); err != nil { ); err != nil {
log.Error("CancelRunningJobs: %v", err) log.Error("CancelRunningJobs: %v", err)
} }
@@ -414,8 +422,8 @@ func handleSchedules(
log.Error("CountSchedules: %v", err) log.Error("CountSchedules: %v", err)
return err return err
} else if count > 0 { } else if count > 0 {
if err := actions_model.DeleteScheduleTaskByRepo(ctx, input.Repo.ID); err != nil { if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
log.Error("DeleteCronTaskByRepo: %v", err) log.Error("CleanRepoScheduleTasks: %v", err)
} }
} }
@@ -456,19 +464,6 @@ func handleSchedules(
Specs: schedules, Specs: schedules,
Content: dwf.Content, Content: dwf.Content,
} }
// cancel running jobs if the event is push
if run.Event == webhook_module.HookEventPush {
// cancel running jobs of the same workflow
if err := actions_model.CancelRunningJobs(
ctx,
run.RepoID,
run.Ref,
run.WorkflowID,
); err != nil {
log.Error("CancelRunningJobs: %v", err)
}
}
crons = append(crons, run) crons = append(crons, run)
} }
+2
View File
@@ -59,6 +59,7 @@ func startTasks(ctx context.Context) error {
row.RepoID, row.RepoID,
row.Schedule.Ref, row.Schedule.Ref,
row.Schedule.WorkflowID, row.Schedule.WorkflowID,
webhook_module.HookEventSchedule,
); err != nil { ); err != nil {
log.Error("CancelRunningJobs: %v", err) log.Error("CancelRunningJobs: %v", err)
} }
@@ -113,6 +114,7 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule)
CommitSHA: cron.CommitSHA, CommitSHA: cron.CommitSHA,
Event: cron.Event, Event: cron.Event,
EventPayload: cron.EventPayload, EventPayload: cron.EventPayload,
TriggerEvent: string(webhook_module.HookEventSchedule),
ScheduleID: cron.ID, ScheduleID: cron.ID,
Status: actions_model.StatusWaiting, Status: actions_model.StatusWaiting,
} }
+16 -4
View File
@@ -143,7 +143,10 @@ Loop:
case always: case always:
break Loop break Loop
case pubkey: case pubkey:
keys, err := asymkey_model.ListGPGKeys(ctx, u.ID, db.ListOptions{}) keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
OwnerID: u.ID,
IncludeSubKeys: true,
})
if err != nil { if err != nil {
return false, "", nil, err return false, "", nil, err
} }
@@ -179,7 +182,10 @@ Loop:
case always: case always:
break Loop break Loop
case pubkey: case pubkey:
keys, err := asymkey_model.ListGPGKeys(ctx, u.ID, db.ListOptions{}) keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
OwnerID: u.ID,
IncludeSubKeys: true,
})
if err != nil { if err != nil {
return false, "", nil, err return false, "", nil, err
} }
@@ -232,7 +238,10 @@ Loop:
case always: case always:
break Loop break Loop
case pubkey: case pubkey:
keys, err := asymkey_model.ListGPGKeys(ctx, u.ID, db.ListOptions{}) keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
OwnerID: u.ID,
IncludeSubKeys: true,
})
if err != nil { if err != nil {
return false, "", nil, err return false, "", nil, err
} }
@@ -294,7 +303,10 @@ Loop:
case always: case always:
break Loop break Loop
case pubkey: case pubkey:
keys, err := asymkey_model.ListGPGKeys(ctx, u.ID, db.ListOptions{}) keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
OwnerID: u.ID,
IncludeSubKeys: true,
})
if err != nil { if err != nil {
return false, "", nil, err return false, "", nil, err
} }
+19 -20
View File
@@ -171,6 +171,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch) *api
BlockOnOfficialReviewRequests: bp.BlockOnOfficialReviewRequests, BlockOnOfficialReviewRequests: bp.BlockOnOfficialReviewRequests,
BlockOnOutdatedBranch: bp.BlockOnOutdatedBranch, BlockOnOutdatedBranch: bp.BlockOnOutdatedBranch,
DismissStaleApprovals: bp.DismissStaleApprovals, DismissStaleApprovals: bp.DismissStaleApprovals,
IgnoreStaleApprovals: bp.IgnoreStaleApprovals,
RequireSignedCommits: bp.RequireSignedCommits, RequireSignedCommits: bp.RequireSignedCommits,
ProtectedFilePatterns: bp.ProtectedFilePatterns, ProtectedFilePatterns: bp.ProtectedFilePatterns,
UnprotectedFilePatterns: bp.UnprotectedFilePatterns, UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
@@ -321,40 +322,38 @@ func ToTeam(ctx context.Context, team *organization.Team, loadOrg ...bool) (*api
// ToTeams convert models.Team list to api.Team list // ToTeams convert models.Team list to api.Team list
func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]*api.Team, error) { func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]*api.Team, error) {
if len(teams) == 0 || teams[0] == nil {
return nil, nil
}
cache := make(map[int64]*api.Organization) cache := make(map[int64]*api.Organization)
apiTeams := make([]*api.Team, len(teams)) apiTeams := make([]*api.Team, 0, len(teams))
for i := range teams { for _, t := range teams {
if err := teams[i].LoadUnits(ctx); err != nil { if err := t.LoadUnits(ctx); err != nil {
return nil, err return nil, err
} }
apiTeams[i] = &api.Team{ apiTeam := &api.Team{
ID: teams[i].ID, ID: t.ID,
Name: teams[i].Name, Name: t.Name,
Description: teams[i].Description, Description: t.Description,
IncludesAllRepositories: teams[i].IncludesAllRepositories, IncludesAllRepositories: t.IncludesAllRepositories,
CanCreateOrgRepo: teams[i].CanCreateOrgRepo, CanCreateOrgRepo: t.CanCreateOrgRepo,
Permission: teams[i].AccessMode.String(), Permission: t.AccessMode.String(),
Units: teams[i].GetUnitNames(), Units: t.GetUnitNames(),
UnitsMap: teams[i].GetUnitsMap(), UnitsMap: t.GetUnitsMap(),
} }
if loadOrgs { if loadOrgs {
apiOrg, ok := cache[teams[i].OrgID] apiOrg, ok := cache[t.OrgID]
if !ok { if !ok {
org, err := organization.GetOrgByID(ctx, teams[i].OrgID) org, err := organization.GetOrgByID(ctx, t.OrgID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
apiOrg = ToOrganization(ctx, org) apiOrg = ToOrganization(ctx, org)
cache[teams[i].OrgID] = apiOrg cache[t.OrgID] = apiOrg
} }
apiTeams[i].Organization = apiOrg apiTeam.Organization = apiOrg
} }
apiTeams = append(apiTeams, apiTeam)
} }
return apiTeams, nil return apiTeams, nil
} }
+8 -6
View File
@@ -21,15 +21,9 @@ func ToPullReview(ctx context.Context, r *issues_model.Review, doer *user_model.
r.Reviewer = user_model.NewGhostUser() r.Reviewer = user_model.NewGhostUser()
} }
apiTeam, err := ToTeam(ctx, r.ReviewerTeam)
if err != nil {
return nil, err
}
result := &api.PullReview{ result := &api.PullReview{
ID: r.ID, ID: r.ID,
Reviewer: ToUser(ctx, r.Reviewer, doer), Reviewer: ToUser(ctx, r.Reviewer, doer),
ReviewerTeam: apiTeam,
State: api.ReviewStateUnknown, State: api.ReviewStateUnknown,
Body: r.Content, Body: r.Content,
CommitID: r.CommitID, CommitID: r.CommitID,
@@ -43,6 +37,14 @@ func ToPullReview(ctx context.Context, r *issues_model.Review, doer *user_model.
HTMLPullURL: r.Issue.HTMLURL(), HTMLPullURL: r.Issue.HTMLURL(),
} }
if r.ReviewerTeam != nil {
var err error
result.ReviewerTeam, err = ToTeam(ctx, r.ReviewerTeam)
if err != nil {
return nil, err
}
}
switch r.Type { switch r.Type {
case issues_model.ReviewTypeApprove: case issues_model.ReviewTypeApprove:
result.State = api.ReviewStateApproved result.State = api.ReviewStateApproved
+6 -1
View File
@@ -8,6 +8,7 @@ import (
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@@ -133,7 +134,11 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
return nil return nil
} }
numReleases, _ := repo_model.GetReleaseCountByRepoID(ctx, repo.ID, repo_model.FindReleasesOptions{IncludeDrafts: false, IncludeTags: false}) numReleases, _ := db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{
IncludeDrafts: false,
IncludeTags: false,
RepoID: repo.ID,
})
mirrorInterval := "" mirrorInterval := ""
var mirrorUpdated time.Time var mirrorUpdated time.Time
+1
View File
@@ -216,6 +216,7 @@ type ProtectBranchForm struct {
BlockOnOfficialReviewRequests bool BlockOnOfficialReviewRequests bool
BlockOnOutdatedBranch bool BlockOnOutdatedBranch bool
DismissStaleApprovals bool DismissStaleApprovals bool
IgnoreStaleApprovals bool
RequireSignedCommits bool RequireSignedCommits bool
ProtectedFilePatterns string ProtectedFilePatterns string
UnprotectedFilePatterns string UnprotectedFilePatterns string
+4 -2
View File
@@ -83,22 +83,24 @@ func TestGiteaUploadRepo(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, labels, 12) assert.Len(t, labels, 12)
releases, err := repo_model.GetReleasesByRepoID(db.DefaultContext, repo.ID, repo_model.FindReleasesOptions{ releases, err := db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
ListOptions: db.ListOptions{ ListOptions: db.ListOptions{
PageSize: 10, PageSize: 10,
Page: 0, Page: 0,
}, },
IncludeTags: true, IncludeTags: true,
RepoID: repo.ID,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, releases, 8) assert.Len(t, releases, 8)
releases, err = repo_model.GetReleasesByRepoID(db.DefaultContext, repo.ID, repo_model.FindReleasesOptions{ releases, err = db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
ListOptions: db.ListOptions{ ListOptions: db.ListOptions{
PageSize: 10, PageSize: 10,
Page: 0, Page: 0,
}, },
IncludeTags: false, IncludeTags: false,
RepoID: repo.ID,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, releases, 1) assert.Len(t, releases, 1)
+20 -17
View File
@@ -125,8 +125,8 @@ type packageData struct {
type packageCache = map[*packages_model.PackageFile]*packageData type packageCache = map[*packages_model.PackageFile]*packageData
// BuildRepositoryFiles builds metadata files for the repository // BuildSpecificRepositoryFiles builds metadata files for the repository
func BuildRepositoryFiles(ctx context.Context, ownerID int64) error { func BuildRepositoryFiles(ctx context.Context, ownerID int64, compositeKey string) error {
pv, err := GetOrCreateRepositoryVersion(ctx, ownerID) pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
if err != nil { if err != nil {
return err return err
@@ -136,6 +136,7 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64) error {
OwnerID: ownerID, OwnerID: ownerID,
PackageType: packages_model.TypeRpm, PackageType: packages_model.TypeRpm,
Query: "%.rpm", Query: "%.rpm",
CompositeKey: compositeKey,
}) })
if err != nil { if err != nil {
return err return err
@@ -194,15 +195,15 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64) error {
cache[pf] = pd cache[pf] = pd
} }
primary, err := buildPrimary(ctx, pv, pfs, cache) primary, err := buildPrimary(ctx, pv, pfs, cache, compositeKey)
if err != nil { if err != nil {
return err return err
} }
filelists, err := buildFilelists(ctx, pv, pfs, cache) filelists, err := buildFilelists(ctx, pv, pfs, cache, compositeKey)
if err != nil { if err != nil {
return err return err
} }
other, err := buildOther(ctx, pv, pfs, cache) other, err := buildOther(ctx, pv, pfs, cache, compositeKey)
if err != nil { if err != nil {
return err return err
} }
@@ -216,11 +217,12 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64) error {
filelists, filelists,
other, other,
}, },
compositeKey,
) )
} }
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#repomd-xml // https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#repomd-xml
func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID int64, data []*repoData) error { func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID int64, data []*repoData, compositeKey string) error {
type Repomd struct { type Repomd struct {
XMLName xml.Name `xml:"repomd"` XMLName xml.Name `xml:"repomd"`
Xmlns string `xml:"xmlns,attr"` Xmlns string `xml:"xmlns,attr"`
@@ -276,6 +278,7 @@ func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID
&packages_service.PackageFileCreationInfo{ &packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{ PackageFileInfo: packages_service.PackageFileInfo{
Filename: file.Name, Filename: file.Name,
CompositeKey: compositeKey,
}, },
Creator: user_model.NewGhostUser(), Creator: user_model.NewGhostUser(),
Data: file.Data, Data: file.Data,
@@ -292,7 +295,7 @@ func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID
} }
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#primary-xml // https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#primary-xml
func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache) (*repoData, error) { func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) {
type Version struct { type Version struct {
Epoch string `xml:"epoch,attr"` Epoch string `xml:"epoch,attr"`
Version string `xml:"ver,attr"` Version string `xml:"ver,attr"`
@@ -372,7 +375,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
files = append(files, f) files = append(files, f)
} }
} }
packageVersion := fmt.Sprintf("%s-%s", pd.FileMetadata.Version, pd.FileMetadata.Release)
packages = append(packages, &Package{ packages = append(packages, &Package{
Type: "rpm", Type: "rpm",
Name: pd.Package.Name, Name: pd.Package.Name,
@@ -401,7 +404,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
Archive: pd.FileMetadata.ArchiveSize, Archive: pd.FileMetadata.ArchiveSize,
}, },
Location: Location{ Location: Location{
Href: fmt.Sprintf("package/%s/%s/%s", url.PathEscape(pd.Package.Name), url.PathEscape(pd.Version.Version), url.PathEscape(pd.FileMetadata.Architecture)), Href: fmt.Sprintf("package/%s/%s/%s/%s", url.PathEscape(pd.Package.Name), url.PathEscape(packageVersion), url.PathEscape(pd.FileMetadata.Architecture), url.PathEscape(fmt.Sprintf("%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture))),
}, },
Format: Format{ Format: Format{
License: pd.VersionMetadata.License, License: pd.VersionMetadata.License,
@@ -431,11 +434,11 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
XmlnsRpm: "http://linux.duke.edu/metadata/rpm", XmlnsRpm: "http://linux.duke.edu/metadata/rpm",
PackageCount: len(pfs), PackageCount: len(pfs),
Packages: packages, Packages: packages,
}) }, compositeKey)
} }
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#filelists-xml // https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#filelists-xml
func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache) (*repoData, error) { //nolint:dupl func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) { //nolint:dupl
type Version struct { type Version struct {
Epoch string `xml:"epoch,attr"` Epoch string `xml:"epoch,attr"`
Version string `xml:"ver,attr"` Version string `xml:"ver,attr"`
@@ -478,11 +481,12 @@ func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs
Xmlns: "http://linux.duke.edu/metadata/other", Xmlns: "http://linux.duke.edu/metadata/other",
PackageCount: len(pfs), PackageCount: len(pfs),
Packages: packages, Packages: packages,
}) },
compositeKey)
} }
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#other-xml // https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#other-xml
func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache) (*repoData, error) { //nolint:dupl func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) { //nolint:dupl
type Version struct { type Version struct {
Epoch string `xml:"epoch,attr"` Epoch string `xml:"epoch,attr"`
Version string `xml:"ver,attr"` Version string `xml:"ver,attr"`
@@ -525,7 +529,7 @@ func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*p
Xmlns: "http://linux.duke.edu/metadata/other", Xmlns: "http://linux.duke.edu/metadata/other",
PackageCount: len(pfs), PackageCount: len(pfs),
Packages: packages, Packages: packages,
}) }, compositeKey)
} }
// writtenCounter counts all written bytes // writtenCounter counts all written bytes
@@ -545,10 +549,8 @@ func (wc *writtenCounter) Written() int64 {
return wc.written return wc.written
} }
func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filetype string, obj any) (*repoData, error) { func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filetype string, obj any, compositeKey string) (*repoData, error) {
content, _ := packages_module.NewHashedBuffer() content, _ := packages_module.NewHashedBuffer()
defer content.Close()
gzw := gzip.NewWriter(content) gzw := gzip.NewWriter(content)
wc := &writtenCounter{} wc := &writtenCounter{}
h := sha256.New() h := sha256.New()
@@ -572,6 +574,7 @@ func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion,
&packages_service.PackageFileCreationInfo{ &packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{ PackageFileInfo: packages_service.PackageFileInfo{
Filename: filename, Filename: filename,
CompositeKey: compositeKey,
}, },
Creator: user_model.NewGhostUser(), Creator: user_model.NewGhostUser(),
Data: content, Data: content,
+1 -1
View File
@@ -324,7 +324,7 @@ func DeleteOldRepositoryArchives(ctx context.Context, olderThan time.Duration) e
log.Trace("Doing: ArchiveCleanup") log.Trace("Doing: ArchiveCleanup")
for { for {
archivers, err := repo_model.FindRepoArchives(ctx, repo_model.FindRepoArchiversOption{ archivers, err := db.Find[repo_model.RepoArchiver](ctx, repo_model.FindRepoArchiversOption{
ListOptions: db.ListOptions{ ListOptions: db.ListOptions{
PageSize: 100, PageSize: 100,
Page: 1, Page: 1,
+65 -1
View File
@@ -10,6 +10,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
@@ -22,6 +23,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
notify_service "code.gitea.io/gitea/services/notify" notify_service "code.gitea.io/gitea/services/notify"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
@@ -308,13 +310,28 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
return "from_not_exist", nil return "from_not_exist", nil
} }
if err := git_model.RenameBranch(ctx, repo, from, to, func(isDefault bool) error { if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error {
err2 := gitRepo.RenameBranch(from, to) err2 := gitRepo.RenameBranch(from, to)
if err2 != nil { if err2 != nil {
return err2 return err2
} }
if isDefault { if isDefault {
// if default branch changed, we need to delete all schedules and cron jobs
if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
log.Error("DeleteCronTaskByRepo: %v", err)
}
// cancel running cron jobs of this repository and delete old schedules
if err := actions_model.CancelRunningJobs(
ctx,
repo.ID,
from,
"",
webhook_module.HookEventSchedule,
); err != nil {
log.Error("CancelRunningJobs: %v", err)
}
err2 = gitRepo.SetDefaultBranch(to) err2 = gitRepo.SetDefaultBranch(to)
if err2 != nil { if err2 != nil {
return err2 return err2
@@ -450,3 +467,50 @@ func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error {
} }
return nil return nil
} }
func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, newBranchName string) error {
if repo.DefaultBranch == newBranchName {
return nil
}
if !gitRepo.IsBranchExist(newBranchName) {
return git_model.ErrBranchNotExist{
BranchName: newBranchName,
}
}
oldDefaultBranchName := repo.DefaultBranch
repo.DefaultBranch = newBranchName
if err := db.WithTx(ctx, func(ctx context.Context) error {
if err := repo_model.UpdateDefaultBranch(ctx, repo); err != nil {
return err
}
if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
log.Error("DeleteCronTaskByRepo: %v", err)
}
// cancel running cron jobs of this repository and delete old schedules
if err := actions_model.CancelRunningJobs(
ctx,
repo.ID,
oldDefaultBranchName,
"",
webhook_module.HookEventSchedule,
); err != nil {
log.Error("CancelRunningJobs: %v", err)
}
if err := gitRepo.SetDefaultBranch(newBranchName); err != nil {
if !git.IsErrUnsupportedVersion(err) {
return err
}
}
return nil
}); err != nil {
return err
}
notify_service.ChangeDefaultBranch(ctx, repo)
return nil
}
+5 -2
View File
@@ -327,9 +327,12 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
lowerTags = append(lowerTags, strings.ToLower(tag)) lowerTags = append(lowerTags, strings.ToLower(tag))
} }
releases, err := repo_model.GetReleasesByRepoIDAndNames(ctx, repo.ID, lowerTags) releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
RepoID: repo.ID,
TagNames: lowerTags,
})
if err != nil { if err != nil {
return fmt.Errorf("GetReleasesByRepoIDAndNames: %w", err) return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
} }
relMap := make(map[string]*repo_model.Release) relMap := make(map[string]*repo_model.Release)
for _, rel := range releases { for _, rel := range releases {
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"context"
"slices"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/log"
)
// UpdateRepositoryUnits updates a repository's units
func UpdateRepositoryUnits(ctx context.Context, repo *repo_model.Repository, units []repo_model.RepoUnit, deleteUnitTypes []unit.Type) (err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
// Delete existing settings of units before adding again
for _, u := range units {
deleteUnitTypes = append(deleteUnitTypes, u.Type)
}
if slices.Contains(deleteUnitTypes, unit.TypeActions) {
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
log.Error("CleanRepoScheduleTasks: %v", err)
}
}
if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(repo_model.RepoUnit)); err != nil {
return err
}
if len(units) > 0 {
if err = db.Insert(ctx, units); err != nil {
return err
}
}
return committer.Commit()
}
+3 -1
View File
@@ -159,7 +159,9 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
// ***** END: PublicKey ***** // ***** END: PublicKey *****
// ***** START: GPGPublicKey ***** // ***** START: GPGPublicKey *****
keys, err := asymkey_model.ListGPGKeys(ctx, u.ID, db.ListOptions{}) keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
OwnerID: u.ID,
})
if err != nil { if err != nil {
return fmt.Errorf("ListGPGKeys: %w", err) return fmt.Errorf("ListGPGKeys: %w", err)
} }
+6 -1
View File
@@ -129,6 +129,10 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
return fmt.Errorf("%s is an organization not a user", u.Name) return fmt.Errorf("%s is an organization not a user", u.Name)
} }
if user_model.IsLastAdminUser(ctx, u) {
return models.ErrDeleteLastAdminUser{UID: u.ID}
}
if purge { if purge {
// Disable the user first // Disable the user first
// NOTE: This is deliberately not within a transaction as it must disable the user immediately to prevent any further action by the user to be purged. // NOTE: This is deliberately not within a transaction as it must disable the user immediately to prevent any further action by the user to be purged.
@@ -295,7 +299,8 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error {
} }
if err := DeleteUser(ctx, u, false); err != nil { if err := DeleteUser(ctx, u, false); err != nil {
// Ignore users that were set inactive by admin. // Ignore users that were set inactive by admin.
if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) { if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) ||
models.IsErrUserOwnPackages(err) || models.IsErrDeleteLastAdminUser(err) {
continue continue
} }
return err return err
+2 -1
View File
@@ -19,6 +19,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/sync"
asymkey_service "code.gitea.io/gitea/services/asymkey" asymkey_service "code.gitea.io/gitea/services/asymkey"
repo_service "code.gitea.io/gitea/services/repository"
) )
// TODO: use clustered lock (unique queue? or *abuse* cache) // TODO: use clustered lock (unique queue? or *abuse* cache)
@@ -350,7 +351,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
// DeleteWiki removes the actual and local copy of repository wiki. // DeleteWiki removes the actual and local copy of repository wiki.
func DeleteWiki(ctx context.Context, repo *repo_model.Repository) error { func DeleteWiki(ctx context.Context, repo *repo_model.Repository) error {
if err := repo_model.UpdateRepositoryUnits(ctx, repo, nil, []unit.Type{unit.TypeWiki}); err != nil { if err := repo_service.UpdateRepositoryUnits(ctx, repo, nil, []unit.Type{unit.TypeWiki}); err != nil {
return err return err
} }
+1 -1
View File
@@ -20,7 +20,7 @@
{{if .DatabaseCheckInconsistentCollationColumns}} {{if .DatabaseCheckInconsistentCollationColumns}}
<div class="ui red message"> <div class="ui red message">
{{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}} {{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}}
<ul class="gt-w-100"> <ul class="gt-w-full">
{{range .DatabaseCheckInconsistentCollationColumns}} {{range .DatabaseCheckInconsistentCollationColumns}}
<li>{{.}}</li> <li>{{.}}</li>
{{end}} {{end}}
+4 -4
View File
@@ -5,7 +5,7 @@
<div id="test-modal-form-1" class="ui mini modal"> <div id="test-modal-form-1" class="ui mini modal">
<div class="header">Form dialog (layout 1)</div> <div class="header">Form dialog (layout 1)</div>
<form class="content" method="post"> <form class="content" method="post">
<div class="ui input gt-w-100"><input name="user_input"></div> <div class="ui input gt-w-full"><input name="user_input"></div>
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}} {{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</form> </form>
</div> </div>
@@ -14,7 +14,7 @@
<div class="header">Form dialog (layout 2)</div> <div class="header">Form dialog (layout 2)</div>
<form method="post"> <form method="post">
<div class="content"> <div class="content">
<div class="ui input gt-w-100"><input name="user_input"></div> <div class="ui input gt-w-full"><input name="user_input"></div>
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}} {{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</div> </div>
</form> </form>
@@ -24,7 +24,7 @@
<div class="header">Form dialog (layout 3)</div> <div class="header">Form dialog (layout 3)</div>
<form method="post"> <form method="post">
<div class="content"> <div class="content">
<div class="ui input gt-w-100"><input name="user_input"></div> <div class="ui input gt-w-full"><input name="user_input"></div>
</div> </div>
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}} {{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</form> </form>
@@ -33,7 +33,7 @@
<div id="test-modal-form-4" class="ui mini modal"> <div id="test-modal-form-4" class="ui mini modal">
<div class="header">Form dialog (layout 4)</div> <div class="header">Form dialog (layout 4)</div>
<div class="content"> <div class="content">
<div class="ui input gt-w-100"><input name="user_input"></div> <div class="ui input gt-w-full"><input name="user_input"></div>
</div> </div>
<form method="post"> <form method="post">
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}} {{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
+1 -1
View File
@@ -26,7 +26,7 @@
{{else if $.IsOrganizationOwner}} {{else if $.IsOrganizationOwner}}
<form method="post" action="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/join"> <form method="post" action="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/join">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<button type="submit" class="ui primary small button" name="uid" value="{{$.SignedUser.ID}}">{{ctx.Locale.Tr "org.teams.join"}}</button> <button type="submit" class="ui primary tiny button" name="uid" value="{{$.SignedUser.ID}}">{{ctx.Locale.Tr "org.teams.join"}}</button>
</form> </form>
{{end}} {{end}}
</div> </div>
+10 -6
View File
@@ -4,19 +4,23 @@
<div class="ui form"> <div class="ui form">
<div class="field"> <div class="field">
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.rpm.registry"}}</label> <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.rpm.registry"}}</label>
<div class="markup"><pre class="code-block"><code># {{ctx.Locale.Tr "packages.rpm.distro.redhat"}} <div class="markup"><pre class="code-block"><code># {{ctx.Locale.Tr "packages.rpm.distros.redhat"}}
dnf config-manager --add-repo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm.repo"></gitea-origin-url> {{$group_name:= StringUtils.ReplaceAllStringRegex .PackageDescriptor.Version.Version "(/[^/]+|[^/]*)\\z" "" -}}
{{- if $group_name -}}
{{- $group_name = (print "/" $group_name) -}}
{{- end -}}
dnf config-manager --add-repo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group_name}}.repo"></gitea-origin-url>
# {{ctx.Locale.Tr "packages.rpm.distro.suse"}} # {{ctx.Locale.Tr "packages.rpm.distros.suse"}}
zypper addrepo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm.repo"></gitea-origin-url></code></pre></div> zypper addrepo <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/rpm{{$group_name}}.repo"></gitea-origin-url></code></pre></div>
</div> </div>
<div class="field"> <div class="field">
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.rpm.install"}}</label> <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.rpm.install"}}</label>
<div class="markup"> <div class="markup">
<pre class="code-block"><code># {{ctx.Locale.Tr "packages.rpm.distro.redhat"}} <pre class="code-block"><code># {{ctx.Locale.Tr "packages.rpm.distros.redhat"}}
dnf install {{$.PackageDescriptor.Package.Name}} dnf install {{$.PackageDescriptor.Package.Name}}
# {{ctx.Locale.Tr "packages.rpm.distro.suse"}} # {{ctx.Locale.Tr "packages.rpm.distros.suse"}}
zypper install {{$.PackageDescriptor.Package.Name}}</code></pre> zypper install {{$.PackageDescriptor.Package.Name}}</code></pre>
</div> </div>
</div> </div>
+1 -1
View File
@@ -203,7 +203,7 @@
{{if $showFileViewToggle}} {{if $showFileViewToggle}}
{{/* for image or CSV, it can have a horizontal scroll bar, there won't be review comment context menu (position absolute) which would be clipped by "overflow" */}} {{/* for image or CSV, it can have a horizontal scroll bar, there won't be review comment context menu (position absolute) which would be clipped by "overflow" */}}
<div id="diff-rendered-{{$file.NameHash}}" class="file-body file-code {{if $.IsSplitStyle}}code-diff-split{{else}}code-diff-unified{{end}} gt-overflow-x-scroll"> <div id="diff-rendered-{{$file.NameHash}}" class="file-body file-code {{if $.IsSplitStyle}}code-diff-split{{else}}code-diff-unified{{end}} gt-overflow-x-scroll">
<table class="chroma gt-w-100"> <table class="chroma gt-w-full">
{{if $isImage}} {{if $isImage}}
{{template "repo/diff/image_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead "sniffedTypeBase" $sniffedTypeBase "sniffedTypeHead" $sniffedTypeHead}} {{template "repo/diff/image_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead "sniffedTypeBase" $sniffedTypeBase "sniffedTypeHead" $sniffedTypeHead}}
{{else}} {{else}}
+34 -30
View File
@@ -2,36 +2,33 @@
{{with .Repository}} {{with .Repository}}
<div class="ui container"> <div class="ui container">
<div class="repo-header"> <div class="repo-header">
<div class="repo-title-wrap gt-df gt-fc"> <div class="flex-item gt-ac">
<div class="repo-title" role="heading" aria-level="1"> <div class="flex-item-leading">{{template "repo/icon" .}}</div>
<div class="gt-mr-3"> <div class="flex-item-main">
{{template "repo/icon" .}} <div class="flex-item-title">
<a class="text light thin" href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a>/
<a class="text primary" href="{{$.RepoLink}}">{{.Name}}</a></div>
</div> </div>
<a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> <div class="flex-item-trailing">
<div class="gt-mx-2">/</div>
<a href="{{$.RepoLink}}">{{.Name}}</a>
<div class="labels gt-df gt-ac gt-fw">
{{if .IsArchived}} {{if .IsArchived}}
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.archived"}}</span> <span class="ui basic label">{{ctx.Locale.Tr "repo.desc.archived"}}</span>
<div class="repo-icon" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.archived"}}">{{svg "octicon-archive" 18}}</div>
{{end}} {{end}}
{{if .IsPrivate}} {{if .IsPrivate}}
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.private"}}</span> <span class="ui basic label">{{ctx.Locale.Tr "repo.desc.private"}}</span>
<div class="repo-icon" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.private"}}">{{svg "octicon-lock" 18}}</div>
{{else}} {{else}}
{{if .Owner.Visibility.IsPrivate}} {{if .Owner.Visibility.IsPrivate}}
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.internal"}}</span> <span class="ui basic label">{{ctx.Locale.Tr "repo.desc.internal"}}</span>
<div class="repo-icon" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.internal"}}">{{svg "octicon-shield-lock" 18}}</div>
{{end}} {{end}}
{{end}} {{end}}
{{if .IsTemplate}} {{if .IsTemplate}}
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.template"}}</span> <span class="ui basic label">{{ctx.Locale.Tr "repo.desc.template"}}</span>
<div class="repo-icon" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.template"}}">{{svg "octicon-repo-template" 18}}</div>
{{end}} {{end}}
</div> </div>
</div> </div>
{{if $.PullMirror}}
<div class="fork-flag">{{ctx.Locale.Tr "repo.mirror_from"}} <a target="_blank" rel="noopener noreferrer" href="{{$.PullMirror.RemoteAddress}}">{{$.PullMirror.RemoteAddress}}</a></div>
{{end}}
{{if .IsFork}}<div class="fork-flag">{{ctx.Locale.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{.BaseRepo.FullName}}</a></div>{{end}}
{{if .IsGenerated}}<div class="fork-flag">{{ctx.Locale.Tr "repo.generated_from"}} <a href="{{(.TemplateRepo ctx).Link}}">{{(.TemplateRepo ctx).FullName}}</a></div>{{end}}
</div>
{{if not (or .IsBeingCreated .IsBroken)}} {{if not (or .IsBeingCreated .IsBroken)}}
<div class="repo-buttons"> <div class="repo-buttons">
{{if $.RepoTransfer}} {{if $.RepoTransfer}}
@@ -62,7 +59,11 @@
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.watch_guest_user"}}"{{end}}> <div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.watch_guest_user"}}"{{end}}>
<button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}> <button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}>
{{if $.IsWatchingRepo}}{{svg "octicon-eye-closed" 16}}{{ctx.Locale.Tr "repo.unwatch"}}{{else}}{{svg "octicon-eye"}}{{ctx.Locale.Tr "repo.watch"}}{{end}} {{if $.IsWatchingRepo}}
{{svg "octicon-eye-closed" 16}}<span class="text">{{ctx.Locale.Tr "repo.unwatch"}}</span>
{{else}}
{{svg "octicon-eye"}}<span class="text">{{ctx.Locale.Tr "repo.watch"}}</span>
{{end}}
</button> </button>
<a class="ui basic label" href="{{.Link}}/watchers"> <a class="ui basic label" href="{{.Link}}/watchers">
{{CountFmt .NumWatches}} {{CountFmt .NumWatches}}
@@ -74,7 +75,11 @@
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.star_guest_user"}}"{{end}}> <div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.star_guest_user"}}"{{end}}>
<button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}> <button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}>
{{if $.IsStaringRepo}}{{svg "octicon-star-fill"}}{{ctx.Locale.Tr "repo.unstar"}}{{else}}{{svg "octicon-star"}}{{ctx.Locale.Tr "repo.star"}}{{end}} {{if $.IsStaringRepo}}
{{svg "octicon-star-fill"}}<span class="text">{{ctx.Locale.Tr "repo.unstar"}}</span>
{{else}}
{{svg "octicon-star"}}<span class="text">{{ctx.Locale.Tr "repo.star"}}</span>
{{end}}
</button> </button>
<a class="ui basic label" href="{{.Link}}/stars"> <a class="ui basic label" href="{{.Link}}/stars">
{{CountFmt .NumStars}} {{CountFmt .NumStars}}
@@ -107,7 +112,7 @@
data-modal="#fork-repo-modal" data-modal="#fork-repo-modal"
{{end}} {{end}}
> >
{{svg "octicon-repo-forked"}}{{ctx.Locale.Tr "repo.fork"}} {{svg "octicon-repo-forked"}}<span class="text">{{ctx.Locale.Tr "repo.fork"}}</span>
</a> </a>
<div class="ui small modal" id="fork-repo-modal"> <div class="ui small modal" id="fork-repo-modal">
<div class="header"> <div class="header">
@@ -117,17 +122,13 @@
<div class="ui list"> <div class="ui list">
{{range $.UserAndOrgForks}} {{range $.UserAndOrgForks}}
<div class="ui item gt-py-3"> <div class="ui item gt-py-3">
<a href="{{.Link}}"> <a href="{{.Link}}">{{svg "octicon-repo-forked" 16 "gt-mr-3"}}{{.FullName}}</a>
{{svg "octicon-repo-forked" 16 "gt-mr-3"}}{{.FullName}}
</a>
</div> </div>
{{end}} {{end}}
</div> </div>
{{if $.CanSignedUserFork}} {{if $.CanSignedUserFork}}
<div class="divider"></div> <div class="divider"></div>
<a href="{{AppSubUrl}}/repo/fork/{{.ID}}"> <a href="{{AppSubUrl}}/repo/fork/{{.ID}}">{{ctx.Locale.Tr "repo.fork_to_different_account"}}</a>
{{ctx.Locale.Tr "repo.fork_to_different_account"}}
</a>
{{end}} {{end}}
</div> </div>
</div> </div>
@@ -138,12 +139,15 @@
{{end}} {{end}}
</div> </div>
{{end}} {{end}}
</div><!-- end grid --> </div>
</div><!-- end container --> {{if $.PullMirror}}<div class="fork-flag">{{ctx.Locale.Tr "repo.mirror_from"}} <a target="_blank" rel="noopener noreferrer" href="{{$.PullMirror.RemoteAddress}}">{{$.PullMirror.RemoteAddress}}</a></div>{{end}}
{{if .IsFork}}<div class="fork-flag">{{ctx.Locale.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{.BaseRepo.FullName}}</a></div>{{end}}
{{if .IsGenerated}}<div class="fork-flag">{{ctx.Locale.Tr "repo.generated_from"}} <a href="{{(.TemplateRepo ctx).Link}}">{{(.TemplateRepo ctx).FullName}}</a></div>{{end}}
</div>
{{end}} {{end}}
<div class="ui tabs container"> <div class="ui container secondary pointing tabular top attached borderless menu new-menu navbar">
{{if not (or .Repository.IsBeingCreated .Repository.IsBroken)}} {{if not (or .Repository.IsBeingCreated .Repository.IsBroken)}}
<div class="ui tabular menu navbar gt-overflow-x-auto gt-overflow-y-hidden"> <div class="new-menu-inner">
{{if .Permission.CanRead $.UnitTypeCode}} {{if .Permission.CanRead $.UnitTypeCode}}
<a class="{{if .PageIsViewCode}}active {{end}}item" href="{{.RepoLink}}{{if and (ne .BranchName .Repository.DefaultBranch) (not $.PageIsWiki)}}/src/{{.BranchNameSubURL}}{{end}}"> <a class="{{if .PageIsViewCode}}active {{end}}item" href="{{.RepoLink}}{{if and (ne .BranchName .Repository.DefaultBranch) (not $.PageIsWiki)}}/src/{{.BranchNameSubURL}}{{end}}">
{{svg "octicon-code"}} {{ctx.Locale.Tr "repo.code"}} {{svg "octicon-code"}} {{ctx.Locale.Tr "repo.code"}}
@@ -228,14 +232,14 @@
{{template "custom/extra_tabs" .}} {{template "custom/extra_tabs" .}}
{{if .Permission.IsAdmin}} {{if .Permission.IsAdmin}}
<a class="{{if .PageIsRepoSettings}}active {{end}}right item" href="{{.RepoLink}}/settings"> <a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings">
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}} {{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
</a> </a>
{{end}} {{end}}
</div> </div>
{{else if .Permission.IsAdmin}} {{else if .Permission.IsAdmin}}
<div class="ui tabular menu navbar gt-overflow-x-auto gt-overflow-y-hidden"> <div class="new-menu-inner">
<a class="{{if .PageIsRepoSettings}}active {{end}}right item" href="{{.RepoLink}}/settings"> <a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings">
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}} {{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
</a> </a>
</div> </div>
+1 -1
View File
@@ -6,7 +6,7 @@
{{end}} {{end}}
</div> </div>
{{end}} {{end}}
<div class="content gt-p-0 gt-w-100"> <div class="content gt-p-0 gt-w-full">
<div class="gt-df gt-items-start"> <div class="gt-df gt-items-start">
<div class="issue-card-icon"> <div class="issue-card-icon">
{{template "shared/issueicon" .}} {{template "shared/issueicon" .}}
@@ -2,7 +2,9 @@
{{range $key, $value := .Reactions}} {{range $key, $value := .Reactions}}
{{$hasReacted := $value.HasUser $.ctxData.SignedUserID}} {{$hasReacted := $value.HasUser $.ctxData.SignedUserID}}
<a role="button" class="ui label basic{{if $hasReacted}} primary{{end}}{{if not $.ctxData.IsSigned}} disabled{{end}} comment-reaction-button" <a role="button" class="ui label basic{{if $hasReacted}} primary{{end}}{{if not $.ctxData.IsSigned}} disabled{{end}} comment-reaction-button"
data-tooltip-content="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}" data-tooltip-content
title="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}"
aria-label="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}"
data-tooltip-placement="bottom-start" data-tooltip-placement="bottom-start"
data-reaction-content="{{$key}}" data-has-reacted="{{$hasReacted}}"> data-reaction-content="{{$key}}" data-has-reacted="{{$hasReacted}}">
<span class="reaction">{{ReactionToEmoji $key}}</span> <span class="reaction">{{ReactionToEmoji $key}}</span>
+3 -3
View File
@@ -5,10 +5,10 @@
<a href="{{.LFSFilesLink}}">{{ctx.Locale.Tr "repo.settings.lfs"}}</a> / <span class="truncate sha">{{.LFSFile.Oid}}</span> <a href="{{.LFSFilesLink}}">{{ctx.Locale.Tr "repo.settings.lfs"}}</a> / <span class="truncate sha">{{.LFSFile.Oid}}</span>
<div class="ui right"> <div class="ui right">
{{if .EscapeStatus.Escaped}} {{if .EscapeStatus.Escaped}}
<a class="ui mini basic button unescape-button gt-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a> <a class="ui tiny basic button unescape-button gt-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
<a class="ui mini basic button escape-button">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a> <a class="ui tiny basic button escape-button">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a>
{{end}} {{end}}
<a class="ui primary button" href="{{.LFSFilesLink}}/find?oid={{.LFSFile.Oid}}&size={{.LFSFile.Size}}">{{ctx.Locale.Tr "repo.settings.lfs_findcommits"}}</a> <a class="ui primary tiny button" href="{{.LFSFilesLink}}/find?oid={{.LFSFile.Oid}}&size={{.LFSFile.Size}}">{{ctx.Locale.Tr "repo.settings.lfs_findcommits"}}</a>
</div> </div>
</h4> </h4>
<div class="ui attached table unstackable segment"> <div class="ui attached table unstackable segment">
+1 -1
View File
@@ -11,7 +11,7 @@
<input type="hidden" name="oid" value="{{.Oid}} {{.Size}}"> <input type="hidden" name="oid" value="{{.Oid}} {{.Size}}">
{{end}} {{end}}
{{end}} {{end}}
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.lfs_pointers.associateAccessible" $.NumAssociatable}}</button> <button class="ui primary tiny button">{{ctx.Locale.Tr "repo.settings.lfs_pointers.associateAccessible" $.NumAssociatable}}</button>
</form> </form>
</div> </div>
{{end}} {{end}}
@@ -207,11 +207,18 @@
</div> </div>
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<input name="dismiss_stale_approvals" type="checkbox" {{if .Rule.DismissStaleApprovals}}checked{{end}}> <input id="dismiss_stale_approvals" name="dismiss_stale_approvals" type="checkbox" {{if .Rule.DismissStaleApprovals}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.dismiss_stale_approvals"}}</label> <label>{{ctx.Locale.Tr "repo.settings.dismiss_stale_approvals"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.dismiss_stale_approvals_desc"}}</p> <p class="help">{{ctx.Locale.Tr "repo.settings.dismiss_stale_approvals_desc"}}</p>
</div> </div>
</div> </div>
<div id="ignore_stale_approvals_box" class="field {{if .Rule.DismissStaleApprovals}}disabled{{end}}">
<div class="ui checkbox">
<input id="ignore_stale_approvals" name="ignore_stale_approvals" type="checkbox" {{if .Rule.IgnoreStaleApprovals}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.ignore_stale_approvals"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.ignore_stale_approvals_desc"}}</p>
</div>
</div>
<div class="grouped fields"> <div class="grouped fields">
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
+15
View File
@@ -677,6 +677,9 @@
"200": { "200": {
"$ref": "#/responses/User" "$ref": "#/responses/User"
}, },
"400": {
"$ref": "#/responses/error"
},
"403": { "403": {
"$ref": "#/responses/forbidden" "$ref": "#/responses/forbidden"
}, },
@@ -17043,6 +17046,10 @@
}, },
"x-go-name": "ForcePushWhitelistUsernames" "x-go-name": "ForcePushWhitelistUsernames"
}, },
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"
},
"merge_whitelist_teams": { "merge_whitelist_teams": {
"type": "array", "type": "array",
"items": { "items": {
@@ -17710,6 +17717,10 @@
}, },
"x-go-name": "ForcePushWhitelistUsernames" "x-go-name": "ForcePushWhitelistUsernames"
}, },
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"
},
"merge_whitelist_teams": { "merge_whitelist_teams": {
"type": "array", "type": "array",
"items": { "items": {
@@ -18867,6 +18878,10 @@
}, },
"x-go-name": "ForcePushWhitelistUsernames" "x-go-name": "ForcePushWhitelistUsernames"
}, },
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"
},
"merge_whitelist_teams": { "merge_whitelist_teams": {
"type": "array", "type": "array",
"items": { "items": {
+2 -2
View File
@@ -47,7 +47,7 @@ func TestPullRequestTargetEvent(t *testing.T) {
assert.NotEmpty(t, baseRepo) assert.NotEmpty(t, baseRepo)
// enable actions // enable actions
err = repo_model.UpdateRepositoryUnits(db.DefaultContext, baseRepo, []repo_model.RepoUnit{{ err = repo_service.UpdateRepositoryUnits(db.DefaultContext, baseRepo, []repo_model.RepoUnit{{
RepoID: baseRepo.ID, RepoID: baseRepo.ID,
Type: unit_model.TypeActions, Type: unit_model.TypeActions,
}}, nil) }}, nil)
@@ -216,7 +216,7 @@ func TestSkipCI(t *testing.T) {
assert.NotEmpty(t, repo) assert.NotEmpty(t, repo)
// enable actions // enable actions
err = repo_model.UpdateRepositoryUnits(db.DefaultContext, repo, []repo_model.RepoUnit{{ err = repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, []repo_model.RepoUnit{{
RepoID: repo.ID, RepoID: repo.ID,
Type: unit_model.TypeActions, Type: unit_model.TypeActions,
}}, nil) }}, nil)
+14 -15
View File
@@ -76,12 +76,12 @@ Mu0UFYgZ/bYnuvn/vz4wtCz8qMwsHUvP0PX3tbYFUctAPdrY6tiiDtcCddDECahx7SuVNP5dpmb5
t.Run("RepositoryConfig", func(t *testing.T) { t.Run("RepositoryConfig", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", rootURL+".repo") req := NewRequest(t, "GET", rootURL+"/el9/stable.repo")
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
expected := fmt.Sprintf(`[gitea-%s] expected := fmt.Sprintf(`[gitea-%s-el9-stable]
name=%s - %s name=%s - %s - el9 - stable
baseurl=%sapi/packages/%s/rpm baseurl=%sapi/packages/%s/rpm/el9/stable/
enabled=1 enabled=1
gpgcheck=1 gpgcheck=1
gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppName, setting.AppURL, user.Name, setting.AppURL, user.Name) gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppName, setting.AppURL, user.Name, setting.AppURL, user.Name)
@@ -100,7 +100,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN
}) })
t.Run("Upload", func(t *testing.T) { t.Run("Upload", func(t *testing.T) {
url := rootURL + "/upload" url := rootURL + "/el9/stable/upload"
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)) req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
MakeRequest(t, req, http.StatusUnauthorized) MakeRequest(t, req, http.StatusUnauthorized)
@@ -118,7 +118,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN
assert.Nil(t, pd.SemVer) assert.Nil(t, pd.SemVer)
assert.IsType(t, &rpm_module.VersionMetadata{}, pd.Metadata) assert.IsType(t, &rpm_module.VersionMetadata{}, pd.Metadata)
assert.Equal(t, packageName, pd.Package.Name) assert.Equal(t, packageName, pd.Package.Name)
assert.Equal(t, packageVersion, pd.Version.Version) assert.Equal(t, fmt.Sprintf("el9/stable/%s", packageVersion), pd.Version.Version)
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
assert.NoError(t, err) assert.NoError(t, err)
@@ -138,7 +138,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN
t.Run("Download", func(t *testing.T) { t.Run("Download", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)) req := NewRequest(t, "GET", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture))
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, content, resp.Body.Bytes()) assert.Equal(t, content, resp.Body.Bytes())
@@ -147,7 +147,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN
t.Run("Repository", func(t *testing.T) { t.Run("Repository", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
url := rootURL + "/repodata" url := rootURL + "/el9/stable/repodata"
req := NewRequest(t, "HEAD", url+"/dummy.xml") req := NewRequest(t, "HEAD", url+"/dummy.xml")
MakeRequest(t, req, http.StatusNotFound) MakeRequest(t, req, http.StatusNotFound)
@@ -201,8 +201,8 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN
switch d.Type { switch d.Type {
case "primary": case "primary":
assert.EqualValues(t, 718, d.Size) assert.EqualValues(t, 722, d.Size)
assert.EqualValues(t, 1729, d.OpenSize) assert.EqualValues(t, 1759, d.OpenSize)
assert.Equal(t, "repodata/primary.xml.gz", d.Location.Href) assert.Equal(t, "repodata/primary.xml.gz", d.Location.Href)
case "filelists": case "filelists":
assert.EqualValues(t, 257, d.Size) assert.EqualValues(t, 257, d.Size)
@@ -311,7 +311,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN
assert.EqualValues(t, len(content), p.Size.Package) assert.EqualValues(t, len(content), p.Size.Package)
assert.EqualValues(t, 13, p.Size.Installed) assert.EqualValues(t, 13, p.Size.Installed)
assert.EqualValues(t, 272, p.Size.Archive) assert.EqualValues(t, 272, p.Size.Archive)
assert.Equal(t, fmt.Sprintf("package/%s/%s/%s", packageName, packageVersion, packageArchitecture), p.Location.Href) assert.Equal(t, fmt.Sprintf("package/%s/%s/%s/%s", packageName, packageVersion, packageArchitecture, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture)), p.Location.Href)
f := p.Format f := p.Format
assert.Equal(t, "MIT", f.License) assert.Equal(t, "MIT", f.License)
assert.Len(t, f.Provides.Entries, 2) assert.Len(t, f.Provides.Entries, 2)
@@ -401,18 +401,17 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppN
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)) req := NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture))
MakeRequest(t, req, http.StatusUnauthorized) MakeRequest(t, req, http.StatusUnauthorized)
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)). req = NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)).
AddBasicAuth(user.Name) AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusNoContent)
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm) pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, pvs) assert.Empty(t, pvs)
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)).
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)).
AddBasicAuth(user.Name) AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNotFound) MakeRequest(t, req, http.StatusNotFound)
}) })
+4 -1
View File
@@ -66,7 +66,10 @@ func TestAPIWatch(t *testing.T) {
t.Run("IsWatching", func(t *testing.T) { t.Run("IsWatching", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/subscription", repo)). req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/subscription", repo))
MakeRequest(t, req, http.StatusUnauthorized)
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/subscription", repo)).
AddTokenAuth(tokenWithRepoScope) AddTokenAuth(tokenWithRepoScope)
MakeRequest(t, req, http.StatusOK) MakeRequest(t, req, http.StatusOK)

Some files were not shown because too many files have changed in this diff Show More