diff --git a/Makefile b/Makefile
index 2a78c907c0..0cd6abb81e 100644
--- a/Makefile
+++ b/Makefile
@@ -30,7 +30,7 @@ EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-che
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
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
-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
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
@@ -397,11 +397,11 @@ lint-md: node_modules
.PHONY: 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
lint-spell-fix:
- @go run $(MISSPELL_PACKAGE) -w $(SPELLCHECK_FILES)
+ @go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -w $(SPELLCHECK_FILES)
.PHONY: lint-go
lint-go:
@@ -908,8 +908,9 @@ webpack: $(WEBPACK_DEST)
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
@$(MAKE) -s node-check node_modules
- rm -rf $(WEBPACK_DEST_ENTRIES)
- npx webpack
+ @rm -rf $(WEBPACK_DEST_ENTRIES)
+ @echo "Running webpack..."
+ @BROWSERSLIST_IGNORE_OLD_DATA=true npx webpack
@touch $(WEBPACK_DEST)
.PHONY: svg
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 12588c1387..62db26fb02 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -1558,8 +1558,8 @@ LEVEL = Info
;; email = use the username part of the email attribute
;; Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria:
;; - diacritics are removed
-;; - the characters in the set `['´\x60]` are removed
-;; - the characters in the set `[\s~+]` are replaced with `-`
+;; - the characters in the set ['´`] are removed
+;; - the characters in the set [\s~+] are replaced with "-"
;USERNAME = nickname
;;
;; Update avatar if available from oauth2 provider.
diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md
index b295ddf53a..7bf23c9b99 100644
--- a/docs/content/administration/config-cheat-sheet.en-us.md
+++ b/docs/content/administration/config-cheat-sheet.en-us.md
@@ -612,7 +612,7 @@ And the following unique queues:
- `email` - use the username part of the email attribute
- Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria:
- 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 `-`
- `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:
@@ -1322,7 +1322,7 @@ Defaultly every storage has their default base path like below
| actions_log | actions_log/ |
| 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
[storage.actions_log]
diff --git a/models/actions/run.go b/models/actions/run.go
index b75fa49f3c..4f886999e9 100644
--- a/models/actions/run.go
+++ b/models/actions/run.go
@@ -74,6 +74,13 @@ func (run *ActionRun) Link() string {
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
func (run *ActionRun) RefLink() string {
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)
}
+func (run *ActionRun) IsSchedule() bool {
+ return run.ScheduleID > 0
+}
+
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID).
SetExpr("num_action_runs",
@@ -251,11 +262,11 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
// InsertRun inserts a run
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 {
return err
}
- defer commiter.Close()
+ defer committer.Close()
index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID)
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) {
diff --git a/models/actions/task.go b/models/actions/task.go
index 9946cf5233..f2f796a626 100644
--- a/models/actions/task.go
+++ b/models/actions/task.go
@@ -216,11 +216,11 @@ func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, erro
}
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 {
return nil, false, err
}
- defer commiter.Close()
+ defer committer.Close()
e := db.GetEngine(ctx)
@@ -322,7 +322,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
task.Job = job
- if err := commiter.Commit(); err != nil {
+ if err := committer.Commit(); err != nil {
return nil, false, err
}
@@ -347,11 +347,11 @@ func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionT
stepStates[v.Id] = v
}
- ctx, commiter, err := db.TxContext(ctx)
+ ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
- defer commiter.Close()
+ defer committer.Close()
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
}
diff --git a/models/actions/tasks_version.go b/models/actions/tasks_version.go
index 5c0a86538d..96c5468c1a 100644
--- a/models/actions/tasks_version.go
+++ b/models/actions/tasks_version.go
@@ -13,7 +13,7 @@ import (
// ActionTasksVersion
// 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.
type ActionTasksVersion struct {
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 {
- ctx, commiter, err := db.TxContext(ctx)
+ ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
- defer commiter.Close()
+ defer committer.Close()
// 1. increase global
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()
}
diff --git a/models/fixtures/pull_request.yml b/models/fixtures/pull_request.yml
index 3fc8ce630d..9a16316e5a 100644
--- a/models/fixtures/pull_request.yml
+++ b/models/fixtures/pull_request.yml
@@ -1,7 +1,7 @@
-
id: 1
type: 0 # gitea pull request
- status: 2 # mergable
+ status: 2 # mergeable
issue_id: 2
index: 2
head_repo_id: 1
@@ -16,7 +16,7 @@
-
id: 2
type: 0 # gitea pull request
- status: 2 # mergable
+ status: 2 # mergeable
issue_id: 3
index: 3
head_repo_id: 1
@@ -29,7 +29,7 @@
-
id: 3
type: 0 # gitea pull request
- status: 2 # mergable
+ status: 2 # mergeable
issue_id: 8
index: 1
head_repo_id: 11
@@ -42,7 +42,7 @@
-
id: 4
type: 0 # gitea pull request
- status: 2 # mergable
+ status: 2 # mergeable
issue_id: 9
index: 1
head_repo_id: 48
@@ -55,7 +55,7 @@
-
id: 5 # this PR is outdated (one commit behind branch1 )
type: 0 # gitea pull request
- status: 2 # mergable
+ status: 2 # mergeable
issue_id: 11
index: 5
head_repo_id: 1
@@ -68,7 +68,7 @@
-
id: 6
type: 0 # gitea pull request
- status: 2 # mergable
+ status: 2 # mergeable
issue_id: 12
index: 2
head_repo_id: 3
@@ -81,7 +81,7 @@
-
id: 7
type: 0 # gitea pull request
- status: 2 # mergable
+ status: 2 # mergeable
issue_id: 19
index: 1
head_repo_id: 58
@@ -94,7 +94,7 @@
-
id: 8
type: 0 # gitea pull request
- status: 2 # mergable
+ status: 2 # mergeable
issue_id: 20
index: 1
head_repo_id: 23
@@ -103,7 +103,7 @@
-
id: 9
type: 0 # gitea pull request
- status: 2 # mergable
+ status: 2 # mergeable
issue_id: 21
index: 1
head_repo_id: 60
@@ -112,7 +112,7 @@
-
id: 10
type: 0 # gitea pull request
- status: 2 # mergable
+ status: 2 # mergeable
issue_id: 22
index: 1
head_repo_id: 61
diff --git a/models/issues/pull.go b/models/issues/pull.go
index dc1b1b956a..4194df2e3d 100644
--- a/models/issues/pull.go
+++ b/models/issues/pull.go
@@ -807,7 +807,7 @@ func UpdateAllowEdits(ctx context.Context, pr *PullRequest) error {
// Mergeable returns if the pullrequest is mergeable.
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.
// - Has a conflict.
// - Received a error while being conflict checked.
diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go
index 4063ca043b..caa582a9fc 100644
--- a/models/issues/tracked_time.go
+++ b/models/issues/tracked_time.go
@@ -187,8 +187,8 @@ func AddTime(ctx context.Context, user *user_model.User, issue *Issue, amount in
Issue: issue,
Repo: issue.Repo,
Doer: user,
- // Content before v1.21 did store the formated string instead of seconds,
- // so use "|" as delimeter to mark the new format
+ // Content before v1.21 did store the formatted string instead of seconds,
+ // so use "|" as delimiter to mark the new format
Content: fmt.Sprintf("|%d", amount),
Type: CommentTypeAddTimeManual,
TimeID: t.ID,
@@ -267,8 +267,8 @@ func DeleteIssueUserTimes(ctx context.Context, issue *Issue, user *user_model.Us
Issue: issue,
Repo: issue.Repo,
Doer: user,
- // Content before v1.21 did store the formated string instead of seconds,
- // so use "|" as delimeter to mark the new format
+ // Content before v1.21 did store the formatted string instead of seconds,
+ // so use "|" as delimiter to mark the new format
Content: fmt.Sprintf("|%d", removedTime),
Type: CommentTypeDeleteTimeManual,
}); err != nil {
@@ -298,8 +298,8 @@ func DeleteTime(ctx context.Context, t *TrackedTime) error {
Issue: t.Issue,
Repo: t.Issue.Repo,
Doer: t.User,
- // Content before v1.21 did store the formated string instead of seconds,
- // so use "|" as delimeter to mark the new format
+ // Content before v1.21 did store the formatted string instead of seconds,
+ // so use "|" as delimiter to mark the new format
Content: fmt.Sprintf("|%d", t.Time),
Type: CommentTypeDeleteTimeManual,
}); err != nil {
diff --git a/models/migrations/v1_16/v210.go b/models/migrations/v1_16/v210.go
index 533bb4bf80..51b7d81e99 100644
--- a/models/migrations/v1_16/v210.go
+++ b/models/migrations/v1_16/v210.go
@@ -43,11 +43,6 @@ func RemigrateU2FCredentials(x *xorm.Engine) error {
if err != nil {
return err
}
- case schemas.ORACLE:
- _, err := x.Exec("ALTER TABLE webauthn_credential MODIFY credential_id VARCHAR(410)")
- if err != nil {
- return err
- }
case schemas.MSSQL:
// This column has an index on it. I could write all of the code to attempt to change the index OR
// I could just use recreate table.
diff --git a/models/migrations/v1_17/v216.go b/models/migrations/v1_17/v216.go
index 59b21d9b2c..268f472a42 100644
--- a/models/migrations/v1_17/v216.go
+++ b/models/migrations/v1_17/v216.go
@@ -4,4 +4,4 @@
package v1_17 //nolint
// 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
diff --git a/models/migrations/v1_22/v286.go b/models/migrations/v1_22/v286.go
index fbbd87344f..f46d494dfe 100644
--- a/models/migrations/v1_22/v286.go
+++ b/models/migrations/v1_22/v286.go
@@ -53,7 +53,7 @@ func expandHashReferencesToSha256(x *xorm.Engine) error {
if setting.Database.Type.IsMySQL() {
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1]))
} else if setting.Database.Type.IsMSSQL() {
- _, err = db.Exec(fmt.Sprintf("ALTER TABLE [%s] ALTER COLUMN [%s] VARCHAR(64)", alts[0], alts[1]))
+ _, err = db.Exec(fmt.Sprintf("ALTER TABLE [%s] ALTER COLUMN [%s] NVARCHAR(64)", alts[0], alts[1]))
} else {
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1]))
}
diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go
index cb90c12f2b..51de18fa9b 100644
--- a/models/unittest/testdb.go
+++ b/models/unittest/testdb.go
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/setting/config"
@@ -106,6 +107,7 @@ func MainTest(m *testing.M, testOpts ...*TestOptions) {
fatalTestError("Error creating test engine: %v\n", err)
}
+ setting.IsInTesting = true
setting.AppURL = "https://try.gitea.io/"
setting.RunUser = "runuser"
setting.SSH.User = "sshuser"
@@ -148,6 +150,9 @@ func MainTest(m *testing.M, testOpts ...*TestOptions) {
config.SetDynGetter(system.NewDatabaseDynKeyGetter())
+ if err = cache.Init(); err != nil {
+ fatalTestError("cache.Init: %v\n", err)
+ }
if err = storage.Init(); err != nil {
fatalTestError("storage.Init: %v\n", err)
}
diff --git a/models/user/user.go b/models/user/user.go
index 7056aecab0..a5a5b5bdf6 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -501,19 +501,19 @@ func GetUserSalt() (string, error) {
// 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
var (
- customCharsReplacement = strings.NewReplacer("Æ", "AE")
- removeCharsRE = regexp.MustCompile(`['´\x60]`)
- removeDiacriticsTransform = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
- replaceCharsHyphenRE = regexp.MustCompile(`[\s~+]`)
+ customCharsReplacement = strings.NewReplacer("Æ", "AE")
+ removeCharsRE = regexp.MustCompile("['`´]")
+ transformDiacritics = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
+ replaceCharsHyphenRE = regexp.MustCompile(`[\s~+]`)
)
-// normalizeUserName returns a string with single-quotes and diacritics
-// removed, and any other non-supported username characters replaced with
-// a `-` character
+// NormalizeUserName only takes the name part if it is an email address, transforms it diacritics to ASCII characters.
+// It returns a string with the single-quotes removed, and any other non-supported username characters are replaced with a `-` character
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 {
- 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
}
diff --git a/models/user/user_test.go b/models/user/user_test.go
index a4550fa655..b4ffa1f322 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -506,15 +506,16 @@ func Test_NormalizeUserFromEmail(t *testing.T) {
Expected string
IsNormalizedValid bool
}{
- {"test", "test", true},
+ {"name@example.com", "name", true},
+ {"test'`´name", "testname", true},
{"Sinéad.O'Connor", "Sinead.OConnor", true},
{"Æsir", "AEsir", true},
- // \u00e9\u0065\u0301
- {"éé", "ee", true},
+ {"éé", "ee", true}, // \u00e9\u0065\u0301
{"Awareness Hub", "Awareness-Hub", true},
{"double__underscore", "double__underscore", false}, // We should consider squashing double non-alpha characters
{".bad.", ".bad.", false},
{"new😀user", "new😀user", false}, // No plans to support
+ {`"quoted"`, `"quoted"`, false}, // No plans to support
}
for _, testCase := range testCases {
normalizedName, err := user_model.NormalizeUserName(testCase.Input)
diff --git a/modules/git/object_format.go b/modules/git/object_format.go
index a056b20e8a..3de9ff8cf4 100644
--- a/modules/git/object_format.go
+++ b/modules/git/object_format.go
@@ -33,7 +33,6 @@ type ObjectFormat interface {
ComputeHash(t ObjectType, content []byte) ObjectID
}
-/* SHA1 Type */
type Sha1ObjectFormatImpl struct{}
var (
@@ -70,14 +69,10 @@ func (h Sha1ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) ObjectID
_, _ = hasher.Write([]byte(" "))
_, _ = hasher.Write([]byte(strconv.FormatInt(int64(len(content)), 10)))
_, _ = hasher.Write([]byte{0})
-
- // HashSum generates a SHA1 for the provided hash
- var sha1 Sha1Hash
- copy(sha1[:], hasher.Sum(nil))
- return &sha1
+ _, _ = hasher.Write(content)
+ return h.MustID(hasher.Sum(nil))
}
-/* SHA256 Type */
type Sha256ObjectFormatImpl struct{}
var (
@@ -116,11 +111,8 @@ func (h Sha256ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) Object
_, _ = hasher.Write([]byte(" "))
_, _ = hasher.Write([]byte(strconv.FormatInt(int64(len(content)), 10)))
_, _ = hasher.Write([]byte{0})
-
- // HashSum generates a SHA256 for the provided hash
- var sha256 Sha1Hash
- copy(sha256[:], hasher.Sum(nil))
- return &sha256
+ _, _ = hasher.Write(content)
+ return h.MustID(hasher.Sum(nil))
}
var (
diff --git a/modules/git/object_id.go b/modules/git/object_id.go
index 4f8c39ee1d..33e5085005 100644
--- a/modules/git/object_id.go
+++ b/modules/git/object_id.go
@@ -16,7 +16,6 @@ type ObjectID interface {
Type() ObjectFormat
}
-/* SHA1 */
type Sha1Hash [20]byte
func (h *Sha1Hash) String() string {
@@ -40,7 +39,6 @@ func MustIDFromString(hexHash string) ObjectID {
return id
}
-/* SHA256 */
type Sha256Hash [32]byte
func (h *Sha256Hash) String() string {
@@ -54,7 +52,6 @@ func (h *Sha256Hash) IsZero() bool {
func (h *Sha256Hash) RawValue() []byte { return h[:] }
func (*Sha256Hash) Type() ObjectFormat { return Sha256ObjectFormat }
-/* utility */
func NewIDFromString(hexHash string) (ObjectID, error) {
var theObjectFormat ObjectFormat
for _, objectFormat := range SupportedObjectFormats {
diff --git a/modules/git/object_id_test.go b/modules/git/object_id_test.go
index 1ad40096a0..03d0c85d87 100644
--- a/modules/git/object_id_test.go
+++ b/modules/git/object_id_test.go
@@ -18,4 +18,8 @@ func TestIsValidSHAPattern(t *testing.T) {
assert.False(t, h.IsValid("abc"))
assert.False(t, h.IsValid("123g"))
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())
}
diff --git a/modules/git/pipeline/lfs_common.go b/modules/git/pipeline/lfs_common.go
new file mode 100644
index 0000000000..188e7d4d65
--- /dev/null
+++ b/modules/git/pipeline/lfs_common.go
@@ -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)
+}
diff --git a/modules/git/pipeline/lfs.go b/modules/git/pipeline/lfs_gogit.go
similarity index 80%
rename from modules/git/pipeline/lfs.go
rename to modules/git/pipeline/lfs_gogit.go
index 6dfca24f29..adcf8ed09c 100644
--- a/modules/git/pipeline/lfs.go
+++ b/modules/git/pipeline/lfs_gogit.go
@@ -7,12 +7,10 @@ package pipeline
import (
"bufio"
- "fmt"
"io"
"sort"
"strings"
"sync"
- "time"
"code.gitea.io/gitea/modules/git"
@@ -21,23 +19,6 @@ import (
"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
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
resultsMap := map[string]*LFSResult{}
@@ -51,7 +32,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
All: true,
})
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 {
@@ -85,7 +66,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
return nil
})
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 {
@@ -156,7 +137,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
select {
case err, has := <-errChan:
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:
}
diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go
index fe320f39f3..349cfbd9ce 100644
--- a/modules/git/pipeline/lfs_nogogit.go
+++ b/modules/git/pipeline/lfs_nogogit.go
@@ -8,33 +8,14 @@ package pipeline
import (
"bufio"
"bytes"
- "fmt"
"io"
"sort"
"strings"
"sync"
- "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
- 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
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
resultsMap := map[string]*LFSResult{}
@@ -137,11 +118,11 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
n += int64(count)
if bytes.Equal(binObjectID, objectID.RawValue()) {
result := LFSResult{
- Name: curPath + string(fname),
- SHA: curCommit.ID.String(),
- Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
- When: curCommit.Author.When,
- ParentIDs: curCommit.Parents,
+ Name: curPath + string(fname),
+ SHA: curCommit.ID.String(),
+ Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
+ When: curCommit.Author.When,
+ ParentHashes: curCommit.Parents,
}
resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
} 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 {
hasParent := false
- for _, parentID := range result.ParentIDs {
+ for _, parentID := range result.ParentHashes {
if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent {
break
}
@@ -240,7 +221,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
select {
case err, has := <-errChan:
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:
}
diff --git a/modules/git/ref.go b/modules/git/ref.go
index ed801f20d5..2db630e2ea 100644
--- a/modules/git/ref.go
+++ b/modules/git/ref.go
@@ -184,7 +184,7 @@ func (ref RefName) RefGroup() string {
}
// 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
func (ref RefName) RefType() string {
var refType string
diff --git a/modules/process/manager.go b/modules/process/manager.go
index 9c21f62152..bdc4931810 100644
--- a/modules/process/manager.go
+++ b/modules/process/manager.go
@@ -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
// 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 {
// 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")
@@ -142,9 +142,9 @@ func (pm *Manager) AddContextTimeout(parent context.Context, timeout time.Durati
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
diff --git a/modules/session/mock.go b/modules/session/mock.go
new file mode 100644
index 0000000000..95231a3655
--- /dev/null
+++ b/modules/session/mock.go
@@ -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)}
+}
diff --git a/modules/session/store.go b/modules/session/store.go
index 70988fcdc5..09d1ef44dd 100644
--- a/modules/session/store.go
+++ b/modules/session/store.go
@@ -6,6 +6,8 @@ package session
import (
"net/http"
+ "code.gitea.io/gitea/modules/setting"
+
"gitea.com/go-chi/session"
)
@@ -14,6 +16,10 @@ type Store interface {
Get(any) any
Set(any, 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
@@ -21,8 +27,21 @@ func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, erro
for _, f := range BeforeRegenerateSession {
f(resp, req)
}
- s, err := session.RegenerateSession(resp, req)
- return s, err
+ if setting.IsInTesting {
+ 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.
diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go
index 34e1a336dc..e59f54420b 100644
--- a/modules/setting/oauth2.go
+++ b/modules/setting/oauth2.go
@@ -16,14 +16,10 @@ import (
type OAuth2UsernameType string
const (
- // OAuth2UsernameUserid oauth2 userid field will be used as gitea name
- OAuth2UsernameUserid OAuth2UsernameType = "userid"
- // OAuth2UsernameNickname oauth2 nickname field will be used as gitea name
- OAuth2UsernameNickname OAuth2UsernameType = "nickname"
- // 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"
+ OAuth2UsernameUserid OAuth2UsernameType = "userid" // use user id (sub) field as gitea's username
+ OAuth2UsernameNickname OAuth2UsernameType = "nickname" // use nickname field
+ OAuth2UsernameEmail OAuth2UsernameType = "email" // use email field
+ OAuth2UsernamePreferredUsername OAuth2UsernameType = "preferred_username" // use preferred_username field
)
func (username OAuth2UsernameType) isValid() bool {
@@ -71,8 +67,8 @@ func loadOAuth2ClientFrom(rootCfg ConfigProvider) {
OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool()
OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname)))
if !OAuth2Client.Username.isValid() {
- log.Warn("Username setting is not valid: '%s', will fallback to '%s'", 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.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingLogin)))
diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go
index 64f29d033e..0cefb7a6b2 100644
--- a/modules/templates/helper_test.go
+++ b/modules/templates/helper_test.go
@@ -49,9 +49,9 @@ func TestSubjectBodySeparator(t *testing.T) {
test("Multiple\n---\n-------\n---\nSeparators",
"Multiple\n",
"\n-------\n---\nSeparators")
- test("Insuficient\n--\nSeparators",
+ test("Insufficient\n--\nSeparators",
"",
- "Insuficient\n--\nSeparators")
+ "Insufficient\n--\nSeparators")
}
func TestJSEscapeSafe(t *testing.T) {
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 4f17b1a6db..fb591be393 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -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.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_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_title = Connect to an existing account
openid_connect_desc = The chosen OpenID URI is unknown. Associate it with a new account here.
diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini
index 64798d6d65..c711c72045 100644
--- a/options/locale/locale_pt-PT.ini
+++ b/options/locale/locale_pt-PT.ini
@@ -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.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_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_title=Estabelecer ligação a uma conta existente
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
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_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
activated=Em uso
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_yes=Pode enviar
settings.protected_branch_can_push_no=Não pode enviar
-settings.branch_protection=Salvaguarda do ramo '%s'
+settings.branch_protection=Regras de salvaguarda do ramo '%s'
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_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_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 github.com/gobwas/glob para ver a sintaxe. Exemplos: .drone.yml
, /docs/**/*.txt
.
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 github.com/gobwas/glob para ver a sintaxe. Exemplos: .drone.yml
, /docs/**/*.txt
.
+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 github.com/gobwas/glob para ver a sintaxe. Exemplos: .drone.yml
, /docs/**/*.txt
.
settings.add_protected_branch=Habilitar salvaguarda
settings.delete_protected_branch=Desabilitar salvaguarda
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.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.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.no_protected_branch=Não existem ramos protegidos.
settings.edit_protected_branch=Editar
@@ -2786,7 +2789,7 @@ self_check=Auto-verificação
identity_access=Identidade e acesso
users=Contas de utilizador
organizations=Organizações
-assets=Recursos de código
+assets=Recursos do código-fonte
repositories=Repositórios
hooks=Automatismos web
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_obtained=Estruturas MCache obtidas
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.next_gc_recycle=Próxima reciclagem da 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_pause=Pausa total 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.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
@@ -3023,7 +3026,7 @@ auths.attribute_surname=Atributo do Sobrenome
auths.attribute_mail=Atributo do email
auths.attribute_ssh_public_key=Atributo da chave pública SSH
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.use_paged_search=Usar pesquisa paginada
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.provider_config=Configuração do fornecedor
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.https_only=Apenas HTTPS
config.cookie_life_time=Tempo de vida do cookie
diff --git a/package-lock.json b/package-lock.json
index 61d86f6b7c..780689a0a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,7 +28,7 @@
"esbuild-loader": "4.1.0",
"escape-goat": "4.0.0",
"fast-glob": "3.3.2",
- "htmx.org": "1.9.11",
+ "htmx.org": "1.9.12",
"idiomorph": "0.3.0",
"jquery": "3.7.1",
"katex": "0.16.10",
@@ -6728,9 +6728,9 @@
}
},
"node_modules/htmx.org": {
- "version": "1.9.11",
- "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.11.tgz",
- "integrity": "sha512-WlVuICn8dfNOOgYmdYzYG8zSnP3++AdHkMHooQAzGZObWpVXYathpz/I37ycF4zikR6YduzfCvEcxk20JkIUsw=="
+ "version": "1.9.12",
+ "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz",
+ "integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw=="
},
"node_modules/human-signals": {
"version": "5.0.0",
diff --git a/package.json b/package.json
index ff1ae4d49e..b0cb67ed4a 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
"esbuild-loader": "4.1.0",
"escape-goat": "4.0.0",
"fast-glob": "3.3.2",
- "htmx.org": "1.9.11",
+ "htmx.org": "1.9.12",
"idiomorph": "0.3.0",
"jquery": "3.7.1",
"katex": "0.16.10",
diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go
index 8198abb8a0..3e717b8d8f 100644
--- a/routers/api/actions/artifacts.go
+++ b/routers/api/actions/artifacts.go
@@ -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.
func (ar artifactRoutes) comfirmUploadArtifact(ctx *ArtifactContext) {
_, runID, ok := validateRunID(ctx)
diff --git a/routers/api/actions/runner/interceptor.go b/routers/api/actions/runner/interceptor.go
index c2f4ade174..0e99f3deda 100644
--- a/routers/api/actions/runner/interceptor.go
+++ b/routers/api/actions/runner/interceptor.go
@@ -36,7 +36,7 @@ var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unar
uuid := request.Header().Get(uuidHeaderKey)
token := request.Header().Get(tokenHeaderKey)
// 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)
runner, err := actions_model.GetRunnerByUUID(ctx, uuid)
@@ -53,7 +53,7 @@ var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unar
cols := []string{"last_online"}
// 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)
if !util.IsEmptyString(version) && runner.Version != version {
runner.Version = version
diff --git a/routers/api/packages/README.md b/routers/api/packages/README.md
index 533a0d32f0..74d14922cb 100644
--- a/routers/api/packages/README.md
+++ b/routers/api/packages/README.md
@@ -19,7 +19,7 @@ The package registry code is divided into multiple modules to split the function
## Models
-Every package registry implementation uses the same underlaying models:
+Every package registry implementation uses the same underlying models:
| Model | Description |
| - | - |
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 5358906f27..73071aa8df 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -93,6 +93,7 @@ import (
"code.gitea.io/gitea/routers/api/v1/settings"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
@@ -835,6 +836,34 @@ func Routes() *web.Route {
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() {
// Miscellaneous (no scope required)
if setting.API.EnableSwagger {
@@ -1073,26 +1102,11 @@ func Routes() *web.Route {
m.Post("/accept", repo.AcceptTransfer)
m.Post("/reject", repo.RejectTransfer)
}, reqToken())
- m.Group("/actions", func() {
- m.Group("/secrets", func() {
- m.Combo("/{secretname}").
- Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret).
- 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)
- })
- })
+ addActionsRoutes(
+ m,
+ reqOwner(),
+ repo.NewAction(),
+ )
m.Group("/hooks/git", func() {
m.Combo("").Get(repo.ListGitHooks)
m.Group("/{id}", func() {
@@ -1460,27 +1474,11 @@ func Routes() *web.Route {
m.Combo("/{username}").Get(reqToken(), org.IsMember).
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
})
- m.Group("/actions", func() {
- m.Group("/secrets", func() {
- m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
- m.Combo("/{secretname}").
- 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)
- })
- })
+ addActionsRoutes(
+ m,
+ reqOrgOwnership(),
+ org.NewAction(),
+ )
m.Group("/public_members", func() {
m.Get("", org.ListPublicMembers)
m.Combo("/{username}").Get(org.IsPublicMember).
diff --git a/routers/api/v1/org/variables.go b/routers/api/v1/org/action.go
similarity index 58%
rename from routers/api/v1/org/variables.go
rename to routers/api/v1/org/action.go
index eaf7bdc45b..03a1fa8ccc 100644
--- a/routers/api/v1/org/variables.go
+++ b/routers/api/v1/org/action.go
@@ -9,16 +9,188 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
"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/shared"
"code.gitea.io/gitea/routers/api/v1/utils"
actions_service "code.gitea.io/gitea/services/actions"
"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
-func ListVariables(ctx *context.APIContext) {
+func (Action) ListVariables(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
// ---
// summary: Get an org-level variables list
@@ -70,7 +242,7 @@ func ListVariables(ctx *context.APIContext) {
}
// 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
// ---
// summary: Get an org-level variable
@@ -119,7 +291,7 @@ func GetVariable(ctx *context.APIContext) {
}
// 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
// ---
// summary: Delete an org-level variable
@@ -163,7 +335,7 @@ func DeleteVariable(ctx *context.APIContext) {
}
// 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
// ---
// summary: Create an org-level variable
@@ -227,7 +399,7 @@ func CreateVariable(ctx *context.APIContext) {
}
// 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
// ---
// summary: Update an org-level variable
@@ -289,3 +461,13 @@ func UpdateVariable(ctx *context.APIContext) {
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{}
+}
diff --git a/routers/api/v1/org/runners.go b/routers/api/v1/org/runners.go
deleted file mode 100644
index 2a52bd8778..0000000000
--- a/routers/api/v1/org/runners.go
+++ /dev/null
@@ -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)
-}
diff --git a/routers/api/v1/org/secrets.go b/routers/api/v1/org/secrets.go
deleted file mode 100644
index abb6bb26c4..0000000000
--- a/routers/api/v1/org/secrets.go
+++ /dev/null
@@ -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)
-}
diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go
index 03321d956d..311cfca6e9 100644
--- a/routers/api/v1/repo/action.go
+++ b/routers/api/v1/repo/action.go
@@ -9,17 +9,76 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
"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/shared"
"code.gitea.io/gitea/routers/api/v1/utils"
actions_service "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/context"
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
-func CreateOrUpdateSecret(ctx *context.APIContext) {
+func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/actions/secrets/{secretname} repository updateRepoSecret
// ---
// 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
-func DeleteSecret(ctx *context.APIContext) {
+func (Action) DeleteSecret(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/actions/secrets/{secretname} repository deleteRepoSecret
// ---
// summary: Delete a secret in a repository
@@ -133,7 +192,7 @@ func DeleteSecret(ctx *context.APIContext) {
}
// 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
// ---
// summary: Get a repo-level variable
@@ -186,7 +245,7 @@ func GetVariable(ctx *context.APIContext) {
}
// 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
// ---
// summary: Delete a repo-level variable
@@ -235,7 +294,7 @@ func DeleteVariable(ctx *context.APIContext) {
}
// 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
// ---
// summary: Create a repo-level variable
@@ -302,7 +361,7 @@ func CreateVariable(ctx *context.APIContext) {
}
// 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
// ---
// summary: Update a repo-level variable
@@ -369,7 +428,7 @@ func UpdateVariable(ctx *context.APIContext) {
}
// 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
// ---
// summary: Get repo-level variables list
@@ -423,3 +482,38 @@ func ListVariables(ctx *context.APIContext) {
ctx.SetTotalCountHeader(count)
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{}
+}
diff --git a/routers/api/v1/repo/runners.go b/routers/api/v1/repo/runners.go
deleted file mode 100644
index fe133b311d..0000000000
--- a/routers/api/v1/repo/runners.go
+++ /dev/null
@@ -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)
-}
diff --git a/routers/api/v1/shared/runners.go b/routers/api/v1/shared/runners.go
index c850ad7866..f088e9a2d4 100644
--- a/routers/api/v1/shared/runners.go
+++ b/routers/api/v1/shared/runners.go
@@ -12,7 +12,7 @@ import (
"code.gitea.io/gitea/services/context"
)
-// RegistrationToken is response related to registeration token
+// RegistrationToken is response related to registration token
// swagger:response RegistrationToken
type RegistrationToken struct {
Token string `json:"token"`
diff --git a/routers/init.go b/routers/init.go
index aaf95920c2..030ef3c740 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -5,6 +5,7 @@ package routers
import (
"context"
+ "net/http"
"reflect"
"runtime"
@@ -25,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/modules/web/routing"
actions_router "code.gitea.io/gitea/routers/api/actions"
packages_router "code.gitea.io/gitea/routers/api/packages"
apiv1 "code.gitea.io/gitea/routers/api/v1"
@@ -202,5 +204,9 @@ func NormalRoutes() *web.Route {
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
}
diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go
index caab6b4c81..f35eb77d42 100644
--- a/routers/private/hook_pre_receive.go
+++ b/routers/private/hook_pre_receive.go
@@ -359,7 +359,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
})
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{
Err: fmt.Sprintf("Unable to get status of pull request %d. Error: %v", ctx.opts.PullRequestID, err),
})
diff --git a/routers/web/admin/orgs.go b/routers/web/admin/orgs.go
index c5454db71e..cea28f8220 100644
--- a/routers/web/admin/orgs.go
+++ b/routers/web/admin/orgs.go
@@ -30,7 +30,7 @@ func Organizations(ctx *context.Context) {
explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
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{
PageSize: setting.UI.Admin.OrgPagingNum,
},
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index ea9d6f4c9c..d2330d5fa1 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -81,7 +81,7 @@ func Users(ctx *context.Context) {
IsRestricted: util.OptionalBoolParse(statusFilterMap["is_restricted"]),
IsTwoFactorEnabled: util.OptionalBoolParse(statusFilterMap["is_2fa_enabled"]),
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,
}, tplUsers)
}
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index 9ef32ebdb1..7c873796fe 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -382,17 +382,17 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
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 {
case setting.OAuth2UsernameEmail:
- return user_model.NormalizeUserName(strings.Split(gothUser.Email, "@")[0])
+ return user_model.NormalizeUserName(gothUser.Email)
case setting.OAuth2UsernamePreferredUsername:
- preferredUsername, exists := gothUser.RawData["preferred_username"]
- if exists {
- 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)
+ if preferredUsername, ok := gothUser.RawData["preferred_username"].(string); ok {
+ return user_model.NormalizeUserName(preferredUsername)
}
+ return "", nil
case setting.OAuth2UsernameNickname:
return user_model.NormalizeUserName(gothUser.NickName)
default: // OAuth2UsernameUserid
diff --git a/routers/web/auth/auth_test.go b/routers/web/auth/auth_test.go
index c6afbf877c..45525a5c6f 100644
--- a/routers/web/auth/auth_test.go
+++ b/routers/web/auth/auth_test.go
@@ -8,12 +8,31 @@ import (
"net/url"
"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/util"
+ "code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/contexttest"
+ "github.com/markbates/goth"
+ "github.com/markbates/goth/gothic"
"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) {
ctx, resp := contexttest.MockContext(t, "/user/login")
SignIn(ctx)
@@ -41,3 +60,24 @@ func TestUserLogin(t *testing.T) {
SignIn(ctx)
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"])
+}
diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go
index f744a57a43..24130df634 100644
--- a/routers/web/auth/linkaccount.go
+++ b/routers/web/auth/linkaccount.go
@@ -48,23 +48,27 @@ func LinkAccount(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
- gothUser := ctx.Session.Get("linkAccountGothUser")
- if gothUser == nil {
- ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
+ gothUser, ok := ctx.Session.Get("linkAccountGothUser").(goth.User)
+ if !ok {
+ // 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
}
- gu, _ := gothUser.(goth.User)
- uname, err := getUserName(&gu)
+ if missingFields, ok := gothUser.RawData["__giteaAutoRegMissingFields"].([]string); ok {
+ ctx.Data["AutoRegistrationFailedPrompt"] = ctx.Tr("auth.oauth_callback_unable_auto_reg", gothUser.Provider, strings.Join(missingFields, ","))
+ }
+
+ uname, err := extractUserNameFromOAuth2(&gothUser)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
- email := gu.Email
+ email := gothUser.Email
ctx.Data["user_name"] = uname
ctx.Data["email"] = email
- if len(email) != 0 {
+ if email != "" {
u, err := user_model.GetUserByEmail(ctx, email)
if err != nil && !user_model.IsErrUserNotExist(err) {
ctx.ServerError("UserSignIn", err)
@@ -73,7 +77,7 @@ func LinkAccount(ctx *context.Context) {
if u != nil {
ctx.Data["user_exists"] = true
}
- } else if len(uname) != 0 {
+ } else if uname != "" {
u, err := user_model.GetUserByName(ctx, uname)
if err != nil && !user_model.IsErrUserNotExist(err) {
ctx.ServerError("UserSignIn", err)
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index 3189d1372e..c9cb7859cd 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -934,7 +934,7 @@ func SignInOAuthCallback(ctx *context.Context) {
if u == 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)
if err != nil {
ctx.ServerError("UserLinkAccount", err)
@@ -952,23 +952,32 @@ func SignInOAuthCallback(ctx *context.Context) {
if gothUser.Email == "" {
missingFields = append(missingFields, "email")
}
- if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname && gothUser.NickName == "" {
- 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)
+ uname, err := extractUserNameFromOAuth2(&gothUser)
if err != nil {
ctx.ServerError("UserSignIn", err)
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{
Name: uname,
FullName: gothUser.Name,
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index db2b11a7ed..3909a64be6 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -67,6 +67,9 @@ type ViewResponse struct {
CanRerun bool `json:"canRerun"`
CanDeleteArtifact bool `json:"canDeleteArtifact"`
Done bool `json:"done"`
+ WorkflowID string `json:"workflowID"`
+ WorkflowLink string `json:"workflowLink"`
+ IsSchedule bool `json:"isSchedule"`
Jobs []*ViewJob `json:"jobs"`
Commit ViewCommit `json:"commit"`
} `json:"run"`
@@ -90,12 +93,10 @@ type ViewJob struct {
}
type ViewCommit struct {
- LocaleCommit string `json:"localeCommit"`
- LocalePushedBy string `json:"localePushedBy"`
- ShortSha string `json:"shortSHA"`
- Link string `json:"link"`
- Pusher ViewUser `json:"pusher"`
- Branch ViewBranch `json:"branch"`
+ ShortSha string `json:"shortSHA"`
+ Link string `json:"link"`
+ Pusher ViewUser `json:"pusher"`
+ Branch ViewBranch `json:"branch"`
}
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.CanDeleteArtifact = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
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.Status = run.Status.String()
for _, v := range jobs {
@@ -172,12 +176,10 @@ func ViewPost(ctx *context_module.Context) {
Link: run.RefLink(),
}
resp.State.Run.Commit = ViewCommit{
- LocaleCommit: ctx.Locale.TrString("actions.runs.commit"),
- LocalePushedBy: ctx.Locale.TrString("actions.runs.pushed_by"),
- ShortSha: base.ShortSha(run.CommitSHA),
- Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
- Pusher: pusher,
- Branch: branch,
+ ShortSha: base.ShortSha(run.CommitSHA),
+ Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
+ Pusher: pusher,
+ Branch: branch,
}
var task *actions_model.ActionTask
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index 8543fa44cc..a2c6ac33e8 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -212,8 +212,6 @@ func SearchCommits(ctx *context.Context) {
// FileHistory show a file's reversions
func FileHistory(ctx *context.Context) {
- ctx.Data["IsRepoToolbarCommits"] = true
-
fileName := ctx.Repo.TreePath
if len(fileName) == 0 {
Commits(ctx)
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index 035a92f228..8c0fee71a0 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -800,7 +800,6 @@ func CompareDiff(ctx *context.Context) {
}
ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + separator + base.ShortSha(afterCommitID)
- ctx.Data["IsRepoToolbarCommits"] = true
ctx.Data["IsDiffCompare"] = true
_, 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.
// In that case the commit message will be prepend to the template body.
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
// one empty line between them.
ctx.Data[pullRequestTemplateKey] = content + "\n\n" + templateContent
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 95f0cf3d71..de6ef9e93b 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -1760,8 +1760,8 @@ func ViewIssue(ctx *context.Context) {
// drop error since times could be pruned from DB..
_ = comment.LoadTime(ctx)
if comment.Content != "" {
- // Content before v1.21 did store the formated string instead of seconds,
- // so "|" is used as delimeter to mark the new format
+ // Content before v1.21 did store the formatted string instead of seconds,
+ // so "|" is used as delimiter to mark the new format
if comment.Content[0] != '|' {
// handle old time comments that have formatted text stored
comment.RenderedContent = templates.SanitizeHTML(comment.Content)
@@ -3149,13 +3149,10 @@ func UpdateCommentContent(ctx *context.Context) {
}
oldContent := comment.Content
- comment.Content = ctx.FormString("content")
- if len(comment.Content) == 0 {
- ctx.JSON(http.StatusOK, map[string]any{
- "content": "",
- })
- return
- }
+ newContent := ctx.FormString("content")
+
+ // allow to save empty content
+ comment.Content = newContent
if err = issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
@@ -3178,21 +3175,27 @@ func UpdateCommentContent(ctx *context.Context) {
}
}
- content, err := markdown.RenderString(&markup.RenderContext{
- Links: markup.Links{
- Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
- },
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
- }, comment.Content)
- if err != nil {
- ctx.ServerError("RenderString", err)
- return
+ var renderedContent template.HTML
+ if comment.Content != "" {
+ renderedContent, err = markdown.RenderString(&markup.RenderContext{
+ Links: markup.Links{
+ Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
+ }, comment.Content)
+ if err != nil {
+ ctx.ServerError("RenderString", err)
+ return
+ }
+ } else {
+ contentEmpty := fmt.Sprintf(`%s`, ctx.Tr("repo.issues.no_content"))
+ renderedContent = template.HTML(contentEmpty)
}
ctx.JSON(http.StatusOK, map[string]any{
- "content": content,
+ "content": renderedContent,
"attachments": attachmentsHTML(ctx, comment.Attachments, comment.Content),
})
}
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 71f25db11b..7f131f2e98 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -863,21 +863,21 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
if pull.HeadRepo != nil {
ctx.Data["SourcePath"] = pull.HeadRepo.Link() + "/src/branch/" + util.PathEscapeSegments(pull.HeadBranch)
- }
- if !pull.HasMerged && ctx.Doer != nil {
- perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
- return
- }
+ if !pull.HasMerged && ctx.Doer != nil {
+ perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
+ if err != nil {
+ ctx.ServerError("GetUserRepoPermission", err)
+ return
+ }
- if perm.CanWrite(unit.TypeCode) || issues_model.CanMaintainerWriteToBranch(ctx, perm, pull.HeadBranch, ctx.Doer) {
- ctx.Data["CanEditFile"] = true
- ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
- ctx.Data["HeadRepoLink"] = pull.HeadRepo.Link()
- ctx.Data["HeadBranchName"] = pull.HeadBranch
- ctx.Data["BackToLink"] = setting.AppSubURL + ctx.Req.URL.RequestURI()
+ if perm.CanWrite(unit.TypeCode) || issues_model.CanMaintainerWriteToBranch(ctx, perm, pull.HeadBranch, ctx.Doer) {
+ ctx.Data["CanEditFile"] = true
+ ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
+ ctx.Data["HeadRepoLink"] = pull.HeadRepo.Link()
+ ctx.Data["HeadBranchName"] = pull.HeadBranch
+ ctx.Data["BackToLink"] = setting.AppSubURL + ctx.Req.URL.RequestURI()
+ }
}
}
}
@@ -1225,7 +1225,6 @@ func CompareAndPullRequestPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
ctx.Data["PageIsComparePull"] = true
ctx.Data["IsDiffCompare"] = true
- ctx.Data["IsRepoToolbarCommits"] = true
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")
diff --git a/routers/web/web.go b/routers/web/web.go
index c6132f0d61..9a6687059b 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -1612,7 +1612,7 @@ func registerRoutes(m *web.Route) {
m.NotFound(func(w http.ResponseWriter, req *http.Request) {
ctx := context.GetWebContext(req)
- routing.UpdateFuncInfo(ctx, routing.GetFuncInfo(ctx.NotFound, "GlobalNotFound"))
+ routing.UpdateFuncInfo(ctx, routing.GetFuncInfo(ctx.NotFound, "WebNotFound"))
ctx.NotFound("", nil)
})
}
diff --git a/services/actions/interface.go b/services/actions/interface.go
new file mode 100644
index 0000000000..d4fa782fec
--- /dev/null
+++ b/services/actions/interface.go
@@ -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)
+}
diff --git a/services/context/context.go b/services/context/context.go
index 88ab5cae0e..aab0485f1a 100644
--- a/services/context/context.go
+++ b/services/context/context.go
@@ -20,14 +20,13 @@ import (
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
+ "code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
web_types "code.gitea.io/gitea/modules/web/types"
-
- "gitea.com/go-chi/session"
)
// 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) {
base, baseCleanUp := NewBaseContext(resp, req)
defer baseCleanUp()
- ctx := NewWebContext(base, rnd, session.GetSession(req))
+ ctx := NewWebContext(base, rnd, session.GetContextSession(req))
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
diff --git a/services/contexttest/context_tests.go b/services/contexttest/context_tests.go
index 3064c56590..0c1e5ee54f 100644
--- a/services/contexttest/context_tests.go
+++ b/services/contexttest/context_tests.go
@@ -19,7 +19,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
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/session"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/web/middleware"
@@ -43,7 +45,8 @@ func mockRequest(t *testing.T, reqPath string) *http.Request {
}
type MockContextOption struct {
- Render context.Render
+ Render context.Render
+ SessionStore *session.MockStore
}
// 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.Locale = &translation.MockLocale{}
+ chiCtx := chi.NewRouteContext()
ctx := context.NewWebContext(base, opt.Render, nil)
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.Data["PageStartTime"] = time.Now()
- chiCtx := chi.NewRouteContext()
- ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
return ctx, resp
}
diff --git a/services/convert/issue_comment.go b/services/convert/issue_comment.go
index 9ffaf1e84c..9ec9ac7684 100644
--- a/services/convert/issue_comment.go
+++ b/services/convert/issue_comment.go
@@ -72,8 +72,8 @@ func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issu
c.Type == issues_model.CommentTypeStopTracking ||
c.Type == issues_model.CommentTypeDeleteTimeManual) &&
c.Content[0] == '|' {
- // TimeTracking Comments from v1.21 on store the seconds instead of an formated string
- // so we check for the "|" delimeter and convert new to legacy format on demand
+ // TimeTracking Comments from v1.21 on store the seconds instead of an formatted string
+ // so we check for the "|" delimiter and convert new to legacy format on demand
c.Content = util.SecToTime(c.Content[1:])
}
}
diff --git a/services/issue/assignee.go b/services/issue/assignee.go
index 8740a6664a..a0aa5a339b 100644
--- a/services/issue/assignee.go
+++ b/services/issue/assignee.go
@@ -229,12 +229,12 @@ func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *use
return comment, teamReviewRequestNotify(ctx, issue, doer, reviewer, isAdd, comment)
}
-func ReviewRequestNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewNotifers []*ReviewRequestNotifier) {
- for _, reviewNotifer := range reviewNotifers {
- if reviewNotifer.Reviwer != nil {
- notify_service.PullRequestReviewRequest(ctx, issue.Poster, issue, reviewNotifer.Reviwer, reviewNotifer.IsAdd, reviewNotifer.Comment)
- } else if reviewNotifer.ReviewTeam != nil {
- if err := teamReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifer.ReviewTeam, reviewNotifer.IsAdd, reviewNotifer.Comment); err != nil {
+func ReviewRequestNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewNotifiers []*ReviewRequestNotifier) {
+ for _, reviewNotifier := range reviewNotifiers {
+ if reviewNotifier.Reviewer != nil {
+ notify_service.PullRequestReviewRequest(ctx, issue.Poster, issue, reviewNotifier.Reviewer, reviewNotifier.IsAdd, reviewNotifier.Comment)
+ } else if reviewNotifier.ReviewTeam != nil {
+ if err := teamReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifier.ReviewTeam, reviewNotifier.IsAdd, reviewNotifier.Comment); err != nil {
log.Error("teamReviewRequestNotify: %v", err)
}
}
diff --git a/services/issue/issue.go b/services/issue/issue.go
index c7fa9f3300..b0e50f2b89 100644
--- a/services/issue/issue.go
+++ b/services/issue/issue.go
@@ -90,17 +90,17 @@ func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_mode
return err
}
- var reviewNotifers []*ReviewRequestNotifier
+ var reviewNotifiers []*ReviewRequestNotifier
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issues_model.HasWorkInProgressPrefix(title) {
var err error
- reviewNotifers, err = PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest)
+ reviewNotifiers, err = PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest)
if err != nil {
log.Error("PullRequestCodeOwnersReview: %v", err)
}
}
notify_service.IssueChangeTitle(ctx, doer, issue, oldTitle)
- ReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifers)
+ ReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifiers)
return nil
}
diff --git a/services/issue/pull.go b/services/issue/pull.go
index 4a0009e82f..896802108d 100644
--- a/services/issue/pull.go
+++ b/services/issue/pull.go
@@ -36,7 +36,7 @@ func getMergeBase(repo *git.Repository, pr *issues_model.PullRequest, baseBranch
type ReviewRequestNotifier struct {
Comment *issues_model.Comment
IsAdd bool
- Reviwer *user_model.User
+ Reviewer *user_model.User
ReviewTeam *org_model.Team
}
@@ -124,9 +124,9 @@ func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue,
return nil, err
}
notifiers = append(notifiers, &ReviewRequestNotifier{
- Comment: comment,
- IsAdd: true,
- Reviwer: u,
+ Comment: comment,
+ IsAdd: true,
+ Reviewer: u,
})
}
}
diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go
index 72e545581a..0270f87039 100644
--- a/services/mirror/mirror.go
+++ b/services/mirror/mirror.go
@@ -40,7 +40,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
}
log.Trace("Doing: Update")
- handler := func(idx int, bean any) error {
+ handler := func(bean any) error {
var repo *repo_model.Repository
var mirrorType SyncType
var referenceID int64
@@ -91,7 +91,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
pullMirrorsRequested := 0
if pullLimit != 0 {
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
}
pullMirrorsRequested++
@@ -105,7 +105,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
pushMirrorsRequested := 0
if pushLimit != 0 {
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
}
pushMirrorsRequested++
diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go
index f5eaeaf091..9f7ffb29c9 100644
--- a/services/mirror/mirror_pull.go
+++ b/services/mirror/mirror_pull.go
@@ -466,7 +466,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results))
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)
return false
}
@@ -564,7 +564,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
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 {
return true
}
diff --git a/services/org/org.go b/services/org/org.go
index dca7794b47..c19572a123 100644
--- a/services/org/org.go
+++ b/services/org/org.go
@@ -20,11 +20,11 @@ import (
// DeleteOrganization completely and permanently deletes everything of organization.
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 {
return err
}
- defer commiter.Close()
+ defer committer.Close()
if purge {
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)
}
- if err := commiter.Commit(); err != nil {
+ if err := committer.Commit(); err != nil {
return err
}
diff --git a/services/pull/check.go b/services/pull/check.go
index f4dd332b14..9495e8ad5f 100644
--- a/services/pull/check.go
+++ b/services/pull/check.go
@@ -66,7 +66,7 @@ const (
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 {
return db.WithTx(stdCtx, func(ctx context.Context) error {
if pr.HasMerged {
diff --git a/services/pull/comment.go b/services/pull/comment.go
index d538b118d5..53587d4f54 100644
--- a/services/pull/comment.go
+++ b/services/pull/comment.go
@@ -46,7 +46,7 @@ func getCommitIDsFromRepo(ctx context.Context, repo *repo_model.Repository, oldC
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)
if err != nil {
return nil, false, err
diff --git a/services/pull/pull.go b/services/pull/pull.go
index 764be5c6e3..5c0ea42d77 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -77,7 +77,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
}
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 := issues_model.NewPullRequest(ctx, repo, issue, labelIDs, uuids, pr); err != nil {
return err
@@ -137,7 +137,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
}
if !pr.IsWorkInProgress(ctx) {
- reviewNotifers, err = issue_service.PullRequestCodeOwnersReview(ctx, issue, pr)
+ reviewNotifiers, err = issue_service.PullRequestCodeOwnersReview(ctx, issue, pr)
if err != nil {
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
- 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)
if err != nil {
diff --git a/services/repository/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go
index 8a62a603d4..444ae04d0c 100644
--- a/services/repository/commitstatus/commitstatus.go
+++ b/services/repository/commitstatus/commitstatus.go
@@ -38,12 +38,10 @@ func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheVal
if ok && statusStr != "" {
var cv commitStatusCacheValue
err := json.Unmarshal([]byte(statusStr), &cv)
- if err == nil && cv.State != "" {
+ if err == nil {
return &cv
}
- if err != nil {
- log.Warn("getCommitStatusCache: json.Unmarshal failed: %v", err)
- }
+ log.Warn("getCommitStatusCache: json.Unmarshal failed: %v", err)
}
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
func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
results := make([]*git_model.CommitStatus, len(repos))
+ allCached := true
for i, repo := range repos {
if cv := getCommitStatusCache(repo.ID, repo.DefaultBranch); cv != nil {
results[i] = &git_model.CommitStatus{
State: api.CommitStatusState(cv.State),
TargetURL: cv.TargetURL,
}
+ } else {
+ allCached = false
}
}
+ if allCached {
+ return results, nil
+ }
+
// 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
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 {
if repo.ID == summary.RepoID {
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
})
- if results[i].State != "" {
+ if results[i] != 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)
}
@@ -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
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 {
if results[i] == nil {
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 {
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
}
diff --git a/templates/repo/actions/runs_list.tmpl b/templates/repo/actions/runs_list.tmpl
index 20330b5d62..09a25ce8bd 100644
--- a/templates/repo/actions/runs_list.tmpl
+++ b/templates/repo/actions/runs_list.tmpl
@@ -15,7 +15,7 @@
{{if .Title}}{{.Title}}{{else}}{{ctx.Locale.Tr "actions.runs.empty_commit_message"}}{{end}}
{{/*
*/}}{{end}}{{/*
*/}}
{{/*
*/}}{{end}}{{/*
@@ -46,7 +46,7 @@
{{end}}
{{else}}
{{range $k, $line := $.section.Lines}}
- {{$inlineDiff.Content}}
{{$inlineDiff.Content}}