mirror of https://github.com/go-gitea/gitea.git
Compare commits
28 Commits
8b73362a23
...
d2bb9ce5b7
Author | SHA1 | Date |
---|---|---|
Giteabot | d2bb9ce5b7 | |
Yarden Shoham | 51c28d9683 | |
wxiaoguang | d3cdef88ad | |
Giteabot | f395171dab | |
Kemal Zebari | dd301cae1c | |
silverwind | 238eb3ff9f | |
silverwind | b2abac5e5f | |
Chongyi Zheng | 4ae6b1a553 | |
silverwind | 9b2536b78f | |
silverwind | dcc3c17e5c | |
GiteaBot | 27861d711b | |
silverwind | c93eefb42b | |
Bo-Yi Wu | 852547d0dc | |
wxiaoguang | 993736d838 | |
wxiaoguang | cd70ab31cd | |
wxiaoguang | 1e749b80d7 | |
Yarden Shoham | 68a3e6b5e6 | |
wxiaoguang | ed8c63cea3 | |
yp05327 | 2a3906d755 | |
Lunny Xiao | 2a6418abb1 | |
wxiaoguang | 6a0750177f | |
GiteaBot | 935330b1b9 | |
wxiaoguang | fd63b96f6a | |
wxiaoguang | bffbbf5470 | |
silverwind | d0bfc978de | |
Lunny Xiao | c685eefe4a | |
GiteaBot | 4ff54933f8 | |
Jiaxin Zhu | a63f14b908 |
11
Makefile
11
Makefile
|
@ -30,7 +30,7 @@ EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-che
|
||||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
|
||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1
|
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@db51e79a0e37c572d8b59ae0c58bf2bbbbe53285
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@db51e79a0e37c572d8b59ae0c58bf2bbbbe53285
|
||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
||||||
|
@ -397,11 +397,11 @@ lint-md: node_modules
|
||||||
|
|
||||||
.PHONY: lint-spell
|
.PHONY: lint-spell
|
||||||
lint-spell:
|
lint-spell:
|
||||||
@go run $(MISSPELL_PACKAGE) -error $(SPELLCHECK_FILES)
|
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -error $(SPELLCHECK_FILES)
|
||||||
|
|
||||||
.PHONY: lint-spell-fix
|
.PHONY: lint-spell-fix
|
||||||
lint-spell-fix:
|
lint-spell-fix:
|
||||||
@go run $(MISSPELL_PACKAGE) -w $(SPELLCHECK_FILES)
|
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -w $(SPELLCHECK_FILES)
|
||||||
|
|
||||||
.PHONY: lint-go
|
.PHONY: lint-go
|
||||||
lint-go:
|
lint-go:
|
||||||
|
@ -908,8 +908,9 @@ webpack: $(WEBPACK_DEST)
|
||||||
|
|
||||||
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
|
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
|
||||||
@$(MAKE) -s node-check node_modules
|
@$(MAKE) -s node-check node_modules
|
||||||
rm -rf $(WEBPACK_DEST_ENTRIES)
|
@rm -rf $(WEBPACK_DEST_ENTRIES)
|
||||||
npx webpack
|
@echo "Running webpack..."
|
||||||
|
@BROWSERSLIST_IGNORE_OLD_DATA=true npx webpack
|
||||||
@touch $(WEBPACK_DEST)
|
@touch $(WEBPACK_DEST)
|
||||||
|
|
||||||
.PHONY: svg
|
.PHONY: svg
|
||||||
|
|
|
@ -35,7 +35,7 @@ var microcmdUserChangePassword = &cli.Command{
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "must-change-password",
|
Name: "must-change-password",
|
||||||
Usage: "User must change password",
|
Usage: "User must change password (can be disabled by --must-change-password=false)",
|
||||||
Value: true,
|
Value: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ var microcmdUserCreate = &cli.Command{
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "must-change-password",
|
Name: "must-change-password",
|
||||||
Usage: "Set to false to prevent forcing the user to change their password after initial login",
|
Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)",
|
||||||
DisableDefaultText: true,
|
DisableDefaultText: true,
|
||||||
},
|
},
|
||||||
&cli.IntFlag{
|
&cli.IntFlag{
|
||||||
|
@ -91,11 +92,16 @@ func runCreateUser(c *cli.Context) error {
|
||||||
_, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
|
_, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
ctx := c.Context
|
||||||
defer cancel()
|
if !setting.IsInTesting {
|
||||||
|
// FIXME: need to refactor the "installSignals/initDB" related code later
|
||||||
if err := initDB(ctx); err != nil {
|
// it doesn't make sense to call it in (almost) every command action function
|
||||||
return err
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = installSignals()
|
||||||
|
defer cancel()
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var password string
|
var password string
|
||||||
|
@ -123,8 +129,8 @@ func runCreateUser(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("IsTableNotEmpty: %w", err)
|
return fmt.Errorf("IsTableNotEmpty: %w", err)
|
||||||
}
|
}
|
||||||
if !hasUserRecord && isAdmin {
|
if !hasUserRecord {
|
||||||
// if this is the first admin being created, don't force to change password (keep the old behavior)
|
// if this is the first one being created, don't force to change password (keep the old behavior)
|
||||||
mustChangePassword = false
|
mustChangePassword = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdminUserCreate(t *testing.T) {
|
||||||
|
app := NewMainApp(AppVersion{})
|
||||||
|
|
||||||
|
reset := func() {
|
||||||
|
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
|
||||||
|
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
type createCheck struct{ IsAdmin, MustChangePassword bool }
|
||||||
|
createUser := func(name, args string) createCheck {
|
||||||
|
assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
|
||||||
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
|
||||||
|
return createCheck{u.IsAdmin, u.MustChangePassword}
|
||||||
|
}
|
||||||
|
reset()
|
||||||
|
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u", ""), "first non-admin user doesn't need to change password")
|
||||||
|
|
||||||
|
reset()
|
||||||
|
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password")
|
||||||
|
|
||||||
|
reset()
|
||||||
|
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password"))
|
||||||
|
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin"))
|
||||||
|
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false"))
|
||||||
|
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", ""))
|
||||||
|
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false"))
|
||||||
|
}
|
|
@ -112,13 +112,18 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMainApp(version, versionExtra string) *cli.App {
|
type AppVersion struct {
|
||||||
|
Version string
|
||||||
|
Extra string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMainApp(appVer AppVersion) *cli.App {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "Gitea"
|
app.Name = "Gitea"
|
||||||
app.HelpName = "gitea"
|
app.HelpName = "gitea"
|
||||||
app.Usage = "A painless self-hosted Git service"
|
app.Usage = "A painless self-hosted Git service"
|
||||||
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
|
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
|
||||||
app.Version = version + versionExtra
|
app.Version = appVer.Version + appVer.Extra
|
||||||
app.EnableBashCompletion = true
|
app.EnableBashCompletion = true
|
||||||
|
|
||||||
// these sub-commands need to use config file
|
// these sub-commands need to use config file
|
||||||
|
|
|
@ -28,7 +28,7 @@ func makePathOutput(workPath, customPath, customConf string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
|
func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
|
||||||
app := NewMainApp("version", "version-extra")
|
app := NewMainApp(AppVersion{})
|
||||||
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
|
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
|
||||||
prepareSubcommandWithConfig(testCmd, appGlobalFlags())
|
prepareSubcommandWithConfig(testCmd, appGlobalFlags())
|
||||||
app.Commands = append(app.Commands, testCmd)
|
app.Commands = append(app.Commands, testCmd)
|
||||||
|
|
|
@ -1558,8 +1558,8 @@ LEVEL = Info
|
||||||
;; email = use the username part of the email attribute
|
;; email = use the username part of the email attribute
|
||||||
;; Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria:
|
;; Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria:
|
||||||
;; - diacritics are removed
|
;; - diacritics are removed
|
||||||
;; - the characters in the set `['´\x60]` are removed
|
;; - the characters in the set ['´`] are removed
|
||||||
;; - the characters in the set `[\s~+]` are replaced with `-`
|
;; - the characters in the set [\s~+] are replaced with "-"
|
||||||
;USERNAME = nickname
|
;USERNAME = nickname
|
||||||
;;
|
;;
|
||||||
;; Update avatar if available from oauth2 provider.
|
;; Update avatar if available from oauth2 provider.
|
||||||
|
|
|
@ -612,7 +612,7 @@ And the following unique queues:
|
||||||
- `email` - use the username part of the email attribute
|
- `email` - use the username part of the email attribute
|
||||||
- Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria:
|
- Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria:
|
||||||
- diacritics are removed
|
- diacritics are removed
|
||||||
- the characters in the set `['´\x60]` are removed
|
- the characters in the set ```['´`]``` are removed
|
||||||
- the characters in the set `[\s~+]` are replaced with `-`
|
- the characters in the set `[\s~+]` are replaced with `-`
|
||||||
- `UPDATE_AVATAR`: **false**: Update avatar if available from oauth2 provider. Update will be performed on each login.
|
- `UPDATE_AVATAR`: **false**: Update avatar if available from oauth2 provider. Update will be performed on each login.
|
||||||
- `ACCOUNT_LINKING`: **login**: How to handle if an account / email already exists:
|
- `ACCOUNT_LINKING`: **login**: How to handle if an account / email already exists:
|
||||||
|
@ -1322,7 +1322,7 @@ Defaultly every storage has their default base path like below
|
||||||
| actions_log | actions_log/ |
|
| actions_log | actions_log/ |
|
||||||
| actions_artifacts | actions_artifacts/ |
|
| actions_artifacts | actions_artifacts/ |
|
||||||
|
|
||||||
And bucket, basepath or `SERVE_DIRECT` could be special or overrided, if you want to use a different you can:
|
And bucket, basepath or `SERVE_DIRECT` could be special or overridden, if you want to use a different you can:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[storage.actions_log]
|
[storage.actions_log]
|
||||||
|
|
2
main.go
2
main.go
|
@ -42,7 +42,7 @@ func main() {
|
||||||
log.GetManager().Close()
|
log.GetManager().Close()
|
||||||
os.Exit(code)
|
os.Exit(code)
|
||||||
}
|
}
|
||||||
app := cmd.NewMainApp(Version, formatBuiltWith())
|
app := cmd.NewMainApp(cmd.AppVersion{Version: Version, Extra: formatBuiltWith()})
|
||||||
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
|
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
|
||||||
log.GetManager().Close()
|
log.GetManager().Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,13 @@ func (run *ActionRun) Link() string {
|
||||||
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index)
|
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (run *ActionRun) WorkflowLink() string {
|
||||||
|
if run.Repo == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/actions/?workflow=%s", run.Repo.Link(), run.WorkflowID)
|
||||||
|
}
|
||||||
|
|
||||||
// RefLink return the url of run's ref
|
// RefLink return the url of run's ref
|
||||||
func (run *ActionRun) RefLink() string {
|
func (run *ActionRun) RefLink() string {
|
||||||
refName := git.RefName(run.Ref)
|
refName := git.RefName(run.Ref)
|
||||||
|
@ -156,6 +163,10 @@ func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, err
|
||||||
return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
|
return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (run *ActionRun) IsSchedule() bool {
|
||||||
|
return run.ScheduleID > 0
|
||||||
|
}
|
||||||
|
|
||||||
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
||||||
_, err := db.GetEngine(ctx).ID(repo.ID).
|
_, err := db.GetEngine(ctx).ID(repo.ID).
|
||||||
SetExpr("num_action_runs",
|
SetExpr("num_action_runs",
|
||||||
|
@ -251,11 +262,11 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
|
||||||
|
|
||||||
// InsertRun inserts a run
|
// InsertRun inserts a run
|
||||||
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
|
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
|
||||||
ctx, commiter, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer commiter.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID)
|
index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -320,7 +331,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return commiter.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
|
func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
|
||||||
|
|
|
@ -216,11 +216,11 @@ func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask, bool, error) {
|
func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask, bool, error) {
|
||||||
ctx, commiter, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
defer commiter.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
|
||||||
|
@ -322,7 +322,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
||||||
|
|
||||||
task.Job = job
|
task.Job = job
|
||||||
|
|
||||||
if err := commiter.Commit(); err != nil {
|
if err := committer.Commit(); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,11 +347,11 @@ func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionT
|
||||||
stepStates[v.Id] = v
|
stepStates[v.Id] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, commiter, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer commiter.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
|
||||||
|
@ -412,7 +412,7 @@ func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := commiter.Commit(); err != nil {
|
if err := committer.Commit(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
// ActionTasksVersion
|
// ActionTasksVersion
|
||||||
// If both ownerID and repoID is zero, its scope is global.
|
// If both ownerID and repoID is zero, its scope is global.
|
||||||
// If ownerID is not zero and repoID is zero, its scope is org (there is no user-level runner currrently).
|
// If ownerID is not zero and repoID is zero, its scope is org (there is no user-level runner currently).
|
||||||
// If ownerID is zero and repoID is not zero, its scope is repo.
|
// If ownerID is zero and repoID is not zero, its scope is repo.
|
||||||
type ActionTasksVersion struct {
|
type ActionTasksVersion struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
@ -73,11 +73,11 @@ func increaseTasksVersionByScope(ctx context.Context, ownerID, repoID int64) err
|
||||||
}
|
}
|
||||||
|
|
||||||
func IncreaseTaskVersion(ctx context.Context, ownerID, repoID int64) error {
|
func IncreaseTaskVersion(ctx context.Context, ownerID, repoID int64) error {
|
||||||
ctx, commiter, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer commiter.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
// 1. increase global
|
// 1. increase global
|
||||||
if err := increaseTasksVersionByScope(ctx, 0, 0); err != nil {
|
if err := increaseTasksVersionByScope(ctx, 0, 0); err != nil {
|
||||||
|
@ -101,5 +101,5 @@ func IncreaseTaskVersion(ctx context.Context, ownerID, repoID int64) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return commiter.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
-
|
-
|
||||||
id: 1
|
id: 1
|
||||||
type: 0 # gitea pull request
|
type: 0 # gitea pull request
|
||||||
status: 2 # mergable
|
status: 2 # mergeable
|
||||||
issue_id: 2
|
issue_id: 2
|
||||||
index: 2
|
index: 2
|
||||||
head_repo_id: 1
|
head_repo_id: 1
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
-
|
-
|
||||||
id: 2
|
id: 2
|
||||||
type: 0 # gitea pull request
|
type: 0 # gitea pull request
|
||||||
status: 2 # mergable
|
status: 2 # mergeable
|
||||||
issue_id: 3
|
issue_id: 3
|
||||||
index: 3
|
index: 3
|
||||||
head_repo_id: 1
|
head_repo_id: 1
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
-
|
-
|
||||||
id: 3
|
id: 3
|
||||||
type: 0 # gitea pull request
|
type: 0 # gitea pull request
|
||||||
status: 2 # mergable
|
status: 2 # mergeable
|
||||||
issue_id: 8
|
issue_id: 8
|
||||||
index: 1
|
index: 1
|
||||||
head_repo_id: 11
|
head_repo_id: 11
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
-
|
-
|
||||||
id: 4
|
id: 4
|
||||||
type: 0 # gitea pull request
|
type: 0 # gitea pull request
|
||||||
status: 2 # mergable
|
status: 2 # mergeable
|
||||||
issue_id: 9
|
issue_id: 9
|
||||||
index: 1
|
index: 1
|
||||||
head_repo_id: 48
|
head_repo_id: 48
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
-
|
-
|
||||||
id: 5 # this PR is outdated (one commit behind branch1 )
|
id: 5 # this PR is outdated (one commit behind branch1 )
|
||||||
type: 0 # gitea pull request
|
type: 0 # gitea pull request
|
||||||
status: 2 # mergable
|
status: 2 # mergeable
|
||||||
issue_id: 11
|
issue_id: 11
|
||||||
index: 5
|
index: 5
|
||||||
head_repo_id: 1
|
head_repo_id: 1
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
-
|
-
|
||||||
id: 6
|
id: 6
|
||||||
type: 0 # gitea pull request
|
type: 0 # gitea pull request
|
||||||
status: 2 # mergable
|
status: 2 # mergeable
|
||||||
issue_id: 12
|
issue_id: 12
|
||||||
index: 2
|
index: 2
|
||||||
head_repo_id: 3
|
head_repo_id: 3
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
-
|
-
|
||||||
id: 7
|
id: 7
|
||||||
type: 0 # gitea pull request
|
type: 0 # gitea pull request
|
||||||
status: 2 # mergable
|
status: 2 # mergeable
|
||||||
issue_id: 19
|
issue_id: 19
|
||||||
index: 1
|
index: 1
|
||||||
head_repo_id: 58
|
head_repo_id: 58
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
-
|
-
|
||||||
id: 8
|
id: 8
|
||||||
type: 0 # gitea pull request
|
type: 0 # gitea pull request
|
||||||
status: 2 # mergable
|
status: 2 # mergeable
|
||||||
issue_id: 20
|
issue_id: 20
|
||||||
index: 1
|
index: 1
|
||||||
head_repo_id: 23
|
head_repo_id: 23
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
-
|
-
|
||||||
id: 9
|
id: 9
|
||||||
type: 0 # gitea pull request
|
type: 0 # gitea pull request
|
||||||
status: 2 # mergable
|
status: 2 # mergeable
|
||||||
issue_id: 21
|
issue_id: 21
|
||||||
index: 1
|
index: 1
|
||||||
head_repo_id: 60
|
head_repo_id: 60
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
-
|
-
|
||||||
id: 10
|
id: 10
|
||||||
type: 0 # gitea pull request
|
type: 0 # gitea pull request
|
||||||
status: 2 # mergable
|
status: 2 # mergeable
|
||||||
issue_id: 22
|
issue_id: 22
|
||||||
index: 1
|
index: 1
|
||||||
head_repo_id: 61
|
head_repo_id: 61
|
||||||
|
|
|
@ -807,7 +807,7 @@ func UpdateAllowEdits(ctx context.Context, pr *PullRequest) error {
|
||||||
|
|
||||||
// Mergeable returns if the pullrequest is mergeable.
|
// Mergeable returns if the pullrequest is mergeable.
|
||||||
func (pr *PullRequest) Mergeable(ctx context.Context) bool {
|
func (pr *PullRequest) Mergeable(ctx context.Context) bool {
|
||||||
// If a pull request isn't mergable if it's:
|
// If a pull request isn't mergeable if it's:
|
||||||
// - Being conflict checked.
|
// - Being conflict checked.
|
||||||
// - Has a conflict.
|
// - Has a conflict.
|
||||||
// - Received a error while being conflict checked.
|
// - Received a error while being conflict checked.
|
||||||
|
|
|
@ -187,8 +187,8 @@ func AddTime(ctx context.Context, user *user_model.User, issue *Issue, amount in
|
||||||
Issue: issue,
|
Issue: issue,
|
||||||
Repo: issue.Repo,
|
Repo: issue.Repo,
|
||||||
Doer: user,
|
Doer: user,
|
||||||
// Content before v1.21 did store the formated string instead of seconds,
|
// Content before v1.21 did store the formatted string instead of seconds,
|
||||||
// so use "|" as delimeter to mark the new format
|
// so use "|" as delimiter to mark the new format
|
||||||
Content: fmt.Sprintf("|%d", amount),
|
Content: fmt.Sprintf("|%d", amount),
|
||||||
Type: CommentTypeAddTimeManual,
|
Type: CommentTypeAddTimeManual,
|
||||||
TimeID: t.ID,
|
TimeID: t.ID,
|
||||||
|
@ -267,8 +267,8 @@ func DeleteIssueUserTimes(ctx context.Context, issue *Issue, user *user_model.Us
|
||||||
Issue: issue,
|
Issue: issue,
|
||||||
Repo: issue.Repo,
|
Repo: issue.Repo,
|
||||||
Doer: user,
|
Doer: user,
|
||||||
// Content before v1.21 did store the formated string instead of seconds,
|
// Content before v1.21 did store the formatted string instead of seconds,
|
||||||
// so use "|" as delimeter to mark the new format
|
// so use "|" as delimiter to mark the new format
|
||||||
Content: fmt.Sprintf("|%d", removedTime),
|
Content: fmt.Sprintf("|%d", removedTime),
|
||||||
Type: CommentTypeDeleteTimeManual,
|
Type: CommentTypeDeleteTimeManual,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -298,8 +298,8 @@ func DeleteTime(ctx context.Context, t *TrackedTime) error {
|
||||||
Issue: t.Issue,
|
Issue: t.Issue,
|
||||||
Repo: t.Issue.Repo,
|
Repo: t.Issue.Repo,
|
||||||
Doer: t.User,
|
Doer: t.User,
|
||||||
// Content before v1.21 did store the formated string instead of seconds,
|
// Content before v1.21 did store the formatted string instead of seconds,
|
||||||
// so use "|" as delimeter to mark the new format
|
// so use "|" as delimiter to mark the new format
|
||||||
Content: fmt.Sprintf("|%d", t.Time),
|
Content: fmt.Sprintf("|%d", t.Time),
|
||||||
Type: CommentTypeDeleteTimeManual,
|
Type: CommentTypeDeleteTimeManual,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|
|
@ -4,4 +4,4 @@
|
||||||
package v1_17 //nolint
|
package v1_17 //nolint
|
||||||
|
|
||||||
// This migration added non-ideal indices to the action table which on larger datasets slowed things down
|
// This migration added non-ideal indices to the action table which on larger datasets slowed things down
|
||||||
// it has been superceded by v218.go
|
// it has been superseded by v218.go
|
||||||
|
|
|
@ -6,7 +6,6 @@ package unittest
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -16,7 +15,9 @@ import (
|
||||||
"code.gitea.io/gitea/models/system"
|
"code.gitea.io/gitea/models/system"
|
||||||
"code.gitea.io/gitea/modules/auth/password/hash"
|
"code.gitea.io/gitea/modules/auth/password/hash"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/setting/config"
|
"code.gitea.io/gitea/modules/setting/config"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
@ -45,6 +46,14 @@ func fatalTestError(fmtStr string, args ...any) {
|
||||||
|
|
||||||
// InitSettings initializes config provider and load common settings for tests
|
// InitSettings initializes config provider and load common settings for tests
|
||||||
func InitSettings() {
|
func InitSettings() {
|
||||||
|
setting.IsInTesting = true
|
||||||
|
log.OsExiter = func(code int) {
|
||||||
|
if code != 0 {
|
||||||
|
// non-zero exit code (log.Fatal) shouldn't occur during testing, if it happens, show a full stacktrace for more details
|
||||||
|
panic(fmt.Errorf("non-zero exit code during testing: %d", code))
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
if setting.CustomConf == "" {
|
if setting.CustomConf == "" {
|
||||||
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
|
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
|
||||||
_ = os.Remove(setting.CustomConf)
|
_ = os.Remove(setting.CustomConf)
|
||||||
|
@ -53,7 +62,7 @@ func InitSettings() {
|
||||||
setting.LoadCommonSettings()
|
setting.LoadCommonSettings()
|
||||||
|
|
||||||
if err := setting.PrepareAppDataPath(); err != nil {
|
if err := setting.PrepareAppDataPath(); err != nil {
|
||||||
log.Fatalf("Can not prepare APP_DATA_PATH: %v", err)
|
log.Fatal("Can not prepare APP_DATA_PATH: %v", err)
|
||||||
}
|
}
|
||||||
// register the dummy hash algorithm function used in the test fixtures
|
// register the dummy hash algorithm function used in the test fixtures
|
||||||
_ = hash.Register("dummy", hash.NewDummyHasher)
|
_ = hash.Register("dummy", hash.NewDummyHasher)
|
||||||
|
@ -106,6 +115,7 @@ func MainTest(m *testing.M, testOpts ...*TestOptions) {
|
||||||
fatalTestError("Error creating test engine: %v\n", err)
|
fatalTestError("Error creating test engine: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setting.IsInTesting = true
|
||||||
setting.AppURL = "https://try.gitea.io/"
|
setting.AppURL = "https://try.gitea.io/"
|
||||||
setting.RunUser = "runuser"
|
setting.RunUser = "runuser"
|
||||||
setting.SSH.User = "sshuser"
|
setting.SSH.User = "sshuser"
|
||||||
|
@ -148,6 +158,9 @@ func MainTest(m *testing.M, testOpts ...*TestOptions) {
|
||||||
|
|
||||||
config.SetDynGetter(system.NewDatabaseDynKeyGetter())
|
config.SetDynGetter(system.NewDatabaseDynKeyGetter())
|
||||||
|
|
||||||
|
if err = cache.Init(); err != nil {
|
||||||
|
fatalTestError("cache.Init: %v\n", err)
|
||||||
|
}
|
||||||
if err = storage.Init(); err != nil {
|
if err = storage.Init(); err != nil {
|
||||||
fatalTestError("storage.Init: %v\n", err)
|
fatalTestError("storage.Init: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -501,19 +501,19 @@ func GetUserSalt() (string, error) {
|
||||||
// Note: The set of characters here can safely expand without a breaking change,
|
// Note: The set of characters here can safely expand without a breaking change,
|
||||||
// but characters removed from this set can cause user account linking to break
|
// but characters removed from this set can cause user account linking to break
|
||||||
var (
|
var (
|
||||||
customCharsReplacement = strings.NewReplacer("Æ", "AE")
|
customCharsReplacement = strings.NewReplacer("Æ", "AE")
|
||||||
removeCharsRE = regexp.MustCompile(`['´\x60]`)
|
removeCharsRE = regexp.MustCompile("['`´]")
|
||||||
removeDiacriticsTransform = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
|
transformDiacritics = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
|
||||||
replaceCharsHyphenRE = regexp.MustCompile(`[\s~+]`)
|
replaceCharsHyphenRE = regexp.MustCompile(`[\s~+]`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// normalizeUserName returns a string with single-quotes and diacritics
|
// NormalizeUserName only takes the name part if it is an email address, transforms it diacritics to ASCII characters.
|
||||||
// removed, and any other non-supported username characters replaced with
|
// It returns a string with the single-quotes removed, and any other non-supported username characters are replaced with a `-` character
|
||||||
// a `-` character
|
|
||||||
func NormalizeUserName(s string) (string, error) {
|
func NormalizeUserName(s string) (string, error) {
|
||||||
strDiacriticsRemoved, n, err := transform.String(removeDiacriticsTransform, customCharsReplacement.Replace(s))
|
s, _, _ = strings.Cut(s, "@")
|
||||||
|
strDiacriticsRemoved, n, err := transform.String(transformDiacritics, customCharsReplacement.Replace(s))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Failed to normalize character `%v` in provided username `%v`", s[n], s)
|
return "", fmt.Errorf("failed to normalize the string of provided username %q at position %d", s, n)
|
||||||
}
|
}
|
||||||
return replaceCharsHyphenRE.ReplaceAllLiteralString(removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil
|
return replaceCharsHyphenRE.ReplaceAllLiteralString(removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -506,15 +506,16 @@ func Test_NormalizeUserFromEmail(t *testing.T) {
|
||||||
Expected string
|
Expected string
|
||||||
IsNormalizedValid bool
|
IsNormalizedValid bool
|
||||||
}{
|
}{
|
||||||
{"test", "test", true},
|
{"name@example.com", "name", true},
|
||||||
|
{"test'`´name", "testname", true},
|
||||||
{"Sinéad.O'Connor", "Sinead.OConnor", true},
|
{"Sinéad.O'Connor", "Sinead.OConnor", true},
|
||||||
{"Æsir", "AEsir", true},
|
{"Æsir", "AEsir", true},
|
||||||
// \u00e9\u0065\u0301
|
{"éé", "ee", true}, // \u00e9\u0065\u0301
|
||||||
{"éé", "ee", true},
|
|
||||||
{"Awareness Hub", "Awareness-Hub", true},
|
{"Awareness Hub", "Awareness-Hub", true},
|
||||||
{"double__underscore", "double__underscore", false}, // We should consider squashing double non-alpha characters
|
{"double__underscore", "double__underscore", false}, // We should consider squashing double non-alpha characters
|
||||||
{".bad.", ".bad.", false},
|
{".bad.", ".bad.", false},
|
||||||
{"new😀user", "new😀user", false}, // No plans to support
|
{"new😀user", "new😀user", false}, // No plans to support
|
||||||
|
{`"quoted"`, `"quoted"`, false}, // No plans to support
|
||||||
}
|
}
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
normalizedName, err := user_model.NormalizeUserName(testCase.Input)
|
normalizedName, err := user_model.NormalizeUserName(testCase.Input)
|
||||||
|
|
|
@ -33,7 +33,6 @@ type ObjectFormat interface {
|
||||||
ComputeHash(t ObjectType, content []byte) ObjectID
|
ComputeHash(t ObjectType, content []byte) ObjectID
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SHA1 Type */
|
|
||||||
type Sha1ObjectFormatImpl struct{}
|
type Sha1ObjectFormatImpl struct{}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -70,14 +69,10 @@ func (h Sha1ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) ObjectID
|
||||||
_, _ = hasher.Write([]byte(" "))
|
_, _ = hasher.Write([]byte(" "))
|
||||||
_, _ = hasher.Write([]byte(strconv.FormatInt(int64(len(content)), 10)))
|
_, _ = hasher.Write([]byte(strconv.FormatInt(int64(len(content)), 10)))
|
||||||
_, _ = hasher.Write([]byte{0})
|
_, _ = hasher.Write([]byte{0})
|
||||||
|
_, _ = hasher.Write(content)
|
||||||
// HashSum generates a SHA1 for the provided hash
|
return h.MustID(hasher.Sum(nil))
|
||||||
var sha1 Sha1Hash
|
|
||||||
copy(sha1[:], hasher.Sum(nil))
|
|
||||||
return &sha1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SHA256 Type */
|
|
||||||
type Sha256ObjectFormatImpl struct{}
|
type Sha256ObjectFormatImpl struct{}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -116,11 +111,8 @@ func (h Sha256ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) Object
|
||||||
_, _ = hasher.Write([]byte(" "))
|
_, _ = hasher.Write([]byte(" "))
|
||||||
_, _ = hasher.Write([]byte(strconv.FormatInt(int64(len(content)), 10)))
|
_, _ = hasher.Write([]byte(strconv.FormatInt(int64(len(content)), 10)))
|
||||||
_, _ = hasher.Write([]byte{0})
|
_, _ = hasher.Write([]byte{0})
|
||||||
|
_, _ = hasher.Write(content)
|
||||||
// HashSum generates a SHA256 for the provided hash
|
return h.MustID(hasher.Sum(nil))
|
||||||
var sha256 Sha1Hash
|
|
||||||
copy(sha256[:], hasher.Sum(nil))
|
|
||||||
return &sha256
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -16,7 +16,6 @@ type ObjectID interface {
|
||||||
Type() ObjectFormat
|
Type() ObjectFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SHA1 */
|
|
||||||
type Sha1Hash [20]byte
|
type Sha1Hash [20]byte
|
||||||
|
|
||||||
func (h *Sha1Hash) String() string {
|
func (h *Sha1Hash) String() string {
|
||||||
|
@ -40,7 +39,6 @@ func MustIDFromString(hexHash string) ObjectID {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SHA256 */
|
|
||||||
type Sha256Hash [32]byte
|
type Sha256Hash [32]byte
|
||||||
|
|
||||||
func (h *Sha256Hash) String() string {
|
func (h *Sha256Hash) String() string {
|
||||||
|
@ -54,7 +52,6 @@ func (h *Sha256Hash) IsZero() bool {
|
||||||
func (h *Sha256Hash) RawValue() []byte { return h[:] }
|
func (h *Sha256Hash) RawValue() []byte { return h[:] }
|
||||||
func (*Sha256Hash) Type() ObjectFormat { return Sha256ObjectFormat }
|
func (*Sha256Hash) Type() ObjectFormat { return Sha256ObjectFormat }
|
||||||
|
|
||||||
/* utility */
|
|
||||||
func NewIDFromString(hexHash string) (ObjectID, error) {
|
func NewIDFromString(hexHash string) (ObjectID, error) {
|
||||||
var theObjectFormat ObjectFormat
|
var theObjectFormat ObjectFormat
|
||||||
for _, objectFormat := range SupportedObjectFormats {
|
for _, objectFormat := range SupportedObjectFormats {
|
||||||
|
|
|
@ -18,4 +18,8 @@ func TestIsValidSHAPattern(t *testing.T) {
|
||||||
assert.False(t, h.IsValid("abc"))
|
assert.False(t, h.IsValid("abc"))
|
||||||
assert.False(t, h.IsValid("123g"))
|
assert.False(t, h.IsValid("123g"))
|
||||||
assert.False(t, h.IsValid("some random text"))
|
assert.False(t, h.IsValid("some random text"))
|
||||||
|
assert.Equal(t, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", ComputeBlobHash(Sha1ObjectFormat, nil).String())
|
||||||
|
assert.Equal(t, "2e65efe2a145dda7ee51d1741299f848e5bf752e", ComputeBlobHash(Sha1ObjectFormat, []byte("a")).String())
|
||||||
|
assert.Equal(t, "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813", ComputeBlobHash(Sha256ObjectFormat, nil).String())
|
||||||
|
assert.Equal(t, "eb337bcee2061c5313c9a1392116b6c76039e9e30d71467ae359b36277e17dc7", ComputeBlobHash(Sha256ObjectFormat, []byte("a")).String())
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package pipeline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LFSResult represents commits found using a provided pointer file hash
|
||||||
|
type LFSResult struct {
|
||||||
|
Name string
|
||||||
|
SHA string
|
||||||
|
Summary string
|
||||||
|
When time.Time
|
||||||
|
ParentHashes []git.ObjectID
|
||||||
|
BranchName string
|
||||||
|
FullCommitName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type lfsResultSlice []*LFSResult
|
||||||
|
|
||||||
|
func (a lfsResultSlice) Len() int { return len(a) }
|
||||||
|
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
|
||||||
|
|
||||||
|
func lfsError(msg string, err error) error {
|
||||||
|
return fmt.Errorf("LFS error occurred, %s: err: %w", msg, err)
|
||||||
|
}
|
|
@ -7,12 +7,10 @@ package pipeline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
|
||||||
|
@ -21,23 +19,6 @@ import (
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LFSResult represents commits found using a provided pointer file hash
|
|
||||||
type LFSResult struct {
|
|
||||||
Name string
|
|
||||||
SHA string
|
|
||||||
Summary string
|
|
||||||
When time.Time
|
|
||||||
ParentHashes []git.ObjectID
|
|
||||||
BranchName string
|
|
||||||
FullCommitName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type lfsResultSlice []*LFSResult
|
|
||||||
|
|
||||||
func (a lfsResultSlice) Len() int { return len(a) }
|
|
||||||
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
|
|
||||||
|
|
||||||
// FindLFSFile finds commits that contain a provided pointer file hash
|
// FindLFSFile finds commits that contain a provided pointer file hash
|
||||||
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
||||||
resultsMap := map[string]*LFSResult{}
|
resultsMap := map[string]*LFSResult{}
|
||||||
|
@ -51,7 +32,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
All: true,
|
All: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to get GoGit CommitsIter. Error: %w", err)
|
return nil, lfsError("failed to get GoGit CommitsIter", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
|
err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
|
||||||
|
@ -85,7 +66,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
return nil, fmt.Errorf("Failure in CommitIter.ForEach: %w", err)
|
return nil, lfsError("failure in CommitIter.ForEach", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, result := range resultsMap {
|
for _, result := range resultsMap {
|
||||||
|
@ -156,7 +137,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
select {
|
select {
|
||||||
case err, has := <-errChan:
|
case err, has := <-errChan:
|
||||||
if has {
|
if has {
|
||||||
return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err)
|
return nil, lfsError("unable to obtain name for LFS files", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
|
@ -8,33 +8,14 @@ package pipeline
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LFSResult represents commits found using a provided pointer file hash
|
|
||||||
type LFSResult struct {
|
|
||||||
Name string
|
|
||||||
SHA string
|
|
||||||
Summary string
|
|
||||||
When time.Time
|
|
||||||
ParentIDs []git.ObjectID
|
|
||||||
BranchName string
|
|
||||||
FullCommitName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type lfsResultSlice []*LFSResult
|
|
||||||
|
|
||||||
func (a lfsResultSlice) Len() int { return len(a) }
|
|
||||||
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
|
|
||||||
|
|
||||||
// FindLFSFile finds commits that contain a provided pointer file hash
|
// FindLFSFile finds commits that contain a provided pointer file hash
|
||||||
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
||||||
resultsMap := map[string]*LFSResult{}
|
resultsMap := map[string]*LFSResult{}
|
||||||
|
@ -137,11 +118,11 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
n += int64(count)
|
n += int64(count)
|
||||||
if bytes.Equal(binObjectID, objectID.RawValue()) {
|
if bytes.Equal(binObjectID, objectID.RawValue()) {
|
||||||
result := LFSResult{
|
result := LFSResult{
|
||||||
Name: curPath + string(fname),
|
Name: curPath + string(fname),
|
||||||
SHA: curCommit.ID.String(),
|
SHA: curCommit.ID.String(),
|
||||||
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
|
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
|
||||||
When: curCommit.Author.When,
|
When: curCommit.Author.When,
|
||||||
ParentIDs: curCommit.Parents,
|
ParentHashes: curCommit.Parents,
|
||||||
}
|
}
|
||||||
resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
|
resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
|
||||||
} else if string(mode) == git.EntryModeTree.String() {
|
} else if string(mode) == git.EntryModeTree.String() {
|
||||||
|
@ -183,7 +164,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
|
|
||||||
for _, result := range resultsMap {
|
for _, result := range resultsMap {
|
||||||
hasParent := false
|
hasParent := false
|
||||||
for _, parentID := range result.ParentIDs {
|
for _, parentID := range result.ParentHashes {
|
||||||
if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent {
|
if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -240,7 +221,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||||
select {
|
select {
|
||||||
case err, has := <-errChan:
|
case err, has := <-errChan:
|
||||||
if has {
|
if has {
|
||||||
return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err)
|
return nil, lfsError("unable to obtain name for LFS files", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,7 +184,7 @@ func (ref RefName) RefGroup() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefType returns the simple ref type of the reference, e.g. branch, tag
|
// RefType returns the simple ref type of the reference, e.g. branch, tag
|
||||||
// It's differrent from RefGroup, which is using the name of the directory under .git/refs
|
// It's different from RefGroup, which is using the name of the directory under .git/refs
|
||||||
// Here we using branch but not heads, using tag but not tags
|
// Here we using branch but not heads, using tag but not tags
|
||||||
func (ref RefName) RefType() string {
|
func (ref RefName) RefType() string {
|
||||||
var refType string
|
var refType string
|
||||||
|
|
|
@ -57,11 +57,13 @@ func Critical(format string, v ...any) {
|
||||||
Log(1, ERROR, format, v...)
|
Log(1, ERROR, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var OsExiter = os.Exit
|
||||||
|
|
||||||
// Fatal records fatal log and exit process
|
// Fatal records fatal log and exit process
|
||||||
func Fatal(format string, v ...any) {
|
func Fatal(format string, v ...any) {
|
||||||
Log(1, FATAL, format, v...)
|
Log(1, FATAL, format, v...)
|
||||||
GetManager().Close()
|
GetManager().Close()
|
||||||
os.Exit(1)
|
OsExiter(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLogger(name string) Logger {
|
func GetLogger(name string) Logger {
|
||||||
|
|
|
@ -134,7 +134,7 @@ func (pm *Manager) AddTypedContext(parent context.Context, description, processT
|
||||||
//
|
//
|
||||||
// Most processes will not need to use the cancel function but there will be cases whereby you want to cancel the process but not immediately remove it from the
|
// Most processes will not need to use the cancel function but there will be cases whereby you want to cancel the process but not immediately remove it from the
|
||||||
// process table.
|
// process table.
|
||||||
func (pm *Manager) AddContextTimeout(parent context.Context, timeout time.Duration, description string) (ctx context.Context, cancel context.CancelFunc, finshed FinishedFunc) {
|
func (pm *Manager) AddContextTimeout(parent context.Context, timeout time.Duration, description string) (ctx context.Context, cancel context.CancelFunc, finished FinishedFunc) {
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
// it's meaningless to use timeout <= 0, and it must be a bug! so we must panic here to tell developers to make the timeout correct
|
// it's meaningless to use timeout <= 0, and it must be a bug! so we must panic here to tell developers to make the timeout correct
|
||||||
panic("the timeout must be greater than zero, otherwise the context will be cancelled immediately")
|
panic("the timeout must be greater than zero, otherwise the context will be cancelled immediately")
|
||||||
|
@ -142,9 +142,9 @@ func (pm *Manager) AddContextTimeout(parent context.Context, timeout time.Durati
|
||||||
|
|
||||||
ctx, cancel = context.WithTimeout(parent, timeout)
|
ctx, cancel = context.WithTimeout(parent, timeout)
|
||||||
|
|
||||||
ctx, _, finshed = pm.Add(ctx, description, cancel, NormalProcessType, true)
|
ctx, _, finished = pm.Add(ctx, description, cancel, NormalProcessType, true)
|
||||||
|
|
||||||
return ctx, cancel, finshed
|
return ctx, cancel, finished
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add create a new process
|
// Add create a new process
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitea.com/go-chi/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockStore struct {
|
||||||
|
*session.MemStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockStore) Destroy(writer http.ResponseWriter, request *http.Request) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockStoreContextKeyStruct struct{}
|
||||||
|
|
||||||
|
var MockStoreContextKey = mockStoreContextKeyStruct{}
|
||||||
|
|
||||||
|
func NewMockStore(sid string) *MockStore {
|
||||||
|
return &MockStore{session.NewMemStore(sid)}
|
||||||
|
}
|
|
@ -6,6 +6,8 @@ package session
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"gitea.com/go-chi/session"
|
"gitea.com/go-chi/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,6 +16,10 @@ type Store interface {
|
||||||
Get(any) any
|
Get(any) any
|
||||||
Set(any, any) error
|
Set(any, any) error
|
||||||
Delete(any) error
|
Delete(any) error
|
||||||
|
ID() string
|
||||||
|
Release() error
|
||||||
|
Flush() error
|
||||||
|
Destroy(http.ResponseWriter, *http.Request) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegenerateSession regenerates the underlying session and returns the new store
|
// RegenerateSession regenerates the underlying session and returns the new store
|
||||||
|
@ -21,8 +27,21 @@ func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, erro
|
||||||
for _, f := range BeforeRegenerateSession {
|
for _, f := range BeforeRegenerateSession {
|
||||||
f(resp, req)
|
f(resp, req)
|
||||||
}
|
}
|
||||||
s, err := session.RegenerateSession(resp, req)
|
if setting.IsInTesting {
|
||||||
return s, err
|
if store, ok := req.Context().Value(MockStoreContextKey).(*MockStore); ok {
|
||||||
|
return store, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return session.RegenerateSession(resp, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetContextSession(req *http.Request) Store {
|
||||||
|
if setting.IsInTesting {
|
||||||
|
if store, ok := req.Context().Value(MockStoreContextKey).(*MockStore); ok {
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return session.GetSession(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeRegenerateSession is a list of functions that are called before a session is regenerated.
|
// BeforeRegenerateSession is a list of functions that are called before a session is regenerated.
|
||||||
|
|
|
@ -16,14 +16,10 @@ import (
|
||||||
type OAuth2UsernameType string
|
type OAuth2UsernameType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// OAuth2UsernameUserid oauth2 userid field will be used as gitea name
|
OAuth2UsernameUserid OAuth2UsernameType = "userid" // use user id (sub) field as gitea's username
|
||||||
OAuth2UsernameUserid OAuth2UsernameType = "userid"
|
OAuth2UsernameNickname OAuth2UsernameType = "nickname" // use nickname field
|
||||||
// OAuth2UsernameNickname oauth2 nickname field will be used as gitea name
|
OAuth2UsernameEmail OAuth2UsernameType = "email" // use email field
|
||||||
OAuth2UsernameNickname OAuth2UsernameType = "nickname"
|
OAuth2UsernamePreferredUsername OAuth2UsernameType = "preferred_username" // use preferred_username field
|
||||||
// OAuth2UsernameEmail username of oauth2 email field will be used as gitea name
|
|
||||||
OAuth2UsernameEmail OAuth2UsernameType = "email"
|
|
||||||
// OAuth2UsernameEmail username of oauth2 preferred_username field will be used as gitea name
|
|
||||||
OAuth2UsernamePreferredUsername OAuth2UsernameType = "preferred_username"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (username OAuth2UsernameType) isValid() bool {
|
func (username OAuth2UsernameType) isValid() bool {
|
||||||
|
@ -71,8 +67,8 @@ func loadOAuth2ClientFrom(rootCfg ConfigProvider) {
|
||||||
OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool()
|
OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool()
|
||||||
OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname)))
|
OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname)))
|
||||||
if !OAuth2Client.Username.isValid() {
|
if !OAuth2Client.Username.isValid() {
|
||||||
log.Warn("Username setting is not valid: '%s', will fallback to '%s'", OAuth2Client.Username, OAuth2UsernameNickname)
|
|
||||||
OAuth2Client.Username = OAuth2UsernameNickname
|
OAuth2Client.Username = OAuth2UsernameNickname
|
||||||
|
log.Warn("[oauth2_client].USERNAME setting is invalid, falls back to %q", OAuth2Client.Username)
|
||||||
}
|
}
|
||||||
OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool()
|
OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool()
|
||||||
OAuth2Client.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingLogin)))
|
OAuth2Client.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingLogin)))
|
||||||
|
|
|
@ -49,9 +49,9 @@ func TestSubjectBodySeparator(t *testing.T) {
|
||||||
test("Multiple\n---\n-------\n---\nSeparators",
|
test("Multiple\n---\n-------\n---\nSeparators",
|
||||||
"Multiple\n",
|
"Multiple\n",
|
||||||
"\n-------\n---\nSeparators")
|
"\n-------\n---\nSeparators")
|
||||||
test("Insuficient\n--\nSeparators",
|
test("Insufficient\n--\nSeparators",
|
||||||
"",
|
"",
|
||||||
"Insuficient\n--\nSeparators")
|
"Insufficient\n--\nSeparators")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSEscapeSafe(t *testing.T) {
|
func TestJSEscapeSafe(t *testing.T) {
|
||||||
|
|
|
@ -436,6 +436,7 @@ oauth_signin_submit = Link Account
|
||||||
oauth.signin.error = There was an error processing the authorization request. If this error persists, please contact the site administrator.
|
oauth.signin.error = There was an error processing the authorization request. If this error persists, please contact the site administrator.
|
||||||
oauth.signin.error.access_denied = The authorization request was denied.
|
oauth.signin.error.access_denied = The authorization request was denied.
|
||||||
oauth.signin.error.temporarily_unavailable = Authorization failed because the authentication server is temporarily unavailable. Please try again later.
|
oauth.signin.error.temporarily_unavailable = Authorization failed because the authentication server is temporarily unavailable. Please try again later.
|
||||||
|
oauth_callback_unable_auto_reg = Auto Registration is enabled, but OAuth2 Provider %[1]s returned missing fields: %[2]s, unable to create an account automatically, please create or link to an account, or contact the site administrator.
|
||||||
openid_connect_submit = Connect
|
openid_connect_submit = Connect
|
||||||
openid_connect_title = Connect to an existing account
|
openid_connect_title = Connect to an existing account
|
||||||
openid_connect_desc = The chosen OpenID URI is unknown. Associate it with a new account here.
|
openid_connect_desc = The chosen OpenID URI is unknown. Associate it with a new account here.
|
||||||
|
|
|
@ -436,6 +436,7 @@ oauth_signin_submit=Vincular conta
|
||||||
oauth.signin.error=Ocorreu um erro durante o processamento do pedido de autorização. Se este erro persistir, contacte o administrador.
|
oauth.signin.error=Ocorreu um erro durante o processamento do pedido de autorização. Se este erro persistir, contacte o administrador.
|
||||||
oauth.signin.error.access_denied=O pedido de autorização foi negado.
|
oauth.signin.error.access_denied=O pedido de autorização foi negado.
|
||||||
oauth.signin.error.temporarily_unavailable=A autorização falhou porque o servidor de autenticação está temporariamente indisponível. Tente mais tarde.
|
oauth.signin.error.temporarily_unavailable=A autorização falhou porque o servidor de autenticação está temporariamente indisponível. Tente mais tarde.
|
||||||
|
oauth_callback_unable_auto_reg=O registo automático está habilitado, mas o fornecedor OAuth2 %[1]s sinalizou campos em falta: %[2]s, por isso não foi possível criar uma conta automaticamente. Crie ou vincule uma conta ou contacte o administrador do sítio.
|
||||||
openid_connect_submit=Estabelecer ligação
|
openid_connect_submit=Estabelecer ligação
|
||||||
openid_connect_title=Estabelecer ligação a uma conta existente
|
openid_connect_title=Estabelecer ligação a uma conta existente
|
||||||
openid_connect_desc=O URI do OpenID escolhido é desconhecido. Associe-o a uma nova conta aqui.
|
openid_connect_desc=O URI do OpenID escolhido é desconhecido. Associe-o a uma nova conta aqui.
|
||||||
|
@ -763,6 +764,8 @@ manage_themes=Escolher o tema padrão
|
||||||
manage_openid=Gerir endereços OpenID
|
manage_openid=Gerir endereços OpenID
|
||||||
email_desc=O seu endereço de email principal irá ser usado para notificações, recuperação de senha e, desde que não esteja oculto, operações Git baseados na web.
|
email_desc=O seu endereço de email principal irá ser usado para notificações, recuperação de senha e, desde que não esteja oculto, operações Git baseados na web.
|
||||||
theme_desc=Este será o seu tema padrão em todo o sítio.
|
theme_desc=Este será o seu tema padrão em todo o sítio.
|
||||||
|
theme_colorblindness_help=Suporte a temas para daltónicos
|
||||||
|
theme_colorblindness_prompt=O Gitea acabou de obter alguns temas com suporte básico para daltónicos que têm apenas algumas cores definidas. O trabalho ainda está em andamento. Poderiam ser feitos mais melhoramentos se fossem definidas mais cores nos ficheiros CSS do tema.
|
||||||
primary=Principal
|
primary=Principal
|
||||||
activated=Em uso
|
activated=Em uso
|
||||||
requires_activation=Tem que ser habilitado
|
requires_activation=Tem que ser habilitado
|
||||||
|
@ -2356,7 +2359,7 @@ settings.protected_branch.delete_rule=Eliminar regra
|
||||||
settings.protected_branch_can_push=Permitir envios?
|
settings.protected_branch_can_push=Permitir envios?
|
||||||
settings.protected_branch_can_push_yes=Pode enviar
|
settings.protected_branch_can_push_yes=Pode enviar
|
||||||
settings.protected_branch_can_push_no=Não pode enviar
|
settings.protected_branch_can_push_no=Não pode enviar
|
||||||
settings.branch_protection=Salvaguarda do ramo '<b>%s</b>'
|
settings.branch_protection=Regras de salvaguarda do ramo '<b>%s</b>'
|
||||||
settings.protect_this_branch=Habilitar salvaguarda do ramo
|
settings.protect_this_branch=Habilitar salvaguarda do ramo
|
||||||
settings.protect_this_branch_desc=Impede a eliminação e restringe envios e integrações do Git no ramo.
|
settings.protect_this_branch_desc=Impede a eliminação e restringe envios e integrações do Git no ramo.
|
||||||
settings.protect_disable_push=Desabilitar envios
|
settings.protect_disable_push=Desabilitar envios
|
||||||
|
@ -2400,7 +2403,7 @@ settings.protect_patterns=Padrões
|
||||||
settings.protect_protected_file_patterns=Padrões de ficheiros protegidos (separados com ponto e vírgula ';'):
|
settings.protect_protected_file_patterns=Padrões de ficheiros protegidos (separados com ponto e vírgula ';'):
|
||||||
settings.protect_protected_file_patterns_desc=Ficheiros protegidos não podem ser modificados imediatamente, mesmo que o utilizador tenha direitos para adicionar, editar ou eliminar ficheiros neste ramo. Múltiplos padrões podem ser separados com ponto e vírgula (';'). Veja a documentação em <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
settings.protect_protected_file_patterns_desc=Ficheiros protegidos não podem ser modificados imediatamente, mesmo que o utilizador tenha direitos para adicionar, editar ou eliminar ficheiros neste ramo. Múltiplos padrões podem ser separados com ponto e vírgula (';'). Veja a documentação em <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||||
settings.protect_unprotected_file_patterns=Padrões de ficheiros desprotegidos (separados com ponto e vírgula ';'):
|
settings.protect_unprotected_file_patterns=Padrões de ficheiros desprotegidos (separados com ponto e vírgula ';'):
|
||||||
settings.protect_unprotected_file_patterns_desc=Ficheiros desprotegidos que podem ser modificados imediatamente se o utilizador tiver direitos de escrita, contornando a restrição no envio. Múltiplos padrões podem ser separados com ponto e vírgula (';'). Veja a documentação em <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
settings.protect_unprotected_file_patterns_desc=Ficheiros desprotegidos que podem ser modificados imediatamente se o utilizador tiver direitos de escrita, contornando a restrição no envio. Padrões múltiplos podem ser separados com ponto e vírgula (';'). Veja a documentação em <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||||
settings.add_protected_branch=Habilitar salvaguarda
|
settings.add_protected_branch=Habilitar salvaguarda
|
||||||
settings.delete_protected_branch=Desabilitar salvaguarda
|
settings.delete_protected_branch=Desabilitar salvaguarda
|
||||||
settings.update_protect_branch_success=A salvaguarda do ramo "%s" foi modificada.
|
settings.update_protect_branch_success=A salvaguarda do ramo "%s" foi modificada.
|
||||||
|
@ -2416,7 +2419,7 @@ settings.block_outdated_branch=Bloquear integração se o pedido de integração
|
||||||
settings.block_outdated_branch_desc=A integração não será possível quando o ramo de topo estiver abaixo do ramo base.
|
settings.block_outdated_branch_desc=A integração não será possível quando o ramo de topo estiver abaixo do ramo base.
|
||||||
settings.default_branch_desc=Escolha um ramo do repositório como sendo o predefinido para pedidos de integração e cometimentos:
|
settings.default_branch_desc=Escolha um ramo do repositório como sendo o predefinido para pedidos de integração e cometimentos:
|
||||||
settings.merge_style_desc=Estilos de integração
|
settings.merge_style_desc=Estilos de integração
|
||||||
settings.default_merge_style_desc=Tipo de integração predefinido para pedidos de integração:
|
settings.default_merge_style_desc=Tipo de integração predefinido
|
||||||
settings.choose_branch=Escolha um ramo…
|
settings.choose_branch=Escolha um ramo…
|
||||||
settings.no_protected_branch=Não existem ramos protegidos.
|
settings.no_protected_branch=Não existem ramos protegidos.
|
||||||
settings.edit_protected_branch=Editar
|
settings.edit_protected_branch=Editar
|
||||||
|
@ -2786,7 +2789,7 @@ self_check=Auto-verificação
|
||||||
identity_access=Identidade e acesso
|
identity_access=Identidade e acesso
|
||||||
users=Contas de utilizador
|
users=Contas de utilizador
|
||||||
organizations=Organizações
|
organizations=Organizações
|
||||||
assets=Recursos de código
|
assets=Recursos do código-fonte
|
||||||
repositories=Repositórios
|
repositories=Repositórios
|
||||||
hooks=Automatismos web
|
hooks=Automatismos web
|
||||||
integrations=Integrações
|
integrations=Integrações
|
||||||
|
@ -2867,14 +2870,14 @@ dashboard.mspan_structures_obtained=Estruturas MSpan obtidas
|
||||||
dashboard.mcache_structures_usage=Uso das estruturas MCache
|
dashboard.mcache_structures_usage=Uso das estruturas MCache
|
||||||
dashboard.mcache_structures_obtained=Estruturas MCache obtidas
|
dashboard.mcache_structures_obtained=Estruturas MCache obtidas
|
||||||
dashboard.profiling_bucket_hash_table_obtained=Perfil obtido da tabela de hash do balde
|
dashboard.profiling_bucket_hash_table_obtained=Perfil obtido da tabela de hash do balde
|
||||||
dashboard.gc_metadata_obtained=Metadados da recolha de lixo obtidos
|
dashboard.gc_metadata_obtained=Metadados obtidos da recolha de lixo
|
||||||
dashboard.other_system_allocation_obtained=Outras alocações de sistema obtidas
|
dashboard.other_system_allocation_obtained=Outras alocações de sistema obtidas
|
||||||
dashboard.next_gc_recycle=Próxima reciclagem da recolha de lixo
|
dashboard.next_gc_recycle=Próxima reciclagem da recolha de lixo
|
||||||
dashboard.last_gc_time=Tempo decorrido desde a última recolha de lixo
|
dashboard.last_gc_time=Tempo decorrido desde a última recolha de lixo
|
||||||
dashboard.total_gc_time=Pausa total da recolha de lixo
|
dashboard.total_gc_time=Pausa total da recolha de lixo
|
||||||
dashboard.total_gc_pause=Pausa total da recolha de lixo
|
dashboard.total_gc_pause=Pausa total da recolha de lixo
|
||||||
dashboard.last_gc_pause=Última pausa da recolha de lixo
|
dashboard.last_gc_pause=Última pausa da recolha de lixo
|
||||||
dashboard.gc_times=Tempos da recolha de lixo
|
dashboard.gc_times=N.º de recolhas de lixo
|
||||||
dashboard.delete_old_actions=Eliminar todas as operações antigas da base de dados
|
dashboard.delete_old_actions=Eliminar todas as operações antigas da base de dados
|
||||||
dashboard.delete_old_actions.started=Foi iniciado o processo de eliminação de todas as operações antigas da base de dados.
|
dashboard.delete_old_actions.started=Foi iniciado o processo de eliminação de todas as operações antigas da base de dados.
|
||||||
dashboard.update_checker=Verificador de novas versões
|
dashboard.update_checker=Verificador de novas versões
|
||||||
|
@ -3023,7 +3026,7 @@ auths.attribute_surname=Atributo do Sobrenome
|
||||||
auths.attribute_mail=Atributo do email
|
auths.attribute_mail=Atributo do email
|
||||||
auths.attribute_ssh_public_key=Atributo da chave pública SSH
|
auths.attribute_ssh_public_key=Atributo da chave pública SSH
|
||||||
auths.attribute_avatar=Atributo do avatar
|
auths.attribute_avatar=Atributo do avatar
|
||||||
auths.attributes_in_bind=Buscar os atributos no contexto de Bind DN
|
auths.attributes_in_bind=Buscar atributos no contexto do Bind DN
|
||||||
auths.allow_deactivate_all=Permitir que um resultado de pesquisa vazio desabilite todos os utilizadores
|
auths.allow_deactivate_all=Permitir que um resultado de pesquisa vazio desabilite todos os utilizadores
|
||||||
auths.use_paged_search=Usar pesquisa paginada
|
auths.use_paged_search=Usar pesquisa paginada
|
||||||
auths.search_page_size=Tamanho da página
|
auths.search_page_size=Tamanho da página
|
||||||
|
@ -3222,7 +3225,7 @@ config.session_config=Configuração de sessão
|
||||||
config.session_provider=Fornecedor da sessão
|
config.session_provider=Fornecedor da sessão
|
||||||
config.provider_config=Configuração do fornecedor
|
config.provider_config=Configuração do fornecedor
|
||||||
config.cookie_name=Nome do cookie
|
config.cookie_name=Nome do cookie
|
||||||
config.gc_interval_time=Intervalo da recolha do lixo
|
config.gc_interval_time=Intervalo de tempo entre recolhas do lixo
|
||||||
config.session_life_time=Tempo de vida da sessão
|
config.session_life_time=Tempo de vida da sessão
|
||||||
config.https_only=Apenas HTTPS
|
config.https_only=Apenas HTTPS
|
||||||
config.cookie_life_time=Tempo de vida do cookie
|
config.cookie_life_time=Tempo de vida do cookie
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
|
@ -4,9 +4,9 @@
|
||||||
"node": ">= 18.0.0"
|
"node": ">= 18.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@citation-js/core": "0.7.9",
|
"@citation-js/core": "0.7.11",
|
||||||
"@citation-js/plugin-bibtex": "0.7.9",
|
"@citation-js/plugin-bibtex": "0.7.11",
|
||||||
"@citation-js/plugin-csl": "0.7.9",
|
"@citation-js/plugin-csl": "0.7.11",
|
||||||
"@citation-js/plugin-software-formats": "0.6.1",
|
"@citation-js/plugin-software-formats": "0.6.1",
|
||||||
"@github/markdown-toolbar-element": "2.2.3",
|
"@github/markdown-toolbar-element": "2.2.3",
|
||||||
"@github/relative-time-element": "4.4.0",
|
"@github/relative-time-element": "4.4.0",
|
||||||
|
@ -27,23 +27,23 @@
|
||||||
"esbuild-loader": "4.1.0",
|
"esbuild-loader": "4.1.0",
|
||||||
"escape-goat": "4.0.0",
|
"escape-goat": "4.0.0",
|
||||||
"fast-glob": "3.3.2",
|
"fast-glob": "3.3.2",
|
||||||
"htmx.org": "1.9.11",
|
"htmx.org": "1.9.12",
|
||||||
"idiomorph": "0.3.0",
|
"idiomorph": "0.3.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"katex": "0.16.10",
|
"katex": "0.16.10",
|
||||||
"license-checker-webpack-plugin": "0.2.1",
|
"license-checker-webpack-plugin": "0.2.1",
|
||||||
"mermaid": "10.9.0",
|
"mermaid": "10.9.0",
|
||||||
"mini-css-extract-plugin": "2.8.1",
|
"mini-css-extract-plugin": "2.9.0",
|
||||||
"minimatch": "9.0.4",
|
"minimatch": "9.0.4",
|
||||||
"monaco-editor": "0.47.0",
|
"monaco-editor": "0.48.0",
|
||||||
"monaco-editor-webpack-plugin": "7.1.0",
|
"monaco-editor-webpack-plugin": "7.1.0",
|
||||||
"pdfobject": "2.3.0",
|
"pdfobject": "2.3.0",
|
||||||
"postcss": "8.4.38",
|
"postcss": "8.4.38",
|
||||||
"postcss-loader": "8.1.1",
|
"postcss-loader": "8.1.1",
|
||||||
"postcss-nesting": "12.1.1",
|
"postcss-nesting": "12.1.2",
|
||||||
"pretty-ms": "9.0.0",
|
"pretty-ms": "9.0.0",
|
||||||
"sortablejs": "1.15.2",
|
"sortablejs": "1.15.2",
|
||||||
"swagger-ui-dist": "5.15.1",
|
"swagger-ui-dist": "5.17.2",
|
||||||
"tailwindcss": "3.4.3",
|
"tailwindcss": "3.4.3",
|
||||||
"temporal-polyfill": "0.2.4",
|
"temporal-polyfill": "0.2.4",
|
||||||
"throttle-debounce": "5.0.0",
|
"throttle-debounce": "5.0.0",
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
"tributejs": "5.1.3",
|
"tributejs": "5.1.3",
|
||||||
"uint8-to-base64": "0.2.0",
|
"uint8-to-base64": "0.2.0",
|
||||||
"vanilla-colorful": "0.7.2",
|
"vanilla-colorful": "0.7.2",
|
||||||
"vue": "3.4.21",
|
"vue": "3.4.25",
|
||||||
"vue-bar-graph": "2.0.0",
|
"vue-bar-graph": "2.0.0",
|
||||||
"vue-chartjs": "5.3.1",
|
"vue-chartjs": "5.3.1",
|
||||||
"vue-loader": "17.4.2",
|
"vue-loader": "17.4.2",
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
"@eslint-community/eslint-plugin-eslint-comments": "4.3.0",
|
"@eslint-community/eslint-plugin-eslint-comments": "4.3.0",
|
||||||
"@playwright/test": "1.43.1",
|
"@playwright/test": "1.43.1",
|
||||||
"@stoplight/spectral-cli": "6.11.1",
|
"@stoplight/spectral-cli": "6.11.1",
|
||||||
"@stylistic/eslint-plugin-js": "1.7.0",
|
"@stylistic/eslint-plugin-js": "1.7.2",
|
||||||
"@stylistic/stylelint-plugin": "2.1.1",
|
"@stylistic/stylelint-plugin": "2.1.1",
|
||||||
"@vitejs/plugin-vue": "5.0.4",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
|
@ -81,20 +81,20 @@
|
||||||
"eslint-plugin-unicorn": "52.0.0",
|
"eslint-plugin-unicorn": "52.0.0",
|
||||||
"eslint-plugin-vitest": "0.4.1",
|
"eslint-plugin-vitest": "0.4.1",
|
||||||
"eslint-plugin-vitest-globals": "1.5.0",
|
"eslint-plugin-vitest-globals": "1.5.0",
|
||||||
"eslint-plugin-vue": "9.24.1",
|
"eslint-plugin-vue": "9.25.0",
|
||||||
"eslint-plugin-vue-scoped-css": "2.8.0",
|
"eslint-plugin-vue-scoped-css": "2.8.0",
|
||||||
"eslint-plugin-wc": "2.1.0",
|
"eslint-plugin-wc": "2.1.0",
|
||||||
"happy-dom": "14.7.1",
|
"happy-dom": "14.7.1",
|
||||||
"markdownlint-cli": "0.39.0",
|
"markdownlint-cli": "0.39.0",
|
||||||
"postcss-html": "1.6.0",
|
"postcss-html": "1.6.0",
|
||||||
"stylelint": "16.3.1",
|
"stylelint": "16.4.0",
|
||||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||||
"stylelint-declaration-strict-value": "1.10.4",
|
"stylelint-declaration-strict-value": "1.10.4",
|
||||||
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||||
"svgo": "3.2.0",
|
"svgo": "3.2.0",
|
||||||
"updates": "16.0.1",
|
"updates": "16.0.1",
|
||||||
"vite-string-plugin": "1.1.5",
|
"vite-string-plugin": "1.2.0",
|
||||||
"vitest": "1.5.0"
|
"vitest": "1.5.2"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"defaults"
|
"defaults"
|
||||||
|
|
|
@ -301,7 +301,7 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// comfirmUploadArtifact comfirm upload artifact.
|
// comfirmUploadArtifact confirm upload artifact.
|
||||||
// if all chunks are uploaded, merge them to one file.
|
// if all chunks are uploaded, merge them to one file.
|
||||||
func (ar artifactRoutes) comfirmUploadArtifact(ctx *ArtifactContext) {
|
func (ar artifactRoutes) comfirmUploadArtifact(ctx *ArtifactContext) {
|
||||||
_, runID, ok := validateRunID(ctx)
|
_, runID, ok := validateRunID(ctx)
|
||||||
|
|
|
@ -36,7 +36,7 @@ var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unar
|
||||||
uuid := request.Header().Get(uuidHeaderKey)
|
uuid := request.Header().Get(uuidHeaderKey)
|
||||||
token := request.Header().Get(tokenHeaderKey)
|
token := request.Header().Get(tokenHeaderKey)
|
||||||
// TODO: version will be removed from request header after Gitea 1.20 released.
|
// TODO: version will be removed from request header after Gitea 1.20 released.
|
||||||
// And Gitea will not try to read version from reuqest header
|
// And Gitea will not try to read version from request header
|
||||||
version := request.Header().Get(versionHeaderKey)
|
version := request.Header().Get(versionHeaderKey)
|
||||||
|
|
||||||
runner, err := actions_model.GetRunnerByUUID(ctx, uuid)
|
runner, err := actions_model.GetRunnerByUUID(ctx, uuid)
|
||||||
|
@ -53,7 +53,7 @@ var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unar
|
||||||
cols := []string{"last_online"}
|
cols := []string{"last_online"}
|
||||||
|
|
||||||
// TODO: version will be removed from request header after Gitea 1.20 released.
|
// TODO: version will be removed from request header after Gitea 1.20 released.
|
||||||
// And Gitea will not try to read version from reuqest header
|
// And Gitea will not try to read version from request header
|
||||||
version, _ = util.SplitStringAtByteN(version, 64)
|
version, _ = util.SplitStringAtByteN(version, 64)
|
||||||
if !util.IsEmptyString(version) && runner.Version != version {
|
if !util.IsEmptyString(version) && runner.Version != version {
|
||||||
runner.Version = version
|
runner.Version = version
|
||||||
|
|
|
@ -19,7 +19,7 @@ The package registry code is divided into multiple modules to split the function
|
||||||
|
|
||||||
## Models
|
## Models
|
||||||
|
|
||||||
Every package registry implementation uses the same underlaying models:
|
Every package registry implementation uses the same underlying models:
|
||||||
|
|
||||||
| Model | Description |
|
| Model | Description |
|
||||||
| - | - |
|
| - | - |
|
||||||
|
|
|
@ -93,6 +93,7 @@ import (
|
||||||
"code.gitea.io/gitea/routers/api/v1/settings"
|
"code.gitea.io/gitea/routers/api/v1/settings"
|
||||||
"code.gitea.io/gitea/routers/api/v1/user"
|
"code.gitea.io/gitea/routers/api/v1/user"
|
||||||
"code.gitea.io/gitea/routers/common"
|
"code.gitea.io/gitea/routers/common"
|
||||||
|
"code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/auth"
|
"code.gitea.io/gitea/services/auth"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
@ -835,6 +836,34 @@ func Routes() *web.Route {
|
||||||
SignInRequired: setting.Service.RequireSignInView,
|
SignInRequired: setting.Service.RequireSignInView,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
addActionsRoutes := func(
|
||||||
|
m *web.Route,
|
||||||
|
reqChecker func(ctx *context.APIContext),
|
||||||
|
act actions.API,
|
||||||
|
) {
|
||||||
|
m.Group("/actions", func() {
|
||||||
|
m.Group("/secrets", func() {
|
||||||
|
m.Get("", reqToken(), reqChecker, act.ListActionsSecrets)
|
||||||
|
m.Combo("/{secretname}").
|
||||||
|
Put(reqToken(), reqChecker, bind(api.CreateOrUpdateSecretOption{}), act.CreateOrUpdateSecret).
|
||||||
|
Delete(reqToken(), reqChecker, act.DeleteSecret)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.Group("/variables", func() {
|
||||||
|
m.Get("", reqToken(), reqChecker, act.ListVariables)
|
||||||
|
m.Combo("/{variablename}").
|
||||||
|
Get(reqToken(), reqChecker, act.GetVariable).
|
||||||
|
Delete(reqToken(), reqChecker, act.DeleteVariable).
|
||||||
|
Post(reqToken(), reqChecker, bind(api.CreateVariableOption{}), act.CreateVariable).
|
||||||
|
Put(reqToken(), reqChecker, bind(api.UpdateVariableOption{}), act.UpdateVariable)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.Group("/runners", func() {
|
||||||
|
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
m.Group("", func() {
|
m.Group("", func() {
|
||||||
// Miscellaneous (no scope required)
|
// Miscellaneous (no scope required)
|
||||||
if setting.API.EnableSwagger {
|
if setting.API.EnableSwagger {
|
||||||
|
@ -1073,26 +1102,11 @@ func Routes() *web.Route {
|
||||||
m.Post("/accept", repo.AcceptTransfer)
|
m.Post("/accept", repo.AcceptTransfer)
|
||||||
m.Post("/reject", repo.RejectTransfer)
|
m.Post("/reject", repo.RejectTransfer)
|
||||||
}, reqToken())
|
}, reqToken())
|
||||||
m.Group("/actions", func() {
|
addActionsRoutes(
|
||||||
m.Group("/secrets", func() {
|
m,
|
||||||
m.Combo("/{secretname}").
|
reqOwner(),
|
||||||
Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret).
|
repo.NewAction(),
|
||||||
Delete(reqToken(), reqOwner(), repo.DeleteSecret)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
m.Group("/variables", func() {
|
|
||||||
m.Get("", reqToken(), reqOwner(), repo.ListVariables)
|
|
||||||
m.Combo("/{variablename}").
|
|
||||||
Get(reqToken(), reqOwner(), repo.GetVariable).
|
|
||||||
Delete(reqToken(), reqOwner(), repo.DeleteVariable).
|
|
||||||
Post(reqToken(), reqOwner(), bind(api.CreateVariableOption{}), repo.CreateVariable).
|
|
||||||
Put(reqToken(), reqOwner(), bind(api.UpdateVariableOption{}), repo.UpdateVariable)
|
|
||||||
})
|
|
||||||
|
|
||||||
m.Group("/runners", func() {
|
|
||||||
m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
m.Group("/hooks/git", func() {
|
m.Group("/hooks/git", func() {
|
||||||
m.Combo("").Get(repo.ListGitHooks)
|
m.Combo("").Get(repo.ListGitHooks)
|
||||||
m.Group("/{id}", func() {
|
m.Group("/{id}", func() {
|
||||||
|
@ -1460,27 +1474,11 @@ func Routes() *web.Route {
|
||||||
m.Combo("/{username}").Get(reqToken(), org.IsMember).
|
m.Combo("/{username}").Get(reqToken(), org.IsMember).
|
||||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
|
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
|
||||||
})
|
})
|
||||||
m.Group("/actions", func() {
|
addActionsRoutes(
|
||||||
m.Group("/secrets", func() {
|
m,
|
||||||
m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
|
reqOrgOwnership(),
|
||||||
m.Combo("/{secretname}").
|
org.NewAction(),
|
||||||
Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret).
|
)
|
||||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
|
|
||||||
})
|
|
||||||
|
|
||||||
m.Group("/variables", func() {
|
|
||||||
m.Get("", reqToken(), reqOrgOwnership(), org.ListVariables)
|
|
||||||
m.Combo("/{variablename}").
|
|
||||||
Get(reqToken(), reqOrgOwnership(), org.GetVariable).
|
|
||||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteVariable).
|
|
||||||
Post(reqToken(), reqOrgOwnership(), bind(api.CreateVariableOption{}), org.CreateVariable).
|
|
||||||
Put(reqToken(), reqOrgOwnership(), bind(api.UpdateVariableOption{}), org.UpdateVariable)
|
|
||||||
})
|
|
||||||
|
|
||||||
m.Group("/runners", func() {
|
|
||||||
m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
m.Group("/public_members", func() {
|
m.Group("/public_members", func() {
|
||||||
m.Get("", org.ListPublicMembers)
|
m.Get("", org.ListPublicMembers)
|
||||||
m.Combo("/{username}").Get(org.IsPublicMember).
|
m.Combo("/{username}").Get(org.IsPublicMember).
|
||||||
|
|
|
@ -9,16 +9,188 @@ import (
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
secret_model "code.gitea.io/gitea/models/secret"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/routers/api/v1/shared"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
actions_service "code.gitea.io/gitea/services/actions"
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
secret_service "code.gitea.io/gitea/services/secrets"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ListActionsSecrets list an organization's actions secrets
|
||||||
|
func (Action) ListActionsSecrets(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /orgs/{org}/actions/secrets organization orgListActionsSecrets
|
||||||
|
// ---
|
||||||
|
// summary: List an organization's actions secrets
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/SecretList"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
opts := &secret_model.FindSecretsOptions{
|
||||||
|
OwnerID: ctx.Org.Organization.ID,
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiSecrets := make([]*api.Secret, len(secrets))
|
||||||
|
for k, v := range secrets {
|
||||||
|
apiSecrets[k] = &api.Secret{
|
||||||
|
Name: v.Name,
|
||||||
|
Created: v.CreatedUnix.AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, apiSecrets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create or update one secret of the organization
|
||||||
|
func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret
|
||||||
|
// ---
|
||||||
|
// summary: Create or Update a secret value in an organization
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: secretname
|
||||||
|
// in: path
|
||||||
|
// description: name of the secret
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/CreateOrUpdateSecretOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// description: response when creating a secret
|
||||||
|
// "204":
|
||||||
|
// description: response when updating a secret
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
|
||||||
|
|
||||||
|
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"), opt.Data)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
|
||||||
|
} else if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if created {
|
||||||
|
ctx.Status(http.StatusCreated)
|
||||||
|
} else {
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSecret delete one secret of the organization
|
||||||
|
func (Action) DeleteSecret(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret
|
||||||
|
// ---
|
||||||
|
// summary: Delete a secret in an organization
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: secretname
|
||||||
|
// in: path
|
||||||
|
// description: name of the secret
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// description: delete one secret of the organization
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
|
||||||
|
} else if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "DeleteSecret", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
||||||
|
// GetRegistrationToken returns the token to register org runners
|
||||||
|
func (Action) GetRegistrationToken(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken
|
||||||
|
// ---
|
||||||
|
// summary: Get an organization's actions runner registration token
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/RegistrationToken"
|
||||||
|
|
||||||
|
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// ListVariables list org-level variables
|
// ListVariables list org-level variables
|
||||||
func ListVariables(ctx *context.APIContext) {
|
func (Action) ListVariables(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
|
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
|
||||||
// ---
|
// ---
|
||||||
// summary: Get an org-level variables list
|
// summary: Get an org-level variables list
|
||||||
|
@ -70,7 +242,7 @@ func ListVariables(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVariable get an org-level variable
|
// GetVariable get an org-level variable
|
||||||
func GetVariable(ctx *context.APIContext) {
|
func (Action) GetVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable
|
// swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Get an org-level variable
|
// summary: Get an org-level variable
|
||||||
|
@ -119,7 +291,7 @@ func GetVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVariable delete an org-level variable
|
// DeleteVariable delete an org-level variable
|
||||||
func DeleteVariable(ctx *context.APIContext) {
|
func (Action) DeleteVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable
|
// swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Delete an org-level variable
|
// summary: Delete an org-level variable
|
||||||
|
@ -163,7 +335,7 @@ func DeleteVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateVariable create an org-level variable
|
// CreateVariable create an org-level variable
|
||||||
func CreateVariable(ctx *context.APIContext) {
|
func (Action) CreateVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable
|
// swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Create an org-level variable
|
// summary: Create an org-level variable
|
||||||
|
@ -227,7 +399,7 @@ func CreateVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateVariable update an org-level variable
|
// UpdateVariable update an org-level variable
|
||||||
func UpdateVariable(ctx *context.APIContext) {
|
func (Action) UpdateVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable
|
// swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Update an org-level variable
|
// summary: Update an org-level variable
|
||||||
|
@ -289,3 +461,13 @@ func UpdateVariable(ctx *context.APIContext) {
|
||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ actions_service.API = new(Action)
|
||||||
|
|
||||||
|
// Action implements actions_service.API
|
||||||
|
type Action struct{}
|
||||||
|
|
||||||
|
// NewAction creates a new Action service
|
||||||
|
func NewAction() actions_service.API {
|
||||||
|
return Action{}
|
||||||
|
}
|
|
@ -1,31 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package org
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/gitea/routers/api/v1/shared"
|
|
||||||
"code.gitea.io/gitea/services/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
|
||||||
|
|
||||||
// GetRegistrationToken returns the token to register org runners
|
|
||||||
func GetRegistrationToken(ctx *context.APIContext) {
|
|
||||||
// swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken
|
|
||||||
// ---
|
|
||||||
// summary: Get an organization's actions runner registration token
|
|
||||||
// produces:
|
|
||||||
// - application/json
|
|
||||||
// parameters:
|
|
||||||
// - name: org
|
|
||||||
// in: path
|
|
||||||
// description: name of the organization
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// responses:
|
|
||||||
// "200":
|
|
||||||
// "$ref": "#/responses/RegistrationToken"
|
|
||||||
|
|
||||||
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package org
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
secret_model "code.gitea.io/gitea/models/secret"
|
|
||||||
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"
|
|
||||||
secret_service "code.gitea.io/gitea/services/secrets"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListActionsSecrets list an organization's actions secrets
|
|
||||||
func ListActionsSecrets(ctx *context.APIContext) {
|
|
||||||
// swagger:operation GET /orgs/{org}/actions/secrets organization orgListActionsSecrets
|
|
||||||
// ---
|
|
||||||
// summary: List an organization's actions secrets
|
|
||||||
// produces:
|
|
||||||
// - application/json
|
|
||||||
// parameters:
|
|
||||||
// - name: org
|
|
||||||
// in: path
|
|
||||||
// description: name of the organization
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// - name: page
|
|
||||||
// in: query
|
|
||||||
// description: page number of results to return (1-based)
|
|
||||||
// type: integer
|
|
||||||
// - name: limit
|
|
||||||
// in: query
|
|
||||||
// description: page size of results
|
|
||||||
// type: integer
|
|
||||||
// responses:
|
|
||||||
// "200":
|
|
||||||
// "$ref": "#/responses/SecretList"
|
|
||||||
// "404":
|
|
||||||
// "$ref": "#/responses/notFound"
|
|
||||||
|
|
||||||
opts := &secret_model.FindSecretsOptions{
|
|
||||||
OwnerID: ctx.Org.Organization.ID,
|
|
||||||
ListOptions: utils.GetListOptions(ctx),
|
|
||||||
}
|
|
||||||
|
|
||||||
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
|
|
||||||
if err != nil {
|
|
||||||
ctx.InternalServerError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
apiSecrets := make([]*api.Secret, len(secrets))
|
|
||||||
for k, v := range secrets {
|
|
||||||
apiSecrets[k] = &api.Secret{
|
|
||||||
Name: v.Name,
|
|
||||||
Created: v.CreatedUnix.AsTime(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.SetTotalCountHeader(count)
|
|
||||||
ctx.JSON(http.StatusOK, apiSecrets)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create or update one secret of the organization
|
|
||||||
func CreateOrUpdateSecret(ctx *context.APIContext) {
|
|
||||||
// swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret
|
|
||||||
// ---
|
|
||||||
// summary: Create or Update a secret value in an organization
|
|
||||||
// consumes:
|
|
||||||
// - application/json
|
|
||||||
// produces:
|
|
||||||
// - application/json
|
|
||||||
// parameters:
|
|
||||||
// - name: org
|
|
||||||
// in: path
|
|
||||||
// description: name of organization
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// - name: secretname
|
|
||||||
// in: path
|
|
||||||
// description: name of the secret
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// - name: body
|
|
||||||
// in: body
|
|
||||||
// schema:
|
|
||||||
// "$ref": "#/definitions/CreateOrUpdateSecretOption"
|
|
||||||
// responses:
|
|
||||||
// "201":
|
|
||||||
// description: response when creating a secret
|
|
||||||
// "204":
|
|
||||||
// description: response when updating a secret
|
|
||||||
// "400":
|
|
||||||
// "$ref": "#/responses/error"
|
|
||||||
// "404":
|
|
||||||
// "$ref": "#/responses/notFound"
|
|
||||||
|
|
||||||
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
|
|
||||||
|
|
||||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"), opt.Data)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, util.ErrInvalidArgument) {
|
|
||||||
ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
|
|
||||||
} else if errors.Is(err, util.ErrNotExist) {
|
|
||||||
ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
|
|
||||||
} else {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if created {
|
|
||||||
ctx.Status(http.StatusCreated)
|
|
||||||
} else {
|
|
||||||
ctx.Status(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSecret delete one secret of the organization
|
|
||||||
func DeleteSecret(ctx *context.APIContext) {
|
|
||||||
// swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret
|
|
||||||
// ---
|
|
||||||
// summary: Delete a secret in an organization
|
|
||||||
// consumes:
|
|
||||||
// - application/json
|
|
||||||
// produces:
|
|
||||||
// - application/json
|
|
||||||
// parameters:
|
|
||||||
// - name: org
|
|
||||||
// in: path
|
|
||||||
// description: name of organization
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// - name: secretname
|
|
||||||
// in: path
|
|
||||||
// description: name of the secret
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// responses:
|
|
||||||
// "204":
|
|
||||||
// description: delete one secret of the organization
|
|
||||||
// "400":
|
|
||||||
// "$ref": "#/responses/error"
|
|
||||||
// "404":
|
|
||||||
// "$ref": "#/responses/notFound"
|
|
||||||
|
|
||||||
err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"))
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, util.ErrInvalidArgument) {
|
|
||||||
ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
|
|
||||||
} else if errors.Is(err, util.ErrNotExist) {
|
|
||||||
ctx.Error(http.StatusNotFound, "DeleteSecret", err)
|
|
||||||
} else {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
|
||||||
}
|
|
|
@ -9,17 +9,76 @@ import (
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
secret_model "code.gitea.io/gitea/models/secret"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/routers/api/v1/shared"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
actions_service "code.gitea.io/gitea/services/actions"
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
secret_service "code.gitea.io/gitea/services/secrets"
|
secret_service "code.gitea.io/gitea/services/secrets"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ListActionsSecrets list an repo's actions secrets
|
||||||
|
func (Action) ListActionsSecrets(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/secrets repository repoListActionsSecrets
|
||||||
|
// ---
|
||||||
|
// summary: List an repo's actions secrets
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/SecretList"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
|
opts := &secret_model.FindSecretsOptions{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiSecrets := make([]*api.Secret, len(secrets))
|
||||||
|
for k, v := range secrets {
|
||||||
|
apiSecrets[k] = &api.Secret{
|
||||||
|
Name: v.Name,
|
||||||
|
Created: v.CreatedUnix.AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, apiSecrets)
|
||||||
|
}
|
||||||
|
|
||||||
// create or update one secret of the repository
|
// create or update one secret of the repository
|
||||||
func CreateOrUpdateSecret(ctx *context.APIContext) {
|
func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||||
// swagger:operation PUT /repos/{owner}/{repo}/actions/secrets/{secretname} repository updateRepoSecret
|
// swagger:operation PUT /repos/{owner}/{repo}/actions/secrets/{secretname} repository updateRepoSecret
|
||||||
// ---
|
// ---
|
||||||
// summary: Create or Update a secret value in a repository
|
// summary: Create or Update a secret value in a repository
|
||||||
|
@ -82,7 +141,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSecret delete one secret of the repository
|
// DeleteSecret delete one secret of the repository
|
||||||
func DeleteSecret(ctx *context.APIContext) {
|
func (Action) DeleteSecret(ctx *context.APIContext) {
|
||||||
// swagger:operation DELETE /repos/{owner}/{repo}/actions/secrets/{secretname} repository deleteRepoSecret
|
// swagger:operation DELETE /repos/{owner}/{repo}/actions/secrets/{secretname} repository deleteRepoSecret
|
||||||
// ---
|
// ---
|
||||||
// summary: Delete a secret in a repository
|
// summary: Delete a secret in a repository
|
||||||
|
@ -133,7 +192,7 @@ func DeleteSecret(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVariable get a repo-level variable
|
// GetVariable get a repo-level variable
|
||||||
func GetVariable(ctx *context.APIContext) {
|
func (Action) GetVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/actions/variables/{variablename} repository getRepoVariable
|
// swagger:operation GET /repos/{owner}/{repo}/actions/variables/{variablename} repository getRepoVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Get a repo-level variable
|
// summary: Get a repo-level variable
|
||||||
|
@ -186,7 +245,7 @@ func GetVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVariable delete a repo-level variable
|
// DeleteVariable delete a repo-level variable
|
||||||
func DeleteVariable(ctx *context.APIContext) {
|
func (Action) DeleteVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation DELETE /repos/{owner}/{repo}/actions/variables/{variablename} repository deleteRepoVariable
|
// swagger:operation DELETE /repos/{owner}/{repo}/actions/variables/{variablename} repository deleteRepoVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Delete a repo-level variable
|
// summary: Delete a repo-level variable
|
||||||
|
@ -235,7 +294,7 @@ func DeleteVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateVariable create a repo-level variable
|
// CreateVariable create a repo-level variable
|
||||||
func CreateVariable(ctx *context.APIContext) {
|
func (Action) CreateVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation POST /repos/{owner}/{repo}/actions/variables/{variablename} repository createRepoVariable
|
// swagger:operation POST /repos/{owner}/{repo}/actions/variables/{variablename} repository createRepoVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Create a repo-level variable
|
// summary: Create a repo-level variable
|
||||||
|
@ -302,7 +361,7 @@ func CreateVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateVariable update a repo-level variable
|
// UpdateVariable update a repo-level variable
|
||||||
func UpdateVariable(ctx *context.APIContext) {
|
func (Action) UpdateVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation PUT /repos/{owner}/{repo}/actions/variables/{variablename} repository updateRepoVariable
|
// swagger:operation PUT /repos/{owner}/{repo}/actions/variables/{variablename} repository updateRepoVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Update a repo-level variable
|
// summary: Update a repo-level variable
|
||||||
|
@ -369,7 +428,7 @@ func UpdateVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListVariables list repo-level variables
|
// ListVariables list repo-level variables
|
||||||
func ListVariables(ctx *context.APIContext) {
|
func (Action) ListVariables(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/actions/variables repository getRepoVariablesList
|
// swagger:operation GET /repos/{owner}/{repo}/actions/variables repository getRepoVariablesList
|
||||||
// ---
|
// ---
|
||||||
// summary: Get repo-level variables list
|
// summary: Get repo-level variables list
|
||||||
|
@ -423,3 +482,38 @@ func ListVariables(ctx *context.APIContext) {
|
||||||
ctx.SetTotalCountHeader(count)
|
ctx.SetTotalCountHeader(count)
|
||||||
ctx.JSON(http.StatusOK, variables)
|
ctx.JSON(http.StatusOK, variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRegistrationToken returns the token to register repo runners
|
||||||
|
func (Action) GetRegistrationToken(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken
|
||||||
|
// ---
|
||||||
|
// summary: Get a repository's actions runner registration token
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/RegistrationToken"
|
||||||
|
|
||||||
|
shared.GetRegistrationToken(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ actions_service.API = new(Action)
|
||||||
|
|
||||||
|
// Action implements actions_service.API
|
||||||
|
type Action struct{}
|
||||||
|
|
||||||
|
// NewAction creates a new Action service
|
||||||
|
func NewAction() actions_service.API {
|
||||||
|
return Action{}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -372,7 +373,11 @@ func CreatePullReview(ctx *context.APIContext) {
|
||||||
// create review and associate all pending review comments
|
// create review and associate all pending review comments
|
||||||
review, _, err := pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, opts.CommitID, nil)
|
review, _, err := pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, opts.CommitID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
|
if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,7 +465,11 @@ func SubmitPullReview(ctx *context.APIContext) {
|
||||||
// create review and associate all pending review comments
|
// create review and associate all pending review comments
|
||||||
review, _, err = pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, headCommitID, nil)
|
review, _, err = pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, headCommitID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
|
if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package repo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/gitea/routers/api/v1/shared"
|
|
||||||
"code.gitea.io/gitea/services/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetRegistrationToken returns the token to register repo runners
|
|
||||||
func GetRegistrationToken(ctx *context.APIContext) {
|
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken
|
|
||||||
// ---
|
|
||||||
// summary: Get a repository's actions runner registration token
|
|
||||||
// produces:
|
|
||||||
// - application/json
|
|
||||||
// parameters:
|
|
||||||
// - name: owner
|
|
||||||
// in: path
|
|
||||||
// description: owner of the repo
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// - name: repo
|
|
||||||
// in: path
|
|
||||||
// description: name of the repo
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// responses:
|
|
||||||
// "200":
|
|
||||||
// "$ref": "#/responses/RegistrationToken"
|
|
||||||
|
|
||||||
shared.GetRegistrationToken(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
|
|
||||||
}
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegistrationToken is response related to registeration token
|
// RegistrationToken is response related to registration token
|
||||||
// swagger:response RegistrationToken
|
// swagger:response RegistrationToken
|
||||||
type RegistrationToken struct {
|
type RegistrationToken struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
|
|
|
@ -5,6 +5,7 @@ package routers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/modules/web/routing"
|
||||||
actions_router "code.gitea.io/gitea/routers/api/actions"
|
actions_router "code.gitea.io/gitea/routers/api/actions"
|
||||||
packages_router "code.gitea.io/gitea/routers/api/packages"
|
packages_router "code.gitea.io/gitea/routers/api/packages"
|
||||||
apiv1 "code.gitea.io/gitea/routers/api/v1"
|
apiv1 "code.gitea.io/gitea/routers/api/v1"
|
||||||
|
@ -202,5 +204,9 @@ func NormalRoutes() *web.Route {
|
||||||
r.Mount(prefix, actions_router.ArtifactsV4Routes(prefix))
|
r.Mount(prefix, actions_router.ArtifactsV4Routes(prefix))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.NotFound(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
routing.UpdateFuncInfo(req.Context(), routing.GetFuncInfo(http.NotFound, "GlobalNotFound"))
|
||||||
|
http.NotFound(w, req)
|
||||||
|
})
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
@ -359,7 +359,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Error("Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v", ctx.opts.UserID, branchName, repo, pr.Index, err)
|
log.Error("Unable to check if mergeable: protected branch %s in %-v and pr #%d. Error: %v", ctx.opts.UserID, branchName, repo, pr.Index, err)
|
||||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||||
Err: fmt.Sprintf("Unable to get status of pull request %d. Error: %v", ctx.opts.PullRequestID, err),
|
Err: fmt.Sprintf("Unable to get status of pull request %d. Error: %v", ctx.opts.PullRequestID, err),
|
||||||
})
|
})
|
||||||
|
|
|
@ -30,7 +30,7 @@ func Organizations(ctx *context.Context) {
|
||||||
explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{
|
explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Type: user_model.UserTypeOrganization,
|
Type: user_model.UserTypeOrganization,
|
||||||
IncludeReserved: true, // administrator needs to list all acounts include reserved
|
IncludeReserved: true, // administrator needs to list all accounts include reserved
|
||||||
ListOptions: db.ListOptions{
|
ListOptions: db.ListOptions{
|
||||||
PageSize: setting.UI.Admin.OrgPagingNum,
|
PageSize: setting.UI.Admin.OrgPagingNum,
|
||||||
},
|
},
|
||||||
|
|
|
@ -81,7 +81,7 @@ func Users(ctx *context.Context) {
|
||||||
IsRestricted: util.OptionalBoolParse(statusFilterMap["is_restricted"]),
|
IsRestricted: util.OptionalBoolParse(statusFilterMap["is_restricted"]),
|
||||||
IsTwoFactorEnabled: util.OptionalBoolParse(statusFilterMap["is_2fa_enabled"]),
|
IsTwoFactorEnabled: util.OptionalBoolParse(statusFilterMap["is_2fa_enabled"]),
|
||||||
IsProhibitLogin: util.OptionalBoolParse(statusFilterMap["is_prohibit_login"]),
|
IsProhibitLogin: util.OptionalBoolParse(statusFilterMap["is_prohibit_login"]),
|
||||||
IncludeReserved: true, // administrator needs to list all acounts include reserved, bot, remote ones
|
IncludeReserved: true, // administrator needs to list all accounts include reserved, bot, remote ones
|
||||||
ExtraParamStrings: extraParamStrings,
|
ExtraParamStrings: extraParamStrings,
|
||||||
}, tplUsers)
|
}, tplUsers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -382,17 +382,17 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
|
||||||
return setting.AppSubURL + "/"
|
return setting.AppSubURL + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserName(gothUser *goth.User) (string, error) {
|
// extractUserNameFromOAuth2 tries to extract a normalized username from the given OAuth2 user.
|
||||||
|
// It returns ("", nil) if the required field doesn't exist.
|
||||||
|
func extractUserNameFromOAuth2(gothUser *goth.User) (string, error) {
|
||||||
switch setting.OAuth2Client.Username {
|
switch setting.OAuth2Client.Username {
|
||||||
case setting.OAuth2UsernameEmail:
|
case setting.OAuth2UsernameEmail:
|
||||||
return user_model.NormalizeUserName(strings.Split(gothUser.Email, "@")[0])
|
return user_model.NormalizeUserName(gothUser.Email)
|
||||||
case setting.OAuth2UsernamePreferredUsername:
|
case setting.OAuth2UsernamePreferredUsername:
|
||||||
preferredUsername, exists := gothUser.RawData["preferred_username"]
|
if preferredUsername, ok := gothUser.RawData["preferred_username"].(string); ok {
|
||||||
if exists {
|
return user_model.NormalizeUserName(preferredUsername)
|
||||||
return user_model.NormalizeUserName(preferredUsername.(string))
|
|
||||||
} else {
|
|
||||||
return "", fmt.Errorf("preferred_username is missing in received user data but configured as username source for user_id %q. Check if OPENID_CONNECT_SCOPES contains profile", gothUser.UserID)
|
|
||||||
}
|
}
|
||||||
|
return "", nil
|
||||||
case setting.OAuth2UsernameNickname:
|
case setting.OAuth2UsernameNickname:
|
||||||
return user_model.NormalizeUserName(gothUser.NickName)
|
return user_model.NormalizeUserName(gothUser.NickName)
|
||||||
default: // OAuth2UsernameUserid
|
default: // OAuth2UsernameUserid
|
||||||
|
|
|
@ -8,12 +8,31 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/session"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||||
"code.gitea.io/gitea/services/contexttest"
|
"code.gitea.io/gitea/services/contexttest"
|
||||||
|
|
||||||
|
"github.com/markbates/goth"
|
||||||
|
"github.com/markbates/goth/gothic"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func addOAuth2Source(t *testing.T, authName string, cfg oauth2.Source) {
|
||||||
|
cfg.Provider = util.IfZero(cfg.Provider, "gitea")
|
||||||
|
err := auth_model.CreateSource(db.DefaultContext, &auth_model.Source{
|
||||||
|
Type: auth_model.OAuth2,
|
||||||
|
Name: authName,
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &cfg,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUserLogin(t *testing.T) {
|
func TestUserLogin(t *testing.T) {
|
||||||
ctx, resp := contexttest.MockContext(t, "/user/login")
|
ctx, resp := contexttest.MockContext(t, "/user/login")
|
||||||
SignIn(ctx)
|
SignIn(ctx)
|
||||||
|
@ -41,3 +60,24 @@ func TestUserLogin(t *testing.T) {
|
||||||
SignIn(ctx)
|
SignIn(ctx)
|
||||||
assert.Equal(t, "/", test.RedirectURL(resp))
|
assert.Equal(t, "/", test.RedirectURL(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSignUpOAuth2ButMissingFields(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
|
||||||
|
defer test.MockVariableValue(&gothic.CompleteUserAuth, func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
||||||
|
return goth.User{Provider: "dummy-auth-source", UserID: "dummy-user"}, nil
|
||||||
|
})()
|
||||||
|
|
||||||
|
addOAuth2Source(t, "dummy-auth-source", oauth2.Source{})
|
||||||
|
|
||||||
|
mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockStore("dummy-sid")}
|
||||||
|
ctx, resp := contexttest.MockContext(t, "/user/oauth2/dummy-auth-source/callback?code=dummy-code", mockOpt)
|
||||||
|
ctx.SetParams("provider", "dummy-auth-source")
|
||||||
|
SignInOAuthCallback(ctx)
|
||||||
|
assert.Equal(t, http.StatusSeeOther, resp.Code)
|
||||||
|
assert.Equal(t, "/user/link_account", test.RedirectURL(resp))
|
||||||
|
|
||||||
|
// then the user will be redirected to the link account page, and see a message about the missing fields
|
||||||
|
ctx, _ = contexttest.MockContext(t, "/user/link_account", mockOpt)
|
||||||
|
LinkAccount(ctx)
|
||||||
|
assert.EqualValues(t, "auth.oauth_callback_unable_auto_reg:dummy-auth-source,email", ctx.Data["AutoRegistrationFailedPrompt"])
|
||||||
|
}
|
||||||
|
|
|
@ -48,23 +48,27 @@ func LinkAccount(ctx *context.Context) {
|
||||||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
|
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
|
||||||
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
|
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
|
||||||
|
|
||||||
gothUser := ctx.Session.Get("linkAccountGothUser")
|
gothUser, ok := ctx.Session.Get("linkAccountGothUser").(goth.User)
|
||||||
if gothUser == nil {
|
if !ok {
|
||||||
ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
|
// no account in session, so just redirect to the login page, then the user could restart the process
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/login")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gu, _ := gothUser.(goth.User)
|
if missingFields, ok := gothUser.RawData["__giteaAutoRegMissingFields"].([]string); ok {
|
||||||
uname, err := getUserName(&gu)
|
ctx.Data["AutoRegistrationFailedPrompt"] = ctx.Tr("auth.oauth_callback_unable_auto_reg", gothUser.Provider, strings.Join(missingFields, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
uname, err := extractUserNameFromOAuth2(&gothUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("UserSignIn", err)
|
ctx.ServerError("UserSignIn", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
email := gu.Email
|
email := gothUser.Email
|
||||||
ctx.Data["user_name"] = uname
|
ctx.Data["user_name"] = uname
|
||||||
ctx.Data["email"] = email
|
ctx.Data["email"] = email
|
||||||
|
|
||||||
if len(email) != 0 {
|
if email != "" {
|
||||||
u, err := user_model.GetUserByEmail(ctx, email)
|
u, err := user_model.GetUserByEmail(ctx, email)
|
||||||
if err != nil && !user_model.IsErrUserNotExist(err) {
|
if err != nil && !user_model.IsErrUserNotExist(err) {
|
||||||
ctx.ServerError("UserSignIn", err)
|
ctx.ServerError("UserSignIn", err)
|
||||||
|
@ -73,7 +77,7 @@ func LinkAccount(ctx *context.Context) {
|
||||||
if u != nil {
|
if u != nil {
|
||||||
ctx.Data["user_exists"] = true
|
ctx.Data["user_exists"] = true
|
||||||
}
|
}
|
||||||
} else if len(uname) != 0 {
|
} else if uname != "" {
|
||||||
u, err := user_model.GetUserByName(ctx, uname)
|
u, err := user_model.GetUserByName(ctx, uname)
|
||||||
if err != nil && !user_model.IsErrUserNotExist(err) {
|
if err != nil && !user_model.IsErrUserNotExist(err) {
|
||||||
ctx.ServerError("UserSignIn", err)
|
ctx.ServerError("UserSignIn", err)
|
||||||
|
|
|
@ -934,7 +934,7 @@ func SignInOAuthCallback(ctx *context.Context) {
|
||||||
|
|
||||||
if u == nil {
|
if u == nil {
|
||||||
if ctx.Doer != nil {
|
if ctx.Doer != nil {
|
||||||
// attach user to already logged in user
|
// attach user to the current signed-in user
|
||||||
err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser)
|
err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("UserLinkAccount", err)
|
ctx.ServerError("UserLinkAccount", err)
|
||||||
|
@ -952,23 +952,32 @@ func SignInOAuthCallback(ctx *context.Context) {
|
||||||
if gothUser.Email == "" {
|
if gothUser.Email == "" {
|
||||||
missingFields = append(missingFields, "email")
|
missingFields = append(missingFields, "email")
|
||||||
}
|
}
|
||||||
if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname && gothUser.NickName == "" {
|
uname, err := extractUserNameFromOAuth2(&gothUser)
|
||||||
missingFields = append(missingFields, "nickname")
|
|
||||||
}
|
|
||||||
if len(missingFields) > 0 {
|
|
||||||
log.Error("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields)
|
|
||||||
if authSource.IsOAuth2() && authSource.Cfg.(*oauth2.Source).Provider == "openidConnect" {
|
|
||||||
log.Error("You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields")
|
|
||||||
}
|
|
||||||
err = fmt.Errorf("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields)
|
|
||||||
ctx.ServerError("CreateUser", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uname, err := getUserName(&gothUser)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("UserSignIn", err)
|
ctx.ServerError("UserSignIn", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if uname == "" {
|
||||||
|
if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname {
|
||||||
|
missingFields = append(missingFields, "nickname")
|
||||||
|
} else if setting.OAuth2Client.Username == setting.OAuth2UsernamePreferredUsername {
|
||||||
|
missingFields = append(missingFields, "preferred_username")
|
||||||
|
} // else: "UserID" and "Email" have been handled above separately
|
||||||
|
}
|
||||||
|
if len(missingFields) > 0 {
|
||||||
|
log.Error(`OAuth2 auto registration (ENABLE_AUTO_REGISTRATION) is enabled but OAuth2 provider %q doesn't return required fields: %s. `+
|
||||||
|
`Suggest to: disable auto registration, or make OPENID_CONNECT_SCOPES (for OpenIDConnect) / Authentication Source Scopes (for Admin panel) to request all required fields, and the fields shouldn't be empty.`,
|
||||||
|
authSource.Name, strings.Join(missingFields, ","))
|
||||||
|
// The RawData is the only way to pass the missing fields to the another page at the moment, other ways all have various problems:
|
||||||
|
// by session or cookie: difficult to clean or reset; by URL: could be injected with uncontrollable content; by ctx.Flash: the link_account page is a mess ...
|
||||||
|
// Since the RawData is for the provider's data, so we need to use our own prefix here to avoid conflict.
|
||||||
|
if gothUser.RawData == nil {
|
||||||
|
gothUser.RawData = make(map[string]any)
|
||||||
|
}
|
||||||
|
gothUser.RawData["__giteaAutoRegMissingFields"] = missingFields
|
||||||
|
showLinkingLogin(ctx, gothUser)
|
||||||
|
return
|
||||||
|
}
|
||||||
u = &user_model.User{
|
u = &user_model.User{
|
||||||
Name: uname,
|
Name: uname,
|
||||||
FullName: gothUser.Name,
|
FullName: gothUser.Name,
|
||||||
|
|
|
@ -67,6 +67,9 @@ type ViewResponse struct {
|
||||||
CanRerun bool `json:"canRerun"`
|
CanRerun bool `json:"canRerun"`
|
||||||
CanDeleteArtifact bool `json:"canDeleteArtifact"`
|
CanDeleteArtifact bool `json:"canDeleteArtifact"`
|
||||||
Done bool `json:"done"`
|
Done bool `json:"done"`
|
||||||
|
WorkflowID string `json:"workflowID"`
|
||||||
|
WorkflowLink string `json:"workflowLink"`
|
||||||
|
IsSchedule bool `json:"isSchedule"`
|
||||||
Jobs []*ViewJob `json:"jobs"`
|
Jobs []*ViewJob `json:"jobs"`
|
||||||
Commit ViewCommit `json:"commit"`
|
Commit ViewCommit `json:"commit"`
|
||||||
} `json:"run"`
|
} `json:"run"`
|
||||||
|
@ -90,12 +93,10 @@ type ViewJob struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ViewCommit struct {
|
type ViewCommit struct {
|
||||||
LocaleCommit string `json:"localeCommit"`
|
ShortSha string `json:"shortSHA"`
|
||||||
LocalePushedBy string `json:"localePushedBy"`
|
Link string `json:"link"`
|
||||||
ShortSha string `json:"shortSHA"`
|
Pusher ViewUser `json:"pusher"`
|
||||||
Link string `json:"link"`
|
Branch ViewBranch `json:"branch"`
|
||||||
Pusher ViewUser `json:"pusher"`
|
|
||||||
Branch ViewBranch `json:"branch"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ViewUser struct {
|
type ViewUser struct {
|
||||||
|
@ -151,6 +152,9 @@ func ViewPost(ctx *context_module.Context) {
|
||||||
resp.State.Run.CanRerun = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
|
resp.State.Run.CanRerun = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
|
||||||
resp.State.Run.CanDeleteArtifact = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
|
resp.State.Run.CanDeleteArtifact = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
|
||||||
resp.State.Run.Done = run.Status.IsDone()
|
resp.State.Run.Done = run.Status.IsDone()
|
||||||
|
resp.State.Run.WorkflowID = run.WorkflowID
|
||||||
|
resp.State.Run.WorkflowLink = run.WorkflowLink()
|
||||||
|
resp.State.Run.IsSchedule = run.IsSchedule()
|
||||||
resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json
|
resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json
|
||||||
resp.State.Run.Status = run.Status.String()
|
resp.State.Run.Status = run.Status.String()
|
||||||
for _, v := range jobs {
|
for _, v := range jobs {
|
||||||
|
@ -172,12 +176,10 @@ func ViewPost(ctx *context_module.Context) {
|
||||||
Link: run.RefLink(),
|
Link: run.RefLink(),
|
||||||
}
|
}
|
||||||
resp.State.Run.Commit = ViewCommit{
|
resp.State.Run.Commit = ViewCommit{
|
||||||
LocaleCommit: ctx.Locale.TrString("actions.runs.commit"),
|
ShortSha: base.ShortSha(run.CommitSHA),
|
||||||
LocalePushedBy: ctx.Locale.TrString("actions.runs.pushed_by"),
|
Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
|
||||||
ShortSha: base.ShortSha(run.CommitSHA),
|
Pusher: pusher,
|
||||||
Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
|
Branch: branch,
|
||||||
Pusher: pusher,
|
|
||||||
Branch: branch,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var task *actions_model.ActionTask
|
var task *actions_model.ActionTask
|
||||||
|
|
|
@ -212,8 +212,6 @@ func SearchCommits(ctx *context.Context) {
|
||||||
|
|
||||||
// FileHistory show a file's reversions
|
// FileHistory show a file's reversions
|
||||||
func FileHistory(ctx *context.Context) {
|
func FileHistory(ctx *context.Context) {
|
||||||
ctx.Data["IsRepoToolbarCommits"] = true
|
|
||||||
|
|
||||||
fileName := ctx.Repo.TreePath
|
fileName := ctx.Repo.TreePath
|
||||||
if len(fileName) == 0 {
|
if len(fileName) == 0 {
|
||||||
Commits(ctx)
|
Commits(ctx)
|
||||||
|
|
|
@ -800,7 +800,6 @@ func CompareDiff(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + separator + base.ShortSha(afterCommitID)
|
ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + separator + base.ShortSha(afterCommitID)
|
||||||
|
|
||||||
ctx.Data["IsRepoToolbarCommits"] = true
|
|
||||||
ctx.Data["IsDiffCompare"] = true
|
ctx.Data["IsDiffCompare"] = true
|
||||||
_, templateErrs := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates)
|
_, templateErrs := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates)
|
||||||
|
|
||||||
|
@ -813,7 +812,7 @@ func CompareDiff(ctx *context.Context) {
|
||||||
// applicable if you have one commit to compare and that commit has a message.
|
// applicable if you have one commit to compare and that commit has a message.
|
||||||
// In that case the commit message will be prepend to the template body.
|
// In that case the commit message will be prepend to the template body.
|
||||||
if templateContent, ok := ctx.Data[pullRequestTemplateKey].(string); ok && templateContent != "" {
|
if templateContent, ok := ctx.Data[pullRequestTemplateKey].(string); ok && templateContent != "" {
|
||||||
// Re-use the same key as that's priortized over the "content" key.
|
// Re-use the same key as that's prioritized over the "content" key.
|
||||||
// Add two new lines between the content to ensure there's always at least
|
// Add two new lines between the content to ensure there's always at least
|
||||||
// one empty line between them.
|
// one empty line between them.
|
||||||
ctx.Data[pullRequestTemplateKey] = content + "\n\n" + templateContent
|
ctx.Data[pullRequestTemplateKey] = content + "\n\n" + templateContent
|
||||||
|
|
|
@ -1760,8 +1760,8 @@ func ViewIssue(ctx *context.Context) {
|
||||||
// drop error since times could be pruned from DB..
|
// drop error since times could be pruned from DB..
|
||||||
_ = comment.LoadTime(ctx)
|
_ = comment.LoadTime(ctx)
|
||||||
if comment.Content != "" {
|
if comment.Content != "" {
|
||||||
// Content before v1.21 did store the formated string instead of seconds,
|
// Content before v1.21 did store the formatted string instead of seconds,
|
||||||
// so "|" is used as delimeter to mark the new format
|
// so "|" is used as delimiter to mark the new format
|
||||||
if comment.Content[0] != '|' {
|
if comment.Content[0] != '|' {
|
||||||
// handle old time comments that have formatted text stored
|
// handle old time comments that have formatted text stored
|
||||||
comment.RenderedContent = templates.SanitizeHTML(comment.Content)
|
comment.RenderedContent = templates.SanitizeHTML(comment.Content)
|
||||||
|
@ -3149,13 +3149,10 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
oldContent := comment.Content
|
oldContent := comment.Content
|
||||||
comment.Content = ctx.FormString("content")
|
newContent := ctx.FormString("content")
|
||||||
if len(comment.Content) == 0 {
|
|
||||||
ctx.JSON(http.StatusOK, map[string]any{
|
// allow to save empty content
|
||||||
"content": "",
|
comment.Content = newContent
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
|
if err = issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
|
||||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||||
ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
|
ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
|
||||||
|
@ -3178,21 +3175,27 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := markdown.RenderString(&markup.RenderContext{
|
var renderedContent template.HTML
|
||||||
Links: markup.Links{
|
if comment.Content != "" {
|
||||||
Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
|
renderedContent, err = markdown.RenderString(&markup.RenderContext{
|
||||||
},
|
Links: markup.Links{
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
},
|
||||||
Ctx: ctx,
|
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
||||||
}, comment.Content)
|
GitRepo: ctx.Repo.GitRepo,
|
||||||
if err != nil {
|
Ctx: ctx,
|
||||||
ctx.ServerError("RenderString", err)
|
}, comment.Content)
|
||||||
return
|
if err != nil {
|
||||||
|
ctx.ServerError("RenderString", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
contentEmpty := fmt.Sprintf(`<span class="no-content">%s</span>`, ctx.Tr("repo.issues.no_content"))
|
||||||
|
renderedContent = template.HTML(contentEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, map[string]any{
|
ctx.JSON(http.StatusOK, map[string]any{
|
||||||
"content": content,
|
"content": renderedContent,
|
||||||
"attachments": attachmentsHTML(ctx, comment.Attachments, comment.Content),
|
"attachments": attachmentsHTML(ctx, comment.Attachments, comment.Content),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1225,7 +1225,6 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
|
ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
|
||||||
ctx.Data["PageIsComparePull"] = true
|
ctx.Data["PageIsComparePull"] = true
|
||||||
ctx.Data["IsDiffCompare"] = true
|
ctx.Data["IsDiffCompare"] = true
|
||||||
ctx.Data["IsRepoToolbarCommits"] = true
|
|
||||||
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
||||||
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||||
upload.AddUploadContext(ctx, "comment")
|
upload.AddUploadContext(ctx, "comment")
|
||||||
|
|
|
@ -264,6 +264,8 @@ func SubmitReview(ctx *context.Context) {
|
||||||
if issues_model.IsContentEmptyErr(err) {
|
if issues_model.IsContentEmptyErr(err) {
|
||||||
ctx.Flash.Error(ctx.Tr("repo.issues.review.content.empty"))
|
ctx.Flash.Error(ctx.Tr("repo.issues.review.content.empty"))
|
||||||
ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
|
ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
|
||||||
|
} else if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) {
|
||||||
|
ctx.Status(http.StatusUnprocessableEntity)
|
||||||
} else {
|
} else {
|
||||||
ctx.ServerError("SubmitReview", err)
|
ctx.ServerError("SubmitReview", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1612,7 +1612,7 @@ func registerRoutes(m *web.Route) {
|
||||||
|
|
||||||
m.NotFound(func(w http.ResponseWriter, req *http.Request) {
|
m.NotFound(func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := context.GetWebContext(req)
|
ctx := context.GetWebContext(req)
|
||||||
routing.UpdateFuncInfo(ctx, routing.GetFuncInfo(ctx.NotFound, "GlobalNotFound"))
|
routing.UpdateFuncInfo(ctx, routing.GetFuncInfo(ctx.NotFound, "WebNotFound"))
|
||||||
ctx.NotFound("", nil)
|
ctx.NotFound("", nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import "code.gitea.io/gitea/services/context"
|
||||||
|
|
||||||
|
// API for actions of a repository or organization
|
||||||
|
type API interface {
|
||||||
|
// ListActionsSecrets list secrets
|
||||||
|
ListActionsSecrets(*context.APIContext)
|
||||||
|
// CreateOrUpdateSecret create or update a secret
|
||||||
|
CreateOrUpdateSecret(*context.APIContext)
|
||||||
|
// DeleteSecret delete a secret
|
||||||
|
DeleteSecret(*context.APIContext)
|
||||||
|
// ListVariables list variables
|
||||||
|
ListVariables(*context.APIContext)
|
||||||
|
// GetVariable get a variable
|
||||||
|
GetVariable(*context.APIContext)
|
||||||
|
// DeleteVariable delete a variable
|
||||||
|
DeleteVariable(*context.APIContext)
|
||||||
|
// CreateVariable create a variable
|
||||||
|
CreateVariable(*context.APIContext)
|
||||||
|
// UpdateVariable update a variable
|
||||||
|
UpdateVariable(*context.APIContext)
|
||||||
|
// GetRegistrationToken get registration token
|
||||||
|
GetRegistrationToken(*context.APIContext)
|
||||||
|
}
|
|
@ -20,14 +20,13 @@ import (
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/httpcache"
|
"code.gitea.io/gitea/modules/httpcache"
|
||||||
|
"code.gitea.io/gitea/modules/session"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
web_types "code.gitea.io/gitea/modules/web/types"
|
web_types "code.gitea.io/gitea/modules/web/types"
|
||||||
|
|
||||||
"gitea.com/go-chi/session"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Render represents a template render
|
// Render represents a template render
|
||||||
|
@ -154,7 +153,7 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||||
base, baseCleanUp := NewBaseContext(resp, req)
|
base, baseCleanUp := NewBaseContext(resp, req)
|
||||||
defer baseCleanUp()
|
defer baseCleanUp()
|
||||||
ctx := NewWebContext(base, rnd, session.GetSession(req))
|
ctx := NewWebContext(base, rnd, session.GetContextSession(req))
|
||||||
|
|
||||||
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
||||||
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
|
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
|
||||||
|
|
|
@ -19,7 +19,9 @@ import (
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
|
"code.gitea.io/gitea/modules/session"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
@ -43,7 +45,8 @@ func mockRequest(t *testing.T, reqPath string) *http.Request {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockContextOption struct {
|
type MockContextOption struct {
|
||||||
Render context.Render
|
Render context.Render
|
||||||
|
SessionStore *session.MockStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockContext mock context for unit tests
|
// MockContext mock context for unit tests
|
||||||
|
@ -62,12 +65,17 @@ func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*cont
|
||||||
base.Data = middleware.GetContextData(req.Context())
|
base.Data = middleware.GetContextData(req.Context())
|
||||||
base.Locale = &translation.MockLocale{}
|
base.Locale = &translation.MockLocale{}
|
||||||
|
|
||||||
|
chiCtx := chi.NewRouteContext()
|
||||||
ctx := context.NewWebContext(base, opt.Render, nil)
|
ctx := context.NewWebContext(base, opt.Render, nil)
|
||||||
ctx.AppendContextValue(context.WebContextKey, ctx)
|
ctx.AppendContextValue(context.WebContextKey, ctx)
|
||||||
|
ctx.AppendContextValue(chi.RouteCtxKey, chiCtx)
|
||||||
|
if opt.SessionStore != nil {
|
||||||
|
ctx.AppendContextValue(session.MockStoreContextKey, opt.SessionStore)
|
||||||
|
ctx.Session = opt.SessionStore
|
||||||
|
}
|
||||||
|
ctx.Cache = cache.GetCache()
|
||||||
ctx.PageData = map[string]any{}
|
ctx.PageData = map[string]any{}
|
||||||
ctx.Data["PageStartTime"] = time.Now()
|
ctx.Data["PageStartTime"] = time.Now()
|
||||||
chiCtx := chi.NewRouteContext()
|
|
||||||
ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
|
|
||||||
return ctx, resp
|
return ctx, resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,8 @@ func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issu
|
||||||
c.Type == issues_model.CommentTypeStopTracking ||
|
c.Type == issues_model.CommentTypeStopTracking ||
|
||||||
c.Type == issues_model.CommentTypeDeleteTimeManual) &&
|
c.Type == issues_model.CommentTypeDeleteTimeManual) &&
|
||||||
c.Content[0] == '|' {
|
c.Content[0] == '|' {
|
||||||
// TimeTracking Comments from v1.21 on store the seconds instead of an formated string
|
// TimeTracking Comments from v1.21 on store the seconds instead of an formatted string
|
||||||
// so we check for the "|" delimeter and convert new to legacy format on demand
|
// so we check for the "|" delimiter and convert new to legacy format on demand
|
||||||
c.Content = util.SecToTime(c.Content[1:])
|
c.Content = util.SecToTime(c.Content[1:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,12 +229,12 @@ func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *use
|
||||||
return comment, teamReviewRequestNotify(ctx, issue, doer, reviewer, isAdd, comment)
|
return comment, teamReviewRequestNotify(ctx, issue, doer, reviewer, isAdd, comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReviewRequestNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewNotifers []*ReviewRequestNotifier) {
|
func ReviewRequestNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewNotifiers []*ReviewRequestNotifier) {
|
||||||
for _, reviewNotifer := range reviewNotifers {
|
for _, reviewNotifier := range reviewNotifiers {
|
||||||
if reviewNotifer.Reviwer != nil {
|
if reviewNotifier.Reviewer != nil {
|
||||||
notify_service.PullRequestReviewRequest(ctx, issue.Poster, issue, reviewNotifer.Reviwer, reviewNotifer.IsAdd, reviewNotifer.Comment)
|
notify_service.PullRequestReviewRequest(ctx, issue.Poster, issue, reviewNotifier.Reviewer, reviewNotifier.IsAdd, reviewNotifier.Comment)
|
||||||
} else if reviewNotifer.ReviewTeam != nil {
|
} else if reviewNotifier.ReviewTeam != nil {
|
||||||
if err := teamReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifer.ReviewTeam, reviewNotifer.IsAdd, reviewNotifer.Comment); err != nil {
|
if err := teamReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifier.ReviewTeam, reviewNotifier.IsAdd, reviewNotifier.Comment); err != nil {
|
||||||
log.Error("teamReviewRequestNotify: %v", err)
|
log.Error("teamReviewRequestNotify: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,17 +90,17 @@ func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_mode
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var reviewNotifers []*ReviewRequestNotifier
|
var reviewNotifiers []*ReviewRequestNotifier
|
||||||
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issues_model.HasWorkInProgressPrefix(title) {
|
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issues_model.HasWorkInProgressPrefix(title) {
|
||||||
var err error
|
var err error
|
||||||
reviewNotifers, err = PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest)
|
reviewNotifiers, err = PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("PullRequestCodeOwnersReview: %v", err)
|
log.Error("PullRequestCodeOwnersReview: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notify_service.IssueChangeTitle(ctx, doer, issue, oldTitle)
|
notify_service.IssueChangeTitle(ctx, doer, issue, oldTitle)
|
||||||
ReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifers)
|
ReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifiers)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ func getMergeBase(repo *git.Repository, pr *issues_model.PullRequest, baseBranch
|
||||||
type ReviewRequestNotifier struct {
|
type ReviewRequestNotifier struct {
|
||||||
Comment *issues_model.Comment
|
Comment *issues_model.Comment
|
||||||
IsAdd bool
|
IsAdd bool
|
||||||
Reviwer *user_model.User
|
Reviewer *user_model.User
|
||||||
ReviewTeam *org_model.Team
|
ReviewTeam *org_model.Team
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +124,9 @@ func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
notifiers = append(notifiers, &ReviewRequestNotifier{
|
notifiers = append(notifiers, &ReviewRequestNotifier{
|
||||||
Comment: comment,
|
Comment: comment,
|
||||||
IsAdd: true,
|
IsAdd: true,
|
||||||
Reviwer: u,
|
Reviewer: u,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
|
||||||
}
|
}
|
||||||
log.Trace("Doing: Update")
|
log.Trace("Doing: Update")
|
||||||
|
|
||||||
handler := func(idx int, bean any) error {
|
handler := func(bean any) error {
|
||||||
var repo *repo_model.Repository
|
var repo *repo_model.Repository
|
||||||
var mirrorType SyncType
|
var mirrorType SyncType
|
||||||
var referenceID int64
|
var referenceID int64
|
||||||
|
@ -91,7 +91,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
|
||||||
pullMirrorsRequested := 0
|
pullMirrorsRequested := 0
|
||||||
if pullLimit != 0 {
|
if pullLimit != 0 {
|
||||||
if err := repo_model.MirrorsIterate(ctx, pullLimit, func(idx int, bean any) error {
|
if err := repo_model.MirrorsIterate(ctx, pullLimit, func(idx int, bean any) error {
|
||||||
if err := handler(idx, bean); err != nil {
|
if err := handler(bean); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pullMirrorsRequested++
|
pullMirrorsRequested++
|
||||||
|
@ -105,7 +105,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
|
||||||
pushMirrorsRequested := 0
|
pushMirrorsRequested := 0
|
||||||
if pushLimit != 0 {
|
if pushLimit != 0 {
|
||||||
if err := repo_model.PushMirrorsIterate(ctx, pushLimit, func(idx int, bean any) error {
|
if err := repo_model.PushMirrorsIterate(ctx, pushLimit, func(idx int, bean any) error {
|
||||||
if err := handler(idx, bean); err != nil {
|
if err := handler(bean); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pushMirrorsRequested++
|
pushMirrorsRequested++
|
||||||
|
|
|
@ -466,7 +466,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
|
||||||
|
|
||||||
log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results))
|
log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results))
|
||||||
if len(results) > 0 {
|
if len(results) > 0 {
|
||||||
if ok := checkAndUpdateEmptyRepository(ctx, m, gitRepo, results); !ok {
|
if ok := checkAndUpdateEmptyRepository(ctx, m, results); !ok {
|
||||||
log.Error("SyncMirrors [repo: %-v]: checkAndUpdateEmptyRepository: %v", m.Repo, err)
|
log.Error("SyncMirrors [repo: %-v]: checkAndUpdateEmptyRepository: %v", m.Repo, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -564,7 +564,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, gitRepo *git.Repository, results []*mirrorSyncResult) bool {
|
func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, results []*mirrorSyncResult) bool {
|
||||||
if !m.Repo.IsEmpty {
|
if !m.Repo.IsEmpty {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,11 @@ import (
|
||||||
|
|
||||||
// DeleteOrganization completely and permanently deletes everything of organization.
|
// DeleteOrganization completely and permanently deletes everything of organization.
|
||||||
func DeleteOrganization(ctx context.Context, org *org_model.Organization, purge bool) error {
|
func DeleteOrganization(ctx context.Context, org *org_model.Organization, purge bool) error {
|
||||||
ctx, commiter, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer commiter.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
if purge {
|
if purge {
|
||||||
err := repo_service.DeleteOwnerRepositoriesDirectly(ctx, org.AsUser())
|
err := repo_service.DeleteOwnerRepositoriesDirectly(ctx, org.AsUser())
|
||||||
|
@ -52,7 +52,7 @@ func DeleteOrganization(ctx context.Context, org *org_model.Organization, purge
|
||||||
return fmt.Errorf("DeleteOrganization: %w", err)
|
return fmt.Errorf("DeleteOrganization: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := commiter.Commit(); err != nil {
|
if err := committer.Commit(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ const (
|
||||||
MergeCheckTypeAuto // Auto Merge (Scheduled Merge) After Checks Succeed
|
MergeCheckTypeAuto // Auto Merge (Scheduled Merge) After Checks Succeed
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckPullMergable check if the pull mergable based on all conditions (branch protection, merge options, ...)
|
// CheckPullMergable check if the pull mergeable based on all conditions (branch protection, merge options, ...)
|
||||||
func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, mergeCheckType MergeCheckType, adminSkipProtectionCheck bool) error {
|
func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, mergeCheckType MergeCheckType, adminSkipProtectionCheck bool) error {
|
||||||
return db.WithTx(stdCtx, func(ctx context.Context) error {
|
return db.WithTx(stdCtx, func(ctx context.Context) error {
|
||||||
if pr.HasMerged {
|
if pr.HasMerged {
|
||||||
|
|
|
@ -46,7 +46,7 @@ func getCommitIDsFromRepo(ctx context.Context, repo *repo_model.Repository, oldC
|
||||||
return commitIDs, isForcePush, err
|
return commitIDs, isForcePush, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find commits between new and old commit exclusing base branch commits
|
// Find commits between new and old commit excluding base branch commits
|
||||||
commits, err := gitRepo.CommitsBetweenNotBase(newCommit, oldCommit, baseBranch)
|
commits, err := gitRepo.CommitsBetweenNotBase(newCommit, oldCommit, baseBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
|
|
|
@ -77,7 +77,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
|
||||||
}
|
}
|
||||||
defer baseGitRepo.Close()
|
defer baseGitRepo.Close()
|
||||||
|
|
||||||
var reviewNotifers []*issue_service.ReviewRequestNotifier
|
var reviewNotifiers []*issue_service.ReviewRequestNotifier
|
||||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
if err := issues_model.NewPullRequest(ctx, repo, issue, labelIDs, uuids, pr); err != nil {
|
if err := issues_model.NewPullRequest(ctx, repo, issue, labelIDs, uuids, pr); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -137,7 +137,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
|
||||||
}
|
}
|
||||||
|
|
||||||
if !pr.IsWorkInProgress(ctx) {
|
if !pr.IsWorkInProgress(ctx) {
|
||||||
reviewNotifers, err = issue_service.PullRequestCodeOwnersReview(ctx, issue, pr)
|
reviewNotifiers, err = issue_service.PullRequestCodeOwnersReview(ctx, issue, pr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
|
||||||
}
|
}
|
||||||
baseGitRepo.Close() // close immediately to avoid notifications will open the repository again
|
baseGitRepo.Close() // close immediately to avoid notifications will open the repository again
|
||||||
|
|
||||||
issue_service.ReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifers)
|
issue_service.ReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifiers)
|
||||||
|
|
||||||
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content)
|
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -6,6 +6,7 @@ package pull
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -43,6 +44,9 @@ func (err ErrDismissRequestOnClosedPR) Unwrap() error {
|
||||||
return util.ErrPermissionDenied
|
return util.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrSubmitReviewOnClosedPR represents an error when an user tries to submit an approve or reject review associated to a closed or merged PR.
|
||||||
|
var ErrSubmitReviewOnClosedPR = errors.New("can't submit review for a closed or merged PR")
|
||||||
|
|
||||||
// checkInvalidation checks if the line of code comment got changed by another commit.
|
// checkInvalidation checks if the line of code comment got changed by another commit.
|
||||||
// If the line got changed the comment is going to be invalidated.
|
// If the line got changed the comment is going to be invalidated.
|
||||||
func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error {
|
func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error {
|
||||||
|
@ -293,6 +297,10 @@ func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repos
|
||||||
if reviewType != issues_model.ReviewTypeApprove && reviewType != issues_model.ReviewTypeReject {
|
if reviewType != issues_model.ReviewTypeApprove && reviewType != issues_model.ReviewTypeReject {
|
||||||
stale = false
|
stale = false
|
||||||
} else {
|
} else {
|
||||||
|
if issue.IsClosed {
|
||||||
|
return nil, nil, ErrSubmitReviewOnClosedPR
|
||||||
|
}
|
||||||
|
|
||||||
headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
|
headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
|
@ -38,12 +38,10 @@ func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheVal
|
||||||
if ok && statusStr != "" {
|
if ok && statusStr != "" {
|
||||||
var cv commitStatusCacheValue
|
var cv commitStatusCacheValue
|
||||||
err := json.Unmarshal([]byte(statusStr), &cv)
|
err := json.Unmarshal([]byte(statusStr), &cv)
|
||||||
if err == nil && cv.State != "" {
|
if err == nil {
|
||||||
return &cv
|
return &cv
|
||||||
}
|
}
|
||||||
if err != nil {
|
log.Warn("getCommitStatusCache: json.Unmarshal failed: %v", err)
|
||||||
log.Warn("getCommitStatusCache: json.Unmarshal failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -128,15 +126,22 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
|
||||||
// FindReposLastestCommitStatuses loading repository default branch latest combinded commit status with cache
|
// FindReposLastestCommitStatuses loading repository default branch latest combinded commit status with cache
|
||||||
func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
|
func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
|
||||||
results := make([]*git_model.CommitStatus, len(repos))
|
results := make([]*git_model.CommitStatus, len(repos))
|
||||||
|
allCached := true
|
||||||
for i, repo := range repos {
|
for i, repo := range repos {
|
||||||
if cv := getCommitStatusCache(repo.ID, repo.DefaultBranch); cv != nil {
|
if cv := getCommitStatusCache(repo.ID, repo.DefaultBranch); cv != nil {
|
||||||
results[i] = &git_model.CommitStatus{
|
results[i] = &git_model.CommitStatus{
|
||||||
State: api.CommitStatusState(cv.State),
|
State: api.CommitStatusState(cv.State),
|
||||||
TargetURL: cv.TargetURL,
|
TargetURL: cv.TargetURL,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
allCached = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if allCached {
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
// collect the latest commit of each repo
|
// collect the latest commit of each repo
|
||||||
// at most there are dozens of repos (limited by MaxResponseItems), so it's not a big problem at the moment
|
// at most there are dozens of repos (limited by MaxResponseItems), so it's not a big problem at the moment
|
||||||
repoBranchNames := make(map[int64]string, len(repos))
|
repoBranchNames := make(map[int64]string, len(repos))
|
||||||
|
@ -165,10 +170,10 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep
|
||||||
for i, repo := range repos {
|
for i, repo := range repos {
|
||||||
if repo.ID == summary.RepoID {
|
if repo.ID == summary.RepoID {
|
||||||
results[i] = summary
|
results[i] = summary
|
||||||
_ = slices.DeleteFunc(repoSHAs, func(repoSHA git_model.RepoSHA) bool {
|
repoSHAs = slices.DeleteFunc(repoSHAs, func(repoSHA git_model.RepoSHA) bool {
|
||||||
return repoSHA.RepoID == repo.ID
|
return repoSHA.RepoID == repo.ID
|
||||||
})
|
})
|
||||||
if results[i].State != "" {
|
if results[i] != nil {
|
||||||
if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil {
|
if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil {
|
||||||
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
|
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
|
||||||
}
|
}
|
||||||
|
@ -177,6 +182,9 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(repoSHAs) == 0 {
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
// call the database O(1) times to get the commit statuses for all repos
|
// call the database O(1) times to get the commit statuses for all repos
|
||||||
repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoSHAs)
|
repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoSHAs)
|
||||||
|
@ -187,7 +195,7 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep
|
||||||
for i, repo := range repos {
|
for i, repo := range repos {
|
||||||
if results[i] == nil {
|
if results[i] == nil {
|
||||||
results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
|
results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
|
||||||
if results[i].State != "" {
|
if results[i] != nil {
|
||||||
if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil {
|
if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil {
|
||||||
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
|
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,8 +191,9 @@ export default {
|
||||||
'no-invalid-double-slash-comments': true,
|
'no-invalid-double-slash-comments': true,
|
||||||
'no-invalid-position-at-import-rule': [true, {ignoreAtRules: ['tailwind']}],
|
'no-invalid-position-at-import-rule': [true, {ignoreAtRules: ['tailwind']}],
|
||||||
'no-irregular-whitespace': true,
|
'no-irregular-whitespace': true,
|
||||||
'no-unknown-animations': null,
|
'no-unknown-animations': null, // disabled until stylelint supports multi-file linting
|
||||||
'no-unknown-custom-properties': null,
|
'no-unknown-custom-media': null, // disabled until stylelint supports multi-file linting
|
||||||
|
'no-unknown-custom-properties': null, // disabled until stylelint supports multi-file linting
|
||||||
'number-max-precision': null,
|
'number-max-precision': null,
|
||||||
'plugin/declaration-block-no-ignored-properties': true,
|
'plugin/declaration-block-no-ignored-properties': true,
|
||||||
'property-allowed-list': null,
|
'property-allowed-list': null,
|
||||||
|
|
|
@ -76,7 +76,8 @@
|
||||||
{{ctx.Locale.Tr "admin.dashboard.system_status"}}
|
{{ctx.Locale.Tr "admin.dashboard.system_status"}}
|
||||||
</h4>
|
</h4>
|
||||||
{{/* TODO: make these stats work in multi-server deployments, likely needs per-server stats in DB */}}
|
{{/* TODO: make these stats work in multi-server deployments, likely needs per-server stats in DB */}}
|
||||||
<div hx-get="{{$.Link}}/system_status" hx-swap="morph:innerHTML" hx-trigger="every 5s" hx-indicator=".divider" class="ui attached table segment">
|
<div class="no-loading-indicator tw-hidden"></div>
|
||||||
|
<div hx-get="{{$.Link}}/system_status" hx-swap="morph:innerHTML" hx-trigger="every 5s" hx-indicator=".no-loading-indicator" class="ui attached table segment">
|
||||||
{{template "admin/system_status" .}}
|
{{template "admin/system_status" .}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
{{if .Title}}{{.Title}}{{else}}{{ctx.Locale.Tr "actions.runs.empty_commit_message"}}{{end}}
|
{{if .Title}}{{.Title}}{{else}}{{ctx.Locale.Tr "actions.runs.empty_commit_message"}}{{end}}
|
||||||
</a>
|
</a>
|
||||||
<div class="flex-item-body">
|
<div class="flex-item-body">
|
||||||
<b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b>:
|
<span><b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b>:</span>
|
||||||
{{- if .ScheduleID -}}
|
{{- if .ScheduleID -}}
|
||||||
{{ctx.Locale.Tr "actions.runs.scheduled"}}
|
{{ctx.Locale.Tr "actions.runs.scheduled"}}
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
data-locale-cancel="{{ctx.Locale.Tr "cancel"}}"
|
data-locale-cancel="{{ctx.Locale.Tr "cancel"}}"
|
||||||
data-locale-rerun="{{ctx.Locale.Tr "rerun"}}"
|
data-locale-rerun="{{ctx.Locale.Tr "rerun"}}"
|
||||||
data-locale-rerun-all="{{ctx.Locale.Tr "rerun_all"}}"
|
data-locale-rerun-all="{{ctx.Locale.Tr "rerun_all"}}"
|
||||||
|
data-locale-runs-scheduled="{{ctx.Locale.Tr "actions.runs.scheduled"}}"
|
||||||
|
data-locale-runs-commit="{{ctx.Locale.Tr "actions.runs.commit"}}"
|
||||||
|
data-locale-runs-pushed-by="{{ctx.Locale.Tr "actions.runs.pushed_by"}}"
|
||||||
data-locale-status-unknown="{{ctx.Locale.Tr "actions.status.unknown"}}"
|
data-locale-status-unknown="{{ctx.Locale.Tr "actions.status.unknown"}}"
|
||||||
data-locale-status-waiting="{{ctx.Locale.Tr "actions.status.waiting"}}"
|
data-locale-status-waiting="{{ctx.Locale.Tr "actions.status.waiting"}}"
|
||||||
data-locale-status-running="{{ctx.Locale.Tr "actions.status.running"}}"
|
data-locale-status-running="{{ctx.Locale.Tr "actions.status.running"}}"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{{if $.IsSplitStyle}}
|
{{if $.IsSplitStyle}}
|
||||||
{{range $k, $line := $.section.Lines}}
|
{{range $k, $line := $.section.Lines}}
|
||||||
<tr class="{{.GetHTMLDiffLineType}}-code nl-{{$k}} ol-{{$k}}">
|
<tr class="{{.GetHTMLDiffLineType}}-code nl-{{$k}} ol-{{$k}} line-expanded">
|
||||||
{{if eq .GetType 4}}
|
{{if eq .GetType 4}}
|
||||||
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}">
|
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}">
|
||||||
<div class="tw-flex">
|
<div class="tw-flex">
|
||||||
|
@ -26,17 +26,17 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
|
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
|
||||||
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$.FileNameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
|
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$.FileNameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
|
||||||
<td class="blob-excerpt lines-escape lines-escape-old">{{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
<td class="lines-escape lines-escape-old">{{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
||||||
<td class="blob-excerpt lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
|
<td class="lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
|
||||||
<td class="blob-excerpt lines-code lines-code-old">{{/*
|
<td class="lines-code lines-code-old">{{/*
|
||||||
*/}}{{if $line.LeftIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/*
|
*/}}{{if $line.LeftIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/*
|
||||||
*/}}<code class="code-inner"></code>{{/*
|
*/}}<code class="code-inner"></code>{{/*
|
||||||
*/}}{{end}}{{/*
|
*/}}{{end}}{{/*
|
||||||
*/}}</td>
|
*/}}</td>
|
||||||
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
|
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
|
||||||
<td class="blob-excerpt lines-escape lines-escape-new">{{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
<td class="lines-escape lines-escape-new">{{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
||||||
<td class="blob-excerpt lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
|
<td class="lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
|
||||||
<td class="blob-excerpt lines-code lines-code-new">{{/*
|
<td class="lines-code lines-code-new">{{/*
|
||||||
*/}}{{if $line.RightIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/*
|
*/}}{{if $line.RightIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/*
|
||||||
*/}}<code class="code-inner"></code>{{/*
|
*/}}<code class="code-inner"></code>{{/*
|
||||||
*/}}{{end}}{{/*
|
*/}}{{end}}{{/*
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{range $k, $line := $.section.Lines}}
|
{{range $k, $line := $.section.Lines}}
|
||||||
<tr class="{{.GetHTMLDiffLineType}}-code nl-{{$k}} ol-{{$k}}">
|
<tr class="{{.GetHTMLDiffLineType}}-code nl-{{$k}} ol-{{$k}} line-expanded">
|
||||||
{{if eq .GetType 4}}
|
{{if eq .GetType 4}}
|
||||||
<td colspan="2" class="lines-num">
|
<td colspan="2" class="lines-num">
|
||||||
<div class="tw-flex">
|
<div class="tw-flex">
|
||||||
|
@ -72,9 +72,9 @@
|
||||||
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
|
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
|
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
|
||||||
<td class="blob-excerpt lines-escape">{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
<td class="lines-escape">{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
||||||
<td class="blob-excerpt lines-type-marker"><span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
|
<td class="lines-type-marker"><span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
|
||||||
<td class="blob-excerpt lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}"><code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td>
|
<td class="lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}"><code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -30,20 +30,24 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
{{$showSelfTooltip := (and $.IsSigned ($.Issue.IsPoster $.SignedUser.ID))}}
|
{{$showSelfTooltip := (and $.IsSigned ($.Issue.IsPoster $.SignedUser.ID))}}
|
||||||
{{if $showSelfTooltip}}
|
{{if not $.Issue.IsClosed}}
|
||||||
<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_approve"}}">
|
{{if $showSelfTooltip}}
|
||||||
<button type="submit" name="type" value="approve" disabled class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
|
<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_approve"}}">
|
||||||
</span>
|
<button type="submit" name="type" value="approve" disabled class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
|
||||||
{{else}}
|
</span>
|
||||||
<button type="submit" name="type" value="approve" class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
|
{{else}}
|
||||||
|
<button type="submit" name="type" value="approve" class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
<button type="submit" name="type" value="comment" class="ui submit tiny basic button btn-submit">{{ctx.Locale.Tr "repo.diff.review.comment"}}</button>
|
<button type="submit" name="type" value="comment" class="ui submit tiny basic button btn-submit">{{ctx.Locale.Tr "repo.diff.review.comment"}}</button>
|
||||||
{{if $showSelfTooltip}}
|
{{if not $.Issue.IsClosed}}
|
||||||
<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_reject"}}">
|
{{if $showSelfTooltip}}
|
||||||
<button type="submit" name="type" value="reject" disabled class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
|
<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_reject"}}">
|
||||||
</span>
|
<button type="submit" name="type" value="reject" disabled class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
|
||||||
{{else}}
|
</span>
|
||||||
<button type="submit" name="type" value="reject" class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
|
{{else}}
|
||||||
|
<button type="submit" name="type" value="reject" class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -90,7 +90,16 @@
|
||||||
{{ctx.Locale.Tr "repo.use_template"}}
|
{{ctx.Locale.Tr "repo.use_template"}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if (not $isHomepage)}}
|
{{if $isHomepage}}
|
||||||
|
{{/* only show the "code search" on the repo home page, it only does global search,
|
||||||
|
so do not show it when viewing file or directory to avoid misleading users (it doesn't search in a directory) */}}
|
||||||
|
<form class="ignore-dirty" action="{{.RepoLink}}/search" method="get">
|
||||||
|
<div class="ui small action input">
|
||||||
|
<input name="q" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
||||||
|
{{template "shared/search/button"}}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{else}}
|
||||||
<span class="breadcrumb repo-path tw-ml-1">
|
<span class="breadcrumb repo-path tw-ml-1">
|
||||||
<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
|
<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
|
||||||
{{- range $i, $v := .TreeNames -}}
|
{{- range $i, $v := .TreeNames -}}
|
||||||
|
@ -103,13 +112,6 @@
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</span>
|
</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<form class="ignore-dirty" action="{{.RepoLink}}/search" method="get">
|
|
||||||
<div class="ui small action input">
|
|
||||||
<input name="q" value="{{.Keyword}}" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
|
||||||
{{template "shared/search/button"}}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-flex tw-items-center">
|
<div class="tw-flex tw-items-center">
|
||||||
<!-- Only show clone panel in repository home page -->
|
<!-- Only show clone panel in repository home page -->
|
||||||
|
@ -136,7 +138,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{template "repo/cite/cite_modal" .}}
|
{{template "repo/cite/cite_modal" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if and (not $isHomepage) (not .IsViewFile) (not .IsBlame)}}
|
{{if and (not $isHomepage) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
|
||||||
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
|
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
|
||||||
{{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
|
{{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
|
||||||
</a>
|
</a>
|
||||||
|
@ -147,7 +149,7 @@
|
||||||
{{template "repo/view_file" .}}
|
{{template "repo/view_file" .}}
|
||||||
{{else if .IsBlame}}
|
{{else if .IsBlame}}
|
||||||
{{template "repo/blame" .}}
|
{{template "repo/blame" .}}
|
||||||
{{else}}
|
{{else}}{{/* IsViewDirectory */}}
|
||||||
{{template "repo/view_list" .}}
|
{{template "repo/view_list" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<span class="ui small label">{{if .NumFiles}}{{.NumFiles}}{{else}}-{{end}}</span>
|
<span class="ui small label">{{if .NumFiles}}{{.NumFiles}}{{else}}-{{end}}</span>
|
||||||
</a>
|
</a>
|
||||||
{{if or .Diff.TotalAddition .Diff.TotalDeletion}}
|
{{if or .Diff.TotalAddition .Diff.TotalDeletion}}
|
||||||
<span class="item tw-ml-auto tw-pr-0 tw-font-bold tw-flex tw-items-center tw-gap-2">
|
<span class="tw-ml-auto tw-pl-3 tw-whitespace-nowrap tw-pr-0 tw-font-bold tw-flex tw-items-center tw-gap-2">
|
||||||
<span><span class="text green">{{if .Diff.TotalAddition}}+{{.Diff.TotalAddition}}{{end}}</span> <span class="text red">{{if .Diff.TotalDeletion}}-{{.Diff.TotalDeletion}}{{end}}</span></span>
|
<span><span class="text green">{{if .Diff.TotalAddition}}+{{.Diff.TotalAddition}}{{end}}</span> <span class="text red">{{if .Diff.TotalDeletion}}-{{.Diff.TotalDeletion}}{{end}}</span></span>
|
||||||
<span class="diff-stats-bar">
|
<span class="diff-stats-bar">
|
||||||
<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .Diff.TotalAddition "/" "(" .Diff.TotalAddition "+" .Diff.TotalDeletion "+" 0.0 ")"}}%"></div>
|
<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .Diff.TotalAddition "/" "(" .Diff.TotalAddition "+" .Diff.TotalDeletion "+" 0.0 ")"}}%"></div>
|
||||||
|
|
|
@ -68,6 +68,6 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{{if .ReadmeExist}}
|
{{if and .ReadmeExist (or .IsMarkup .IsPlainText)}}
|
||||||
{{template "repo/view_file" .}}
|
{{template "repo/view_file" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -3843,6 +3843,54 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/secrets": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "List an repo's actions secrets",
|
||||||
|
"operationId": "repoListActionsSecrets",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repository",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/SecretList"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/actions/secrets/{secretname}": {
|
"/repos/{owner}/{repo}/actions/secrets/{secretname}": {
|
||||||
"put": {
|
"put": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
|
@ -25234,7 +25282,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"RegistrationToken": {
|
"RegistrationToken": {
|
||||||
"description": "RegistrationToken is response related to registeration token",
|
"description": "RegistrationToken is response related to registration token",
|
||||||
"headers": {
|
"headers": {
|
||||||
"token": {
|
"token": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
|
@ -17,15 +17,12 @@
|
||||||
</overflow-menu>
|
</overflow-menu>
|
||||||
<div class="ui middle very relaxed page grid">
|
<div class="ui middle very relaxed page grid">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="ui tab {{if not .user_exists}}active{{end}}"
|
<div class="ui tab {{if not .user_exists}}active{{end}}" data-tab="auth-link-signup-tab">
|
||||||
data-tab="auth-link-signup-tab">
|
{{if .AutoRegistrationFailedPrompt}}<div class="ui message">{{.AutoRegistrationFailedPrompt}}</div>{{end}}
|
||||||
{{template "user/auth/signup_inner" .}}
|
{{template "user/auth/signup_inner" .}}
|
||||||
</div>
|
</div>
|
||||||
<div class="ui tab {{if .user_exists}}active{{end}}"
|
<div class="ui tab {{if .user_exists}}active{{end}}" data-tab="auth-link-signin-tab">
|
||||||
data-tab="auth-link-signin-tab">
|
{{template "user/auth/signin_inner" .}}
|
||||||
<div class="ui user signin container icon">
|
|
||||||
{{template "user/auth/signin_inner" .}}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -119,9 +119,9 @@ func TestAPIRepoIssueConfigPaths(t *testing.T) {
|
||||||
".github/issue_template/config",
|
".github/issue_template/config",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, canidate := range templateConfigCandidates {
|
for _, candidate := range templateConfigCandidates {
|
||||||
for _, extension := range []string{".yaml", ".yml"} {
|
for _, extension := range []string{".yaml", ".yml"} {
|
||||||
fullPath := canidate + extension
|
fullPath := candidate + extension
|
||||||
t.Run(fullPath, func(t *testing.T) {
|
t.Run(fullPath, func(t *testing.T) {
|
||||||
configMap := make(map[string]any)
|
configMap := make(map[string]any)
|
||||||
configMap["blank_issues_enabled"] = false
|
configMap["blank_issues_enabled"] = false
|
||||||
|
|
|
@ -24,6 +24,12 @@ func TestAPIRepoSecrets(t *testing.T) {
|
||||||
session := loginUser(t, user.Name)
|
session := loginUser(t, user.Name)
|
||||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
t.Run("List", func(t *testing.T) {
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/secrets", repo.FullName())).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Create", func(t *testing.T) {
|
t.Run("Create", func(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -31,7 +37,7 @@ func TestAPIRepoSecrets(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Name: "",
|
Name: "",
|
||||||
ExpectedStatus: http.StatusNotFound,
|
ExpectedStatus: http.StatusMethodNotAllowed,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "-",
|
Name: "-",
|
||||||
|
|
|
@ -67,7 +67,7 @@ func TestCompareBranches(t *testing.T) {
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
// Inderect compare remove-files-b (head) with add-csv (base) branch
|
// Indirect compare remove-files-b (head) with add-csv (base) branch
|
||||||
//
|
//
|
||||||
// 'link_hi' and 'test.csv' are deleted, 'test.txt' is added
|
// 'link_hi' and 'test.csv' are deleted, 'test.txt' is added
|
||||||
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b")
|
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b")
|
||||||
|
@ -79,7 +79,7 @@ func TestCompareBranches(t *testing.T) {
|
||||||
|
|
||||||
inspectCompare(t, htmlDoc, diffCount, diffChanges)
|
inspectCompare(t, htmlDoc, diffCount, diffChanges)
|
||||||
|
|
||||||
// Inderect compare remove-files-b (head) with remove-files-a (base) branch
|
// Indirect compare remove-files-b (head) with remove-files-a (base) branch
|
||||||
//
|
//
|
||||||
// 'link_hi' and 'test.csv' are deleted, 'test.txt' is added
|
// 'link_hi' and 'test.csv' are deleted, 'test.txt' is added
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ func TestCompareBranches(t *testing.T) {
|
||||||
|
|
||||||
inspectCompare(t, htmlDoc, diffCount, diffChanges)
|
inspectCompare(t, htmlDoc, diffCount, diffChanges)
|
||||||
|
|
||||||
// Inderect compare remove-files-a (head) with remove-files-b (base) branch
|
// Indirect compare remove-files-a (head) with remove-files-b (base) branch
|
||||||
//
|
//
|
||||||
// 'link_hi' and 'test.csv' are deleted
|
// 'link_hi' and 'test.csv' are deleted
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -46,22 +48,25 @@ func TestPullCompare(t *testing.T) {
|
||||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||||
testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusSeeOther)
|
testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusSeeOther)
|
||||||
testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n")
|
testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n")
|
||||||
resp = testPullCreate(t, session, "user1", "repo1", false, "master", "master1", "This is a pull title")
|
testPullCreate(t, session, "user1", "repo1", false, "master", "master1", "This is a pull title")
|
||||||
|
|
||||||
// the max value on issue_index.yml for repo_id=1 is 5
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
|
||||||
req = NewRequest(t, "GET", "/user2/repo1/pulls/6/files")
|
issueIndex := unittest.AssertExistsAndLoadBean(t, &issues_model.IssueIndex{GroupID: repo1.ID}, unittest.OrderBy("group_id ASC"))
|
||||||
|
prFilesURL := fmt.Sprintf("/user2/repo1/pulls/%d/files", issueIndex.MaxIndex)
|
||||||
|
req = NewRequest(t, "GET", prFilesURL)
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
doc := NewHTMLParser(t, resp.Body)
|
doc := NewHTMLParser(t, resp.Body)
|
||||||
editButtonCount := doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length()
|
editButtonCount := doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length()
|
||||||
assert.Greater(t, editButtonCount, 0, "Expected to find a button to edit a file in the PR diff view but there were none")
|
assert.Greater(t, editButtonCount, 0, "Expected to find a button to edit a file in the PR diff view but there were none")
|
||||||
|
|
||||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
||||||
repoForked := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"})
|
repoForked := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"})
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
// delete the head repository and revisit the PR diff view
|
// delete the head repository and revisit the PR diff view
|
||||||
err := repo_service.DeleteRepositoryDirectly(db.DefaultContext, user2, repoForked.ID)
|
err := repo_service.DeleteRepositoryDirectly(db.DefaultContext, user2, repoForked.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
req = NewRequest(t, "GET", "/user2/repo1/pulls/6/files")
|
req = NewRequest(t, "GET", prFilesURL)
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
doc = NewHTMLParser(t, resp.Body)
|
doc = NewHTMLParser(t, resp.Body)
|
||||||
editButtonCount = doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length()
|
editButtonCount = doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length()
|
||||||
|
|
|
@ -5,12 +5,15 @@ package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
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/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
@ -176,3 +179,82 @@ func TestPullView_CodeOwner(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||||
|
user1Session := loginUser(t, "user1")
|
||||||
|
user2Session := loginUser(t, "user2")
|
||||||
|
|
||||||
|
// Have user1 create a fork of repo1.
|
||||||
|
testRepoFork(t, user1Session, "user2", "repo1", "user1", "repo1")
|
||||||
|
|
||||||
|
t.Run("Submit approve/reject review on merged PR", func(t *testing.T) {
|
||||||
|
// Create a merged PR (made by user1) in the upstream repo1.
|
||||||
|
testEditFile(t, user1Session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
||||||
|
resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "master", "This is a pull title")
|
||||||
|
elem := strings.Split(test.RedirectURL(resp), "/")
|
||||||
|
assert.EqualValues(t, "pulls", elem[3])
|
||||||
|
testPullMerge(t, user1Session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false)
|
||||||
|
|
||||||
|
// Grab the CSRF token.
|
||||||
|
req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4]))
|
||||||
|
resp = user2Session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
// Submit an approve review on the PR.
|
||||||
|
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "approve", http.StatusUnprocessableEntity)
|
||||||
|
|
||||||
|
// Submit a reject review on the PR.
|
||||||
|
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "reject", http.StatusUnprocessableEntity)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Submit approve/reject review on closed PR", func(t *testing.T) {
|
||||||
|
// Created a closed PR (made by user1) in the upstream repo1.
|
||||||
|
testEditFileToNewBranch(t, user1Session, "user1", "repo1", "master", "a-test-branch", "README.md", "Hello, World (Editied...again)\n")
|
||||||
|
resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "a-test-branch", "This is a pull title")
|
||||||
|
elem := strings.Split(test.RedirectURL(resp), "/")
|
||||||
|
assert.EqualValues(t, "pulls", elem[3])
|
||||||
|
testIssueClose(t, user1Session, elem[1], elem[2], elem[4])
|
||||||
|
|
||||||
|
// Grab the CSRF token.
|
||||||
|
req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4]))
|
||||||
|
resp = user2Session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
// Submit an approve review on the PR.
|
||||||
|
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "approve", http.StatusUnprocessableEntity)
|
||||||
|
|
||||||
|
// Submit a reject review on the PR.
|
||||||
|
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "reject", http.StatusUnprocessableEntity)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSubmitReview(t *testing.T, session *TestSession, csrf, owner, repo, pullNumber, reviewType string, expectedSubmitStatus int) *httptest.ResponseRecorder {
|
||||||
|
options := map[string]string{
|
||||||
|
"_csrf": csrf,
|
||||||
|
"commit_id": "",
|
||||||
|
"content": "test",
|
||||||
|
"type": reviewType,
|
||||||
|
}
|
||||||
|
|
||||||
|
submitURL := path.Join(owner, repo, "pulls", pullNumber, "files", "reviews", "submit")
|
||||||
|
req := NewRequestWithValues(t, "POST", submitURL, options)
|
||||||
|
return session.MakeRequest(t, req, expectedSubmitStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIssueClose(t *testing.T, session *TestSession, owner, repo, issueNumber string) *httptest.ResponseRecorder {
|
||||||
|
req := NewRequest(t, "GET", path.Join(owner, repo, "pulls", issueNumber))
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
closeURL := path.Join(owner, repo, "issues", issueNumber, "comments")
|
||||||
|
|
||||||
|
options := map[string]string{
|
||||||
|
"_csrf": htmlDoc.GetCSRF(),
|
||||||
|
"status": "close",
|
||||||
|
}
|
||||||
|
|
||||||
|
req = NewRequestWithValues(t, "POST", closeURL, options)
|
||||||
|
return session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
|
@ -46,7 +46,6 @@ func InitTest(requireGitea bool) {
|
||||||
// TODO: Speedup tests that rely on the event source ticker, confirm whether there is any bug or failure.
|
// TODO: Speedup tests that rely on the event source ticker, confirm whether there is any bug or failure.
|
||||||
// setting.UI.Notification.EventSourceUpdateTime = time.Second
|
// setting.UI.Notification.EventSourceUpdateTime = time.Second
|
||||||
|
|
||||||
setting.IsInTesting = true
|
|
||||||
setting.AppWorkPath = giteaRoot
|
setting.AppWorkPath = giteaRoot
|
||||||
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
||||||
if requireGitea {
|
if requireGitea {
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
acounts,accounts
|
||||||
|
canidate,candidate
|
||||||
|
comfirm,confirm
|
||||||
|
converage,coverage
|
||||||
|
currrently,currently
|
||||||
|
delimeter,delimiter
|
||||||
|
differrent,different
|
||||||
|
exclusing,excluding
|
||||||
|
finshed,finished
|
||||||
|
formated,formatted
|
||||||
|
inderect,indirect
|
||||||
|
insuficient,insufficient
|
||||||
|
likly,likely
|
||||||
|
mergable,mergeable
|
||||||
|
overrided,overridden
|
||||||
|
priortized,prioritized
|
||||||
|
registeration,registration
|
||||||
|
reuqest,request
|
||||||
|
reviwer,reviewer
|
||||||
|
superceded,superseded
|
||||||
|
underlaying,underlying
|
|
|
@ -403,7 +403,7 @@
|
||||||
background: var(--color-body);
|
background: var(--color-body);
|
||||||
border-top-width: 1px;
|
border-top-width: 1px;
|
||||||
border-color: var(--color-secondary);
|
border-color: var(--color-secondary);
|
||||||
font-weight: var(--font-weight-medium);
|
color: var(--color-text-dark);
|
||||||
margin-bottom: -1px;
|
margin-bottom: -1px;
|
||||||
border-radius: 0.28571429rem 0.28571429rem 0 0 !important;
|
border-radius: 0.28571429rem 0.28571429rem 0 0 !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2377,7 +2377,7 @@ tbody.commit-list {
|
||||||
|
|
||||||
.tag-code,
|
.tag-code,
|
||||||
.tag-code td,
|
.tag-code td,
|
||||||
.tag-code .blob-excerpt {
|
.tag-code.line-expanded {
|
||||||
background-color: var(--color-box-body-highlight);
|
background-color: var(--color-box-body-highlight);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
@ -2393,8 +2393,8 @@ tbody.commit-list {
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blob-excerpt {
|
.line-expanded {
|
||||||
background-color: var(--color-secondary-alpha-30);
|
background-color: var(--color-secondary-alpha-20);
|
||||||
}
|
}
|
||||||
|
|
||||||
.issue-keyword {
|
.issue-keyword {
|
||||||
|
@ -2520,7 +2520,7 @@ tbody.commit-list {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: var(--color-red);
|
background-color: var(--color-red);
|
||||||
height: 12px;
|
height: 12px;
|
||||||
width: 40px;
|
width: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.diff-stats-bar .diff-stats-add-bar {
|
.diff-stats-bar .diff-stats-add-bar {
|
||||||
|
@ -2553,11 +2553,9 @@ tbody.commit-list {
|
||||||
|
|
||||||
.code-diff-unified .add-code,
|
.code-diff-unified .add-code,
|
||||||
.code-diff-unified .add-code td,
|
.code-diff-unified .add-code td,
|
||||||
.code-diff-split .add-code .lines-num-new,
|
|
||||||
.code-diff-split .add-code .lines-type-marker-new,
|
.code-diff-split .add-code .lines-type-marker-new,
|
||||||
.code-diff-split .add-code .lines-escape-new,
|
.code-diff-split .add-code .lines-escape-new,
|
||||||
.code-diff-split .add-code .lines-code-new,
|
.code-diff-split .add-code .lines-code-new,
|
||||||
.code-diff-split .del-code .add-code.lines-num-new,
|
|
||||||
.code-diff-split .del-code .add-code.lines-type-marker-new,
|
.code-diff-split .del-code .add-code.lines-type-marker-new,
|
||||||
.code-diff-split .del-code .add-code.lines-escape-new,
|
.code-diff-split .del-code .add-code.lines-escape-new,
|
||||||
.code-diff-split .del-code .add-code.lines-code-new {
|
.code-diff-split .del-code .add-code.lines-code-new {
|
||||||
|
@ -2565,17 +2563,33 @@ tbody.commit-list {
|
||||||
border-color: var(--color-diff-added-row-border);
|
border-color: var(--color-diff-added-row-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-diff-split .del-code .lines-num-new,
|
|
||||||
.code-diff-split .del-code .lines-type-marker-new,
|
.code-diff-split .del-code .lines-type-marker-new,
|
||||||
.code-diff-split .del-code .lines-code-new,
|
.code-diff-split .del-code .lines-code-new,
|
||||||
.code-diff-split .del-code .lines-escape-new,
|
.code-diff-split .del-code .lines-escape-new,
|
||||||
.code-diff-split .add-code .lines-num-old,
|
|
||||||
.code-diff-split .add-code .lines-escape-old,
|
.code-diff-split .add-code .lines-escape-old,
|
||||||
.code-diff-split .add-code .lines-type-marker-old,
|
.code-diff-split .add-code .lines-type-marker-old,
|
||||||
.code-diff-split .add-code .lines-code-old {
|
.code-diff-split .add-code .lines-code-old {
|
||||||
background: var(--color-diff-inactive);
|
background: var(--color-diff-inactive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.code-diff-split .add-code .lines-num.lines-num-old,
|
||||||
|
.code-diff-split .del-code .lines-num.lines-num-new {
|
||||||
|
background: var(--color-diff-inactive);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-diff-unified .del-code .lines-num,
|
||||||
|
.code-diff-split .del-code .lines-num {
|
||||||
|
background: var(--color-diff-removed-linenum-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-diff-unified .add-code .lines-num,
|
||||||
|
.code-diff-split .add-code .lines-num,
|
||||||
|
.code-diff-split .del-code .add-code.lines-num {
|
||||||
|
background: var(--color-diff-added-linenum-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
.code-diff-split tbody tr td:nth-child(5),
|
.code-diff-split tbody tr td:nth-child(5),
|
||||||
.code-diff-split tbody tr td.add-comment-right {
|
.code-diff-split tbody tr td.add-comment-right {
|
||||||
border-left: 1px solid var(--color-secondary);
|
border-left: 1px solid var(--color-secondary);
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
/* red/green colorblind-friendly colors */
|
/* red/green colorblind-friendly colors */
|
||||||
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
|
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
|
||||||
:root {
|
:root {
|
||||||
--color-diff-added-word-bg: #388bfd66;
|
--color-diff-added-linenum-bg: #1979fd46;
|
||||||
--color-diff-added-row-bg: #388bfd26;
|
--color-diff-added-row-bg: #1979fd20;
|
||||||
|
--color-diff-added-word-bg: #1979fd66;
|
||||||
--color-diff-removed-word-bg: #db6d2866;
|
--color-diff-removed-linenum-bg: #c8622146;
|
||||||
--color-diff-removed-row-bg: #db6d2826;
|
--color-diff-removed-row-bg: #c8622120;
|
||||||
|
--color-diff-removed-word-bg: #c8622166;
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,14 +143,16 @@
|
||||||
--color-grey-light: #818f9e;
|
--color-grey-light: #818f9e;
|
||||||
--color-gold: #b1983b;
|
--color-gold: #b1983b;
|
||||||
--color-white: #ffffff;
|
--color-white: #ffffff;
|
||||||
--color-diff-removed-word-bg: #6f3333;
|
--color-diff-added-linenum-bg: #274227;
|
||||||
--color-diff-added-word-bg: #3c653c;
|
--color-diff-added-row-bg: #203224;
|
||||||
--color-diff-removed-row-bg: #3c2626;
|
|
||||||
--color-diff-moved-row-bg: #818044;
|
|
||||||
--color-diff-added-row-bg: #283e2d;
|
|
||||||
--color-diff-removed-row-border: #634343;
|
|
||||||
--color-diff-moved-row-border: #bcca6f;
|
|
||||||
--color-diff-added-row-border: #314a37;
|
--color-diff-added-row-border: #314a37;
|
||||||
|
--color-diff-added-word-bg: #3c653c;
|
||||||
|
--color-diff-moved-row-bg: #818044;
|
||||||
|
--color-diff-moved-row-border: #bcca6f;
|
||||||
|
--color-diff-removed-linenum-bg: #482121;
|
||||||
|
--color-diff-removed-row-bg: #301e1e;
|
||||||
|
--color-diff-removed-row-border: #634343;
|
||||||
|
--color-diff-removed-word-bg: #6f3333;
|
||||||
--color-diff-inactive: #22282d;
|
--color-diff-inactive: #22282d;
|
||||||
--color-error-border: #a04141;
|
--color-error-border: #a04141;
|
||||||
--color-error-bg: #522;
|
--color-error-bg: #522;
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
/* red/green colorblind-friendly colors */
|
/* red/green colorblind-friendly colors */
|
||||||
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
|
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
|
||||||
:root {
|
:root {
|
||||||
--color-diff-added-word-bg: #54aeff66;
|
--color-diff-added-linenum-bg: #54aeff4d;
|
||||||
--color-diff-added-row-bg: #ddf4ff80;
|
--color-diff-added-row-bg: #ddf4ff80;
|
||||||
|
--color-diff-added-word-bg: #54aeff66;
|
||||||
--color-diff-removed-word-bg: #ffb77c80;
|
--color-diff-removed-linenum-bg: #ffb77c4d;
|
||||||
--color-diff-removed-row-bg: #fff1e580;
|
--color-diff-removed-row-bg: #fff1e580;
|
||||||
|
--color-diff-removed-word-bg: #ffb77c80;
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue