diff --git a/.gitignore b/.gitignore
index 814d910315..8f2544866a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,7 +15,7 @@ _test
# MS VSCode
.vscode
-__debug_bin
+__debug_bin*
*.cgo1.go
*.cgo2.c
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 5451537d02..dc5aa691ee 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -956,6 +956,12 @@ LEVEL = Info
;GO_GET_CLONE_URL_PROTOCOL = https
;;
;; Close issues as long as a commit on any branch marks it as fixed
+;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
+;;
+;; Allow users to push local repositories to Gitea and have them automatically created for a user or an org
+;ENABLE_PUSH_CREATE_USER = false
+;ENABLE_PUSH_CREATE_ORG = false
+;;
;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects, repo.packages, repo.actions.
;DISABLED_REPO_UNITS =
;;
@@ -1474,8 +1480,9 @@ LEVEL = Info
;;
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
-;; Disabled features for users, could be "deletion", more features can be disabled in future
+;; Disabled features for users, could be "deletion","manage_gpg_keys" more features can be disabled in future
;; - deletion: a user cannot delete their own account
+;; - manage_gpg_keys: a user cannot configure gpg keys
;USER_DISABLED_FEATURES =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md
index 643932de6c..ea6e1eb1a4 100644
--- a/docs/content/administration/config-cheat-sheet.en-us.md
+++ b/docs/content/administration/config-cheat-sheet.en-us.md
@@ -518,8 +518,9 @@ And the following unique queues:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
-- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion` and more features can be added in future.
+- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion`, `manage_gpg_keys` and more features can be added in future.
- `deletion`: User cannot delete their own account.
+ - `manage_gpg_keys`: User cannot configure gpg keys
## Security (`security`)
diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md
index 5fe0a62215..5cc5734359 100644
--- a/docs/content/administration/config-cheat-sheet.zh-cn.md
+++ b/docs/content/administration/config-cheat-sheet.zh-cn.md
@@ -497,8 +497,9 @@ Gitea 创建以下非唯一队列:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**:用户电子邮件通知的默认配置(用户可配置)。选项:enabled、onmention、disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。
-- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion`, 未来可以增加更多设置。
+- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion`,`manage_gpg_keys` 未来可以增加更多设置。
- `deletion`: 用户不能通过界面或者API删除他自己。
+ - `manage_gpg_keys`: 用户不能配置 GPG 密钥
## 安全性 (`security`)
diff --git a/models/actions/runner.go b/models/actions/runner.go
index b646146ee6..67f003387b 100644
--- a/models/actions/runner.go
+++ b/models/actions/runner.go
@@ -13,6 +13,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/shared/types"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
@@ -159,7 +160,7 @@ type FindRunnerOptions struct {
OwnerID int64
Sort string
Filter string
- IsOnline util.OptionalBool
+ IsOnline optional.Option[bool]
WithAvailable bool // not only runners belong to, but also runners can be used
}
@@ -186,10 +187,12 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
cond = cond.And(builder.Like{"name", opts.Filter})
}
- if opts.IsOnline.IsTrue() {
- cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
- } else if opts.IsOnline.IsFalse() {
- cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
+ if opts.IsOnline.Has() {
+ if opts.IsOnline.Value() {
+ cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
+ } else {
+ cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
+ }
}
return cond
}
diff --git a/models/activities/action.go b/models/activities/action.go
index 15bd9a52ac..fcc97e3872 100644
--- a/models/activities/action.go
+++ b/models/activities/action.go
@@ -225,8 +225,8 @@ func (a *Action) ShortActUserName(ctx context.Context) string {
return base.EllipsisString(a.GetActUserName(ctx), 20)
}
-// GetDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
-func (a *Action) GetDisplayName(ctx context.Context) string {
+// GetActDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
+func (a *Action) GetActDisplayName(ctx context.Context) string {
if setting.UI.DefaultShowFullName {
trimmedFullName := strings.TrimSpace(a.GetActFullName(ctx))
if len(trimmedFullName) > 0 {
@@ -236,8 +236,8 @@ func (a *Action) GetDisplayName(ctx context.Context) string {
return a.ShortActUserName(ctx)
}
-// GetDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
-func (a *Action) GetDisplayNameTitle(ctx context.Context) string {
+// GetActDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
+func (a *Action) GetActDisplayNameTitle(ctx context.Context) string {
if setting.UI.DefaultShowFullName {
return a.ShortActUserName(ctx)
}
diff --git a/models/auth/source.go b/models/auth/source.go
index 1bdde8235c..f360ca9801 100644
--- a/models/auth/source.go
+++ b/models/auth/source.go
@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -243,14 +244,14 @@ func CreateSource(ctx context.Context, source *Source) error {
type FindSourcesOptions struct {
db.ListOptions
- IsActive util.OptionalBool
+ IsActive optional.Option[bool]
LoginType Type
}
func (opts FindSourcesOptions) ToConds() builder.Cond {
conds := builder.NewCond()
- if !opts.IsActive.IsNone() {
- conds = conds.And(builder.Eq{"is_active": opts.IsActive.IsTrue()})
+ if opts.IsActive.Has() {
+ conds = conds.And(builder.Eq{"is_active": opts.IsActive.Value()})
}
if opts.LoginType != NoType {
conds = conds.And(builder.Eq{"`type`": opts.LoginType})
@@ -262,7 +263,7 @@ func (opts FindSourcesOptions) ToConds() builder.Cond {
// source of type LoginSSPI
func IsSSPIEnabled(ctx context.Context) bool {
exist, err := db.Exist[Source](ctx, FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
LoginType: SSPI,
}.ToConds())
if err != nil {
diff --git a/models/fixtures/action_run.yml b/models/fixtures/action_run.yml
index 2c2151f354..a42ab77ca5 100644
--- a/models/fixtures/action_run.yml
+++ b/models/fixtures/action_run.yml
@@ -17,3 +17,22 @@
updated: 1683636626
need_approval: 0
approved_by: 0
+-
+ id: 792
+ title: "update actions"
+ repo_id: 4
+ owner_id: 1
+ workflow_id: "artifact.yaml"
+ index: 188
+ trigger_user_id: 1
+ ref: "refs/heads/master"
+ commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
+ event: "push"
+ is_fork_pull_request: 0
+ status: 1
+ started: 1683636528
+ stopped: 1683636626
+ created: 1683636108
+ updated: 1683636626
+ need_approval: 0
+ approved_by: 0
diff --git a/models/fixtures/action_run_job.yml b/models/fixtures/action_run_job.yml
index 071998b979..fd90f4fd5d 100644
--- a/models/fixtures/action_run_job.yml
+++ b/models/fixtures/action_run_job.yml
@@ -12,3 +12,17 @@
status: 1
started: 1683636528
stopped: 1683636626
+-
+ id: 193
+ run_id: 792
+ repo_id: 4
+ owner_id: 1
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ is_fork_pull_request: 0
+ name: job_2
+ attempt: 1
+ job_id: job_2
+ task_id: 48
+ status: 1
+ started: 1683636528
+ stopped: 1683636626
diff --git a/models/fixtures/action_task.yml b/models/fixtures/action_task.yml
index c78fb3c5d6..443effe08c 100644
--- a/models/fixtures/action_task.yml
+++ b/models/fixtures/action_task.yml
@@ -18,3 +18,23 @@
log_length: 707
log_size: 90179
log_expired: 0
+-
+ id: 48
+ job_id: 193
+ attempt: 1
+ runner_id: 1
+ status: 6 # 6 is the status code for "running", running task can upload artifacts
+ started: 1683636528
+ stopped: 1683636626
+ repo_id: 4
+ owner_id: 1
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ is_fork_pull_request: 0
+ token_hash: ffffcfffffffbffffffffffffffffefffffffafffffffffffffffffffffffffffffdffffffffffffffffffffffffffffffff
+ token_salt: ffffffffff
+ token_last_eight: ffffffff
+ log_filename: artifact-test2/2f/47.log
+ log_in_storage: 1
+ log_length: 707
+ log_size: 90179
+ log_expired: 0
diff --git a/models/issues/comment.go b/models/issues/comment.go
index da91a83384..e37f844b5c 100644
--- a/models/issues/comment.go
+++ b/models/issues/comment.go
@@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
@@ -1036,8 +1037,8 @@ type FindCommentsOptions struct {
TreePath string
Type CommentType
IssueIDs []int64
- Invalidated util.OptionalBool
- IsPull util.OptionalBool
+ Invalidated optional.Option[bool]
+ IsPull optional.Option[bool]
}
// ToConds implements FindOptions interface
@@ -1069,11 +1070,11 @@ func (opts FindCommentsOptions) ToConds() builder.Cond {
if len(opts.TreePath) > 0 {
cond = cond.And(builder.Eq{"comment.tree_path": opts.TreePath})
}
- if !opts.Invalidated.IsNone() {
- cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.IsTrue()})
+ if opts.Invalidated.Has() {
+ cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.Value()})
}
- if opts.IsPull != util.OptionalBoolNone {
- cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()})
+ if opts.IsPull.Has() {
+ cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.Value()})
}
return cond
}
@@ -1082,7 +1083,7 @@ func (opts FindCommentsOptions) ToConds() builder.Cond {
func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) {
comments := make([]*Comment, 0, 10)
sess := db.GetEngine(ctx).Where(opts.ToConds())
- if opts.RepoID > 0 || opts.IsPull != util.OptionalBoolNone {
+ if opts.RepoID > 0 || opts.IsPull.Has() {
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
}
diff --git a/models/issues/content_history.go b/models/issues/content_history.go
index 8b00adda99..31c80d2cea 100644
--- a/models/issues/content_history.go
+++ b/models/issues/content_history.go
@@ -172,13 +172,9 @@ func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int6
// HasIssueContentHistory check if a ContentHistory entry exists
func HasIssueContentHistory(dbCtx context.Context, issueID, commentID int64) (bool, error) {
- exists, err := db.GetEngine(dbCtx).Cols("id").Exist(&ContentHistory{
- IssueID: issueID,
- CommentID: commentID,
- })
+ exists, err := db.GetEngine(dbCtx).Where(builder.Eq{"issue_id": issueID, "comment_id": commentID}).Exist(&ContentHistory{})
if err != nil {
- log.Error("can not fetch issue content history. err=%v", err)
- return false, err
+ return false, fmt.Errorf("can not check issue content history. err: %w", err)
}
return exists, err
}
diff --git a/models/issues/content_history_test.go b/models/issues/content_history_test.go
index 0ea1d0f7b2..1caa73a948 100644
--- a/models/issues/content_history_test.go
+++ b/models/issues/content_history_test.go
@@ -78,3 +78,22 @@ func TestContentHistory(t *testing.T) {
assert.EqualValues(t, 7, list2[1].HistoryID)
assert.EqualValues(t, 4, list2[2].HistoryID)
}
+
+func TestHasIssueContentHistoryForCommentOnly(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ _ = db.TruncateBeans(db.DefaultContext, &issues_model.ContentHistory{})
+
+ hasHistory1, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 10, 0)
+ assert.False(t, hasHistory1)
+ hasHistory2, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 10, 100)
+ assert.False(t, hasHistory2)
+
+ _ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 10, 100, timeutil.TimeStampNow(), "c-a", true)
+ _ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 10, 100, timeutil.TimeStampNow().Add(5), "c-b", false)
+
+ hasHistory1, _ = issues_model.HasIssueContentHistory(db.DefaultContext, 10, 0)
+ assert.False(t, hasHistory1)
+ hasHistory2, _ = issues_model.HasIssueContentHistory(db.DefaultContext, 10, 100)
+ assert.True(t, hasHistory2)
+}
diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go
index 7dc277327a..c5c9cecdb9 100644
--- a/models/issues/issue_search.go
+++ b/models/issues/issue_search.go
@@ -13,7 +13,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"xorm.io/builder"
"xorm.io/xorm"
@@ -34,8 +34,8 @@ type IssuesOptions struct { //nolint
MilestoneIDs []int64
ProjectID int64
ProjectBoardID int64
- IsClosed util.OptionalBool
- IsPull util.OptionalBool
+ IsClosed optional.Option[bool]
+ IsPull optional.Option[bool]
LabelIDs []int64
IncludedLabelNames []string
ExcludedLabelNames []string
@@ -46,7 +46,7 @@ type IssuesOptions struct { //nolint
UpdatedBeforeUnix int64
// prioritize issues from this repo
PriorityRepoID int64
- IsArchived util.OptionalBool
+ IsArchived optional.Option[bool]
Org *organization.Organization // issues permission scope
Team *organization.Team // issues permission scope
User *user_model.User // issues permission scope
@@ -217,8 +217,8 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
applyRepoConditions(sess, opts)
- if !opts.IsClosed.IsNone() {
- sess.And("issue.is_closed=?", opts.IsClosed.IsTrue())
+ if opts.IsClosed.Has() {
+ sess.And("issue.is_closed=?", opts.IsClosed.Value())
}
if opts.AssigneeID > 0 {
@@ -260,21 +260,18 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
applyProjectBoardCondition(sess, opts)
- switch opts.IsPull {
- case util.OptionalBoolTrue:
- sess.And("issue.is_pull=?", true)
- case util.OptionalBoolFalse:
- sess.And("issue.is_pull=?", false)
+ if opts.IsPull.Has() {
+ sess.And("issue.is_pull=?", opts.IsPull.Value())
}
- if opts.IsArchived != util.OptionalBoolNone {
- sess.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()})
+ if opts.IsArchived.Has() {
+ sess.And(builder.Eq{"repository.is_archived": opts.IsArchived.Value()})
}
applyLabelsCondition(sess, opts)
if opts.User != nil {
- sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue()))
+ sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value()))
}
return sess
diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go
index 99ca19f804..32c5674fc9 100644
--- a/models/issues/issue_stats.go
+++ b/models/issues/issue_stats.go
@@ -8,7 +8,6 @@ import (
"fmt"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/util"
"xorm.io/builder"
"xorm.io/xorm"
@@ -170,11 +169,8 @@ func applyIssuesOptions(sess *xorm.Session, opts *IssuesOptions, issueIDs []int6
applyReviewedCondition(sess, opts.ReviewedID)
}
- switch opts.IsPull {
- case util.OptionalBoolTrue:
- sess.And("issue.is_pull=?", true)
- case util.OptionalBoolFalse:
- sess.And("issue.is_pull=?", false)
+ if opts.IsPull.Has() {
+ sess.And("issue.is_pull=?", opts.IsPull.Value())
}
return sess
diff --git a/models/issues/label.go b/models/issues/label.go
index 527d8d7853..f6ecc68cd1 100644
--- a/models/issues/label.go
+++ b/models/issues/label.go
@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/label"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -126,7 +127,7 @@ func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
RepoIDs: []int64{repoID},
LabelIDs: []int64{labelID},
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
})
for _, count := range counts {
diff --git a/models/issues/milestone.go b/models/issues/milestone.go
index ea52a64c81..db0312adf0 100644
--- a/models/issues/milestone.go
+++ b/models/issues/milestone.go
@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -302,7 +303,7 @@ func DeleteMilestoneByRepoID(ctx context.Context, repoID, id int64) error {
}
numClosedMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{
RepoID: repo.ID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
if err != nil {
return err
diff --git a/models/issues/milestone_list.go b/models/issues/milestone_list.go
index a73bf73c17..d1b3f0301b 100644
--- a/models/issues/milestone_list.go
+++ b/models/issues/milestone_list.go
@@ -8,7 +8,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"xorm.io/builder"
)
@@ -28,7 +28,7 @@ func (milestones MilestoneList) getMilestoneIDs() []int64 {
type FindMilestoneOptions struct {
db.ListOptions
RepoID int64
- IsClosed util.OptionalBool
+ IsClosed optional.Option[bool]
Name string
SortType string
RepoCond builder.Cond
@@ -40,8 +40,8 @@ func (opts FindMilestoneOptions) ToConds() builder.Cond {
if opts.RepoID != 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
- if opts.IsClosed != util.OptionalBoolNone {
- cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.IsTrue()})
+ if opts.IsClosed.Has() {
+ cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.Value()})
}
if opts.RepoCond != nil && opts.RepoCond.IsValid() {
cond = cond.And(builder.In("repo_id", builder.Select("id").From("repository").Where(opts.RepoCond)))
diff --git a/models/issues/milestone_test.go b/models/issues/milestone_test.go
index 7477af92c8..e5f6f15ca2 100644
--- a/models/issues/milestone_test.go
+++ b/models/issues/milestone_test.go
@@ -11,10 +11,10 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
@@ -39,10 +39,10 @@ func TestGetMilestoneByRepoID(t *testing.T) {
func TestGetMilestonesByRepoID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
test := func(repoID int64, state api.StateType) {
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch state {
case api.StateClosed, api.StateOpen:
- isClosed = util.OptionalBoolOf(state == api.StateClosed)
+ isClosed = optional.Some(state == api.StateClosed)
}
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
@@ -84,7 +84,7 @@ func TestGetMilestonesByRepoID(t *testing.T) {
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
RepoID: unittest.NonexistentID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
})
assert.NoError(t, err)
assert.Len(t, milestones, 0)
@@ -101,7 +101,7 @@ func TestGetMilestones(t *testing.T) {
PageSize: setting.UI.IssuePagingNum,
},
RepoID: repo.ID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
SortType: sortType,
})
assert.NoError(t, err)
@@ -118,7 +118,7 @@ func TestGetMilestones(t *testing.T) {
PageSize: setting.UI.IssuePagingNum,
},
RepoID: repo.ID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
Name: "",
SortType: sortType,
})
@@ -178,7 +178,7 @@ func TestCountRepoClosedMilestones(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
RepoID: repoID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
assert.NoError(t, err)
assert.EqualValues(t, repo.NumClosedMilestones, count)
@@ -189,7 +189,7 @@ func TestCountRepoClosedMilestones(t *testing.T) {
count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
RepoID: unittest.NonexistentID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
assert.NoError(t, err)
assert.EqualValues(t, 0, count)
@@ -206,7 +206,7 @@ func TestCountMilestonesByRepoIDs(t *testing.T) {
openCounts, err := issues_model.CountMilestonesMap(db.DefaultContext, issues_model.FindMilestoneOptions{
RepoIDs: []int64{1, 2},
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
})
assert.NoError(t, err)
assert.EqualValues(t, repo1OpenCount, openCounts[1])
@@ -215,7 +215,7 @@ func TestCountMilestonesByRepoIDs(t *testing.T) {
closedCounts, err := issues_model.CountMilestonesMap(db.DefaultContext,
issues_model.FindMilestoneOptions{
RepoIDs: []int64{1, 2},
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
assert.NoError(t, err)
assert.EqualValues(t, repo1ClosedCount, closedCounts[1])
@@ -234,7 +234,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
PageSize: setting.UI.IssuePagingNum,
},
RepoIDs: []int64{repo1.ID, repo2.ID},
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
SortType: sortType,
})
assert.NoError(t, err)
@@ -252,7 +252,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
PageSize: setting.UI.IssuePagingNum,
},
RepoIDs: []int64{repo1.ID, repo2.ID},
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
SortType: sortType,
})
assert.NoError(t, err)
diff --git a/models/issues/review_list.go b/models/issues/review_list.go
index 282f18b4f7..ec6cb07988 100644
--- a/models/issues/review_list.go
+++ b/models/issues/review_list.go
@@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"xorm.io/builder"
)
@@ -68,7 +68,7 @@ type FindReviewOptions struct {
IssueID int64
ReviewerID int64
OfficialOnly bool
- Dismissed util.OptionalBool
+ Dismissed optional.Option[bool]
}
func (opts *FindReviewOptions) toCond() builder.Cond {
@@ -85,8 +85,8 @@ func (opts *FindReviewOptions) toCond() builder.Cond {
if opts.OfficialOnly {
cond = cond.And(builder.Eq{"official": true})
}
- if !opts.Dismissed.IsNone() {
- cond = cond.And(builder.Eq{"dismissed": opts.Dismissed.IsTrue()})
+ if opts.Dismissed.Has() {
+ cond = cond.And(builder.Eq{"dismissed": opts.Dismissed.Value()})
}
return cond
}
diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go
index 91c4832e49..4063ca043b 100644
--- a/models/issues/tracked_time.go
+++ b/models/issues/tracked_time.go
@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -340,7 +341,7 @@ func GetTrackedTimeByID(ctx context.Context, id int64) (*TrackedTime, error) {
}
// GetIssueTotalTrackedTime returns the total tracked time for issues by given conditions.
-func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed util.OptionalBool) (int64, error) {
+func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed optional.Option[bool]) (int64, error) {
if len(opts.IssueIDs) <= MaxQueryParameters {
return getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs)
}
@@ -363,7 +364,7 @@ func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed
return accum, nil
}
-func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isClosed util.OptionalBool, issueIDs []int64) (int64, error) {
+func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isClosed optional.Option[bool], issueIDs []int64) (int64, error) {
sumSession := func(opts *IssuesOptions, issueIDs []int64) *xorm.Session {
sess := db.GetEngine(ctx).
Table("tracked_time").
@@ -378,8 +379,8 @@ func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isC
}
session := sumSession(opts, issueIDs)
- if !isClosed.IsNone() {
- session = session.And("issue.is_closed = ?", isClosed.IsTrue())
+ if isClosed.Has() {
+ session = session.And("issue.is_closed = ?", isClosed.Value())
}
return session.SumInt(new(trackedTime), "tracked_time.time")
}
diff --git a/models/issues/tracked_time_test.go b/models/issues/tracked_time_test.go
index 9beb862ffb..d82bff967a 100644
--- a/models/issues/tracked_time_test.go
+++ b/models/issues/tracked_time_test.go
@@ -11,7 +11,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert"
)
@@ -120,15 +120,15 @@ func TestTotalTimesForEachUser(t *testing.T) {
func TestGetIssueTotalTrackedTime(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- ttt, err := issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolFalse)
+ ttt, err := issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, optional.Some(false))
assert.NoError(t, err)
assert.EqualValues(t, 3682, ttt)
- ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolTrue)
+ ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, optional.Some(true))
assert.NoError(t, err)
assert.EqualValues(t, 0, ttt)
- ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolNone)
+ ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, optional.None[bool]())
assert.NoError(t, err)
assert.EqualValues(t, 3682, ttt)
}
diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go
index f849ab5c04..b8ef698d38 100644
--- a/models/packages/descriptor.go
+++ b/models/packages/descriptor.go
@@ -70,16 +70,26 @@ type PackageFileDescriptor struct {
Properties PackagePropertyList
}
-// PackageWebLink returns the package web link
+// PackageWebLink returns the relative package web link
func (pd *PackageDescriptor) PackageWebLink() string {
return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HomeLink(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
}
-// FullWebLink returns the package version web link
-func (pd *PackageDescriptor) FullWebLink() string {
+// VersionWebLink returns the relative package version web link
+func (pd *PackageDescriptor) VersionWebLink() string {
return fmt.Sprintf("%s/%s", pd.PackageWebLink(), url.PathEscape(pd.Version.LowerVersion))
}
+// PackageHTMLURL returns the absolute package HTML URL
+func (pd *PackageDescriptor) PackageHTMLURL() string {
+ return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HTMLURL(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
+}
+
+// VersionHTMLURL returns the absolute package version HTML URL
+func (pd *PackageDescriptor) VersionHTMLURL() string {
+ return fmt.Sprintf("%s/%s", pd.PackageHTMLURL(), url.PathEscape(pd.Version.LowerVersion))
+}
+
// CalculateBlobSize returns the total blobs size in bytes
func (pd *PackageDescriptor) CalculateBlobSize() int64 {
size := int64(0)
diff --git a/models/packages/nuget/search.go b/models/packages/nuget/search.go
index 53cdf2d4ad..7a505ff08f 100644
--- a/models/packages/nuget/search.go
+++ b/models/packages/nuget/search.go
@@ -55,7 +55,7 @@ func CountPackages(ctx context.Context, opts *packages_model.PackageSearchOption
func toConds(opts *packages_model.PackageSearchOptions) builder.Cond {
var cond builder.Cond = builder.Eq{
- "package.is_internal": opts.IsInternal.IsTrue(),
+ "package.is_internal": opts.IsInternal.Value(),
"package.owner_id": opts.OwnerID,
"package.type": packages_model.TypeNuGet,
}
diff --git a/models/packages/package_version.go b/models/packages/package_version.go
index 8fc475691b..505dbaa0a5 100644
--- a/models/packages/package_version.go
+++ b/models/packages/package_version.go
@@ -9,6 +9,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -105,7 +106,7 @@ func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType
ExactMatch: true,
Value: version,
},
- IsInternal: util.OptionalBoolOf(isInternal),
+ IsInternal: optional.Some(isInternal),
Paginator: db.NewAbsoluteListOptions(0, 1),
})
if err != nil {
@@ -122,7 +123,7 @@ func GetVersionsByPackageType(ctx context.Context, ownerID int64, packageType Ty
pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
OwnerID: ownerID,
Type: packageType,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
return pvs, err
}
@@ -136,7 +137,7 @@ func GetVersionsByPackageName(ctx context.Context, ownerID int64, packageType Ty
ExactMatch: true,
Value: name,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
return pvs, err
}
@@ -182,18 +183,18 @@ type PackageSearchOptions struct {
Name SearchValue // only results with the specific name are found
Version SearchValue // only results with the specific version are found
Properties map[string]string // only results are found which contain all listed version properties with the specific value
- IsInternal util.OptionalBool
- HasFileWithName string // only results are found which are associated with a file with the specific name
- HasFiles util.OptionalBool // only results are found which have associated files
+ IsInternal optional.Option[bool]
+ HasFileWithName string // only results are found which are associated with a file with the specific name
+ HasFiles optional.Option[bool] // only results are found which have associated files
Sort VersionSort
db.Paginator
}
func (opts *PackageSearchOptions) ToConds() builder.Cond {
cond := builder.NewCond()
- if !opts.IsInternal.IsNone() {
+ if opts.IsInternal.Has() {
cond = builder.Eq{
- "package_version.is_internal": opts.IsInternal.IsTrue(),
+ "package_version.is_internal": opts.IsInternal.Value(),
}
}
@@ -250,10 +251,10 @@ func (opts *PackageSearchOptions) ToConds() builder.Cond {
cond = cond.And(builder.Exists(builder.Select("package_file.id").From("package_file").Where(fileCond)))
}
- if !opts.HasFiles.IsNone() {
+ if opts.HasFiles.Has() {
filesCond := builder.Exists(builder.Select("package_file.id").From("package_file").Where(builder.Expr("package_file.version_id = package_version.id")))
- if opts.HasFiles.IsFalse() {
+ if !opts.HasFiles.Value() {
filesCond = builder.Not{filesCond}
}
@@ -307,8 +308,8 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
And(builder.Expr("pv2.id IS NULL"))
joinCond := builder.Expr("package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))")
- if !opts.IsInternal.IsNone() {
- joinCond = joinCond.And(builder.Eq{"pv2.is_internal": opts.IsInternal.IsTrue()})
+ if opts.IsInternal.Has() {
+ joinCond = joinCond.And(builder.Eq{"pv2.is_internal": opts.IsInternal.Value()})
}
sess := db.GetEngine(ctx).
diff --git a/models/project/project.go b/models/project/project.go
index 42b06e58c9..8f9ee2a99e 100644
--- a/models/project/project.go
+++ b/models/project/project.go
@@ -12,6 +12,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -196,7 +197,7 @@ type SearchOptions struct {
db.ListOptions
OwnerID int64
RepoID int64
- IsClosed util.OptionalBool
+ IsClosed optional.Option[bool]
OrderBy db.SearchOrderBy
Type Type
Title string
@@ -207,11 +208,8 @@ func (opts SearchOptions) ToConds() builder.Cond {
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
- switch opts.IsClosed {
- case util.OptionalBoolTrue:
- cond = cond.And(builder.Eq{"is_closed": true})
- case util.OptionalBoolFalse:
- cond = cond.And(builder.Eq{"is_closed": false})
+ if opts.IsClosed.Has() {
+ cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.Value()})
}
if opts.Type > 0 {
diff --git a/models/repo/repo.go b/models/repo/repo.go
index 13493ba6e8..5ce3ecb58a 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
@@ -840,7 +841,7 @@ func (repo *Repository) TemplateRepo(ctx context.Context) *Repository {
type CountRepositoryOptions struct {
OwnerID int64
- Private util.OptionalBool
+ Private optional.Option[bool]
}
// CountRepositories returns number of repositories.
@@ -852,8 +853,8 @@ func CountRepositories(ctx context.Context, opts CountRepositoryOptions) (int64,
if opts.OwnerID > 0 {
sess.And("owner_id = ?", opts.OwnerID)
}
- if !opts.Private.IsNone() {
- sess.And("is_private=?", opts.Private.IsTrue())
+ if opts.Private.Has() {
+ sess.And("is_private=?", opts.Private.Value())
}
count, err := sess.Count(new(Repository))
diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go
index ca9209d751..1a870224bf 100644
--- a/models/repo/repo_test.go
+++ b/models/repo/repo_test.go
@@ -12,17 +12,17 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
- "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
var (
countRepospts = repo_model.CountRepositoryOptions{OwnerID: 10}
- countReposptsPublic = repo_model.CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolFalse}
- countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolTrue}
+ countReposptsPublic = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(false)}
+ countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(true)}
)
func TestGetRepositoryCount(t *testing.T) {
diff --git a/models/user/user.go b/models/user/user.go
index e92bbd4d0b..a898e71a2d 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -715,7 +715,7 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
// 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 {
+ if user.IsAdmin && CountUsers(ctx, &CountUserFilter{IsAdmin: optional.Some(true)}) <= 1 {
return true
}
return false
@@ -724,7 +724,7 @@ func IsLastAdminUser(ctx context.Context, user *User) bool {
// CountUserFilter represent optional filters for CountUsers
type CountUserFilter struct {
LastLoginSince *int64
- IsAdmin util.OptionalBool
+ IsAdmin optional.Option[bool]
}
// CountUsers returns number of users.
@@ -742,8 +742,8 @@ func countUsers(ctx context.Context, opts *CountUserFilter) int64 {
cond = cond.And(builder.Gte{"last_login_unix": *opts.LastLoginSince})
}
- if !opts.IsAdmin.IsNone() {
- cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()})
+ if opts.IsAdmin.Has() {
+ cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.Value()})
}
}
diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go
index 4a84a3d411..894357e36a 100644
--- a/models/webhook/webhook.go
+++ b/models/webhook/webhook.go
@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@@ -433,7 +434,7 @@ type ListWebhookOptions struct {
db.ListOptions
RepoID int64
OwnerID int64
- IsActive util.OptionalBool
+ IsActive optional.Option[bool]
}
func (opts ListWebhookOptions) ToConds() builder.Cond {
@@ -444,8 +445,8 @@ func (opts ListWebhookOptions) ToConds() builder.Cond {
if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID})
}
- if !opts.IsActive.IsNone() {
- cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.IsTrue()})
+ if opts.IsActive.Has() {
+ cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.Value()})
}
return cond
}
diff --git a/models/webhook/webhook_system.go b/models/webhook/webhook_system.go
index 2e89f9547b..a2a9ee321a 100644
--- a/models/webhook/webhook_system.go
+++ b/models/webhook/webhook_system.go
@@ -8,7 +8,7 @@ import (
"fmt"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
)
// GetDefaultWebhooks returns all admin-default webhooks.
@@ -34,15 +34,15 @@ func GetSystemOrDefaultWebhook(ctx context.Context, id int64) (*Webhook, error)
}
// GetSystemWebhooks returns all admin system webhooks.
-func GetSystemWebhooks(ctx context.Context, isActive util.OptionalBool) ([]*Webhook, error) {
+func GetSystemWebhooks(ctx context.Context, isActive optional.Option[bool]) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5)
- if isActive.IsNone() {
+ if !isActive.Has() {
return webhooks, db.GetEngine(ctx).
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, true).
Find(&webhooks)
}
return webhooks, db.GetEngine(ctx).
- Where("repo_id=? AND owner_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()).
+ Where("repo_id=? AND owner_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.Value()).
Find(&webhooks)
}
diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go
index 694fd7a873..c70c8e99fc 100644
--- a/models/webhook/webhook_test.go
+++ b/models/webhook/webhook_test.go
@@ -11,9 +11,9 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/stretchr/testify/assert"
@@ -123,7 +123,7 @@ func TestGetWebhookByOwnerID(t *testing.T) {
func TestGetActiveWebhooksByRepoID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{RepoID: 1, IsActive: util.OptionalBoolTrue})
+ hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{RepoID: 1, IsActive: optional.Some(true)})
assert.NoError(t, err)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(1), hooks[0].ID)
@@ -143,7 +143,7 @@ func TestGetWebhooksByRepoID(t *testing.T) {
func TestGetActiveWebhooksByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{OwnerID: 3, IsActive: util.OptionalBoolTrue})
+ hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{OwnerID: 3, IsActive: optional.Some(true)})
assert.NoError(t, err)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(3), hooks[0].ID)
diff --git a/modules/actions/task_state.go b/modules/actions/task_state.go
index cbbc0b357d..fe925bbb5d 100644
--- a/modules/actions/task_state.go
+++ b/modules/actions/task_state.go
@@ -35,6 +35,9 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
} else if task.Status.IsDone() {
preStep.Stopped = task.Stopped
preStep.Status = actions_model.StatusFailure
+ if task.Status.IsSkipped() {
+ preStep.Status = actions_model.StatusSkipped
+ }
}
logIndex += preStep.LogLength
diff --git a/modules/git/command.go b/modules/git/command.go
index 9305ef6f92..371109730a 100644
--- a/modules/git/command.go
+++ b/modules/git/command.go
@@ -12,6 +12,7 @@ import (
"io"
"os"
"os/exec"
+ "runtime"
"strings"
"time"
@@ -344,6 +345,17 @@ func (c *Command) Run(opts *RunOpts) error {
log.Debug("slow git.Command.Run: %s (%s)", c, elapsed)
}
+ // We need to check if the context is canceled by the program on Windows.
+ // This is because Windows does not have signal checking when terminating the process.
+ // It always returns exit code 1, unlike Linux, which has many exit codes for signals.
+ if runtime.GOOS == "windows" &&
+ err != nil &&
+ err.Error() == "" &&
+ cmd.ProcessState.ExitCode() == 1 &&
+ ctx.Err() == context.Canceled {
+ return ctx.Err()
+ }
+
if err != nil && ctx.Err() != context.DeadlineExceeded {
return err
}
diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go
index 7c82cfbb79..6a5d65cb66 100644
--- a/modules/indexer/issues/bleve/bleve.go
+++ b/modules/indexer/issues/bleve/bleve.go
@@ -175,11 +175,11 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
queries = append(queries, bleve.NewDisjunctionQuery(repoQueries...))
}
- if !options.IsPull.IsNone() {
- queries = append(queries, inner_bleve.BoolFieldQuery(options.IsPull.IsTrue(), "is_pull"))
+ if options.IsPull.Has() {
+ queries = append(queries, inner_bleve.BoolFieldQuery(options.IsPull.Value(), "is_pull"))
}
- if !options.IsClosed.IsNone() {
- queries = append(queries, inner_bleve.BoolFieldQuery(options.IsClosed.IsTrue(), "is_closed"))
+ if options.IsClosed.Has() {
+ queries = append(queries, inner_bleve.BoolFieldQuery(options.IsClosed.Value(), "is_closed"))
}
if options.NoLabelOnly {
diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go
index 5406715bbc..69146573a8 100644
--- a/modules/indexer/issues/db/options.go
+++ b/modules/indexer/issues/db/options.go
@@ -11,6 +11,7 @@ import (
issue_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/indexer/issues/internal"
+ "code.gitea.io/gitea/modules/optional"
)
func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_model.IssuesOptions, error) {
@@ -75,7 +76,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
UpdatedAfterUnix: convertInt64(options.UpdatedAfterUnix),
UpdatedBeforeUnix: convertInt64(options.UpdatedBeforeUnix),
PriorityRepoID: 0,
- IsArchived: 0,
+ IsArchived: optional.None[bool](),
Org: nil,
Team: nil,
User: nil,
diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go
index d059f76b32..3acd3ade71 100644
--- a/modules/indexer/issues/elasticsearch/elasticsearch.go
+++ b/modules/indexer/issues/elasticsearch/elasticsearch.go
@@ -153,11 +153,11 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
query.Must(q)
}
- if !options.IsPull.IsNone() {
- query.Must(elastic.NewTermQuery("is_pull", options.IsPull.IsTrue()))
+ if options.IsPull.Has() {
+ query.Must(elastic.NewTermQuery("is_pull", options.IsPull.Value()))
}
- if !options.IsClosed.IsNone() {
- query.Must(elastic.NewTermQuery("is_closed", options.IsClosed.IsTrue()))
+ if options.IsClosed.Has() {
+ query.Must(elastic.NewTermQuery("is_closed", options.IsClosed.Value()))
}
if options.NoLabelOnly {
diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go
index 3b96686d98..10ffa7cbe6 100644
--- a/modules/indexer/issues/indexer_test.go
+++ b/modules/indexer/issues/indexer_test.go
@@ -10,8 +10,8 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/indexer/issues/internal"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
_ "code.gitea.io/gitea/models"
_ "code.gitea.io/gitea/models/actions"
@@ -210,13 +210,13 @@ func searchIssueIsPull(t *testing.T) {
}{
{
SearchOptions{
- IsPull: util.OptionalBoolFalse,
+ IsPull: optional.Some(false),
},
[]int64{17, 16, 15, 14, 13, 6, 5, 18, 10, 7, 4, 1},
},
{
SearchOptions{
- IsPull: util.OptionalBoolTrue,
+ IsPull: optional.Some(true),
},
[]int64{22, 21, 12, 11, 20, 19, 9, 8, 3, 2},
},
@@ -237,13 +237,13 @@ func searchIssueIsClosed(t *testing.T) {
}{
{
SearchOptions{
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
},
[]int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 19, 18, 10, 7, 9, 8, 3, 2, 1},
},
{
SearchOptions{
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
},
[]int64{5, 4},
},
diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go
index 031745dd2f..947335d8ce 100644
--- a/modules/indexer/issues/internal/model.go
+++ b/modules/indexer/issues/internal/model.go
@@ -5,8 +5,8 @@ package internal
import (
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
)
// IndexerData data stored in the issue indexer
@@ -77,8 +77,8 @@ type SearchOptions struct {
RepoIDs []int64 // repository IDs which the issues belong to
AllPublic bool // if include all public repositories
- IsPull util.OptionalBool // if the issues is a pull request
- IsClosed util.OptionalBool // if the issues is closed
+ IsPull optional.Option[bool] // if the issues is a pull request
+ IsClosed optional.Option[bool] // if the issues is closed
IncludedLabelIDs []int64 // labels the issues have
ExcludedLabelIDs []int64 // labels the issues don't have
diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go
index 06fddeb65b..6724471539 100644
--- a/modules/indexer/issues/internal/tests/tests.go
+++ b/modules/indexer/issues/internal/tests/tests.go
@@ -16,8 +16,8 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/indexer/issues/internal"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -166,7 +166,7 @@ var cases = []*testIndexerCase{
Paginator: &db.ListOptions{
PageSize: 5,
},
- IsPull: util.OptionalBoolFalse,
+ IsPull: optional.Some(false),
},
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits))
@@ -182,7 +182,7 @@ var cases = []*testIndexerCase{
Paginator: &db.ListOptions{
PageSize: 5,
},
- IsPull: util.OptionalBoolTrue,
+ IsPull: optional.Some(true),
},
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits))
@@ -198,7 +198,7 @@ var cases = []*testIndexerCase{
Paginator: &db.ListOptions{
PageSize: 5,
},
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
},
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits))
@@ -214,7 +214,7 @@ var cases = []*testIndexerCase{
Paginator: &db.ListOptions{
PageSize: 5,
},
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
},
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits))
diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go
index ab8dcd0af4..325883196b 100644
--- a/modules/indexer/issues/meilisearch/meilisearch.go
+++ b/modules/indexer/issues/meilisearch/meilisearch.go
@@ -131,11 +131,11 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
query.And(q)
}
- if !options.IsPull.IsNone() {
- query.And(inner_meilisearch.NewFilterEq("is_pull", options.IsPull.IsTrue()))
+ if options.IsPull.Has() {
+ query.And(inner_meilisearch.NewFilterEq("is_pull", options.IsPull.Value()))
}
- if !options.IsClosed.IsNone() {
- query.And(inner_meilisearch.NewFilterEq("is_closed", options.IsClosed.IsTrue()))
+ if options.IsClosed.Has() {
+ query.And(inner_meilisearch.NewFilterEq("is_closed", options.IsClosed.Value()))
}
if options.NoLabelOnly {
diff --git a/modules/queue/workergroup.go b/modules/queue/workergroup.go
index 147a4f335e..e3801ef2b2 100644
--- a/modules/queue/workergroup.go
+++ b/modules/queue/workergroup.go
@@ -60,6 +60,9 @@ func (q *WorkerPoolQueue[T]) doDispatchBatchToWorker(wg *workerGroup[T], flushCh
full = true
}
+ // TODO: the logic could be improved in the future, to avoid a data-race between "doStartNewWorker" and "workerNum"
+ // The root problem is that if we skip "doStartNewWorker" here, the "workerNum" might be decreased by other workers later
+ // So ideally, it should check whether there are enough workers by some approaches, and start new workers if necessary.
q.workerNumMu.Lock()
noWorker := q.workerNum == 0
if full || noWorker {
@@ -143,7 +146,11 @@ func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) {
log.Debug("Queue %q starts new worker", q.GetName())
defer log.Debug("Queue %q stops idle worker", q.GetName())
+ atomic.AddInt32(&q.workerStartedCounter, 1) // Only increase counter, used for debugging
+
t := time.NewTicker(workerIdleDuration)
+ defer t.Stop()
+
keepWorking := true
stopWorking := func() {
q.workerNumMu.Lock()
@@ -158,13 +165,18 @@ func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) {
case batch, ok := <-q.batchChan:
if !ok {
stopWorking()
- } else {
- q.doWorkerHandle(batch)
- t.Reset(workerIdleDuration)
+ continue
+ }
+ q.doWorkerHandle(batch)
+ // reset the idle ticker, and drain the tick after reset in case a tick is already triggered
+ t.Reset(workerIdleDuration)
+ select {
+ case <-t.C:
+ default:
}
case <-t.C:
q.workerNumMu.Lock()
- keepWorking = q.workerNum <= 1
+ keepWorking = q.workerNum <= 1 // keep the last worker running
if !keepWorking {
q.workerNum--
}
diff --git a/modules/queue/workerqueue.go b/modules/queue/workerqueue.go
index b28fd88027..4160622d81 100644
--- a/modules/queue/workerqueue.go
+++ b/modules/queue/workerqueue.go
@@ -40,6 +40,8 @@ type WorkerPoolQueue[T any] struct {
workerMaxNum int
workerActiveNum int
workerNumMu sync.Mutex
+
+ workerStartedCounter int32
}
type flushType chan struct{}
diff --git a/modules/queue/workerqueue_test.go b/modules/queue/workerqueue_test.go
index e60120162a..e09669c542 100644
--- a/modules/queue/workerqueue_test.go
+++ b/modules/queue/workerqueue_test.go
@@ -11,6 +11,7 @@ import (
"time"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
@@ -175,11 +176,7 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett
}
func TestWorkerPoolQueueActiveWorkers(t *testing.T) {
- oldWorkerIdleDuration := workerIdleDuration
- workerIdleDuration = 300 * time.Millisecond
- defer func() {
- workerIdleDuration = oldWorkerIdleDuration
- }()
+ defer test.MockVariableValue(&workerIdleDuration, 300*time.Millisecond)()
handler := func(items ...int) (unhandled []int) {
time.Sleep(100 * time.Millisecond)
@@ -250,3 +247,25 @@ func TestWorkerPoolQueueShutdown(t *testing.T) {
q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", qs, handler, false)
assert.EqualValues(t, 20, q.GetQueueItemNumber())
}
+
+func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) {
+ defer test.MockVariableValue(&workerIdleDuration, 10*time.Millisecond)()
+
+ handler := func(items ...int) (unhandled []int) {
+ time.Sleep(50 * time.Millisecond)
+ return nil
+ }
+
+ q, _ := newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 2, Length: 100}, handler, false)
+ stop := runWorkerPoolQueue(q)
+ for i := 0; i < 20; i++ {
+ assert.NoError(t, q.Push(i))
+ }
+
+ time.Sleep(500 * time.Millisecond)
+ assert.EqualValues(t, 2, q.GetWorkerNumber())
+ assert.EqualValues(t, 2, q.GetWorkerActiveNumber())
+ // when the queue never becomes empty, the existing workers should keep working
+ assert.EqualValues(t, 2, q.workerStartedCounter)
+ stop()
+}
diff --git a/modules/references/references.go b/modules/references/references.go
index 7758312564..761d6ee3d1 100644
--- a/modules/references/references.go
+++ b/modules/references/references.go
@@ -31,9 +31,9 @@ var (
// mentionPattern matches all mentions in the form of "@user" or "@org/team"
mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_]+\/?[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_][0-9a-zA-Z-_.]+\/?[0-9a-zA-Z-_.]+[0-9a-zA-Z-_])(?:\s|[:,;.?!]\s|[:,;.?!]?$|\)|\])`)
// issueNumericPattern matches string that references to a numeric issue, e.g. #1287
- issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\')([#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
+ issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`)
// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
- issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$))`)
+ issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\')`)
// crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository
// e.g. org/repo#12345
crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
diff --git a/modules/references/references_test.go b/modules/references/references_test.go
index ba7dda80cc..0c32933619 100644
--- a/modules/references/references_test.go
+++ b/modules/references/references_test.go
@@ -429,6 +429,8 @@ func TestRegExp_issueNumericPattern(t *testing.T) {
" #12",
"#12:",
"ref: #12: msg",
+ "\"#1234\"",
+ "'#1234'",
}
falseTestCases := []string{
"# 1234",
@@ -459,6 +461,8 @@ func TestRegExp_issueAlphanumericPattern(t *testing.T) {
"(ABC-123)",
"[ABC-123]",
"ABC-123:",
+ "\"ABC-123\"",
+ "'ABC-123'",
}
falseTestCases := []string{
"RC-08",
diff --git a/modules/setting/admin.go b/modules/setting/admin.go
index 48a2ea9744..29bb947bc4 100644
--- a/modules/setting/admin.go
+++ b/modules/setting/admin.go
@@ -20,5 +20,6 @@ func loadAdminFrom(rootCfg ConfigProvider) {
}
const (
- UserFeatureDeletion = "deletion"
+ UserFeatureDeletion = "deletion"
+ UserFeatureManageGPGKeys = "manage_gpg_keys"
)
diff --git a/modules/util/util.go b/modules/util/util.go
index 615f654e47..5c75158196 100644
--- a/modules/util/util.go
+++ b/modules/util/util.go
@@ -17,57 +17,6 @@ import (
"golang.org/x/text/language"
)
-// OptionalBool a boolean that can be "null"
-type OptionalBool byte
-
-const (
- // OptionalBoolNone a "null" boolean value
- OptionalBoolNone OptionalBool = iota
- // OptionalBoolTrue a "true" boolean value
- OptionalBoolTrue
- // OptionalBoolFalse a "false" boolean value
- OptionalBoolFalse
-)
-
-// IsTrue return true if equal to OptionalBoolTrue
-func (o OptionalBool) IsTrue() bool {
- return o == OptionalBoolTrue
-}
-
-// IsFalse return true if equal to OptionalBoolFalse
-func (o OptionalBool) IsFalse() bool {
- return o == OptionalBoolFalse
-}
-
-// IsNone return true if equal to OptionalBoolNone
-func (o OptionalBool) IsNone() bool {
- return o == OptionalBoolNone
-}
-
-// ToGeneric converts OptionalBool to optional.Option[bool]
-func (o OptionalBool) ToGeneric() optional.Option[bool] {
- if o.IsNone() {
- return optional.None[bool]()
- }
- return optional.Some[bool](o.IsTrue())
-}
-
-// OptionalBoolFromGeneric converts optional.Option[bool] to OptionalBool
-func OptionalBoolFromGeneric(o optional.Option[bool]) OptionalBool {
- if o.Has() {
- return OptionalBoolOf(o.Value())
- }
- return OptionalBoolNone
-}
-
-// OptionalBoolOf get the corresponding OptionalBool of a bool
-func OptionalBoolOf(b bool) OptionalBool {
- if b {
- return OptionalBoolTrue
- }
- return OptionalBoolFalse
-}
-
// OptionalBoolParse get the corresponding optional.Option[bool] of a string using strconv.ParseBool
func OptionalBoolParse(s string) optional.Option[bool] {
v, e := strconv.ParseBool(s)
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index f4bea9ddd0..c47affe372 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1793,9 +1793,9 @@ pulls.unrelated_histories = Merge Failed: The merge head and base do not share a
pulls.merge_out_of_date = Merge Failed: Whilst generating the merge, the base was updated. Hint: Try again.
pulls.head_out_of_date = Merge Failed: Whilst generating the merge, the head was updated. Hint: Try again.
pulls.has_merged = Failed: The pull request has been merged, you cannot merge again or change the target branch.
-pulls.push_rejected = Merge Failed: The push was rejected. Review the Git Hooks for this repository.
+pulls.push_rejected = Push Failed: The push was rejected. Review the Git Hooks for this repository.
pulls.push_rejected_summary = Full Rejection Message
-pulls.push_rejected_no_message = Merge Failed: The push was rejected but there was no remote message.
Review the Git Hooks for this repository
+pulls.push_rejected_no_message = Push Failed: The push was rejected but there was no remote message. Review the Git Hooks for this repository
pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because there is a pending pull request (#%d) with identical properties.`
pulls.status_checking = Some checks are pending
pulls.status_checks_success = All checks were successful
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index 7fd79446cc..20ef954cd2 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -424,6 +424,7 @@ authorization_failed_desc=L'autorisation a échoué car nous avons détecté une
sspi_auth_failed=Échec de l'authentification SSPI
password_pwned=Le mot de passe que vous avez choisi se trouve sur la liste des mots de passe ayant fuité sur internet. Veuillez réessayer avec un mot de passe différent et considérer remplacer ce mot de passe si vous l'utilisez ailleurs.
password_pwned_err=Impossible d'envoyer la demande à HaveIBeenPwned
+last_admin=Vous ne pouvez pas supprimer ce compte car au moins un administrateur est requis.
[mail]
view_it_on=Voir sur %s
@@ -1714,6 +1715,7 @@ pulls.select_commit_hold_shift_for_range=Maintenir Maj et cliquer sur des révis
pulls.review_only_possible_for_full_diff=Une évaluation n'est possible que lorsque vous affichez le différentiel complet.
pulls.filter_changes_by_commit=Filtrer par révision
pulls.nothing_to_compare=Ces branches sont identiques. Il n’y a pas besoin de créer une demande d'ajout.
+pulls.nothing_to_compare_have_tag=Les branches/étiquettes sélectionnées sont équivalentes.
pulls.nothing_to_compare_and_allow_empty_pr=Ces branches sont égales. Cette demande d'ajout sera vide.
pulls.has_pull_request='Il existe déjà une demande d'ajout entre ces deux branches : %[2]s#%[3]d'
pulls.create=Créer une demande d'ajout
diff --git a/routers/api/actions/artifact.pb.go b/routers/api/actions/artifact.pb.go
new file mode 100644
index 0000000000..590eda9fb9
--- /dev/null
+++ b/routers/api/actions/artifact.pb.go
@@ -0,0 +1,1058 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.32.0
+// protoc v4.25.2
+// source: artifact.proto
+
+package actions
+
+import (
+ reflect "reflect"
+ sync "sync"
+
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ timestamppb "google.golang.org/protobuf/types/known/timestamppb"
+ wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type CreateArtifactRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+ ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"`
+ Version int32 `protobuf:"varint,5,opt,name=version,proto3" json:"version,omitempty"`
+}
+
+func (x *CreateArtifactRequest) Reset() {
+ *x = CreateArtifactRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *CreateArtifactRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateArtifactRequest) ProtoMessage() {}
+
+func (x *CreateArtifactRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateArtifactRequest.ProtoReflect.Descriptor instead.
+func (*CreateArtifactRequest) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *CreateArtifactRequest) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *CreateArtifactRequest) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *CreateArtifactRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *CreateArtifactRequest) GetExpiresAt() *timestamppb.Timestamp {
+ if x != nil {
+ return x.ExpiresAt
+ }
+ return nil
+}
+
+func (x *CreateArtifactRequest) GetVersion() int32 {
+ if x != nil {
+ return x.Version
+ }
+ return 0
+}
+
+type CreateArtifactResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
+ SignedUploadUrl string `protobuf:"bytes,2,opt,name=signed_upload_url,json=signedUploadUrl,proto3" json:"signed_upload_url,omitempty"`
+}
+
+func (x *CreateArtifactResponse) Reset() {
+ *x = CreateArtifactResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *CreateArtifactResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateArtifactResponse) ProtoMessage() {}
+
+func (x *CreateArtifactResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateArtifactResponse.ProtoReflect.Descriptor instead.
+func (*CreateArtifactResponse) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *CreateArtifactResponse) GetOk() bool {
+ if x != nil {
+ return x.Ok
+ }
+ return false
+}
+
+func (x *CreateArtifactResponse) GetSignedUploadUrl() string {
+ if x != nil {
+ return x.SignedUploadUrl
+ }
+ return ""
+}
+
+type FinalizeArtifactRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+ Size int64 `protobuf:"varint,4,opt,name=size,proto3" json:"size,omitempty"`
+ Hash *wrapperspb.StringValue `protobuf:"bytes,5,opt,name=hash,proto3" json:"hash,omitempty"`
+}
+
+func (x *FinalizeArtifactRequest) Reset() {
+ *x = FinalizeArtifactRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *FinalizeArtifactRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FinalizeArtifactRequest) ProtoMessage() {}
+
+func (x *FinalizeArtifactRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use FinalizeArtifactRequest.ProtoReflect.Descriptor instead.
+func (*FinalizeArtifactRequest) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *FinalizeArtifactRequest) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *FinalizeArtifactRequest) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *FinalizeArtifactRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *FinalizeArtifactRequest) GetSize() int64 {
+ if x != nil {
+ return x.Size
+ }
+ return 0
+}
+
+func (x *FinalizeArtifactRequest) GetHash() *wrapperspb.StringValue {
+ if x != nil {
+ return x.Hash
+ }
+ return nil
+}
+
+type FinalizeArtifactResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
+ ArtifactId int64 `protobuf:"varint,2,opt,name=artifact_id,json=artifactId,proto3" json:"artifact_id,omitempty"`
+}
+
+func (x *FinalizeArtifactResponse) Reset() {
+ *x = FinalizeArtifactResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *FinalizeArtifactResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FinalizeArtifactResponse) ProtoMessage() {}
+
+func (x *FinalizeArtifactResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use FinalizeArtifactResponse.ProtoReflect.Descriptor instead.
+func (*FinalizeArtifactResponse) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *FinalizeArtifactResponse) GetOk() bool {
+ if x != nil {
+ return x.Ok
+ }
+ return false
+}
+
+func (x *FinalizeArtifactResponse) GetArtifactId() int64 {
+ if x != nil {
+ return x.ArtifactId
+ }
+ return 0
+}
+
+type ListArtifactsRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ NameFilter *wrapperspb.StringValue `protobuf:"bytes,3,opt,name=name_filter,json=nameFilter,proto3" json:"name_filter,omitempty"`
+ IdFilter *wrapperspb.Int64Value `protobuf:"bytes,4,opt,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"`
+}
+
+func (x *ListArtifactsRequest) Reset() {
+ *x = ListArtifactsRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListArtifactsRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListArtifactsRequest) ProtoMessage() {}
+
+func (x *ListArtifactsRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListArtifactsRequest.ProtoReflect.Descriptor instead.
+func (*ListArtifactsRequest) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *ListArtifactsRequest) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *ListArtifactsRequest) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *ListArtifactsRequest) GetNameFilter() *wrapperspb.StringValue {
+ if x != nil {
+ return x.NameFilter
+ }
+ return nil
+}
+
+func (x *ListArtifactsRequest) GetIdFilter() *wrapperspb.Int64Value {
+ if x != nil {
+ return x.IdFilter
+ }
+ return nil
+}
+
+type ListArtifactsResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Artifacts []*ListArtifactsResponse_MonolithArtifact `protobuf:"bytes,1,rep,name=artifacts,proto3" json:"artifacts,omitempty"`
+}
+
+func (x *ListArtifactsResponse) Reset() {
+ *x = ListArtifactsResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListArtifactsResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListArtifactsResponse) ProtoMessage() {}
+
+func (x *ListArtifactsResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[5]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListArtifactsResponse.ProtoReflect.Descriptor instead.
+func (*ListArtifactsResponse) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *ListArtifactsResponse) GetArtifacts() []*ListArtifactsResponse_MonolithArtifact {
+ if x != nil {
+ return x.Artifacts
+ }
+ return nil
+}
+
+type ListArtifactsResponse_MonolithArtifact struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ DatabaseId int64 `protobuf:"varint,3,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"`
+ Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"`
+ Size int64 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"`
+ CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) Reset() {
+ *x = ListArtifactsResponse_MonolithArtifact{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListArtifactsResponse_MonolithArtifact) ProtoMessage() {}
+
+func (x *ListArtifactsResponse_MonolithArtifact) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[6]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListArtifactsResponse_MonolithArtifact.ProtoReflect.Descriptor instead.
+func (*ListArtifactsResponse_MonolithArtifact) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetDatabaseId() int64 {
+ if x != nil {
+ return x.DatabaseId
+ }
+ return 0
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetSize() int64 {
+ if x != nil {
+ return x.Size
+ }
+ return 0
+}
+
+func (x *ListArtifactsResponse_MonolithArtifact) GetCreatedAt() *timestamppb.Timestamp {
+ if x != nil {
+ return x.CreatedAt
+ }
+ return nil
+}
+
+type GetSignedArtifactURLRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *GetSignedArtifactURLRequest) Reset() {
+ *x = GetSignedArtifactURLRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GetSignedArtifactURLRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetSignedArtifactURLRequest) ProtoMessage() {}
+
+func (x *GetSignedArtifactURLRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[7]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetSignedArtifactURLRequest.ProtoReflect.Descriptor instead.
+func (*GetSignedArtifactURLRequest) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *GetSignedArtifactURLRequest) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *GetSignedArtifactURLRequest) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *GetSignedArtifactURLRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+type GetSignedArtifactURLResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ SignedUrl string `protobuf:"bytes,1,opt,name=signed_url,json=signedUrl,proto3" json:"signed_url,omitempty"`
+}
+
+func (x *GetSignedArtifactURLResponse) Reset() {
+ *x = GetSignedArtifactURLResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GetSignedArtifactURLResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetSignedArtifactURLResponse) ProtoMessage() {}
+
+func (x *GetSignedArtifactURLResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[8]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetSignedArtifactURLResponse.ProtoReflect.Descriptor instead.
+func (*GetSignedArtifactURLResponse) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *GetSignedArtifactURLResponse) GetSignedUrl() string {
+ if x != nil {
+ return x.SignedUrl
+ }
+ return ""
+}
+
+type DeleteArtifactRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ WorkflowRunBackendId string `protobuf:"bytes,1,opt,name=workflow_run_backend_id,json=workflowRunBackendId,proto3" json:"workflow_run_backend_id,omitempty"`
+ WorkflowJobRunBackendId string `protobuf:"bytes,2,opt,name=workflow_job_run_backend_id,json=workflowJobRunBackendId,proto3" json:"workflow_job_run_backend_id,omitempty"`
+ Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *DeleteArtifactRequest) Reset() {
+ *x = DeleteArtifactRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DeleteArtifactRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteArtifactRequest) ProtoMessage() {}
+
+func (x *DeleteArtifactRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[9]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteArtifactRequest.ProtoReflect.Descriptor instead.
+func (*DeleteArtifactRequest) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *DeleteArtifactRequest) GetWorkflowRunBackendId() string {
+ if x != nil {
+ return x.WorkflowRunBackendId
+ }
+ return ""
+}
+
+func (x *DeleteArtifactRequest) GetWorkflowJobRunBackendId() string {
+ if x != nil {
+ return x.WorkflowJobRunBackendId
+ }
+ return ""
+}
+
+func (x *DeleteArtifactRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+type DeleteArtifactResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
+ ArtifactId int64 `protobuf:"varint,2,opt,name=artifact_id,json=artifactId,proto3" json:"artifact_id,omitempty"`
+}
+
+func (x *DeleteArtifactResponse) Reset() {
+ *x = DeleteArtifactResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_artifact_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DeleteArtifactResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteArtifactResponse) ProtoMessage() {}
+
+func (x *DeleteArtifactResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_artifact_proto_msgTypes[10]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteArtifactResponse.ProtoReflect.Descriptor instead.
+func (*DeleteArtifactResponse) Descriptor() ([]byte, []int) {
+ return file_artifact_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *DeleteArtifactResponse) GetOk() bool {
+ if x != nil {
+ return x.Ok
+ }
+ return false
+}
+
+func (x *DeleteArtifactResponse) GetArtifactId() int64 {
+ if x != nil {
+ return x.ArtifactId
+ }
+ return 0
+}
+
+var File_artifact_proto protoreflect.FileDescriptor
+
+var file_artifact_proto_rawDesc = []byte{
+ 0x0a, 0x0e, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x12, 0x1d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+ 0x2e, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x1a,
+ 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+ 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+ 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x22, 0xf5, 0x01, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66,
+ 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x77, 0x6f,
+ 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72,
+ 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49,
+ 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6a, 0x6f,
+ 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77,
+ 0x4a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12,
+ 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
+ 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61,
+ 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
+ 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x18,
+ 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52,
+ 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x54, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61,
+ 0x74, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+ 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02,
+ 0x6f, 0x6b, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x75, 0x70, 0x6c,
+ 0x6f, 0x61, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73,
+ 0x69, 0x67, 0x6e, 0x65, 0x64, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x72, 0x6c, 0x22, 0xe8,
+ 0x01, 0x0a, 0x17, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66,
+ 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x77, 0x6f,
+ 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72,
+ 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49,
+ 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6a, 0x6f,
+ 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77,
+ 0x4a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12,
+ 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
+ 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
+ 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18,
+ 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
+ 0x6c, 0x75, 0x65, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0x4b, 0x0a, 0x18, 0x46, 0x69, 0x6e,
+ 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63,
+ 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x72, 0x74, 0x69,
+ 0x66, 0x61, 0x63, 0x74, 0x49, 0x64, 0x22, 0x84, 0x02, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41,
+ 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+ 0x35, 0x0a, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f,
+ 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63,
+ 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c,
+ 0x6f, 0x77, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72,
+ 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x49, 0x64, 0x12, 0x3d, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x66, 0x69, 0x6c,
+ 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
+ 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69,
+ 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x46, 0x69, 0x6c,
+ 0x74, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72,
+ 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61,
+ 0x6c, 0x75, 0x65, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x7c, 0x0a,
+ 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65,
+ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x09, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61,
+ 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x67, 0x69, 0x74, 0x68,
+ 0x75, 0x62, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x75, 0x6c,
+ 0x74, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x72,
+ 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f,
+ 0x4d, 0x6f, 0x6e, 0x6f, 0x6c, 0x69, 0x74, 0x68, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74,
+ 0x52, 0x09, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x22, 0xa1, 0x02, 0x0a, 0x26,
+ 0x4c, 0x69, 0x73, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x6f, 0x6c, 0x69, 0x74, 0x68, 0x41, 0x72,
+ 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c,
+ 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69,
+ 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f,
+ 0x77, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x3c, 0x0a,
+ 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x75,
+ 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x52,
+ 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64,
+ 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
+ 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04,
+ 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+ 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04,
+ 0x73, 0x69, 0x7a, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f,
+ 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
+ 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
+ 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22,
+ 0xa6, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x72, 0x74,
+ 0x69, 0x66, 0x61, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+ 0x35, 0x0a, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f,
+ 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63,
+ 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c,
+ 0x6f, 0x77, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72,
+ 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x65,
+ 0x6e, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3d, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x53,
+ 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x55, 0x52, 0x4c,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, 0x67, 0x6e,
+ 0x65, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69,
+ 0x67, 0x6e, 0x65, 0x64, 0x55, 0x72, 0x6c, 0x22, 0xa0, 0x01, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65,
+ 0x74, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x12, 0x35, 0x0a, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75,
+ 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x42,
+ 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x77, 0x6f, 0x72, 0x6b,
+ 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x62, 0x61, 0x63,
+ 0x6b, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77,
+ 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x42, 0x61, 0x63,
+ 0x6b, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x49, 0x0a, 0x16, 0x44, 0x65,
+ 0x6c, 0x65, 0x74, 0x65, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70,
+ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
+ 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74,
+ 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x72, 0x74, 0x69, 0x66,
+ 0x61, 0x63, 0x74, 0x49, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_artifact_proto_rawDescOnce sync.Once
+ file_artifact_proto_rawDescData = file_artifact_proto_rawDesc
+)
+
+func file_artifact_proto_rawDescGZIP() []byte {
+ file_artifact_proto_rawDescOnce.Do(func() {
+ file_artifact_proto_rawDescData = protoimpl.X.CompressGZIP(file_artifact_proto_rawDescData)
+ })
+ return file_artifact_proto_rawDescData
+}
+
+var (
+ file_artifact_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
+ file_artifact_proto_goTypes = []interface{}{
+ (*CreateArtifactRequest)(nil), // 0: github.actions.results.api.v1.CreateArtifactRequest
+ (*CreateArtifactResponse)(nil), // 1: github.actions.results.api.v1.CreateArtifactResponse
+ (*FinalizeArtifactRequest)(nil), // 2: github.actions.results.api.v1.FinalizeArtifactRequest
+ (*FinalizeArtifactResponse)(nil), // 3: github.actions.results.api.v1.FinalizeArtifactResponse
+ (*ListArtifactsRequest)(nil), // 4: github.actions.results.api.v1.ListArtifactsRequest
+ (*ListArtifactsResponse)(nil), // 5: github.actions.results.api.v1.ListArtifactsResponse
+ (*ListArtifactsResponse_MonolithArtifact)(nil), // 6: github.actions.results.api.v1.ListArtifactsResponse_MonolithArtifact
+ (*GetSignedArtifactURLRequest)(nil), // 7: github.actions.results.api.v1.GetSignedArtifactURLRequest
+ (*GetSignedArtifactURLResponse)(nil), // 8: github.actions.results.api.v1.GetSignedArtifactURLResponse
+ (*DeleteArtifactRequest)(nil), // 9: github.actions.results.api.v1.DeleteArtifactRequest
+ (*DeleteArtifactResponse)(nil), // 10: github.actions.results.api.v1.DeleteArtifactResponse
+ (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp
+ (*wrapperspb.StringValue)(nil), // 12: google.protobuf.StringValue
+ (*wrapperspb.Int64Value)(nil), // 13: google.protobuf.Int64Value
+ }
+)
+
+var file_artifact_proto_depIdxs = []int32{
+ 11, // 0: github.actions.results.api.v1.CreateArtifactRequest.expires_at:type_name -> google.protobuf.Timestamp
+ 12, // 1: github.actions.results.api.v1.FinalizeArtifactRequest.hash:type_name -> google.protobuf.StringValue
+ 12, // 2: github.actions.results.api.v1.ListArtifactsRequest.name_filter:type_name -> google.protobuf.StringValue
+ 13, // 3: github.actions.results.api.v1.ListArtifactsRequest.id_filter:type_name -> google.protobuf.Int64Value
+ 6, // 4: github.actions.results.api.v1.ListArtifactsResponse.artifacts:type_name -> github.actions.results.api.v1.ListArtifactsResponse_MonolithArtifact
+ 11, // 5: github.actions.results.api.v1.ListArtifactsResponse_MonolithArtifact.created_at:type_name -> google.protobuf.Timestamp
+ 6, // [6:6] is the sub-list for method output_type
+ 6, // [6:6] is the sub-list for method input_type
+ 6, // [6:6] is the sub-list for extension type_name
+ 6, // [6:6] is the sub-list for extension extendee
+ 0, // [0:6] is the sub-list for field type_name
+}
+
+func init() { file_artifact_proto_init() }
+func file_artifact_proto_init() {
+ if File_artifact_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_artifact_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*CreateArtifactRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*CreateArtifactResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*FinalizeArtifactRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*FinalizeArtifactResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListArtifactsRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListArtifactsResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListArtifactsResponse_MonolithArtifact); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GetSignedArtifactURLRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GetSignedArtifactURLResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*DeleteArtifactRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_artifact_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*DeleteArtifactResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_artifact_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 11,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_artifact_proto_goTypes,
+ DependencyIndexes: file_artifact_proto_depIdxs,
+ MessageInfos: file_artifact_proto_msgTypes,
+ }.Build()
+ File_artifact_proto = out.File
+ file_artifact_proto_rawDesc = nil
+ file_artifact_proto_goTypes = nil
+ file_artifact_proto_depIdxs = nil
+}
diff --git a/routers/api/actions/artifact.proto b/routers/api/actions/artifact.proto
new file mode 100644
index 0000000000..c68e5d030d
--- /dev/null
+++ b/routers/api/actions/artifact.proto
@@ -0,0 +1,73 @@
+syntax = "proto3";
+
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
+
+package github.actions.results.api.v1;
+
+message CreateArtifactRequest {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ string name = 3;
+ google.protobuf.Timestamp expires_at = 4;
+ int32 version = 5;
+}
+
+message CreateArtifactResponse {
+ bool ok = 1;
+ string signed_upload_url = 2;
+}
+
+message FinalizeArtifactRequest {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ string name = 3;
+ int64 size = 4;
+ google.protobuf.StringValue hash = 5;
+}
+
+message FinalizeArtifactResponse {
+ bool ok = 1;
+ int64 artifact_id = 2;
+}
+
+message ListArtifactsRequest {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ google.protobuf.StringValue name_filter = 3;
+ google.protobuf.Int64Value id_filter = 4;
+}
+
+message ListArtifactsResponse {
+ repeated ListArtifactsResponse_MonolithArtifact artifacts = 1;
+}
+
+message ListArtifactsResponse_MonolithArtifact {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ int64 database_id = 3;
+ string name = 4;
+ int64 size = 5;
+ google.protobuf.Timestamp created_at = 6;
+}
+
+message GetSignedArtifactURLRequest {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ string name = 3;
+}
+
+message GetSignedArtifactURLResponse {
+ string signed_url = 1;
+}
+
+message DeleteArtifactRequest {
+ string workflow_run_backend_id = 1;
+ string workflow_job_run_backend_id = 2;
+ string name = 3;
+}
+
+message DeleteArtifactResponse {
+ bool ok = 1;
+ int64 artifact_id = 2;
+}
diff --git a/routers/api/actions/artifacts_chunks.go b/routers/api/actions/artifacts_chunks.go
index 0713c8bba8..3a81724b3a 100644
--- a/routers/api/actions/artifacts_chunks.go
+++ b/routers/api/actions/artifacts_chunks.go
@@ -5,11 +5,16 @@ package actions
import (
"crypto/md5"
+ "crypto/sha256"
"encoding/base64"
+ "encoding/hex"
+ "errors"
"fmt"
+ "hash"
"io"
"path/filepath"
"sort"
+ "strings"
"time"
"code.gitea.io/gitea/models/actions"
@@ -18,6 +23,52 @@ import (
"code.gitea.io/gitea/modules/storage"
)
+func saveUploadChunkBase(st storage.ObjectStorage, ctx *ArtifactContext,
+ artifact *actions.ActionArtifact,
+ contentSize, runID, start, end, length int64, checkMd5 bool,
+) (int64, error) {
+ // build chunk store path
+ storagePath := fmt.Sprintf("tmp%d/%d-%d-%d-%d.chunk", runID, runID, artifact.ID, start, end)
+ var r io.Reader = ctx.Req.Body
+ var hasher hash.Hash
+ if checkMd5 {
+ // use io.TeeReader to avoid reading all body to md5 sum.
+ // it writes data to hasher after reading end
+ // if hash is not matched, delete the read-end result
+ hasher = md5.New()
+ r = io.TeeReader(r, hasher)
+ }
+ // save chunk to storage
+ writtenSize, err := st.Save(storagePath, r, -1)
+ if err != nil {
+ return -1, fmt.Errorf("save chunk to storage error: %v", err)
+ }
+ var checkErr error
+ if checkMd5 {
+ // check md5
+ reqMd5String := ctx.Req.Header.Get(artifactXActionsResultsMD5Header)
+ chunkMd5String := base64.StdEncoding.EncodeToString(hasher.Sum(nil))
+ log.Info("[artifact] check chunk md5, sum: %s, header: %s", chunkMd5String, reqMd5String)
+ // if md5 not match, delete the chunk
+ if reqMd5String != chunkMd5String {
+ checkErr = fmt.Errorf("md5 not match")
+ }
+ }
+ if writtenSize != contentSize {
+ checkErr = errors.Join(checkErr, fmt.Errorf("contentSize not match body size"))
+ }
+ if checkErr != nil {
+ if err := st.Delete(storagePath); err != nil {
+ log.Error("Error deleting chunk: %s, %v", storagePath, err)
+ }
+ return -1, checkErr
+ }
+ log.Info("[artifact] save chunk %s, size: %d, artifact id: %d, start: %d, end: %d",
+ storagePath, contentSize, artifact.ID, start, end)
+ // return chunk total size
+ return length, nil
+}
+
func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
artifact *actions.ActionArtifact,
contentSize, runID int64,
@@ -29,33 +80,15 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
log.Warn("parse content range error: %v, content-range: %s", err, contentRange)
return -1, fmt.Errorf("parse content range error: %v", err)
}
- // build chunk store path
- storagePath := fmt.Sprintf("tmp%d/%d-%d-%d-%d.chunk", runID, runID, artifact.ID, start, end)
- // use io.TeeReader to avoid reading all body to md5 sum.
- // it writes data to hasher after reading end
- // if hash is not matched, delete the read-end result
- hasher := md5.New()
- r := io.TeeReader(ctx.Req.Body, hasher)
- // save chunk to storage
- writtenSize, err := st.Save(storagePath, r, -1)
- if err != nil {
- return -1, fmt.Errorf("save chunk to storage error: %v", err)
- }
- // check md5
- reqMd5String := ctx.Req.Header.Get(artifactXActionsResultsMD5Header)
- chunkMd5String := base64.StdEncoding.EncodeToString(hasher.Sum(nil))
- log.Info("[artifact] check chunk md5, sum: %s, header: %s", chunkMd5String, reqMd5String)
- // if md5 not match, delete the chunk
- if reqMd5String != chunkMd5String || writtenSize != contentSize {
- if err := st.Delete(storagePath); err != nil {
- log.Error("Error deleting chunk: %s, %v", storagePath, err)
- }
- return -1, fmt.Errorf("md5 not match")
- }
- log.Info("[artifact] save chunk %s, size: %d, artifact id: %d, start: %d, end: %d",
- storagePath, contentSize, artifact.ID, start, end)
- // return chunk total size
- return length, nil
+ return saveUploadChunkBase(st, ctx, artifact, contentSize, runID, start, end, length, true)
+}
+
+func appendUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
+ artifact *actions.ActionArtifact,
+ start, contentSize, runID int64,
+) (int64, error) {
+ end := start + contentSize - 1
+ return saveUploadChunkBase(st, ctx, artifact, contentSize, runID, start, end, contentSize, false)
}
type chunkFileItem struct {
@@ -111,14 +144,14 @@ func mergeChunksForRun(ctx *ArtifactContext, st storage.ObjectStorage, runID int
log.Debug("artifact %d chunks not found", art.ID)
continue
}
- if err := mergeChunksForArtifact(ctx, chunks, st, art); err != nil {
+ if err := mergeChunksForArtifact(ctx, chunks, st, art, ""); err != nil {
return err
}
}
return nil
}
-func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st storage.ObjectStorage, artifact *actions.ActionArtifact) error {
+func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st storage.ObjectStorage, artifact *actions.ActionArtifact, checksum string) error {
sort.Slice(chunks, func(i, j int) bool {
return chunks[i].Start < chunks[j].Start
})
@@ -157,6 +190,14 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st
readers = append(readers, readCloser)
}
mergedReader := io.MultiReader(readers...)
+ shaPrefix := "sha256:"
+ var hash hash.Hash
+ if strings.HasPrefix(checksum, shaPrefix) {
+ hash = sha256.New()
+ }
+ if hash != nil {
+ mergedReader = io.TeeReader(mergedReader, hash)
+ }
// if chunk is gzip, use gz as extension
// download-artifact action will use content-encoding header to decide if it should decompress the file
@@ -185,6 +226,14 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st
}
}()
+ if hash != nil {
+ rawChecksum := hash.Sum(nil)
+ actualChecksum := hex.EncodeToString(rawChecksum)
+ if !strings.HasSuffix(checksum, actualChecksum) {
+ return fmt.Errorf("update artifact error checksum is invalid")
+ }
+ }
+
// save storage path to artifact
log.Debug("[artifact] merge chunks to artifact: %d, %s, old:%s", artifact.ID, storagePath, artifact.StoragePath)
// if artifact is already uploaded, delete the old file
diff --git a/routers/api/actions/artifacts_utils.go b/routers/api/actions/artifacts_utils.go
index 381e7eb16e..aaf89ef40e 100644
--- a/routers/api/actions/artifacts_utils.go
+++ b/routers/api/actions/artifacts_utils.go
@@ -43,6 +43,17 @@ func validateRunID(ctx *ArtifactContext) (*actions.ActionTask, int64, bool) {
return task, runID, true
}
+func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.ActionTask, int64, bool) {
+ task := ctx.ActionTask
+ runID, err := strconv.ParseInt(rawRunID, 10, 64)
+ if err != nil || task.Job.RunID != runID {
+ log.Error("Error runID not match")
+ ctx.Error(http.StatusBadRequest, "run-id does not match")
+ return nil, 0, false
+ }
+ return task, runID, true
+}
+
func validateArtifactHash(ctx *ArtifactContext, artifactName string) bool {
paramHash := ctx.Params("artifact_hash")
// use artifact name to create upload url
diff --git a/routers/api/actions/artifactsv4.go b/routers/api/actions/artifactsv4.go
new file mode 100644
index 0000000000..8300989c75
--- /dev/null
+++ b/routers/api/actions/artifactsv4.go
@@ -0,0 +1,512 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+// GitHub Actions Artifacts V4 API Simple Description
+//
+// 1. Upload artifact
+// 1.1. CreateArtifact
+// Post: /twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact
+// Request:
+// {
+// "workflow_run_backend_id": "21",
+// "workflow_job_run_backend_id": "49",
+// "name": "test",
+// "version": 4
+// }
+// Response:
+// {
+// "ok": true,
+// "signedUploadUrl": "http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75"
+// }
+// 1.2. Upload Zip Content to Blobstorage (unauthenticated request)
+// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=block
+// 1.3. Continue Upload Zip Content to Blobstorage (unauthenticated request), repeat until everything is uploaded
+// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=appendBlock
+// 1.4. Unknown xml payload to Blobstorage (unauthenticated request), ignored for now
+// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=blockList
+// 1.5. FinalizeArtifact
+// Post: /twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact
+// Request
+// {
+// "workflow_run_backend_id": "21",
+// "workflow_job_run_backend_id": "49",
+// "name": "test",
+// "size": "2097",
+// "hash": "sha256:b6325614d5649338b87215d9536b3c0477729b8638994c74cdefacb020a2cad4"
+// }
+// Response
+// {
+// "ok": true,
+// "artifactId": "4"
+// }
+// 2. Download artifact
+// 2.1. ListArtifacts and optionally filter by artifact exact name or id
+// Post: /twirp/github.actions.results.api.v1.ArtifactService/ListArtifacts
+// Request
+// {
+// "workflow_run_backend_id": "21",
+// "workflow_job_run_backend_id": "49",
+// "name_filter": "test"
+// }
+// Response
+// {
+// "artifacts": [
+// {
+// "workflowRunBackendId": "21",
+// "workflowJobRunBackendId": "49",
+// "databaseId": "4",
+// "name": "test",
+// "size": "2093",
+// "createdAt": "2024-01-23T00:13:28Z"
+// }
+// ]
+// }
+// 2.2. GetSignedArtifactURL get the URL to download the artifact zip file of a specific artifact
+// Post: /twirp/github.actions.results.api.v1.ArtifactService/GetSignedArtifactURL
+// Request
+// {
+// "workflow_run_backend_id": "21",
+// "workflow_job_run_backend_id": "49",
+// "name": "test"
+// }
+// Response
+// {
+// "signedUrl": "http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/DownloadArtifact?sig=wHzFOwpF-6220-5CA0CIRmAX9VbiTC2Mji89UOqo1E8=&expires=2024-01-23+21%3A51%3A56.872846295+%2B0100+CET&artifactName=test&taskID=76"
+// }
+// 2.3. Download Zip from Blobstorage (unauthenticated request)
+// GET: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/DownloadArtifact?sig=wHzFOwpF-6220-5CA0CIRmAX9VbiTC2Mji89UOqo1E8=&expires=2024-01-23+21%3A51%3A56.872846295+%2B0100+CET&artifactName=test&taskID=76
+
+import (
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/storage"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
+
+ "google.golang.org/protobuf/encoding/protojson"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ "google.golang.org/protobuf/types/known/timestamppb"
+)
+
+const (
+ ArtifactV4RouteBase = "/twirp/github.actions.results.api.v1.ArtifactService"
+ ArtifactV4ContentEncoding = "application/zip"
+)
+
+type artifactV4Routes struct {
+ prefix string
+ fs storage.ObjectStorage
+}
+
+func ArtifactV4Contexter() func(next http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ base, baseCleanUp := context.NewBaseContext(resp, req)
+ defer baseCleanUp()
+
+ ctx := &ArtifactContext{Base: base}
+ ctx.AppendContextValue(artifactContextKey, ctx)
+
+ next.ServeHTTP(ctx.Resp, ctx.Req)
+ })
+ }
+}
+
+func ArtifactsV4Routes(prefix string) *web.Route {
+ m := web.NewRoute()
+
+ r := artifactV4Routes{
+ prefix: prefix,
+ fs: storage.ActionsArtifacts,
+ }
+
+ m.Group("", func() {
+ m.Post("CreateArtifact", r.createArtifact)
+ m.Post("FinalizeArtifact", r.finalizeArtifact)
+ m.Post("ListArtifacts", r.listArtifacts)
+ m.Post("GetSignedArtifactURL", r.getSignedArtifactURL)
+ m.Post("DeleteArtifact", r.deleteArtifact)
+ }, ArtifactContexter())
+ m.Group("", func() {
+ m.Put("UploadArtifact", r.uploadArtifact)
+ m.Get("DownloadArtifact", r.downloadArtifact)
+ }, ArtifactV4Contexter())
+
+ return m
+}
+
+func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, taskID int64) []byte {
+ mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
+ mac.Write([]byte(endp))
+ mac.Write([]byte(expires))
+ mac.Write([]byte(artifactName))
+ mac.Write([]byte(fmt.Sprint(taskID)))
+ return mac.Sum(nil)
+}
+
+func (r artifactV4Routes) buildArtifactURL(endp, artifactName string, taskID int64) string {
+ expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST")
+ uploadURL := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(r.prefix, "/") +
+ "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID)
+ return uploadURL
+}
+
+func (r artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (*actions.ActionTask, string, bool) {
+ rawTaskID := ctx.Req.URL.Query().Get("taskID")
+ sig := ctx.Req.URL.Query().Get("sig")
+ expires := ctx.Req.URL.Query().Get("expires")
+ artifactName := ctx.Req.URL.Query().Get("artifactName")
+ dsig, _ := base64.URLEncoding.DecodeString(sig)
+ taskID, _ := strconv.ParseInt(rawTaskID, 10, 64)
+
+ expecedsig := r.buildSignature(endp, expires, artifactName, taskID)
+ if !hmac.Equal(dsig, expecedsig) {
+ log.Error("Error unauthorized")
+ ctx.Error(http.StatusUnauthorized, "Error unauthorized")
+ return nil, "", false
+ }
+ t, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", expires)
+ if err != nil || t.Before(time.Now()) {
+ log.Error("Error link expired")
+ ctx.Error(http.StatusUnauthorized, "Error link expired")
+ return nil, "", false
+ }
+ task, err := actions.GetTaskByID(ctx, taskID)
+ if err != nil {
+ log.Error("Error runner api getting task by ID: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error runner api getting task by ID")
+ return nil, "", false
+ }
+ if task.Status != actions.StatusRunning {
+ log.Error("Error runner api getting task: task is not running")
+ ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running")
+ return nil, "", false
+ }
+ if err := task.LoadJob(ctx); err != nil {
+ log.Error("Error runner api getting job: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error runner api getting job")
+ return nil, "", false
+ }
+ return task, artifactName, true
+}
+
+func (r *artifactV4Routes) getArtifactByName(ctx *ArtifactContext, runID int64, name string) (*actions.ActionArtifact, error) {
+ var art actions.ActionArtifact
+ has, err := db.GetEngine(ctx).Where("run_id = ? AND artifact_name = ? AND artifact_path = ? AND content_encoding = ?", runID, name, name+".zip", ArtifactV4ContentEncoding).Get(&art)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, util.ErrNotExist
+ }
+ return &art, nil
+}
+
+func (r *artifactV4Routes) parseProtbufBody(ctx *ArtifactContext, req protoreflect.ProtoMessage) bool {
+ body, err := io.ReadAll(ctx.Req.Body)
+ if err != nil {
+ log.Error("Error decode request body: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error decode request body")
+ return false
+ }
+ err = protojson.Unmarshal(body, req)
+ if err != nil {
+ log.Error("Error decode request body: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error decode request body")
+ return false
+ }
+ return true
+}
+
+func (r *artifactV4Routes) sendProtbufBody(ctx *ArtifactContext, req protoreflect.ProtoMessage) {
+ resp, err := protojson.Marshal(req)
+ if err != nil {
+ log.Error("Error encode response body: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error encode response body")
+ return
+ }
+ ctx.Resp.Header().Set("Content-Type", "application/json;charset=utf-8")
+ ctx.Resp.WriteHeader(http.StatusOK)
+ _, _ = ctx.Resp.Write(resp)
+}
+
+func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
+ var req CreateArtifactRequest
+
+ if ok := r.parseProtbufBody(ctx, &req); !ok {
+ return
+ }
+ _, _, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
+ if !ok {
+ return
+ }
+
+ artifactName := req.Name
+
+ rententionDays := setting.Actions.ArtifactRetentionDays
+ if req.ExpiresAt != nil {
+ rententionDays = int64(time.Until(req.ExpiresAt.AsTime()).Hours() / 24)
+ }
+ // create or get artifact with name and path
+ artifact, err := actions.CreateArtifact(ctx, ctx.ActionTask, artifactName, artifactName+".zip", rententionDays)
+ if err != nil {
+ log.Error("Error create or get artifact: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error create or get artifact")
+ return
+ }
+ artifact.ContentEncoding = ArtifactV4ContentEncoding
+ if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
+ log.Error("Error UpdateArtifactByID: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID")
+ return
+ }
+
+ respData := CreateArtifactResponse{
+ Ok: true,
+ SignedUploadUrl: r.buildArtifactURL("UploadArtifact", artifactName, ctx.ActionTask.ID),
+ }
+ r.sendProtbufBody(ctx, &respData)
+}
+
+func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
+ task, artifactName, ok := r.verifySignature(ctx, "UploadArtifact")
+ if !ok {
+ return
+ }
+
+ comp := ctx.Req.URL.Query().Get("comp")
+ switch comp {
+ case "block", "appendBlock":
+ // get artifact by name
+ artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName)
+ if err != nil {
+ log.Error("Error artifact not found: %v", err)
+ ctx.Error(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+
+ if comp == "block" {
+ artifact.FileSize = 0
+ artifact.FileCompressedSize = 0
+ }
+
+ _, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID)
+ if err != nil {
+ log.Error("Error runner api getting task: task is not running")
+ ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running")
+ return
+ }
+ artifact.FileCompressedSize += ctx.Req.ContentLength
+ artifact.FileSize += ctx.Req.ContentLength
+ if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
+ log.Error("Error UpdateArtifactByID: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID")
+ return
+ }
+ ctx.JSON(http.StatusCreated, "appended")
+ case "blocklist":
+ ctx.JSON(http.StatusCreated, "created")
+ }
+}
+
+func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
+ var req FinalizeArtifactRequest
+
+ if ok := r.parseProtbufBody(ctx, &req); !ok {
+ return
+ }
+ _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
+ if !ok {
+ return
+ }
+
+ // get artifact by name
+ artifact, err := r.getArtifactByName(ctx, runID, req.Name)
+ if err != nil {
+ log.Error("Error artifact not found: %v", err)
+ ctx.Error(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+ chunkMap, err := listChunksByRunID(r.fs, runID)
+ if err != nil {
+ log.Error("Error merge chunks: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error merge chunks")
+ return
+ }
+ chunks, ok := chunkMap[artifact.ID]
+ if !ok {
+ log.Error("Error merge chunks")
+ ctx.Error(http.StatusInternalServerError, "Error merge chunks")
+ return
+ }
+ checksum := ""
+ if req.Hash != nil {
+ checksum = req.Hash.Value
+ }
+ if err := mergeChunksForArtifact(ctx, chunks, r.fs, artifact, checksum); err != nil {
+ log.Error("Error merge chunks: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error merge chunks")
+ return
+ }
+
+ respData := FinalizeArtifactResponse{
+ Ok: true,
+ ArtifactId: artifact.ID,
+ }
+ r.sendProtbufBody(ctx, &respData)
+}
+
+func (r *artifactV4Routes) listArtifacts(ctx *ArtifactContext) {
+ var req ListArtifactsRequest
+
+ if ok := r.parseProtbufBody(ctx, &req); !ok {
+ return
+ }
+ _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
+ if !ok {
+ return
+ }
+
+ artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{RunID: runID})
+ if err != nil {
+ log.Error("Error getting artifacts: %v", err)
+ ctx.Error(http.StatusInternalServerError, err.Error())
+ return
+ }
+ if len(artifacts) == 0 {
+ log.Debug("[artifact] handleListArtifacts, no artifacts")
+ ctx.Error(http.StatusNotFound)
+ return
+ }
+
+ list := []*ListArtifactsResponse_MonolithArtifact{}
+
+ table := map[string]*ListArtifactsResponse_MonolithArtifact{}
+ for _, artifact := range artifacts {
+ if _, ok := table[artifact.ArtifactName]; ok || req.IdFilter != nil && artifact.ID != req.IdFilter.Value || req.NameFilter != nil && artifact.ArtifactName != req.NameFilter.Value || artifact.ArtifactName+".zip" != artifact.ArtifactPath || artifact.ContentEncoding != ArtifactV4ContentEncoding {
+ table[artifact.ArtifactName] = nil
+ continue
+ }
+
+ table[artifact.ArtifactName] = &ListArtifactsResponse_MonolithArtifact{
+ Name: artifact.ArtifactName,
+ CreatedAt: timestamppb.New(artifact.CreatedUnix.AsTime()),
+ DatabaseId: artifact.ID,
+ WorkflowRunBackendId: req.WorkflowRunBackendId,
+ WorkflowJobRunBackendId: req.WorkflowJobRunBackendId,
+ Size: artifact.FileSize,
+ }
+ }
+ for _, artifact := range table {
+ if artifact != nil {
+ list = append(list, artifact)
+ }
+ }
+
+ respData := ListArtifactsResponse{
+ Artifacts: list,
+ }
+ r.sendProtbufBody(ctx, &respData)
+}
+
+func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
+ var req GetSignedArtifactURLRequest
+
+ if ok := r.parseProtbufBody(ctx, &req); !ok {
+ return
+ }
+ _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
+ if !ok {
+ return
+ }
+
+ artifactName := req.Name
+
+ // get artifact by name
+ artifact, err := r.getArtifactByName(ctx, runID, artifactName)
+ if err != nil {
+ log.Error("Error artifact not found: %v", err)
+ ctx.Error(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+
+ respData := GetSignedArtifactURLResponse{}
+
+ if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect {
+ u, err := storage.ActionsArtifacts.URL(artifact.StoragePath, artifact.ArtifactPath)
+ if u != nil && err == nil {
+ respData.SignedUrl = u.String()
+ }
+ }
+ if respData.SignedUrl == "" {
+ respData.SignedUrl = r.buildArtifactURL("DownloadArtifact", artifactName, ctx.ActionTask.ID)
+ }
+ r.sendProtbufBody(ctx, &respData)
+}
+
+func (r *artifactV4Routes) downloadArtifact(ctx *ArtifactContext) {
+ task, artifactName, ok := r.verifySignature(ctx, "DownloadArtifact")
+ if !ok {
+ return
+ }
+
+ // get artifact by name
+ artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName)
+ if err != nil {
+ log.Error("Error artifact not found: %v", err)
+ ctx.Error(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+
+ file, _ := r.fs.Open(artifact.StoragePath)
+
+ _, _ = io.Copy(ctx.Resp, file)
+}
+
+func (r *artifactV4Routes) deleteArtifact(ctx *ArtifactContext) {
+ var req DeleteArtifactRequest
+
+ if ok := r.parseProtbufBody(ctx, &req); !ok {
+ return
+ }
+ _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId)
+ if !ok {
+ return
+ }
+
+ // get artifact by name
+ artifact, err := r.getArtifactByName(ctx, runID, req.Name)
+ if err != nil {
+ log.Error("Error artifact not found: %v", err)
+ ctx.Error(http.StatusNotFound, "Error artifact not found")
+ return
+ }
+
+ err = actions.SetArtifactNeedDelete(ctx, runID, req.Name)
+ if err != nil {
+ log.Error("Error deleting artifacts: %v", err)
+ ctx.Error(http.StatusInternalServerError, err.Error())
+ return
+ }
+
+ respData := DeleteArtifactResponse{
+ Ok: true,
+ ArtifactId: artifact.ID,
+ }
+ r.sendProtbufBody(ctx, &respData)
+}
diff --git a/routers/api/packages/cargo/cargo.go b/routers/api/packages/cargo/cargo.go
index d01a13d78f..140e532efd 100644
--- a/routers/api/packages/cargo/cargo.go
+++ b/routers/api/packages/cargo/cargo.go
@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
cargo_module "code.gitea.io/gitea/modules/packages/cargo"
"code.gitea.io/gitea/modules/setting"
@@ -110,7 +111,7 @@ func SearchPackages(ctx *context.Context) {
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeCargo,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: &paginator,
},
)
diff --git a/routers/api/packages/chef/chef.go b/routers/api/packages/chef/chef.go
index 720fce0a2a..b49f4e9d0a 100644
--- a/routers/api/packages/chef/chef.go
+++ b/routers/api/packages/chef/chef.go
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
chef_module "code.gitea.io/gitea/modules/packages/chef"
"code.gitea.io/gitea/modules/setting"
@@ -40,7 +41,7 @@ func PackagesUniverse(ctx *context.Context) {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeChef,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -85,7 +86,7 @@ func EnumeratePackages(ctx *context.Context) {
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeChef,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: db.NewAbsoluteListOptions(
ctx.FormInt("start"),
ctx.FormInt("items"),
diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go
index 346408d261..a045da40de 100644
--- a/routers/api/packages/composer/composer.go
+++ b/routers/api/packages/composer/composer.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
composer_module "code.gitea.io/gitea/modules/packages/composer"
"code.gitea.io/gitea/modules/setting"
@@ -66,7 +67,7 @@ func SearchPackages(ctx *context.Context) {
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeComposer,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: &paginator,
}
if ctx.FormTrim("type") != "" {
diff --git a/routers/api/packages/goproxy/goproxy.go b/routers/api/packages/goproxy/goproxy.go
index 9eb515d9a1..d658066bb4 100644
--- a/routers/api/packages/goproxy/goproxy.go
+++ b/routers/api/packages/goproxy/goproxy.go
@@ -12,6 +12,7 @@ import (
"time"
packages_model "code.gitea.io/gitea/models/packages"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
goproxy_module "code.gitea.io/gitea/modules/packages/goproxy"
"code.gitea.io/gitea/modules/util"
@@ -129,7 +130,7 @@ func resolvePackage(ctx *context.Context, ownerID int64, name, version string) (
Value: name,
ExactMatch: true,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Sort: packages_model.SortCreatedDesc,
})
if err != nil {
diff --git a/routers/api/packages/helm/helm.go b/routers/api/packages/helm/helm.go
index e7a346d9ca..efdb83ec0e 100644
--- a/routers/api/packages/helm/helm.go
+++ b/routers/api/packages/helm/helm.go
@@ -15,6 +15,7 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
helm_module "code.gitea.io/gitea/modules/packages/helm"
"code.gitea.io/gitea/modules/setting"
@@ -42,7 +43,7 @@ func Index(ctx *context.Context) {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeHelm,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -110,7 +111,7 @@ func DownloadPackageFile(ctx *context.Context) {
Value: ctx.Params("package"),
},
HasFileWithName: filename,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
diff --git a/routers/api/packages/npm/api.go b/routers/api/packages/npm/api.go
index 8470874884..f8e839c424 100644
--- a/routers/api/packages/npm/api.go
+++ b/routers/api/packages/npm/api.go
@@ -12,6 +12,7 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
npm_module "code.gitea.io/gitea/modules/packages/npm"
+ "code.gitea.io/gitea/modules/setting"
)
func createPackageMetadataResponse(registryURL string, pds []*packages_model.PackageDescriptor) *npm_module.PackageMetadata {
@@ -98,7 +99,7 @@ func createPackageSearchResponse(pds []*packages_model.PackageDescriptor, total
Maintainers: []npm_module.User{}, // npm cli needs this field
Keywords: metadata.Keywords,
Links: &npm_module.PackageSearchPackageLinks{
- Registry: pd.FullWebLink(),
+ Registry: setting.AppURL + "api/packages/" + pd.Owner.Name + "/npm",
Homepage: metadata.ProjectURL,
},
},
diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go
index 72b4305928..84acfffae2 100644
--- a/routers/api/packages/npm/npm.go
+++ b/routers/api/packages/npm/npm.go
@@ -17,6 +17,7 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
npm_module "code.gitea.io/gitea/modules/packages/npm"
"code.gitea.io/gitea/modules/setting"
@@ -120,7 +121,7 @@ func DownloadPackageFileByName(ctx *context.Context) {
Value: packageNameFromParams(ctx),
},
HasFileWithName: filename,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -395,7 +396,7 @@ func setPackageTag(ctx std_ctx.Context, tag string, pv *packages_model.PackageVe
Properties: map[string]string{
npm_module.TagProperty: tag,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
return err
@@ -431,7 +432,7 @@ func PackageSearch(ctx *context.Context) {
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNpm,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Name: packages_model.SearchValue{
ExactMatch: false,
Value: ctx.FormTrim("text"),
diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go
index a0273aad5a..c28bc6c9d9 100644
--- a/routers/api/packages/nuget/nuget.go
+++ b/routers/api/packages/nuget/nuget.go
@@ -18,6 +18,7 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
nuget_model "code.gitea.io/gitea/models/packages/nuget"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
"code.gitea.io/gitea/modules/setting"
@@ -122,7 +123,7 @@ func SearchServiceV2(ctx *context.Context) {
Name: packages_model.SearchValue{
Value: getSearchTerm(ctx),
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: paginator,
})
if err != nil {
@@ -172,7 +173,7 @@ func SearchServiceV2Count(ctx *context.Context) {
Name: packages_model.SearchValue{
Value: getSearchTerm(ctx),
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -187,7 +188,7 @@ func SearchServiceV3(ctx *context.Context) {
pvs, count, err := nuget_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: db.NewAbsoluteListOptions(
ctx.FormInt("skip"),
ctx.FormInt("take"),
@@ -313,7 +314,7 @@ func EnumeratePackageVersionsV2(ctx *context.Context) {
ExactMatch: true,
Value: packageName,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: paginator,
})
if err != nil {
@@ -358,7 +359,7 @@ func EnumeratePackageVersionsV2Count(ctx *context.Context) {
ExactMatch: true,
Value: strings.Trim(ctx.FormTrim("id"), "'"),
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go
index 5d05b6d524..d2fbcd01f0 100644
--- a/routers/api/packages/rubygems/rubygems.go
+++ b/routers/api/packages/rubygems/rubygems.go
@@ -13,6 +13,7 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
rubygems_module "code.gitea.io/gitea/modules/packages/rubygems"
"code.gitea.io/gitea/modules/util"
@@ -43,7 +44,7 @@ func EnumeratePackagesLatest(ctx *context.Context) {
pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeRubyGems,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -304,7 +305,7 @@ func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_m
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeRubyGems,
HasFileWithName: filename,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
return pvs, err
}
diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go
index 1fc8baeaac..a9da3ea9c2 100644
--- a/routers/api/packages/swift/swift.go
+++ b/routers/api/packages/swift/swift.go
@@ -15,6 +15,7 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
swift_module "code.gitea.io/gitea/modules/packages/swift"
"code.gitea.io/gitea/modules/setting"
@@ -433,7 +434,7 @@ func LookupPackageIdentifiers(ctx *context.Context) {
Properties: map[string]string{
swift_module.PropertyRepositoryURL: url,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
diff --git a/routers/api/v1/admin/hooks.go b/routers/api/v1/admin/hooks.go
index 2217d002a0..4c168b55bf 100644
--- a/routers/api/v1/admin/hooks.go
+++ b/routers/api/v1/admin/hooks.go
@@ -8,6 +8,7 @@ import (
"net/http"
"code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
@@ -37,7 +38,7 @@ func ListHooks(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/HookList"
- sysHooks, err := webhook.GetSystemWebhooks(ctx, util.OptionalBoolNone)
+ sysHooks, err := webhook.GetSystemWebhooks(ctx, optional.None[bool]())
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetSystemWebhooks", err)
return
diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go
index 3be31b13ae..b38aa13167 100644
--- a/routers/api/v1/packages/package.go
+++ b/routers/api/v1/packages/package.go
@@ -7,8 +7,8 @@ import (
"net/http"
"code.gitea.io/gitea/models/packages"
+ "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
@@ -60,7 +60,7 @@ func ListPackages(ctx *context.APIContext) {
OwnerID: ctx.Package.Owner.ID,
Type: packages.Type(packageType),
Name: packages.SearchValue{Value: query},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: &listOptions,
})
if err != nil {
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index 227e0e725c..1b2ecd474b 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -23,7 +23,6 @@ import (
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
@@ -123,14 +122,14 @@ func SearchIssues(ctx *context.APIContext) {
return
}
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch ctx.FormString("state") {
case "closed":
- isClosed = util.OptionalBoolTrue
+ isClosed = optional.Some(true)
case "all":
- isClosed = util.OptionalBoolNone
+ isClosed = optional.None[bool]()
default:
- isClosed = util.OptionalBoolFalse
+ isClosed = optional.Some(false)
}
var (
@@ -205,14 +204,14 @@ func SearchIssues(ctx *context.APIContext) {
keyword = ""
}
- var isPull util.OptionalBool
+ var isPull optional.Option[bool]
switch ctx.FormString("type") {
case "pulls":
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
case "issues":
- isPull = util.OptionalBoolFalse
+ isPull = optional.Some(false)
default:
- isPull = util.OptionalBoolNone
+ isPull = optional.None[bool]()
}
var includedAnyLabels []int64
@@ -397,14 +396,14 @@ func ListIssues(ctx *context.APIContext) {
return
}
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch ctx.FormString("state") {
case "closed":
- isClosed = util.OptionalBoolTrue
+ isClosed = optional.Some(true)
case "all":
- isClosed = util.OptionalBoolNone
+ isClosed = optional.None[bool]()
default:
- isClosed = util.OptionalBoolFalse
+ isClosed = optional.Some(false)
}
keyword := ctx.FormTrim("q")
@@ -453,31 +452,29 @@ func ListIssues(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
- var isPull util.OptionalBool
+ isPull := optional.None[bool]()
switch ctx.FormString("type") {
case "pulls":
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
case "issues":
- isPull = util.OptionalBoolFalse
- default:
- isPull = util.OptionalBoolNone
+ isPull = optional.Some(false)
}
- if isPull != util.OptionalBoolNone && !ctx.Repo.CanReadIssuesOrPulls(isPull.IsTrue()) {
+ if isPull.Has() && !ctx.Repo.CanReadIssuesOrPulls(isPull.Value()) {
ctx.NotFound()
return
}
- if isPull == util.OptionalBoolNone {
+ if !isPull.Has() {
canReadIssues := ctx.Repo.CanRead(unit.TypeIssues)
canReadPulls := ctx.Repo.CanRead(unit.TypePullRequests)
if !canReadIssues && !canReadPulls {
ctx.NotFound()
return
} else if !canReadIssues {
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
} else if !canReadPulls {
- isPull = util.OptionalBoolFalse
+ isPull = optional.Some(false)
}
}
diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go
index 763419b7a2..6209e960af 100644
--- a/routers/api/v1/repo/issue_comment.go
+++ b/routers/api/v1/repo/issue_comment.go
@@ -14,8 +14,8 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
@@ -278,15 +278,15 @@ func ListRepoIssueComments(ctx *context.APIContext) {
return
}
- var isPull util.OptionalBool
+ var isPull optional.Option[bool]
canReadIssue := ctx.Repo.CanRead(unit.TypeIssues)
canReadPull := ctx.Repo.CanRead(unit.TypePullRequests)
if canReadIssue && canReadPull {
- isPull = util.OptionalBoolNone
+ isPull = optional.None[bool]()
} else if canReadIssue {
- isPull = util.OptionalBoolFalse
+ isPull = optional.Some(false)
} else if canReadPull {
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
} else {
ctx.NotFound()
return
diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go
index d4c828fe8b..b9534016e4 100644
--- a/routers/api/v1/repo/milestone.go
+++ b/routers/api/v1/repo/milestone.go
@@ -11,9 +11,9 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
@@ -61,10 +61,10 @@ func ListMilestones(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
state := api.StateType(ctx.FormString("state"))
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch state {
case api.StateClosed, api.StateOpen:
- isClosed = util.OptionalBoolOf(state == api.StateClosed)
+ isClosed = optional.Some(state == api.StateClosed)
}
milestones, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
diff --git a/routers/api/v1/repo/release_attachment.go b/routers/api/v1/repo/release_attachment.go
index a29bce66a4..59fd83e3a2 100644
--- a/routers/api/v1/repo/release_attachment.go
+++ b/routers/api/v1/repo/release_attachment.go
@@ -4,7 +4,9 @@
package repo
import (
+ "io"
"net/http"
+ "strings"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/log"
@@ -154,6 +156,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
// - application/json
// consumes:
// - multipart/form-data
+ // - application/octet-stream
// parameters:
// - name: owner
// in: path
@@ -180,7 +183,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
// in: formData
// description: attachment to upload
// type: file
- // required: true
+ // required: false
// responses:
// "201":
// "$ref": "#/responses/Attachment"
@@ -202,20 +205,36 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
}
// Get uploaded file from request
- file, header, err := ctx.Req.FormFile("attachment")
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetFile", err)
- return
- }
- defer file.Close()
+ var content io.ReadCloser
+ var filename string
+ var size int64 = -1
- filename := header.Filename
- if query := ctx.FormString("name"); query != "" {
- filename = query
+ if strings.HasPrefix(strings.ToLower(ctx.Req.Header.Get("Content-Type")), "multipart/form-data") {
+ file, header, err := ctx.Req.FormFile("attachment")
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetFile", err)
+ return
+ }
+ defer file.Close()
+
+ content = file
+ size = header.Size
+ filename = header.Filename
+ if name := ctx.FormString("name"); name != "" {
+ filename = name
+ }
+ } else {
+ content = ctx.Req.Body
+ filename = ctx.FormString("name")
+ }
+
+ if filename == "" {
+ ctx.Error(http.StatusBadRequest, "CreateReleaseAttachment", "Could not determine name of attachment.")
+ return
}
// Create a new attachment and save the file
- attach, err := attachment.UploadAttachment(ctx, file, setting.Repository.Release.AllowedTypes, header.Size, &repo_model.Attachment{
+ attach, err := attachment.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, size, &repo_model.Attachment{
Name: filename,
UploaderID: ctx.Doer.ID,
RepoID: ctx.Repo.Repository.ID,
diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go
index b8438cd2aa..dcf5da0b2e 100644
--- a/routers/api/v1/user/gpg_key.go
+++ b/routers/api/v1/user/gpg_key.go
@@ -10,6 +10,7 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
@@ -132,6 +133,11 @@ func GetGPGKey(ctx *context.APIContext) {
// CreateUserGPGKey creates new GPG key to given user by ID.
func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
+ return
+ }
+
token := asymkey_model.VerificationToken(ctx.Doer, 1)
lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
@@ -268,6 +274,11 @@ func DeleteGPGKey(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
+ return
+ }
+
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.ParamsInt64(":id")); err != nil {
if asymkey_model.IsErrGPGKeyAccessDenied(err) {
ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
diff --git a/routers/init.go b/routers/init.go
index e0a7150ba3..1dedbebeb5 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -198,6 +198,8 @@ func NormalRoutes() *web.Route {
// TODO: this prefix should be generated with a token string with runner ?
prefix = "/api/actions_pipeline"
r.Mount(prefix, actions_router.ArtifactsRoutes(prefix))
+ prefix = actions_router.ArtifactV4RouteBase
+ r.Mount(prefix, actions_router.ArtifactsV4Routes(prefix))
}
return r
diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go
index 8d4c66fdb2..8d59fbb858 100644
--- a/routers/web/admin/hooks.go
+++ b/routers/web/admin/hooks.go
@@ -8,8 +8,8 @@ import (
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
)
@@ -35,7 +35,7 @@ func DefaultOrSystemWebhooks(ctx *context.Context) {
sys["Title"] = ctx.Tr("admin.systemhooks")
sys["Description"] = ctx.Tr("admin.systemhooks.desc")
- sys["Webhooks"], err = webhook.GetSystemWebhooks(ctx, util.OptionalBoolNone)
+ sys["Webhooks"], err = webhook.GetSystemWebhooks(ctx, optional.None[bool]())
sys["BaseLink"] = setting.AppSubURL + "/admin/hooks"
sys["BaseLinkNew"] = setting.AppSubURL + "/admin/system-hooks"
if err != nil {
diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go
index 7c16b69a85..39f064a1be 100644
--- a/routers/web/admin/packages.go
+++ b/routers/web/admin/packages.go
@@ -11,8 +11,8 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup"
@@ -36,7 +36,7 @@ func Packages(ctx *context.Context) {
Type: packages_model.Type(packageType),
Name: packages_model.SearchValue{Value: query},
Sort: sort,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Paginator: &db.ListOptions{
PageSize: setting.UI.PackagesPagingNum,
Page: page,
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index bbdbc820d7..a34e0d0f0d 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -96,7 +96,7 @@ func NewUser(ctx *context.Context) {
ctx.Data["login_type"] = "0-0"
sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
})
if err != nil {
ctx.ServerError("auth.Sources", err)
@@ -117,7 +117,7 @@ func NewUserPost(ctx *context.Context) {
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
})
if err != nil {
ctx.ServerError("auth.Sources", err)
@@ -439,6 +439,7 @@ func EditUserPost(ctx *context.Context) {
AllowCreateOrganization: optional.Some(form.AllowCreateOrganization),
IsRestricted: optional.Some(form.Restricted),
Visibility: optional.Some(form.Visibility),
+ Language: optional.Some(form.Language),
}
if err := user_service.UpdateUser(ctx, u, opts); err != nil {
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index 7704a110a6..04e410543d 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -163,7 +163,7 @@ func SignIn(ctx *context.Context) {
return
}
- oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
+ oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
ctx.ServerError("UserSignIn", err)
return
@@ -186,7 +186,7 @@ func SignIn(ctx *context.Context) {
func SignInPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("sign_in")
- oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
+ oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
ctx.ServerError("UserSignIn", err)
return
@@ -410,7 +410,7 @@ func SignUp(ctx *context.Context) {
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
- oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
+ oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
ctx.ServerError("UserSignUp", err)
return
@@ -439,7 +439,7 @@ func SignUpPost(ctx *context.Context) {
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
- oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
+ oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
ctx.ServerError("UserSignUp", err)
return
diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go
index 82cd91997a..ad8bb90d9e 100644
--- a/routers/web/org/projects.go
+++ b/routers/web/org/projects.go
@@ -18,9 +18,9 @@ import (
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
@@ -67,7 +67,7 @@ func Projects(ctx *context.Context) {
PageSize: setting.UI.IssuePagingNum,
},
OwnerID: ctx.ContextUser.ID,
- IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsClosed: optional.Some(isShowClosed),
OrderBy: project_model.GetSearchOrderByBySortType(sortType),
Type: projectType,
Title: keyword,
@@ -79,7 +79,7 @@ func Projects(ctx *context.Context) {
opTotal, err := db.Count[project_model.Project](ctx, project_model.SearchOptions{
OwnerID: ctx.ContextUser.ID,
- IsClosed: util.OptionalBoolOf(!isShowClosed),
+ IsClosed: optional.Some(!isShowClosed),
Type: projectType,
})
if err != nil {
@@ -388,7 +388,7 @@ func ViewProject(ctx *context.Context) {
if len(referencedIDs) > 0 {
if linkedPrs, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
IssueIDs: referencedIDs,
- IsPull: util.OptionalBoolTrue,
+ IsPull: optional.Some(true),
}); err == nil {
linkedPrsMap[issue.ID] = linkedPrs
}
diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go
index ef9d50081d..c6b9900e0d 100644
--- a/routers/web/repo/actions/actions.go
+++ b/routers/web/repo/actions/actions.go
@@ -21,7 +21,6 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
@@ -87,7 +86,7 @@ func List(ctx *context.Context) {
// Get all runner labels
runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{
RepoID: ctx.Repo.Repository.ID,
- IsOnline: util.OptionalBoolTrue,
+ IsOnline: optional.Some(true),
WithAvailable: true,
})
if err != nil {
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index 9b26db7225..ed5b6b554f 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
@@ -611,6 +612,28 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(artifactName), artifactName))
+ // Artifacts using the v4 backend are stored as a single combined zip file per artifact on the backend
+ // The v4 backend enshures ContentEncoding is set to "application/zip", which is not the case for the old backend
+ if len(artifacts) == 1 && artifacts[0].ArtifactName+".zip" == artifacts[0].ArtifactPath && artifacts[0].ContentEncoding == "application/zip" {
+ art := artifacts[0]
+ if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect {
+ u, err := storage.ActionsArtifacts.URL(art.StoragePath, art.ArtifactPath)
+ if u != nil && err == nil {
+ ctx.Redirect(u.String())
+ return
+ }
+ }
+ f, err := storage.ActionsArtifacts.Open(art.StoragePath)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, err.Error())
+ return
+ }
+ _, _ = io.Copy(ctx.Resp, f)
+ return
+ }
+
+ // Artifacts using the v1-v3 backend are stored as multiple individual files per artifact on the backend
+ // Those need to be zipped for download
writer := zip.NewWriter(ctx.Resp)
defer writer.Close()
for _, art := range artifacts {
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index 05f06a3ceb..ae51f0596b 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -53,7 +54,7 @@ func Branches(ctx *context.Context) {
kw := ctx.FormString("q")
- defaultBranch, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, util.OptionalBoolNone, kw, page, pageSize)
+ defaultBranch, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, optional.None[bool](), kw, page, pageSize)
if err != nil {
ctx.ServerError("LoadBranches", err)
return
@@ -231,7 +232,7 @@ func CreateBranch(ctx *context.Context) {
if len(e.Message) == 0 {
ctx.Flash.Error(ctx.Tr("repo.editor.push_rejected_no_message"))
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.push_rejected"),
"Summary": ctx.Tr("repo.editor.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(e.Message),
diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go
index 8f3d9612ec..6146ce4ce4 100644
--- a/routers/web/repo/editor.go
+++ b/routers/web/repo/editor.go
@@ -341,7 +341,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
if len(errPushRej.Message) == 0 {
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplEditFile, &form)
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.push_rejected"),
"Summary": ctx.Tr("repo.editor.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(errPushRej.Message),
@@ -353,7 +353,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
ctx.RenderWithErr(flashError, tplEditFile, &form)
}
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.fail_to_update_file", form.TreePath),
"Summary": ctx.Tr("repo.editor.fail_to_update_file_summary"),
"Details": utils.SanitizeFlashErrorString(err.Error()),
@@ -542,7 +542,7 @@ func DeleteFilePost(ctx *context.Context) {
if len(errPushRej.Message) == 0 {
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplDeleteFile, &form)
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.push_rejected"),
"Summary": ctx.Tr("repo.editor.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(errPushRej.Message),
@@ -742,7 +742,7 @@ func UploadFilePost(ctx *context.Context) {
if len(errPushRej.Message) == 0 {
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplUploadFile, &form)
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.push_rejected"),
"Summary": ctx.Tr("repo.editor.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(errPushRej.Message),
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index ebaa955ac8..1abd5e2ba5 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -9,6 +9,7 @@ import (
stdCtx "context"
"errors"
"fmt"
+ "html/template"
"math/big"
"net/http"
"net/url"
@@ -139,7 +140,7 @@ func MustAllowPulls(ctx *context.Context) {
}
}
-func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption util.OptionalBool) {
+func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption optional.Option[bool]) {
var err error
viewType := ctx.FormString("type")
sortType := ctx.FormString("sort")
@@ -240,18 +241,18 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
}
}
- var isShowClosed util.OptionalBool
+ var isShowClosed optional.Option[bool]
switch ctx.FormString("state") {
case "closed":
- isShowClosed = util.OptionalBoolTrue
+ isShowClosed = optional.Some(true)
case "all":
- isShowClosed = util.OptionalBoolNone
+ isShowClosed = optional.None[bool]()
default:
- isShowClosed = util.OptionalBoolFalse
+ isShowClosed = optional.Some(false)
}
// if there are closed issues and no open issues, default to showing all issues
if len(ctx.FormString("state")) == 0 && issueStats.OpenCount == 0 && issueStats.ClosedCount != 0 {
- isShowClosed = util.OptionalBoolNone
+ isShowClosed = optional.None[bool]()
}
if repo.IsTimetrackerEnabled(ctx) {
@@ -271,10 +272,10 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
}
var total int
- switch isShowClosed {
- case util.OptionalBoolTrue:
+ switch {
+ case isShowClosed.Value():
total = int(issueStats.ClosedCount)
- case util.OptionalBoolNone:
+ case !isShowClosed.Has():
total = int(issueStats.OpenCount + issueStats.ClosedCount)
default:
total = int(issueStats.OpenCount)
@@ -430,7 +431,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
return
}
- pinned, err := issues_model.GetPinnedIssues(ctx, repo.ID, isPullOption.IsTrue())
+ pinned, err := issues_model.GetPinnedIssues(ctx, repo.ID, isPullOption.Value())
if err != nil {
ctx.ServerError("GetPinnedIssues", err)
return
@@ -460,10 +461,10 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
ctx.Data["AssigneeID"] = assigneeID
ctx.Data["PosterID"] = posterID
ctx.Data["Keyword"] = keyword
- switch isShowClosed {
- case util.OptionalBoolTrue:
+ switch {
+ case isShowClosed.Value():
ctx.Data["State"] = "closed"
- case util.OptionalBoolNone:
+ case !isShowClosed.Has():
ctx.Data["State"] = "all"
default:
ctx.Data["State"] = "open"
@@ -512,7 +513,7 @@ func Issues(ctx *context.Context) {
ctx.Data["NewIssueChooseTemplate"] = issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo)
}
- issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), util.OptionalBoolOf(isPullList))
+ issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), optional.Some(isPullList))
if ctx.Written() {
return
}
@@ -554,7 +555,7 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R
var err error
ctx.Data["OpenMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
RepoID: repo.ID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
})
if err != nil {
ctx.ServerError("GetMilestones", err)
@@ -562,7 +563,7 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R
}
ctx.Data["ClosedMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
RepoID: repo.ID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
if err != nil {
ctx.ServerError("GetMilestones", err)
@@ -590,7 +591,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
projects, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
RepoID: repo.ID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
Type: project_model.TypeRepository,
})
if err != nil {
@@ -600,7 +601,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
projects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
OwnerID: repo.OwnerID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
Type: repoOwnerType,
})
if err != nil {
@@ -613,7 +614,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
projects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
RepoID: repo.ID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
Type: project_model.TypeRepository,
})
if err != nil {
@@ -623,7 +624,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
projects2, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
ListOptions: db.ListOptionsAll,
OwnerID: repo.OwnerID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
Type: repoOwnerType,
})
if err != nil {
@@ -1016,7 +1017,7 @@ func NewIssue(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplIssueNew)
}
-func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) string {
+func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) template.HTML {
var files []string
for k := range errs {
files = append(files, k)
@@ -1028,14 +1029,14 @@ func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) string
lines = append(lines, fmt.Sprintf("%s: %v", file, errs[file]))
}
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.issues.choose.ignore_invalid_templates"),
"Summary": ctx.Tr("repo.issues.choose.invalid_templates", len(errs)),
"Details": utils.SanitizeFlashErrorString(strings.Join(lines, "\n")),
})
if err != nil {
log.Debug("render flash error: %v", err)
- flashError = ctx.Locale.TrString("repo.issues.choose.ignore_invalid_templates")
+ flashError = ctx.Locale.Tr("repo.issues.choose.ignore_invalid_templates")
}
return flashError
}
@@ -2501,14 +2502,14 @@ func SearchIssues(ctx *context.Context) {
return
}
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch ctx.FormString("state") {
case "closed":
- isClosed = util.OptionalBoolTrue
+ isClosed = optional.Some(true)
case "all":
- isClosed = util.OptionalBoolNone
+ isClosed = optional.None[bool]()
default:
- isClosed = util.OptionalBoolFalse
+ isClosed = optional.Some(false)
}
var (
@@ -2583,14 +2584,12 @@ func SearchIssues(ctx *context.Context) {
keyword = ""
}
- var isPull util.OptionalBool
+ isPull := optional.None[bool]()
switch ctx.FormString("type") {
case "pulls":
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
case "issues":
- isPull = util.OptionalBoolFalse
- default:
- isPull = util.OptionalBoolNone
+ isPull = optional.Some(false)
}
var includedAnyLabels []int64
@@ -2725,14 +2724,14 @@ func ListIssues(ctx *context.Context) {
return
}
- var isClosed util.OptionalBool
+ var isClosed optional.Option[bool]
switch ctx.FormString("state") {
case "closed":
- isClosed = util.OptionalBoolTrue
+ isClosed = optional.Some(true)
case "all":
- isClosed = util.OptionalBoolNone
+ isClosed = optional.None[bool]()
default:
- isClosed = util.OptionalBoolFalse
+ isClosed = optional.Some(false)
}
keyword := ctx.FormTrim("q")
@@ -2784,14 +2783,12 @@ func ListIssues(ctx *context.Context) {
projectID = &v
}
- var isPull util.OptionalBool
+ isPull := optional.None[bool]()
switch ctx.FormString("type") {
case "pulls":
- isPull = util.OptionalBoolTrue
+ isPull = optional.Some(true)
case "issues":
- isPull = util.OptionalBoolFalse
- default:
- isPull = util.OptionalBoolNone
+ isPull = optional.Some(false)
}
// FIXME: we should be more efficient here
@@ -3296,7 +3293,7 @@ func ChangeIssueReaction(ctx *context.Context) {
return
}
- html, err := ctx.RenderToString(tplReactions, map[string]any{
+ html, err := ctx.RenderToHTML(tplReactions, map[string]any{
"ctxData": ctx.Data,
"ActionURL": fmt.Sprintf("%s/issues/%d/reactions", ctx.Repo.RepoLink, issue.Index),
"Reactions": issue.Reactions.GroupByType(),
@@ -3403,7 +3400,7 @@ func ChangeCommentReaction(ctx *context.Context) {
return
}
- html, err := ctx.RenderToString(tplReactions, map[string]any{
+ html, err := ctx.RenderToHTML(tplReactions, map[string]any{
"ctxData": ctx.Data,
"ActionURL": fmt.Sprintf("%s/comments/%d/reactions", ctx.Repo.RepoLink, comment.ID),
"Reactions": comment.Reactions.GroupByType(),
@@ -3546,8 +3543,8 @@ func updateAttachments(ctx *context.Context, item any, files []string) error {
return err
}
-func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment, content string) string {
- attachHTML, err := ctx.RenderToString(tplAttachment, map[string]any{
+func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment, content string) template.HTML {
+ attachHTML, err := ctx.RenderToHTML(tplAttachment, map[string]any{
"ctxData": ctx.Data,
"Attachments": attachments,
"Content": content,
diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go
index fce0eccc7b..1ec497658f 100644
--- a/routers/web/repo/issue_content_history.go
+++ b/routers/web/repo/issue_content_history.go
@@ -94,7 +94,7 @@ func canSoftDeleteContentHistory(ctx *context.Context, issue *issues_model.Issue
// CanWrite means the doer can manage the issue/PR list
if ctx.Repo.IsOwner() || ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
canSoftDelete = true
- } else {
+ } else if ctx.Doer != nil {
// for read-only users, they could still post issues or comments,
// they should be able to delete the history related to their own issue/comment, a case is:
// 1. the user posts some sensitive data
@@ -186,6 +186,10 @@ func SoftDeleteContentHistory(ctx *context.Context) {
if ctx.Written() {
return
}
+ if ctx.Doer == nil {
+ ctx.NotFound("Require SignIn", nil)
+ return
+ }
commentID := ctx.FormInt64("comment_id")
historyID := ctx.FormInt64("history_id")
diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go
index 49ac94aaf1..c41b844ce4 100644
--- a/routers/web/repo/milestone.go
+++ b/routers/web/repo/milestone.go
@@ -14,9 +14,9 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
@@ -51,7 +51,7 @@ func Milestones(ctx *context.Context) {
PageSize: setting.UI.IssuePagingNum,
},
RepoID: ctx.Repo.Repository.ID,
- IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsClosed: optional.Some(isShowClosed),
SortType: sortType,
Name: keyword,
})
@@ -292,7 +292,7 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
ctx.Data["Title"] = milestone.Name
ctx.Data["Milestone"] = milestone
- issues(ctx, milestoneID, projectID, util.OptionalBoolNone)
+ issues(ctx, milestoneID, projectID, optional.None[bool]())
ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
ctx.Data["NewIssueChooseTemplate"] = len(ret.IssueTemplates) > 0
diff --git a/routers/web/repo/packages.go b/routers/web/repo/packages.go
index 6ed5909dcf..11874ab0d0 100644
--- a/routers/web/repo/packages.go
+++ b/routers/web/repo/packages.go
@@ -10,8 +10,8 @@ import (
"code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
)
@@ -37,7 +37,7 @@ func Packages(ctx *context.Context) {
RepoID: ctx.Repo.Repository.ID,
Type: packages.Type(packageType),
Name: packages.SearchValue{Value: query},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
ctx.ServerError("SearchLatestVersions", err)
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 1f9ee727c3..4c171defbd 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -20,8 +20,8 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
@@ -78,7 +78,7 @@ func Projects(ctx *context.Context) {
Page: page,
},
RepoID: repo.ID,
- IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsClosed: optional.Some(isShowClosed),
OrderBy: project_model.GetSearchOrderByBySortType(sortType),
Type: project_model.TypeRepository,
Title: keyword,
@@ -349,7 +349,7 @@ func ViewProject(ctx *context.Context) {
if len(referencedIDs) > 0 {
if linkedPrs, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
IssueIDs: referencedIDs,
- IsPull: util.OptionalBoolTrue,
+ IsPull: optional.Some(true),
}); err == nil {
linkedPrsMap[issue.ID] = linkedPrs
}
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index b1521a2112..bf52d76e95 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -112,7 +112,7 @@ func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository {
}
func getForkRepository(ctx *context.Context) *repo_model.Repository {
- forkRepo := getRepository(ctx, ctx.ParamsInt64(":repoid"))
+ forkRepo := ctx.Repo.Repository
if ctx.Written() {
return nil
}
@@ -1129,7 +1129,7 @@ func UpdatePullRequest(ctx *context.Context) {
if err = pull_service.Update(ctx, issue.PullRequest, ctx.Doer, message, rebase); err != nil {
if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts)
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.merge_conflict"),
"Summary": ctx.Tr("repo.pulls.merge_conflict_summary"),
"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "
" + utils.SanitizeFlashErrorString(conflictError.StdOut),
@@ -1143,7 +1143,7 @@ func UpdatePullRequest(ctx *context.Context) {
return
} else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts)
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
"Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "
" + utils.SanitizeFlashErrorString(conflictError.StdOut),
@@ -1275,7 +1275,7 @@ func MergePullRequest(ctx *context.Context) {
ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
} else if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts)
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.merge_conflict"),
"Summary": ctx.Tr("repo.editor.merge_conflict_summary"),
"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "
" + utils.SanitizeFlashErrorString(conflictError.StdOut),
@@ -1288,7 +1288,7 @@ func MergePullRequest(ctx *context.Context) {
ctx.JSONRedirect(issue.Link())
} else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts)
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
"Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "
" + utils.SanitizeFlashErrorString(conflictError.StdOut),
@@ -1318,7 +1318,7 @@ func MergePullRequest(ctx *context.Context) {
if len(message) == 0 {
ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message"))
} else {
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.push_rejected"),
"Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
@@ -1491,7 +1491,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
ctx.JSONError(ctx.Tr("repo.pulls.push_rejected_no_message"))
return
}
- flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.push_rejected"),
"Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
"Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
@@ -1500,8 +1500,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
ctx.ServerError("CompareAndPullRequest.HTMLString", err)
return
}
- ctx.Flash.Error(flashError)
- ctx.JSONRedirect(pullIssue.Link()) // FIXME: it's unfriendly, and will make the content lost
+ ctx.JSONError(flashError)
return
}
ctx.ServerError("NewPullRequest", err)
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index c6d8c45af1..dbc190928f 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -185,6 +185,11 @@ func Releases(ctx *context.Context) {
ctx.ServerError("getReleaseInfos", err)
return
}
+ for _, rel := range releases {
+ if rel.Release.IsTag && rel.Release.Title == "" {
+ rel.Release.Title = rel.Release.TagName
+ }
+ }
ctx.Data["Releases"] = releases
@@ -283,6 +288,7 @@ func SingleRelease(ctx *context.Context) {
TagNames: []string{ctx.Params("*")},
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
IncludeDrafts: writeAccess,
+ IncludeTags: true,
})
if err != nil {
ctx.ServerError("getReleaseInfos", err)
@@ -294,6 +300,9 @@ func SingleRelease(ctx *context.Context) {
}
release := releases[0].Release
+ if release.IsTag && release.Title == "" {
+ release.Title = release.TagName
+ }
ctx.Data["PageIsSingleTag"] = release.IsTag
if release.IsTag {
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index e89739e2fb..4df10fbea1 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -35,7 +35,6 @@ import (
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/lfs"
@@ -859,25 +858,18 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
defer cancel()
}
- selected := make(container.Set[string])
- selected.AddMultiple(ctx.FormStrings("f[]")...)
-
- entries := allEntries
- if len(selected) > 0 {
- entries = make(git.Entries, 0, len(selected))
- for _, entry := range allEntries {
- if selected.Contains(entry.Name()) {
- entries = append(entries, entry)
- }
- }
- }
-
- var latestCommit *git.Commit
- ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(commitInfoCtx, ctx.Repo.Commit, ctx.Repo.TreePath)
+ files, latestCommit, err := allEntries.GetCommitsInfo(commitInfoCtx, ctx.Repo.Commit, ctx.Repo.TreePath)
if err != nil {
ctx.ServerError("GetCommitsInfo", err)
return nil
}
+ ctx.Data["Files"] = files
+ for _, f := range files {
+ if f.Commit == nil {
+ ctx.Data["HasFilesWithoutLatestCommit"] = true
+ break
+ }
+ }
if !loadLatestCommitData(ctx, latestCommit) {
return nil
diff --git a/routers/web/shared/packages/packages.go b/routers/web/shared/packages/packages.go
index 1454396f04..57671ad8f1 100644
--- a/routers/web/shared/packages/packages.go
+++ b/routers/web/shared/packages/packages.go
@@ -13,7 +13,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
@@ -157,7 +157,7 @@ func SetRulePreviewContext(ctx *context.Context, owner *user_model.User) {
for _, p := range packages {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
PackageID: p.ID,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Sort: packages_model.SortCreatedDesc,
Paginator: db.NewAbsoluteListOptions(pcr.KeepCount, 200),
})
diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go
index 51b04e0613..3bc1adae99 100644
--- a/routers/web/shared/user/header.go
+++ b/routers/web/shared/user/header.go
@@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
)
@@ -131,7 +130,7 @@ func LoadHeaderCount(ctx *context.Context) error {
}
projectCount, err := db.Count[project_model.Project](ctx, project_model.SearchOptions{
OwnerID: ctx.ContextUser.ID,
- IsClosed: util.OptionalBoolOf(false),
+ IsClosed: optional.Some(false),
Type: projectType,
})
if err != nil {
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index 6f36806ff7..caa7115259 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -30,7 +30,6 @@ import (
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/feed"
"code.gitea.io/gitea/services/context"
issue_service "code.gitea.io/gitea/services/issue"
@@ -215,7 +214,7 @@ func Milestones(ctx *context.Context) {
counts, err := issues_model.CountMilestonesMap(ctx, issues_model.FindMilestoneOptions{
RepoCond: userRepoCond,
Name: keyword,
- IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsClosed: optional.Some(isShowClosed),
})
if err != nil {
ctx.ServerError("CountMilestonesByRepoIDs", err)
@@ -228,7 +227,7 @@ func Milestones(ctx *context.Context) {
PageSize: setting.UI.IssuePagingNum,
},
RepoCond: repoCond,
- IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsClosed: optional.Some(isShowClosed),
SortType: sortType,
Name: keyword,
})
@@ -440,9 +439,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
isPullList := unitType == unit.TypePullRequests
opts := &issues_model.IssuesOptions{
- IsPull: util.OptionalBoolOf(isPullList),
+ IsPull: optional.Some(isPullList),
SortType: sortType,
- IsArchived: util.OptionalBoolFalse,
+ IsArchived: optional.Some(false),
Org: org,
Team: team,
User: ctx.Doer,
@@ -516,7 +515,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
// Educated guess: Do or don't show closed issues.
isShowClosed := ctx.FormString("state") == "closed"
- opts.IsClosed = util.OptionalBoolOf(isShowClosed)
+ opts.IsClosed = optional.Some(isShowClosed)
// Make sure page number is at least 1. Will be posted to ctx.Data.
page := ctx.FormInt("page")
@@ -800,12 +799,12 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
case issues_model.FilterModeReviewed:
openClosedOpts.ReviewedID = &doerID
}
- openClosedOpts.IsClosed = util.OptionalBoolFalse
+ openClosedOpts.IsClosed = optional.Some(false)
ret.OpenCount, err = issue_indexer.CountIssues(ctx, openClosedOpts)
if err != nil {
return nil, err
}
- openClosedOpts.IsClosed = util.OptionalBoolTrue
+ openClosedOpts.IsClosed = optional.Some(true)
ret.ClosedCount, err = issue_indexer.CountIssues(ctx, openClosedOpts)
if err != nil {
return nil, err
diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go
index 801e1cf95e..09e592d63a 100644
--- a/routers/web/user/notification.go
+++ b/routers/web/user/notification.go
@@ -233,26 +233,25 @@ func NotificationSubscriptions(ctx *context.Context) {
if !util.SliceContainsString([]string{"all", "open", "closed"}, state, true) {
state = "all"
}
+
ctx.Data["State"] = state
- var showClosed util.OptionalBool
+ // default state filter is "all"
+ showClosed := optional.None[bool]()
switch state {
- case "all":
- showClosed = util.OptionalBoolNone
case "closed":
- showClosed = util.OptionalBoolTrue
+ showClosed = optional.Some(true)
case "open":
- showClosed = util.OptionalBoolFalse
+ showClosed = optional.Some(false)
}
- var issueTypeBool util.OptionalBool
issueType := ctx.FormString("issueType")
+ // default issue type is no filter
+ issueTypeBool := optional.None[bool]()
switch issueType {
case "issues":
- issueTypeBool = util.OptionalBoolFalse
+ issueTypeBool = optional.Some(false)
case "pulls":
- issueTypeBool = util.OptionalBoolTrue
- default:
- issueTypeBool = util.OptionalBoolNone
+ issueTypeBool = optional.Some(true)
}
ctx.Data["IssueType"] = issueType
diff --git a/routers/web/user/package.go b/routers/web/user/package.go
index d03b28309f..3ecc59a2ab 100644
--- a/routers/web/user/package.go
+++ b/routers/web/user/package.go
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
debian_module "code.gitea.io/gitea/modules/packages/debian"
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
@@ -54,7 +55,7 @@ func ListPackages(ctx *context.Context) {
OwnerID: ctx.ContextUser.ID,
Type: packages_model.Type(packageType),
Name: packages_model.SearchValue{Value: query},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
ctx.ServerError("SearchLatestVersions", err)
@@ -145,7 +146,7 @@ func RedirectToLastVersion(ctx *context.Context) {
pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
PackageID: p.ID,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
ctx.ServerError("GetPackageByName", err)
@@ -162,7 +163,7 @@ func RedirectToLastVersion(ctx *context.Context) {
return
}
- ctx.Redirect(pd.FullWebLink())
+ ctx.Redirect(pd.VersionWebLink())
}
// ViewPackageVersion displays a single package version
@@ -255,7 +256,7 @@ func ViewPackageVersion(ctx *context.Context) {
pvs, total, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
Paginator: db.NewAbsoluteListOptions(0, 5),
PackageID: pd.Package.ID,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
}
if err != nil {
@@ -359,7 +360,7 @@ func ListPackageVersions(ctx *context.Context) {
ExactMatch: false,
Value: query,
},
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Sort: sort,
})
if err != nil {
@@ -467,7 +468,7 @@ func PackageSettingsPost(ctx *context.Context) {
redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages"
// redirect to the package if there are still versions available
- if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID, IsInternal: util.OptionalBoolFalse}); has {
+ if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID, IsInternal: optional.Some(false)}); has {
redirectURL = ctx.Package.Descriptor.PackageWebLink()
}
diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go
index 0a12777e5e..cb01913bda 100644
--- a/routers/web/user/setting/keys.go
+++ b/routers/web/user/setting/keys.go
@@ -5,6 +5,7 @@
package setting
import (
+ "fmt"
"net/http"
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -77,6 +78,11 @@ func KeysPost(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content))
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
case "gpg":
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
+ return
+ }
+
token := asymkey_model.VerificationToken(ctx.Doer, 1)
lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
@@ -224,6 +230,10 @@ func KeysPost(ctx *context.Context) {
func DeleteKey(ctx *context.Context) {
switch ctx.FormString("type") {
case "gpg":
+ if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) {
+ ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
+ return
+ }
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.FormInt64("id")); err != nil {
ctx.Flash.Error("DeleteGPGKey: " + err.Error())
} else {
diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go
index 30611dd9f1..8d6859ab87 100644
--- a/routers/web/user/setting/security/security.go
+++ b/routers/web/user/setting/security/security.go
@@ -12,8 +12,8 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/context"
)
@@ -112,7 +112,7 @@ func loadSecurityData(ctx *context.Context) {
ctx.Data["AccountLinks"] = sources
authSources, err := db.Find[auth_model.Source](ctx, auth_model.FindSourcesOptions{
- IsActive: util.OptionalBoolNone,
+ IsActive: optional.None[bool](),
LoginType: auth_model.OAuth2,
})
if err != nil {
diff --git a/routers/web/web.go b/routers/web/web.go
index 8826a8c56b..2342c8eb31 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -956,10 +956,6 @@ func registerRoutes(m *web.Route) {
m.Post("/create", web.Bind(forms.CreateRepoForm{}), repo.CreatePost)
m.Get("/migrate", repo.Migrate)
m.Post("/migrate", web.Bind(forms.MigrateRepoForm{}), repo.MigratePost)
- m.Group("/fork", func() {
- m.Combo("/{repoid}").Get(repo.Fork).
- Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost)
- }, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader)
m.Get("/search", repo.SearchRepo)
}, reqSignIn)
@@ -1255,6 +1251,8 @@ func registerRoutes(m *web.Route) {
m.Post("/delete", repo.DeleteBranchPost)
m.Post("/restore", repo.RestoreBranchPost)
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
+
+ m.Combo("/fork", reqRepoCodeReader).Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost)
}, reqSignIn, context.RepoAssignment, context.UnitTypes())
// Tags
diff --git a/services/attachment/attachment.go b/services/attachment/attachment.go
index eab3d0b142..0fd51e4fa5 100644
--- a/services/attachment/attachment.go
+++ b/services/attachment/attachment.go
@@ -39,14 +39,14 @@ func NewAttachment(ctx context.Context, attach *repo_model.Attachment, file io.R
}
// UploadAttachment upload new attachment into storage and update database
-func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, opts *repo_model.Attachment) (*repo_model.Attachment, error) {
+func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, attach *repo_model.Attachment) (*repo_model.Attachment, error) {
buf := make([]byte, 1024)
n, _ := util.ReadAtMost(file, buf)
buf = buf[:n]
- if err := upload.Verify(buf, opts.Name, allowedTypes); err != nil {
+ if err := upload.Verify(buf, attach.Name, allowedTypes); err != nil {
return nil, err
}
- return NewAttachment(ctx, opts, io.MultiReader(bytes.NewReader(buf), file), fileSize)
+ return NewAttachment(ctx, attach, io.MultiReader(bytes.NewReader(buf), file), fileSize)
}
diff --git a/services/auth/signin.go b/services/auth/signin.go
index fafe3ef3c6..e116a088e0 100644
--- a/services/auth/signin.go
+++ b/services/auth/signin.go
@@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/auth/source/smtp"
@@ -87,7 +87,7 @@ func UserSignIn(ctx context.Context, username, password string) (*user_model.Use
}
sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
})
if err != nil {
return nil, nil, err
diff --git a/services/auth/source/oauth2/init.go b/services/auth/source/oauth2/init.go
index 3ad6e307f1..5c25681548 100644
--- a/services/auth/source/oauth2/init.go
+++ b/services/auth/source/oauth2/init.go
@@ -12,8 +12,8 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"github.com/google/uuid"
"github.com/gorilla/sessions"
@@ -66,7 +66,7 @@ func ResetOAuth2(ctx context.Context) error {
// initOAuth2Sources is used to load and register all active OAuth2 providers
func initOAuth2Sources(ctx context.Context) error {
authSources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
LoginType: auth.OAuth2,
})
if err != nil {
diff --git a/services/auth/source/oauth2/providers.go b/services/auth/source/oauth2/providers.go
index f4edb507f2..ac32647839 100644
--- a/services/auth/source/oauth2/providers.go
+++ b/services/auth/source/oauth2/providers.go
@@ -15,8 +15,8 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"github.com/markbates/goth"
)
@@ -107,7 +107,7 @@ func CreateProviderFromSource(source *auth.Source) (Provider, error) {
}
// GetOAuth2Providers returns the list of configured OAuth2 providers
-func GetOAuth2Providers(ctx context.Context, isActive util.OptionalBool) ([]Provider, error) {
+func GetOAuth2Providers(ctx context.Context, isActive optional.Option[bool]) ([]Provider, error) {
authSources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
IsActive: isActive,
LoginType: auth.OAuth2,
diff --git a/services/auth/sspi.go b/services/auth/sspi.go
index 9108a0a668..64a127e97a 100644
--- a/services/auth/sspi.go
+++ b/services/auth/sspi.go
@@ -17,7 +17,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/auth/source/sspi"
gitea_context "code.gitea.io/gitea/services/context"
@@ -132,7 +131,7 @@ func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore,
// getConfig retrieves the SSPI configuration from login sources
func (s *SSPI) getConfig(ctx context.Context) (*sspi.Source, error) {
sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
LoginType: auth.SSPI,
})
if err != nil {
diff --git a/services/context/context_response.go b/services/context/context_response.go
index 829bca1f59..372b4cb38b 100644
--- a/services/context/context_response.go
+++ b/services/context/context_response.go
@@ -6,6 +6,7 @@ package context
import (
"errors"
"fmt"
+ "html/template"
"net"
"net/http"
"net/url"
@@ -104,11 +105,11 @@ func (ctx *Context) JSONTemplate(tmpl base.TplName) {
}
}
-// RenderToString renders the template content to a string
-func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) {
+// RenderToHTML renders the template content to a HTML string
+func (ctx *Context) RenderToHTML(name base.TplName, data map[string]any) (template.HTML, error) {
var buf strings.Builder
- err := ctx.Render.HTML(&buf, http.StatusOK, string(name), data, ctx.TemplateContext)
- return buf.String(), err
+ err := ctx.Render.HTML(&buf, 0, string(name), data, ctx.TemplateContext)
+ return template.HTML(buf.String()), err
}
// RenderWithErr used for page has form validation but need to prompt error to users.
diff --git a/services/context/repo.go b/services/context/repo.go
index d6a68c0c1a..0b15c95e59 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -408,26 +408,6 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
}
-// RepoIDAssignment returns a handler which assigns the repo to the context.
-func RepoIDAssignment() func(ctx *Context) {
- return func(ctx *Context) {
- repoID := ctx.ParamsInt64(":repoid")
-
- // Get repository.
- repo, err := repo_model.GetRepositoryByID(ctx, repoID)
- if err != nil {
- if repo_model.IsErrRepoNotExist(err) {
- ctx.NotFound("GetRepositoryByID", nil)
- } else {
- ctx.ServerError("GetRepositoryByID", err)
- }
- return
- }
-
- repoAssignment(ctx, repo)
- }
-}
-
// RepoAssignment returns a middleware to handle repository assignment
func RepoAssignment(ctx *Context) context.CancelFunc {
if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce {
diff --git a/services/convert/package.go b/services/convert/package.go
index e90ce8a00f..b5fca21a3c 100644
--- a/services/convert/package.go
+++ b/services/convert/package.go
@@ -35,7 +35,7 @@ func ToPackage(ctx context.Context, pd *packages.PackageDescriptor, doer *user_m
Name: pd.Package.Name,
Version: pd.Version.Version,
CreatedAt: pd.Version.CreatedUnix.AsTime(),
- HTMLURL: pd.FullWebLink(),
+ HTMLURL: pd.VersionHTMLURL(),
}, nil
}
diff --git a/services/forms/admin.go b/services/forms/admin.go
index f112013060..81276f8f46 100644
--- a/services/forms/admin.go
+++ b/services/forms/admin.go
@@ -41,6 +41,7 @@ type AdminEditUserForm struct {
Password string `binding:"MaxSize(255)"`
Website string `binding:"ValidUrl;MaxSize(255)"`
Location string `binding:"MaxSize(50)"`
+ Language string `binding:"MaxSize(5)"`
MaxRepoCreation int
Active bool
Admin bool
diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go
index 740c748347..b05c210a0c 100644
--- a/services/gitdiff/gitdiff.go
+++ b/services/gitdiff/gitdiff.go
@@ -154,7 +154,7 @@ func (d *DiffLine) GetBlobExcerptQuery() string {
// GetExpandDirection gets DiffLineExpandDirection
func (d *DiffLine) GetExpandDirection() DiffLineExpandDirection {
- if d.Type != DiffLineSection || d.SectionInfo == nil || d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx <= 1 {
+ if d.Type != DiffLineSection || d.SectionInfo == nil || d.SectionInfo.LeftIdx-d.SectionInfo.LastLeftIdx <= 1 || d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx <= 1 {
return DiffLineExpandNone
}
if d.SectionInfo.LastLeftIdx <= 0 && d.SectionInfo.LastRightIdx <= 0 {
diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go
index c8102c6b8b..c9b9248098 100644
--- a/services/migrations/gitea_uploader_test.go
+++ b/services/migrations/gitea_uploader_test.go
@@ -23,9 +23,9 @@ import (
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
- "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
@@ -68,14 +68,14 @@ func TestGiteaUploadRepo(t *testing.T) {
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
RepoID: repo.ID,
- IsClosed: util.OptionalBoolFalse,
+ IsClosed: optional.Some(false),
})
assert.NoError(t, err)
assert.Len(t, milestones, 1)
milestones, err = db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
RepoID: repo.ID,
- IsClosed: util.OptionalBoolTrue,
+ IsClosed: optional.Some(true),
})
assert.NoError(t, err)
assert.Empty(t, milestones)
@@ -108,7 +108,7 @@ func TestGiteaUploadRepo(t *testing.T) {
issues, err := issues_model.Issues(db.DefaultContext, &issues_model.IssuesOptions{
RepoIDs: []int64{repo.ID},
- IsPull: util.OptionalBoolFalse,
+ IsPull: optional.Some(false),
SortType: "oldest",
})
assert.NoError(t, err)
diff --git a/services/packages/cleanup/cleanup.go b/services/packages/cleanup/cleanup.go
index 0ff8077bc9..5d5120c6a0 100644
--- a/services/packages/cleanup/cleanup.go
+++ b/services/packages/cleanup/cleanup.go
@@ -12,8 +12,8 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
- "code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages"
alpine_service "code.gitea.io/gitea/services/packages/alpine"
cargo_service "code.gitea.io/gitea/services/packages/cargo"
@@ -60,7 +60,7 @@ func ExecuteCleanupRules(outerCtx context.Context) error {
for _, p := range packages {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
PackageID: p.ID,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
Sort: packages_model.SortCreatedDesc,
Paginator: db.NewAbsoluteListOptions(pcr.KeepCount, 200),
})
diff --git a/services/packages/container/cleanup.go b/services/packages/container/cleanup.go
index dd3f158dbf..3f5f43bbc0 100644
--- a/services/packages/container/cleanup.go
+++ b/services/packages/container/cleanup.go
@@ -9,8 +9,8 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
+ "code.gitea.io/gitea/modules/optional"
container_module "code.gitea.io/gitea/modules/packages/container"
- "code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages"
digest "github.com/opencontainers/go-digest"
@@ -59,8 +59,8 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e
ExactMatch: true,
Value: container_model.UploadVersion,
},
- IsInternal: util.OptionalBoolTrue,
- HasFiles: util.OptionalBoolFalse,
+ IsInternal: optional.Some(true),
+ HasFiles: optional.Some(false),
})
if err != nil {
return err
diff --git a/services/packages/packages.go b/services/packages/packages.go
index 56d5cc04de..64b1ddd869 100644
--- a/services/packages/packages.go
+++ b/services/packages/packages.go
@@ -18,10 +18,10 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
- "code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
)
@@ -330,7 +330,7 @@ func CheckCountQuotaExceeded(ctx context.Context, doer, owner *user_model.User)
if setting.Packages.LimitTotalOwnerCount > -1 {
totalCount, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: owner.ID,
- IsInternal: util.OptionalBoolFalse,
+ IsInternal: optional.Some(false),
})
if err != nil {
log.Error("CountVersions failed: %v", err)
@@ -640,7 +640,7 @@ func RemoveAllPackages(ctx context.Context, userID int64) (int, error) {
Page: 1,
},
OwnerID: userID,
- IsInternal: util.OptionalBoolNone,
+ IsInternal: optional.None[bool](),
})
if err != nil {
return count, fmt.Errorf("GetOwnedPackages[%d]: %w", userID, err)
diff --git a/services/pull/review.go b/services/pull/review.go
index 3ffc276778..90d07c8358 100644
--- a/services/pull/review.go
+++ b/services/pull/review.go
@@ -18,8 +18,8 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
)
@@ -56,7 +56,7 @@ func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestLis
ListAll: true,
},
Type: issues_model.CommentTypeCode,
- Invalidated: util.OptionalBoolFalse,
+ Invalidated: optional.Some(false),
IssueIDs: issueIDs,
})
if err != nil {
@@ -327,7 +327,7 @@ func DismissApprovalReviews(ctx context.Context, doer *user_model.User, pull *is
},
IssueID: pull.IssueID,
Type: issues_model.ReviewTypeApprove,
- Dismissed: util.OptionalBoolFalse,
+ Dismissed: optional.Some(false),
})
if err != nil {
return err
@@ -394,7 +394,7 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string,
reviews, err := issues_model.FindReviews(ctx, issues_model.FindReviewOptions{
IssueID: review.IssueID,
ReviewerID: review.ReviewerID,
- Dismissed: util.OptionalBoolFalse,
+ Dismissed: optional.Some(false),
})
if err != nil {
return nil, err
diff --git a/services/repository/branch.go b/services/repository/branch.go
index ec41173da8..55cedf5d84 100644
--- a/services/repository/branch.go
+++ b/services/repository/branch.go
@@ -24,7 +24,6 @@ import (
"code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
notify_service "code.gitea.io/gitea/services/notify"
files_service "code.gitea.io/gitea/services/repository/files"
@@ -54,7 +53,7 @@ type Branch struct {
}
// LoadBranches loads branches from the repository limited by page & pageSize.
-func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch util.OptionalBool, keyword string, page, pageSize int) (*Branch, []*Branch, int64, error) {
+func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch optional.Option[bool], keyword string, page, pageSize int) (*Branch, []*Branch, int64, error) {
defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch)
if err != nil {
return nil, nil, 0, err
@@ -62,7 +61,7 @@ func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git
branchOpts := git_model.FindBranchOptions{
RepoID: repo.ID,
- IsDeletedBranch: isDeletedBranch.ToGeneric(),
+ IsDeletedBranch: isDeletedBranch,
ListOptions: db.ListOptions{
Page: page,
PageSize: pageSize,
diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go
index ac18da3525..35c760dc62 100644
--- a/services/webhook/webhook.go
+++ b/services/webhook/webhook.go
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@@ -225,7 +226,7 @@ func PrepareWebhooks(ctx context.Context, source EventSource, event webhook_modu
if source.Repository != nil {
repoHooks, err := db.Find[webhook_model.Webhook](ctx, webhook_model.ListWebhookOptions{
RepoID: source.Repository.ID,
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
})
if err != nil {
return fmt.Errorf("ListWebhooksByOpts: %w", err)
@@ -239,7 +240,7 @@ func PrepareWebhooks(ctx context.Context, source EventSource, event webhook_modu
if owner != nil {
ownerHooks, err := db.Find[webhook_model.Webhook](ctx, webhook_model.ListWebhookOptions{
OwnerID: owner.ID,
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
})
if err != nil {
return fmt.Errorf("ListWebhooksByOpts: %w", err)
@@ -248,7 +249,7 @@ func PrepareWebhooks(ctx context.Context, source EventSource, event webhook_modu
}
// Add any admin-defined system webhooks
- systemHooks, err := webhook_model.GetSystemWebhooks(ctx, util.OptionalBoolTrue)
+ systemHooks, err := webhook_model.GetSystemWebhooks(ctx, optional.Some(true))
if err != nil {
return fmt.Errorf("GetSystemWebhooks: %w", err)
}
diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl
index fcb8ce0827..159c821099 100644
--- a/templates/admin/user/edit.tmpl
+++ b/templates/admin/user/edit.tmpl
@@ -70,6 +70,21 @@
{{ctx.Locale.Tr "admin.users.password_helper"}}
+ +